diff --git a/stand/common/load_elf.c b/stand/common/load_elf.c --- a/stand/common/load_elf.c +++ b/stand/common/load_elf.c @@ -545,6 +545,7 @@ Elf_Dyn *dp; Elf_Addr adp; Elf_Addr ctors; + Elf_Addr kcfi; int ndp; int symstrindex; int symtabindex; @@ -731,16 +732,26 @@ shdr[ehdr->e_shstrndx].sh_offset, chunk); if (shstr) { for (i = 0; i < ehdr->e_shnum; i++) { - if (strcmp(shstr + shdr[i].sh_name, - ".ctors") != 0) - continue; - ctors = shdr[i].sh_addr; - file_addmetadata(fp, MODINFOMD_CTORS_ADDR, - sizeof(ctors), &ctors); - size = shdr[i].sh_size; - file_addmetadata(fp, MODINFOMD_CTORS_SIZE, - sizeof(size), &size); - break; + if (strcmp(shstr + shdr[i].sh_name, ".ctors") == + 0) { + ctors = shdr[i].sh_addr; + file_addmetadata(fp, + MODINFOMD_CTORS_ADDR, sizeof(ctors), + &ctors); + size = shdr[i].sh_size; + file_addmetadata(fp, + MODINFOMD_CTORS_SIZE, sizeof(size), + &size); + } + else if (strcmp(shstr + shdr[i].sh_name, + ".kcfi_traps") == 0) { + kcfi = shdr[i].sh_addr; + file_addmetadata(fp, MODINFOMD_CFI_ADDR, + sizeof(kcfi), &kcfi); + size = shdr[i].sh_size; + file_addmetadata(fp, MODINFOMD_CFI_SIZE, + sizeof(size), &size); + } } free(shstr); } diff --git a/sys/amd64/amd64/cfi.c b/sys/amd64/amd64/cfi.c new file mode 100644 --- /dev/null +++ b/sys/amd64/amd64/cfi.c @@ -0,0 +1,106 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2025 SHENGYI HUNG + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include +#include +#include + +static int +regoff(int reg) +{ +#define _MATCH_REG(i, reg) \ + case i: \ + return ( \ + offsetof(struct trapframe, tf_##reg) / sizeof(register_t)) + switch (reg) { + _MATCH_REG(0, rax); + _MATCH_REG(1, rcx); + _MATCH_REG(2, rdx); + _MATCH_REG(3, rbx); + _MATCH_REG(4, rsp); /* SIB when mod != 3 */ + _MATCH_REG(5, rbp); + _MATCH_REG(6, rsi); + _MATCH_REG(7, rdi); + _MATCH_REG(8, r8); /* REX.R is set */ + _MATCH_REG(9, r9); + _MATCH_REG(10, r10); + _MATCH_REG(11, r11); + _MATCH_REG(12, r12); + _MATCH_REG(13, r13); + _MATCH_REG(14, r14); + _MATCH_REG(15, r15); + } +#undef _MATCH_REG + return (0); +} + +bool +decode_cfi_frame(struct trapframe *tf, uintptr_t *callee_addr, uint32_t *type) +{ + unsigned char buffer[14]; + + memcpy(buffer, (unsigned char *)(tf->tf_rip - 12), sizeof(buffer)); + /* + * clang generates following instructions: + * + * mov $typeid, %reg + * add $callee-16, %reg + * je .Lcorrect + * ud2 + * .Lcorrect + * + * What we do is to compare if the previous context is trigger by + * mov(0xba) and following with one add(0x03) and ud2 itself, if it is, we can + * identified it maybe CFI fault + */ + + if (buffer[1] != 0xba) + return (false); + if (buffer[7] != 0x03) + return (false); + if (buffer[13] != 0x0b) + return (false); + + *type = *((uint32_t *)(buffer + 2)); + + /* + * Decode register(prefix & REX.R) | (MODRM.rm(3bit)) + */ + *callee_addr = ((register_t *)tf)[regoff( + (((buffer[6] >> 2) & 0x1) << 3) | (buffer[8] & 0x7))]; + return (true); +} + +void +post_cfi(struct trapframe *frame) +{ + TRAPF_PC(frame) += 2; +} diff --git a/sys/amd64/amd64/trap.c b/sys/amd64/amd64/trap.c --- a/sys/amd64/amd64/trap.c +++ b/sys/amd64/amd64/trap.c @@ -51,6 +51,7 @@ #include #include #include +#include #include #include #include @@ -440,6 +441,14 @@ (void)trap_pfault(frame, false, NULL, NULL); return; + case T_PRIVINFLT: +/* Triggered by ud2 with kCFI enabled */ +#ifdef KCFI + if (cfi_handler(frame)) + return; +#endif + break; + case T_DNA: if (PCB_USER_FPU(td->td_pcb)) panic("Unregistered use of FPU in kernel"); diff --git a/sys/amd64/include/cfi.h b/sys/amd64/include/cfi.h new file mode 100644 --- /dev/null +++ b/sys/amd64/include/cfi.h @@ -0,0 +1,12 @@ +#ifndef _MACHINE_CFI_H +#define _MACHINE_CFI_H + +#include + +#include +#include + +bool decode_cfi_frame(struct trapframe *, uintptr_t *, uint32_t *); +void post_cfi(struct trapframe *); + +#endif diff --git a/sys/conf/files b/sys/conf/files --- a/sys/conf/files +++ b/sys/conf/files @@ -3895,6 +3895,7 @@ kern/subr_bus_dma.c standard kern/subr_bufring.c standard kern/subr_capability.c standard +kern/subr_cfi.c optional kcfi kern/subr_clock.c standard kern/subr_compressor.c standard \ compile-with "${NORMAL_C} -I$S/contrib/zstd/lib/freebsd" diff --git a/sys/conf/files.amd64 b/sys/conf/files.amd64 --- a/sys/conf/files.amd64 +++ b/sys/conf/files.amd64 @@ -64,6 +64,7 @@ amd64/amd64/bios.c standard amd64/amd64/bpf_jit_machdep.c optional bpf_jitter amd64/amd64/copyout.c standard +amd64/amd64/cfi.c optional kcfi amd64/amd64/cpu_switch.S standard amd64/amd64/db_disasm.c optional ddb amd64/amd64/db_interface.c optional ddb diff --git a/sys/conf/kern.mk b/sys/conf/kern.mk --- a/sys/conf/kern.mk +++ b/sys/conf/kern.mk @@ -291,6 +291,10 @@ -fsanitize=thread .endif +.if !empty(KCFI_ENABLED) +SAN_CFLAGS+= -fsanitize=kcfi +.endif + # # Kernel Memory SANitizer support # diff --git a/sys/conf/kern.post.mk b/sys/conf/kern.post.mk --- a/sys/conf/kern.post.mk +++ b/sys/conf/kern.post.mk @@ -37,6 +37,10 @@ MKMODULESENV+= KCSAN_ENABLED="yes" .endif +.if !empty(KCFI_ENABLED) +MKMODULESENV+= KCFI_ENABLED="yes" +.endif + .if defined(GCOV_CFLAGS) MKMODULESENV+= GCOV_CFLAGS="${GCOV_CFLAGS}" .endif @@ -210,11 +214,15 @@ OBJS_DEPEND_GUESS+= offset.inc assym.inc vnode_if.h ${BEFORE_DEPEND:M*.h} \ ${MFILES:T:S/.m$/.h/} +.if !empty(KCFI_ENABLED) +MAKEOBJOPT= "-s" +.endif + .for mfile in ${MFILES} # XXX the low quality .m.o rules gnerated by config are normally used # instead of the .m.c rules here. -${mfile:T:S/.m$/.c/}: ${mfile} - ${AWK} -f $S/tools/makeobjops.awk ${mfile} -c +${mfile:T:S/.m$/.c/}: ${mfile} $S/tools/makeobjops.awk + ${AWK} -f $S/tools/makeobjops.awk ${mfile} -c ${MAKEOBJOPT} ${mfile:T:S/.m$/.h/}: ${mfile} ${AWK} -f $S/tools/makeobjops.awk ${mfile} -h .endfor diff --git a/sys/conf/kern.pre.mk b/sys/conf/kern.pre.mk --- a/sys/conf/kern.pre.mk +++ b/sys/conf/kern.pre.mk @@ -92,6 +92,7 @@ COMPAT_FREEBSD32_ENABLED!= grep COMPAT_FREEBSD32 opt_global.h || true ; echo +KCFI_ENABLED!= grep KCFI opt_global.h || true ; echo KASAN_ENABLED!= grep KASAN opt_global.h || true ; echo KCSAN_ENABLED!= grep KCSAN opt_global.h || true ; echo KMSAN_ENABLED!= grep KMSAN opt_global.h || true ; echo @@ -105,6 +106,10 @@ .endif .endif +.if !empty(KCFI_ENABLED) +MAKEOBJOPT= "-s" +.endif + CFLAGS+= ${GCOV_CFLAGS} # Put configuration-specific C flags last so that they can override @@ -149,7 +154,7 @@ NORMAL_S= ${CC:N${CCACHE_BIN}} -c ${ASM_CFLAGS} ${WERROR} ${.IMPSRC} NORMAL_C_NOWERROR= ${CC} -c ${CFLAGS} ${.IMPSRC} -NORMAL_M= ${AWK} -f $S/tools/makeobjops.awk ${.IMPSRC} -c ; \ +NORMAL_M= ${AWK} -f $S/tools/makeobjops.awk ${.IMPSRC} -c ${MAKEOBJOPT} ; \ ${CC} -c ${CFLAGS} ${WERROR} ${.PREFIX}.c NORMAL_FW= uudecode -o ${.TARGET} ${.ALLSRC} diff --git a/sys/conf/kmod.mk b/sys/conf/kmod.mk --- a/sys/conf/kmod.mk +++ b/sys/conf/kmod.mk @@ -406,6 +406,7 @@ .endfor .endif +KCFI_ENABLED= ${KERN_OPTS:MKCFI} KASAN_ENABLED= ${KERN_OPTS:MKASAN} KCSAN_ENABLED= ${KERN_OPTS:MKCSAN} KMSAN_ENABLED= ${KERN_OPTS:MKMSAN} @@ -435,6 +436,10 @@ ${AWK} -f ${SYSDIR}/tools/vnode_if.awk ${SYSDIR}/kern/vnode_if.src -q .endif +.if !empty(KCFI_ENABLED) +MAKEOBJOPT= "-s" +.endif + # Build _if.[ch] from _if.m, and clean them when we're done. # __MPATH defined in config.mk _MFILES=${__MPATH:T:O} @@ -448,7 +453,7 @@ .endif .endfor # _i .m.c: ${SYSDIR}/tools/makeobjops.awk - ${AWK} -f ${SYSDIR}/tools/makeobjops.awk ${.IMPSRC} -c + ${AWK} -f ${SYSDIR}/tools/makeobjops.awk ${.IMPSRC} -c ${MAKEOBJOPT} .m.h: ${SYSDIR}/tools/makeobjops.awk ${AWK} -f ${SYSDIR}/tools/makeobjops.awk ${.IMPSRC} -h diff --git a/sys/conf/options b/sys/conf/options --- a/sys/conf/options +++ b/sys/conf/options @@ -249,6 +249,7 @@ COVERAGE opt_global.h KASAN opt_global.h KCOV +KCFI opt_global.h KCSAN opt_global.h KMSAN opt_global.h KUBSAN opt_global.h diff --git a/sys/kern/kern_linker.c b/sys/kern/kern_linker.c --- a/sys/kern/kern_linker.c +++ b/sys/kern/kern_linker.c @@ -672,6 +672,10 @@ #ifdef __arm__ lf->exidx_addr = 0; lf->exidx_size = 0; +#endif +#ifdef KCFI + lf->kcfi_traps_addr = 0; + lf->kcfi_traps_size = 0; #endif STAILQ_INIT(&lf->common); TAILQ_INIT(&lf->modules); diff --git a/sys/kern/link_elf.c b/sys/kern/link_elf.c --- a/sys/kern/link_elf.c +++ b/sys/kern/link_elf.c @@ -30,6 +30,7 @@ #include "opt_gdb.h" #include +#include #include #include #include @@ -358,7 +359,7 @@ } static void -link_elf_invoke_cbs(caddr_t addr, size_t size) +link_elf_invoke_cbs(caddr_t addr, size_t size) __nosanitizekcfi { void (**ctor)(void); size_t i, cnt; @@ -448,8 +449,8 @@ link_elf_init(void* arg) { Elf_Dyn *dp; - Elf_Addr *ctors_addrp; - Elf_Size *ctors_sizep; + Elf_Addr *addrp; + Elf_Size *sizep; caddr_t modptr, baseptr, sizeptr; elf_file_t ef; const char *modname; @@ -501,15 +502,25 @@ sizeptr = preload_search_info(modptr, MODINFO_SIZE); if (sizeptr != NULL) linker_kernel_file->size = *(size_t *)sizeptr; - ctors_addrp = (Elf_Addr *)preload_search_info(modptr, - MODINFO_METADATA | MODINFOMD_CTORS_ADDR); - ctors_sizep = (Elf_Size *)preload_search_info(modptr, - MODINFO_METADATA | MODINFOMD_CTORS_SIZE); - if (ctors_addrp != NULL && ctors_sizep != NULL) { - linker_kernel_file->ctors_addr = ef->address + - *ctors_addrp; - linker_kernel_file->ctors_size = *ctors_sizep; + addrp = (Elf_Addr *)preload_search_info(modptr, + MODINFO_METADATA | MODINFOMD_CTORS_ADDR); + sizep = (Elf_Size *)preload_search_info(modptr, + MODINFO_METADATA | MODINFOMD_CTORS_SIZE); + if (addrp != NULL && sizep != NULL) { + linker_kernel_file->ctors_addr = ef->address + *addrp; + linker_kernel_file->ctors_size = *sizep; + } +#ifdef KCFI + addrp = (Elf_Addr *)preload_search_info(modptr, + MODINFO_METADATA | MODINFOMD_CFI_ADDR); + sizep = (Elf_Size *)preload_search_info(modptr, + MODINFO_METADATA | MODINFOMD_CFI_SIZE); + if (addrp != NULL && sizep != NULL) { + linker_kernel_file->kcfi_traps_addr = ef->address + + *addrp; + linker_kernel_file->kcfi_traps_size = *sizep; } +#endif } (void)link_elf_preload_parse_symbols(ef); @@ -876,6 +887,26 @@ #endif /* __arm__ */ +#ifdef KCFI + +static void +link_elf_locate_kcfi_traps_preload(struct linker_file *lf, caddr_t modptr) +{ + Elf_Addr *kcfi_addrp; + Elf_Size *kcfi_sizep; + + kcfi_addrp = (Elf_Addr *)preload_search_info(modptr, + MODINFO_METADATA | MODINFOMD_CFI_ADDR); + kcfi_sizep = (Elf_Size *)preload_search_info(modptr, + MODINFO_METADATA | MODINFOMD_CFI_SIZE); + if (kcfi_addrp != NULL && kcfi_sizep != NULL) { + lf->kcfi_traps_addr = ((elf_file_t)lf)->address + *kcfi_addrp; + lf->kcfi_traps_size = *kcfi_sizep; + } +} + +#endif /* KCFI */ + static int link_elf_link_preload(linker_class_t cls, const char *filename, linker_file_t *result) @@ -935,6 +966,10 @@ link_elf_locate_exidx_preload(lf, modptr); #endif +#ifdef KCFI + link_elf_locate_kcfi_traps_preload(lf, modptr); +#endif + error = parse_dynamic(ef); if (error == 0) error = parse_dpcpu(ef); @@ -1291,11 +1326,23 @@ if (shdr[i].sh_type == SHT_SYMTAB) { symtabindex = i; symstrindex = shdr[i].sh_link; - } else if (shstrs != NULL && shdr[i].sh_name != 0 && - strcmp(shstrs + shdr[i].sh_name, ".ctors") == 0) { - /* Record relocated address and size of .ctors. */ - lf->ctors_addr = mapbase + shdr[i].sh_addr - base_vaddr; - lf->ctors_size = shdr[i].sh_size; + } else if (shstrs != NULL && shdr[i].sh_name != 0) { + if (strcmp(shstrs + shdr[i].sh_name, ".ctors") == 0) { + /* + * Record relocated address and size of .ctors. + */ + lf->ctors_addr = mapbase + shdr[i].sh_addr - + base_vaddr; + lf->ctors_size = shdr[i].sh_size; + } +#ifdef KCFI + else if (strcmp(shstrs + shdr[i].sh_name, + ".kcfi_traps") == 0) { + lf->kcfi_traps_addr = mapbase + + shdr[i].sh_addr - base_vaddr; + lf->kcfi_traps_size = shdr[i].sh_size; + } +#endif } } if (symtabindex < 0 || symstrindex < 0) @@ -1636,7 +1683,7 @@ static int link_elf_symbol_values1(linker_file_t lf, c_linker_sym_t sym, - linker_symval_t *symval, bool see_local) + linker_symval_t *symval, bool see_local) __nosanitizekcfi { elf_file_t ef; const Elf_Sym *es; @@ -1669,7 +1716,7 @@ static int link_elf_debug_symbol_values(linker_file_t lf, c_linker_sym_t sym, - linker_symval_t *symval) + linker_symval_t *symval) __nosanitizekcfi { elf_file_t ef = (elf_file_t)lf; const Elf_Sym *es = (const Elf_Sym *)sym; @@ -1999,7 +2046,7 @@ */ static int elf_lookup_ifunc(linker_file_t lf, Elf_Size symidx, int deps __unused, - Elf_Addr *res) + Elf_Addr *res) __nosanitizekcfi { elf_file_t ef; const Elf_Sym *symp; diff --git a/sys/kern/link_elf_obj.c b/sys/kern/link_elf_obj.c --- a/sys/kern/link_elf_obj.c +++ b/sys/kern/link_elf_obj.c @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -576,7 +577,13 @@ lf->dtors_size = shdr[i].sh_size; } } - +#ifdef KCFI + else if (ef->progtab[pb].name != NULL && + strcmp(ef->progtab[pb].name, ".kcfi_traps") == 0) { + lf->kcfi_traps_addr = ef->progtab[pb].addr; + lf->kcfi_traps_size = shdr[i].sh_size; + } +#endif /* Update all symbol values with the offset. */ for (j = 0; j < ef->ddbsymcnt; j++) { es = &ef->ddbsymtab[j]; @@ -643,7 +650,7 @@ } static void -link_elf_invoke_cbs(caddr_t addr, size_t size) +link_elf_invoke_cbs(caddr_t addr, size_t size) __nosanitizekcfi { void (**ctor)(void); size_t i, cnt; @@ -1081,6 +1088,13 @@ shdr[i].sh_size; } } +#ifdef KCFI + else if (!strcmp(ef->progtab[pb].name, + ".kcfi_traps")) { + lf->kcfi_traps_addr = (caddr_t)mapbase; + lf->kcfi_traps_size = shdr[i].sh_size; + } +#endif } else if (shdr[i].sh_type == SHT_PROGBITS) ef->progtab[pb].name = "<>"; #ifdef __amd64__ @@ -1515,7 +1529,7 @@ static int link_elf_symbol_values1(linker_file_t lf, c_linker_sym_t sym, - linker_symval_t *symval, bool see_local) + linker_symval_t *symval, bool see_local) __nosanitizekcfi { elf_file_t ef; const Elf_Sym *es; @@ -1689,7 +1703,8 @@ * the case that the symbol can be found through the hash table. */ static int -elf_obj_lookup(linker_file_t lf, Elf_Size symidx, int deps, Elf_Addr *res) +elf_obj_lookup(linker_file_t lf, Elf_Size symidx, int deps, + Elf_Addr *res) __nosanitizekcfi { elf_file_t ef = (elf_file_t)lf; Elf_Sym *sym; diff --git a/sys/kern/subr_cfi.c b/sys/kern/subr_cfi.c new file mode 100644 --- /dev/null +++ b/sys/kern/subr_cfi.c @@ -0,0 +1,129 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2025 SHENGYI HUNG + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include +#include + +#define CFI_DEBUG_MSG "CFI check failed on PC %p for callee %p and type %x\n" + +FEATURE(kcfi, "Kernel control flow integrity"); + +static SYSCTL_NODE(_debug, OID_AUTO, kcfi, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, + "KCFI options"); + +static bool cfi_panic __read_mostly = false; +SYSCTL_BOOL(_debug_kcfi, OID_AUTO, panic_on_violation, + CTLFLAG_RDTUN | CTLFLAG_NOFETCH, &cfi_panic, 0, "Panic on KCFI violation"); + +static bool cfi_disabled __read_mostly = false; +SYSCTL_BOOL(_debug_kcfi, OID_AUTO, disabled, CTLFLAG_RDTUN | CTLFLAG_NOFETCH, + &cfi_disabled, 0, "Disable KCFI message"); + +static void +report_cfi(uintptr_t caller_addr, uintptr_t callee_addr, uint32_t type) +{ + if (cfi_panic) + panic(CFI_DEBUG_MSG, (void *)caller_addr, (void *)callee_addr, + type); + else + printf(CFI_DEBUG_MSG, (void *)caller_addr, (void *)callee_addr, + type); +} + +static int +kcfi_lookup_module(linker_file_t lf, void *arg) +{ + uintptr_t *module = (uintptr_t *)arg; + + if (lf->address <= (caddr_t)*module && + (lf->address + lf->size) >= (caddr_t)*module) { + *module = (uintptr_t)lf; + return (1); + } + + return (0); +} + +static inline uintptr_t +cfi_trap_addr(int32_t *addr) +{ + return ((uintptr_t)((int32_t)(intptr_t)addr + *addr)); +} + +static bool +is_cfi_exception(uintptr_t address) +{ + linker_file_t module; + int32_t *trap, *traps_end; + + module = (linker_file_t)address; + linker_file_foreach(kcfi_lookup_module, &module); + + if ((uintptr_t)module == address) + return (false); + + trap = (int32_t *)(module->kcfi_traps_addr); + traps_end = (int32_t *)(module->kcfi_traps_addr + + module->kcfi_traps_size); + + if (trap == NULL) + return (false); + + for (; trap != traps_end; trap++) + if (cfi_trap_addr(trap) == address) + return (true); + + return (false); +} + +bool +cfi_handler(struct trapframe *frame) +{ + uintptr_t callee_addr, caller_addr; + uint32_t type; + + caller_addr = TRAPF_PC(frame); + + if (decode_cfi_frame(frame, &callee_addr, &type) && + is_cfi_exception((uintptr_t)caller_addr)) { + report_cfi(caller_addr, callee_addr, type); + /* needs to be platform dependent */ + post_cfi(frame); + return (true); + } + + return (false); +} + diff --git a/sys/kern/subr_module.c b/sys/kern/subr_module.c --- a/sys/kern/subr_module.c +++ b/sys/kern/subr_module.c @@ -385,6 +385,12 @@ case MODINFOMD_KEYBUF: sbuf_cat(sbp, "MODINFOMD_KEYBUF"); break; + case MODINFOMD_CFI_ADDR: + sbuf_cat(sbp, "MODINFOMD_CFI_ADDR"); + break; + case MODINFOMD_CFI_SIZE: + sbuf_cat(sbp, "MODINFOMD_CFI_SIZE"); + break; #ifdef MODINFOMD_SMAP case MODINFOMD_SMAP: sbuf_cat(sbp, "MODINFOMD_SMAP"); @@ -455,6 +461,7 @@ break; case MODINFO_SIZE: case MODINFO_METADATA | MODINFOMD_CTORS_SIZE: + case MODINFO_METADATA | MODINFOMD_CFI_SIZE: sbuf_printf(sbp, "%lu", *(u_long *)bptr); break; case MODINFO_ADDR: @@ -464,6 +471,7 @@ case MODINFO_METADATA | MODINFOMD_KERNEND: case MODINFO_METADATA | MODINFOMD_ENVP: case MODINFO_METADATA | MODINFOMD_CTORS_ADDR: + case MODINFO_METADATA | MODINFOMD_CFI_ADDR: #ifdef MODINFOMD_SMAP case MODINFO_METADATA | MODINFOMD_SMAP: #endif diff --git a/sys/sys/cdefs.h b/sys/sys/cdefs.h --- a/sys/sys/cdefs.h +++ b/sys/sys/cdefs.h @@ -771,6 +771,11 @@ #else #define __nosanitizethread #endif +#if __has_feature(kcfi) && defined(__clang__) +#define __nosanitizekcfi __attribute__((no_sanitize("kcfi"))) +#else +#define __nosanitizekcfi +#endif /* * Make it possible to opt out of stack smashing protection. diff --git a/sys/sys/cfi.h b/sys/sys/cfi.h new file mode 100644 --- /dev/null +++ b/sys/sys/cfi.h @@ -0,0 +1,14 @@ +/* + */ +#ifndef _SYS_CFI_H_ +#define _SYS_CFI_H_ + +#ifdef _KERNEL + +struct trapframe; + +bool cfi_handler(struct trapframe *); + +#endif /* _KERNEL */ + +#endif /* _SYS_CFI_H_ */ diff --git a/sys/sys/linker.h b/sys/sys/linker.h --- a/sys/sys/linker.h +++ b/sys/sys/linker.h @@ -109,6 +109,11 @@ caddr_t exidx_addr; /* Unwind data index table start */ size_t exidx_size; /* Unwind data index table size */ #endif + +#ifdef KCFI + caddr_t kcfi_traps_addr; /* KCFI trap table start */ + size_t kcfi_traps_size; /* KCFI trap table size */ +#endif }; /* @@ -251,6 +256,8 @@ #define MODINFOMD_KEYBUF 0x000d /* Crypto key intake buffer */ #define MODINFOMD_FONT 0x000e /* Console font */ #define MODINFOMD_SPLASH 0x000f /* Console splash screen */ +#define MODINFOMD_CFI_ADDR 0x0010 /* address of .kcfi_traps */ +#define MODINFOMD_CFI_SIZE 0x0020 /* size of .kcfi_traps */ #define MODINFOMD_NOCOPY 0x8000 /* don't copy this metadata to the kernel */ #define MODINFOMD_DEPLIST (0x4001 | MODINFOMD_NOCOPY) /* depends on */