$RodHat_
MOTD

LLVM 23 is out. FreeBSD gets shadow call stack on RISC-V. Your GCC build got nothing.

Published by

A RISC-V development board beside stacked amber layers representing protected compiler call-stack frames.
Photo: AI-generated / AI-generated by OpenAI

LLVM 23 shipped on October 8, 2026. FreeBSD has been running LLVM/Clang as the base compiler since FreeBSD 10. That is twelve years of the BSDs tracking the toolchain that Linux distributions treat as optional and GCC loyalists pretend does not exist.

Here is what changed in 23 and why it matters.

Shadow call stack lands on RISC-V

Shadow call stack has been in LLVM for AArch64 since version 11. The mechanism is simple: the compiler maintains a second stack pointer in a dedicated register and stores return addresses there instead of on the normal stack. On AArch64, that register is x18. The shadow stack lives at a separate address protected by its own page table entries. A buffer overflow corrupting the program stack does not reach the return address because the return address is not there.

LLVM 23 extends -fsanitize=shadow-call-stack to RISC-V, using the global pointer register gp (x3) as the shadow stack pointer. RISC-V is shipping in embedded controllers, single-board hardware, and increasingly in server silicon. Getting compiler hardening in place before that hardware enters production is how you avoid the retrospective mess Linux had with Spectre mitigations: reactive, painful, and years behind the hardware.

This is a different mechanism than Intel and AMD shadow stacks (CET SHSTK). Intel’s hardware shadow stack requires CPU feature gating, kernel support, and glibc cooperation. LLVM’s approach is a pure compiler transform, no new instructions required, which means it works on any RISC-V chip available today.

The cost is a dedicated register. gp is a calling-convention-visible register in the RISC-V ABI, and pulling it into service for shadow call stack changes code generation across a function. On embedded targets with already-tight register budgets you will see the impact. On server RISC-V code with plenty of registers and a threat model that includes stack corruption, the tradeoff is worth it.

CFI indirect-call checking is tighter

Control Flow Integrity is the family of protections that prevent an attacker from redirecting execution through indirect calls or vtable pointers. LLVM has shipped -fsanitize=cfi for years. LLVM 23 tightens the type-checking model for indirect function calls: the checker now validates calling convention compatibility in addition to return type and argument count. That closes a class of type confusion attacks where the attacker substitutes a function with a matching type layout but a different calling convention.

This matters more for C than for C++. The vtable checks in C++ CFI have been solid for a while. C has a lot of function pointer tables with implicit conventions, and tightening the checker without requiring new source annotations is a net improvement with no developer cost.

LLVM 23 also improves cross-DSO CFI, the mode you need when your binary loads shared libraries and you want CFI to work across the boundary. Cross-DSO CFI has historically required instrumenting every library in the chain, which is a deployment problem in practice. The hash-lookup overhead at shared library call boundaries is reduced in 23, and the cross-DSO checks are faster. If you abandoned cross-DSO CFI because the overhead was prohibitive, this is worth re-evaluating.

LLD and the size of what you build

LLD has been the default linker in FreeBSD since version 12. It is faster than GNU ld on large link jobs, handles parallel LTO correctly, and produces better DWARF in combination with the rest of the LLVM stack.

LLVM 23 fixes a long-standing LLD behavior where dead code in template instantiations (and dead code in monomorphized Rust functions) survived link-time garbage collection when it should have been eliminated. The bug appeared when -ffunction-sections -fdata-sections combined with LTO: the GC pass missed dead sections that became dead only after cross-module inlining resolved their one caller. The fix reduces binary size on C++ and Rust builds by one to three percent depending on how template-heavy the codebase is, with no build flag changes required.

FreeBSD ports include a significant amount of C++ with heavy template use. Binary size reductions that do not require developer intervention are real savings, compounded across a ports tree.

What GCC is doing about any of this

GCC has had -fstack-protector since 2005. It has shadow stacks through glibc’s CET integration on x86, which requires Intel Tiger Lake or AMD Zen 3 or newer and kernel support. It does not have a compiler-managed shadow call stack in the LLVM sense: a register-based return address store that works on any target without CPU feature gating. GCC has -mcf-protection for Intel IBT (the indirect branch half of CET), but that also requires hardware.

LLVM’s shadow call stack approach deploys on the hardware you have now, at a register budget cost you choose to pay or not. For new architectures like RISC-V this is not even a comparison: there is no RISC-V CET equivalent. LLVM is the only option if you want compiler-enforced return address protection on RISC-V today.

FreeBSD 15.1 ships it

FreeBSD 15.1 ships with LLVM 23 as the base compiler. Building world on a RISC-V machine now gives you shadow call stack available in CFLAGS for hardened ports. The Ports tree covers which WITH_* options enable CFI and shadow call stack for C and C++ ports.

LLVM is not the right compiler for every workload. On numerically intensive code where GCC’s x86 target tuning has thirty years of accumulated work, GCC still often wins on throughput. For system software, kernel modules, and daemons where the security properties of the generated code matter more than the last two percent of performance: LLVM’s story is coherent in a way GCC’s is not.

Twelve years in, FreeBSD’s decision to ship LLVM as the base compiler looks right. Nobody planned shadow call stack on RISC-V in 2014. The decision to track the toolchain moving faster on static analysis, CFI, and sanitizer infrastructure compounded well anyway.

The gcc-14 -fhardened coverage covers what GCC’s hardening story looks like from the other side. The landlock post is a useful counterpoint: the right mechanism existing is not the same as anyone using it.

Sources

  1. LLVM 23.0.0 Release Notes (The LLVM Project)
  2. Clang ShadowCallStack documentation (The LLVM Project)