Recently I added some changes to FreeBSD (src and ports) which allow enabling some compiler features when building FreeBSD or 3rd party programs. While they are documented in mitigations(7) and src.conf(5), the already existing ones (not implemented by me) do not seem to be known well, and information for the new ones should be shared as well.
Short: enable the following (some things may only work on FreeBSD 15 onward, like ZEROREGS) and you will get some additional security (may have a small performance impact). This should all work on 64bit x86. Support for other architectures depends on the corresponding support in the compiler.
- /etc/src-env.conf:
FORTIFY_SOURCE=2 - /etc/src.conf:
WITH_RETPOLINE=yes
WITH_KERNEL_RETPOLINE=yes
WITH_BIND_NOW=yes
OPT_INIT_ALL=zero
WITH_ZEROREGS=yes - /etc/make.conf (for ports):
WITH_PIE=yes
WITH_RELRO=yes
WITH_BIND_NOW=yes
WITH_FORTIFY=yes
WITH_STACK_AUTOINIT=yes
WITH_ZEROREGS=yes
The long explanation…
Various mitigations and security hardening options can be enabled at build time of FreeBSD and the 3rd party packages. For the basesystem and the kernel some of them are enabled in /etc/src-env.conf, and others in /etc/src.conf. For the 3rd party ports build they can be enabled in /etc/make.conf. All of this depends on the corresponding support in the compiler for a particular CPU architecture.
In 2017 a class of side-channel vulnerabilities was found in modern CPUs regarding their use of speculative execution and indirect branch prediction. Since then multiple ways of doing such a side-channel attack was found in various CPUs. Even in 2022 some variations of this were found. Some of the CPUs weren’t fixed, some could get a microcode update to have some hardware supported mitigation, and in some cases the software needs to do some things together with some new features from such microcode updates in CPUs. Things FreeBSD can detect and use at run-time, will automatically be handled. For those cases where an automatic mitigation possibility is not detectable, or costs too much performance, there is the possibility to tell the compiler to build the software with CPU instructions in smart places to mitigate this. This can be enabled by adding WITH_REPOLINE=yes and WITH_KERNEL_RETPOLINE=yes in /etc/src.conf. The kernel and the userland need to be recompiled afterwards, as this is a feature built into the compiler. Compared to software which is compiled without this, there may be a performance impact by enabling this. If this is really an impact for your use case, depends on what your software does. Even in 2022 the recommendation in the security community was to build with RETPOLINE in security sensitive areas, as still some new vulnerabilities were found for which this prevents side-channel attacks (and who knows if there are still some issues lurking around to be found).
Another area where the compiler can help is, by initializing memory regions (the compiler knows about) to zero to prevent information disclosure issues. For this we have OPT_INIT_ALL=zero (/etc/src.conf) which initializes trivial automatic variables (not some large buffers a programmer allocates with calls to malloc or similar). Again, this needs a rebuild of the userland and the kernel, and it may result in a small performance impact. For the FreeBSD ports collection I have committed the possibility to use the option WITH_STACK_AUTOINIT=yes in /etc/make.conf. Again, this requires a rebuild of the software, either locally, or in your own poudriere instance (and then you have to use a poudriere specific make.conf, see the docs of poudriere).
There is not only the possibility of information disclosure of memory regions, but also the possibility of information disclosure of CPU register contents. For this I have added WITH_ZEROREGS=yes (basesystem: /etc/src.conf, ports: /etc/make.conf) recently. This zeros caller-used register contents just before returning from a function, ensuring that temporary values are not leaked beyond the function boundary. This means that register contents are less likely to be available for side-channel attacks and information exposures. Additionally it reduces the amount of simple “write-what-where” ROP gadgets. In some cases it is possible that some software relies on some register contents which are cleared by this. For the about 1000 3rd party software I have installed as a package (build in my own poudriere instance with those options), I have not noticed any issues. In case you stumble upon such software, it is possible to disable this for particular ports with ZEROREGS_UNSAFE=yes (in the Makefile of the port).
Two features which go hand-in-hand are WITH_RELRO=yes and WITH_BIND_NOW=yes. Both of them are available for the userland (src.conf) and the FreeBSD ports (make.conf). For the FreeBSD userland RELRO is enabled by default, it applies the Relocation Read-Only (RELRO) vulnerability mitigation. For the ports collection it needs to be enabled in make.conf. When WITH_BIND_NOW is used, the binaries build with this option instruct the run-time loader to perform all relocation processing at process startup rather than on demand. The combination of the BIND_NOW and RELRO options provides “full” Relocation Read-Only (RELRO) support. With full RELRO the entire GOT is made read-only after performing relocation at startup, avoiding GOT overwrite attacks.
The FORTIFY_SOURCE=2 option enables additional bounds checking in various functions that accept buffers to be written into (e.g. some string functions). Again, this may have a performance impact depending on what the program does. For the ports collection I have created the feature WITH_FORTIFY=yes for make.conf. There may be ports which do not build with this option enabled. Possible failure causes are wrong/missing includes, and maybe other reasons. As of this writing I compile with WITHOUT_FORTIFY_PORTS=misc/mbuffer devel/libepoll-shim lang/gcc* devel/gperf devel/bsddialog textproc/jade math/mpfr security/rhash devel/libudev-devd net/norm devel/glib21 devel/py-libzfs math/mpfi devel/cmake-core devel/re2c net-p2p/libutp textproc/xmlstarlet textproc/groff net/samba419 www/qt6-webengine science/hdf5 graphics/netpbm in make.conf. When you read this, this may or may not still be needed (it’s a list that has grown naturally and hasn’t been re-validated recently).
In the ports collection there is also the feature WITH_PIE=yes (/etc/make.conf). The base system is already using PIE where possible and useful. This enables building position-independent executables. This is needed for ASLR to work for a given program. This will not work with every port, several ports are already marked as PIE_UNSAFE.
Already enabled by default is the WITH_SSP feature. It enables the stack protector feature of compilers. Recently I added the stack smash protection feature to the FreeBSD build. The ports collection does not have this yet.
For my use cases (jail hosts with mysql, postgresql, postfix, dovecot, nginx, php, various java/go/python based server applications, bind, unbound, …), I have not noticed an obvious performance impact by enabling all this (subjective assessment, no objective performance benchmark, I do not have speed-critical workloads to run). For your use cases you may want to check, and in case you notice some difference, you need to evaluate if the performance difference is worth the additional security you get with this.
