Understanding SELinux on Android Custom ROMs
Security-Enhanced Linux (SELinux) is a mandatory access control (MAC) system that provides a mechanism for supporting security policies, including United States Department of Defense style multi-level security (MLS) and multi-category security (MCS). On Android, SELinux operates in one of two primary modes: enforcing or permissive.
In enforcing mode, SELinux actively denies unauthorized operations and logs all denials. This is the default and recommended mode for production devices, providing robust security by restricting processes to only the resources and actions they are explicitly allowed. Conversely, in permissive mode, SELinux does not enforce policy but still logs all denials. This mode is often used during development and debugging, as it allows applications to run even if they violate SELinux policy, making it easier to identify necessary rule additions without breaking functionality.
For advanced custom ROM users and developers, understanding and customizing SELinux rules is crucial. While custom ROMs like LineageOS often come with well-tuned policies, specific hardware configurations, specialized applications, or custom kernel modules can lead to SELinux denials, preventing functionality. This guide will walk you through the process of building your own SELinux rules to address these issues, maintaining an enforcing policy for optimal security.
Prerequisites and Tools
Before diving into rule creation, ensure you have the necessary environment set up:
- AOSP Source Tree: A full Android Open Source Project (AOSP) source tree, preferably matching your custom ROM’s base version, is highly recommended. This provides the necessary build tools and existing SELinux policy files.
- Android NDK: For compiling SELinux policy modules.
- Device with ADB Access: Your Android device should be rooted (optional, but simplifies log extraction) and have ADB debugging enabled.
- Basic Linux Command Line Skills: Familiarity with shell commands is essential.
Identifying SELinux Denials
The first step is to identify what SELinux is preventing. Denials are typically logged in the kernel ring buffer (`dmesg`) and Android’s logging system (`logcat`).
To capture denials, perform the action that fails, then immediately pull logs from your device:
adb shell su -c dmesg | grep 'avc: denied' > selinux_denials.txt
adb logcat -b all -d | grep 'avc: denied' >> selinux_denials.txt
Alternatively, if you have a custom recovery or can boot into a debug kernel, you might be able to access `audit.log` if `auditd` is running. A typical denial message looks like this:
type=1400 audit(1678886400.000:123): avc: denied { read } for pid=1234 comm="my_app" name="my_device" dev="tmpfs" ino=5678 scontext=u:r:untrusted_app:s0:c123,c456 tcontext=u:object_r:my_device_file:s0 pcontext=u:r:untrusted_app:s0:c123,c456 permissive=0
Key elements to note are `scontext` (source context), `tcontext` (target context), `class` (e.g., `file`, `socket`), and `perm` (permission, e.g., `read`, `write`, `execute`).
Creating a Custom Policy Module (.te file)
Once you have a list of denials, you can start crafting your own Type Enforcement (TE) rules. The goal is to grant only the minimum necessary permissions.
Method 1: Using `audit2allow` (For Quick Fixes)
The `audit2allow` tool (part of the AOSP build system, located in `external/selinux/audit2allow`) can automate the generation of basic rules from denial logs. While convenient, always review its output carefully, as it can be overly permissive.
- Place your `selinux_denials.txt` file in your AOSP root.
- Run `audit2allow` from the AOSP root:
./external/selinux/audit2allow/audit2allow -i selinux_denials.txt -o my_custom_policy.te - Review `my_custom_policy.te`. For example, a denial like `avc: denied { read } for pid=1234 comm=”my_app” name=”my_device” dev=”tmpfs” ino=5678 scontext=u:r:untrusted_app:s0 tcontext=u:object_r:my_device_file:s0` might generate:
allow untrusted_app my_device_file:file { read };
Method 2: Manual Rule Crafting (Recommended)
For more control and security, manually write your `.te` file. Create a new file, e.g., `device/myvendor/mydevice-sepolicy/my_custom_policy.te`, within your device’s sepolicy directory in the AOSP tree.
Let’s say your custom daemon `my_daemon` (running with `my_daemon_exec` type) needs to write to `/data/misc/mydata` (which is labeled `my_data_file`).
First, define the type for your daemon’s executable and for its data directory if they don’t exist:
# In device/myvendor/mydevice-sepolicy/file_contexts (or similar)
/data/misc/mydata(/.*)? u:object_r:my_data_file:s0
# In device/myvendor/mydevice-sepolicy/my_custom_policy.te
type my_daemon_t, domain;
type my_daemon_exec, file_type, exec_type;
type my_data_file, file_type, data_file_type;
# Associate your daemon's process with its domain
init_daemon_domain(my_daemon_t)
# Grant necessary permissions
allow my_daemon_t my_data_file:dir { create search add_name write remove_name rmdir };
allow my_daemon_t my_data_file:file { create getattr setattr read write append unlink open };
# If your daemon needs to execute other binaries, e.g., toolbox commands
allow my_daemon_t system_file:file { execute_no_trans };
allowxperm my_daemon_t self:socket { ioctl }; # Example for socket operations
Remember to define specific types for executables (`exec_type`), files (`file_type`), and directories (`dir_type`), and use existing types where appropriate (e.g., `system_file` for `/system` binaries). The `init_daemon_domain` macro helps integrate your new daemon type into the init process’s domain transition rules.
Integrating and Compiling Your Custom Policy
Once your `.te` file is ready, you need to integrate it into your ROM’s build system.
- Add to `sepolicy.mk`: Navigate to your device’s `sepolicy` directory (e.g., `device/myvendor/mydevice-sepolicy`). Edit the `sepolicy.mk` file (or equivalent) to include your new `.te` file. For LineageOS, this is often done in `BoardConfig.mk` by adding your `.te` file to `BOARD_SEPOLICY_DIRS` or directly listing in `BOARD_SEPOLICY_UNION` (though `BOARD_SEPOLICY_DIRS` is preferred for modularity).
# Example addition to BoardConfig.mk (or a referenced .mk file) BOARD_SEPOLICY_DIRS += n device/myvendor/mydevice-sepolicyThen, ensure your `device/myvendor/mydevice-sepolicy/Android.mk` is set up to pick up `.te` files:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := my_device_sepolicy LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := n file_contexts n my_custom_policy.te n # ... other .te files include $(BUILD_SEPOLICY) - Update `file_contexts`: If you introduced new file types (like `my_data_file`), you must define their context in `device/myvendor/mydevice-sepolicy/file_contexts`. This file maps file paths to SELinux contexts.
# Example entry in file_contexts /data/misc/mydata(/.*)? u:object_r:my_data_file:s0 - Build AOSP/ROM: Now, recompile your entire custom ROM. This process will include your new SELinux rules.
source build/envsetup.sh lunch lineage_mydevice-userdebug # or similar m make -j$(nproc)
Testing and Debugging
After flashing the new ROM, test the functionality that was previously failing. If it still fails, repeat the process of collecting denials. SELinux policy development is often an iterative process.
- Check for New Denials: Always collect fresh logs after each policy change.
- Temporary Permissive Mode: If you’re stuck, you can temporarily set SELinux to permissive mode for a specific domain to isolate issues. This should *never* be done for the entire system in a production environment. For instance, to set `my_daemon_t` to permissive:
adb shell su -c 'setenforce 0' # Or if setenforce 0 fails, use sepolicy-inject (more targeted): adb push sepolicy-inject /data/local/tmp adb shell /data/local/tmp/sepolicy-inject -s my_daemon_t -p -P /sys/fs/selinux/policyNote: `sepolicy-inject` requires a debuggable build and often root, and changes are not persistent across reboots.
- Use `sesearch`: This tool (part of AOSP) allows you to query the compiled SELinux policy. It’s invaluable for understanding existing rules.
sesearch -A -s untrusted_app -t my_device_file -c file -p read
Best Practices and Security Considerations
- Principle of Least Privilege: Always grant the absolute minimum permissions required. Overly broad rules compromise security.
- Review `audit2allow` Output: Never use `audit2allow` output directly without careful review and refinement.
- Use Existing Types: Leverage existing SELinux types and attributes defined in the AOSP policy whenever possible to maintain consistency and reduce complexity.
- Context Matters: Pay close attention to source context (`scontext`) and target context (`tcontext`) when writing rules.
- Avoid `dontaudit`: While `dontaudit` rules suppress logging of denials, they don’t fix the underlying policy issue. Use them sparingly and only when you are absolutely certain a denial is benign and cannot be fixed with a proper `allow` rule.
- Test Thoroughly: Fully test your custom ROM with the new policies enabled to ensure no regressions or unexpected behaviors arise.
Building custom SELinux rules is an advanced skill that significantly enhances your ability to tailor Android custom ROMs to your specific needs while maintaining a high level of security. By following this guide, you can confidently address SELinux denials and ensure your custom software and hardware components operate correctly under an enforcing policy.
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 →