Introduction to SELinux in Android
Security-Enhanced Linux (SELinux) is a mandatory access control (MAC) system that enforces security policies on Android devices. Unlike discretionary access control (DAC), where user permissions dictate access, SELinux operates on a ‘default deny’ principle: if a specific permission is not explicitly granted, it is denied. This robust security model significantly strengthens Android’s integrity, isolating applications, system services, and hardware components from unauthorized access.
For custom ROM developers, mastering SELinux policy writing is crucial. While AOSP (Android Open Source Project) provides a baseline, custom features, drivers, and services often introduce new interactions that the default policy doesn’t cover. Incorrectly configured SELinux policies can lead to boot failures, service crashes, or even security vulnerabilities, turning a promising custom ROM into a frustrating experience.
Understanding SELinux Denials
The first step in writing or debugging SELinux policy is to identify what’s being denied. When SELinux blocks an operation, it logs an Access Vector Cache (AVC) denial. These denials are your primary guide to understanding what permissions are missing.
Locating AVC Denials
AVC denials are typically found in the kernel ring buffer and the audit log. You can access them via ADB:
adb shell dmesg | grep avc
Or through `logcat`, which might include more context from `auditserver`:
adb shell logcat -b all | grep avc
A typical AVC denial looks like this:
avc: denied { read } for pid=1234 comm="my_service" name="config.txt" dev="mmcblk0p1" ino=5678 scontext=u:r:my_service_domain:s0 tcontext=u:object_r:system_file:s0 tclass=file permissive=0
This log entry tells us:
- `denied { read }`: The permission that was denied.
- `pid=1234 comm=”my_service”`: The process (source) attempting the action.
- `name=”config.txt”`: The specific file or resource involved.
- `scontext=u:r:my_service_domain:s0`: The security context of the source process (domain).
- `tcontext=u:object_r:system_file:s0`: The security context of the target resource (type).
- `tclass=file`: The class of the target resource (e.g., file, directory, socket).
Essential Tools for Auditing and Debugging
1. Permissive Mode (`setenforce 0`)
During initial development or debugging, switching SELinux to permissive mode can save a lot of time. In permissive mode, SELinux logs denials but doesn’t enforce them. This allows your system to boot and function while you collect a comprehensive list of all potential denials.
adb shell su 0 setenforce 0
Remember to revert to enforcing mode (`setenforce 1`) once debugging is complete.
2. `audit2allow`
`audit2allow` is a powerful tool for generating SELinux policy rules from AVC denials. It parses audit logs and suggests `allow` rules. While incredibly useful, it must be used with caution, as blindly applying its suggestions can lead to overly permissive policies.
# On your host machine, pull the kernel log
adb shell su 0 dmesg > dmesg.log
# Or combine with logcat for more detail
adb shell su 0 logcat -b all > logcat.log
# Create a policy module from the log
cat dmesg.log | audit2allow -M my_custom_policy
# This will generate:
# my_custom_policy.te (type enforcement rules)
# my_custom_policy.fc (file context rules)
# my_custom_policy.if (interface definitions, if any)
Always review the generated `.te` files. Look for unnecessary permissions or broad `allow` rules that could weaken security. For example, `allow my_service_domain system_file:file { read write open };` might be too broad if your service only needs to read a specific configuration file.
3. `sealert` (SELinux Alert Browser)
While less common in Android-specific development compared to desktop Linux, `sealert` can sometimes provide more user-friendly explanations of denials and suggest solutions. It’s often part of the `setroubleshoot` package.
Writing Custom SELinux Policy Rules
SELinux policy is defined in `.te` (type enforcement), `.fc` (file contexts), and `.if` (interface) files.
1. Type Enforcement (`.te` files)
These files define domains, types, and the permissions between them. Key directives include:
- `type my_service_domain, domain;`: Declares a new domain for your service.
- `type my_config_file_type, file_type, system_file_type;`: Declares a new type for your configuration file.
- `allow my_service_domain my_config_file_type:file { read getattr open };`: Explicitly allows `my_service_domain` to read, get attributes, and open files of type `my_config_file_type`.
- `neverallow`: Crucial for enforcing stricter security by explicitly forbidding certain interactions, even if another rule would grant it.
2. File Contexts (`.fc` files)
These files map file paths to SELinux types. For example, to label your `config.txt` file:
/vendor/etc/my_service/config.txt u:object_r:my_config_file_type:s0
3. Integrating Policy into the Android Build System
Custom ROMs typically place their SELinux policies in `device/your_vendor/your_device/sepolicy` or `vendor/your_vendor/your_device/sepolicy`. These directories contain the `.te`, `.fc`, and other related files.
The Android build system (`BOARD_SEPOLICY_DIRS`, `BOARD_VENDOR_SEPOLICY_DIRS`, etc.) automatically compiles these policies into the final `sepolicy` image.
Automating Policy Development Workflow
Manual policy writing can be tedious. Here’s a structured, automated approach for faster development:
Phase 1: Initial Bring-up (Permissive Mode Collection)
- Boot your custom ROM in permissive mode (`setenforce 0`).
- Run all new services, applications, and features you’ve introduced. Try to trigger all possible code paths and interactions.
- Collect all AVC denials after a thorough test session:
adb shell su 0 dmesg > full_denials.log
Phase 2: Generate Draft Policy
Use a script to process the `full_denials.log` and generate initial `.te` files. This script should be run on your host machine.
#!/bin/bash
LOG_FILE="full_denials.log"
OUTPUT_DIR="policy_drafts"
mkdir -p "$OUTPUT_DIR"
# Extract unique AVC denials and generate policy per unique scontext/tcontext pair
grep "avc: denied" "$LOG_FILE" |
awk -F'scontext=' '{print $2}' |
awk -F' tcontext=' '{print $1" "$2}' |
awk -F' ' '{print $1" "$2}' |
sort -u |
while read scontext tcontext;
do
echo "Processing $scontext -> $tcontext"
# Filter denials specific to this scontext/tcontext pair
grep "scontext=${scontext}" "$LOG_FILE" |
grep "tcontext=${tcontext}" |
audit2allow -M "${OUTPUT_DIR}/${scontext}_${tcontext}_policy"
done
# Optional: Generate a single comprehensive policy (less granular, requires more review)
# cat "$LOG_FILE" | audit2allow -M "${OUTPUT_DIR}/comprehensive_policy"
echo "Draft policies generated in $OUTPUT_DIR"
Phase 3: Refine and Review
- Manual Review is Critical: Go through each generated `.te` file. Look for overly broad `allow` rules. For instance, if `audit2allow` suggests `allow my_domain system_file:file { read write execute entrypoint };`, but your service only needs to read its config, trim it down.
- Consolidate and Generalize: Instead of many specific rules, look for opportunities to use existing types or define new, more generic types (e.g., `my_app_data_file`) and grant permissions to them.
- Introduce `neverallow` Rules: Proactively prevent specific, dangerous interactions that should never happen. For example, `neverallow system_server system_file:file execmod;` to prevent modification of executable system files.
- Create `.fc` files: Ensure all custom files and directories introduced by your ROM have correct contexts defined in `.fc` files.
Phase 4: Enforcement and Iteration
- Rebuild and Test: Integrate the refined policies into your ROM, rebuild, and flash.
- Enforcing Mode (`setenforce 1`): Boot in enforcing mode.
- Systematic Testing: Go through all your custom features. Expect new denials as you uncover more interactions.
- Repeat: Collect new denials, refine policy, rebuild, and test. This iterative process continues until your system runs stably in enforcing mode with minimal or no AVC denials.
Best Practices for Robust SELinux Policies
- Least Privilege: Grant only the absolute minimum permissions required for a component to function.
- Modularity: Organize your policy into logical modules (e.g., per service or feature) for easier management and debugging.
- Utilize Existing Types: Leverage AOSP’s extensive set of predefined types and domains whenever possible, rather than creating new ones unnecessarily.
- Avoid `dontaudit` when possible: While `dontaudit` hides denials, it also hides potential security issues. Use it sparingly, only for known, harmless, noisy denials that cannot be resolved otherwise.
- Version Control: Treat your SELinux policy files as critical source code. Keep them under version control to track changes and facilitate rollbacks.
- Continuous Integration: Integrate SELinux policy validation into your CI pipeline. Automated tests can help catch regressions early.
By adopting these tools and techniques, custom ROM developers can streamline the often-complex process of SELinux policy creation, leading to more secure, stable, and faster-to-develop Android distributions.
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 →