The many meanings of "system app" in modern Android
Not all Android apps are created equal. The Settings app on an Android device, for example, can change numerous things that no “normal” app can, regardless of how many permissions that app requests. Apps with special privileges like Settings are often called “system apps.” But what makes an app a “system app”? In answering that question for ourselves, we noticed that AOSP’s resources on the subject are disparate and assume a great deal of Android internals knowledge. We wrote this post to summarize what we learned for the benefit of security researchers, app developers, and enthusiasts alike.
In modern Android, two main things determine what an app can do—the set of permissions it’s been granted and the SELinux domain it runs in. Permissions typically gate Binder access to system services and app components on a call-by-call basis, while SELinux gates access to Linux kernel objects (e.g. files, sockets, device nodes) and to entire Binder services. Although any app on Android can define a permission, we’ll constrain our discussion to platform permissions, which are permissions built into Android that control access to system APIs.
Most platform permissions are defined in the manifest of the special android package1. Note how each one includes a protectionLevel string consisting of a base type (normal, dangerous, signature, or internal) modified by zero or more flags (denoted with a leading |, as in |privileged). A permission’s protectionLevel indirectly specifies which apps may use it.
App SELinux domains are defined as part of Android’s OS-wide SELinux policy. SELinux is used for more than just apps, so app domains (e.g. priv_app) are denoted by the appdomain attribute. A set of textual policy rules, located in /system/etc/selinux/plat_seapp_contexts and similar files on other Project Treble partitions2, tell Android which domain to assign a given app.
Although protectionLevel and seapp_contexts fully define the privileges available to each app, both are complex and have many special cases, so it can be hard to pick out which parts matter. In our experience, most apps on a device can be classified into one or more of the following five groups, each of which grants certain privileges. Every group but the first represents some flavor of “system app”:
- Untrusted apps are apps that can be built by a third-party developer and installed to any Android device. Android will only grant them platform permissions with base type
normalordangerous, and they run in some flavor ofuntrusted_appSELinux domain. This is the vast majority of apps on the Play Store. - Preinstalled apps are all the apps that come with a device. They’re located in
app/andpriv-app/directories under/system, other Project Treble partitions, and APEX modules. Such apps can be granted|preinstalledpermissions and are marked FLAG_SYSTEM. (Although that flag’s documentation says it “should not be used to make security decisions,” some APIs give it special treatment anyway.) Updates to preinstalled apps, which live in/data/app/like untrusted apps, are still considered preinstalled. - Privileged apps are preinstalled apps that live in
priv-app/instead ofapp/. (Prior to Android 4.4, there was nopriv-app/and all preinstalled apps were privileged.) They run in thepriv_appSELinux domain and can request|privilegedpermissions subject to the constraints ofprivapp-permissions.xml(introduced in Android 8). - Platform-signed apps are apps that share a signing key (the “platform key”) with
/system/framework/framework-res.apkand so can be grantedsignatureplatform permissions. They run in theplatform_appSELinux domain. Platform-signed apps don’t need to be preinstalled, but they typically are. - System-UID apps are platform-signed apps that run as the
systemuser (UID 1000) by specifyingandroid:sharedUserId="android.uid.system"in their manifest; for example, here’s where the Settings app specifies it. Only platform-signed apps can do that, as the standard rules ofsharedUserIdrequire all apps in a UID to share a signing key. System-UID apps run in thesystem_appSELinux domain and bypass all permission checks. Notably,systemis just one of several special users that appropriately-signed apps can run as.
If an app qualifies for multiple SELinux domains, Android prioritizes them in this order: system_app, platform_app, priv_app, untrusted_app. This is theoretically the order of descending privilege, but nothing enforces that. For example, we’ve seen devices whose vendor SELinux policy lets priv_app access resources that system_app cannot!
These groups describe the most common ways apps qualify for special privileges, but they are not comprehensive. Here are just a few examples of why two apps in the same group(s) might end up with different privileges:
- The user has opted into a
dangerouspermission for one but not the other. - Their signing keys differ, giving them access to different sets of app-defined
signaturepermissions. - One has been assigned a role that grants it certain privileged permissions. (
|roleis one of several lesser-usedprotectionLevelflags we didn’t discuss.) seapp_contextscontains a rule that puts one into a special SELinux domain based on its package name or UID. (Vendors often use such rules to give their own apps extra hardware access.)
Nonetheless, we find these groups very useful as a mental model. For example, they helped us quantify the security impact of two vulnerabilities we discovered that let attackers run code in the context of various apps. We hope you’ll find them equally useful.
Thanks to Yiannis Kozyrakis, Adam Sindelar, Nik Tsytsarkin, and Vlad Ionescu for improvements and corrections.