Posted by Sami Tolvanen, Employees Software program Engineer, Android Safety
Android’s safety mannequin is enforced by the Linux kernel, which makes it a tempting goal for attackers. We’ve put quite a lot 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 towards code reuse assaults.
Google’s Pixel three would be the first Android system to ship with LLVM’s forward-edge Control Flow Integrity (CFI) enforcement within the kernel, and we now have made CFI support available in Android kernel versions 4.9 and 4.14. This publish describes how kernel CFI works and gives options to the most typical points builders would possibly run into when enabling the characteristic.
Defending towards code reuse assaults
A standard 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 deal with that had been pushed to the stack. This enables an attacker to execute arbitrary elements of the kernel code to finish their exploit, even when they can’t inject executable code of their very own. This technique of gaining code execution is especially fashionable with the kernel due to the massive variety of operate pointers it makes use of, and the prevailing reminiscence protections that make code injection tougher.
CFI makes an attempt to mitigate these assaults by including further checks to substantiate that the kernel’s management circulation stays inside a precomputed graph. This does not stop 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 harder in apply.
Determine 1. In an Android system 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)
With a purpose 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 depart merging the article recordsdata to the linker. LLVM’s answer 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 mission will result in compatibility points, which we now 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 consequence of world optimizations. Nevertheless, further optimizations usually end in a bigger binary dimension, which can be undesirable on units with very restricted assets. Disabling LTO-specific optimizations, comparable to world inlining and loop unrolling, can scale back binary dimension by sacrificing a few of the efficiency positive aspects. 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
Observe that flags to disable particular person optimizations aren’t a part of the secure LLVM interface and will change in future compiler variations.
Implementing CFI within the Linux kernel
LLVM’s CFI implementation provides a examine earlier than every oblique department to substantiate that the goal deal with 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 referred to as. As C compilers don’t implement comparable restrictions on oblique branches, there have been a number of CFI violations as a consequence of operate sort declaration mismatches even within the core kernel that we now 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. With a purpose to help loadable modules, we now have carried out LLVM’s cross-DSO CFI help within the kernel, together with a CFI shadow that accelerates cross-module look-ups. When compiled with cross-DSO help, every kernel module comprises details about legitimate native department targets, and the kernel appears to be like up info from the right module based mostly on the goal deal with and the modules’ reminiscence structure.
Determine three. An instance of a cross-DSO CFI examine injected into an arm64 kernel. Sort info is handed in X0 and the goal deal with to validate in X1.
CFI checks naturally add some overhead to oblique branches, however as a consequence of extra aggressive optimizations, our exams present that the impression is minimal, and general system efficiency even improved 1-2% in lots of circumstances.
Enabling kernel CFI for an Android system
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 obtainable 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 may show useful when debugging a CFI violation or throughout system bring-up. This selection turns a violation right into a warning as a substitute of a kernel panic.
As talked about within the earlier part, the most typical subject 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 comprises the decision stack on the time of the failure, and the decision goal that failed the CFI examine. Altering the code to make use of an accurate operate pointer sort fixes the difficulty. Whereas we now have mounted all identified oblique department sort mismatches within the Android kernel, comparable issues could also be nonetheless present in system 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 deal with house conflicts, however this ought to be much less widespread 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 deal with will end in a CFI violation. A lot of these failures might be addressed by disabling CFI for a single operate utilizing the __nocfi attribute, and even disabling CFI for whole code recordsdata 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 deal with 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 harder to debug, however reminiscence debugging instruments comparable to KASAN can assist right here.
We’ve carried out help for LLVM’s CFI in Android kernels four.9 and four.14. Google’s Pixel three would be the first Android system to ship with these protections, and we now have made the characteristic accessible to all system distributors by the Android widespread kernel. In case you are delivery a brand new arm64 system operating Android 9, we strongly advocate enabling kernel CFI to assist shield towards kernel vulnerabilities.
LLVM’s CFI protects oblique branches towards attackers who handle to achieve entry to a operate pointer saved in kernel reminiscence. This makes a standard technique of exploiting the kernel harder. Our future work includes additionally defending operate return addresses from comparable assaults utilizing LLVM’s Shadow Call Stack, which will probably be accessible in an upcoming compiler launch.