Google this week revealed that Android’s kernel is becoming more resilient to code reuse attacks, courtesy of implemented support for LLVM’s Control Flow Integrity (CFI).
CFI support, Google says, was added to Android kernel versions 4.9 and 4.14 and the feature is available to all device vendors. However, Google Pixel 3, which was launched earlier this week, is the first device to take advantage of the new security mitigations.
One of the manners in which attackers achieve code execution even without injecting executable code of their own, Google reveals, is by abusing kernel bugs to overwrite a function pointer stored in memory. The method is popular with the kernel given the large number of function pointers the latter uses and the protections that make code injection difficult.
CFI, however, was designed to mitigate these attacks through additional checks applied to the kernel’s control flow. While this still allows an attacker to change a function pointer if a bug provides write access to one, it significantly restricts the valid call targets, thus making exploitation more difficult.
LLVM’s solution to CFI also requires the use of Link Time Optimization (LTO), which also requires the adoption of LLVM’s integrated assembler for inline assembly. The GNU toolchain, which Linux kernel relies on for assembling, compiling, and linking the kernel, will continue to be used for stand-alone assembly code.
“LLVM’s CFI implementation adds a check before each indirect branch to confirm that the target address points to a valid function with a correct signature. This prevents an indirect branch from jumping to an arbitrary code location and even limits the functions that can be called,” Google explains.
Kernel modules, which are loaded at runtime and can be compiled independently from the rest of the kernel, add another complication to CFI and Google implemented LLVM’s cross-DSO CFI support in the kernel, to ensure kernel modules are supported.
“When compiled with cross-DSO support, each kernel module contains information about valid local branch targets, and the kernel looks up information from the correct module based on the target address and the modules’ memory layout,” Google explains.
The CFI checks add overhead to indirect branches, but aggressive optimizations result in the overall system performance getting improved even 1-2% in many cases.
CFI for arm64, Google notes, requires clang version 5.0 and higher, as well as binutils 2.27 and higher. The LLVMgold.so plug-in should also be available in LD_LIBRARY_PATH. Google has already added pre-built toolchain binaries for clang and binutils in AOSP, but says that upstream binaries can also be used.
The use of CFI comes with its own pitfalls, such as violations caused by function pointer type mismatches, which Google has encountered plenty. Address space conflicts could also arise, and CFI can also be tripped by memory corruption errors that would normally result in random kernel crashes.
“If you are shipping a new arm64 device running Android 9, we strongly recommend enabling kernel CFI to help protect against kernel vulnerabilities. LLVM’s CFI protects indirect branches against attackers who manage to gain access to a function pointer stored in kernel memory. This makes a common method of exploiting the kernel more difficult,” Google says.
The tech giant also plans on protecting function return addresses from similar attacks with the help of LLVM’s Shadow Call Stack. This change, however, will be available in an upcoming compiler release.