Introduction: The Unseen Guardian of Android Security
Security-Enhanced Linux (SELinux) is a mandatory access control (MAC) system implemented in the Linux kernel. For Android, SELinux isn’t just a feature; it’s a fundamental pillar of its security model, enhancing protection beyond traditional Discretionary Access Control (DAC) permissions. While DAC relies on user/group ownership and read/write/execute bits, SELinux enforces policies based on security contexts (labels) assigned to every process and file. This means even a root user can be restricted by SELinux if the policy dictates it. For custom ROM developers, understanding and modifying SELinux policies is crucial for integrating new features, hardware, or services securely.
This deep dive will guide you through the architecture of SELinux in Android, how to diagnose policy violations, and, most importantly, how to confidently write and integrate your own SELinux policy rules into an AOSP-based custom ROM.
Deciphering SELinux Policy: The AOSP Architecture
In AOSP, SELinux policies are defined within the system/sepolicy directory, with device-specific policies often found in device/<vendor>/<device>/sepolicy or vendor/etc/selinux. The policy is composed of several key elements:
- Types (or Domains): Labels assigned to processes (domains) and objects (files, sockets, devices). For example,
initis a process type, andsystem_fileis a file type. - Attributes: Groups of types. For instance,
domainis an attribute that includes all process types. - Classes: Represent different kinds of objects that can be protected (e.g.,
file,socket,service_manager). - Permissions: Specific actions that can be performed on an object (e.g.,
read,write,openfor files;add,findforservice_manager).
The policy source files are typically written in Type Enforcement (TE) language and compiled into a binary policy file loaded by the kernel during boot. Key file types include:
.te(Type Enforcement files): Define types, attributes, and the rules governing interactions between types..fc(File Contexts files): Map file paths to their SELinux security contexts..rc(Init script files): Android’sinitprocess uses these to start services and set initial contexts.
How Android Loads and Enforces Policies
During the boot process, the kernel loads the SELinux policy from the boot image (specifically, /sepolicy or /vendor/etc/selinux/precompiled_sepolicy). The init process then applies file contexts defined in file_contexts files to the filesystem and sets contexts for new services it spawns. All subsequent process creations, file accesses, and inter-process communications are then mediated by the loaded SELinux policy.
Identifying SELinux Denials: Your First Step to Customization
When a process attempts an action not explicitly allowed by the SELinux policy, the kernel logs an Access Vector Cache (AVC) denial. These denials are the starting point for any policy customization.
You can view these denials using adb logcat or adb shell dmesg:
adb shell dmesg | grep 'avc: denied'
A typical denial message looks like this:
avc: denied { read } for pid=1234 comm="my_daemon" name="mydevice" dev="tmpfs" ino=5678 scontext=u:r:my_daemon:s0 tcontext=u:object_r:my_device:s0 tclass=chr_file permissive=0
Let’s break down this denial:
{ read }: The permission being denied.pid=1234 comm="my_daemon": The process ID and name of the subject (the entity attempting the action).name="mydevice" dev="tmpfs" ino=5678: The object being acted upon.scontext=u:r:my_daemon:s0: The security context of the subject (scontext). Here,my_daemonis the domain.tcontext=u:object_r:my_device:s0: The security context of the target object (tcontext). Here,my_deviceis the type.tclass=chr_file: The class of the target object (e.g., file, directory, socket, chr_file for character device).permissive=0: Indicates that SELinux is in enforcing mode (1would mean permissive).
This denial tells us that a process running in the my_daemon domain tried to read a character device with the type my_device, and the policy did not allow it.
Crafting Custom SELinux Policies for Your ROM
Let’s consider a scenario where you’ve added a custom daemon, my_daemon, which needs to run as a specific SELinux type and access a custom character device, /dev/mydevice.
1. Define the New Type and File Context
First, create a new .te file for your daemon, e.g., device/<vendor>/<device>/sepolicy/my_daemon.te:
# my_daemon.te type my_daemon, domain; type my_device, dev_type; init_daemon_domain(my_daemon) allow my_daemon self:capability { setuid setgid net_raw }; allow my_daemon my_device:chr_file { read write open ioctl }; allow my_daemon system_file:file execute_no_trans; # If my_daemon runs from /vendor/bin allow my_daemon vendor_file:file execute_no_trans; # Example if my_daemon needs to connect to logd allow my_daemon logd:unix_stream_socket connectto;
Explanation:
type my_daemon, domain;: Declaresmy_daemonas a new process type (domain).type my_device, dev_type;: Declaresmy_deviceas a new device type.init_daemon_domain(my_daemon): A macro that sets common permissions for daemons started byinit.allow my_daemon self:capability { ... };: Grants capabilities to the daemon itself.allow my_daemon my_device:chr_file { read write open ioctl };: This is the critical rule. It allows processes in themy_daemondomain toread,write,open, and performioctloperations on character files (chr_file) labeled withmy_device.allow my_daemon system_file:file execute_no_trans;: Allows the daemon to execute itself from/system/bin. Adjust if your daemon is in/vendor/bin.
Next, define the file context for your custom device in device/<vendor>/<device>/sepolicy/file_contexts (or add to an existing one like vendor_file_contexts):
/dev/mydevice u:object_r:my_device:s0
This line tells SELinux to label the file /dev/mydevice with the type my_device.
2. Integrate into Init Scripts
Finally, ensure your daemon starts with the correct SELinux context. In your init.rc or a device-specific init.<device>.rc file (e.g., device/<vendor>/<device>/init.<device>.rc):
service my_daemon /vendor/bin/my_daemon class core user root group root seclabel u:r:my_daemon:s0 oneshot
The seclabel u:r:my_daemon:s0 line is crucial; it instructs init to run my_daemon in the my_daemon SELinux domain.
Building and Flashing Your Modified Policy
After making these changes, you need to rebuild your AOSP project. Navigate to your AOSP root directory and run:
source build/envsetup.sh lunch <your_device_target> make -j$(nproc)
This will compile the entire AOSP system, including your SELinux policy changes. The new policy is typically bundled into boot.img and/or vendor.img. You will then need to flash these images to your device:
adb reboot bootloader fastboot flash boot boot.img fastboot flash vendor vendor.img fastboot reboot
Always back up your existing partitions before flashing.
Best Practices and Security Implications
- Principle of Least Privilege: Only grant the absolute minimum permissions necessary. Avoid broad
allowrules likeallow my_daemon *:file *;. - Use Specific Types: Create specific types for your custom services and devices rather than reusing existing ones, which can lead to over-permissioning.
- Audit and Test Thoroughly: After implementing changes, run your system in permissive mode temporarily (
adb shell setenforce 0) to log all denials without blocking operations. Analyze these logs, refine your policy, and then switch back to enforcing mode (adb shell setenforce 1). - Understand
neverallowrules: AOSP contains manyneverallowrules designed to prevent common security flaws. Your custom policy must not violate any of these. Violations will cause the policy compilation to fail. - Leverage Existing Attributes and Interfaces: AOSP provides many common attributes and policy interfaces (macros) that encapsulate sets of permissions. Use them where appropriate to keep your policy concise and maintainable. For example,
allow my_app app_data_file:dir { create search add_name };could be simplified using an interface if available.
Conclusion: Empowering Secure Customization
Mastering SELinux for Android might seem daunting initially, but it’s an indispensable skill for any custom ROM developer committed to building secure, robust, and privacy-respecting systems. By understanding how AOSP structures its policies, diligently analyzing AVC denials, and applying the principle of least privilege, you can confidently extend Android’s security model to accommodate your unique customizations without compromising the device’s integrity. Embrace SELinux as your partner in creating a more secure Android experience.
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 →