Android R, Package Visibility, and Some Holes

One of the changes that showed up in R DP2 is the package visibility restriction. Once your targetSdkVersion reaches R , you will not be able to “see” some of the other apps on the device:

Explicit Intents cannot be used to start or bind to components

Many (if not all) PackageManager methods filter them out

So, PackageManger methods like getInstalledApplications() , getInstalledPackages() , getPackagesHoldingPermissions() , and queryIntentActivities() all give you a filtered view of what is really on the device.

The argument for this change is fingerprinting, at least in part. Many users do not install or remove apps frequently, so libraries can use that information to generate identifiers that work between apps. This is much like how ad networks use various bits of your browser settings to track you across sites, which you can visualize in the EFF’s Panopticlick tool.

The Android R documentation is a bit “hand-wavy” as to what your app can and cannot see. Based on some light experimentation, it appears that your app can still see pre-installed apps but not user-installed ones. From a fingerprinting standpoint, this seems reasonable – all users with the same model device should have the same mix of pre-installed apps.

If you need to see more apps than that, you can use a <queries> manifest element to whitelist certain packages or Intent structures. There is no user acceptance required; whatever you request in the whitelist, you can see. This allows something like a launcher to whitelist apps with a component that supports ACTION_MAIN and CATEGORY_LAUNCHER , for example.

Hole #1: Whitelisting the World

And that’s the bigger of the holes: anyone can whitelist anything, so long as it fits the <queries> structure.

So, the simple way to bypass this restriction is to whitelist ACTION_MAIN and CATEGORY_LAUNCHER , as the vast majority of Android apps will have a launcher activity. And now you can see (nearly) everything again.

Right now, at least, the whitelist affects the visibility of apps, across multiple use cases. Whitelisting ACTION_MAIN and CATEGORY_LAUNCHER does not just allow those activities to be visible via queryIntentActivities() . Apps with a launcher activity become visible in getInstalledApplications() and getInstalledPackages() as well. My guess is that they become as visible as a built-in app.

A possible upcoming counter-move would be to tie certain <queries> configurations to certain roles in RoleManager , so only the launcher can whitelist ACTION_MAIN and CATEGORY_LAUNCHER .

But, from the standpoint of fingerprinting, that might be insufficient. An app could whitelist a bunch of common Intent structures, such as ACTION_BOOT_COMPLETED and ACTION_SEND . Similarly, an app could elect to whitelist common user-installed packages, rather than Intent structures. Given enough packages, you might be able to develop a decent fingerprint just by seeing who has what mix of those packages installed.

Android old-timers like me will remember the Android 1.x/2.x days, when malware would register a BroadcastReceiver for a zillion common actions, in hopes that their app would get control before the user had realized that the malware was, indeed, malware. That same sort of attack seems likely here. That too could be policed, perhaps at the Play Store approval level, but that can become fraught with false positives (developers getting bans for legitimate activities) and false negatives (fingerprinters slipping through because their list wasn’t quite big enough to raise a warning).

Perhaps Android starts warning users about these whitelists, or starts requiring dangerous permissions for using certain PackageManager APIs used by fingerprinters.

Hole #2: Disabled Things Are Disabled

The objective of fingerprinting is to identify a user. This package visibility change is focused on one way of identifying the user: seeing what user-installed apps exist.

However, sometimes, users disable built-in apps. That still affects PackageManager results. queryIntentActivities() does not return activities from disabled apps, for example.

I do this a fair bit, disabling a bunch of pre-installed crap that I don’t want and cannot uninstall. So, a fingerprinter could try to identify people by the “negative space”: what should be on their device (given the model) but does not show up.

My guess is that not all that many people disable apps, so perhaps it is not worthy of a fingerprinter trying to exploit it. And I don’t know what Google could do about it anyway. So, this is just a small hole.

There may be other holes in this system — I have not tried all that much, and this “isn’t really my jam”. I’m just trying to figure out what to tell developers about how this works.

Plus, this is only Developer Preview 2, so some of this may be tweaked in future updates.

But my fear is the package visibility change winds up not satisfying anyone, by:

Adding stumbling blocks for legitimate scenarios, and

Not really solving the intended problem(s), such that attacks are still usable

Want an expert opinion on your Android app architecture decisions? Perhaps Mark Murphy can help!

— Apr 05, 2020