Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

objtool: Validate kCFI calls

Validate that all indirect calls adhere to kCFI rules. Notably doing
nocfi indirect call to a cfi function is broken.

Apparently some Rust 'core' code violates this and explodes when ran
with FineIBT.

All the ANNOTATE_NOCFI_SYM sites are prime targets for attackers.

- runtime EFI is especially henous because it also needs to disable
IBT. Basically calling unknown code without CFI protection at
runtime is a massice security issue.

- Kexec image handover; if you can exploit this, you get to keep it :-)

Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Acked-by: Josh Poimboeuf <jpoimboe@kernel.org>
Acked-by: Sean Christopherson <seanjc@google.com>
Link: https://lkml.kernel.org/r/20250714103441.496787279@infradead.org

+72
+4
arch/x86/kernel/machine_kexec_64.c
··· 453 453 454 454 __ftrace_enabled_restore(save_ftrace_enabled); 455 455 } 456 + /* 457 + * Handover to the next kernel, no CFI concern. 458 + */ 459 + ANNOTATE_NOCFI_SYM(machine_kexec); 456 460 457 461 /* arch-dependent functionality related to kexec file-based syscall */ 458 462
+4
arch/x86/kvm/vmx/vmenter.S
··· 361 361 362 362 .section .text, "ax" 363 363 364 + #ifndef CONFIG_X86_FRED 365 + 364 366 SYM_FUNC_START(vmx_do_interrupt_irqoff) 365 367 VMX_DO_EVENT_IRQOFF CALL_NOSPEC _ASM_ARG1 366 368 SYM_FUNC_END(vmx_do_interrupt_irqoff) 369 + 370 + #endif
+4
arch/x86/platform/efi/efi_stub_64.S
··· 11 11 #include <asm/nospec-branch.h> 12 12 13 13 SYM_FUNC_START(__efi_call) 14 + /* 15 + * The EFI code doesn't have any CFI, annotate away the CFI violation. 16 + */ 17 + ANNOTATE_NOCFI_SYM 14 18 pushq %rbp 15 19 movq %rsp, %rbp 16 20 and $~0xf, %rsp
+5
drivers/misc/lkdtm/perms.c
··· 9 9 #include <linux/vmalloc.h> 10 10 #include <linux/mman.h> 11 11 #include <linux/uaccess.h> 12 + #include <linux/objtool.h> 12 13 #include <asm/cacheflush.h> 13 14 #include <asm/sections.h> 14 15 ··· 87 86 func(); 88 87 pr_err("FAIL: func returned\n"); 89 88 } 89 + /* 90 + * Explicitly doing the wrong thing for testing. 91 + */ 92 + ANNOTATE_NOCFI_SYM(execute_location); 90 93 91 94 static void execute_user_location(void *dst) 92 95 {
+10
include/linux/objtool.h
··· 184 184 * WARN using UD2. 185 185 */ 186 186 #define ANNOTATE_REACHABLE(label) __ASM_ANNOTATE(label, ANNOTYPE_REACHABLE) 187 + /* 188 + * This should not be used; it annotates away CFI violations. There are a few 189 + * valid use cases like kexec handover to the next kernel image, and there is 190 + * no security concern there. 191 + * 192 + * There are also a few real issues annotated away, like EFI because we can't 193 + * control the EFI code. 194 + */ 195 + #define ANNOTATE_NOCFI_SYM(sym) asm(__ASM_ANNOTATE(sym, ANNOTYPE_NOCFI)) 187 196 188 197 #else 189 198 #define ANNOTATE_NOENDBR ANNOTATE type=ANNOTYPE_NOENDBR ··· 203 194 #define ANNOTATE_INTRA_FUNCTION_CALL ANNOTATE type=ANNOTYPE_INTRA_FUNCTION_CALL 204 195 #define ANNOTATE_UNRET_BEGIN ANNOTATE type=ANNOTYPE_UNRET_BEGIN 205 196 #define ANNOTATE_REACHABLE ANNOTATE type=ANNOTYPE_REACHABLE 197 + #define ANNOTATE_NOCFI_SYM ANNOTATE type=ANNOTYPE_NOCFI 206 198 #endif 207 199 208 200 #if defined(CONFIG_NOINSTR_VALIDATION) && \
+1
include/linux/objtool_types.h
··· 65 65 #define ANNOTYPE_IGNORE_ALTS 6 66 66 #define ANNOTYPE_INTRA_FUNCTION_CALL 7 67 67 #define ANNOTYPE_REACHABLE 8 68 + #define ANNOTYPE_NOCFI 9 68 69 69 70 #endif /* _LINUX_OBJTOOL_TYPES_H */
+1
tools/include/linux/objtool_types.h
··· 65 65 #define ANNOTYPE_IGNORE_ALTS 6 66 66 #define ANNOTYPE_INTRA_FUNCTION_CALL 7 67 67 #define ANNOTYPE_REACHABLE 8 68 + #define ANNOTYPE_NOCFI 9 68 69 69 70 #endif /* _LINUX_OBJTOOL_TYPES_H */
+42
tools/objtool/check.c
··· 2392 2392 2393 2393 static int __annotate_late(struct objtool_file *file, int type, struct instruction *insn) 2394 2394 { 2395 + struct symbol *sym; 2396 + 2395 2397 switch (type) { 2396 2398 case ANNOTYPE_NOENDBR: 2397 2399 /* early */ ··· 2433 2431 2434 2432 case ANNOTYPE_REACHABLE: 2435 2433 insn->dead_end = false; 2434 + break; 2435 + 2436 + case ANNOTYPE_NOCFI: 2437 + sym = insn->sym; 2438 + if (!sym) { 2439 + ERROR_INSN(insn, "dodgy NOCFI annotation"); 2440 + return -1; 2441 + } 2442 + insn->sym->nocfi = 1; 2436 2443 break; 2437 2444 2438 2445 default: ··· 4011 4000 WARN_INSN(insn, "indirect %s found in MITIGATION_RETPOLINE build", 4012 4001 insn->type == INSN_JUMP_DYNAMIC ? "jump" : "call"); 4013 4002 warnings++; 4003 + } 4004 + 4005 + if (!opts.cfi) 4006 + return warnings; 4007 + 4008 + /* 4009 + * kCFI call sites look like: 4010 + * 4011 + * movl $(-0x12345678), %r10d 4012 + * addl -4(%r11), %r10d 4013 + * jz 1f 4014 + * ud2 4015 + * 1: cs call __x86_indirect_thunk_r11 4016 + * 4017 + * Verify all indirect calls are kCFI adorned by checking for the 4018 + * UD2. Notably, doing __nocfi calls to regular (cfi) functions is 4019 + * broken. 4020 + */ 4021 + list_for_each_entry(insn, &file->retpoline_call_list, call_node) { 4022 + struct symbol *sym = insn->sym; 4023 + 4024 + if (sym && (sym->type == STT_NOTYPE || 4025 + sym->type == STT_FUNC) && !sym->nocfi) { 4026 + struct instruction *prev = 4027 + prev_insn_same_sym(file, insn); 4028 + 4029 + if (!prev || prev->type != INSN_BUG) { 4030 + WARN_INSN(insn, "no-cfi indirect call!"); 4031 + warnings++; 4032 + } 4033 + } 4014 4034 } 4015 4035 4016 4036 return warnings;
+1
tools/objtool/include/objtool/elf.h
··· 70 70 u8 local_label : 1; 71 71 u8 frame_pointer : 1; 72 72 u8 ignore : 1; 73 + u8 nocfi : 1; 73 74 struct list_head pv_target; 74 75 struct reloc *relocs; 75 76 struct section *group_sec;