In our internet connected world, which relies on a growing volume of software – it’s crucial that new products are created with security in mind. Yet much of the software we create or depend on is written with memory-unsafe languages such as C and C++ – It’s a worrying fact given that 70% of all reported vulnerabilities (CVEs) relate to memory safety issues. Of course, it’s possible to reduce the risk by migrating to memory-safe languages (e.g. Rust) or by adopting secure coding practices. In this blog post we’re going to look at a Yocto feature that mitigates some of risk by taking advantage of compiler features.
Modern compilers such as GCC and Clang offer a wide range of compiler flags (usually disabled by default) that can can make it significantly harder for an attacker to exploit certain types of vulnerabilities – here are some (GCC) examples:
- -Wformat Adds compile time checks to detect issues relating to format string arguments in common library functions such as printf, scanf, strftime, etc.
- -D_FORTIFY_SOURCE Adds compile and run time checks to detect buffer overflows in memory and string functions.
- -fstack-protector Adds runtime checks to detect buffer overflows and stack smashing
- -fpie Enable position independent code which allows for loading the binary at randomised locations thus making certain types of attacks (ROP) more difficult
- –Wl,-z,relro,-z,now Makes it much harder to abuse a binarie’s GOT table
Yocto makes it really easy to enable all these flags (and more) by default on all the packages it builds, all you need to do is add the following in your machine configuration or local.conf:
It’s a relatively simple ‘low-hanging-fruit’ change that can make some attacks a little more difficult. However adding these flags may result in additional warnings and errors that can break a build (perhaps this is why it’s disabled by default) – so some work may be required. Fortunately, Yocto provides a way to disable certain compiler flags for troublesome packages – take a look at the security_flags.inc file to see how this is achieved.
Finally, it’s possible, and often helpful to verify that binaries in a filesystem have had common compiler exploit mitigation features applied – one such tool is checksec, let’s try that:
$ ./checksec --file=./rootfs/bin/bash RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH No Symbols Yes 14 31 ./rootfs/bin/bash
As you can see, for a given binary, it will indicate which features have been enabled.
As this blog post shows, attention to matters beyond the code – such as tooling and configuration – can make a difference to security – this is why creating secure products requires that security is considered in all aspects of the software development lifecycle.