Android Nesting Intents
Does your app provide a service that starts another app’s
Activity as a callback when some action occurs? For example, does it accept an
Intent as an extra parameter of another
Intent, which is used as a parameter of a
Did you know this can leave your app vulnerable?
In the rest of this post, I’ll explain the problems of using this approach, and provide a solution that allows your app to provide the same functionality more safely.
The way we would expect this type of interaction to work would be something like this:
Here, the Client App creates an
Intent for its
ClientCallbackActivity and adds it as an extra to the
Intent that it will use to start the Provider App’s
ApiService. After processing the request, the Provider App uses the
Intent that the Client App provided to start the
❗️The thing to take note of here is that the Provider App is calling
startActivity() within its own app
Context. This has two consequences, both of which are suboptimal:
ClientCallbackActivityis being started by Provider App, it must be marked as
exported, which allows not only Provider App to start it, but also any other apps on the device.
- The nested
Intentpassed to ApiService can be used to start any
Activitythat’s part of Provider App. This included private, potentially sensitive, non-exported activitys!
To demonstrate, consider what would happen if the calling app didn’t provide an
Intent for its own activity, i.e.:
ClientCallbackActivity, but instead put in an
Intent to start a private activity within the Provider App.
Because a nested
Intent is used, it’s difficult for Provider App to guard against
Intents that target private, potentially sensitive activities. Because Provider App is calling
startActivity() on the intent directly, it’s able to start
ApiSensitiveActivity even though it’s not exported.
The solution: PendingIntent
The solution is simple: instead of accepting an
Intent, Provider App could instead accept a
The difference between an
Intent and a
PendingIntent is that a
PendingIntent is always processed with the identity it was created with:
Because the callback is supplied as a
PendingIntent, when Provider App calls
send() on it,
startActivity() proceeds as if Attacker App had called it, and since the Attacker App doesn’t have privileges to start
ApiSensitiveActivity, the system prevents the activity from starting.
That’s certainly a benefit for Provider App, but what about our app, Client App? Well, since we’ve supplied a
PendingIntent, it’s now possible for
ClientCallbackActivity to be a private, non-exported Activity. The change has allowed better security for both apps!
If you’re familiar with the notification or alarm manager APIs, you’ll notice that they use
PendingIntents for activating actions and alerting an app of an alarm. This is the reason the system uses
PendingIntents, which are handled as the app that created them, rather than ordinary Intents.
Intent as a mechanism to implement a callback to an
Activity can lead to vulnerabilities, both in the provider and client apps. This is due to the fact that
Intents are always processed in the
Context of the app they are called within. This
Context opens the possibility to start any non-exported activities in the provider app, and forces the client app to export the activity that should receive the callback.
PendingIntents, by contrast, are always processed inside the
Context that created them. This not only allows a provider app to freely use them, without exposing non-exported activities, but also allows the client to specify any activity, including non-exported activities, to receive callbacks.
Learn more about how Android 12 is helping protect apps against unsafe launches of nested intents.