Mastering the Tool Chain - Exploring SE for Android (2015)

Exploring SE for Android (2015)

Chapter 12. Mastering the Tool Chain

So far, we have taken a deep dive into the code and policies that drive SE for Android technologies, but the build system and tools are often overlooked. Mastering the tool chain will help you improve your development practices. In this chapter, we will look at all the components of the SE for Android build and how they work. We will cover the following topics:

· Building specific targets

· The sepolicy Android.mk file

· Custom build policy configuration

· Build tools:

· check_seapp

· insertkeys.py

· checkpolicy

· checkfc

· sepolicy-check

· sepolicy-analyze

Building subcomponents – targets and projects

So far, we have run some magical commands such as mm, mmm, and make bootimage to actually build various portions of the SE for Android code. Google officially describes some of these tools in the documents at https://source.android.com/source/building-running.html, but most commands are not listed. Nonetheless, http://elinux.org/Android_Build_System has a write up that is more comprehensive.

In Google's "building and running" documentation, they describe the target as the device, which is ultimately what you lunch for. When building Android, the lunch command sets up environment variables for the make command you execute later. It sets up the build system to output the correct configuration for the target device. This concept of a target is not what will be discussed in this chapter. Instead, when target is mentioned herein, it means a specific make target. However, in the event of needing to mention the target device, the complete phrase "target device" will be used. While somewhat confusing, this terminology is standard and will be understood by engineers in the field.

We have issued make a few times, optionally providing a target as an argument and an option, for example the -j16 option. Something like make or make -j16 essentially builds all of Android. Optionally, you can specify a target or list of targets as command arguments. An example of this is when boot.img was built. The boot.img file can be built and rebuilt by specifying the bootimage target. The command we use for this purpose is make bootimage. It helps to expedite builds by rebuilding only the portions of the system that are needed. But what if you only need to rebuild a particular file? Perhaps, you only want to rebuild sepolicy. You can specify that as the target to build, as in make sepolicy. This leads to the question, "What about the other files such as mac_permissions.xml,seapp_contexts, and so on?" They can be built in the same way. The more intriguing question is, "How does one know what the target name is? Is it always the file output name?"

Android's build system is constructed on top of GNU make (http://www.gnu.org/software/make/). The core of the Android build system's makefiles system can be found in build/core, and the documentation can be found in the NDK (https://developer.android.com/tools/sdk/ndk/index.html). The major take away from that reading is that a typical Android.mk file defines something called LOCAL_MODULE := mymodulename, and something called mymodulename is built. The target names are defined by theseLOCAL_MODULE statements. Let's look at the Android.mk for external sepolicy, and focus on the sepolicy portion of it, as there are other local modules or targets defined in that Makefile. The following is an example from Android 4.3:

include $(CLEAR_VARS)

LOCAL_MODULE := sepolicy

LOCAL_MODULE_CLASS := ETC

LOCAL_MODULE_TAGS := optional

LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)

...

One can find all the modules for within an Android.mk file by just looking for lines that begin with LOCAL_MODULE declarations and are whole word matches:

$ grep -w '^LOCAL_MODULE' Android.mk

LOCAL_MODULE := sepolicy

LOCAL_MODULE := file_contexts

LOCAL_MODULE := seapp_contexts

LOCAL_MODULE := property_contexts

LOCAL_MODULE := selinux-network.sh

LOCAL_MODULE := mac_permissions.xml

LOCAL_MODULE := eops.xml

Regular expressions dictate that ^ is the beginning of the line, and the grep man page states that -w provides whole word search.

The preceding list is comprehensive for the version of Android we are using on the UDOO. However, you should run the command on your exact version of the Makefile to get an idea of what things can be built.

Android has some additional tools that are separate from building targets and get added to your environment when you use source build/envsetup.sh. These are mm and mmm. They both perform the same task, which is to build all the targets specified in an Android.mkfile, however, differing that they do not build any of their dependencies. The two commands only differ in where they source the location of the Android.mk to scour for build targets. The mm command uses the current working directory, whereas mmm uses a supplied path. Also, a great option for either command is -B, which forces a rebuild. An engineer can save a lot of time by using the mm(m) commands over make <target>. The full make command wastes a lot of time figuring out the dependency tree, so executing mmm path/to/project on a previously built source tree (if you know that all your changes are within a project) can save a few minutes. However, since it doesn't build the dependencies, you'll need to ensure that they are already built and have no dependent changes.

