Introduction: Navigating Android’s SELinux Landscape
The Android operating system, built upon the Linux kernel, leverages Security-Enhanced Linux (SELinux) as a critical component of its security architecture. Introduced in Android 4.3 Jelly Bean, SELinux enforces mandatory access control (MAC) policies, significantly bolstering device security by isolating processes and restricting resource access based on predefined contexts. While this provides robust protection, it poses a unique challenge for developers maintaining legacy applications, custom services, or building custom ROMs like LineageOS. As Android evolves, so do its SELinux policies, often leading to access denials for older components not explicitly accounted for in modern policy sets. This article delves into understanding SELinux contexts, identifying policy violations, and providing practical strategies for migrating legacy Android apps and services to achieve compliance with contemporary SELinux policies.
Understanding SELinux Contexts in Android
At its core, SELinux operates by labeling every system resource (files, processes, sockets, IPC, etc.) with a security context. These contexts consist of user, role, type, and sensitivity (e.g., u:object_r:system_file:s0). In Android, the ‘type’ attribute is most commonly used for defining policy rules. When a subject (e.g., a process with type untrusted_app_t) attempts to access an object (e.g., a file with type apk_data_file), the SELinux kernel checks the policy database. If no explicit rule permits the interaction, access is denied, regardless of traditional Linux discretionary access controls (DAC).
Key SELinux Policy Files in Android:
file_contexts: Defines default contexts for files based on their path.genfs_contexts: Specifies contexts for pseudo-filesystems (e.g.,proc,sys).seapp_contexts: Assigns process contexts to Android applications based on their package name and UID.*.te(Type Enforcement files): Source files defining types, attributes, and rules.sepolicy: The compiled policy binary loaded by the kernel.
For legacy components, the primary challenges arise when: 1) a file is created in a location with an unexpected default context, or 2) a process attempts an action (read, write, execute, bind, connect) on a resource whose context is not allowed by its own process context.
Identifying SELinux Denials: The Audit Log
The first step in any SELinux migration is identifying what is being denied. SELinux logs all denials to the kernel audit log. These messages can be viewed via logcat or dmesg.
To capture denial messages:
adb logcat | grep 'avc: denied'
Or directly from the kernel ring buffer:
adb shell dmesg | grep 'avc: denied'
A typical denial message looks like this:
avc: denied { read } for pid=1234 comm="my_legacy_app" name="config.xml" dev="tmpfs" ino=5678 scontext=u:r:untrusted_app:s0:c512,c768 tcontext=u:object_r:system_data_file:s0 tclass=file permissive=0
Dissecting this message is crucial:
{ read }: The permission being denied.pid=1234 comm="my_legacy_app": The process (subject) attempting the action.name="config.xml": The name of the resource (object).scontext=u:r:untrusted_app:s0: The security context of the subject (source context).tcontext=u:object_r:system_data_file:s0: The security context of the object (target context).tclass=file: The class of the object (e.g., file, dir, socket, process).
From this, we know that a process running with the untrusted_app_t type tried to `read` a `file` labeled system_data_file_t, and the policy forbids this interaction.
Strategies for Context Migration and Policy Compliance
Once denials are identified, you have several approaches:
1. Correcting File Contexts
Often, a legacy app or service creates files in a location where the default `file_contexts` assigns an overly restrictive or incorrect label. You might need to add or modify rules in `file_contexts` in your device’s `sepolicy` project.
Example: A custom daemon creates logs in /data/mydaemon/logs, but they are getting labeled app_data_file_t, and your daemon needs to access them with a custom type.
Add a line to your `device/<vendor>/<device>/sepolicy/file_contexts` (or similar path in your build tree):
/data/mydaemon(/.*)? u:object_r:mydaemon_data_file:s0
This rule ensures any file or directory under /data/mydaemon is labeled mydaemon_data_file_t. You’ll need to define this new type in your `mydaemon.te` policy file.
For existing files on a running system, you can temporarily relabel using chcon or `restorecon` (requires root):
adb shell su -c 'chcon u:object_r:mydaemon_data_file:s0 /data/mydaemon/logs/log.txt'adb shell su -c 'restorecon -R /data/mydaemon' # Restores default contexts recursively
2. Adjusting Process Contexts for Custom Services
Custom native services, often launched via `init.rc` scripts, require careful context management. The `init` process itself runs with the `init_t` context, but child services need their own, more restricted contexts.
In `init.rc` (or `init.<device>.rc`):
service mydaemon /vendor/bin/mydaemon class main user root group system seclabel u:r:mydaemon_t:s0 # Assigns the custom process context capabilities NET_RAW NET_ADMIN # ... other options
Here, `seclabel u:r:mydaemon_t:s0` is crucial. It tells `init` to transition the `mydaemon` process into the `mydaemon_t` SELinux domain. You *must* define `mydaemon_t` in your custom policy.
3. Writing Custom SELinux Type Enforcement (.te) Policies
This is the most powerful and complex method. It involves defining new types and rules in a `.te` file within your `sepolicy` project.
Example: Creating a policy for `mydaemon_t` to access its data file and bind to a network port.
device/<vendor>/<device>/sepolicy/mydaemon.te:
# Define the domain type for mydaemontype mydaemon_t;type mydaemon_data_file, file_type, data_file_type;# Allow mydaemon to run as a domaininit_daemon_domain(mydaemon_t)# Allow mydaemon to read/write its data filesallow mydaemon_t mydaemon_data_file:file { read write create getattr setattr };allow mydaemon_t mydaemon_data_file:dir { read write add_name remove_name search };# Allow mydaemon to bind to a specific network port (e.g., 12345)allow mydaemon_t self:socket { create bind };allow mydaemon_t self:tcp_socket create_socket_perms;permissive mydaemon_t; # Temporarily allows everything for mydaemon_t, for debugging
After initial testing with `permissive mydaemon_t;`, replace it with specific rules based on `audit2allow` suggestions (if using a Linux environment to process audit logs) or careful analysis of denial logs. For instance, if `mydaemon_t` gets `avc: denied { setcap }`, you might add `allow mydaemon_t self:capability setcap;` if truly needed.
You must also include your new `.te` file in your `BoardConfig.mk` or equivalent for compilation into the final `sepolicy.b` file.
# In device/<vendor>/<device>/BoardConfig.mkBOARD_SEPOLICY_DIRS += device/<vendor>/<device>/sepolicy
4. Adapting Legacy Android Applications
For standard Android applications, their process context is usually assigned by `seapp_contexts` (e.g., `untrusted_app_t`, `platform_app_t`). If a legacy app encounters denials, it often means it’s trying to access resources that modern policies restrict for its assigned type.
- Refactor App Behavior: The ideal solution is to modify the app to use Android’s official APIs for resource access, which inherently respect SELinux policies. For instance, using `Context.getExternalFilesDir()` instead of hardcoded paths.
- Custom Policies (for system apps/privileged apps): If the app is part of a custom ROM or a privileged system app, you might need to extend its policy in a dedicated `.te` file (e.g., `app_name.te`) to grant necessary permissions. This should be done judiciously, adhering to the principle of least privilege.
Best Practices for SELinux Migration
- Principle of Least Privilege: Only grant the permissions absolutely necessary for the component to function. Overly broad rules (like `allow mydaemon_t *:* *;`) undermine SELinux’s security benefits.
- Iterative Development: Address one denial at a time. Start in `permissive` mode if necessary (though not recommended for long-term production), capture all denials, write policy, re-test in `enforcing` mode.
- Leverage Existing Types: Before creating new types, see if an existing type (`hal_server_t`, `system_app_t`, `vendor_init_t`, etc.) already fits the component’s purpose and has appropriate permissions.
- Documentation: Clearly document any custom SELinux rules and the rationale behind them. This is crucial for future maintenance and debugging.
- Testing: Thoroughly test the application or service after policy modifications to ensure all functionality works correctly and no new denials arise.
Conclusion
SELinux context migration is a fundamental task for maintaining security and compatibility when dealing with legacy Android applications, custom services, or building custom Android distributions. By understanding how SELinux contexts work, diligently analyzing denial logs, and applying targeted policy adjustments, developers can successfully adapt their components to comply with modern Android security policies. This not only ensures proper functionality but also reinforces the overall security posture of the Android device.
Android Mobile Specs & Compare Directory
Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!
Compare Devices Specs →