AOSP Framework & Internals
5 min read

SELinux in Permissive vs Enforcing

Learn about SELinux in Permissive vs Enforcing.

Building a new hardware abstraction layer or system service on Android is difficult. When you write native code that touches hardware or inter-process communication mechanisms, SELinux steps in and blocks access. You see permission denied errors, and your service crashes before executing its core logic. Writing the perfect security policy before your code even works is impossible. You need a way to build and test your implementation first, then handle the security constraints later.

Android provides a way to temporarily relax these security restrictions. Instead of blindly guessing which rules you need upfront, you can tell the security subsystem to observe without interfering.

The Two States of Security

When your code crashes due to security constraints, you need to understand how the kernel handles the violation. SELinux operates in one of two primary states. The default state for a production device is Enforcing mode. In this state, the kernel applies the policy strictly. The system immediately blocks any access attempt that lacks an explicit allow rule, and it logs an Access Vector Cache (AVC) denial.

Permissive mode provides a more forgiving alternative. When running in this state, SELinux still evaluates every access request against the policy. The kernel still logs AVC denials to the buffer when it finds a missing rule. The critical difference is that SELinux does not actually block the operation. Execution continues normally because the kernel allows your code to proceed as if the policy permitted the action.

This flowchart illustrates how an access request flows through the security server under both modes. The diagram shows how policy evaluation and logging happen regardless of the current mode. Look closely at the decision branch to see how Permissive mode bypasses the denial action.

The evaluation happens in the kernel for both modes. In Permissive mode, you can exercise the full functionality of your new component. As you test different code paths, SELinux silently builds a complete list of all the permissions your code actually needs. This transforms policy creation from a guessing game into a straightforward logging exercise. To start gathering these logs, you must first know how to put the device into this observation state.

Controlling the Global State

When you need to test a new component quickly, modifying policy files and recompiling takes too long. You can dynamically switch the SELinux mode on a running device without rebooting. A debuggable build like userdebug or eng is required to perform this switch.

The tool for this job is the setenforce command executed via the Android Debug Bridge. You also need root privileges, which means running adb root before attempting to change the security state. First, check your current state using getenforce. Then use setenforce 0 to drop the device into Permissive mode.

# Check current status
adb shell getenforce

# Switch to Permissive mode
adb shell setenforce 0

# Verify the change
adb shell getenforce

# Return to Enforcing mode
adb shell setenforce 1

Running these commands allows you to toggle the state instantly.

Common Mistake: Trying to run setenforce 0 on a production user build will fail. Production builds lock SELinux permanently into Enforcing mode at the kernel level.

Toggling the entire device to Permissive mode is useful, but comes with a major downside. The entire operating system is suddenly free to violate policy. Your kernel logs quickly fill up with AVC denials from unrelated system services. This noise makes it difficult to isolate the denials caused by your specific code.

Isolating Noise with Per-Domain Permissive

To solve the log noise problem, AOSP allows you to relax security constraints for a single process while keeping the rest of the system locked down. Engineers call this per-domain permissive mode.

Instead of changing the state of the whole device at runtime, you define this exception directly in your SELinux policy files. You accomplish this by adding the permissive macro to the type enforcement file associated with your process domain.

# Allow my_new_hal to run in permissive mode
permissive my_new_hal;

When you compile and flash this policy, the system allows only my_new_hal to bypass denials. This is the recommended approach for developing new components. This technique isolates the security relaxation to the exact process you are working on. Your kernel output remains clean, showing only the AVC denials relevant to your active task. However, you cannot leave these exceptions in your codebase forever.

The CTS Enforcement Mandate

Leaving a device in Permissive mode creates a massive security vulnerability. The Android Compatibility Definition Document mandates that all certified devices ship with SELinux globally in Enforcing mode. There are zero exceptions for production devices.

The Compatibility Test Suite includes automated tests that explicitly verify the device enforces policy. The suite also verifies that the compiled policy contains no permissive domains. If you forget to remove a permissive declaration before finalizing a build, your device fails certification.

All policy development must eventually reach a conclusion. You must remove your permissive flags, compile a strict policy, and verify your feature under Enforcing mode before release. Now that you know how to collect all those missing permissions in Permissive mode, the next logical step is learning how to automatically convert those raw logs into valid SELinux rules.