Posted by Sami Tolvanen, Workers Software program Engineer, Android Safety
Android’s safety mannequin is enforced by the Linux kernel, which makes it a tempting goal for attackers. We have now put loads of effort into hardening the kernel in earlier Android releases and in Android 9, we continued this work by specializing in compiler-based security mitigations in opposition to code reuse assaults.
Google’s Pixel three would be the first Android machine to ship with LLVM’s forward-edge Control Flow Integrity (CFI) enforcement within the kernel, and now we have made CFI support available in Android kernel versions 4.9 and 4.14. This put up describes how kernel CFI works and gives options to the most typical points builders may run into when enabling the function.
Defending in opposition to code reuse assaults
A typical technique of exploiting the kernel is utilizing a bug to overwrite a operate pointer saved in reminiscence, comparable to a saved callback pointer or a return tackle that had been pushed to the stack. This permits an attacker to execute arbitrary elements of the kernel code to finish their exploit, even when they can not inject executable code of their very own. This technique of gaining code execution is especially widespread with the kernel due to the large variety of operate pointers it makes use of, and the prevailing reminiscence protections that make code injection more difficult.
CFI makes an attempt to mitigate these assaults by including further checks to verify that the kernel’s management circulation stays inside a precomputed graph. This does not forestall an attacker from altering a operate pointer if a bug gives write entry to 1, however it considerably restricts the legitimate name targets, which makes exploiting such a bug tougher in follow.
Determine 1. In an Android machine kernel, LLVM’s CFI limits 55% of oblique calls to at most 5 potential targets and 80% to at most 20 targets.
Gaining full program visibility with Hyperlink Time Optimization (LTO)
So as to decide all legitimate name targets for every oblique department, the compiler must see all the kernel code without delay. Historically, compilers work on a single compilation unit (supply file) at a time and go away merging the article information to the linker. LLVM’s resolution to CFI is to require using LTO, the place the compiler produces LLVM-specific bitcode for all C compilation models, and an LTO-aware linker makes use of the LLVM back-end to mix the bitcode and compile it into native code.
Determine 2. A simplified overview of how LTO works within the kernel. All LLVM bitcode is mixed, optimized, and generated into native code at hyperlink time.
Linux has used the GNU toolchain for assembling, compiling, and linking the kernel for many years. Whereas we proceed to make use of the GNU assembler for stand-alone meeting code, LTO requires us to modify to LLVM’s built-in assembler for inline meeting, and both GNU gold or LLVM’s personal lld because the linker. Switching to a comparatively untested toolchain on an enormous software program challenge will result in compatibility points, which now we have addressed in our arm64 LTO patch units for kernel variations 4.9 and 4.14.
Along with making CFI potential, LTO additionally produces sooner code as a result of international optimizations. Nonetheless, further optimizations typically end in a bigger binary measurement, which can be undesirable on gadgets with very restricted assets. Disabling LTO-specific optimizations, comparable to international inlining and loop unrolling, can cut back binary measurement by sacrificing a few of the efficiency beneficial properties. When utilizing GNU gold, the aforementioned optimizations might be disabled with the next additions to LDFLAGS:
LDFLAGS += -plugin-opt=-inline-threshold=zero -plugin-opt=-unroll-threshold=zero
Be aware that flags to disable particular person optimizations usually are not a part of the steady LLVM interface and should change in future compiler variations.
Implementing CFI within the Linux kernel
LLVM’s CFI implementation provides a verify earlier than every oblique department to verify that the goal tackle factors to a sound operate with an accurate signature. This prevents an oblique department from leaping to an arbitrary code location and even limits the features that may be known as. As C compilers don’t implement related restrictions on oblique branches, there have been a number of CFI violations as a result of operate sort declaration mismatches even within the core kernel that now we have addressed in our CFI patch units for kernels 4.9 and 4.14.
Kernel modules add one other complication to CFI, as they’re loaded at runtime and might be compiled independently from the remainder of the kernel. So as to assist loadable modules, now we have carried out LLVM’s cross-DSO CFI assist within the kernel, together with a CFI shadow that quickens cross-module look-ups. When compiled with cross-DSO assist, every kernel module accommodates details about legitimate native department targets, and the kernel seems up info from the proper module primarily based on the goal tackle and the modules’ reminiscence structure.
Determine three. An instance of a cross-DSO CFI verify injected into an arm64 kernel. Sort info is handed in X0 and the goal tackle to validate in X1.
CFI checks naturally add some overhead to oblique branches, however as a result of extra aggressive optimizations, our checks present that the influence is minimal, and general system efficiency even improved 1-2% in lots of circumstances.
Enabling kernel CFI for an Android machine
CFI for arm64 requires clang model >= 5.zero and binutils >= 2.27. The kernel construct system additionally assumes that the LLVMgold.so plug-in is out there in LD_LIBRARY_PATH. Pre-built toolchain binaries for clang and binutils can be found in AOSP, however upstream binaries will also be used.
The next kernel configuration choices are wanted to allow kernel CFI:
Utilizing CONFIG_CFI_PERMISSIVE=y can also show useful when debugging a CFI violation or throughout machine bring-up. This selection turns a violation right into a warning as an alternative of a kernel panic.
As talked about within the earlier part, the most typical challenge we bumped into when enabling CFI on Pixel three had been benign violations attributable to operate pointer sort mismatches. When the kernel runs into such a violation, it prints out a runtime warning that accommodates the decision stack on the time of the failure, and the decision goal that failed the CFI verify. Altering the code to make use of an accurate operate pointer sort fixes the difficulty. Whereas now we have fastened all identified oblique department sort mismatches within the Android kernel, related issues could also be nonetheless present in machine particular drivers, for instance.
CFI failure (goal: [<fffffff3e83d4d80>] my_target_function+0x0/0xd80): ------------[ cut here ]------------ kernel BUG at kernel/cfi.c:32! Inside error: Oops - BUG: zero [#1] PREEMPT SMP … Name hint: … [<ffffff8752d00084>] handle_cfi_failure+0x20/0x28 [<ffffff8752d00268>] my_buggy_function+0x0/0x10 …
Determine four. An instance of a kernel panic attributable to a CFI failure.
One other potential pitfall are tackle house conflicts, however this must be much less frequent in driver code. LLVM’s CFI checks solely perceive kernel digital addresses and any code that runs at one other exception stage or makes an oblique name to a bodily tackle will end in a CFI violation. A majority of these failures might be addressed by disabling CFI for a single operate utilizing the __nocfi attribute, and even disabling CFI for whole code information utilizing the $(DISABLE_CFI) compiler flag within the Makefile.
static int __nocfi address_space_conflict()
Determine 5. An instance of fixing a CFI failure attributable to an tackle house battle.
Lastly, like many hardening options, CFI will also be tripped by reminiscence corruption errors which may in any other case end in random kernel crashes at a later time. These could also be tougher to debug, however reminiscence debugging instruments comparable to KASAN may help right here.
We have now carried out assist for LLVM’s CFI in Android kernels four.9 and four.14. Google’s Pixel three would be the first Android machine to ship with these protections, and now we have made the function accessible to all machine distributors by the Android frequent kernel. In case you are delivery a brand new arm64 machine operating Android 9, we strongly advocate enabling kernel CFI to assist shield in opposition to kernel vulnerabilities.
LLVM’s CFI protects oblique branches in opposition to attackers who handle to realize entry to a operate pointer saved in kernel reminiscence. This makes a typical technique of exploiting the kernel tougher. Our future work includes additionally defending operate return addresses from related assaults utilizing LLVM’s Shadow Call Stack, which can be accessible in an upcoming compiler launch.