Getting to Enforcing Mode - Exploring SE for Android (2015)

Exploring SE for Android (2015)

Chapter 13. Getting to Enforcing Mode

As an engineer, you're handed some Android device, and the requirement is to apply SE for Android controls to the device to enhance its security posture. So far, we have seen all the pieces that need to be configured and how they work to enable such a system. In this chapter, we'll take all the skills covered to get our UDOO in enforcing mode. We will:

· Run, evaluate, and respond to audit logs from CTS

· Develop secure policy for the UDOO

· Switch to enforcing mode

Updating to SEPolicy master

Many changes to the sepolicy directory have occurred in the AOSP master branch since the 4.3 release. At the time of this writing, the master branch of the external/sepolicy project was on Git commit SHA b5ffb. The authors recommend attempting to use the most recent commit. However, for illustrative purposes, we will show you how to optionally check out commit b5ffb so you can accurately follow the examples in this chapter.

First, you'll need to clone the external/sepolicy project. In these instructions, we assume your working directory has the UDOO sources contained in the ./udoo directory:

$ git clone

$ cd sepolicy

If you want to follow the examples in this chapter precisely, you'll need to check out commit b5ffb with the following command. If you skip it, you will end up using the latest commit in the master branch:

$ git checkout b5ffb

Now, we'll replace the UDOO 4.3 sepolicy with what we just acquired from Google:

$ cd ..

$ rm -rf udoo/external/sepolicy

$ cp -r sepolicy udoo/external/sepolicy

Optionally, you can remove the .git folder from the newly copied sepolicy with the following command, but this is not necessary:

$ rm –rf udoo/external/sepolicy/.git

Also, copy the audit.te file and restore it.

Additionally, restore the auditd commit from the NSA Bitbucket seandroid repository. For your reference, it's commit SHA d270aa3.

After that, remove all references to setool from udoo/build/core/Makefile. This command will help you locate them:

$ grep -nw setool udoo/build/core/Makefile

Purging the device

At this point, our UDOO is messy, so let's reflash it, including the data directory, and start afresh. We want to have only the code and the init script changes, without the additional sepolicy. Then we can author a policy properly and apply all the techniques and tools we've encountered. We'll start by resetting to a state analogous to the completion of Chapter 4, Installation on the UDOO. However, the major difference is we need to build a userdebug version rather than an engineering (eng) version for CTS. The version is selected in the setup script, which ultimately calls lunch. To build this version, execute the following commands from the UDOO workspace:

$ . setup udoo-userdebug

$ make -j8 2>&1 | tee logz

Flash the system, boot to the SD card, and wipe userdata with the following commands, assuming the SD card is inserted into the host and userdata is not mounted:

$ mkdir ~/userdata

$ sudo mount /dev/sdd4 ~/userdata

$ cd ~/userdata/

$ sudo rm -rf *

$ cd ..

$ sudo umount ~/userdata

Setting up CTS

You must pass CTS if your organization seeks Android branding. However, even if you don't, it's a good idea to run these tests to help ensure a device will be compliant with applications. Based on your security goals and desires, you may fail portions of CTS if you're not seeking Android branding. For our case, we're looking at CTS as a way to exercise the system and uncover policy issues that prevent the proper functioning of the UDOO. Its source is located in the cts/ directory, but we recommend downloading the binary directly from Google. You can get more information and the CTS binary itself from and

Download the CTS 4.3 binary from the Downloads tab. Then select the CTS binary. The Compatibility Definition Document (CDD) is also worth reading. It covers the high-level details of CTS and compatibility requirements.

Download CTS from and extract it. Select the CTS version that matches your Android version. If you don't know which version your device is running, you can always check the from the UDOO with getprop

$ mkdir ~/udoo-cts

$ cd ~/udoo-cts

$ wget

$ unzip

Running CTS

The CTS exercises many components on the device and helps test various parts of the system. A good, general policy should allow proper functioning of Android and pass CTS.

Follow the directions in the Android CTS user manual to set up your device (see Section 3.3, Setting up your device). Typically, you will see some failures if you don't follow all the steps precisely, as you may not have the access or the capabilities to acquire all the resources needed. However, CTS will still exercise some code paths. At a minimum, we recommend getting the media files copied and Wi-Fi active. Once your device is set up, ensure adb is active and initiate the testing:

$ ./cts-tradefed

11-30 10:30:08 I/: Detected new device 0123456789ABCDEF

cts-tf > run cts --plan CTS

cts-tf >

time passes here

11-30 10:30:28 I/TestInvocation: Starting invocation for 'cts' on build '4.3_r2' on device 0123456789ABCDEF

11-30 10:30:28 I/0123456789ABCDEF: Created result dir 2014.11.30_10.30.28

