Custom chooser activity: SecurityException UID n does not have permission to content:// uri
Asked Answered
H

1

6

I'm building a Chooser app that replaces the native Android Share dialog. It works fine except when I try to share an image from Chrome via longpress image > share image.

I found that Google+ doesn't catch the exception (it crashes) so I can have a look at it via Logcat:

  • Do an image search on Google.
  • Select an image (this should show the preview)
  • Longpress the image
  • Choose "Share image"
  • My chooser activity pops up
  • Select Google+
  • Google+ crashes with this error:

java.lang.SecurityException: UID 10130 does not have permission to content://com.android.chrome.FileProvider/images/screenshot/15307295588677864462883877407218.jpg [user 0]

My code (simplified):

@Override
public void onCreate() {
    handleIntent();
}

private void handleIntent() {

    // Get intent and payload
    mIntent = getIntent();
    mPayloadIntent = (Intent) mIntent.getParcelableExtra(Intent.EXTRA_INTENT);

    // Nullify some things for queryIntentActivities (or no results will be found)
    mPayloadIntent.setComponent(null);
    mPayloadIntent.setPackage(null);

    // Retrieve a list of targets we can send mPayloadIntent to..
    List<ResolveInfo> targets = context.getPackageManager().queryIntentActivities(mPayloadIntent, 0);
    // etc...

}

private void onClickTarget(ResolveInfo target) {

    // Prepare..
    ComponentName compName = new ComponentName(
                target.activityInfo.applicationInfo.packageName,
                target.activityInfo.name);

    // Build a 'new' shareIntent
    Intent shareIntent = new Intent(mPayloadIntent);
    shareIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
    shareIntent.setComponent(compName);

    // Start the targeted activity with the shareIntent
    startActivity(shareIntent);
    finish();

}

AndroidManifest.xml:

<activity
    android:name=".ActShareReplace"
    android:label="Sharedr"
    android:theme="@style/AppTheme.TransparentActivity"
    >
    <intent-filter>
        <action android:name="android.intent.action.CHOOSER" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

If I look at the documentation for Intent.ACTION_CHOOSER it says:

If you need to grant URI permissions through a chooser, you must specify the permissions to be granted on the ACTION_CHOOSER Intent in addition to the EXTRA_INTENT inside. This means using setClipData(ClipData) to specify the URIs to be granted as well as FLAG_GRANT_READ_URI_PERMISSION and/or FLAG_GRANT_WRITE_URI_PERMISSION as appropriate.

I'm not completely sure if this is something my app has to do or if it's the responsibility of the app that invoked the chooser activity - but I would assume it's the latter. My app can't set URI permissions for intents it's receiving, can it?

Anyway, if I inspect the extra's and flags on mIntent and mPayloadIntent I get:

mIntent only has extras, no flags (as far as I can tell):

android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER IntentSender{4fa3901: android.os.BinderProxy@3aec3a6} (android.content.IntentSender)

android.intent.extra.INTENT Intent { act=android.intent.action.SEND typ=image/jpeg flg=0x80001 clip={image/jpeg U:content://com.android.chrome.FileProvider/images/screenshot/15307316967108618905323381238187.jpg} (has extras) } (android.content.Intent)

android.intent.extra.TITLE Share via (java.lang.String)

mPayloadIntent:

android.intent.extra.STREAM content://com.android.chrome.FileProvider/images/screenshot/1530731945132897653908815339041.jpg (android.net.Uri$HierarchicalUri)

  • FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
  • FLAG_ACTIVITY_NEW_DOCUMENT
  • FLAG_GRANT_READ_URI_PERMISSION

So mPayloadIntent does have the FLAG_GRANT_READ_URI_PERMISSION but mIntent does not. According to the docs it should.

I've read that it's possible that my app consumed the URI permission so I tried caching the file myself but as soon as I try to access the URI through a ContentResolver, I get a - you guessed it - permission error.

I then realized that I probably shouldn't have to cache the file as Android's native Chooser Activity doesn't seem to do that either. So that's where I'm at now. Back to square one.

Is this a Chrome bug? An Android bug? Or am I doing something wrong?

I'd happily blame Chrome and file a bug report but someone who's working on a similar project (and ran into the same issue) told me Whatsapp has a similar problem. It, too, shares images via a content:// uri.

For completeness, I'm testing this on a Pixel 2016 with Android 8.1. I have no idea what the other guy (who ran into the same issue with WA) is using.

Humes answered 4/7, 2018 at 19:31 Comment(0)
V
4

Is this a Chrome bug? An Android bug? Or am I doing something wrong?

My guess is that it is a client-side bug, coming from people creating ACTION_CHOOSER Intent objects directly rather than through Intent.createChooser(). Intent.createChooser() looks like it is taking the flags from what you called mPayloadIntent and adds them to mIntent.

You should be able to test this yourself. Create a scrap app that creates an ACTION_SEND Intent with EXTRA_STREAM pointing to some piece of content (e.g., served by FileProvider). Then, try invoking your chooser three ways:

  1. Wrap the Intent via Intent.createChooser()

  2. Wrap the Intent via an ACTION_CHOOSER Intent, where you follow what the docs say and put the flags on both Intent objects

  3. Wrap the Intent via an ACTION_CHOOSER Intent, where you skip the flags on the ACTION_CHOOSER Intent

If I am correct, #1 and #2 will work, and #3 will fail with the same basic failure mode that you're seeing.

If my theory holds up so far, try running the three apps again, but this time use the system chooser. My guess is that the system chooser does get some special benefits from being part of the core OS and all three will work. Otherwise, the developers of Chrome and WhatsApp would have run into this problem in their testing and would have fixed it.

And, if all of this theory holds up... you're somewhat screwed. I would assume that more people use Intent.createChooser() than use ACTION_CHOOSER directly, as Intent.createChooser() is simpler. And, some subset of the people who use ACTION_CHOOSER might actually follow the documentation...

hahahahahahahahaha... gasp ...hahahahahahahahahaha!

...and for those, you're OK. And, some people using ACTION_CHOOSER might have a Uri in EXTRA_STREAM that is world-readable (which isn't a good idea, but it works in your favor here). It will only be for buggy clients that create ACTION_CHOOSER manually, fail to set the Intent flags properly, but do secure their content properly, that you won't be able to correctly process the Intent.

Vermiculite answered 4/7, 2018 at 20:7 Comment(3)
Thanks, I'll give that a go. Maybe I can look into Chrome's source code (it's OS), see how they put that intent together. I did find their (custom/extended) FileProvider implementation.Humes
Bah. It looks like #1, #2 and #3 all have the same issue. The receiving app crashes (or catches the error and just bails out). Also, it looks like Intent.createChooser() doesn't add the URI permission flags on the intent or the payload intent so I tried adding those but no cigar.Humes
Reading more through the code from Android's native ChooserActivity I found it uses the startActivityAsCaller() method to invoke the intent. It's declared in android/app/Activity.java and not part of the public API. "This is for the resolver and chooser activities which operate as intermediaries". I think I'm ****ed :(((Humes

© 2022 - 2024 — McMap. All rights reserved.