Android SELinux Internals Part I | 8kSec Blogs

This is part I of a 2 part series on Android SELinux Internals where we will do a deepdive into the world of SELinux on Android and understand its inner workings, along with its functionalities and benefits. We’ll discuss how SELinux provides security on Android devices and ways to bypass it.

This is going to be a long read so grab your cup of coffee and lets start!

What is SELinux?

SELinux is a Mandatory Access Control (MAC) mechanism that enforces a set of security policies on your Android device. These policies define what resources, such as files, or network connections each process or application running on your device can access. It also controls what actions these different processes can take on your device.

For example, let’s say you have installed a new social media application on your Android device. When you install the application, the application could request for accessing your device contacts so it can suggest you already your existing friends on the platform. If your device did not have SELinux, the application would be granted complete access to the entire contacts list without any further restrictions or checks. This could put your device data to risk of compromise by malicious actors.

When an application requests access to a sensitive resource, such as the device’s contacts, SELinux will compare the label assigned to the application’s process to the label assigned to the contacts. Access is allowed only if the applications process label has sufficient privilege to access that the contacts. This is especially useful when an application with malicious intent installed on the device attempts to exploit vulnerabilities in the system, to bypass the permission prompts and gain access to sensitive resources on the device without the user’s knowledge. SELinux helps restrict the malicious application’s access to resources in all such scenarios based on its security context.

How do you read the SELinux context of a file or directory in Android?

When an application requests access to a sensitive resource, like the contacts on your device, SELinux uses a combination of security contexts and security labels to enforce access controls.

You can use the command adb shell ls -Z /system to view the security context labels of a directory/file using ADB.

				
					adb shell ls -Z /system

u:object_r:system_file:s0     apex
u:object_r:system_file:s0     app
u:object_r:system_file:s0     bin
u:object_r:system_file:s0     build.prop
u:object_r:system_file:s0     etc
u:object_r:system_file:s0     fonts
u:object_r:system_file:s0     framework
u:object_r:system_lib_file:s0 lib64
u:object_r:system_file:s0     priv-app
u:object_r:system_file:s0     product
u:object_r:system_file:s0     system_ext
u:object_r:system_file:s0     usr
u:object_r:vendor_file:s0     vendor
u:object_r:system_file:s0     xbin
				
			

When your phone boots, SELinux will load the SELinux policy and label every file with its own security label. The above command lists the contents of the /system directory on our Android device and displays the SELinux context of each of the files and directories.

The security context label for the /system directory on Android is u:object_r:system_file:s0. This label provides information about the security domain, object class, and sensitivity level of the directory. Let us break down the security context label for this process and understand it further.

  • The first part of the label, u, indicates the SELinux user for the object. In this case, the user is u which corresponds to the **system** user.

  • The second part of the label, object_r, specifies the security domain for the object. The system_file domain is used for system files that should be protected from unauthorized modification or access. This domain is defined in the SELinux policy and has a set of associated permissions and rules.

    Some examples of security domains used for apps include:

    • - app_data_file: This is used for files that belong to an application’s data directory, such as its private preferences, databases, and cached data.
    • - app_exec: This is used for app executables, such as the main app process and any services or broadcast receivers.

    • - app_domain: This is used for app domains, which provide a context for running scripts or plugins within an app.

    • - media_rw_file: This is used for files that are accessible by the media server, such as music and videos.

    • - untrusted_app: This is used for apps that run in an untrusted environment, such as when running code downloaded from the internet.

    Each security domains has a set of associated permissions and rules that define what resources the application is allowed to access and what actions it can perform.

  • The third part of the label, s0, specifies the sensitivity level for the object. The sensitivity level defines the default security level for the object and determines the permissions that are granted to processes that access the object. In this case, the sensitivity level is s0, which is the default sensitivity level for system files and provides a high degree of protection against unauthorized access or modification. For example, a process running with the s0 sensitivity level might be prevented from reading or modifying files in another app’s data directory, or from sending network requests to a restricted set of IP addresses.

    However, if needed, some system or privileged applications may be assigned higher security levels such as s1 or s2. This grants them greater access to system resources, but also subject to stricter access controls.

How do you read the SELinux context of a process in Android?

When an application requests access to a sensitive resource, like the contacts on your device, SELinux uses a combination of security contexts and security labels to enforce access controls.

Just like we explained how adb can be used to read the SELinux context for a file, you can use the command adb shell ps -Z to view the security context labels of a process.

Let us see how things look when we use a target Android APK. We launched a custom application named  “AccessLocalFile” to further look at these security context labels further.

				
					adb shell ps -Z | grep com.dns.accesslocalfile
u:r:untrusted_app:s0:c132,c256,c512,c768 u0_a132 6384 289 13720540 143576 do_epoll_wait      0 S com.dns.accesslocalfile
				
			

This command shows the SELinux context label for our installed sample application “com.dns.accesslocalfile”.

The SELinux context label for the process is u:r:untrusted_app:s0:c132,c256,c512,c768 . This label provides information about the security domain, role, and sensitivity level for the process, as well as the set of security contexts it has been granted access to.