11-30 10:31:44 I/0123456789ABCDEF: Collecting device info

11-30 10:31:45 I/0123456789ABCDEF: -----------------------------------------

11-30 10:31:45 I/0123456789ABCDEF: Test package android.aadb started

11-30 10:31:45 I/0123456789ABCDEF: -----------------------------------------

11-30 10:32:15 I/0123456789ABCDEF: PASS


The tests take many hours to execute, so be patient; but you can check the status of the test:

cts-tf > l i

Command Id Exec Time Device State

1 8m:22 0123456789ABCDEF running cts on build 4.3_r2

Plug in speakers to enjoy the sounds from the media tests and ringtones! Also, CTS reboots the device. If your ADB session is not restored after rebooting, ADB may not execute any tests. Use the --disable-reboot option when running the cts-tf > run cts --plan CTS --disable-reboot plan.

Gathering the results

First, we'll consider the CTS results. Although we expect some failures, we also expect the problem will not get worse when we go to enforcing mode. Second, we'll look at the audit logs. Let's pull both of these files from the device.

CTS test results

CTS creates a test results directory each time it is run. CTS is indicating the directory name but not the location:

11-30 10:30:28 I/0123456789ABCDEF: Created result dir 2014.11.30_10.30.28

The location is mentioned by the CTS manual and can be found under the extracted CTS directory in repository/results, typically at android-cts/repository/results. The test directories contain an XML test report, testResult.xml. This can be opened in most web browsers. It has a nice overview of the tests and details of all executed tests. The pass:fail ratio is our baseline. The authors had 18,736 pass, and only 53 fail, which is fairly good considering half of those are feature issues, such as no Bluetooth or returning true for camera support.

Audit logs

We will use the audit logs to address deficiencies in our policy. Pull these off the device using the standard adb pull commands we have used throughout the book. Since this is a userdebug build and default adb terminals are shell uid (not root), start adb as root withadb root. su is also available on userdebug builds.


You may get an error saying /data/misc/audit/audit.log does not exist. The solution is to run adb as root via the adb root command. Also, when running this command, it may hang. Just go to settings, disable, and then enable USB Debugging under Developer Options. Then kill the adb-root command and verify you have root by running adb shell. Now you should be a root user again.

Authoring device policy

Run both audit.log and audit.old through audit2allow to see what's going on. The output of audit2allow is grouped by source domain. Rather than going through it all, we will highlight the unusual cases, starting with the interpreted results of audit2allow. Assuming you are in the audit log directory, perform cat audit.* | audit2allow | less. Any policy work will be done in the device-specific UDOO sepolicy directory.


The following are our adbd denials as filtered through audit2allow:

#============= adbd ==============

allow adbd ashmem_device:chr_file execute;

allow adbd dumpstate:unix_stream_socket connectto;

allow adbd dumpstate_socket:sock_file write;

allow adbd input_device:chr_file { write getattr open };

allow adbd log_device:chr_file { write read ioctl open };

allow adbd logcat_exec:file { read getattr open execute execute_no_trans };

allow adbd mediaserver:binder { transfer call };

allow adbd mediaserver:fd use;

allow adbd self:capability { net_raw dac_override };

allow adbd self:process execmem;

allow adbd shell_data_file:file { execute execute_no_trans };

allow adbd system_server:binder { transfer call };

allow adbd tmpfs:file execute;

allow adbd unlabeled:dir getattr;

The denials in the adbd domain are quite strange. The first thing that caught our eye was the execute on /dev/ashmem, which is a character driver. Typically, this is only needed for Dalvik JIT. Looking at the raw audits (cat audit.* | grep adbd | grep execute), we see the following:

type=1400 msg=audit(1417416666.182:788): avc: denied { execute } for pid=3680 comm="Compiler" path=2F6465762F6173686D656D2F64616C76696B2D6A69742D636F64652D6361636865202864656C6574656429 dev=tmpfs ino=412027 scontext=u:r:adbd:s0 tcontext=u:object_r:tmpfs:s0 tclass=file

type=1400 msg=audit(1417416670.352:831): avc: denied { execute } for pid=3753 comm="Compiler" path="/dev/ashmem" dev=tmpfs ino=1127 scontext=u:r:adbd:s0 tcontext=u:object_r:ashmem_device:s0 tclass=chr_file

Something with the process comm field of the compiler is executing on ashmem. Our guess is it has something to do with Dalvik, but why is it in the adbd domain? Also, why is adbd writing to the input device? All this is strange behavior. Typically, when you see things like this, it's because the children didn't end up in the proper domain. Run this command to check the domains and confirm our suspicions:

$ adb shell ps -Z | grep adbd

