Would you like to make your Yocto image a tiny bit harder to hack ‘n’ crack? Of course you would. This time we’re going to be doing two things to improve its security: hardening the Linux kernel, and setting the hardening flags for GCC. The motivation for these is quite obvious. Kernel is the privileged core of the system, so it better be as hardened as possible. GCC compilation flags on the other hand affect almost every C and C++ binary and library that gets compiled into the system. As you may know, over the years we’ve gotten quite a few of them, so it’s a good idea to use any help the compiler can provide with hardening them.
Kernel Configuration Hardening
Linux kernel is the heart of the operating system and environment. As one can guess, configuring the kernel incorrectly or enabling everything in the kernel “just in case” will in the best situation lead to suboptimal performance and/or size, and in the worst case, it’ll provide unnecessary attack surfaces. However, optimizing the configuration manually for size, speed, or safety is a massive undertaking. According to Linux from Scratch, there are almost 12,000 configuration switches in the kernel, so going through all of them isn’t really an option.
Fortunately, there are automatic kernel configuration checkers that can help guide the way. Alexander Popov’s Kernel Hardening Checker is one such tool, focusing on the safety of the kernel. It combines a few different security recommendations into one checker. The project’s README contains the list of recommendations it uses as the guideline for a safe configuration. The README also contains plenty of other useful information, like how to run the checker. Who would have guessed! For the sake of example, let’s go through the usage here as well.
Obtaining and Analyzing Kernel Hardening Information
kernel-hardening-checker doesn’t actually only check the kernel configuration that defines the build time hardening, but it also checks the command line and sysctl parameters for boot-time and runtime hardening as well. Here’s how you can obtain the info for each of the checks:
- Kernel configuration: in Yocto, you can usually find this from
- Command line parameters: run
cat /proc/cmdlineon the system to print the command line parameters
- Sysctl parameters: run
sysctl -aon the system to print the sysctl information
Once you’ve collected all the information you want to check, you can install and run the tool in a Python virtual environment like this:
python3 -m venv venv
pip install git+https://github.com/a13xp0p0v/kernel-hardening-checker
kernel-hardening-checker -c <path-to-config> -l <path-to-cmdline> -s <path-to-sysctl>
Note that you don’t have to perform all the checks if you don’t want to. The command will print out the report, most likely recommending plenty of fixes. Green text is better than red text. Note that not all of the recommendations necessarily apply to your system. However, at least disabling the unused features is usually a good idea because it reduces the attack surface and (possibly) optimizes the kernel.
To generate the config fragment that contains the recommended configuration, you can use the
-g flag without the input files. As the README states, the configuration flags may have performance and/or size impacts on the kernel. This is listed as recommended reading about the performance impact.
Whether you like it or not, GCC is the default compiler in Yocto builds. Well, there exists meta-clang for building with clang, and as far as I know, the support is already in quite good shape, but that’s beside the point. Yocto has had hardening flags for GCC compilation for quite some time. To check these flags, you can run the following command:
bitbake <image-name> -e | grep ^SECURITY_CFLAGS=
How the security flags get applied to the actual build flags may vary between Yocto versions. In Kirkstone,
SECURITY_CFLAGS gets added to
TARGET_CC_ARCH variable, which gets set to
HOST_CC_ARCH, which finally gets added to
HOST_CC_ARCH gets also added to
CPP commands, so
SECURITY_CFLAGS apply also to C++ programs.
bitbake -e is your friend when trying to figure out what gets set and where.
So, in addition to checking the
SECURITY_CFLAGS, you most likely want to check the
CC variable as well to see that the flags actually get added to the command that gets run:
# Note that the CC variable gets exported so grep is slightly different
bitbake <image-name> -e |grep "^export CC="
The flags are defined in security_flags.inc file in Poky (link goes to Kirkstone version of the file). It also shows how to make package-specific exceptions with
pn-<package-name> override. The
PIE (position-independent executables) flags are perhaps worth mentioning as they’re a bit special. The compiler is built to create position-independent executables by default (seen in
GCCPIE variable), so
PIE flags are empty and not part of the
SECURITY_CFLAGS. Only if
PIE flags are not wanted, they are explicitly disabled.
Extra Flags for GCC
Are the flags defined in
security_flags.inc any good? Yes, they are, but they can also be expanded a bit. GCC will most likely get in early 2024 new
-fhardened flag that sets some options not present in Yocto’s security flags:
-D_FORTIFY_SOURCE=3 (or =2 for older glibcs)
-fPIE -pie -Wl,-z,relro,-z,now
-fcf-protection=full (x86 GNU/Linux only)
Lines 2, 3, and 6 are not present in the Yocto flags. Those could be added using a
SECURITY_CFLAGS:append in a suitable place if so desired. I had some trouble with the
trivial-auto-var-init flag though, seems like it is introduced in GCC version 12.1 while Yocto Kirkstone is still using 11 series. Most of the aforementioned flags are explained quite well in this Red Hat article. Considering there’s plenty of overlap with the
-fhardened, it may be that in future versions of Poky the security flags will contain just
-fhardened (assuming that the flag actually gets implemented).
All in all, assuming you have a fairly modern version of Yocto, this GCC hardening chapter consists mostly of just checking that you have the
SECURITY_CFLAGS present in
CC variable and adding a few flags. Note once again that using the hardening flags has its own performance hit, so if you are writing something really time- or resource-critical you need to find a suitable balance with the hardening and optimization levels.
While this was quite a short text, and the GCC hardening chapter mostly consisted of two
grep commands and silly memes, the Linux kernel hardening work is something that actually takes a long time to complete and verify. At least my simple check for
core-image-minimal with systemd enabled resulted in 136 recommendation fails and 110 passes. Fixing it would most likely take quite a bit longer than writing this text. Perhaps not all of the issues need to be fixed but deciding what’s an actual issue and what isn’t takes its own time as well. So good luck with that, and until next time!
As a reminder, if you liked this text and/or found it useful, you can find the whole Yocto hardening series from here.
The Movember blog series continues with this text! As usual, after reading this text I ask you to do something good. Good is a bit subjective, so you most likely know what’s something good that you can do. I’m going to eat a hamburger, change ventilation filters, and donate a bit to charity.