On Thu, 21 Aug 2025, Kees Cook wrote: > On Thu, Aug 21, 2025 at 01:01:37PM +0200, Richard Biener wrote: > > On Thu, 21 Aug 2025, Peter Zijlstra wrote: > > > > > On Thu, Aug 21, 2025 at 01:16:56AM -0700, Andrew Pinski wrote: > > > > > > > > +/* Compute KCFI type ID for a function declaration or function type > > > > > (internal) */ > > > > > +static uint32_t > > > > > +compute_kcfi_type_id (tree fntype_or_fndecl) > > > > > +{ > > > > > + if (!fntype_or_fndecl) > > > > > + return 0; > > > > > + > > > > > + const char *canonical_name = mangle_function_type > > > > > (fntype_or_fndecl); > > > > > + uint32_t base_type_id = kcfi_hash_string (canonical_name); > > > > > > > > > > > > > Now I am curious why this needs to be a mangled function name? Since the > > > > function in C the symbol is just its name. > > > > Is there documentation that says the hash needs to be based on all of > > > > the > > > > function arguments types? > > > > > > The whole point of kCFI is to limit the targets of indirect calls to > > > functions of the same signature. The actual function name is immaterial. > > > > What's the attack vector and how does kCFI achieve mitigating it? > > To add some more detail to Peter's answer, the attack vector is dealing > with stored function pointers (generally on the heap, but potentially > also the stack), that can be changed by attacker-controlled writes (via > buffer overflows or use-after-free writes). The idea being that instead > of being able to call anywhere through an indirect call site that uses a > manipulated pointer, now the attacker is limited to a subset of "matching" > destinations. (KCFI depends on a system already enforcing W^X memory in > the sense that if an attacker can construct an executable region, they > could write whatever hash they want into in addition to whatever code.) > > The general CFI ideas for this are discussed here, but focuses more on a > CFG analysis to construct valid call destinations, which tends to require > LTO, etc: > https://users.soe.ucsc.edu/~abadi/Papers/cfi-tissec-revised.pdf > > Later refinement for using jump tables (constructed via CFG analysis > during LTO) was proposed here: > https://www.usenix.org/system/files/conference/usenixsecurity14/sec14-paper-tice.pdf > Linux used the above implementation from 2018 to 2022: > https://android-developers.googleblog.com/2018/10/control-flow-integrity-in-android-kernel.html > but the corner cases for target addresses not being the actual functions > (i.e. pointing into the jump table) was a continual source of problems, > and generating the jump tables required full LTO, which had its own set > of problems. > > Looking at function prototypes as the source of call validity was > presented here, though still relied on LTO: > https://www.blackhat.com/docs/asia-17/materials/asia-17-Moreira-Drop-The-Rop-Fine-Grained-Control-Flow-Integrity-For-The-Linux-Kernel-wp.pdf > > The KCFI approach built on the function-prototype idea, but avoided > needing LTO, and could be further updated to deal with CPU errata > (retpolines, etc): > https://lpc.events/event/16/contributions/1315/ > This has been working very well now for 3 years, but has been limited to > only Linux built with Clang (e.g. Linux kernel CFI, first LLVM-CFI and now > KCFI, has been deployed on all Android phones and all Chrome OS devices > since 2018.) > > I have been trying to find someone to work on KCFI in GCC since 2018, > (see my annual arm-waving at the Linux Plumbers conference Toolchain > track) and other than Dan Li who took a stab at it for aarch64 in 2023, > no one has stepped up to do it, so I thought I'd finally try tackling > it. :)
Thanks for the write-up. So I wonder whether a more general solution would be to detach "hash value" computation and assignment and leave that to external tooling that could do better than non-LTO can and the compiler working with attributed function types instead? Say void __attribute__((cfi_hash("voidvoidbutonlykind1"))) foo (void); that decouples the way to arrive at the hashes from the instrumentation. You could then of course have compiler assisted auto-attribution based on function types. So implementation-wise I'd try to separate both? Oh, and somehow it reminds me of vtable verification? That said, how's the collision rate on the kernel side? What's the usual exploitation of overwriting a call function pointer? That is, what's a good useful function to target? I'd guess a hypothetical setuid (int)? Somehow it feels like a more academic thing, with other attack vectors being much more accessible? Richard.