“The availability of a huge number of function pointers in the kernel assists the popularity of this attack pattern. Even if attackers cannot inject executable code of their own, arbitrary parts of existing kernel code can be executed to complete their exploit.
LLVM‘s CFI attempts to mitigate these attacks by restricting valid call targets and forcing a kernel panic when detecting a CFI violation. A check is added 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. An attacker will still be able to change a function pointer, if a bug allows access. But LLVM’s CFI limits 55% of indirect calls to at most 5 possible targets and 80% to at most 20 targets. In order to determine all valid call targets for each indirect branch, the compiler needs to see all of the kernel code at once.
The usage of LTO (Link Time Optimization) makes this possible. LLVM’s CFI requires the usage of LTO, where the compiler produces LLVM-specific bitcode for all C compilation units, and an LTO-aware linker uses the LLVM backend to combine the bitcode and compile it into native code.
Supplementary to permitting the usage of CFI, LTO achieves better runtime performance through whole-program analysis and cross-module optimization.
ThinLTO has nearly caught up to LTOs performance improvement. In ThinLTO mode, as with regular LTO, Clang emits LLVM bitcode after the compile phase. The ThinLTO bitcode is augmented with a compact summary of the module. During the link step, only the summaries are read and merged into a combined summary index, which includes an index of function locations for later cross-module function importing. Afterwards fast and efficient whole-program analysis is performed on the combined summary index. ThinLTO allows a multi-threaded linking process, which results in reduced compilation time.
Due to CFI interrupting program execution when hitting certain bug classes, it also classifies as a bug finding tool, as previously mentioned, when used in permissive mode. Permissive CFI will show CFI violations in the kernel log, without forcing a kernel panic. The core 4.9 (Pixel 3 generation devices) and 4.14 (Pixel 4 generation devices) kernels had several function type mismatches resulting in CFI violations, which were addressed by Google in patchsets available on the kernel/common repos.
However, due to the nature of the Android ecosystem, these mismatches are likely to be found in SoC manufacturer (in this case, Qualcomm) or OEM (OnePlus) specific code as well. Several CFI violations in Qualcomm-code distinct to the 4.19 kernel were fixed on the Kirisakura kernel for the OnePlus 8 Pro (example: 1, 2, 3).
Running the kernel in permissive CFI revealed CFI violations in code related to OnePlus drivers as well (relevant commits can be found here and here). Kirisakura kernel for the OnePlus 8 Pro runs with CFI enforced, protecting its users against this kind of code reuse attacks.”