|
|
Log in / Subscribe / Register

Kernel control-flow-integrity support comes to GCC

By Daroc Alden
February 6, 2026

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.



to post comments

banishing pointers in static domains

Posted Feb 7, 2026 0:09 UTC (Sat) by Fowl (subscriber, #65667) [Link] (3 responses)

I’m wondering if an index+switch scheme would have a prohibitive performance impact. Would avoid the problem of having to suppress optimizations and in fact they’d likely be welcomed - devirtualization in the kernel wow!

I’m not sure how to resolve the “C files are compiled separately” thing - *waves hand* linker magic? More hand waving for loadable modules.

Perhaps there would be compensating memory savings - eg. use a u8 instead of a pointer for each function pointer member, modulo alignment/atomicity. 2^8 possible functions per signature ought to be enough for anyone ey?

banishing pointers in static domains

Posted Feb 7, 2026 0:23 UTC (Sat) by Fowl (subscriber, #65667) [Link] (2 responses)

Ahh reading the documentation (super power), it’s hinted that jump tables can be generated for the non-kernel CFI, but with static linking limitations.

> -fsanitize=kcfi
> This is an alternative indirect call control-flow integrity scheme designed for low-level system software, such as operating system kernels. Unlike -fsanitize=cfi-icall, it doesn’t require -flto, won’t result in function pointers being replaced with jump table references, and never breaks cross-DSO function address equality. These properties make KCFI easier to adopt in low-level software. KCFI is limited to checking only function pointers, and isn’t compatible with executable-only memory.

https://clang.llvm.org/docs/ControlFlowIntegrity.html?utm...

I guess being willing to make deeper changes to the code rather than sticking mostly inside the compiler could overcome that.

banishing pointers in static domains

Posted Feb 7, 2026 16:39 UTC (Sat) by nivedita76 (subscriber, #121790) [Link] (1 responses)

The first couple of links are earlier lwn articles that describe the jump table implementation and some of its limitations.

banishing pointers in static domains

Posted Feb 7, 2026 16:42 UTC (Sat) by nivedita76 (subscriber, #121790) [Link]

https://lwn.net/Articles/856514/

Actually this is probably the one that has most detail.

Software CFI

Posted Feb 7, 2026 11:02 UTC (Sat) by mss (subscriber, #138799) [Link]

This is a pure software implementation of forward-edge CFI, which does not require things like IBT, correct?

Are there any benchmarks available?

Not really like CHERI at all

Posted Feb 19, 2026 13:41 UTC (Thu) by Vorpal (guest, #136011) [Link] (2 responses)

> CHERI-like hardware-enforced capabilities can be used to completely block indirect-jump-based attacks.

CHERI is unrelated to indirect jumping or control flow in general. CHERI is about protecting about out of bounds accesses by adding metadata to pointers to indicate what memory range they are valid for.

A clarification to the article is probably warranted.

Not really like CHERI at all

Posted Feb 19, 2026 13:59 UTC (Thu) by daroc (editor, #160859) [Link] (1 responses)

CHERI supports using sealed function pointers that encode type information about the function being jumped to:

https://cheriot.org/isa/ibex/2024/06/26/sentries-cfi.html
https://cheriot.org/rtos/sealing/2025/11/06/sealing.html

So saying that CHERI is unrelated to control flow is incorrect. It might help to know that the accesses through a pointer that CHERI metadata controls includes whether the resulting address can be executed or not. And capability sealing, which was originally intended to allow passing opaque handles to untrusted code, combines well with that ability.

Not really like CHERI at all

Posted Feb 28, 2026 7:12 UTC (Sat) by Vorpal (guest, #136011) [Link]

Interesting! I wasn't aware it had that feature.

"Itanium C++ ABI" is a bit of a misnomer

Posted Feb 20, 2026 15:57 UTC (Fri) by zwol (guest, #126152) [Link]

The "Itanium C++ ABI" doesn't actually have anything to do with the Itanium architecture. It's called that because GCC's Itanium back end was the first GCC back end to implement it, and it was written in collaboration with HP and Intel, with the goal that all C++ compilers for Itanium would be binary compatible. But it is a mostly architecture-independent description of how C++ features that need to be represented at the ABI level — name mangling, vtable layout, etc. — should be handled. Nowadays I believe GCC and LLVM both use it uniformly on all platforms except Windows (where there is an attempt to be MSVC-compatible instead).


Copyright © 2026, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds