Introduction: Why Extend Android SELinux?
Android’s security model is built on several layers, with SELinux (Security-Enhanced Linux) serving as a critical pillar for Mandatory Access Control (MAC). By default, Android’s SELinux policy is highly restrictive, enforcing the principle of least privilege across the entire system. While this provides robust protection, it poses a challenge for developers and system integrators who introduce custom applications, system services, or hardware abstractions (HALs) that require unique permissions not covered by the default policy. Attempting to run such components without proper SELinux context will inevitably lead to runtime failures and ‘permission denied’ errors.
This article provides an expert-level, step-by-step guide on how to extend the Android SELinux policy to grant specific, custom permissions to your own apps or system services. We will delve into defining new types, creating domains, granting necessary access, and integrating these changes seamlessly into the Android Open Source Project (AOSP) build system. The goal is to enable your custom components to function correctly while maintaining the system’s overall security posture through precise policy definition.
Understanding Android SELinux Fundamentals
Key Concepts: Types, Domains, and Policy Rules
At its core, SELinux operates by labeling every subject (processes, known as ‘domains’) and object (files, devices, properties, sockets, etc., known as ‘types’) with a security context. The policy then defines rules that dictate which source contexts (domains) can perform which operations (permissions) on which target contexts (types) of a given class.
- Types (Labels): Every file, process, and IPC object has an associated type. For processes, this type is also referred to as a ‘domain’. Examples include
untrusted_app,system_app,init,system_server,vendor_app, etc. For files, examples aresystem_file,device,tmpfs. - Classes: Represent abstract categories of system resources, such as
file,dir,socket,property_service,binder,chr_file(character device file). Each class has a predefined set of permissions (e.g.,read,write,ioctlforfileclass;call,transferforbinderclass). - Rules: The policy is composed of rules, typically in the format:
allow source_domain target_type:class { permissions };These rules explicitly allow interactions. If no explicit `allow` rule exists, the interaction is implicitly denied.
AVC Denials and the Enforcement Process
When an application or service attempts an operation not explicitly permitted by the SELinux policy, the kernel’s SELinux module will block the action and log an Access Vector Cache (AVC) denial. These denials are crucial for debugging and developing SELinux policies, providing precise information about the blocked operation, the requesting process (source context), the target resource (target context), and the requested permission.
Android’s SELinux operates in ‘enforcing’ mode by default. This means all unauthorized actions are blocked. In contrast, ‘permissive’ mode would allow actions but still log denials, which is sometimes used during early development but should never be deployed in production.
Prerequisites for SELinux Policy Customization
Before proceeding, ensure you have the following:
- AOSP Build Environment: A functional AOSP build environment capable of compiling your target Android version. This is essential for building and flashing your customized SELinux policy.
- Basic SELinux Knowledge: A foundational understanding of SELinux concepts (types, domains, rules, AVC denials) will greatly aid in troubleshooting.
- Rooted Android Device or Emulator (for testing/debugging): Access to a device where you can flash custom builds and use `adb` to inspect logs and run commands.
Scenario: Securing a Custom System Service
Let’s consider a practical scenario: You are developing a custom Android system service, named my_special_service, that runs as a dedicated daemon and requires specific, elevated permissions. This service will:
- Access and manipulate a custom character device node:
/dev/my_custom_device. - Read a custom system property:
my.custom.property.
Without SELinux policy extensions, my_special_service would be sandboxed by the default domain (e.g., untrusted_app if launched without a specific context) and immediately encounter permission denials.
Step-by-Step: Extending the SELinux Policy
We’ll create our custom policy files typically within your device’s `sepolicy` directory, for example, `device/<vendor>/<device>/sepolicy/`. This allows for device-specific extensions without modifying the core AOSP policy.
Step 1: Define New Types for Custom Resources
First, we need to assign unique SELinux types to our custom device node and system property. This makes them identifiable by the policy.
Create a new SELinux Type Enforcement (TE) file, e.g., device/<vendor>/<device>/sepolicy/my_special_service.te:
# Define the type for our custom character device node. It inherits from 'dev_type'.
type my_custom_device, dev_type;
# Define the type for our custom system property. It inherits from 'property_type'.
type my_custom_property_type, property_type;
Next, label the actual device node in `file_contexts`. This file maps file paths to their SELinux contexts. Add the following to `device/<vendor>/<device>/sepolicy/file_contexts`:
# Label for our custom device node
/dev/my_custom_device u:object_r:my_custom_device:s0
Similarly, label the system property in `property_contexts`. Add this to `device/<vendor>/<device>/sepolicy/property_contexts`:
# Label for our custom system property
my.custom.property u:object_r:my_custom_property_type:s0
Step 2: Create a New Domain for Your Service
Every process runs in an SELinux domain. For our custom service, we’ll create a dedicated domain to encapsulate its permissions, ensuring isolation from other system processes. In `my_special_service.te` (the file created in Step 1):
# Define the domain for our service. All processes running in this domain will have this label.
type my_special_service, domain;
# Define the type for the executable file of our service. It's an exec_type and system_file_type.
type my_special_service_exec, exec_type, file_type, system_file_type;
# This macro initializes our domain as a system daemon.
# It grants basic permissions like logging, IPC with system_server, and transitioning from init.
init_daemon_domain(my_special_service)
Now, we need to label the executable of our service so that `init` (or whatever launches your service) knows which SELinux domain to transition it into. Add this to `device/<vendor>/<device>/sepolicy/file_contexts`:
# Label for the executable of our custom service
/system/bin/my_special_service u:object_r:my_special_service_exec:s0
Step 3: Grant Permissions to the New Domain
With our custom types and domain defined, we can now grant the necessary permissions. Add these rules to `my_special_service.te`:
# Allow my_special_service domain to perform various operations on my_custom_device.
# We explicitly list common file operations: create, write, setattr, read, ioctl, open.
allow my_special_service my_custom_device:chr_file { create write setattr read ioctl open };
# Allow my_special_service domain to read the my_custom_property.
# Access to properties is mediated through the 'property_service' class.
allow my_special_service my_custom_property_type:property_service { read };
# Grant common capabilities for a system service. Adjust as per actual needs.
# setuid and setgid are often needed for process privilege management.
allow my_special_service self:capability { setuid setgid net_raw };
# If your service interacts with the Android Service Manager or system_server via Binder:
allow my_special_service servicemanager:binder { call transfer };
allow my_special_service system_server:binder { call transfer };
# Further permissions might be needed depending on your service's functionality.
# For example, logging to logd, accessing other files, etc.
allow my_special_service logd:log_buffer { read };
Remember to follow the principle of least privilege: only grant the permissions absolutely necessary for your service to function. Overly permissive policies weaken security.
Step 4: Integrate Policy into the AOSP Build System
For your new `.te`, `file_contexts`, and `property_contexts` entries to be included in the compiled SELinux policy, they must be referenced by the AOSP build system. This is typically done in your device’s `BoardConfig.mk` or a `sepolicy/Android.bp` file.
For older AOSP versions (using Makefiles), in `device/<vendor>/<device>/BoardConfig.mk`:
# Add your device's sepolicy directory to be included in the platform policy build.
BOARD_PLAT_PUBLIC_SEPOLICY_DIR += device/<vendor>/<device>/sepolicy
For newer AOSP versions (using Soong/Android.bp), ensure your policy files are referenced. You might need to add `my_special_service.te` to the `srcs` list of a `sepolicy_union` or `sepolicy_contrib` module in `device/<vendor>/<device>/sepolicy/Android.bp`:
// Example in device/<vendor>/<device>/sepolicy/Android.bp
sepolicy_union {
name:
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 →