Rooting, Flashing, & Bootloader Exploits

Crafting Custom SELinux Policies for Android: A Guide for Advanced System Modders

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction to SELinux on Android

For advanced Android system modders, navigating the complexities of SELinux (Security-Enhanced Linux) is crucial for building stable, secure, and functional modifications. Since Android 4.3, SELinux has become a cornerstone of the operating system’s security model, enforcing Mandatory Access Control (MAC) over all processes, files, and system resources. This prevents rogue applications or exploits from gaining unauthorized access, even with root privileges. However, for those looking to introduce new services, binaries, or modify core system components, SELinux often becomes a formidable barrier, leading to ‘Permission Denied’ errors and unexpected behavior.

Historically, many modders circumvented SELinux by setting it to ‘permissive’ mode (setenforce 0). While this instantly resolves permission issues, it fundamentally undermines Android’s security architecture, leaving the device vulnerable. This guide aims to move beyond this insecure practice, empowering you to craft precise, secure custom SELinux policies that allow your modifications to function correctly while maintaining the device’s integrity in ‘enforcing’ mode.

SELinux Modes: Permissive vs. Enforcing

Understanding the distinction between SELinux’s two primary modes is fundamental:

  • Permissive Mode: In this mode, SELinux policy violations are logged but not enforced. The system will operate as if there are no restrictions, but every attempted denial will be recorded in the kernel’s audit log. This mode is invaluable for debugging new policies, as it allows you to identify what actions would be denied without actually blocking them. While useful for development, running a device in permissive mode for daily use is strongly discouraged due to the significant security risk.
  • Enforcing Mode: This is the default and most secure mode for Android devices. In enforcing mode, SELinux actively blocks any action that violates the loaded policy. If a process attempts an unauthorized operation, SELinux will prevent it, log the denial, and the operation will fail. The goal for any custom modification is to have a policy that allows its legitimate operations to succeed while the system remains in enforcing mode.

The implications are clear: permissive mode offers convenience at the expense of security, while enforcing mode provides robust protection but requires meticulous policy configuration for custom system changes.

Prerequisites and Essential Tools

Before diving into policy creation, ensure you have the following setup:

  • AOSP Source Tree (or relevant device-specific policy files): Access to the Android Open Source Project (AOSP) source is ideal, as it contains the complete SELinux policy definitions. If you’re targeting a specific device, you might be able to extract its sepolicy from the device’s boot/vendor image.
  • Android NDK/SDK: For adb, fastboot, and potentially compiling some AOSP tools.
  • Linux Environment: Policy development is best done on a Linux machine.
  • SELinux Development Tools:
    • audit2allow: A powerful tool that can generate SELinux policy rules from AVC denial messages. Often built from AOSP.
    • sesearch: For querying existing SELinux policy rules.
    • sepolicy-inject (optional but highly recommended): A tool that allows injecting custom policies into a live system’s sepolicy partition or a Magisk module, avoiding a full AOSP recompilation for every change.

If building audit2allow from AOSP, navigate to your AOSP root and run:

. build/envsetup.sh lunch aosp_x86_64-user # Or your preferred target m audit2allow sesearch sepolicy-inject

Understanding SELinux Denials

The first step in crafting a custom policy is identifying what SELinux is denying. Denials are reported as AVC (Access Vector Cache) messages in the kernel log. You can capture these using adb logcat or dmesg:

adb logcat | grep 'avc:' dmesg | grep 'avc:'

An example AVC denial message looks like this:

avc: denied { read } for pid=1234 comm="my_service" name="config.txt" dev="mmcblk0pXYZ" ino=12345 scontext=u:r:my_service:s0 tcontext=u:object_r:system_file:s0 tclass=file permissive=0

Let’s break down the key components:

  • denied { read }: The permission that was denied (e.g., read, write, execute, open, getattr, connect).
  • pid=1234 comm="my_service": The process ID and name of the process attempting the action.
  • name="config.txt": The name of the resource being accessed.
  • scontext=u:r:my_service:s0: The source context (the SELinux label of the process trying to do something). Here, my_service is the type.
  • tcontext=u:object_r:system_file:s0: The target context (the SELinux label of the resource being accessed). Here, system_file is the type.
  • tclass=file: The class of the target resource (e.g., file, dir, socket, process, service).
  • permissive=0: Indicates the denial happened in enforcing mode (1 for permissive).

Crafting Your First Custom Policy (.te files)

Let’s imagine you’ve developed a custom background service, my_daemon, which needs to read a configuration file located at /data/local/tmp/my_config.conf. Initially, it’s getting AVC denials. Our goal is to create a new SELinux type for my_daemon and its configuration file, and then grant the necessary permissions.

1. Define New Types and Contexts

First, we need to define a type for our daemon and its configuration file. Create a new .te (type enforcement) file, e.g., my_daemon.te:

# my_daemon.te type my_daemon, domain; # This is our daemon process type type my_daemon_data_file, file_type, data_file_type; # Type for its config file # Allow my_daemon to interact with itself and common system resources allow my_daemon self:capability { setuid setgid net_raw }; allow my_daemon domain:fd use; allow my_daemon domain:process { siginh noatsecure rlimitinh }; allow my_daemon domain:unix_stream_socket { read write setopt getopt }; # Add specific permissions as needed here # Allow my_daemon to create new processes if it forks allow my_daemon self:process { fork execmem }; # For its config file on /data/local/tmp file_type_auto_trans(my_daemon_data_file)

We also need to define how our files get labeled. This is done in file_contexts. Add an entry to a new file_contexts file (e.g., my_daemon_file_contexts) for your specific file path:

# my_daemon_file_contexts /data/local/tmp/my_config.conf u:object_r:my_daemon_data_file:s0

This tells SELinux to label /data/local/tmp/my_config.conf with the my_daemon_data_file type.

2. Grant Specific Permissions

Now, let’s allow my_daemon to read its config file. Add this to my_daemon.te:

# Allow my_daemon to read its config file allow my_daemon my_daemon_data_file:file { read getattr open }; # If it needs to write to it, add 'write' as well

And if my_daemon runs an executable, you might need to transition its domain:

# Define an init service for my_daemon type my_daemon_exec, exec_type, file_type; init_daemon_domain(my_daemon) # This macro sets up domain transition for my_daemon from init # If my_daemon itself is an executable, label its binary as my_daemon_exec

You would then add an entry to file_contexts for the executable, e.g., /vendor/bin/my_daemon u:object_r:my_daemon_exec:s0, and ensure your .rc script starts it with seclabel u:r:my_daemon:s0.

3. Generating Policy Rules with audit2allow

For complex scenarios, manually writing rules from denials can be tedious. audit2allow automates this. While your device is in *permissive* mode and running your daemon, collect AVC denials:

adb shell "dmesg -c" # Clear kernel log adb shell "logcat -c" # Clear logcat # Run your daemon/trigger the denied action adb shell "dmesg" > denials.log adb logcat >> denials.log

Then, feed the denials to audit2allow:

audit2allow -i denials.log -o my_new_rules.te

Review my_new_rules.te carefully. audit2allow can sometimes be overly permissive; refine the generated rules to grant only the minimum necessary permissions (principle of least privilege).

Compiling and Injecting Policies

Once your .te files are ready, there are two primary ways to apply them:

Method 1: Recompiling AOSP (for integrated development)

This method is for those working directly within the AOSP source tree. Add your .te and file_contexts files to the relevant AOSP directories (e.g., system/sepolicy/public, system/sepolicy/private, or device-specific policy directories). Then, recompile the entire AOSP project to generate a new sepolicy image or vendor_boot.img (depending on the Android version and device partitioning).

. build/envsetup.sh lunch aosp_x86_64-user # Or your target make update-api make sepolicy # Or make bootimage/vendor_bootimage for flashing

Flash the new images to your device using fastboot.

Method 2: Using sepolicy-inject (for Magisk modules or live injection)

sepolicy-inject is a game-changer for modders, allowing you to add or modify rules without recompiling AOSP. It works by patching the existing sepolicy partition or image.

1. Extract Current Policy

adb pull /sys/fs/selinux/policy policy.current

2. Create a Policy Module (.cil)

Convert your .te files into CIL (Common Intermediate Language) format. This usually involves Android’s sepolicy_compile tool or similar build system components. If you’re using a tool like Magisk’s AOSP module template, it handles this compilation for you.

# Example using prebuilt AOSP tools: sepolicy_compile -f my_daemon.te -o my_daemon.cil

3. Inject the Policy

Use sepolicy-inject to apply your new CIL rules to the extracted policy:

sepolicy-inject -s policy.current -o policy.new --add my_daemon.cil --file-contexts my_daemon_file_contexts

The policy.new file now contains your updated SELinux policy.

4. Flash/Load the New Policy

If you’re creating a Magisk module, place policy.new in the module’s sepolicy.rule file or similar, and Magisk will load it at boot. For temporary testing, you might be able to push and load it directly (requires a patched kernel or specific recovery modes).

Testing and Debugging Iteratively

Policy development is an iterative process:

  1. Start with permissive mode to gather initial denials.
  2. Write or generate initial .te rules and file_contexts.
  3. Compile and inject the new policy.
  4. Reboot the device.
  5. Set SELinux to enforcing mode (setenforce 1) if not already.
  6. Run your custom modification and monitor dmesg/logcat for new AVC denials.
  7. If denials appear, refine your .te files or use audit2allow again.
  8. Repeat until no denials appear for your intended functionality in enforcing mode.

Always verify the state of SELinux after a reboot:

adb shell getenforce

It should return Enforcing. If it returns Permissive, your policy might have a critical error preventing it from loading fully in enforcing mode, or the system decided to boot into permissive as a fallback.

Advanced Considerations and Best Practices

  • Policy Versions: Android’s SELinux policy evolves. Be aware of your target Android version’s policy and adjust rules accordingly (e.g., type transitions, new domains, deprecated permissions).
  • Context Files: Beyond file_contexts, you might encounter genfs_contexts (for virtual file systems like proc, sysfs) and service_contexts (for Binder services).
  • Least Privilege: Always grant the absolute minimum permissions required. Overly broad rules introduce security holes.
  • Never use `allow *:*` or `allow my_domain *:class *;`. These are extremely dangerous and defeat the purpose of SELinux.
  • Understand Existing Policy: Use sesearch to examine existing rules. Often, similar functionalities already have established patterns that you can adapt. For example: sesearch -A -t system_server -s appdomain -c process

Conclusion

Crafting custom SELinux policies for Android is a challenging but essential skill for advanced system modders. By understanding the core concepts of SELinux, diligently analyzing AVC denials, and iteratively refining your policy rules, you can ensure your custom modifications operate flawlessly in Android’s robust enforcing mode. This approach not only makes your projects more secure and stable but also deepens your understanding of the underlying Android security model, elevating your status from a simple ‘rooter’ to a true system architect.

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 →
Google AdSense Inline Placement - Content Footer banner