问题
I have an app that, when notified by a ContentObserver
of a change to a ContentProvider
, attempts to query the provider on a background thread. This causes an SecurityException
to be thrown:
8-10 15:54:29.577 3057-3200/com.xxxx.mobile.android.xxx W/Binder﹕ Caught a RuntimeException from the binder stub implementation. java.lang.SecurityException: Permission Denial: reading com.xxx.mobile.android.mdk.model.customer.ContentProvider uri content://com.xxx.mobile.android.consumer.xxx/vehicle from pid=0, uid=1000 requires the provider be exported, or grantUriPermission() at android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:539) at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:452) at android.content.ContentProvider$Transport.query(ContentProvider.java:205) at android.content.ContentResolver.query(ContentResolver.java:478) at android.content.ContentResolver.query(ContentResolver.java:422)
How would a thread created by an app end up with a different UID from the app's ContentProvider?
By placing an exception breakpoint in android.content.ContentProvider
I see that UserHandle.isSameApp(uid, mMyUid)
is false
and UserHandle.isSameUser(uid, mMyUid)
is true
. I also see that the providers UID is 10087.
回答1:
I got the same problem while trying to interact with my ContentProvider in a system callback (LeScanCallback
). The problem is that the callback thread is owned by the Android system, and not by my app, even if the code is in my app.
Passing the work from the callback to one of my app threads before trying to interact with my ContentProvider solved the problem successfully.
To reduce boilerplate for thread creation and recycling (needed for frequent callbacks to reduce overhead), I used AndroidAnnotation's @Background annotation on my delegate method (but would use Kotlin Coroutines today).
回答2:
The uid value of 1000 belongs to the Android system. Many features of Android involve proxying requests to the system thread for processing. If an exception is thrown during this, the error will include the uid of the system, rather than the original requestor.
For the other points:
UserHandle.isSameApp(uid, mMyUid) is false
UserHandle.isSameUser(uid, mMyUid) is true
These are easiest to explain by looking at the source. On an Android device with multi-user support, each user is defined by a range of UIDs. isSameApp
is false because the modulus of the ids do not match:
public static final boolean isSameApp(int uid1, int uid2) {
return getAppId(uid1) == getAppId(uid2);
}
public static final int getAppId(int uid) {
return uid % PER_USER_RANGE;
}
Similarly, the two ids belong to the same user because they live in the same range:
public static final boolean isSameUser(int uid1, int uid2) {
return getUserId(uid1) == getUserId(uid2);
}
public static final int getUserId(int uid) {
if (MU_ENABLED) {
return uid / PER_USER_RANGE;
} else {
return 0;
}
}
Note that this logic is flawed because it means that all Android system uids (< 10000) will be assumed to "belong" to the first user.
Also note that if a second user installs more than 1000 apps(!), there's the possibility that an App will be mistaken for a system app (both uid % PER_USER_RANGE
will return 1000). It won't really matter though, because the strong sandboxing would prevent anything too bad from happening.
回答3:
If a Thread
is started by any component of the application that has the provider, then you can access the ContentProvider
without any SecurityException
.
I am using a ContentProvider
in my application just as an extra abstraction layer and I haven't exposed the content to other applications. I am accessing the ContentProvider
in background thread (Not AsyncTask
, but a simple java.lang.Thread
). I am not getting any SecurityException
. The following is the code from my application.
AndroidManifest.xml
<provider
android:authorities="com.sample.provider"
android:name="com.sample.MyProvider"
android:exported="false" />
MainActivity
public void performContinue(Bundle extras){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
String AUTHORITY = "com.sample.provider";
Uri BASE_URI = Uri.parse("content://" + AUTHORITY);
Uri currentUri = BASE_URI.buildUpon().appendPath("SAMPLE_COUNT").build();
final Cursor query = InputActivity.this.getContentResolver().query(currentUri, null, null, null, null);
if (query != null) {
final int count = query.getCount();
Log.d("DEBUG","CONTENT = " + count);
}else{
Log.d("DEBUG","CONTENT = CURSOR NULL");
}
}
});
thread.setName("THREAD_1");
thread.start();
}
I don't seem to get any SecurityException
. Ideally we need to be using AsyncQueryHandler
for accessing the ContentProvider
, because that allows you do all the fetching process in the background thread and will use the UI Thread to post the results in the UI. But after seeing this post, I just wanted to see if I could just use Thread
and check if I could still access it without any Exception. It works fine.
来源:https://stackoverflow.com/questions/31929908/how-would-a-thread-created-by-an-app-be-considered-a-different-app-from-the-app