13

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.

Julian A.
  • 10,928
  • 16
  • 67
  • 107
  • Are you asking about uid=1000 - that's the Android system user id. It's likely that the request is internally being proxied to the system for processing. – adelphus Aug 10 '15 at 22:23
  • @adelphus Yes. I thought that was the cause of the security exception, but now I'm not sure because `UserHandle.isSameUser` returns `true`. – Julian A. Aug 10 '15 at 22:38
  • Android users are not related to App uid values. Don't mix them up! App uid values are used to enforce sandboxing between Apps, user security is implemented differently. – adelphus Aug 10 '15 at 22:45
  • Ah, ok. So the problem is why `UserHandle.isSameApp` returns false even though the thread is created by the app. – Julian A. Aug 10 '15 at 23:03
  • When you say `background thread`, do you mean `Thread` or `AsyncTask`, or something else ? – Henry Oct 29 '15 at 10:19

3 Answers3

5

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.

adelphus
  • 10,116
  • 5
  • 36
  • 46
  • This helps me understand how IDs work. Thank-you. Do you have a suggestion about how I might avoid the security exception without opening up the content provider to access by other apps? – Julian A. Aug 10 '15 at 23:49
  • The normal way to protect content providers is by defining and including custom permissions [like this](http://stackoverflow.com/questions/14793672/requesting-read-permission-from-my-own-contentprovider-in-another-app) . But I'm not entirely sure what you're trying to achieve, so perhaps this isn't what you need. – adelphus Aug 10 '15 at 23:57
  • That link seems useful for connecting one app to the content provider of another. But in my case I only have one app. And the content provider of this app seems to think a thread spawned by the app is coming from another app, and so throws a security exception. – Julian A. Aug 11 '15 at 00:39
  • @Julian sorry - the point of that link was to demonstrate that you always need to *use* the permission even in the manifest that declares the permission or you cannot access your own CP. But there's probably something else going on here. – adelphus Aug 11 '15 at 09:57
  • No worries. Thanks for trying :) – Julian A. Aug 11 '15 at 20:55
3

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).

Louis CAD
  • 10,965
  • 2
  • 39
  • 58
2

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.

Henry
  • 17,490
  • 7
  • 63
  • 98