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
normal
ordangerous
, and they run in some flavor ofuntrusted_app
SELinux 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|preinstalled
permissions 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_app
SELinux domain and can request|privileged
permissions 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.apk
and so can be grantedsignature
platform permissions. They run in theplatform_app
SELinux 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
system
user (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 ofsharedUserId
require all apps in a UID to share a signing key. System-UID apps run in thesystem_app
SELinux domain and bypass all permission checks. Notably,system
is 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
dangerous
permission for one but not the other. - Their signing keys differ, giving them access to different sets of app-defined
signature
permissions. - One has been assigned a role that grants it certain privileged permissions. (
|role
is one of several lesser-usedprotectionLevel
flags we didn’t discuss.) seapp_contexts
contains 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.