Android security trade-offs 1: Root access
Android security trade-offs: Rooting
“Rooting” has been part of the Android ecosystem pretty much since its creation. Within the context of this blog post, I define rooting as a method to disable standard sandboxing mechanisms for particular processes, which is a superset of Nick Kralevich’s earlier definition because many posts mix up the intentional, user-driven root access with exploitation of vulnerabilities. In this post I mean granting select apps and their processes the “root” privilege, which entitles them to ignore access control mechanisms on the system and kernel levels. More details on the technical aspects are described below.
Reasons to root Android devices
The reasons for rooting are manifold (although the classic “jailbreak” to install applications from non-market sources is not required on Android and therefore not one of the normal reasons). Some typical use cases for rooting include:
- Changing the appearance or behaviour of low-level Android system and framework components
- Research and development outside the supported API surface, including reverse engineering and analysis of apps (malware or legitimate) or development of low-level system components
- Backup and restore of data that apps do not include through the standard Android backup using its backup agent infrastructure - although that carries the major risk of making data exfiltration/extraction easier against the user’s interest
- Blocking app or system functionality (e.g. filtering network traffic or API access) beyond supported methods1
- Enabling functionality that has been blocked by various policies (e.g. tethering, geographic, or DRM restrictions)
There are different ways in which users can grant particular apps or processes root privileges:
- The standard, supported way is to recompile the system image to allow
adbdto transition to root (using the
adb rootsubcommand, which gives the subsequent adb shell root privileges). This either requires the system property
ro.secure=0(not recommended) or
service.adb.root=1in userdebug builds.
Note that this allows adb shell to execute as root, but does not allow arbitrary applications on the system to do so, as adbd cannot be used through a localhost socket. That is, it fulfills the needs for research and development and local backup through adb.
However, this requires the possibility to recompile an image to make those changes from source, or for the manufacturer to release userdebug builds. Google Pixel and Nexus devices as well as some other OEM devices officially support building images from AOSP and installing them on the device after unlocking its bootloader.
- One of the most common options is to add a new binary (often
su) to the system or boot images that, when called by an arbitrary app process, will fork a new process running with root privileges. The currently popular framework Magisk does this through modifying the boot image with overlay mounts to change the system tree. This allows on-device apps to execute code as root (some ready-made libraries for apps even make calling code as root simple from a developer perspective).
Note that most benign implementations of such an
sucommand ask the user for consent through a pop-up dialog, comparable to run-time permissions handled by the Android framework.
- Unpatched vulnerabilities in system code may be used to temporarily elevate privileges of a calling process through a matching exploit. This is the most common case for malicious code breaking the app sandbox.
Technical detail: SELinux
On modern Android releases (starting with Android 4.4, strengthened from release to release), SELinux constrains even what a process running with
UID=0 can do. Therefore, forking a process with this UID is no longer sufficient to break the sandbox - and this is exactly one of the reasons why SELinux has been introduced with an enforcing MAC policy. That is, even temporary exploits to system services no longer allow malicious code unfettered access.
To deal with these additional restrictions, current rooting approaches also install additional SELinux policies that allow their processes to access system resources the normal policy would not. The specific techniques vary from disabling SELinux enforcement altogether (which is strongly discouraged, as it disables one of the most effective defense-in-depth methods in Android platform security) to adding a new system daemon started through init with highly permissive SELinux labels.
Risks in rooting Android devices
Obviously, code running as root in the way described above is not subject to sandboxing or other limitations and can - temporarily or permanently - read and modify arbitrary data held within Android. Not only can malicious code act outside its own app security domain, but guarantees given to other apps through the Android platform security model no longer hold.
While this gives more control to users who are in physical control of a device, it can also put their owners at much greater risk from malware exploiting these same mechanisms or another user who manages to gain physical hold of the device. One specific example is theft or loss of a device - when rooted with sensitive data still stored on the flash, protections against unauthorized extraction of data may not hold even though the device may be encrypted.
Resolving the trade-off
Because rooting causes more risk than benefit for the majority of Android users, it is not officially supported by standard, so-called “user” builds. There is intentionally no option to allow app processes to execute code as root and break their sandbox and other security measures.
Also note that, over the years, Android has added new APIs and built-in functionality that previously may have required rooting devices. Examples range from night/dark mode, flexible VPN support (which can e.g. be used for on-device firewalling), screenshots, end-to-end encrypted backup, or NFC Host Card Emulation (HCE) and secure element access (also here), to privacy preserving features such as WiFi MAC randomization (previously e.g. mitigated through WiFi Privacy Police) or IPv6 address privacy (my only Android app formerly in the Play store used to do that before it became standard and hence the app became obsolete).2 One of the biggest reasons for power users rooting on older Android releases may have been to achieve fine-grained control over app access to data (with XPrivacy / XPrivacyLua being well-known modules). With Android 6.0, the introduction of ApOps in the framework and run-time permissions for user control provided one of the biggest steps forward in these regards. Android 8.0 and 9.0 continued to add more privacy controls, again making rooting unnecessary for most scenarios.
For power users and developers, the recommended way to gain root privileges on Android devices is to install a “userdebug” build or otherwise customize a build of AOSP, as Nick already described over 8 years ago. This leaves the app sandbox intact for all apps running on the device, but allows elevated privilege access through the adb shell.
However, it is important to note that such modified builds are not considered to be Android. While many apps may still run as expected, compatibility requirements are no longer completely fulfilled - specifically, the Compatibility Test Suite (CTS) is expected to fail when run on builds that support security model bypass (among other, through so-called “neverallow” SELinux rules the check that certain access patterns are not permitted by added rules). Some apps may fail to run or exhibit unexpected behavior on such builds, and app developers may use mechanisms like hardware-backed Key Attestation (specifically the
VerifiedBootState attributes) or SafetyNet Attestation to verify if a device fulfills the compatibility requirements.
Often, such blocking or filtering will cause apps to crash or behave unexpectedly. Some users are willing to take this breakage for specific filtering to be applied, while it would be unsuitable for a broad population to make such filtering decisions on a fine-granular level. The most well-known generic framework at the time of this writing is Xposed with a large number of end-user targeting modules. ↩︎
Full disclosure: Until roughly 2016, all of my daily-use Android devices were rooted because I always missed some functionality or wanted more control. Since 2017, I have not rooted the phone I use as my respective daily driver, because - for my personal use cases - the need to do so vanished. Nonetheless, I still run userdebug builds on development devices for debugging and analysis. ↩︎