Exploring SE for Android (2015)

Chapter 5. Booting the System

Now that we have an SE for Android system, we need to see how we can make use of it, and get it into a usable state. In this chapter, we will:

·        Modify the log level to gain more details while debugging

·        Follow the boot process relative to the policy loader

·        Investigate SELinux APIs and SELinuxFS

·        Correct issues with the maximum policy version number

·        Apply patches to load and verify an NSA policy

You might have noticed some disturbing error messages dmesg in Chapter 4Installation on the UDOO. To refresh your memory, here are some of them:

# dmesg | grep –i selinux

<6>SELinux: Initializing.

<7>SELinux: Starting in permissive mode

<7>SELinux: Registering netfilter hooks

<3>SELinux: policydb version 26 does not match my version range 15-23

...

It would appear that even though SELinux is enabled, we don't quite have an error-free system. At this point, we need to understand what causes this error, and what we can do to rectify it. At the end of this chapter, we should be able to identify the boot process of an SE for Android device with respect to policy loading, and how that policy is loaded into the kernel. We will then address the policy version error.

Policy load

An Android device follows a boot sequence similar to that of the *NIX booting sequence. The boot loader boots the kernel, and the kernel finally executes the init process. The init process is responsible for managing the boot process of the device through init scripts and some hard coded logic in the daemon. Like all processes, init has an entry point at the main function. This is where the first userspace process begins. The code can be found by navigating to system/core/init/init.c.

When the init process enters main (refer to the following code excerpt), it processes cmdline, mounts some tmpfs filesystems such as /dev, and some pseudo-filesystems such as procfs. For SE for Android devices, init was modified to load the policy into the kernel as early in the boot process as possible. The policy in an SELinux system is not built into the kernel; it resides in a separate file. In Android, the only filesystem mounted in early boot is the root filesystem, a ramdisk built into boot.img. The policy can be found in this root filesystem at /sepolicy on the UDOO or target device. At this point, the init process calls a function to load the policy from the disk and sends it to the kernel, as follows:

