While Bootlin is largely known for its expertise with the Buildroot and Yocto/OpenEmbedded build systems, we do also work with other build systems for customer projects. Specifically, in 2019, we have worked for one of our customers on extending OpenWrt to add support for two security features: dm-verity and SELinux, which we have contributed to upstream OpenWrt. In this blog post, we provide some details about those features, and how they are integrated in OpenWrt.
dm-verity is a device mapper target that allows to create a block device on top of an existing block device, with a transparent integrity checking in-between. Provided a tree of per-block hashes that is generated offline, dm-verity will verify at run-time that all the data read from the underlying block device matches the hashes that are provided. This allows to guarantee that the data has not been modified, as a root hash must be passed from a trusted source when setting up the dm-verity block device at boot time. If any bit in the storage has been modified, the verification of the hashes all the way up to the root hash will fail, and the I/O operation on the block of data being read from storage will be rejected. Therefore, dm-verity is typically used as part of a secure boot strategy, which allows the root hash to be passed by the bootloader to the kernel, where the bootloader and kernel themselves are verified by other means. Also, due to the nature of the integrity verification, dm-verity provides a read-only block device, and will therefore only work with read-only filesystems.
dm-verity was also presented in our Secure Boot from A to Z talk the Embedded Linux Conference 2018, from slide 28.
We implemented an integration of this mechanism in OpenWrt, and contributed a first version back in March, and we just sent a second version in November.
In essence, our integration consists in:
- Packaging the different tools that are needed to generate at build time the tree of hashes corresponding to a given filesystem image. The important package here is cryptsetup (patch 05/12), but it requires packaging a few dependencies: libjson-c (patch 04/12), popt (patch 03/12), lvm2 (patch 02/12) and libaio (patch 01/12)
- Extending the mechanism of OpenWrt to generate FIT images for the kernel so that it can include a U-Boot script (patch 06/12 and patch 08/12). Indeed, we have chosen to embed the root hash information inside the FIT image, as FIT image can be signed and verified by the bootloader before booting, ensuring that the root hash is part of the trusted information.
- Extending the squashfs filesystem image generation logic so that a dm-verity-capable image can optionally be generated (patch 09/12). If this is the case, then the squashfs image itself is concatenated with the tree of hashes, and a U-Boot script containing the details of the dm-verity image is generated. This includes the important root hash information.
- Backporting to the 4.14 and 4.19 Linux kernels currently supported by OpenWrt the DM_INIT mechanism that is in upstream Linux since 5.1, and which allows to setup a device mapper target at boot time using the
dm-mod.create=kernel argument (patch 10/12). This allows to have the root filesystem on a device mapper block device, without the need for an initramfs to setup the device mapper target.
- Showing with the example of the Marvell Armada XP GP platform how to enable this mechanism on a specific hardware platform already supported by OpenWrt (patch 11/12 and patch 12/12).
For more details, you can read the cover letter of the patch series.
SELinux is a Linux security module that implements Mandatory Access Control and that is generally pretty infamously known in the Linux user community for being difficult to use and configure. However, it is widely used in security-sensitive systems, including embedded systems and as such, makes sense to see supported in OpenWrt. For example, SELinux is already supported in the Yocto/OpenEmbedded ecosystem through the meta-selinux layer, and in the Buildroot project since 2014, contributed by Collins Aerospace.
In short, the basic principle of SELinux is that important objects in the system (files, processes, etc.) are associated to a security context. Then, a policy defines which operations are allowed, depending on the security context of who is doing the operation and on what the operation takes place. This policy is compiled into a binary policy, which is loaded into the kernel early at boot time, and then enforced by the kernel during the system life. Of course, around this, SELinux provides a wide range of tools and libraries to manipulate the policy, build the policy, debug policy violations, and more.
The SELinux support in OpenWrt comes in two parts: a number of additional packages for various libraries and applications, and some integration work in OpenWrt. We will cover both in the next sections. It is worth mentioning that our work does not provide a SELinux policy specifically modified or adjusted for OpenWrt: we simply use the SELinux reference policy, which users will have to tune to their needs.
Getting SELinux to work required a number of new packages to be added in OpenWrt. Those packages were contributed to the community-maintained package feed at https://github.com/openwrt/packages/. They were initially submitted through the mailing list, and then submitted as a pull request.
In short, it contains:
- libsepol, the binary policy manipulation library.
- libselinux, the runtime SELinux library that provides interfaces to ELinux-aware applications.
- audit, which contains the user space utilities for storing and searching the audit records generated by the audit subsystem of the Linux kernel.
- libcap-ng, which allows to use the POSIX capabilities, and is needed by policycoreutils.
- policycoreutils, which is the set of core SELinux utilities such as sestatus, secon, setfiles, load_policy and more.
- libsemanage, which is the policy management library.
- checkpolicy, which is the SELinux policy compiler.
- refpolicy, which is the SELinux reference policy.
- selinux-python, a number of SELinux tools written in Python, especially audit2allow for policy debugging.
Our second patch series, for OpenWrt itself, allows to build a SELinux-enabled system thanks to the following changes:
- Allow to build Busybox with SELinux support, so that all the Busybox applets that support SELinux specific options such as
-Zcan be built with libselinux (patch 1/7)
- Add support in OpenWrt’s init application, called
procd, for loading the SELinux policy at boot time (patch 2/7). This patch has been submitted separately for integration into the
- Add support for building a new host tool called
- Add support for building squashfs images with extended attributes generated by SELinux
setfilestool (patch 4/7). This is why fakeroot is needed: writing those extended attributes that store SELinux security contexts require root access, so we run the entire process within a fakeroot environment. This also requires building the squashfs tools with extended attributes support (patch 7/7).
- Add new options to enable in the Linux kernel support for SELinux and SquashFS with extended attributes (patch 5/7 and patch 6/7).
Integrating those two security features in OpenWrt required numerous changes in the build system, and the corresponding patches are still under review by the OpenWrt community. We hope to see these features merged in 2020.
9 thoughts on “Security contributions to OpenWrt: dm-verity and SELinux”
Would opkg need some sort of SELinux awareness as well? It would probably need to use getfscreatecon(3) to get and set the contexts to install the files with at runtime to ensure consistent labels?
This is what RPM uses:
Yes, obviously opkg would need SELinux support as well. Complete SELinux support in OpenWrt will definitely require a lot of effort: our proposal is some minimal SELinux integration, which works fine for basic OpenWrt systems that don’t use packages (our system uses dm-verity, so the rootfs is completely read-only).
Thanks. Will your proposal support runtime configuration, or will SELinux configuration be immutable at runtime by default?
Nevermind, I think I found the answer to my question in one of the patches. Looks lile SECURITY_SELINUX_DEVELOP will be set to y.
Great article, great job!
Just have a question related to the safe root hash and other parameters passing to kernel. I guess it requires some patching of u-boot, say to generate bootargs in the code before handing over control to kernel, or encrypting u-boot environment. Otherwise the environment could be compromised even if u-boot and kernel are trusted.
The other thing, I couldn’t find a way on how to make u-boot to verify script’s node signature, ‘source’ command just checks hashes.
Just curies how you handled this in your test?
In a typical secure boot scenario:
– The U-Boot script that includes the root hash will be part of a FIT image, which itself is signed, and its signature is verified by U-Boot before the U-Boot script is executed.
– U-Boot will indeed not read a complete environment from storage, as the environment is not signed. Instead, U-Boot should only use a built-in, read-only environment that is part of the U-Boot image, as the U-Boot image will have been verified by an earlier boot stage (ROM code, or U-Boot SPL).
the hyperlink in SELinux integration section can’t be opened:
such as: http://lists.infradead.org/pipermail/openwrt-devel/2019-November/020261.html
The requested URL was not found on this server.
Thanks for the comment. It seems like infradead.org has re-generated the mailing list archives, and the links have changed. I have updated the blog post accordingly. I think I should also mention that the SELinux work has since then been integrated in OpenWrt, with further improvements/additions from the community.
Great job, Thanks.