Android security part 3: Security-Enhanced Linux in Android
Published on June 26, 2015
In our Lollipop release blog post, we mentioned that SELinux is now enforced for all the domains starting with Lollipop. This blog post will provide some details about the technology behind SELinux, its Android implementation as well as the different options to create, extend or remove policies. The content below mainly comes from Android website but we tried to shorten it to get to the point and to give examples from our BSP.
What is SELinux?
Mandatory Access Control (MAC)
SELinux is a mandatory access control (MAC) system for the Linux operating system. As a MAC system, it differs from Linux’s familiar discretionary access control (DAC) system. In a DAC system, a concept of ownership exists, whereby an owner of a particular resource controls access permissions associated with it. This is generally coarse-grained and subject to unintended privilege escalation. A MAC system, however, consults a central authority for a decision on all access attempts.
SELinux has been implemented as part of the Linux Security Module (LSM) framework, which recognizes various kernel objects, and sensitive actions performed on them. At the point at which each of these actions would be performed, an LSM hook function is called to determine whether or not the action should be allowed based on the information for it stored in an opaque security object. SELinux provides an implementation for these hooks and management of these security objects, which combine with its own policy, to determine the access decisions.
Enforcement levels
Become familiar with the following terms to understand how SELinux can be implemented to varying strengths.
- Permissive - SELinux security policy is not enforced, only logged.
- Enforcing - Security policy is enforced and logged. Failures appear as EPERM errors.
This choice is binary and determines whether your policy takes action or merely allows you to gather potential failures. Permissive is especially useful during implementation.
- Unconfined - A very light policy that prohibits certain tasks and provides a temporary stop-gap during development. Should not be used for anything outside of the Android Open Source Project (AOSP).
- Confined - A custom-written policy designed for the service. That policy should define precisely what is allowed.
Unconfined policies are available to help implement SELinux in Android quickly. They are suitable for most root-level applications. But they should be converted to confined policies wherever possible over time to restrict each application to precisely the resources it needs.
Ideally, your policy is both in enforcing mode and confined. Unconfined policies in enforcement mode can mask potential violations that would have been logged in permissive mode with a confined policy. Therefore, we strongly recommend partners implement true confined policies.
Labels, rules and domains
SELinux depends upon labels to match actions and policies. Labels determine what is allowed. Sockets, files, and processes all have labels in SELinux. SELinux decisions are based fundamentally on labels assigned to these objects and the policy defining how they may interact. In SELinux, a label takes the form: user:role:type:mls_level
, where the type is the primary component of the access decisions, which may be modified by the other sections components which make up the label. The objects are mapped to classes and the different types of access for each class are represented by permissions.
The policy rules come in the form: allow domains types:classes permissions;
, where:
- Domain - A label for the process or set of processes. Also called a domain type as it is just a type for a process.
- Type - A label for the object (e.g. file, socket) or set of objects.
- Class - The kind of object (e.g. file, socket) being accessed.
- Permission - The operation (e.g. read, write) being performed.
And so an example use of this would follow the structure:
allow appdomain app_data_file:file rw_file_perms;
Android implementation
The Android security model is based in part on the concept of application sandboxes. Each application runs in its own sandbox. Prior to Android 4.3, these sandboxes were defined by the creation of a unique Linux UID for each application at time of installation. Starting with Android 4.3, Security-Enhanced Linux (SELinux) is used to further define the boundaries of the Android application sandbox. SELinux enhances Android security by confining privileged processes and automating security policy creation.
SELinux is set up to default-deny, which means that every single access for which it has a hook in the kernel must be explicitly allowed by policy. This means a policy file is comprised of a large amount of information regarding rules, types, classes, permissions, and more.
Main differences
Although SELinux is a security module available in standard Linux kernel, it had to be modified specifically for Android. This is due to the use of Binder as the IPC mechanism, some hooks needed to be created in order for the security module to control Binder communications.
Android provides the necessary patches as part of its own Linux kernel:
- https://android.googlesource.com/kernel/common/+/android-3.10/security/selinux/
- https://android.googlesource.com/kernel/common/+/c76b9f83
Regarding the labels, it has been said above they contain four different fields. Only two of them are actually used in Android: role and type. The other fields still exist but user is set to u and mls_context to s0. Also, the role is always set to r for domains (processes) or to object_r for objects.
Key files
Apart from the kernel modifications listed above, all the SELinux tools and policies are located in one location: external/sepolicy
.
Here are the files you must create or edit in order to implement SELinux:
- New SELinux policy source (*.te) files - Located in the /device/manufacturer/device-name/sepolicy directory. These files define domains and their labels. The new policy files get concatenated with the existing policy files during compilation into a single SELinux kernel policy file.
- Important: Do not alter the app.te file provided by the Android Open Source Project. Doing so risks breaking all third-party applications.
- Updated BoardConfig.mk makefile - Located in the directory containing the sepolicy subdirectory. It must be updated to reference the sepolicy subdirectory once created if it wasn’t in initial implementation.
- file_contexts - Located in the sepolicy subdirectory. This file assigns labels to files and is used by various userspace components. As you create new policies, create or update this file to assign new labels to files. In order to apply new file_contexts, you must rebuild the filesystem image or run restorecon on the file to be relabeled. On upgrades, changes to file_contexts are automatically applied to the system and userdata partitions as part of the upgrade. Changes can also be automatically applied on upgrade to other partitions by adding restorecon_recursive calls to your init.board.rc file after the partition has been mounted read-write.
- genfs_contexts - Located in the sepolicy subdirectory. This file assigns labels to filesystems such as proc or vfat that do not support extended attributes. This configuration is loaded as part of the kernel policy but changes may not take effect for in-core inodes, requiring a reboot or unmounting and re-mounting the filesystem to fully apply the change. Specific labels may also be assigned to specific mounts such as vfat using the context= mount option.
- property_contexts - Located in the sepolicy subdirectory. This file assigns labels to Android system properties to control what processes can set them. This configuration is read by the init process during startup and whenever the selinux.reload_policy property is set to 1.
- service_contexts - Located in the sepolicy subdirectory. This file assigns labels to Android binder services to control what processes can add (register) and find (lookup) a binder reference for the service. This configuration is read by the servicemanager process during startup and whenever the selinux.reload_policy property is set to 1.
- seapp_contexts - Located in the sepolicy subdirectory. This file assigns labels to app processes and /data/data directories. This configuration is read by the zygote process on each app launch and by installd during startup and whenever the selinux.reload_policy property is set to 1.
- mac_permissions.xml - Located in the sepolicy subdirectory. This file assigns a seinfo tag to apps based on their signature and optionally their package name. The seinfo tag can then be used as a key in the seapp_contexts file to assign a specific label to all apps with that seinfo tag. This configuration is read by system_server during startup.
Tools
In order to be able to take advantage of SELinux, several tools are available. Here is a non-exhaustive list:
- chcon: changes a file security context
- getenforce: gives the current SELinux mode
- ls -Z: displays the security context of files
- ps -Z: display the security context of processes
- restorecon: restores the security context of a file
- runcon: runs a program in the specified secrutiy context
- setenforce: sets the enforcing mode
How to work with it?
How to create/extend a policy?
The first step is to be able to identify the denials from. If SELinux is set to be permissive or enforcing, you can simply look at the kernel logs.
$ adb shell dmesg | grep avc
Some tools exist to generate policies directly from the above logs.
$ sudo apt-get install policycoreutils
$ adb shell dmesg | grep avc | audit2allow
Once the policy has been generated, it needs to be included to the SELinux system configuration.
The first rule to add a policy is to NEVER modify the external/sepolicy
project. Instead your BoardConfig.mk
should specify some BOARD_SEPOLICY_DIRS
directories from which policy can be overridden or unionised. In our BSP, every BoardConfig.mk
include sepolicy.mk
which specify the .te files location/
BOARD_SEPOLICY_DIRS :=
device/boundary/sepolicy
device/fsl/sabresd_6dq/sepolicy
BOARD_SEPOLICY_UNION :=
app.te
file_contexts
fs_use
genfs_contexts
netd.te
untrusted_app.te
We invite you to browse our policy changes made for the Lollipop release:
How to disable it?
Since SELinux can impede development, it might be useful to disable it during debugging phase. For a runtime disablement, you can use setenforce.
$ adb shell setenforce 0
For a more permanent option, we've added a trigger to the boot script that allows you to disable enforcement from U-Boot.
- If you want to be permissive (denials logged in dmesg but not enforced)
U-Boot > setenv selinux permissive
- If you want to disable selinux completely (no logs)
U-Boot > setenv selinux disabled
NOTE: disabling SELinux completely isn't possible since Android Nougat due to this patch.
Use case: uim-sysfs
In order to have a practical approach to this whole SELinux architecture, we will detail the changes made for uim-sysfs to work. Indeed, when first porting the BSP to Lollipop, uim-sysfs wasn't working for several reasons.
First of all, uim-sysfs is a daemon provided by TI to manage the HCI connection to their WL12xx chips. As it didn't have any domain set, init was complaining about it. To fix this issue, a domain must be defined to the service using the seclabel
in init.rc
. Looking at the sepolicy folder, hci_attach
seemed like a good fit to this daemon:
- https://androidxref.com/5.0.0_r2/xref/external/sepolicy/hci_attach.te
- https://github.com/boundarydevices/android_device_boundary/commit/11187234
Once the domain is set, the binary itself must have a security context that defines it as a hci_attach_exec
. Also, this binary needs to access the char device /dev/hci_tty
which therefore be declared as en hci_attach_dev
.
As the security context of a file is stored in his file-system when it can (possible with ext4 for instance), the uim-sysfs file context must be updated manually or the system must be re-flashed for the change to happen.
$ adb reboot
$ adb shell restorecon /system/bin/uim-sysfs
You can then verify the different security contexts with the tools listed above.
$ adb shell ps -Z | grep uim-sysfs
u:r:hci_attach:s0 bluetooth 197 1 /system/bin/uim-sysfs
$ adb shell ls -Z /dev/hci_tty
crw-rw---- bluetooth net_bt_stack u:object_r:hci_attach_dev:s0 hci_tty
$ adb shell ls -Z /system/bin/uim-sysfs
-rwxr-xr-x root shell u:object_r:hci_attach_exec:s0 uim-sysfs
Then other denials appeared as the process was also trying to access some sysfs entries. Here is the patch that fixes it:
Random hints & tricks
- Check the policies built through the intermediates files
-
$OUT/obj/ETC/sepolicy_intermediates/policy.conf
- The file will list all the processed policies with a comment detailing the source
-
- In order to check that your modifications are allowed, build only the sepolicy project
mmm -B external/sepolicy
- Once you've built your sepolicy project, you need to create a new boot image
make bootimage && adb push $OUT/boot/uramdisk.img /boot/
- Use
adb shell
more than the serial as the former has more SE permissions than the latter.setenforce
for instance is not permitted in the serial console
- Use permissive while debugging but do not ship a product not enforced
- If you change one file context; make sure to update its context with
restorecon
References
- https://source.android.com/devices/tech/security/selinux/index.html
- https://events.linuxfoundation.org/sites/events/files/slides/abs2014_seforandroid_smalley.pdf
- https://www.nostarch.com/androidsecurity
- https://www.kroah.com/linux/talks/ols_2002_lsm_paper/lsm.pdf
As always please tell us how this works for you. Contact us if you have any trouble or ask a question by posting a reply below.