int main(int argc, char *argv[]) {

...

  process_kernel_cmdline();

  unionselinux_callback cb;

  cb.func_log = klog_write;

  selinux_set_callback(SELINUX_CB_LOG, cb);

  cb.func_audit = audit_callback;

  selinux_set_callback(SELINUX_CB_AUDIT, cb);

  INFO("loading selinux policy\n");

  if (selinux_enabled) {

    if (selinux_android_load_policy() < 0) {

      selinux_enabled = 0;

      INFO("SELinux: Disabled due to failed policy load\n");

    } else {

      selinux_init_all_handles();

    }

  } else {

    INFO("SELinux:  Disabled by command line option\n");

  }

In the preceding code, you will notice the very nice log message, SELinux: Disabled due to failed policy load, and wonder why we didn't see this when we ran dmesg before. This code executes before setlevel in init.rc is executed.

The default init log level is set by the definition of KLOG_DEFAULT_LEVEL in system/core/include/cutils/klog.h. If we really wanted to, we could change that, rebuild, and actually see that message.

Now that we have identified the initial path of the policy load, let's follow it on its course through the system. The selinux_android_load_policy() function can be found in the Android fork of libselinux, which is in the UDOO Android source tree. The library can be found at external/libselinux, and all of the Android modifications can be found in src/android.c.

The function starts by mounting a pseudo-filesystem called SELinuxFS. If you recall, this was one of the new filesystems mentioned in /proc/filesystems that we saw in Chapter 4Installation on the UDOO. In systems that do not have sysfs mounted, the mount point is /selinux; on systems that have sysfs mounted, the mount point is /sys/fs/selinux.

You can check mountpoints on a running system using the following command:

# mount | grep selinuxfs

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

SELinuxFS is an important filesystem as it provides the interface between the kernel and userspace for controlling and manipulating SELinux. As such, it has to be mounted for the policy load to work. The policy load uses the filesystem to send the policy file bytes to the kernel. This happens in the selinux_android_load_policy() function:

int selinux_android_load_policy(void)

{

  char *mnt = SELINUXMNT;

  int rc;

  rc = mount(SELINUXFS, mnt, SELINUXFS, 0, NULL);

  if (rc < 0) {

    if (errno == ENODEV) {

      /* SELinux not enabled in kernel */

      return -1;

    }

    if (errno == ENOENT) {

      /* Fall back to legacy mountpoint. */

      mnt = OLDSELINUXMNT;

      rc = mkdir(mnt, 0755);

      if (rc == -1 && errno != EEXIST) {

        selinux_log(SELINUX_ERROR,"SELinux: Could not mkdir:  %s\n",

        strerror(errno));

        return -1;

      }

      rc = mount(SELINUXFS, mnt, SELINUXFS, 0, NULL);

    }

  }

  if (rc < 0) {

    selinux_log(SELINUX_ERROR,"SELinux:  Could not mount selinuxfs:  %s\n",

    strerror(errno));

    return -1;

  }

  set_selinuxmnt(mnt);

  return selinux_android_reload_policy();

}

The set_selinuxmnt(car *mnt) function changes a global variable in libselinux so that other routines can find the location of this vital interface. From there it calls another helper function, selinux_android_reload_policy(), which is located in the same libselinuxandroid.c file. It loops through an array of possible policy locations in priority order. This array is defined as follows:

Static const char *const sepolicy_file[] = {

  "/data/security/current/sepolicy",

  "/sepolicy",

  0 };

Since only the root filesystem is mounted, it chooses /sepolicy at this time. The other path is for dynamic runtime reloads of policy. After acquiring a valid file descriptor to the policy file, the system is memory mapped into its address space, and callssecurity_load_policy(map, size) to load it to the kernel. This function is defined in load_policy.c. Here, the map parameter is the pointer to the beginning of the policy file, and the size parameter is the size of the file in bytes:

int selinux_android_reload_policy(void)

{

  int fd = -1, rc;

  struct stat sb;

  void *map = NULL;

  int i = 0;

  while (fd < 0 && sepolicy_file[i]) {

    fd = open(sepolicy_file[i], O_RDONLY | O_NOFOLLOW);

    i++;

  }

  if (fd < 0) {

    selinux_log(SELINUX_ERROR, "SELinux:  Could not open sepolicy:  %s\n",

    strerror(errno));

    return -1;

  }

  if (fstat(fd, &sb) < 0) {

    selinux_log(SELINUX_ERROR, "SELinux:  Could not stat %s:  %s\n",

    sepolicy_file[i], strerror(errno));

    close(fd);

    return -1;

  }

  map = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);

  if (map == MAP_FAILED) {

    selinux_log(SELINUX_ERROR, "SELinux:  Could not map %s:  %s\n",

    sepolicy_file[i], strerror(errno));

    close(fd);

    return -1;

  }

  rc = security_load_policy(map, sb.st_size);

  if (rc < 0) {

    selinux_log(SELINUX_ERROR, "SELinux:  Could not load policy:  %s\n",

    strerror(errno));

    munmap(map, sb.st_size);

    close(fd);

    return -1;

  }

  munmap(map, sb.st_size);

  close(fd);

  selinux_log(SELINUX_INFO, "SELinux: Loaded policy from %s\n", sepolicy_file[i]);

  return 0;

}

The security load policy opens the <selinuxmnt>/load file, which in our case is /sys/fs/selinux/load. At this point, the policy is written to the kernel via this pseudo file:

int security_load_policy(void *data, size_t len)

{

  char path[PATH_MAX];

  int fd, ret;

  if (!selinux_mnt) {

    errno = ENOENT;

    return -1;

  }

  snprintf(path, sizeof path, "%s/load", selinux_mnt);

  fd = open(path, O_RDWR);

  if (fd < 0)

  return -1;

  ret = write(fd, data, len);

  close(fd);

  if (ret < 0)

  return -1;

  return 0;

}

Fixing the policy version

