Introduction: Bridging Kernel-Level Root and System Security
In the evolving landscape of Android modification, tools like KernelSU have emerged as powerful alternatives to traditional root solutions. KernelSU operates at the kernel level, offering a more robust and discreet method for granting root access. However, integrating such a profound system modification inevitably clashes with Android’s robust security mechanisms, primarily SELinux (Security-Enhanced Linux). This article delves into the intricate relationship between KernelSU and SELinux, exploring how KernelSU enforces kernel-level permissions while respecting (or strategically augmenting) SELinux policies to maintain system integrity.
Understanding this synergy is crucial for developers building KernelSU modules and for power users seeking to deeply customize their Android experience without compromising security. We will dissect KernelSU’s architecture, refresh our knowledge of SELinux fundamentals, and most importantly, learn how to analyze and customize SELinux policies to enable KernelSU modules to function correctly and securely.
Understanding KernelSU’s Architecture and Integration
KernelSU is unique because it injects its capabilities directly into the Linux kernel. Unlike userspace rooting solutions, KernelSU implements a kernel module (driver) that intercepts system calls related to permissions. When an application requests root access, the KernelSU driver determines if the calling process’s UID is among those allowed to escalate privileges (typically managed by a companion root manager app). If authorized, KernelSU modifies the effective user ID (EUID) and group IDs (EGID) of the process to 0 (root), allowing it to perform privileged operations.
This kernel-level intervention provides several advantages:
- Stealth: Harder for detection mechanisms to spot.
- Stability: Direct kernel interaction can be more robust.
- Granularity: Potential for finer-grained control over root permissions.
The core of KernelSU’s operation involves redirecting specific system calls (e.g., execve, mount, ioctl) and injecting its logic. This allows it to, for instance, remount partitions or modify system files in a way that userspace applications typically cannot. The root manager application acts as the user-facing interface, allowing users to grant or deny root access to specific apps and manage KernelSU modules.
SELinux Fundamentals: Android’s Mandatory Access Control
SELinux is a mandatory access control (MAC) system built into the Linux kernel. Instead of traditional discretionary access control (DAC) where file owners decide permissions, SELinux uses a policy to define what subjects (processes) can access which objects (files, sockets, IPC, etc.), and in what manner. Every file, process, and system resource on an Android device has an associated SELinux context, which is typically in the format user:role:type:level (e.g., u:r:untrusted_app:s0).
Key SELinux concepts:
- Types: Labels assigned to files and processes (e.g.,
system_server_t,app_data_file). - Domains: Special types for processes (e.g.,
init_t,zygote_t). - Policies: A set of rules defining allowed interactions between types/domains.
- Enforcing Mode: SELinux actively blocks unauthorized operations.
- Permissive Mode: SELinux logs unauthorized operations but doesn’t block them.
Android heavily relies on SELinux to enforce application sandboxing and system integrity. An app running in the untrusted_app domain, for instance, is severely restricted in what it can access, preventing malicious apps from compromising the system or other apps’ data. KernelSU’s challenge is to introduce root capabilities without completely dismantling these vital security boundaries.
The Intersection: KernelSU Operations and SELinux Policies
When KernelSU grants root to a process, that process still operates within an SELinux context. If a root process (e.g., a shell granted root by KernelSU) tries to access a file that its current SELinux context does not permit, SELinux will still deny the operation, even if the process has UID 0. This is where the complexity arises: simply having root privileges (UID 0) is often not enough; you also need the correct SELinux context.
KernelSU manages this by often running root processes within a specialized SELinux context or by providing mechanisms for modules to adjust contexts. For instance, the KernelSU manager app itself needs specific permissions to communicate with the kernel driver and manage modules. Rooted shells typically run under a context like su_t or similar, which might have more permissions than a standard app context but is still constrained.
The common scenario for module developers is encountering SELinux AVC (Access Vector Cache) denials. These denials indicate that an operation was blocked by SELinux. They appear in the kernel log (dmesg) or system logs (logcat) and look like this:
audit: avc: denied { read } for pid=1234 comm="my_module_daemon" name="my_config.xml" dev="dm-0" ino=5678 scontext=u:r:my_module_t:s0 tcontext=u:object_r:data_file:s0 tclass=file permissive=0
This log entry tells us:
scontext: The source context (the process trying to access).tcontext: The target context (the file or resource being accessed).tclass: The class of the target (e.g.,file,dir,socket).name: The name of the object.{ read }: The permission being denied.
To make our KernelSU modules function, we need to add specific SELinux rules that grant these necessary permissions.
Customizing SELinux Policies for KernelSU Modules
Customizing SELinux policies for KernelSU modules typically involves creating a new SELinux type for your module’s daemon or processes and then defining rules that allow it to interact with specific resources. This is usually done by including custom SELinux policy fragments within your KernelSU module’s ZIP structure.
Step 1: Identify Denials
The first step is to run your module and observe SELinux denials. Connect your device via ADB and use:
adb shell su -c dmesg | grep 'avc: denied'adb shell logcat | grep 'avc: denied'
Or, if using a rooted shell directly:
dmesg | grep 'avc: denied'logcat | grep 'avc: denied'
Collect all relevant denials. Each denial represents a permission your module needs but doesn’t have.
Step 2: Define New Types and Rules
Let’s say your module (named my_kernel_mod) runs a daemon that needs to read a configuration file located at /data/local/tmp/my_config.json, which currently has the context u:object_r:app_data_file:s0 (or a similar generic data file context). Your module’s process runs under a custom context like u:r:my_kernel_mod_t:s0.
You’d need to create a .te (type enforcement) file (e.g., my_kernel_mod.te) for your module’s custom SELinux policies. This file would typically reside in an /sepolicy.d/ directory within your KernelSU module ZIP.
Example my_kernel_mod.te:
# Define a new type for our module's processesetype my_kernel_mod_t; # Inherit from base types and allow essential operationsallow my_kernel_mod_t self:capability { setuid setgid net_raw };allow my_kernel_mod_t self:process { execute_no_trans transition siginh rlimit };# Allow our module processes to execute init scripts (if needed)allow my_kernel_mod_t init_t:process { signal_perms };# Allow our module to read its configuration file (if placed under a specific context)allow my_kernel_mod_t app_data_file:file { read getattr open };# Allow creating and managing its own directory (example: /data/misc/my_kernel_mod)type my_kernel_mod_data_file, file_type, data_file_type;allow my_kernel_mod_t my_kernel_mod_data_file:dir { create search add_name write remove_name rmdir };allow my_kernel_mod_t my_kernel_mod_data_file:file { create read write getattr setattr unlink open };
You also need to tell SELinux what context to apply to your module’s files. This is done in a file_contexts file (e.g., my_kernel_mod.fc):
/data/local/tmp/my_config.json u:object_r:my_kernel_mod_data_file:s0/data/misc/my_kernel_mod(/.*)? u:object_r:my_kernel_mod_data_file:s0
In this example, we’re giving my_config.json a new, more specific type (my_kernel_mod_data_file) and then allowing our module process type (my_kernel_mod_t) to interact with it.
Step 3: Compiling and Applying Policy
KernelSU modules usually simplify this process. You place your .te and .fc files in a specific directory (e.g., sepolicy.d) within your module’s ZIP. When the module is installed, KernelSU’s system will attempt to compile and inject these rules into the device’s running SELinux policy. This compilation often happens using the checkpolicy tool on-device or via a pre-compiled Common Intermediate Language (CIL) file.
A typical KernelSU module structure for SELinux would include:
my_module.zip├── module.prop├── customize.sh├── service.sh└── sepolicy.d ├── my_kernel_mod.te └── my_kernel_mod.fc
The customize.sh or service.sh scripts might contain commands to dynamically load or apply parts of the policy, or more commonly, KernelSU’s framework handles the integration if the files are placed correctly.
Practical Steps for Module Developers
- Start Minimal: Begin with a basic module and introduce functionality incrementally.
- Monitor Constantly: Keep an eye on
dmesgandlogcatfor AVC denials as you test new features. - Use
audit2allow(on a development machine): While not directly used on-device for KernelSU,audit2allowis invaluable for generating initial policy rules from denials. - Refine Rules: Never blindly add all rules suggested by
audit2allow. Apply the principle of least privilege: grant only the permissions absolutely necessary. - Test Thoroughly: After applying new policies, ensure all module functionalities work and, crucially, that no unintended access is granted.
# Example usage on a Linux machine with SELinux toolsaudit2allow -i audit.log -M my_policy # Generates my_policy.te and my_policy.cid
Best Practices and Security Considerations
- Principle of Least Privilege: Only grant the exact permissions required for your module to function. Overly broad rules (e.g., allowing all file access) can create severe security vulnerabilities.
- Specific Types: Always define specific SELinux types for your module’s processes and its associated data. Avoid reusing general system types.
- Avoid Permissive Mode: While tempting for debugging, running SELinux in permissive mode globally defeats its purpose. Debug specific components in permissive mode only if absolutely necessary and for a limited time.
- Impact on OTA: Customizing SELinux can sometimes interfere with Over-The-Air (OTA) updates, especially if the changes are deep within the policy structure. Always be prepared to revert or re-apply changes after updates.
- Documentation: Clearly document the SELinux policy requirements for your module, both for your own understanding and for other developers.
Conclusion
KernelSU offers a powerful, kernel-level approach to Android rooting, enabling advanced system modifications. However, its effectiveness and security are inextricably linked with SELinux. Successfully integrating KernelSU modules requires not just an understanding of kernel interactions but also a deep appreciation for Android’s robust SELinux policies. By methodically identifying denials, crafting precise policy rules, and adhering to best practices, developers can create powerful KernelSU modules that extend Android’s capabilities without sacrificing its fundamental security architecture. Mastering the art of SELinux policy customization is the key to unlocking the full potential of KernelSU in a secure and stable manner.
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 →