Google recommends reducing the attack surface of GPU units in Android by restricting IOCTL commands that applications do not need in production, rather than relying solely on discovering and fixing individual vulnerabilities. This practice resulted from collaboration between the Android Red Team and Arm to analyze the Mali GPU and its driver, with a focus on preventing access to unnecessary functions using SELinux.
This step is important for security because the GPU has become an attractive target for attackers due to its complexity and elevated privileges within the system. According to the article, the majority of exploits relying on Android kernel drivers since 2021 have targeted the GPU, with attacks primarily focused on the interface between the user-mode driver, known as the UMD, and the privileged kernel-mode driver, or KMD.
Why Is Reducing the Attack Surface a Priority?
Malicious inputs passing through this interface can exploit flaws that lead to memory corruption. Google believes that making unnecessary paths inaccessible provides an effective, and often faster, way to improve protection, alongside continuing to discover and fix software bugs.
The joint analysis with Arm covered the Mali driver used in approximately 45% of Android devices and helped identify parts of the attack surface that pose security risks but are not required to operate devices in production. Accordingly, the policy focused on IOCTL commands, which represent the GPU driver's input and output and therefore constitute an important part of the attack surface.
The proposed approach divides Mali commands into three categories:
- Unrestricted commands: Required for normal operation and remain available to applications.
- Profiling and tooling commands: Used by performance-analysis and debugging tools to monitor GPU performance.
- Restricted commands: Not expected to be used by production applications. They include commands intended for GPU development, as well as legacy commands no longer used in the device's current UMD version.
The policy aims to block legacy and debugging commands in production, while limiting profiling commands to shell or applications marked as debuggable. Production commands remain available to ordinary applications.
Gradual Deployment of the SELinux Policy
Google adopted a gradual approach to reduce the possibility of disrupting legitimate applications. The process began with an optional policy and created a new SELinux attribute named gpu_harden that blocks profiling commands, then applied it to a specific set of system applications to test its impact. During this phase, it used the allowxperm rule to log access attempts instead of blocking them immediately, then monitored denial logs to verify that no crashes occurred.
After confirming the approach was safe, the policy moved to a stricter default model, with a domain named gpu_debug that permits access to profiling commands. Applications were thereby protected by default, while developers could use exceptions in specific cases:
- Running the application on a device with root privileges.
- Setting android:debuggable="true" in the application's manifest.
- Requesting a permanent exception in the application's SELinux policy.
Steps for Applying the Policy to Devices
Google provides the guidance to help partners and the broader ecosystem adopt a similar mechanism, while separating the general policy logic from the details of each device or driver. The process begins by using a general macro module at the Android platform level within system/sepolicy. This module is located in /sepolicy/public/te_macros and allows device policies to pass lists of IOCTL commands that should be filtered.
The module is designed to allow all applications, or appdomain, to access the list of unrestricted commands and to limit sensitive profiling commands to debugging tools such as shell or runas_app when the application is debuggable. It also blocks privileged commands based on the application's target SDK version, while maintaining compatibility with older applications.
Next, the device developer creates an ioctl_macros file inside the device's SELinux policy directory, such as device/your_company/your_device/sepolicy/ioctl_macros, and then defines the command lists for the GPU driver. Google recommends having at least separate lists for production, profiling, and debugging commands, including the driver's hexadecimal IOCTL numbers.
Arm provided an official classification of its IOCTL commands in the file Documentation/ioctl-categories.rst in version r54p2, noting that the list will continue to be updated with future driver releases.
Testing Before Enforcement
In the next step, the policy is applied to the GPU device node by creating a gpu.te file in the device policy directory, then invoking the general macro module and passing the device label and the previously defined IOCTL lists.
Google emphasizes that developing an SELinux policy should remain an iterative process involving testing, refinement, and then enforcement. This approach makes it possible to collect real-world usage data, identify cases that may require exceptions, and gradually expand the policy's scope rather than imposing broad restrictions from the outset.
Google believes that reducing the attack surface not only protects against known vulnerabilities, but may also make vulnerabilities that have not yet been discovered, or that may emerge in the future, inaccessible through restricted paths. Expanding this approach requires cooperation among Android, device manufacturers, and ecosystem partners, while Android security teams said they are committed to supporting the adoption of these measures more broadly.