Introduction to Advanced SELinux in Android
SELinux (Security-Enhanced Linux) is a mandatory access control (MAC) system implemented in the Linux kernel. In Android, it plays a pivotal role in ensuring system integrity and application sandbox isolation. While basic SELinux policy involves assigning types to processes and files and then defining rules for their interactions, advanced policy writing leverages a powerful concept: attributes. For custom ROM developers, mastering SELinux attributes is essential for creating scalable, maintainable, and robust security policies that go beyond simple denials, providing fine-grained control over system components.
Understanding SELinux Types and Attributes
Types: The Foundation of SELinux
At its core, SELinux operates by assigning a label, known as a ‘type’, to every subject (process) and object (file, directory, socket, etc.) within the system. These types are then used in Access Vector Cache (AVC) rules to determine what interactions are permitted. For example, a web server process might have the type httpd_t, and its content files might have httpd_content_t. A rule like allow httpd_t httpd_content_t:file { read getattr }; would permit the web server to read its content.
Attributes: Enhancing Policy Scalability
While types are specific, attributes are generic identifiers that can be assigned to multiple types. Think of an attribute as a ‘role’ or a ‘category’ that various types might share. By defining rules for an attribute, you implicitly apply those rules to all types that possess that attribute. This dramatically reduces policy complexity and improves maintainability, especially in complex systems like Android where hundreds of types exist.
For instance, instead of writing individual rules for app_data_file, vendor_data_file, and system_data_file to allow a debugging tool to read them, you could define an attribute debuggable_data_attr and assign it to all relevant data types. Then, a single rule permitting the debugging tool to access debuggable_data_attr would suffice.
Declaring and Using Attributes
Attributes are declared using the attribute keyword in a SELinux policy file (e.g., .te file). Once declared, types are associated with this attribute using the typeattribute keyword.
# Declare a new attribute for services that need network access.tagattribute network_service_attr; # Assign specific service types to this attribute.typeattribute system_server_t network_service_attr;typeattribute wifi_hal_service_t network_service_attr; # Now, a single rule can apply to both system_server_t and wifi_hal_service_tallow network_service_attr self:socket create;allow network_service_attr self:netlink_route_socket create;
In this example, any rule applied to network_service_attr will automatically apply to system_server_t and wifi_hal_service_t. This modularity is crucial for managing large policies.
Practical Implementation: Securing a Custom Android Service
Let’s walk through an example of securing a custom daemon, my_daemon, in an Android custom ROM using advanced SELinux concepts.
Step 1: Define New Types and Attributes
First, we need to define the specific types for our daemon and its associated files. We’ll also create a custom attribute to categorize it.
# In a new .te file (e.g., my_daemon.te)type my_daemon_t; # The domain type for the running processtype my_daemon_exec_t; # The type for the daemon's binary executabletype my_daemon_data_file; # The type for data files created/accessed by the daemon # Declare an attribute for custom application daemonsattribute custom_app_daemon_attr; # Assign our daemon's type to this attribute. This allows common rules to apply.typeattribute my_daemon_t custom_app_daemon_attr;
Step 2: Crafting Initial SELinux Rules
Next, we write the rules that govern our daemon’s behavior. We’ll leverage existing SELinux macros for common patterns and use our newly defined attribute.
# my_daemon_t domain (the running process) # Utilize init_daemon_domain to set up basic daemon permissionsinit_daemon_domain(my_daemon_t) # Allow the init process to transition to my_daemon_t when executing my_daemon_exec_tdomain_auto_trans(init, my_daemon_exec_t, my_daemon_t) # Basic capabilities required by a typical daemon (adjust as needed)allow my_daemon_t self:capability { dac_override setgid setuid sys_nice };allow my_daemon_t self:process { signal sigkill }; # Allow my_daemon_t to access its data filesallow my_daemon_t my_daemon_data_file:file { getattr read write open create unlink rename };allow my_daemon_t my_daemon_data_file:dir { getattr read write open add_name remove_name search }; # Declare my_daemon_data_file as a file typefile_type(my_daemon_data_file) # If our daemon needs to communicate over sockets (e.g., AF_UNIX)allow my_daemon_t self:socket { create bind listen accept };allow my_daemon_t my_daemon_socket:sock_file { create unlink setattr };unix_socket_send(my_daemon_t, system_server_t); # Example: If my_daemon_t needs to read system propertiesallow my_daemon_t property_socket:sock_file write;allow my_daemon_t property_type:property_service { set get };
Step 3: Defining File Contexts
For SELinux to correctly label your files, you need to define their contexts in a file_contexts file (e.g., my_daemon_file_contexts in your sepolicy directory). These regex patterns map file paths to SELinux types.
# In my_daemon_file_contexts/vendor/bin/my_daemon u:object_r:my_daemon_exec_t:s0/data/misc/my_daemon(/.*)? u:object_r:my_daemon_data_file:s0/dev/socket/my_daemon_socket u:object_r:my_daemon_socket:s0
Step 4: Auditing and Debugging Your Policy
Developing SELinux policies is an iterative process of writing rules, testing, and debugging denials. Android uses `auditd` to log denials to the kernel ring buffer, which can be viewed with `dmesg`.
# Check current enforcement modegetenforce # If in 'enforcing' mode, switch to 'permissive' for easier debugging. # Denials are logged but not enforced.setenforce 0 # Start your custom daemon and interact with it to trigger operations. # Then, check kernel logs for AVC denials.dmesg | grep 'avc: denied' # Example denial output: # avc: denied { read } for pid=1234 comm="my_daemon" name="some_file" dev="mmcblk0pXY" ino=12345 scontext=u:r:my_daemon_t:s0 tcontext=u:object_r:system_file:s0 tclass=file permissive=1
From the denial, you can identify the `scontext` (source type, `my_daemon_t`), `tcontext` (target type, `system_file`), `tclass` (object class, `file`), and the `denied { permission }` (read). You would then add a corresponding rule:
allow my_daemon_t system_file:file read;
Tools like `audit2allow` can automate rule generation from audit logs, but *use it with extreme caution*. It often generates overly permissive rules, so always review and refine its output for least privilege.
# Capture audit logs into a file (requires root)logcat -b kernel -d | grep 'avc: denied' > audit.log# Generate initial policy rules (review carefully!)audit2allow -a -M my_daemon_policy -i audit.log# This creates my_daemon_policy.te and my_daemon_policy.cil.
The `sesearch` utility (part of `sepolicy-tools`) is invaluable for understanding existing policies and verifying your rules. It allows you to query the compiled SELinux policy.
# List all rules where my_daemon_t is the source contextsesearch -A -s my_daemon_t # List all rules that allow my_daemon_t to read files of type system_filesesearch -A -s my_daemon_t -t system_file -c file -p read
Step 5: Integrating and Enforcing the Policy
To integrate your policy into a custom ROM (AOSP based), place your `my_daemon.te` and `my_daemon_file_contexts` files in the appropriate `sepolicy` directory (e.g., `device/<vendor>/<device>/sepolicy/private`). You might need to add `BOARD_SEPOLICY_DIRS += device/<vendor>/<device>/sepolicy/private` to your `BoardConfig.mk`. After recompiling and flashing your ROM, set SELinux to enforcing mode (`setenforce 1`) and verify everything works as expected.
Best Practices for SELinux Policy Development
- Principle of Least Privilege: Grant only the minimum permissions necessary for a component to function. Overly broad rules introduce security vulnerabilities.
- Leverage Attributes for Reusability: Group similar types with attributes to create modular and manageable policies.
- Thorough Auditing and Testing: Never deploy a policy without extensive testing in permissive mode and careful review of all denials.
- Use Existing Types and Macros: Android’s SELinux policy provides many predefined types and macros (e.g., `init_daemon_domain`, `file_type`). Reuse them to maintain consistency and reduce errors.
- Document Your Policy: Add comments to your `.te` files explaining the rationale behind complex rules.
Conclusion
Mastering SELinux types and attributes empowers custom ROM developers to build truly secure Android systems. By strategically using attributes, you can simplify complex policies, enhance maintainability, and ensure that new components integrate seamlessly into Android’s robust security model. While the learning curve can be steep, the benefits of fine-grained control and a hardened system are invaluable for advanced Android system securing, hardening, and privacy initiatives.
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 →