Introduction to SELinux on Android
Security-Enhanced Linux (SELinux) is a mandatory access control (MAC) system implemented in the Linux kernel. On Android, SELinux is critical for enforcing isolation between applications, protecting system resources, and mitigating the impact of security vulnerabilities. Unlike discretionary access control (DAC), which allows users to control permissions for their own files, SELinux policies define comprehensive rules that govern all interactions between processes and resources, regardless of traditional Unix permissions. When an operation violates an SELinux policy rule, the kernel denies the action and logs an Access Vector Cache (AVC) denial message.
Understanding and resolving these AVC denials is a fundamental skill for anyone developing custom Android ROMs, porting new devices, or deeply customizing system services. Blanket permissive policies (`setenforce 0`) undermine the security benefits and are not suitable for production environments. The goal is to craft precise, minimal policy rules that allow necessary operations without weakening the overall security posture.
Identifying and Analyzing AVC Denials
The first step in debugging an SELinux issue is to locate the AVC denial messages. These are typically found in the kernel’s message buffer or Android’s logcat.
Accessing Kernel Logs (dmesg)
You can retrieve kernel messages directly from a rooted Android device or an ADB shell:
adb shell
su
dmesg | grep audit
Or, if you prefer to pull the logs to your computer:
adb shell dmesg > dmesg.log
grep audit dmesg.log
Accessing Android Logcat
Logcat provides a more comprehensive view of system events, including SELinux denials, especially if they are propagated through Android’s logging system:
adb logcat | grep "avc: denied"
A typical AVC denial message will look something like this:
audit: avc: denied { read } for pid=1234 comm="my_service" name="some_file" dev="tmpfs" ino=5678 scontext=u:r:my_service:s0 tcontext=u:object_r:system_file:s0 tclass=file permissive=0
Let’s break down the key components of this message:
avc: denied { read }: The specific permission that was denied (e.g., `read`, `write`, `execute`, `open`, `getattr`).pid=1234 comm="my_service": The process ID and command name of the process attempting the action.name="some_file": The name of the file or resource being accessed.scontext=u:r:my_service:s0: The Security Context of the Subject (the process). This tells you the domain of the process.tcontext=u:object_r:system_file:s0: The Security Context of the Target (the resource). This tells you the type of the resource.tclass=file: The Target Class (e.g., `file`, `dir`, `socket`, `service_manager`, `binder`).permissive=0: Indicates SELinux is in enforcing mode. If `permissive=1`, the action would have been allowed, but an alert would still be logged.
Crafting Precise Policy Rules
The goal is to write a rule that permits the specific `read` action for `my_service` on `system_file` of `file` class, and nothing more.
Step 1: Initial Policy Generation (audit2allow)
The `audit2allow` tool is a useful starting point for generating basic SELinux policy rules from denial messages. While convenient, its output often generates overly broad rules, so it should be used with caution and its suggestions always reviewed and refined.
You can feed the denial message directly into `audit2allow`:
echo 'audit: avc: denied { read } for pid=1234 comm="my_service" name="some_file" dev="tmpfs" ino=5678 scontext=u:r:my_service:s0 tcontext=u:object_r:system_file:s0 tclass=file permissive=0' | audit2allow
This might produce an output like:
#============= my_service =============
allow my_service system_file:file { read };
This is a good starting point, but let’s consider how to make it more precise.
Step 2: Understanding SELinux Policy Language
SELinux policies are written in a specific language. Key concepts include:
- Type Enforcement (TE): The core of SELinux, defining rules based on types.
- Types: Labels assigned to subjects (processes) and objects (files, sockets, etc.). For example, `my_service` is a subject type, `system_file` is an object type.
- Classes: Represent categories of objects, like `file`, `dir`, `socket`.
- Permissions: Specific actions within a class, like `read`, `write`, `execute`.
- Attributes: Groups of types that can be referenced collectively in rules.
The basic rule structure is:
allow SOURCE_TYPE TARGET_TYPE:CLASS { PERMISSIONS };
Where:
SOURCE_TYPEis the `scontext` type (e.g., `my_service`).TARGET_TYPEis the `tcontext` type (e.g., `system_file`).CLASSis the `tclass` (e.g., `file`).PERMISSIONSare the denied actions (e.g., `read`).
Step 3: Refining the Policy Rule
The `audit2allow` output `allow my_service system_file:file { read };` is often too broad. If `system_file` refers to many files, `my_service` will be able to read all of them. We want to grant access only to `some_file` if possible.
First, identify the exact path of `some_file`. If it’s a specific configuration file in `/data/misc/`, for example, you might want to create a new type for it.
Creating a New File Type
Let’s say `some_file` is located at `/data/misc/my_app/config.json`. We would first define a type for this specific file or directory in a `.te` file (e.g., `my_service.te`):
type my_app_config_file, file_type, data_file_type;
# Or, if it's a directory
type my_app_config_dir, file_type, data_file_type, core_data_file_type;
Then, in a `file_contexts` file (e.g., `my_service_file_contexts` or `device/manufacturer/device-name/sepolicy/file_contexts`), we map the path to this new type:
/data/misc/my_app/config.json u:object_r:my_app_config_file:s0
/data/misc/my_app(/.*)? u:object_r:my_app_config_dir:s0
Now, your policy rule becomes much more precise:
allow my_service my_app_config_file:file { read };
This grants `my_service` read access *only* to files labeled `my_app_config_file`, not all `system_file` types.
Considering Neverallow Rules
Android’s SELinux policy extensively uses `neverallow` rules, which explicitly forbid certain operations. If your precise rule still results in denials or doesn’t work, it might be conflicting with a `neverallow` rule. These rules prevent common security holes. You can check for `neverallow` rules using `sepolicy-analyze` on your compiled policy (`sepolicy`).
sepolicy-analyze neverallow system/etc/selinux/plat_sepolicy.cil
Circumventing `neverallow` rules is generally discouraged as it often indicates a fundamental security design flaw in your approach. Re-evaluate if the operation is truly necessary or if there’s a more secure alternative.
Applying and Testing the New Policy
Once you’ve crafted your `.te` and `file_contexts` rules, you need to compile them into your device’s SELinux policy. This typically involves placing your `.te` and `file_contexts` files in the appropriate `device/manufacturer/device-name/sepolicy/` directory within your AOSP build tree and recompiling the system image.
# Example build commands from AOSP root
source build/envsetup.sh
TARGET_PRODUCT=your_device_name lunch
m -j$(nproc)
After flashing the new system image, re-run the problematic operation and check `logcat` or `dmesg` again for any new AVC denials. This iterative process of identify-analyze-craft-test is crucial for developing robust SELinux policies.
Conclusion
Mastering SELinux policy development on Android is an essential skill for system integrators and advanced developers. By systematically reverse engineering AVC denials, leveraging tools like `audit2allow` judiciously, and understanding the nuances of the SELinux policy language, you can craft precise, secure, and maintainable policies. Always strive for the principle of least privilege, granting only the necessary permissions and refining broad rules into highly specific ones to maintain the integrity and security of the Android platform.
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 →