kernel

Yocto Hardening: IMA and EVM

You can find the other Yocto hardening posts from here!

I think I made a big promise on LinkedIn that I’ll “write multiple texts about measurement” this year. Well, the end of the year is approaching rapidly and I still have written only one text about it, the measured boot one. For some reason, this always tends to happen when I promise something. I hope now that I’m writing a second text it counts as “multiple texts”. So let’s continue measuring where we left off, move from the bootloader side to the kernel world, and try out the IMA, integrity measurement subsystem in Linux.

What Is IMA (and EVM)

IMA is an acronym for integrity measurement architecture. It is a security subsystem in Linux that can be used to ensure the system’s integrity by measuring and appraising files before they are accessed or executed. The measurement consists of creating hashes of the files in the system and storing the hashes in the measurement list. Appraisal on the other hand is a fancy word for validation. The appraisal process compares the signature (or hash) calculated from the file content to a known good signature that is usually stored in the file’s extended attributes. If the actual and expected values do not match, access or execution may be denied. This kind of feature is mostly useful for read-only file systems.

IMA measurement can be thought of as an extension to measured boot, as it extends the measurements performed by the bootloader to the Linux space. These measurements can then be checked by the attestation process to confirm the system’s integrity. IMA appraisal on the other hand is a kind of a Linux extension to the secure boot, where the file usage may be denied if their content doesn’t match the expected values.

Closely related to IMA is EVM, the extended verification module. While IMA focuses on protecting the file integrity, EVM protects the metadata of the file, like permissions, ownership and extended attributes. Not only is keeping the permissions and ownership in check good for preventing unauthorized access, but this kind of verification is also useful because IMA (and EVM as well) use the extended attributes to store security information. EVM generates a signature or an HMAC of the metadata and stores it in the xattrs for appraisal needs.

Enabling IMA

Since IMA is a kernel feature, it is enabled via kernel configuration. To enable it you can set CONFIG_IMA=y in your configuration. However, since we are talking about quite a large feature, it can be configured further quite extensively (as can be seen from the Kconfig file). In addition, it is also a security feature that tend to be quite complex to configure. At least in my personal experience. That one option should be enough to get started, after that, you can start going through the documentation and source code to figure out why it isn’t working like you expect.

Enabling IMA in Yocto

To enable IMA it’s usually better to try and find some proper configuration fragment that sets a sensible default configuration. In Yocto, you’ll most likely want to check out meta-integrity sub-layer in the meta-security repository. It can be used to easily enable IMA, set the required keys, sign the root file system contents, install some extra packages, and more. It can also enable EVM.

According to the readme of the meta-integrity, you want to enable (at least) the following configuration items in local.conf to enable IMA with the debug keys:

DISTRO_FEATURES:append = " integrity ima"

IMAGE_CLASSES += "ima-evm-rootfs"

IMA_EVM_KEY_DIR = "${INTEGRITY_BASE}/data/debug-keys"
IMA_EVM_PRIVKEY = "${IMA_EVM_KEY_DIR}/privkey_ima.pem"
IMA_EVM_X509 = "${IMA_EVM_KEY_DIR}/x509_ima.der"
IMA_EVM_ROOT_CA = "${IMA_EVM_KEY_DIR}/ima-local-ca.pem"

In reality, you don’t want to use debug keys, because they are publicly available for anyone. The readme gives instructions on how to generate your keys since it is a somewhat complex process and includes always-so-confusing openssl commands. I recommend taking a look at the scripts used to generate the CA and CSR.

It is only four commands, but if one command takes 12 options it takes a while to understand what’s going on.

The two distro features are used in a few places in the meta-layer, but both should be added to enable IMA. The kernel configuration consists of enabling ima feature, which enables this configuration fragment. Note that this applies only to linux-yocto, for other kernel recipes you need to do some integration work. As you can see there are quite a few more options than just CONFIG_IMA, not all of them related to IMA.