At this point, we have a clear idea of how the policy is loaded into the kernel. This is very important. SELinux integration with Android began in Android 4.0, so when porting to various forks and fragments, this breaks, and code is often missing. Understanding all parts of the system, however cursory, will help us to correct issues as they appear in the wild and develop. This information is also useful to understand the system as a whole, so when modifications need to be made, you'll know where to look and how things work. At this point, we're ready to correct the policy versions.

The logs and kernel config are clear; only policy versions up to 23 are supported, and we're trying to load policy version 26. This will probably be a common problem with Android considering kernels are often out of date.

There is also an issue with the 4.3 sepolicy shipped by Google. Some changes by Google made it a bit more difficult to configure devices as they tailored the policy to meet their release goals. Essentially, the policy allows nearly everything and therefore generates very few denial logs. Some domains in the policy are completely permissive via a per-domain permissive statement, and those domains also have rules to allow everything so denial logs do not get generated. To correct this, we can use a more complete policy from the NSA. Replace external/sepolicy with the download from https://bitbucket.org/seandroid/external-sepolicy/get/seandroid-4.3.tar.bz2.

After we extract the NSA's policy, we need to correct the policy version. The policy is located in external/sepolicy and is compiled with a tool called check_policy. The Android.mk file for sepolicy will have to pass this version number to the compiler, so we can adjust this here. On the top of the file, we find the culprit:

...

# Must be <= /selinux/policyvers reported by the Android kernel.

# Must be within the compatibility range reported by checkpolicy -V.

POLICYVERS ?= 26

...

Since the variable is overridable by the ?= assignment. We can override this in BoardConfig.mk. Edit device/fsl/imx6/BoardConfigCommon.mk, adding the following POLICYVERS line to the bottom of the file:

...

BOARD_FLASH_BLOCK_SIZE := 4096

TARGET_RECOVERY_UI_LIB := librecovery_ui_imx

# SELinux Settings

POLICYVERS := 23

-include device/google/gapps/gapps_config.mk

Since the policy is on the boot.img image, build the policy and bootimage:

$ mmm -B external/sepolicy/

$ make –j4 bootimage 2>&1 | tee logz

!!!!!!!!! WARNING !!!!!!!!! VERIFY BLOCK DEVICE !!!!!!!!!

$ sudo chmod 666 /dev/sdd1

$ dd if=$OUT/boot.img of=/dev/sdd1 bs=8192 conv=fsync

Eject the SD card, place it into the UDOO, and boot.

Tip

The first of the preceding commands should produce the following log output:

out/host/linux-x86/bin/checkpolicy: writing binary representation (version 23) to out/target/product/udoo/obj/ETC/sepolicy_intermediates/sepolicy

At this point, by checking the SELinux logs using dmesg, we can see the following:

# dmesg | grep –i selinux

<6>init: loading selinux policy

<7>SELinux: 128 avtab hash slots, 490 rules.

<7>SELinux: 128 avtab hash slots, 490 rules.

<7>SELinux: 1 users, 2 roles, 274 types, 0 bools, 1 sens, 1024 cats

<7>SELinux: 84 classes, 490 rules

<7>SELinux: Completing initialization.

Another command we need to run is getenforce. The getenforce command gets the SELinux enforcing status. It can be in one of three states:

·        Disabled: No policy is loaded or there is no kernel support

·        Permissive: Policy is loaded and the device logs denials (but is not in enforcing mode)

·        Enforcing: This state is similar to the permissive state except that policy violations result in EACCESS being returned to userspace

One of the goals while booting an SELinux system is to get to the enforcing state. Permissive is used for debugging, as follows:

# getenforce

Permissive

Summary

In this chapter, we covered the important policy load flow through the init process. We also changed the policy version to suit our development efforts and kernel version. From there, we were able to load the NSA policy and verify that the system loaded it. This chapter additionally showcased some of the SELinux APIs and their interactions with SELinuxFS. In the next chapter, we will examine the filesystem and then move forward in our quest to get the system into enforcing mode.