Exploring sepolicy's Android.mk

The project located at external/sepolicy uses an Android.mk file, like any other Android project, to build their outputs. Let's dissect this file and see what it does.

Building sepolicy

We'll start in the middle by looking at the target for sepolicy. It starts off with fairly boilerplate Android.mk stuff:

...

include $(CLEAR_VARS)

LOCAL_MODULE := sepolicy

LOCAL_MODULE_CLASS := ETC

LOCAL_MODULE_TAGS := optional

LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)

include $(BUILD_SYSTEM)/base_rules.mk

...

The next portion is a bit more like standard make. It starts off by declaring a target file that gets built into the intermediates location. The intermediates location is defined by the Android build system. It then assigns the values of MLS_SENS and MLS_CATS to some local variables for later use. The last line is the most interesting. It uses a make function, called build_policy, and takes filenames as arguments:

...

sepolicy_policy.conf := $(intermediates)/policy.conf

$(sepolicy_policy.conf): PRIVATE_MLS_SENS := $(MLS_SENS)

$(sepolicy_policy.conf): PRIVATE_MLS_CATS := $(MLS_CATS)

$(sepolicy_policy.conf) : $(call build_policy, security_classes initial_sids access_vectors global_macros mls_macros mls policy_capabilities te_macros attributes bools *.te roles users initial_sid_contexts fs_use genfs_contexts port_contexts)

...

Next, we define the recipe for building this intermediate target, policy.conf. The interesting bits of the recipe are the m4 command and the sed command.

Note

For more information on m4, see http://www.gnu.org/software/m4/manual/m4.html, and for more information on sed, refer to https://www.gnu.org/software/sed/manual/sed.html.

SELinux policy files get processed using m4. m4 is a macro processor language that is often used as a frontend to a compiler. The m4 command takes some of the values such as PRIVATE_MLS_SENS and PRIVATE_MLS_CATS and passes them through as macro definitions. This is analogous to the gcc -D option. It then takes the dependencies for the target as input via the make expansion, $^, and outputs them to the target name using the make expansion of $@. It also takes that output and generates a .dontaudit version. That version has all of the dontaudit lines deleted from the policy file using sed. The MLS values tell SELinux how many categories and sensitivities to generate. These must be statically defined in the policy blob that is loaded into the kernel, as follows:

...

@mkdir -p $(dir $@)

$(hide) m4 -D mls_num_sens=$(PRIVATE_MLS_SENS) -D mls_num_cats=$(PRIVATE_MLS_CATS) -s $^ > $@

$(hide) sed '/dontaudit/d' $@ > $@.dontaudit

...

The next portion defines the recipe for building the actual target, named from LOCAL_MODULE_POLICY, even if this is not obvious. LOCAL_BUILT_MODULE expands to the intermediate file to be built, sepolicy in this case. It finally gets copied by the Android build system asLOCAL_INSTALLED_MODULE behind the scenes. This target depends on the intermediate policy.conf file and on checkpolicy. It uses checkpolicy to transform the m4 expanded policy.conf and policy.conf.dontaudit into two sepolicy files, sepolicy and sepolicy.dontaudit. The actual tool that is used to compile the SELinux statements in binary form to load to the kernel is checkpolicy, as follows:

...

$(LOCAL_BUILT_MODULE) : $(sepolicy_policy.conf) $(HOST_OUT_EXECUTABLES)/checkpolicy

@mkdir -p $(dir $@)

$(hide) $(HOST_OUT_EXECUTABLES)/checkpolicy -M -c $(POLICYVERS) -o $@ $<

$(hide) $(HOST_OUT_EXECUTABLES)/checkpolicy -M -c $(POLICYVERS) -o $(dir $<)/$(notdir $@).dontaudit $<.dontaudit

