Kernel control-flow-integrity support comes to GCC
Control-flow integrity (CFI) is a set of techniques that make it more difficult for attackers to hijack indirect jumps to exploit a system. The Linux kernel has supported forward-edge CFI (which protects indirect function calls) since 2020, with the most recent implementation of the feature introduced in 2022. That version avoids the overhead introduced by the earlier approach by using a compiler flag (-fsanitize=kcfi) that is present in Clang but not in GCC. Now, Kees Cook has a patch set adding that support to GCC that looks likely to land in GCC 17.
CFI has a tricky problem to solve: a program should only make indirect function calls that the developer intends to make. If there were no bugs in the program, this would be straightforward — the function pointers involved would always be correct, and there would be nothing to worry about. The kernel is not free of bugs, however, and there is always the possibility that an attacker will manage to overwrite a function pointer with some value they control. How can the compiler protect against incorrect function calls when the function pointers involved are potentially compromised?
The approach that the kernel's forward-edge CFI takes is to split functions up according to their type signature. If some piece of code is expecting to call a function that takes a long, but the function pointer actually takes a char *, for example, that's a sign that something has gone off the rails. By recognizing that kind of type confusion and panicking the kernel, forward-edge CFI can prevent some attacks. This isn't a perfect solution: an attacker could still redirect an indirect function call as long as the function signatures match. It's still more protection than the alternative, though.
This kind of mitigation is typical of the Linux kernel self-protection project, on behalf of which Cook did this work. The project is based on the reality that many bugs in the kernel have a long lifetime. LWN recently did an in-depth breakdown of bug lifetime for the 6.17 kernel (showing, among other things, that it usually takes several years to find the majority of bugs known to exist in a given kernel version). LWN subscribers can see the details for any kernel version in the LWN kernel source database. Given that bugs have a way of sticking around, the kernel self-protection project supports work on techniques to keep the kernel secure even in the presence of those bugs. The project tracks work-in-progress at its GitHub issue tracker.
Clang implements -fsanitize=kcfi by computing a hash of the function's type, and storing it just before the function implementation in memory. When a call site in the code makes an indirect call, it first verifies that the hash matches what it expects. The plan is for GCC to implement the feature in the same way, but unfortunately adding support isn't quite as simple as it may seem. The patch set has gone through nine versions since the initial version in August 2025.
One complication is in the handling of type aliases: these are frequently used for readability in C code, and it would break existing kernel code to generate a different hash for two type aliases that refer to the same underlying type. Therefore, most type aliases are resolved during the hashing process and the underlying type is used instead. There is one case where type aliases are usually meaningful in C code: as names for otherwise-anonymous structures, unions, and enumeration types. If two anonymous unions happen to have the same fields, but they are named differently, it is almost always the case that the programmer would like to ensure that one is not confused for the other. Here, the compiler uses the type alias name as part of the hash.
// This is treated the same as a plain int:
typedef int port_number;
// But these two types are considered different:
typedef struct {
void *data;
} foo;
typedef struct {
void *data;
} bar;
The other complication comes from a desire for compatibility with LLVM. In the past, a kernel build was theoretically performed entirely with GCC or with Clang. In practice, Cook wants to support CFI for kernels where the C code is built with GCC while the Rust code is built with rustc (which uses LLVM for code generation). Eventually, once GCC's Rust front end is working, this may not be necessary. But for now, C code that makes an indirect function call to Rust code or vice versa needs to compute the same type hash as the other language so that the values will match at run time. Clang's type hashes are based on the function-name mangling required by the Itanium C++ ABI; Cook implemented the same algorithm for GCC. Sami Tolvanen does not seem to have recorded why the Itanium ABI was chosen for LLVM's original implementation, but it may be because the Itanium ABI is the only ABI standard for POSIX systems that actually specifies name-mangling rules, rather than leaving them to the compiler.
The relevant hashes need to be calculated at every call site, but they also need to be calculated for every function that could be called indirectly. Since C files are compiled separately, there's no way to know whether a function that has its address taken is actually called or not. To be safe, GCC calculates and embeds hashes for every function that has its address taken, plus every function that is directly visible to other translation units.
The actual checks that the type hashes match are implemented late in the compiler pipeline, to ensure that they are not optimized out or otherwise broken up. Therefore, there's some architecture-specific code to implement the checks. Cook's patch set includes support for x86_64, 32- and 64-bit Arm, and RISC-V.
While there was a good deal of discussion on earlier versions, the current version has not attracted much commentary from GCC developers. Jeffrey Law believes the patch set is ready to go into GCC 17 as soon as the last GCC 16 release is made. Even though support hasn't quite landed in GCC, the kernel has already renamed the relevant configuration option from CONFIG_CFI_CLANG to CONFIG_CFI in version 6.18. The configuration option uses feature detection, so older stable kernels will support the option with GCC 17 despite the name.
There is still more work to be done, however. For example, there was a discussion in 2022 about the possibility of adding a programmer-specified per-function seed to the CFI hash. That would let programmers manually partition sets of functions with identical signatures into different groups with different CFI hashes. Bill Wendling and Aaron Ballman added support for that to LLVM in August 2025, although the kernel doesn't yet make use of it.
Advances in kernel security don't come all at once. CHERI-like hardware-enforced capabilities can be used to completely block indirect-jump-based attacks. Modern CPUs are increasingly adding hardware support for CFI operations. For older devices, software fixes like Clang's CFI support help make up the gap. Now, GCC will bring that same CFI scheme to the people who choose to use GCC for reasons of compatibility or ideology. Eventually, perhaps, this kind of mitigation will simply be the default.
