Introduction
Security-Enhanced Linux (SELinux) is a mandatory access control (MAC) system implemented at the kernel level, crucial for bolstering the security of Android devices. While Google provides a robust default SELinux policy, custom Android ROM developers often encounter a common nemesis: Access Vector Cache (AVC) denials. These denials, often cryptic, halt system functionality or applications, turning the path to a fully secure and functional custom build into a challenging maze. This expert guide dives deep into mastering SELinux auditing, equipping you with the knowledge and tools to diagnose, understand, and resolve AVC denials in your Android custom builds, moving from a permissive to a fully enforcing security posture.
Understanding SELinux in Android
Unlike discretionary access control (DAC) where file owners dictate permissions, SELinux operates on the principle of least privilege, ensuring that every process and resource access is explicitly allowed by a security policy. In Android, SELinux categorizes every process, file, and resource with a security context. When an operation is attempted, the kernel’s SELinux module checks if the source context (scontext) is permitted to perform the operation (perms) on the target context (tcontext) of a specific class (tclass). If not, an AVC denial is logged, and the operation is blocked.
SELinux Modes
- Enforcing: The default and most secure mode. All unauthorized actions are blocked, and AVC denials are logged.
- Permissive: Unauthorized actions are logged as AVC denials but are not blocked. This mode is invaluable for policy development and debugging.
- Disabled: SELinux is completely turned off. This is highly discouraged for security reasons.
The Anatomy of an AVC Denial
AVC denials are typically found in the kernel ring buffer (`dmesg`) or `logcat` if the audit daemon forwards them to userspace. A typical AVC log entry looks like this:
audit: type=1400 audit(1678886400.000:123): avc: denied { read } for pid=1234 comm="my_daemon" name="my_config.conf" dev="mmcblk0pXX" ino=5678 scontext=u:r:my_daemon_type:s0 tcontext=u:object_r:system_file:s0 tclass=file permissive=0
Let’s break down the critical components:
avc: denied { read }: The permission that was denied (e.g., read, write, execute, connect, bind).pid=1234 comm="my_daemon": The process ID and command that initiated the action.scontext=u:r:my_daemon_type:s0: The security context of the subject (the process trying to access something). Here,my_daemon_typeis the crucial type.tcontext=u:object_r:system_file:s0: The security context of the target (the resource being accessed). Here,system_fileis the type.tclass=file: The class of the target (e.g., file, dir, socket, service).permissive=0: Indicates SELinux is in enforcing mode (1for permissive).
Setting Up Your Build Environment for Auditing
To effectively audit SELinux, you’ll need a userdebug or eng build. These builds include debugging tools and allow changing SELinux mode at runtime. You’ll also need to structure your custom policy files.
1. Build Type and Custom Policy Integration
Ensure your BoardConfig.mk or device-specific configuration includes:
# Enable custom SELinux policy directoriesADDITIONAL_SEPOLICY_DIRS += device/<vendor>/<device>/sepolicyBOARD_SEPOLICY_DIRS += device/<vendor>/<device>/sepolicy# For Userdebug/Eng buildsENG_BUILD_TYPE := userdebug
Create your custom policy directory, e.g., device/<vendor>/<device>/sepolicy/. Inside, you’ll typically have file_contexts, genfs_contexts, property_contexts, service_contexts, and policy.te (or multiple .te files).
2. Temporarily Switching to Permissive Mode
While developing, it’s often easiest to switch to permissive mode to identify all denials without blocking system operations. This can be done via ADB:
adb shell setenforce 0
This change is temporary and reverts on reboot. For persistent permissive mode during development, modify your init.rc (or a custom init.<device>.rc) to include setenforce 0 before any critical services start, or modify the boot parameters.
Auditing Tools and Techniques
1. Capturing AVC Denials
The primary tools are logcat and dmesg:
logcat: Captures system logs, including audit events.adb logcat -b all | grep -E 'avc:|type=1400'dmesg: Directly reads the kernel ring buffer.adb shell dmesg | grep 'avc:'
For more structured analysis, consider pulling the kernel logs:
adb shell "cat /proc/kmsg > /sdcard/kmsg_log.txt"adb pull /sdcard/kmsg_log.txt .
2. Using audit2allow
audit2allow is a powerful tool to generate SELinux policy rules from AVC denials. While it can be run on the host with a full AOSP build setup, you can also use a simplified approach:
# On the device, capture recent denialsdmesg | grep 'avc: denied' > /sdcard/avc_denials.txtadb pull /sdcard/avc_denials.txt .# On your host machine, with audit2allow installedaudit2allow -i avc_denials.txt
This will output suggested `allow` rules. **Caution:** Always review these rules. `audit2allow` can be overly broad; refine the rules to follow the principle of least privilege.
Step-by-Step AVC Resolution
Let’s walk through a typical scenario: your custom daemon `my_daemon` can’t read its configuration file.
1. Identify the Denial
After encountering the issue (e.g., `my_daemon` crashes or fails to start), check `dmesg` or `logcat`:
audit: type=1400 audit(1678886400.000:123): avc: denied { read } for pid=1234 comm="my_daemon" name="my_config.conf" dev="mmcblk0pXX" ino=5678 scontext=u:r:my_daemon_type:s0 tcontext=u:object_r:system_file:s0 tclass=file permissive=0
2. Analyze Components
- Subject (scontext):
my_daemon_type(the type assigned to your daemon process). - Target (tcontext):
system_file(the type assigned tomy_config.conf). - Permission (perms):
read. - Class (tclass):
file.
The denial clearly states `my_daemon_type` is trying to `read` a `file` typed as `system_file`, and it’s denied.
3. Formulate a Policy Rule
Based on the analysis, you need to allow `my_daemon_type` to read `system_file` resources:
allow my_daemon_type system_file:file read;
If `my_config.conf` were a specific, unique file that shouldn’t be `system_file`, you’d first create a new type for it (e.g., `my_daemon_config_file`) and then assign it in `file_contexts`.
4. Add the Rule to Your Custom Policy
Navigate to your custom SELinux policy directory (e.g., device/<vendor>/<device>/sepolicy/).
a. Define New Types (if necessary)
If `my_daemon_type` doesn’t exist, create a `my_daemon.te` file:
# device/<vendor>/<device>/sepolicy/my_daemon.tetype my_daemon_type;type my_daemon_exec, exec_type, file_type, system_file_type;init_daemon_domain(my_daemon_type)
And add the new context for the daemon’s executable in `file_contexts`:
# device/<vendor>/<device>/sepolicy/file_contexts/system/bin/my_daemon u:object_r:my_daemon_exec:s0
b. Add the `allow` Rule
Add the formulated rule to an appropriate `.te` file, for example, a new `my_daemon_access.te` or your main `policy.te`:
# device/<vendor>/<device>/sepolicy/my_daemon_access.teallow my_daemon_type system_file:file read;
Don’t forget to include these new `.te` files in your `BoardConfig.mk` if you use `BOARD_SEPOLICY_DIRS` and individual `.te` file inclusion isn’t automatic.
5. Rebuild and Test
Rebuild your SELinux policy and flash your device:
# From AOSP root directory:make selinux_policy# Or a full build if needed:make -j$(nproc)
After flashing, reboot the device, put SELinux back into enforcing mode (`setenforce 1`), and re-test the functionality. If new denials appear, repeat the process.
Best Practices for Policy Writing
- Principle of Least Privilege: Grant only the necessary permissions. Avoid overly broad rules like `allow my_daemon_type *:*:* { * };`.
- Use Existing Types: Leverage existing SELinux types and attributes defined in AOSP whenever possible to reduce policy complexity and maintain consistency.
- Custom Types for Custom Components: Define specific types for your custom services, binaries, and files.
- Context Labeling: Ensure all your custom files, directories, and processes have correct SELinux contexts defined in
file_contexts,property_contexts, etc. dontaudit(Use with Caution): For known, benign, and noisy denials that cannot be easily resolved or are outside your control (e.g., from vendor blobs), you can usedontauditrules. However, this hides potential security issues, so use it sparingly and only after thorough investigation.- Documentation: Comment your custom policy files clearly explaining the purpose of each rule.
Conclusion
Mastering SELinux auditing is a fundamental skill for anyone involved in developing custom Android ROMs. By systematically identifying, analyzing, and resolving AVC denials, you not only ensure the smooth operation of your custom features but also significantly enhance the overall security posture of your Android device. It’s an iterative process that demands patience and attention to detail, but the result is a robust, secure, and stable custom build capable of operating fully in SELinux enforcing mode, providing superior protection against security vulnerabilities.
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 →