Let us understand it in further detail.

  •  The first part of the label, u, specifies the SELinux user for the process. In this case, the user is u, which corresponds to the system user. System user is the user account that system processes and services run under.

  •  The second part of the label, r, specifies the SELinux role for the process. The untrusted_app role is used for apps that run in an untrusted environment, such as when running code downloaded from the internet.

  • - s0 represents the initial SELinux security level. The initial SELinux security level s0 refers to the default security context for a process. This level provides a basic level of protection and restricts the process’s access to system resources, such as files, directories, and network sockets. However, if needed, some system or privileged applications may be assigned higher security levels such as s1 or s2. This grants them greater access to system resources, but also subject to stricter access controls.

  •  The fourth part of the label – capabilities c132,c256,c512,c768, specifies the set of security contexts the process has been granted access to. These security contexts correspond to different levels of access to system resources, with lower context levels representing more privileged access and higher context levels representing more restricted access.

  •  The output also shows that the process is in state S, which indicates that it is sleeping, waiting for an event to occur. The command used to start the process is do_epoll_wait, which is a system call used to wait for events on a set of file descriptors using the epoll I/O event notification mechanism.

How does SELinux work under the hood?

SELinux uses a set of policies, written in a specialized language called SELinux policy language.

All allowed operations and rules are stored in type enforcement files (.te files). These files specify the security contexts for different types of objects, such as files, directories, and processes, as well as the permissions and access controls that apply to those objects.

Head over to the following link to see the AOSP code repository which points to the .te files used in the SELinux policy. From here, you can browse and download the files as needed.

https://android.googlesource.com/platform/external/sepolicy/+/refs/heads/master

As another example, you can head over to https://android.googlesource.com/platform/external/sepolicy/+/refs/heads/brillo-m10-release to view the sepolicy directory that contains the SELinux policy files used in the Brillo operating system, which may be different from the SELinux policy used in other versions of Android.

Also take a look at the following folders in the AOSP code:

On a physical Android devices and emulators, these policies are stored in a binary file. You can run the following command to print out the location of the the SELinux policy files for your device.

				
					emu64a:/ $ grep selinuxfs /proc/mounts
selinuxfs /sys/fs/selinux selinuxfs rw,relatime 0 0
				
			

So in our case, the policy file will be in /sys/fs/selinux, and in a file as “policy” – **/sys/fs/selinux/policy**.

Also observe that the file can only be accessible as a root user.

How do we interact with this SELinux policy file?

  1. To read an SELinux policy file, we can use the utilities from https://github.com/xmikos/setools-android. Go ahead and download the compiles binaries from the releases section.

  2. You can use the command adb shell getprop ro.product.cpu.abi to get the architecture of your device. In our case it is arm64-v8a. So, we will push the binaries from the folder setools-android/arm64-v8a to the device.

				
					adb push arm64-v8a/* /data/local/tmp
				
			
  1. Using adb navigate to /data/local/tmp and make the binaries executable.

  1. Change the shell to root.

				
					su
				
			
  1. You can view the list of available SELinux policy attributes using the following command:

				
					./seinfo -a /sys/fs/selinux/policy
				
			
  1. You can view the list of available SELinux object classes using the following command:

				
					./seinfo -c /sys/fs/selinux/policy
				
			
  1. You can view the information of the available roles defined in an selinux policy using the command:

				
					./seinfo --role /sys/fs/selinux/policy
				
			

In our case we have the following roles:

				
					# ./seinfo --role /sys/fs/selinux/policy                      

Roles: 4
   auditadm_r
   r
   object_r
   secadm_r
				
			
  1. you can view the list of all policy capabilities defined in the SELinux policy file using the following command:

				
					# ./seinfo --polcap  /sys/fs/selinux/policy 

Policy Capabilities: 4
   network_peer_controls
   open_perms
   extended_socket_class
				
			
  1. You can get the statistics for the SELinux policy file using the following command:

				
					./seinfo --stats  /sys/fs/selinux/policy 
				
			
				
					# ./seinfo --stats  /sys/fs/selinux/policy                  

Statistics for policy file: /sys/fs/selinux/policy
Policy Version & Type: v.30 (binary, mls)

   Classes:           103    Permissions:       234
   Common classes:      5
   Sensitivities:       1    Categories:       1024
   Types:               0    Attributes:       1949
   Users:               1    Roles:               4
   Booleans:            0    Cond. Expr.:         0
   Allow:           30132    Neverallow:          0
   Auditallow:         67    Dontaudit:        1044
   Type_trans:        678    Type_change:         0
   Type_member:         0    Role allow:          0
   Role_trans:          0    Range_trans:         0
   Constraints:        89    Validatetrans:       0
   Initial SIDs:       27    Fs_use:             20
   Genfscon:          505    Portcon:             0
   Netifcon:            0    Nodecon:             0
   Permissives:         1    Polcap:              4
				
			
  1. To search for all SELinux policy rules in the SELinux policy file, you can use the following command:

				
					./sesearch -A  /sys/fs/selinux/policy