u:r:adbd:s0 root 20046 1 /sbin/adbd

u:r:adbd:s0 root 20101 20046 ps

We then run adb shell ps -Z | grep adbd to see which things were running in the adb domain, further confirming our suspicions:

u:r:adbd:s0 root 20046 1 /sbin/adbd

u:r:adbd:s0 root 20101 20046 ps

The ps command should not be running in the adbd context; it should be running in shell. This confirmed that shell is not in the right domain:

$ adb shell

root@udoo:/ # id

uid=0(root) gid=0(root) context=u:r:adbd:s0

The first thing to check is the context on the file:

root@udoo:/ # ls -Z /system/bin/sh

lrwxr-xr-x root shell u:object_r:system_file:s0 sh -> mksh

root@udoo:/ # ls -Z /system/bin/mksh

-rwxr-xr-x root shell u:object_r:system_file:s0 mksh

The base policy defines a domain transition when adbd loads the shell using exec to go to the shell domain. This is defined in the adbd.te external sepolicy as domain_auto_trans(adbd, shell_exec, shell).

Obviously, an incorrect label has been applied to shell, so let's look at file_contexts in the external sepolicy to find out why.

$ cat file_contexts | grep shell_exec

/system/bin/sh -- u:object_r:shell_exec:s0

The two dashes mean that only regular files will be labeled and symbolic links will be skipped. We probably don't want to label the symlink, but rather the mksh destination. Do this by adding a custom file_contexts entry to the device UDOO sepolicy and adding the file to the BOARD_SEPOLICY_UNION config. In file_contexts, add /system/bin/mksh -- u:object_r:shell_exec:s0, and in, add BOARD_SEPOLICY_UNION += file_contexts.


Throughout the remainder of the chapter, whenever you create or modify policy files (for example, context files or *.te files), don't forget to add them to BOARD_SEPOLICY_UNION in

Since this is a fairly fatal issue with the policy and adbd, we won't worry about the denials for now, with the exception of the unlabeled. Whenever one encounters an unlabeled file, it should be addressed. The avc denial that caused this is as follows:

type=1400 msg=audit(1417405835.872:435): avc: denied { getattr } for pid=4078 comm="ls" path="/device" dev=mmcblk0p7 ino=2 scontext=u:r:adbd:s0 tcontext=u:object_r:unlabeled:s0 tclass=dir

Because this is mounted at /device and Android mounts are typically at /, we should look at the mount table:

root@udoo:/ # mount | grep device

/dev/block/mmcblk0p7 /device ext4 ro,seclabel,nosuid,nodev,relatime,user_xattr,barrier=1,data=ordered 0 0

Typically, mount commands are in the init scripts following a mkdir, or in an fstab file with the init built-in, mount_all. A quick search for device and mkdir in init.rc finds nothing, but we do find it in fstab.freescale. The device is read-only, so we should be able to give it a type, label it with file contexts, and apply the getattr domain to its directory class. Since it's read-only and empty, nobody should need more permissions. Looking at the script, we notice that partition 7 of the block device is the vender directory. This is a misspelling of the common vendor directory that OEMs place proprietary blobs in. We place file types in file.te and the domain allow rules in domain.te.

In file.te, add this:

type udoo_device_file, file_type;

In domain.te, add the following:

allow domain udoo_device_file:dir getattr;

In file_contexts, add this:

/device u:object_r:udoo_device_file:s0

If this directory is not empty, you must manually run restorecon -R on it to label existing files.

If you pull the audit logs multiple times from the UDOO, you may also end up with denials showing that you did so, as adbd will not be able to access them. You may see this:

#============= adbd ==============

allow adbd audit_log:file { read getattr open };

This rule comes from the end of the test when you adb pulled the audit logs. We can safely dontaudit this and add a neverallow to ensure it doesn't accidentally get allowed. The audit logs contain information a malware writer could use to navigate through the policy, and this information should be protected. In a device sepolicy folder, add an adbd.te file and union it in the file:

In adbd.te, add this:

# dont audit adb pull and adb shell cat of audit logs

dontaudit adbd audit_log:file r_file_perms;

dontaudit shell audit_log:file r_file_perms;

In auditd.te, add this:

# Make sure no one adds an allow to the audit logs

# from anything but system server (read only) and

# auditd, rw access.

neverallow { domain -system_server -auditd -init -kernel } audit_log:file ~getattr;

neverallow system_server audit_log:file ~r_file_perms;

If auditd.te is still in external/sepolicy, move it to device/fsl/udoo/sepolicy along with all dependent types.