...

Finally, it ends by setting a local variable, built_policy, for use elsewhere within the Android.mk file, and clears policy.conf to avoid polluting the global namespace of make, as shown:

...

built_sepolicy := $(LOCAL_BUILT_MODULE)

sepolicy_policy.conf :=

...

Additionally, building sepolicy also depends on the POLICYVERS variable, which is conditionally assigned a value of 26 if not set. This is the policy version number used by checkpolicy, and as we saw earlier in the book, we had to override this for our UDOO.

Controlling the policy build

We saw that the sepolicy statement calls the build_policy function. We also see its use in that Android.mk file for building sepolicy, file_contexts, seapp_contexts, property_contexts, and mac_permissions.xml, so it reasons that it is fairly important. This function outputs a list of fully resolved paths used for policy files. The function takes as inputs a variable argument list of filenames and includes regular expression support (note *.te in the build_policy for target sepolicy). Internally, that function uses some magic to allow you to override or append to the current policy build without modifying the external/sepolicy directory directly. This is meant for OEMs and device builders to be able to augment policy to cover their specific devices.

When building a policy, you can set the following make variables, typically in the device's Makefile, to control the resulting build. The variables are as follows:

· BOARD_SEPOLICY_DIRS: This is the search path for potential policy files

· BOARD_SEPOLICY_UNION: This is a policy file of name to append to all files with the same name

· BOARD_SEPOLICY_REPLACE: This is a policy file used to override the base external/sepolicy policy file

· BOARD_SEPOLICY_IGNORE: This is used to remove a particular policy file from the build, given a repository's relative path

Using the UDOO as an example, the proper way to author a policy was never to modify external/sepolicy but to create a directory in device/fsl/udoo/sepolicy:

$ mkdir <PATH>

Then we modify the BoardConfig.mk:

$ vim BoardConfig.mk

Next, we add the following lines:

BOARD_SEPOLICY_DIRS += device/fsl/udoo/sepolicy

Tip

Be very careful with += as opposed to :=. In large project trees, some of these variables may be set higher in the build tree by common BoardConfigs, and you could wipe out their settings. Typically, the safest bet is +=. For further details, see Variable Assignmentin the GNU make manual, at http://www.gnu.org/software/make/manual/make.html.

This will tell the build_policy() function in Android.mk to search not only external/sepolicy but also device/fsl/udoo/sepolicy for policy files.

Next, we can create a file_contexts file in this directory, and move our changes for labeling to this directory by creating a new file_contexts file in device/fsl/udoo/sepolicy.

After this, we need to instruct the build system to combine, or union, our file_contexts file with the one in external/sepolicy. We accomplish this by adding the following statement to the BoardConfig.mk file:

BOARD_SEPOLICY_UNION += file_contexts

You can do this for any policy file, even custom files. It does a match on the filename by basename only (no directories). For instance, if you had a watchdog.te rules file you wanted to add to the base watchdog.te rules file, you could just add watchdog.te, as shown:

BOARD_SEPOLICY_UNION += file_contexts watchdog.te

This produces a new watchdog.te file during the build that unions your new rules with the ones found in external/sepolicy/watchdog.te.

Also note that you add new files into the build with BOARD_SEPOLICY_UNION, so to add a .te file for a custom domain, such as custom.te, you could:

BOARD_SEPOLICY_UNION += file_contexts watchdog.te custom.te

Let's say you want to override the external/sepolicy watchdog.te file with your own. You can add it to BOARD_SEPOLICY_REPLACE, as shown:

BOARD_SEPOLICY_REPLACE := watchdog.te

Note that you can't replace a file that does not exist in the base policy. Also, you can't have the same file appear in UNION and REPLACE, as it's ambiguous. You can't have more than one specification of BOARD_SEPOLICY_REPLACE on the same policy file.

Suppose we have a hierarchical build occurring for two fictitious devices, device X and device Y. The two devices, device X and device Y, both inherit BoardConfigCommon.mk from device A. Device A is not a real device, but since X and Y share commonalities, the common bits are kept in device A.

Suppose the BoardConfigCommon.mk for device A contains these statements:

BOARD_SEPOLICY_DIRS += device/OEM/A

BOARD_SEPOLICY_UNION += file_contexts custom.te

Suppose that device X's BoardConfig.mk contains:

BOARD_SEPOLICY_DIRS += device/OEM/X

BOARD_SEPOLICY_UNION += file_contexts custom.te

Finally, suppose device Y's BoardConfig.mk contains:

BOARD_SEPOLICY_DIRS += device/OEM/Y

BOARD_SEPOLICY_UNION += file_contexts custom.te

The resulting policy sets used to build device X and device Y are the following:

Device X policy set:

device/OEM/A/file_contexts

device/OEM/A/custom.te

device/OEM/X/file_contexts

device/OEM/X/custome.te

external/sepolicy/* (base policy files)

Device Y also contains:

device/OEM/A/file_contexts

device/OEM/A/custom.te

device/OEM/Y/file_contexts

device/OEM/Y/custom.te

external/sepolicy/* (base policy files)

In a common scenario, you might not want the resulting policy set for device Y to contain device/OEM/A/custom.te. This is a use case for BOARD_SEPOLICY_IGNORE. You can use this to filter out specific policy files. However, you have to be specific and use the repository's relative path. For example, in device Y's BoardConfig.mk:

BOARD_SEPOLICY_IGNORE += device/OEM/A/custom.te

Now, when you build a policy for device Y, the policy set will not include that file. BOARD_SEPOLICY_IGNORE can also be used with BOARD_SEPOLICY_REPLACE, allowing multiple uses in the device hierarchy, but only one BOARD_SEPOLICY_REPLACE statement takes effect.

Digging deeper into build_policy

Now that we have seen how to use some new mechanisms to control the policy build, let's actually dissect where in the build process happens. As stated earlier, the policy build is controlled by the Android.mk file. We encountered calls to the build_policy() function earlier, and this is precisely where the magic happens with respect to all of the BOARD_SEPOLICY_* variables we set. Examining the build_policy function, we see references to the sepolicy_replace_paths variable, so let's start by looking at that variable.

The sepolicy_replace_paths variable begins life by getting evaluated when the Makefile is evaluated. In other words, it is executed unconditionally. The code starts off by looping over all the BOARD_SEPOLICY_REPLACE files and checks whether any are inBOARD_SEPOLICY_UNION. If one is found, an error is printed and the build fails, showing Ambiguous request for sepolicy $(pf). Appears in both BOARD_SEPOLICY_REPLACE and BOARD_SEPOLICY_UNION, where $(pf) is expanded to the offending policy file. After that, it expands theBOARD_SEPOLICY_REPLACE entries with those found on the search paths set by BOARD_SEPOLICY_DIRS, thus resulting in full relative paths from the root of the Android tree. Then it filters these entries against BOARD_SEPOLICY_IGNORE, dropping anything that should be ignored. It then ensures that only one file candidate for replacement is found. Otherwise, it issues the appropriate error message. Lastly, it ensures that the file exists in the LOCAL_PATH or base policy, and if none of the two is found, it issues an error message:

...

# Quick edge case error detection for BOARD_SEPOLICY_REPLACE.

# Builds the singular path for each replace file.

sepolicy_replace_paths :=

$(foreach pf, $(BOARD_SEPOLICY_REPLACE), \

$(if $(filter $(pf), $(BOARD_SEPOLICY_UNION)), \

$(error Ambiguous request for sepolicy $(pf). Appears in both \

BOARD_SEPOLICY_REPLACE and BOARD_SEPOLICY_UNION), \

) \

$(eval _paths := $(filter-out $(BOARD_SEPOLICY_IGNORE), \

$(wildcard $(addsuffix /$(pf), $(BOARD_SEPOLICY_DIRS))))) \

$(eval _occurrences := $(words $(_paths))) \

$(if $(filter 0,$(_occurrences)), \

$(error No sepolicy file found for $(pf) in $(BOARD_SEPOLICY_DIRS)), \

) \

$(if $(filter 1, $(_occurrences)), \

$(eval sepolicy_replace_paths += $(_paths)), \

$(error Multiple occurrences of replace file $(pf) in $(_paths)) \

) \

$(if $(filter 0, $(words $(wildcard $(addsuffix /$(pf), $(LOCAL_PATH))))), \

$(error Specified the sepolicy file $(pf) in BOARD_SEPOLICY_REPLACE, \

but none found in $(LOCAL_PATH)), \

) \

)

After this, calls to build policy can use replace_paths as an expanded list of files that will be replaced during the build.

The arguments of the build_policy function are the filenames you wish to expand into their Android root-relative path names, using the power provided by the BOARD_SEPOLICY_* family of variables. For instance, a call to $(build_policy, file_contexts) in the context of our devices A, X, and Y would result in this:

device/OEM/A/file_contexts

device/OEM/Y/file_contexts

The build_policy function is a bit tricky to read. Many nested function calls result in the deepest indents running first. However, like all code, we read it from top to bottom and left to right, so the explanation will begin there. The function starts by looping through all the files passed as arguments. It then expands them against the BOARD_SEPOLICY_DIRS once for replace and once for a union. The sepolicy_replace_paths variable is error checked to ensure a file does not appear in both locations, replace and union. For the replace path expansion, it checks whether the expanded path is in sepolicy_replace_dirs, and if it is, replaces it. For the union portion, it just expands them. The results of these expansions are then fed through a filter on BOARD_SEPOLICY_IGNORE, thus dropping any of the explicitly ignored paths:

# Builds paths for all requested policy files w.r.t

# both BOARD_SEPOLICY_REPLACE and BOARD_SEPOLICY_UNION

# product variables.

# $(1): the set of policy name paths to build

build_policy = $(foreach type, $(1), \

$(filter-out $(BOARD_SEPOLICY_IGNORE), \

$(foreach expanded_type, $(notdir $(wildcard $(addsuffix /$(type), $(LOCAL_PATH)))), \

$(if $(filter $(expanded_type), $(BOARD_SEPOLICY_REPLACE)), \

$(wildcard $(addsuffix $(expanded_type), $(sort $(dir $(sepolicy_replace_paths))))), \

$(LOCAL_PATH)/$(expanded_type) \

) \

) \

$(foreach union_policy, $(wildcard $(addsuffix /$(type), $(BOARD_SEPOLICY_DIRS))), \

$(if $(filter $(notdir $(union_policy)), $(BOARD_SEPOLICY_UNION)), \

$(union_policy), \

) \

) \

) \

)

...

Building mac_permissions.xml

The mac_permissions.xml build is a bit tricky, as we saw in Chapter 10, Placing Applications in Domains. First, mac_permissions.xml can be used with all the BOARD_SEPOLICY_* variables introduced thus far. The end result is one XML file adhering to the rules of those variables. Additionally, the raw XML files are processed by a tool called insertkeys.py, located in sepolicy/tools. The insertkeys.py tool uses keys.conf to map tags in the XML file signature stanza with .pem files containing the certificate. The keys.conf file is also subject to use in BOARD_SEPOLICY_* variables. The build recipe first calls build_policy on keys.conf and uses m4 to concatenate the results. Thus, m4 declarations in keys.conf will be respected. However, this has not been used. The initial intention was to use the m4 -ssync lines so that you can follow the inclusion chain in the keys.conf file when concatenated by m4 processing. On the other hand, sync lines are provided by m4 when concatenating many files, and they provide commented lines adhering to the #line NUM "FILE"'lines. These are useful because m4 takes multiple input files and combines them into a single, expanded output file. There will be sync lines indicating the beginning of each of those files, and they can help you track down issues. Continuing back to themac_permissions.xml build, after expansion of keys.conf by m4, this file, along with all the mac_permissions.xml files from a call to build_policy() are finally fed to insertkeys.py. The insertkeys.py tool then uses the keys.conf file to replace all matching signature=<TAG> lines with an actual hex-encoded X509 from the PEM file, that is, signature=308E3600. Additionally, the insertkeys.py tool combines the XML files into one file, and strips whitespace and comments to reduce its size on disk. This has no build dependencies on the other major files such as sepolicy, seapp_contexts, property_contexts, and mac_permissions.xml.

Building seapp_contexts

The seapp_contexts file is also subject to all the BOARD_SEPOLICY_* variables. All of the seapp_contexts files from a resultant call to build_policy() are also fed through m4 -s to get a single seapp_contexts file that contains sync lines. Again, like mac_permissions.xml file's build of keys.conf, m4 hasn't been used other than for the synclines. This resulting, concatenated seapp_contexts file is then fed into check_seapp. This tool is authored in the C programming language and built into an executable during the build. The source can be found in tools/check_seapp. This tool reads the seapp_contexts file and checks its syntax. It verifies that there are no invalid key value pairs, that levelFrom is a valid identifier, and that the type and domain fields are valid for a given sepolicy. This build is dependent onsepolicy for the strict type checking of domain and type fields against the policy file.

Building file_contexts

The file_contexts file is also subject to all of the BOARD_SEPOLICY_* variables. The resulting set is passed through m4 -s, and the single output is run through the checkfc tool. The checkfc tool checks the grammar and syntax of the file and also verifies that the types exist in the built sepolicy. Because of this, it is dependent on the sepolicy build.

Building property_contexts

The property_contexts behaves exactly like the file_contexts build, except that it checks a property_contexts file. It also uses checkfc.

Current NSA research files

Additionally, work on Enterprise Operations (eops) is already underway at the NSA. As this feature hasn't been merged into mainstream Android and is likely to change wildly, it won't be covered here. However, the best place for the bleeding edge is always the source and NSA Bitbucket repositories. The selinux-network.sh also falls under this category; it hasn't seen mainstream adoption yet, and will likely be dropped from AOSP (https://android-review.googlesource.com/#/c/114380/).

Standalone tools

There are also some standalone tools built for Android policy evaluation that you may find useful. We will explore some of them and their usages. Most of the standard desktop tools you'll find in other references still work on SE for Android SELinux policy. Note that if you run any of the following tools and get a segmentation fault, you will likely need to apply the patch from the thread at http://marc.info/?l=seandroid-list&m=141684060409894&w=2.

sepolicy-check

This tool allows you to see whether a given allow rule exists in a policy file. The basic syntax of its command is as follows:

sepolicy-check -s <domain> -t <type> -c <class> -p <permission> -P <policy_file>

For instance, if you want to see whether system_app can write to system_data_file for class file, you can execute:

$ sepolicy-check -s system_app -t system_data_file -c file -p write -P $OUT/root/sepolicy

sepolicy-analyze

This is a good tool to check for common issues in SELinux development and it catches some of the common pitfalls of new SELinux policy writers. It can check for equivalent domains, duplicate allow rules. It can also perform policy type difference checks.

The domain equivalence check feature is very helpful. It shows you domains you may (in theory) want to be different, even though they converged in the implementation. These types would be ideal candidates to coalesce. However, it might have also shown an issue in the design of the policy that should be corrected. In other words, you didn't expect these domains to be equivalent. Invoking the command is as follows:

$ sepolicy-analyze -e -P $OUT/root/sepolicy

The duplicate allow rule checks whether allow rules exist on types that also exist on attributes that the type inherits from. The allow rule on the specific type is a candidate for removal, since there is already an allow on the attribute. To execute this check, run the following command:

$sepolicy-analyze -D -P $OUT/root/sepolicy

The difference is also handy is also handy to view type differences within a file. If you want to see what the difference between two domains is, you can use this feature. This is useful for identifying possible domains to coalesce. To perform this check, execute the following command:

$sepolicy-analyze -d -P $OUT/root/sepolicy

Summary

In this chapter, we covered how the various components that control the policy on the device are actually built and created, such as sepolicy and mac_permissions.xml. This chapter also presented the BOARD_SEPOLICY_* variables used to manage and build a policy across devices and configurations. Then we reviewed the Android.mk components, detailing how the heart of the build and configuration management works.