ima-evm-rootfs image class adds a new rootfs task that generates the IMA & EVM signatures for the files in the root file system so we won’t have to do that ourselves. This is done using evmctl command. In theory, this step could be done on the hardware as it is running, but since it can be done as a part of the image build it is easier to do it before booting the image. If you want to check the generated signatures, you can use the following command

getfattr -m - -e hex -d <FILE_NAME>

Then, the keys. There are three different key files defined: certificate authority (IMA_EVM_ROOT_CA), a CSR signed by the certificate authority (IMA_EVM_X509), and a private key that has signed the CSR (IMA_EVM_PRIVKEY). The CA gets built into the trusted keyring of the kernel. The CSR is stored somewhere in the device, as IMA uses it to verify the signatures. The private key that has been used to sign CSR will be used for generating the root file system signatures. There should be no private keys installed in the system.

After these are set, you should be good to go for building your Yocto image with IMA enabled.

IMA Measurement

Let’s get to the actual IMA functionality. First, let’s measure stuff as it’s simpler. After all, it’s easier to measure stuff and not care about the result. Caring about the results is the responsibility of the ever-so-distant attestation process that I keep talking about but never really properly addressing. Anyway, to perform the measurement, all you have to do is add the following to the kernel boot parameters:

ima_policy=tcb

After that, you can read the measurement results from /sys/kernel/security/ima/ascii_runtime_measurements. The measurements in this file are extended, meaning that a new value is hashed with the previous value to create a hash chain. tcb policy measures following, according to the kernel documentation:

The "tcb" policy measures all programs exec'd, files mmap'd for exec, and all files opened with the read mode bit set by either the effective uid (euid=0) or uid=0.

If a TPM is available when the measurement begins, the IMA will start the measurement list from something called boot_aggregate. It is a hash of TPM’s PCR registers 0 to 7. This extends the measured boot to the Linux kernel as the IMA continues the measurement from where the measured boot left off. If TPM is not available, the measurement will still start from boot_aggregate, but its value is zero.

IMA Appraisal

That was the quick part, then to the more complicated section: appraisal. In a way, IMA measurement could be considered a remote attestation feature, and the IMA appraisal would be its local counterpart.

First, we need to add some additional kernel configuration options. For one reason or another, the ima kernel feature enabled by ima distro feature doesn’t enable appraisal, so let’s add these two items to the kernel configuration:

CONFIG_IMA_APPRAISE=y
CONFIG_IMA_LOAD_X509=y

The first one is required for the appraisal feature to be built. The second one is required for the IMA xattr signature verification. The ima distro feature installs the signed CSR to the default /etc/keys location, but if the X509 load configuration option isn’t enabled, it doesn’t get loaded to the .ima keyring by kernel. If you don’t want to have the CSR in your root file system, you could load it with a script from initramfs, but let’s settle for using the file system for now. In addition to these two options, you most likely want to add CONFIG_AUDIT=y so that the IMA appraisal violations get logged to dmesg.

Then, we need to generate the signatures for the file contents. The ima-evm-rootfs image class does this for us during build. I however strongly recommend taking a look at the script and seeing how the signing is done to get a better understanding of it. A single evmctl command can generate both IMA and EVM signatures (more on EVM later).

Testing IMA Appraisal

We should now have a kernel and root file system ready. To make the appraisal feature actually do something, we need to define the appraisal policy. The following can be added to the kernel boot arguments to use one of the default appraisal policies:

ima_policy=appraise_tcb

This in practice appraises all the files owned by root. In addition to defining policy, ima_appraise argument can be used to control how the IMA behaves if it detects appraisal issues. Default is enforce, meaning the policy gets enforced (more on policies a bit later). In addition to enforcing the policy, appraisal can also just log issues, do nothing with off, or correct the incorrect signatures with fix option. In the first boot, you may want to have the following boot arguments:

ima_policy=appraise_tcb ima_appraise=fix

However, this shouldn’t be required in our case. As mentioned, the ima-evm-rootfs image feature should generate correct signatures to the rootfs image.

