Yocto Hardening: Kernel Module Signing

You can find the other Yocto hardening posts from here!

Another day, another new way to harden your Yocto systems. This time, we have a relatively simple and effective hardening measure that may prevent big headaches: kernel module signing. I’ve been adding this feature to my secure Yocto distribution Sulka, and I thought that I might as well write a blog post about it.

Module Signing

So, this time we will investigate how to sign Linux kernel modules. Modules are binaries that are built to be run in the kernel with high privileges. A common example of this is device drivers. These modules may be built by the kernel during the build process, or outside the kernel build by third parties. Signing these modules allows the kernel maintainer to control which code runs in the operating system.

Allowing the user (or adversary) to run arbitrary code in the operating system can result in hazardous situations. As the modules are stored in user-space, commonly in the root file system, an adversary can fairly easily create a malicious module that can act for example as a keylogger, rootkit, or backdoor. Loading modules requires superuser privileges, so it is not completely trivial to run malicious code, but still.

We should sign the kernel modules to prevent this. In addition to that, the kernel itself must be configured to require the signatures. Asymmetric cryptography is used here: the kernel modules are signed with a private signing key, and the public signing key is built into the kernel. This should ensure that the modules originate from a trusted source. To take the security one step further, we can also sign the public signing key with a certificate authority private key, and add the certificate authority public key to the kernel trusted keyring. This ensures that the signing keys are trusted.

Me when I have to think about asymmetric key cryptography, what are the secrets, and where does the public information go.

According to the kernel documentation, CONFIG_MODULE_SIG is the kernel configuration flag that should be enabled to enable kernel module signing facility. In addition to that, CONFIG_MODULE_SIG_FORCE should be enabled to ensure that the unsigned modules do not get loaded. Once the signing facility is enabled, the modules built by the kernel are signed with keys defined in the kernel configuration. The keys can either be auto-generated or manually provided using CONFIG_MODULE_SIG_KEY option.

Signing Kernel Modules in Yocto

Let’s move to the Yocto side of things. Like so many times before, we are going to be using meta-security, specifically sub-layer meta-integrity. Long-time readers may remember this layer from the IMA & EVM hardening text. I noticed that meta-secure-core also has a sub-layer meta-integrity that has a module signing feature that works pretty much the same, so as far as I know, it should also work. However, I’ll be sticking to meta-security for now.

Generating Keys

When enabling the module signing in Yocto, we want to manually provide the keys to the build system. This way, the build process and key management are clearly separated. The first step is generating a certificate authority that will be used to sign the module signing certificate. The meta-integrity layer has ima-gen-local-ca.sh script for doing this. Before running it, open the script and edit the [ req_distinguished_name ] section with the correct information. Then, running the script is as simple as:

meta-security/meta-integrity/scripts/ima-gen-local-ca.sh

This command will generate the following files:

ima-local-ca.genkey  # Information used for generating the CA
ima-local-ca.pem     # CA public certificate in plaintext
ima-local-ca.priv    # CA private key. Keep this secret!
ima-local-ca.x509    # CA public certificate in DER format

Once this is done, we can create a certificate signing request and sign it using the certificate authority. The meta-integrity layer has a script for this as well. This script again needs to be updated with correct information, so open the script and edit the [ req_distinguished_name ] section as needed. Once that is done, run the script:

meta-security/meta-integrity/scripts/ima-gen-CA-signed.sh

This command will generate the following files:

csr_ima.pem      # Certificate signing request in plaintext
ima.genkey       # Information used for generating the CSR
privkey_ima.pem  # Private key of the CSR. Keep this secret as well!
x509_ima.der     # Certificate signed by the CA in DER format

Note that all the file names have _ima part in them. I suppose this script is usually used for generating the IMA keys, but it works for module signing keys as well. Just change the _ima to _modsign, e.g. x509_ima.der to x509_modsign.der, and you’re golden.

Finally, we need to convert the DER file into PEM format. This can be done with the openssl using the following command:

openssl x509 -inform DER -in x509_modsign.der -outform PEM -out x509_modsign.crt

After this, you should have the following files that will be used in the Yocto build:

ima-local-ca.pem     # Trust anchor CA that will be built into the kernel
privkey_modsign.pem  # Private key used to sign the kernel modules
x509_modsign.crt     # Public certificate built in kernel for module signature verification
That’s a nice set of keys you have there. Would be a shame if something were to happen to them.

Configuring Yocto

The next steps are enabling the Yocto features to sign the modules, enforcing the module signing in the kernel, and configuring the keys for the build. Let’s begin with the keys.

In your build configuration (e.g. local.conf) you should set the following variables:

MODSIGN_KEY_DIR = "<PATH/TO/KEY/DIRECTORY>"
IMA_EVM_ROOT_CA = "${MODSIGN_KEY_DIR}/ima-local-ca.pem"