The neverallow entries show you how to use the compliment, ~, and set difference, -, operators for strong assertions or brevity. The first neverallow starts with domain, and all process types (domains) are members of the domain attribute. We prevent access through set difference, leaving the set that must never have access. We then compliment the access vector set to allow only getattr or stat on the logs. The second neverallow uses compliment to ensure system_server is limited to read operations.


The bootanim domain is assigned to the boot animation service that presents splash screens on boot, typically the carrier's branding:

#============= bootanim ==============

allow bootanim init:unix_stream_socket connectto;

allow bootanim log_device:chr_file { write open };

allow bootanim property_socket:sock_file write;

Anything touching the init domain is a red flag. Here, bootanim connects to an init Unix domain socket. This is a part of the property system, and we can see that after connecting, it writes to the property socket. The socket object and its URI are separate. In this case, it's the filesystem, but it could be an anonymous socket:

type=1400 msg=audit(1417405616.640:255): avc: denied { connectto } for pid=2534 comm="BootAnimation" path="/dev/socket/property_service" scontext=u:r:bootanim:s0 tcontext=u:r:init:s0 tclass=unix_stream_socket

The log_device is deprecated in new versions of Android and replaced with logd. However, we are backporting a new master sepolicy to 4.3, so we must support this. The patch that removed support is at

Rather than apply a reverse patch to the external sepolicy, we can just add the rules to our device policy in a domain.te file. We can safely allow these using the proper macros and styles in the device UDOO sepolicy folder. In bootanim.te, addunix_socket_connect(bootanim, property, init), and in domain.te, add this:

allow domain udoo_device_file:dir getattr;

allow domain log_device:dir search;

allow domain log_device:chr_file rw_file_perms;


#============= debuggerd ==============

allow debuggerd log_device:chr_file { write read open };

allow debuggerd system_data_file:sock_file write;

The log device denial was addressed under bootanim by adding the allow rules for all domains to use log_device. The system_data_file:sock_file write is strange. In most circumstances, you'll almost never want to allow a cross-domain write, but this is special. Look at the raw denial:

type=1400 msg=audit(1417415122.602:502): avc: denied { write } for pid=2284 comm="debuggerd" name="ndebugsocket" dev=mmcblk0p4 ino=129525 scontext=u:r:debuggerd:s0 tcontext=u:object_r:system_data_file:s0 tclass=sock_file

The denial is on ndebugsocket. Grepping for this uncovers a named type transition, which policy version 23 does not support:

system_server.te:297:type_transition system_server system_data_file:sock_file system_ndebug_socket "ndebugsocket";

We have to change the code to set the proper context or just allow it, which we will. We won't grant additional permissions because it never asked for open, and we're crossing domains. Preventing file opens across domains is ideal, as the only way to get this file descriptor is through an IPC call into the owning domain. In debuggerd.te, add allow debuggerd system_data_file:sock_file write;.


#============= drmserver ==============

allow drmserver log_device:chr_file { write open };

This is taken care of by domain.te rules, so we have nothing to do here.


#============= dumpstate ==============

allow dumpstate init:binder call;

allow dumpstate init:process signal;

allow dumpstate log_device:chr_file { write read open };

allow dumpstate node:rawip_socket node_bind;

allow dumpstate self:capability sys_resource;

allow dumpstate system_data_file:file { write rename create setattr };

The denial to init:binder call on dumpstate is strange because init doesn't use binder. Some process must stay in the init domain. Let's check our process listing for init:

$ adb shell ps -Z | grep init

u:r:init:s0 root 1 0 /init

u:r:init:s0 root 2286 1 zygote

u:r:init:s0 radio 2759 2286

Here, zygote and should not be running as init. This must be a labeling error on the app_process file, which is the zygote. The ls -laZ /system/bin/app_process command reveals u:object_r:system_file:s0 app_process, so add an entry to file_contextsto correct this. We can find the label to use in zygote.te in the base sepolicy defined as the zygote_exec type:

# zygote

type zygote, domain;

type zygote_exec, exec_type, file_type;

In file_contexts, add /system/bin/app_process u:object_r:zygote_exec:s0.


The added domain.te rules handle installd.


#============= keystore ==============

allow keystore app_data_file:file write;

allow keystore log_device:chr_file { write open };

The log device is taken care of by the domain.te rules. Let's look at the raw app_data_file denial:

type=1400 msg=audit(1417417454.442:845): avc: denied { write } for pid=15339 comm="onCtsTestRunner" path="/data/data/" dev=mmcblk0p4 ino=131242 scontext=u:r:keystore:s0 tcontext=u:object_r:app_data_file:s0:c512,c768 tclass=file

Categories are defined in the contexts. This means MLS support is activated for app domains. In the seapp_contexts base sepolicy, we see this:

