Exploring SE for Android (2015)

Chapter 6. Exploring SELinuxFS

In the last few chapters, we saw SELinuxFS surface on numerous occasions. From its entry in /proc/filesystems to the policy load in the init daemon, it sees frequent use in an SELinux-enabled system. SELinuxFS is the kernel-to-userspace interface and the foundation on which higher userspace idioms and libselinux are built. In this chapter, we will explore the capabilities of this filesystem for a deeper understanding of how the system works. Specifically, we will:

·        Determine how to find the mount point of the SELinux filesystem

·        Extract status information about our current SELinux system

·        Modify our SELinux system status on the fly from the shell and through code

·        Investigate ProcFS interfaces

Locating the filesystem

The first thing we need to do is locate the mount point for the filesystem. libselinux mounts the filesystem in either of two places: /selinux (by default) or /sys/fs/selinux. However, this is not a strict requirement and can be altered with a call to voidset_selinuxmnt(char *mnt), which sets the SELinux mount point location. However, this should happen and should not need any adjustment in most circumstances.

The best way to find the mount point in the system is by running the mount command and finding the location of the filesystem. From the serial console, issue the following commands:

root@udoo:/ # mount | grep selinux

selinuxfs /sys/fs/selinux selinuxfs rw,relatime 0 0

As you can see, the mount point is /sys/fs/selinux. Let's go to that directory by issuing the following command at the serial terminal prompt:

root@udoo:/ # cd /sys/fs/selinux

root@udoo:/sys/fs/selinux #

You are now in the root of the SELinux filesystem.

Interrogating the filesystem

You can interrogate SELinuxFS to find out what the kernel's highest supported policy version is. This is useful when you begin to work with systems you did not build from source. It is also useful when you do not have direct access to the KConfig file. It is important to note that both DAC and MAC permissions apply to this filesystem. With respect to MAC and SELinux, the access vectors for this are enumerated in class security in the policy file located at external/sepolicy/access_vectors:

root@udoo:/sys/fs/selinux # echo 'cat policyvers'

23

Tip

In the previous command, and in several commands to follow, we do not just print the files with the cat command. This is because these files do not have a trailing newline at the end of the file. Without the newline, the command prompt following the command's execution would be at the end of the last line of the output. Wrapping the cat command with echo guarantees a newline. An alternate way to get the same effect is by using cat policyvers ; echo.

As we expected, the supported version is 23. As you recall, we set this value in Chapter 4Installation on the UDOO while configuring the kernel to enable SELinux using make menuconfig from the kernel_imx directory. This is also accessible by the libselinux API:

int security_policyvers(void);

It should not require any elevated permissions and is readable by anyone on the system.

The enforce node

In previous chapters, we discussed that SELinux operates in two modes, enforcing and permissive. Both modes log policy violations, however, enforcing mode causes the kernel to deny access to the resource and return an error to the calling userspace process (for example, EACCESS). SELinuxFS has an interface to query this status—the file node enforce. Reading from this file returns the status 0 or 1 depending on whether we are running in permissive or enforcing mode, respectively:

root@udoo:/sys/fs/selinux # echo 'cat enforce'

0

As you can see, our system is in permissive mode. Android has a toolbox command for printing this as well. This command returns the status Permissive or Enforcing depending on whether we are running in a permissive or enforcing mode, respectively:

root@udoo:/sys/fs/selinux # getenforce

Permissive

You can also write to the enforce file. The DAC permissions for this filesystem are:

Owner: root read, write

Group: root read

Others: read

Anyone can get the enforcing status, but to set it, you must be the root user. The MAC permission required for this is:

class: security

vector: setenforce

A command called setenforce can change the status:

root@udoo:/sys/fs/selinux # setenforce 0

To see what the command does, run it in strace:

root@udoo:/sys/fs/selinux # strace setenforce 0

...

open("/proc/self/task/3275/attr/current", O_RDONLY) = 4

brk(0x41d80000) = 0x41d80000

read(4, "u:r:init_shell:s0\0", 4095) = 18

close(4) = 0

open("/sys/fs/selinux/enforce", O_RDWR) = 4

write(4, "0", 1)

...

As we can see, the interface to enforce is as simple as writing 0 or 1. The function in libselinux to do this is int security_setenforce(int value). Another interesting artifact of the preceding command is we can see procfs was accessed. SELinux has some additional entries in procfs as well. Those will be covered further in this chapter.

The disable file interface

SELinux can also be disabled at runtime using the disable file interface. However, the kernel must be built with CONFIG_SECURITY_SELINUX_DISABLE=y. Our kernel was not built with this option. This file is write only by owner and has no specific MAC permission associated with it. We recommend keeping this option disabled. Additionally, SELinux can be disabled before a policy is loaded. Even when the option is enabled, once a policy is loaded, it is disabled.

The policy file

The policy file lets you read the current SELinux policy file that was loaded into the kernel. This can be read and saved to disk:

root@udoo:/sys/fs/selinux # cat policy > /sdcard/policy

By enabling the adb interface, you can now extract it from the device and analyze it on the host with the standard SELinux tools. The DAC permissions on this file are owner: root, read. There is no SELinux permission specific to this file.

