AOSP Framework & Internals
5 min read

Contexts

Learn about Contexts.

Why Everything Needs an Identity

The Linux kernel constantly fields requests from processes trying to open files, read properties, or send messages. To decide if a request is safe, the kernel needs to know exactly who is asking and what they are targeting. Traditional Linux relies on user IDs and group IDs. That model falls apart in Android where thousands of sandboxed apps share the same physical device.

Security contexts solve this by giving every actor and target a strict, unchanging label. A context is a structured string that looks like user:role:type:sensitivity_level. The system uses the user component mostly as a placeholder, keeping it as u almost everywhere. Roles matter for distinguishing passive files from active processes. Android Type Enforcement depends entirely on the type component.

When a process attempts an action, SELinux compares the process type against the target type. The kernel consults its policy matrix to approve or deny that specific interaction. A browser app gets a generic untrusted type, while the camera daemon gets a highly privileged type. The system simply checks if the policy grants the browser type access to the camera type.

This constant checking works perfectly for active processes living in memory. Processes vanish when the device powers down, so the kernel can build their labels fresh on boot. Files on a permanent disk require a completely different approach.

Stamping Files on Disk

A device reboots, and the kernel wakes up with empty memory. The thousands of binaries, configuration files, and libraries sitting on the flash storage need their security labels back. Without a persistent record, the kernel would treat every file as a generic, untrusted blob.

The system solves this persistence problem using file_contexts mappings. These text files act as a master directory linking filesystem paths to SELinux types. The build system bakes these mappings directly into the system image. When the operating system mounts a partition, it uses these rules to stamp every file with its correct identity.

Developers use regular expressions within file_contexts to assign labels. The path /system/bin/app_process32 might map directly to the zygote_exec type. A wildcard rule might label an entire directory tree. When a running process creates a brand new file, that file inherits the security context of its parent directory by default.

Common Mistake: Forgetting to add a file_contexts entry for a brand new vendor binary guarantees a boot failure. The binary inherits a useless directory label and the system refuses to run it.

Unlabeled files sit uselessly on disk because the policy denies access to them. Correctly labeled executables face a different problem when they finally run. A child process inheriting the permissions of its parent creates a massive security hole.

Restricting Child Processes

The init process starts first and runs with absolute root privileges. It has to spawn every hardware daemon, system service, and background task. If a specialized audio daemon inherits the full power of init, a simple bug in the audio stack compromises the entire device. We need a way to strip away permissions the moment a new program starts.

Domain transitions force a process to shed privileges. A policy rule dictates exactly how a process changes its security domain upon executing a specific file. When a highly privileged parent executes a restricted binary, the kernel intercepts the transition. It forces the new child process into a confined domain.

Macros like domain_auto_trans orchestrate this precise downgrade. The parent domain targets an executable file type. The kernel then spawns the new process into the designated child domain.

Visualizing this flow makes the downgrade obvious. You will see how the highly privileged init process spawns a restricted audio daemon. Look for the transition step where the file type dictates the final process domain.

Execution triggers the kernel to apply the transition policy. The audio daemon wakes up trapped in a tiny sandbox. A hacker exploiting the audio service finds themselves completely unable to modify system files or affect other processes.

Containment strategies like this secure the filesystem perfectly. Modern Android apps rarely attack the filesystem directly anymore. They attack Binder endpoints and system properties instead.

Securing IPC and System State

Malicious applications know that writing to /system is impossible. They bypass the filesystem entirely by sending crafted messages to privileged system services. Attackers can also try to alter global configuration values to weaken device security. SELinux would be useless if it only watched files and ignored memory-based communication.

Android extends contexts to cover abstract communication channels using property_contexts and service_contexts. These files assign standard SELinux labels to invisible system state. The property_contexts file maps prefixes like sys.usb. to specific security types. Service mappings assign types to Binder names like activity or wifi.

When an application attempts to read a property, the kernel intercepts the request. It checks if the app domain possesses the get_prop permission for that specific property type. Finding a Binder service works identically. The kernel verifies the find permission before letting the app talk to the Activity Manager.

This architecture turns a simple filesystem guard into a complete platform security model. You cannot impersonate a system service or toggle USB debugging just because you found a loophole in an app. Security contexts provide the foundational identities for every interaction on the device.

We now have a system where every process, file, and message channel has a permanent identity. These labels sit idle until the kernel knows exactly what to do with them. The next step is examining how the SELinux policy language actually ties these identities together.