Despite the name, IMA_EVM_ROOT_CA can be used as the certificate authority for module signing keys. You could consider it to be a more generic integrity key certificate authority than strictly a module signing CA. If you changed the names of the generated keys more than just by replacing _ima with _modsign, you’ll need to define the individual key files in the configuration. If not, the default names should get automatically picked up.

Moving on from the keys to enabling the distro features. First, you’ll want to enable the generic integrity feature to activate the meta-integrity layer. Then, you’ll want to enable modsign feature to enable module signing. In addition to these, you’ll need to enable ima distro feature as well.

At least at the time of writing, it seems that the certificate authority does not get added to the kernel configuration without ima feature (unsigned module signing keys might work, but I didn’t try). Enabling ima distro feature enables the IMA configuration in the kernel, but does not really enable the feature itself. That requires setting up an IMA policy. So, enabling the IMA feature should not affect your system that much in practice.

Long story short, add the following features to your build configuration:

DISTRO_FEATURES:append = " integrity ima modsign "

And that should be all that’s required. The build system configures the kernel based on these distro features, so we don’t need to touch the kernel configuration. This should work for both in-tree and out-of-tree modules, as long as you are building the out-of-tree modules with a correctly configured kernel. Note that this feature does not sign the pre-built modules that might be distributed by vendors, and that has to be done manually with sign-file script that can be found in the kernel sources.

Testing It Out

To test the module signing, we can first check from the kernel logs that the desired keys actually get loaded:

test@qemux86-64:~$ sudo dmesg |grep cert
[    3.529384] Loaded X.509 cert '[example.com](http://example.com/): meta-intel-iot-security example signing key: 460501e7a2143d52e3abaeb1d24d378d824bff54'
[    3.546836] Loaded X.509 cert '[example.com](http://example.com/): meta-intel-iot-security example certificate signing key: e16733db10cf8d84f5b52aeafe18daead1746c0d'

Uh oh, seems like someone didn’t update their distinguished name information before generating the keys. Shame on me. Before probing modules, let’s first try running modinfo command on one of the modules in the system to confirm the existence of a signature:

test@qemux86-64:~$ sudo modinfo hello
filename:       /lib/modules/6.6.84-yocto-standard/updates/hello.ko
license:        GPL
depends:
retpoline:      Y
name:           hello
vermagic:       6.6.84-yocto-standard SMP preempt mod_unload modversions RANDSTRUCT_6412be83e6a0d7d324039d767555aa87b3efbbf555f150fa215355374b9cd01a
sig_id:         PKCS#7
signer:         meta-intel-iot-security example certificate signing key
sig_key:        65:57:D4:6C:85:3F:A3:96:6D:5C:F1:9C:ED:3E:BF:04:9B:DF:9E:62
sig_hashalgo:   sha256
signature:      30:44:02:20:36:F4:9A:17:8E:B5:35:DB:99:EF:D2:F8:9A:EF:07:0A:
61:1E:63:7F:06:1C:42:AC:79:4B:E1:D7:28:D8:62:5F:02:20:6F:C6:
7D:60:D6:36:6B:3D:07:93:0B:DA:3A:8E:5F:80:50:22:BC:CF:05:46:
DE:CE:E2:FD:7B:93:90:54:9B:64

We can see that the module is signed from the last few lines, namely from the signature field. Next, we should try to probe it:

test@qemux86-64:~$ sudo insmod /lib/modules/6.6.84-yocto-standard/updates/hello.ko
[  183.837366] hello: loading out-of-tree module taints kernel.
[  183.850445] Hello World!
test@qemux86-64:~$

Of course, this wasn’t all that fascinating. I guess we’ve all loaded a module at some point. So, what you could do instead is try to load an unsigned module, or a module signed with the wrong key to see what happens. Here are examples of such failures:

# Module with the wrong signing key
test@qemux86-64:~$ sudo insmod /tmp/hello.ko
[  119.908851] Loading of module with unavailable key is rejected
insmod: ERROR: could not insert module /tmp/hello.ko: Key was rejected by service

# Module without signature
test@qemux86-64:/lib/modules/6.6.84-yocto-standard$ sudo insmod /tmp/hello_unsigned.ko
[  697.716526] Loading of unsigned module is rejected
insmod: ERROR: could not insert module /tmp/hello_unsigned.ko: Key was rejected by service

But now that I showed you what happens in the error cases, it’s not that exciting anymore I guess.

Conclusion

That’s all for this time. Hopefully, you found this useful and can enable the signing feature. Well, enabling the signing itself is quite straightforward, but dealing with the signing keys and the certificates can be cumbersome if you haven’t set up an infrastructure for that. But, all things considered, module signing is a useful security feature that can help to harden your system. More security advice coming soon, so stay tuned!

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

Recommended Reading

Share