Let’s give the appraisal a try. Boot up the image, run for example kbdinfo --help and ensure that something gets output. Then try to echo something to /usr/bin/kbdinfo. This should fail with Permission denied error. If you’re using a virtualized image, you can mount the disk image and then edit the file contents on a machine that doesn’t have IMA active. After doing that, if we just try to run kbdinfo we’ll get the Permission denied error.

If hash based appraisal was used we could simply update the hash to the updated value. However, since we have the signature based appraisal we need to use the CSR signing key to create the correct signature for the modified file. Any other key for generating the signature doesn’t work, because the CA for the signed CSR is built into the kernel. Someone could in theory switch both the signed CSR file in /etc/keys and the CA in the kernel to be able to modify the filesystem without IMA being alerted. To prevent this kind of situation you’d most likely want to have a secure boot in place to avoid kernel being modified or switched.

Writing Custom IMA Policies

The appraise_tcb policy is quite strict. Too strict in fact, as it causes some issues with systemd that edits files during boot. At least writing the transient machine ID and loading/saving the random seed fails. In addition, all operations related to ld.so.cache and shell history fail. I’m not going to show how to fix these, but I’ll show how to write a custom policy that could be used to fix (some of) them. Also, if you want to know how to disable the shell history you can read about it here.

So, we want to write a custom policy that is a bit more lenient. We can start building from the default policy and go from there. Boot a kernel with the desired default policy, and read it from /sys/kernel/security/ima/policy. If you want to enable both measurement and appraisal, you can define ima_policy parameter multiple times, and the resulting policy will be a union of the defined policies. The default appraisal policy looks like this:

dont_appraise fsmagic=0x9fa0
dont_appraise fsmagic=0x62656572
dont_appraise fsmagic=0x64626720
dont_appraise fsmagic=0x1021994
dont_appraise fsmagic=0x858458f6
dont_appraise fsmagic=0x1cd1
dont_appraise fsmagic=0x42494e4d
dont_appraise fsmagic=0x73636673
dont_appraise fsmagic=0xf97cff8c
dont_appraise fsmagic=0x43415d53
dont_appraise fsmagic=0x6e736673
dont_appraise fsmagic=0xde5e81e4
dont_appraise fsmagic=0x27e0eb
dont_appraise fsmagic=0x63677270
appraise func=POLICY_CHECK appraise_type=imasig
appraise fowner=0

There are plenty of pseudo file systems that are listed as not to be appraised. Then there’s the POLICY_CHECK line that should ensure that a policy loaded after this should be signed, and finally the fowner line that defines that all the root-owned files should be appraised.

The policy syntax can be found here. Unfortunately, it doesn’t allow too fine-grained control over what gets appraised and what doesn’t. It mostly allows filtering files by the file system ID, owner/group, and the processes by UID/GID options. Let’s for the sake of example disable appraisal based on the group of the file.

The core-image-base image in Yocto installs the dbus package that installs one interesting file: /usr/libexec/dbus-daemon-launch-helper. This file’s group is set to messagebus, and as an example we can easily disable the appraisal for that file. The default policy can be extended with one line to do that:

dont_appraise fsmagic=0x9fa0
dont_appraise fsmagic=0x62656572
dont_appraise fsmagic=0x64626720
dont_appraise fsmagic=0x1021994
dont_appraise fsmagic=0x858458f6
dont_appraise fsmagic=0x1cd1
dont_appraise fsmagic=0x42494e4d
dont_appraise fsmagic=0x73636673
dont_appraise fsmagic=0xf97cff8c
dont_appraise fsmagic=0x43415d53
dont_appraise fsmagic=0x6e736673
dont_appraise fsmagic=0xde5e81e4
dont_appraise fsmagic=0x27e0eb
dont_appraise fsmagic=0x63677270
dont_appraise fgroup=999
appraise fowner=0

Note that the filtering happens before listing the files that should be appraised. In our case we don’t want to appraise files that have the group attribute set to 999, which should be the messagebus group. After the custom policy is ready it can be installed by defining IMA_EVM_POLICY for example in the local.conf:

IMA_EVM_POLICY="<PATH_TO_CUSTOM_POLICY" 

This installs the policy to /etc/ima/ima-policy, where it gets loaded as a part of the systemd initialisation. You may want to use the default policies during the early boot or use initramfs to load a custom policy earlier. You’ll also want to use initramfs to load the custom policy if you’re not using systemd. Example of this is available in the meta-integrity.

After defining the custom policy you should be able to modify the dbus-daemon-launch-helper as much as you want, but editing any other file owned by root should fail. This way you can create more lenient appraisal processes that won’t interfere with the system initialization.

For what it’s worth, meta-security has a policy named ima_policy_appraise_all. This appraises all the executables and libraries when they are about to be executed or loaded into memory. This kind of appraisal should prevent tampered or unsigned code from being run, but it doesn’t prevent modifications to the file system. Here are the relevant two lines from that policy:

# First there are the usual fsmagic exclusions
# Then:
# Check when memmapping executable code
appraise func=MMAP_CHECK mask=MAY_EXEC
# Check when binary program is executed
appraise func=BPRM_CHECK

It is also worth knowing that a custom policy can define what files get measured. I focused mostly on appraisal, but configuring the measurement works the same way.

EVM

Finally, the closely related security feature securing IMA (and a few other security features). Extended verification module, or simply put EVM, is a feature that signs the metadata of the file. This metadata includes the mode, UID & GID, and the signatures from multiple different security features. These get mashed into one signature that is then used to verify the metadata. This EVM chapter is mostly going to be reiterating the IMA chapter because the features are quite similar.

s/ima/evm/g

Enabling EVM is easy. Just add the following items to your kernel configuration:

CONFIG_EVM=y
CONFIG_EVM_LOAD_X509=y

The first configuration item enables EVM. The second feature is similar to the one in IMA, it enables loading the public signed CSR from /etc/keys (or other configurable location) to the .evm keyring. This is again required for the signature verification. It is worth noting that since we’re using a single evmctl command to generate both IMA and EVM signatures the public key will be the same.

Next, we need to generate the EVM signatures. Once again, the ima-evm-rootfs image class generates these for us. Therefore, we don’t need to concern ourselves with it, but it’s good to know that it should be done in case it’s not done automatically.

After that we can just boot up the device, no special kernel command line parameters are required. Well, there’s evm=fix parameter for labeling purposes, but it is not required to enable EVM. Instead, to enable EVM, we need to echo a value to /sys/kernel/security/evm. The possible values are defined in IMA & EVM documentation. In our case, we want to set the EVM mode to 2 to enable signature verification. However, since we have enabled CONFIG_EVM_LOAD_X509 the status gets set to 2 after the key is loaded and we don’t even have to do that.

To demonstrate the feature, we can try a similar flow as with IMA, but instead of modifying the file contents, we will modify the metadata. For example, attempting chmod on a file should fail with Operation not permitted. Similarly, we can try an offline attack by mounting the disk image, modifying the file metadata and booting the machine again. Now just reading/executing a file should fail with the Operation not permitted because the EVM signature doesn’t match the metadata.

Closing Words

This text was in the works for about eight months. I originally intended IMA just to be a subchapter in the measured boot text, but that would have been quite a long subchapter. In the end, the core idea of IMA is quite simple, but there are a lot of things to understand to get it working right. This blog text considers a simple case with an immutable, pre-signed root file system. However, if you for example have to perform a partial update on the file system, generating the signatures and getting everything right again will be more problematic. On that depressing note, thanks for reading and hopefully I’ll see you in the next blog post!

As a reminder, if you’re interested you can find the previous Yocto hardening posts from here!

Recommended Reading and Sources

Finally, here’s the list of the source material I used for writing this blog text. I’ll try to start a habit of adding these to the end of each blog text for easy access. These are complex topics and usually reading about the same thing from multiple sources can help understand the big picture better. Or it’ll just be confusing, it’s a bit of a 50/50 chance. The list is not complete, but I think these were the most useful ones: