Introducing CFI in HardenedBSD

Control Flow Integrity, or CFI, is an exploit mitigation technique that helps prevent attackers from modifying the behavior of a program and jumping to undefined or arbitrary memory locations. Microsoft has implemented a variant of CFI, which they term Control Flow Guard, or CFG. The PaX team has spent the last few years perfecting their Reuse Attack Protector, RAP. CFI, CFG, and RAP all attempt to accomplish the same goal, with RAP being the most complete and effective implementation. Clang's CFI is stronger than Microsoft's CFG and PaX Team's RAP is stronger than both CFI and CFG. RAP would be a great addition to HardenedBSD; however, it requires a GPLv3 toolchain and is patented.

HardenedBSD is excited to announce the integration of Clang's CFI into base. CFI is enabled by default in HardenedBSD 12-CURRENT on amd64 and can be disabled by setting WITHOUT_CFI in src.conf. CFI is not applicable to architectures other than amd64, though Shawn is working on porting SafeStack to arm64.

Clang's CFI requires a linker that supports Link-Time Optimization (LTO). On 02 March 2017, version 4.0.0 of the lld linker from the llvm project was imported into both FreeBSD and HardenedBSD. lld 4.0.0 is the first version of lld that is usable in base and provides HardenedBSD with a linker that supports LTO. We have been working hard over the past few months in developing and testing the integration of Clang's CFI in HardenedBSD's base. All CFI schemes have been enabled for all of base in HardenedBSD 12-CURRENT/amd64, with the exception of the cfi-icall scheme for a handful of applications. It is possible that we may need to disable the cfi-icall scheme for more applications and we'll need to rely on our user base to identify edge cases. Any application that calls function pointers resolved via dlopen+dlsym will require the cfi-icall scheme to be disabled.

At this time, we have not applied CFI to shared libraries (aka, cross-DSO CFI). We are working on cross-DSO CFI support in base, though a few core modifications will need to be made. Upon initial investigation, we need to make llvm-ar and llvm-nm the default ar and nm and we need to build the libclang_rt.cfi static library. Once we gain that support, we should be able to enable cfi-icall across the board. Just as with SafeStack, cross-DSO CFI requires both ASLR and W^X in order to be effective. If an attacker knows the memory layout of an application, the attacker might be able to craft a data-only attack, modifying the CFI control data.

As of this writing, the following applications have cfi-icall disabled:

  1. /sbin/md5
  2. /usr/bin/less
  3. /usr/bin/mail
  4. /usr/bin/top
  5. /usr/bin/tsort
  6. /usr/bin/vi
  7. /usr/sbin/bhyveload
  8. /usr/sbin/pwd_mkdb
  9. /usr/sbin/sendmail
  10. /usr/sbin/services