Android Nesting Intents

Nicole Borrelli
Feb 5 · 4 min read
Illustration by Molly Hensley

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 startActivity() call?

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 problem

The way we would expect this type of interaction to work would be something like this:

Flowchart showing how an Intent to start a callback activity is added as an extra to an Intent to start a service, which then uses it to start the provided Activity.

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

❗️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:

  • Since ClientCallbackActivity is 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 Intent passed to ApiService can be used to start any Activity that’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.

Flowchart showing how a carefully constructed Intent can be used to start ApiSensitiveActivity in Provider App, even though it isn’t exported and shouldn’t be able to be started by other apps.

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

The difference between an Intent and a PendingIntent is that a PendingIntent is always processed with the identity it was created with:

Flowchart showing how accepting a PendingIntent is processed as the identity of the app that created it, preventing it from calling non-exported activities in Provider App.

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.

Summary

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

Android Developers

The official Android Developers publication on Medium