user=_app domain=untrusted_app type=app_data_file levelFrom=user

user=_app seinfo=platform domain=platform_app type=app_data_file levelFrom=user

MLS separation of application data is still under development and didn't work on 4.3, so we can disable this. We can just declare them in a device-specific seapp_contexts file. In seapp_contexts, add user=_app domain=untrusted_app type=app_data_file and user=_app seinfo=platform domain=platform_app type=app_data_file. In 4.3, any changes to context on data require a factory reset. The 4.4 version added smart relabel capabilities.


#============= mediaserver ==============

allow mediaserver adbd:binder { transfer call };

allow mediaserver init:binder { transfer call };

allow mediaserver log_device:chr_file { write open };

The log device was addressed in the domain.te rules. We'll skip init and adbd too, since their issues were triggered by improper process domains. It's important not to add allow rules blindly, as most of the work for existing domains can be handled with small label changes or a few rules.


#============= netd ==============

allow netd kernel:system module_request;

allow netd log_device:chr_file { write open };

The log device denial of netd was addressed by domain.te. However, we should scrutinize anything requesting a capability. When granting capabilities, the policy author needs to be very careful. If a domain is granted the ability to load a system module and that domain or module binary itself is compromised, it could lead to the injection of malware into the kernel via loadable modules. However, netd needs loadable kernel module support to support some cards. Add the allow rule to a file called netd.te in the device UDOO sepolicy. In netd.te, add allow netd self:capability sys_module;.


#============= rild ==============

allow rild log_device:chr_file { write open };

This is taken care of by domain.te rules, so we have nothing to do here.


#============= servicemanager ==============

allow servicemanager init:binder transfer;

allow servicemanager log_device:chr_file { write open };

Again, the log device was handled in domain.te. We'll skip init, since its issues were triggered by improper process domains.


#============= surfaceflinger ==============

allow surfaceflinger init:binder transfer;

allow surfaceflinger log_device:chr_file { write open };

Again, the log device was handled in domain.te. We'll skip init too, since its issues were triggered by improper process domains.


#============= system_server ==============

allow system_server adbd:binder { transfer call };

allow system_server dalvikcache_data_file:file { write setattr };

allow system_server init:binder { transfer call };

allow system_server init:file write;

allow system_server init:process { setsched sigkill getsched };

allow system_server init_tmpfs:file read;

allow system_server log_device:chr_file write;

Since log_device is taken care of by domain.te, and init and adbd are polluted, we will only address the Dalvik cache denial:

type=1400 msg=audit(1417405611.550:159): avc: denied { write } for pid=2571 comm="er.ServerThread" name="system@app@SettingsProvider.apk@classes.dex" dev=mmcblk0p4 ino=129458 scontext=u:r:system_server:s0 tcontext=u:object_r:dalvikcache_data_file:s0 tclass=file

type=1400 msg=audit(1417405611.550:160): avc: denied { setattr } for pid=2571 comm="er.ServerThread" name="system@app@SettingsProvider.apk@classes.dex" dev=mmcblk0p4 ino=129458 scontext=u:r:system_server:s0 tcontext=u:object_r:dalvikcache_data_file:s0 tclass=file

The external sepolicy seandroid-4.3 branch allowed domain.te:allow domain dalvikcache_data_file:file r_file_perms;. Writes were allowed by system_app with system_app.te:allow system_app dalvikcache_data_file:file { write setattr };. We should be able to grant this write access because there may be a need to update its Dalvik cache file. In domain.te, add allow domain dalvikcache_data_file:file r_file_perms;, and in system_server.te, add allow system_server dalvikcache_data_file:file { write setattr };.


#============= toolbox ==============

allow toolbox sysfs:file write;

Typically, one should not write to sysfs. Now look at the raw denial for the offending sysfs file:

type=1400 msg=audit(1417405599.660:43): avc: denied { write } for pid=2309 comm="cat" path="/sys/module/usbtouchscreen/parameters/calibration" dev=sysfs ino=2318 scontext=u:r:toolbox:s0 tcontext=u:object_r:sysfs:s0 tclass=file

From here, we properly label /sys/module/usbtouchscreen/parameters/calibration. We place an entry in file_contexts to label sysfs, declare a type in file.te, and allow toolbox access to it. In file.te, add type sysfs_touchscreen_calibration, fs_type, sysfs_type, mlstrustedobject;, and in file_contexts, add /sys/module/usbtouchscreen/parameters/calibration -- u:object_r:sysfs_touchscreen_calibration:s0, and in toolbox.te, add allow toolbox sysfs_touchscreen_calibration:file w_file_perms;.


#============= untrusted_app ==============

allow untrusted_app adb_device:chr_file getattr;

allow untrusted_app adbd:binder { transfer call };

allow untrusted_app adbd:dir { read getattr open search };

allow untrusted_app adbd:file { read getattr open };

allow untrusted_app adbd:lnk_file read;


untrusted_app had many denials. Considering the domain labeling issues, we won't address most of these now. However, you should look out for mislabeled and unlabeled target files. While searching the denial logs as interpreted by audit2allow, the following was found:

allow untrusted_app device:chr_file { read getattr };

allow untrusted_app unlabeled:dir { read getattr open };

For the chr_file device, we get this:

type=1400 msg=audit(1417416653.742:620): avc: denied { read } for pid=3696 comm="onCtsTestRunner" name="rfkill" dev=tmpfs ino=1126 scontext=u:r:untrusted_app:s0:c512,c768 tcontext=u:object_r:device:s0 tclass=chr_file

type=1400 msg=audit(1417416666.152:784): avc: denied { getattr } for pid=3696 comm="onCtsTestRunner" path="/dev/mxs_viim" dev=tmpfs ino=1131 scontext=u:r:untrusted_app:s0:c512,c768 tcontext=u:object_r:device:s0 tclass=chr_file

type=1400 msg=audit(1417416653.592:561): avc: denied { getattr } for pid=3696 comm="onCtsTestRunner" path="/dev/.coldboot_done" dev=tmpfs ino=578 scontext=u:r:untrusted_app:s0:c512,c768 tcontext=u:object_r:device:s0 tclass=file

Therefore, we need to label /dev/.coldboot_done, /dev/rfkill properly, and /dev/mxs_viim. /dev/rfkill should be labeled in line with what the 4.3 policy had:

file_contexts:/sys/class/rfkill/rfkill[0-9]*/state -- u:object_r:sysfs_bluetooth_writable:s0

file_contexts:/sys/class/rfkill/rfkill[0-9]*/type -- u:object_r:sysfs_bluetooth_writable:s0

The /dev/mxs_viim device seems to be a globally accessible GPU. We recommend a thorough review of the source code, but for now, we will label it as gpu_device. /dev/.coldboot_done is created by ueventd when the coldboot process completes. If ueventd is restarted, it skips the coldboot. We don't need to label this. This denial is caused by the source domain MLS on a target file that is not a subset of the categories of the source and does not have the mlstrustedsubject attribute; it should go away when we drop MLS support from apps.

In file_contexts:

# touch screen calibration

/sys/module/usbtouchscreen/parameters/calibration -- u:object_r:sysfs_touchscreen_calibration:s0

#BT RFKill node

/sys/class/rfkill/rfkill[0-9]*/state -- u:object_r:sysfs_bluetooth_writable:s0

/sys/class/rfkill/rfkill[0-9]*/type -- u:object_r:sysfs_bluetooth_writable:s0


#============= vold ==============

allow vold log_device:chr_file { write open };

Again, the log device was handled in domain.te.


#============= watchdogd ==============

allow watchdogd device:chr_file { read write create unlink open };

The raw denials from watchdog paint in interesting portrait:

type=1400 msg=audit(1417405598.000:8): avc: denied { create } for pid=2267 comm="watchdogd" name="__null__" scontext=u:r:watchdogd:s0 tcontext=u:object_r:device:s0 tclass=chr_file

type=1400 msg=audit(1417405598.000:9): avc: denied { read write } for pid=2267 comm="watchdogd" name="__null__" dev=tmpfs ino=2580 scontext=u:r:watchdogd:s0 tcontext=u:object_r:device:s0 tclass=chr_file

type=1400 msg=audit(1417405598.000:10): avc: denied { open } for pid=2267 comm="watchdogd" name="__null__" dev=tmpfs ino=2580 scontext=u:r:watchdogd:s0 tcontext=u:object_r:device:s0 tclass=chr_file

type=1400 msg=audit(1417405598.000:11): avc: denied { unlink } for pid=2267 comm="watchdogd" name="__null__" dev=tmpfs ino=2580 scontext=u:r:watchdogd:s0 tcontext=u:object_r:device:s0 tclass=chr_file

type=1400 msg=audit(1417416653.602:575): avc: denied { getattr } for pid=3696 comm="onCtsTestRunner" path="/dev/watchdog" dev=tmpfs ino=1095 scontext=u:r:untrusted_app:s0:c512,c768 tcontext=u:object_r:watchdog_device:s0 tclass=chr_file

A file is created and unlinked by watchdog, which keeps a handle to an anonymous file. No filesystem reference exists after the unlink, but the file descriptor is valid and only watchdog can use it. In this case, we can just allow watchdog this rule. In watchdogd.te, addallow watchdogd device:chr_file create_file_perms;. This rule, however, causes a neverallow violation in the base policy:

out/host/linux-x86/bin/checkpolicy: loading policy configuration from out/target/product/udoo/obj/ETC/sepolicy_intermediates/policy.conf

libsepol.check_assertion_helper: neverallow on line 5375 violated by allow watchdogd device:chr_file { read write open };

Error while expanding policy

The neverallow rule is in the domain.te base policy as neverallow { domain -init -ueventd -recovery } device:chr_file { open read write };. For such a simple change, we'll just modify the base sepolicy to neverallow { domain -init -ueventd -recovery -watchdogd } device:chr_file { open read write };.


#============= wpa ==============

allow wpa device:chr_file { read open };

allow wpa log_device:chr_file { write open };

allow wpa system_data_file:dir { write remove_name add_name setattr };

allow wpa system_data_file:sock_file { write create unlink setattr };

Again, the log device was handled in domain.te. The system data accesses need further investigation, starting with the raw denials:

type=1400 msg=audit(1417405614.060:193): avc: denied { setattr } for pid=2639 comm="wpa_supplicant" name="wpa_supplicant" dev=mmcblk0p4 ino=129295 scontext=u:r:wpa:s0 tcontext=u:object_r:system_data_file:s0 tclass=dir

type=1400 msg=audit(1417405614.060:194): avc: denied { write } for pid=2639 comm="wpa_supplicant" name="wlan0" dev=mmcblk0p4 ino=129318 scontext=u:r:wpa:s0 tcontext=u:object_r:system_data_file:s0 tclass=sock_file

type=1400 msg=audit(1417405614.060:195): avc: denied { write } for pid=2639 comm="wpa_supplicant" name="wpa_supplicant" dev=mmcblk0p4 ino=129295 scontext=u:r:wpa:s0 tcontext=u:object_r:system_data_file:s0 tclass=dir

type=1400 msg=audit(1417405614.060:196): avc: denied { remove_name } for pid=2639 co

The offending file was located using ls -laR:


srwxrwx--- wifi wifi 2014-12-01 06:43 wlan0

This socket is created by the wpa_supplicant itself. Relabeling it without type transitions is impossible, so we have to allow it. In wpa.te, add allow wpa system_data_file:dir rw_dir_perms; and allow wpa system_data_file:sock_file create_file_perms;. The unlabeled device has already been dealt with; it was on rfkill:

type=1400 msg=audit(1417405613.640:175): avc: denied { read } for pid=2639 comm="wpa_supplicant" name="rfkill" dev=tmpfs ino=1126 scontext=u:r:wpa:s0 tcontext=u:object_r:device:s0 tclass=chr_file

Second policy pass

After loading the drafted policy, the device still has denials on boot:

#============= init ==============

allow init rootfs:file { write create };

allow init system_file:file execute_no_trans;

#============= shell ==============

allow shell device:chr_file { read write getattr };

allow shell system_file:file entrypoint;

All of these denials should be investigated because they target sensitive types, tcontext specifically.


The raw denials for init are as follows:

<5>type=1400 audit(4.380:3): avc: denied { create } for pid=2268 comm="init" name="tasks" scontext=u:r:init:s0 tcontext=u:object_r:rootfs:s0 tclass=file

<5>type=1400 audit(4.380:4): avc: denied { write } for pid=2268 comm="init" name="tasks" dev=rootfs ino=3080 scontext=u:r:init:s0 tcontext=u:object_r:rootfs:s0 tclass=file

These occur before init remounts / as read-only. We can safely allow these, and since init is running unconfined, we can just add it to init.te. We could add the allow rule to the unconfined set, but since that is going away, let's minimize the permission only toinit:

allow int rootfs:file create_file_perms;


Unconfined is not completely unconfined. Rules get stripped from this domain as AOSP moves closer to zero unconfined domains.

Doing this, however, causes another neverallow to fail. We can modify external/sepolicy domain.te to bypass this. Change the neverallow from this:

# Nothing should be writing to files in the rootfs.

neverallow { domain -recovery} rootfs:file { create write setattr relabelto append unlink link rename };

Change it to this:

# Nothing should be writing to files in the rootfs.

neverallow { domain -recovery -init } rootfs:file { create write setattr relabelto append unlink link rename };


If you need to modify neverallow entries to build, you will fail CTS. The proper approach is to remove this behavior from init.

Additionally, we need to see what is loaded with exec without a domain transition, causing the execute_no_trans denial:

<5>type=1400 audit(4.460:6): avc: denied { execute_no_trans } for pid=2292 comm="init" path="/system/bin/magd" dev=mmcblk0p5 ino=146 scontext=u:r:init:s0 tcontext=u:object_r:system_file:s0 tclass=file

<5>type=1400 audit(4.460:6): avc: denied { execute_no_trans } for pid=2292 comm="init" path="/system/bin/rfkill" dev=mmcblk0p5 ino=148 scontext=u:r:init:s0 tcontext=u:object_r:system_file:s0 tclass=file

To resolve this, we can relabel magd with its own type and place it in its own unconfined domain. A neverallow in the base policy forces us to move each executable into its own domain.

Create a file called magd.te, add it to BOARD_SEPOLICY_UNION, and add the following contents to it:

type magd, domain;

type magd_exec, exec_type, file_type;


Also update file_contexts to contain this:

/system/bin/magd u:object_r:magd_exec:s0

Repeat the steps that were done for magd for rfkill. Just replace magd with rfkill in the preceding example. Later testing revealed an entry-point denial where the source context was init_shell and the target was rfkill_exec. After adding the shell rules, it was discovered that rfkill is loaded using exec from the init_shell domain, so let's also add domain_auto_trans(init_shell, rfkill_exec, rfkill) to the rfkill.te file. Additionally grouped with this discovery was rfkill attempting to open, read, and write /dev/rfkill. So we must label /dev/rfkill with rfkill_device, allow rfkill access to it, and append allow rfkill rfkill_device:chr_file rw_file_perms; to the rfkill.te file. Create a new file to declare this device type, called device.te, and add type rfkill_device, dev_type;. After that, label it with file_contexts by adding /dev/rfkill u:object_r:rfkill_device:s0.


The first shell denial we will evaluate is the denial on entrypoint:

<5>type=1400 audit(4.460:5): avc: denied { entrypoint } for pid=2279 comm="init" path="/system/bin/mksh" dev=mmcblk0p5 ino=154 scontext=u:r:shell:s0 tcontext=u:object_r:system_file:s0 tclass=file

Since we did not label mksh, we need to label it now. We can create an unconfined domain for shells spawned by init to end up in the init_shell domain. The console still ends up in the shell domain via an explicit seclabel, and other invocations end up asinit_shell. Create a new file, init_shell.te, and add it to BOARD_SEPOLICY_UNION.


type init_shell, domain;

domain_auto_trans(init, shell_exec, init_shell);


Update file_contexts to include this:

/system/bin/mksh u:object_r:shell_exec:s0;

Now we will handle shell access to the raw device:

<5>type=1400 audit(6.510:7): avc: denied { read write } for pid=2279 comm="sh" name="ttymxc1" dev=tmpfs ino=122 scontext=u:r:shell:s0 tcontext=u:object_r:device:s0 tclass=chr_file

<5>type=1400 audit(7.339:8): avc: denied { getattr } for pid=2279 comm="sh" path="/dev/ttymxc1" dev=tmpfs ino=122 scontext=u:r:shell:s0 tcontext=u:object_r:device:s0 tclass=chr_file

This is just a mislabeled tty, so we can label this as a tty_device. Add the following entry to the file contexts:

/dev/ttymxc[0-9]* u:object_r:tty_device:s0

Field trials

At this point, rebuild the source tree, wipe the data filesystem, flash, and re-run CTS. Repeat this until all denials are addressed.

Once you're done with CTS and internal QA trials, we recommend performing a field trial with the device in permissive mode. During this period, you should be gathering the logs and refining policy. If the domains are not stable, you can declare them as permissive in the policy file and still put the device in enforcing mode; enforcing some domains is better than enforcing none.

Going enforcing

You can pass the enforcing mode either using bootloader (which will not be covered here) or with the init.rc script early in boot time. You can do this right after setcon:

setcon u:r:init:s0

setenforce 1

Once this statement is compiled into the init.rc script, it can only be undone with a subsequent build and a reflash of boot.img. You can check this by running the getenforce command. Also, as an interesting test, you can try to run the reboot command from the root serial console and watch it fail:

root@udoo:/ # getenforce


root@udoo:/ # reboot

reboot: Operation not permitted


In this chapter, all of your previous understanding of the system was used to develop real SE for Android policy for a brand new device. You are now empowered with the knowledge of how to write SELinux policy for Android, where and how the components of the system work, and how to port and enable these features on various Android platforms. Since this is a fairly new feature that influences many system interactions, issues that will require code changes as well as policy changes will arise. Understanding both is crucial.

As policy authors and security personnel in general, the responsibility to secure the system rests on our shoulders. In most organizations, you're required to work in the dark. However, if you can, do as much work and ask as many questions as you want to in the mailing list, and never accept the status quo. The SE for Android and AOSP projects welcome all to contribute, and by contributing, you will help make the project better and enhance the feature sets for all.