The inverse to the policy file is the load file. We have seen this file appear when the policy file is loaded by init using the libselinux API:

int security_load_policy(void *data, size_t len);

The null file

The null file is used by SELinux to redirect unauthorized file accesses when domain transitions occur. Remember that a domain transition is when you transition from one context to another. In most cases, this occurs when a program performs a fork and exec function, but this could happen programmatically. In either case, the process has file references it can no longer access, and to help keep processes from crashing, they just write/read from the SELinux null device.

The mls file

One of the capabilities our system has is that our current policy is using multilevel security (MLS) support. This is either 0 or 1, based on whether the loaded policy file is using it. Since we have it enabled, we would expect to see 1 from this file:

root@udoo:/sys/fs/selinux # echo 'cat mls'

1

The mls file is readable by all and has a corresponding SELinux API:

int is_selinux_mls_enabled(void)

The status file

The version file allows a mechanism by which you can be informed of updates that occur within SELinux. One such example would be when a policy reload occurs. A userspace object manager could cache decision results and use the reload event as a trigger to flush their cache. The status file is read only by everyone and has no specific MAC permissions. The libselinux API interface is:

int selinux_status_open(int fallback);

void selinux_status_close();

int selinux_status_updated(void);

int selinux_status_getenforce(void);

int selinux_status_policyload(void);

int selinux_status_deny_unknown(void);

By checking the status structure, you can detect changes and flush the cache. Currently, however, you are missing this API in your libselinux, but we'll correct that in Chapter 7Utilizing Audit Logs.

There are many SELinuxFS files in the file tree; our intent here was only to cover several files because of their importance or pertinence to what we've done and where we're going. We did not cover:

·        access

·        checkreqprot

·        commit_pending_bools

·        context

·        create

·        deny_unknown

·        member

·        reject_unknown

·        relabel

The use of these files is not simple and is typically done by userspace object managers that are using the libselinux API to abstract the complexities.

Access Vector Cache

SELinuxFS also has some directories you can explore. The first is avc. This stands for "Access Vector Cache" and can be used to get statistics about the security server in the kernel:

root@udoo:/sys/fs/selinux # cd avc/

root@udoo:/sys/fs/selinux/avc # ls

cache_stats

cache_threshold

hash_stats

All these files can be read with the cat command:

root@udoo:/sys/fs/selinux/avc # cat cache_stats

lookups hits misses allocations reclaims frees

285710 285438 272 272 128 128

245827 245409 418 418 288 288

267511 267227 284 284 192 193

214328 213883 445 445 288 298

The cache_stats file is readable by all and requires no special MAC permissions.

The next file to look at is hash_stats:

root@udoo:/sys/fs/selinux/avc # cat hash_stats

entries: 512

buckets used: 284/512

longest chain: 7

The underlying data structure for the Access Vector Cache is a hash table; hash_stats lists the current properties. As we can see in the output of the preceding command, we have 512 slots in the table, with 284 of them in use. For collisions, we have the longest chain at 7 entries. This file is world readable and requires no special MAC permissions. You can modify the number of entries in this table through the cache_threshold file.

The cache_threshold file is used to tune the number of entries in the avc hash table. It is world readable and owner writeable. It requires the SELinux permission setsecparam, and can be written to and read from with the following simple commands, respectively:

root@udoo:/sys/fs/selinux/avc # echo "1024" > cache_threshold

root@udoo:/sys/fs/selinux/avc # echo 'cat cache_threshold'

1024

You can disable the cache by writing 0. However, outside the benchmarking tests, this is not encouraged.

The booleans directory

The second directory to look into is booleans. An SELinux boolean allows policy statements to change dynamically via boolean conditions. By changing the boolean state, you can affect the behavior of the loaded policy. The current policy does not define any booleans; so this directory is empty. In policies that define booleans, the directory would be populated with files named after each boolean. You can then read and write to these files to change the boolean state. The Android toolbox has been modified to include the getsebooland setsebool commands. The libselinux API also exposes these capabilities:

int security_get_boolean_names(char ***names, int *len);

int security_get_boolean_pending(const char *name);

int security_get_boolean_active(const char *name);

int security_set_boolean(const char *name, int value);

int security_commit_booleans(void);

int security_set_boolean_list(size_t boolcnt, SELboolean * boollist, int permanent);

Booleans are transactional. This means it is an all or nothing set. When you use security_set_boolean*, you must call security_commit_booleans() to make it take effect. Unlike Linux desktop systems, permanent booleans are not supported. Changing the runtime value does not persist across reboots. Also, on Android, if you are attempting Android Compatibility Test Suite (CTS) compliance, booleans will cause the tests to fail. Booleans can have varying DAC permissions based on the target, but they always require the SELinux permission, setbool.

Tip

You must pass the Android Compatability Test Suite for Android branding. More on CTS can be found at https://source.android.com/compatibility/cts-intro.html.

The class directory

The next directory to look at is class. The class directory contains all the classes defined in the access_vectors SELinux policy file or via the class keyword in the SELinux policy language. For each class defined in the policy, a directory exists with the same name. For instance, run the following on the serial terminal:

root@udoo:/sys/fs/selinux/class # ls -la

...

dr-xr-xr-x root root 1970-01-02 01:58 peer

dr-xr-xr-x root root 1970-01-02 01:58 process

dr-xr-xr-x root root 1970-01-02 01:58 property_service

dr-xr-xr-x root root 1970-01-02 01:58 rawip_socket

dr-xr-xr-x root root 1970-01-02 01:58 security

...

As you can see from the preceding command, there are quite a few directories. Let's examine the property_service directory. This directory was chosen because it is only one defined on Android. However, the files present in each directory are the same and includeindex and perms:

root@udoo:/sys/fs/selinux/class/property_service # ls

index

perms

The mapping between string and some arbitrary integer that is defined in the SELinux kernel module is index. A directory that contains all the permissions possible for that class is perms:

root@udoo:/sys/fs/selinux/class/property_service # cd perms/

root@udoo:/sys/fs/selinux/class/property_service/perms # ls

set

As you can see, the set access vector is available for the property_service class. The class directory can be very beneficial to observe a policy file already loaded in a system.

The initial_contexts directory

The next directory entry to peer into is initial_contexts. This is the static mapping of the initial security contexts, better known as security identifier (sid). This map tells the SELinux system which context should be used to start each kernel object:

root@udoo:/sys/fs/selinux/initial_contexts # ls

any_socket

devnull

file

...

We can see what the initial sid for file is by performing:

root@udoo:/sys/fs/selinux/initial_contexts # echo 'cat file'

u:object_r:unlabeled:s0

This corresponds to the entry in external/sepolicy/initial_sid_contexts:

...

sid file u:object_r:unlabeled:s0

...

The policy_capabilities directory

The last directory to look into is policy_capabilities. This directory defines any additional capabilities the policy might have. For our current setup, we should have:

root@udoo:/sys/fs/selinux/policy_capabilities # ls

network_peer_controls

open_perms

Each file entry contains a boolean indicating whether the feature is enabled:

root@udoo:/sys/fs/selinux/policy_capabilities # echo 'cat open_perms'

1

The entries are readable by all and writeable by none.

ProcFS

We alluded to some of the procfs interfaces that are being exported. Much of what is discussed is the security contexts, so that means the shell should have some security context associated with it... but how do we achieve this? Since this is a general mechanism that all LSMs use, the security contexts are both read and written through procfs:

root@udoo:/sys/fs/selinux/policy_capabilities # echo 'cat /proc/self/attr/current'

u:r:init_shell:s0

You can also get per-thread contexts as well:

root@udoo:/sys/fs/selinux/policy_capabilities # echo '/proc/self/task/2278/attr/current'

u:r:init_shell:s0

Just replace 2278 with the thread ID you want.

The DAC permissions on the current file are read and write for everyone, but those files are typically very restricted by MAC permissions. Typically, only the process that owns the procfs entry can read the files, and you must have both standard write permissions and a combination of setcurrent. Note that the "from" and "to" domains must be allowed using a dyntransition. To read, you must have getattr. All of these permissions are attained from the security class, process. The libselinux API functions getcon and setconallow you to manipulate current.

The prev file can be used to find the previous context you switched from. This file is not writeable:

root@udoo:/proc/self/attr # echo 'cat prev'

u:r:init:s0

Our serial terminal's former domain or security context was u:r:init:s0.

The exec file is used to set the label for children processes. This is set before running an exec. All the permissions on these files are the same with respect to the MAC permissions used to actually set them. The caller attempting to set this must also hold setexecfrom the process class. The libselinux API int setexeccon(security_context_t context) and int getexeccon(security_context_t *context) can be used for setting and retrieving the label.

The fscreate, keycreate, and sockcreate files do similar things. When a process creates any one of the corresponding objects, fs objects (files, named pipes, or other objects), keys, or sockets, the values set here are used. The caller must also hold setfscreate,setsockcreate, and setkeycreate from the process class. The following SELinux API is used to alter these:

int set*createcon(security_context_t context);

int get*createcon(security_context_t *con);

Where * can be fs, key, or socket.

It's important to note that these special process class permissions give you the ability to change the proc/attr file. You still need to get through the DAC permissions and any SELinux permissions set on the file objects themselves. Then and only then do you need the additional permission, such as setfscreate.

Java SELinux API

Similar APIs to the C APIs discussed previously exist for Java as well. In this case, it is assumed you will build the code with the platform, as these are not public APIs shipped with the Android SDK. The API is located atframeworks/base/core/java/android/os/SELinux.java. However, this is a very limited subset of the API.

Summary

In this chapter, we explored the interface between the kernel and userspace with respect to SELinux, and reinforced the concepts of access vector class and security context. In the next chapter, we will perform some upgrades to our system and look at the audit logs getting one step closer to our ultimate goal—an operable device in SELinux enforcing mode. We say operable because we can put it in enforcing mode now. However, if you do it now via setenforce 1 on a UDOO, your device will become unstable. On our system, for example, the browser fails to launch if we do this.