Validating SELinux

Android strongly encourages OEMs to test their SELinux implementations thoroughly. As manufacturers implement SELinux, they should apply the new policy to a test pool of devices first.

After applying a new policy, make sure SELinux is running in the correct mode on the device by issuing the command getenforce.

This prints the global SELinux mode: either Enforcing or Permissive. To determine the SELinux mode for each domain, you must examine the corresponding files or run the latest version of sepolicy-analyze with the appropriate (-p) flag, present in /platform/system/sepolicy/tools/.

Reading denials

Check for errors, which are routed as event logs to dmesg and logcat and are viewable locally on the device. Manufacturers should examine the SELinux output to dmesg on these devices and refine settings prior to public release in permissive mode and eventual switch to enforcing mode. SELinux log messages contain avc: and so may easily be found with grep. It is possible to capture the ongoing denial logs by running cat /proc/kmsg or to capture denial logs from the previous boot by running cat /sys/fs/pstore/console-ramoops.

SELinux error messages are rate-limited after boot complete to avoid swamping the logs. To make sure you see all the relevant messages you can disable this by running adb shell auditctl -r 0.

With this output, manufacturers can readily identify when system users or components are in violation of SELinux policy. Manufacturers can then repair this bad behavior, either by changes to the software, SELinux policy, or both.

Specifically, these log messages indicate what processes would fail under enforcing mode and why. Here is an example:

avc: denied  { connectto } for  pid=2671 comm="ping" path="/dev/socket/dnsproxyd"
scontext=u:r:shell:s0 tcontext=u:r:netd:s0 tclass=unix_stream_socket

Interpret this output like so:

  • The { connectto } above represents the action being taken. Together with the tclass at the end (unix_stream_socket), it tells you roughly what was being done to what. In this case, something was trying to connect to a unix stream socket.
  • The scontext (u:r:shell:s0) tells you what context initiated the action. In this case this is something running as the shell.
  • The tcontext (u:r:netd:s0) tells you the context of the action’s target. In this case, that’s a unix_stream_socket owned by netd.
  • The comm="ping" at the top gives you an additional hint about what was being run at the time the denial was generated. In this case, it’s a pretty good hint.

Another example:

adb shell su root dmesg | grep 'avc: '

Output:

<5> type=1400 audit: avc:  denied  { read write } for  pid=177
comm="rmt_storage" name="mem" dev="tmpfs" ino=6004 scontext=u:r:rmt:s0
tcontext=u:object_r:kmem_device:s0 tclass=chr_file

Here are the key elements from this denial:

  • Action - the attempted action is highlighted in brackets, read write or setenforce.
  • Actor - The scontext (source context) entry represents the actor, in this case the rmt_storage daemon.
  • Object - The tcontext (target context) entry represents the object being acted upon, in this case kmem.
  • Result - The tclass (target class) entry indicates the type of object being acted upon, in this case a chr_file (character device).

Dumping User and Kernel Stacks

In some cases, the information contained in the event log is not sufficient to pinpoint the origin of the denial. It is often useful to gather the call chain, including kernel and userspace, to better understand why the denial occurred.

Recent kernels define a tracepoint named avc:selinux_audited. Use Android simpleperf to enable this tracepoint and capture the callchain.

Supported configuration

  • Linux kernel >= 5.10, in particular Android Common Kernel branches mainline and android12-5.10 are supported. The android12-5.4 branch is also supported. You may use simpleperf to determine if the tracepoint is defined on your device: adb root && adb shell simpleperf list | grep avc:selinux_audited. For other kernel versions, you may cherry pick commits dd81662 and 30969bc.
  • It should be possible to reproduce the event you are debugging. Boot time events are not supported using simpleperf; however you may still be able to restart the service to trigger the event.

Capturing the call chain

The first step is to record the event using simpleperf record:

adb shell -t "cd /data/local/tmp && su root simpleperf record -a -g -e avc:selinux_audited"

Then, the event that caused the denial should be triggered. After that, the recording should be stopped. In this example, by using Ctrl-c, the sample should have been captured:

^Csimpleperf I cmd_record.cpp:751] Samples recorded: 1. Samples lost: 0.

Finally, simpleperf report may be used to inspect the captured stacktrace. For instance:

adb shell -t "cd /data/local/tmp && su root simpleperf report -g --full-callgraph"
[...]
Children  Self     Command  Pid   Tid   Shared Object                                   Symbol
100.00%   0.00%    dmesg    3318  3318  /apex/com.android.runtime/lib64/bionic/libc.so  __libc_init
       |
       -- __libc_init
          |
           -- main
              toybox_main
              toy_exec_which
              dmesg_main
              klogctl
              entry_SYSCALL_64_after_hwframe
              do_syscall_64
              __x64_sys_syslog
              do_syslog
              selinux_syslog
              slow_avc_audit
              common_lsm_audit
              avc_audit_post_callback
              avc_audit_post_callback

The call chain above is a unified kernel and userspace call chain. It gives you with a better view of the code flow by starting the trace from userspace all the way down to the kernel where the denial happens. For more information on simpleperf, see the Simpleperf Executable commands reference

Switching to permissive

SELinux enforcement can be disabled via ADB on userdebug or eng builds. To do so, first switch ADB to root by running adb root. Then, to disable SELinux enforcement, run:

adb shell setenforce 0

Or at the kernel command line (during early device bring-up):

androidboot.selinux=permissive
androidboot.selinux=enforcing

Or through bootconfig in Android 12:

androidboot.selinux=permissive
androidboot.selinux=enforcing

Using audit2allow

The audit2allow tool takes dmesg denials and converts them into corresponding SELinux policy statements. As such, it can greatly speed SELinux development.

To use it, run:

adb pull /sys/fs/selinux/policy
adb logcat -b events -d | audit2allow -p policy

Nevertheless, care must be taken to examine each potential addition for overreaching permissions. For example, feeding audit2allow the rmt_storage denial shown earlier results in the following suggested SELinux policy statement:

#============= shell ==============
allow shell kernel:security setenforce;
#============= rmt ==============
allow rmt kmem_device:chr_file { read write };

This would grant rmt the ability to write kernel memory, a glaring security hole. Often the audit2allow statements are only a starting point. After employing these statements, you may need to change the source domain and the label of the target, as well as incorporate proper macros, to arrive at a good policy. Sometimes the denial being examined should not result in any policy changes at all; rather the offending application should be changed.