SELinux¶
Security-Enhanced Linux on Android provides mandatory access control (MAC) that confines every process -- including root -- to a defined policy. Since Android 5.0, SELinux runs in enforcing mode on all production devices. For attackers, SELinux is the primary obstacle between a kernel or system exploit and full device control. Understanding its structure, its weaknesses, and the ways it has been bypassed is essential for Android offensive research.
Policy Architecture¶
SELinux on Android uses a combination of type enforcement (TE), role-based access control (RBAC), and multi-level security (MLS) to define what every process can access.
Type Enforcement¶
Every process and object on the system receives a security label (context) in the format user:role:type:mls_level. Type enforcement rules define allowed interactions between types:
allow untrusted_app app_data_file:file { read write open getattr };
allow untrusted_app activity_service:service_manager find;
The kernel denies any access not explicitly granted by an allow rule. There is no implicit permission -- even root running as u:r:su:s0 is subject to whatever rules exist for the su type.
Domain Transitions¶
When a process executes a binary, SELinux can force a domain transition to a new security context. The Android init process starts as u:r:init:s0 and transitions child processes into their assigned domains. The Zygote process (u:r:zygote:s0) forks app processes and transitions them into the appropriate app domain based on the app's signing certificate and seinfo tag.
Multi-Level Security (MLS)¶
Android uses MLS categories to isolate apps from each other. Each app receives a unique category pair (e.g., s0:c149,c256,c512,c768), preventing one untrusted app from accessing another's files even though both run in the untrusted_app domain. The category assignment is derived from the app's UID.
App Process Contexts¶
The seapp_contexts file maps apps to their SELinux domains based on signing certificate, seinfo tag, and user type.
| Domain | Assigned To | Capabilities |
|---|---|---|
untrusted_app |
Third-party apps from Play Store or sideloaded | Standard app sandbox, no access to system internals |
untrusted_app_32 |
32-bit third-party apps on 64-bit devices | Same as untrusted_app with 32-bit ABI |
platform_app |
Apps signed with the platform certificate | Access to platform-level services and some system APIs |
system_app |
Apps in /system/app or /system/priv-app signed with platform cert |
Broader system access, can interact with system services |
priv_app |
Privileged apps in /system/priv-app |
Extended permissions beyond standard apps |
isolated_app |
Isolated service processes (isolatedProcess=true) |
Extremely restricted, no network, no filesystem access |
ephemeral_app |
Instant apps | Reduced permissions compared to untrusted_app |
The seinfo tag is assigned by the mac_permissions.xml file based on the app's signing certificate. Apps signed with the platform key receive seinfo=platform, which maps to the platform_app domain. All other apps default to untrusted_app.
Policy Files and Analysis Tools¶
Policy Locations¶
| Path | Contents |
|---|---|
/sys/fs/selinux/policy |
Compiled binary policy currently loaded in kernel |
/vendor/etc/selinux/ |
Vendor-specific policy fragments |
/system/etc/selinux/ |
System policy fragments |
/sepolicy |
Legacy monolithic policy (pre-Android 8.0 Treble) |
boot.img ramdisk |
Contains sepolicy on some device configurations |
Analysis Tools¶
| Tool | Purpose |
|---|---|
seinfo |
Display SELinux policy info, list types and attributes |
sesearch |
Search policy rules (allow, neverallow, type_transition) |
sepolicy-analyze |
AOSP tool for analyzing compiled policy |
audit2allow |
Convert SELinux denial logs into allow rules |
setenforce |
Toggle between enforcing (1) and permissive (0) -- requires root |
getenforce |
Query current SELinux mode |
chcon |
Change security context of a file |
restorecon |
Restore file context to policy default |
Extracting and decompiling the running policy from a device:
adb pull /sys/fs/selinux/policy /tmp/sepolicy
sesearch --allow -s untrusted_app /tmp/sepolicy
sesearch --allow -t app_data_file /tmp/sepolicy
seinfo -t /tmp/sepolicy | grep app
Known Bypasses and Weaknesses¶
Neverallow Violations¶
The AOSP policy includes neverallow rules that define security invariants -- transitions and accesses that must never be permitted. These are checked at policy compile time. However, vendor policy additions (particularly on OEM devices shipping custom SELinux rules) have historically introduced rules that violate these invariants.
Google's CTS tests verify that neverallow rules are respected, but devices that ship outside the CTS program or with incomplete testing have been caught with policy violations. Research from 8kSec on Android SELinux internals documents how vendor policy fragments can inadvertently weaken the security posture.
Permissive Domains in Production¶
During development, new SELinux domains are initially set to permissive mode to avoid breaking functionality while the correct rules are authored. The AOSP documentation explicitly states that permissive mode is not supported on production devices. CTS tests check that the global SELinux mode is enforcing.
However, individual domains can be set to permissive even when the global mode is enforcing. OEM vendors have shipped devices with permissive vendor-specific domains, allowing processes in those domains to bypass all MAC restrictions while the rest of the system remains enforcing. This was common on budget devices from smaller manufacturers who lacked the resources for complete policy development.
CVEs Involving SELinux Bypass¶
| CVE | Year | Description |
|---|---|---|
| CVE-2022-20421 | 2022 | Android Binder use-after-free achieving full kernel R/W, leading to root and complete SELinux bypass on Pixel 6 |
| CVE-2024-53197 | 2024 | Privilege escalation exploited as part of a zero-day chain reportedly used by Cellebrite against Serbian activists |
| CVE-2025-27363 | 2025 | Malformed binder transactions overwriting kernel structures, bypassing SELinux protections |
| CVE-2025-38352 | 2025 | Local privilege escalation via race condition in POSIX CPU timers, enabling sandbox escape |
The klecko blog on SELinux bypasses documents historical techniques for defeating SELinux after obtaining kernel code execution, including patching the in-kernel policy, disabling the enforcement flag, and manipulating process credentials.
Kernel Exploit to SELinux Disable¶
Once an attacker achieves kernel code execution (through a kernel vulnerability), SELinux can be neutralized by:
- Patching the enforcement flag: Writing
0to the kernel'sselinux_enforcingvariable switches the entire system to permissive mode - Modifying process credentials: Overwriting the
securityfield in the task'scredstructure to assign an unrestricted SELinux context - Disabling LSM hooks: Zeroing out the SELinux hook functions in the Linux Security Module framework
Samsung devices with RKP (Real-time Kernel Protection) implement hypervisor-level protection of these structures, but research has shown that even RKP can be bypassed by targeting the hypervisor itself or finding gaps in its coverage.
Magisk and SELinux¶
Magisk provides root access on Android while attempting to maintain a functional SELinux enforcing mode. Understanding how Magisk interacts with SELinux is critical for both root detection and offensive research.
MagiskSU Context¶
All processes spawned from the Magisk daemon, including root shells and their child processes, run in the context u:r:magisk:s0. This is a custom domain that Magisk injects into the SELinux policy during boot. The magisk domain is granted broad permissions through injected policy rules, giving root shells access equivalent to the init or kernel domain while maintaining the enforcing state for all other processes.
Sepolicy Injection¶
The magiskinit binary replaces the stock /init as the first userspace process (PID 1). Before executing the real init, it:
- Loads the stock SELinux policy from the boot image or vendor partition
- Patches the policy in memory to inject the
magiskdomain and associated rules - Writes the modified policy for the kernel to load
This happens before any other process starts, so the injected rules are active from the earliest point of the boot sequence. Magisk modules can provide additional policy patches via sepolicy.rule files, though compatibility varies across devices.
Zygisk¶
Zygisk loads Magisk code directly into the Zygote process, allowing module developers to execute code in every app process before specialization. From a SELinux perspective, the Zygisk code runs within the zygote domain during injection, then transitions to the app's assigned domain when the process specializes. This is relevant for tools that need to run with elevated context before the app sandbox takes effect.
Detection Implications¶
Because Magisk keeps SELinux in enforcing mode, simple getenforce checks return "Enforcing" on rooted Magisk devices. This means enforcement mode alone is not a reliable indicator of an unmodified device. More sophisticated detection checks for the presence of the magisk type in the loaded policy:
Permissive Mode Detection¶
How Malware Uses Permissive Mode¶
When SELinux is in permissive mode, critical security restrictions are disabled. As documented by Magisk's developer, any arbitrary app can permanently root a device without user consent by exploiting known vulnerabilities that are normally blocked by SELinux policy. When SELinux is permissive during boot, Zygote also disables seccomp syscall filters, further unrestricting third-party processes.
Malware targeting rooted or compromised devices checks for permissive mode to determine if advanced exploitation techniques are available. A permissive device is effectively unprotected -- the app sandbox, inter-app isolation, and system service restrictions all become advisory rather than enforced.
Detection by Banking Apps and Security Software¶
Banking apps and root detection libraries check SELinux status through multiple methods:
| Method | Detail |
|---|---|
getenforce command |
Runs the binary and parses stdout for "Enforcing" or "Permissive" |
/sys/fs/selinux/enforce |
Reads the kernel's enforcement flag directly (0 = permissive, 1 = enforcing) |
android.os.SELinux |
Hidden system API with isSELinuxEnforced() method |
| Process context check | Reads /proc/self/attr/current to verify the expected SELinux label |
| Policy analysis | Checks for overly permissive rules or custom domains like magisk |
How Root Hiders Counter Detection¶
Root management solutions (Magisk, KernelSU, APatch) maintain enforcing mode specifically to pass these checks. The SELinux status reported through all standard interfaces shows "Enforcing" because the system genuinely is enforcing -- Magisk achieves its goals through injected policy rules rather than disabling enforcement.
Tools like Shamiko and PlayIntegrityFix go further by hiding the magisk domain from policy inspection and spoofing device properties that root detection examines.
Offensive Relevance¶
For Exploit Development¶
SELinux policy analysis reveals what a compromised process can and cannot do. Before developing a post-exploitation payload, an attacker must know what the target domain's policy allows:
sesearch --allow -s untrusted_app /tmp/sepolicy | wc -l
sesearch --allow -s untrusted_app -t binder_device /tmp/sepolicy
sesearch --allow -s platform_app -c service_manager /tmp/sepolicy
The gap between what untrusted_app can do and what platform_app or system_app can do defines the value of privilege escalation from one domain to another.
For Malware Analysis¶
When analyzing a sample that requires root or system-level access, understanding the SELinux context it expects to run in reveals its operational requirements. Malware that checks for permissive mode likely relies on exploitation techniques blocked by enforcing policy. Malware that functions within untrusted_app constraints is more sophisticated -- it operates within the sandbox using permitted APIs like accessibility services.
For Forensics¶
SELinux audit logs (/data/misc/audit/audit.log and dmesg) record all policy denials. On a compromised device, these logs reveal what the attacker attempted that was blocked by policy, providing a trace of exploitation activity even when the attack itself left no other artifacts.
Cross-References¶
- Accessibility Abuse operates entirely within the
untrusted_appSELinux domain, requiring no policy bypass - Persistence Techniques must work within SELinux constraints on non-rooted devices
- Verified Boot protects the SELinux policy files from modification on locked bootloader devices
- Play Integrity checks enforcing mode as one signal in device integrity attestation