ARM32 KCFI violations currently show as generic "Oops - undefined instruction" errors, making debugging CFI failures difficult. Add a proper KCFI trap handler similar to the aarch64 implementation to provide clear CFI error messages. The GCC ARM32 KCFI implementation uses UDF instructions with a specific encoding pattern: - UDF instruction format: cccc 0111 1111 imm12 1111 imm4 - 16-bit immediate reconstructed from bits 19-8 and 3-0 - KCFI encoding: 0x8000 | (type_reg_num << 5) | (target_reg_num & 31) - Bit 15: KCFI trap identifier (0x8000) - Bits 9-5: Type ID register field (0x1F when unavailable) - Bits 4-0: Target address register number When the type register field is 0x1F (unavailable due to stack spilling), the handler walks back up to 5 preceding instructions to locate the movw/movt instruction pair that loads the 32-bit type ID, similar to x86 CFI trap reconstruction. The undef_hook pattern matching includes the KCFI bit requirement to ensure the handler is only called for KCFI violations, not arbitrary UDF instructions. Signed-off-by: Kees Cook <kees@xxxxxxxxxx> --- Cc: "Russell King (Oracle)" <rmk+kernel@xxxxxxxxxxxxxxx> Cc: Zhen Lei <thunder.leizhen@xxxxxxxxxx> Cc: Arnd Bergmann <arnd@xxxxxxxx> Cc: "Michał Pecio" <michal.pecio@xxxxxxxxx> Cc: Sebastian Andrzej Siewior <bigeasy@xxxxxxxxxxxxx> Cc: Sami Tolvanen <samitolvanen@xxxxxxxxxx> Cc: <linux-arm-kernel@xxxxxxxxxxxxxxxxxxx> --- arch/arm/kernel/traps.c | 102 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c index afbd2ebe5c39..f2e4e18541e0 100644 --- a/arch/arm/kernel/traps.c +++ b/arch/arm/kernel/traps.c @@ -27,6 +27,7 @@ #include <linux/sched/task_stack.h> #include <linux/irq.h> #include <linux/vmalloc.h> +#include <linux/cfi.h> #include <linux/atomic.h> #include <asm/cacheflush.h> @@ -40,6 +41,7 @@ #include <asm/stacktrace.h> #include <asm/system_misc.h> #include <asm/opcodes.h> +#include <linux/bitfield.h> static const char *handler[]= { @@ -685,6 +687,106 @@ asmlinkage int arm_syscall(int no, struct pt_regs *regs) return 0; } +#ifdef CONFIG_CFI +/* + * ARM32 KCFI trap handler. + * UDF instruction format: cccc 0111 1111 imm12 1111 imm4 + * Immediate is reconstructed from bits 19-8 (12 bits) and bits 3-0 (4 bits) + * KCFI immediate encoding: 0x8000 | (0x1F << 5) | (target_reg_num & 31) + * - Bit 15: KCFI trap identifier (0x8000) + * - Bits 9-5: Type ID register field (0x1F when invalid due to stack spilling) + * - Bits 4-0: Target address register number + */ +#define CFI_UDF_KCFI_BIT BIT(15) /* KCFI identifier bit (0x8000) */ +#define CFI_UDF_IMM_TARGET GENMASK(4, 0) /* Target register (bits 4:0) */ +#define CFI_UDF_IMM_TYPE GENMASK(9, 5) /* Type register (bits 9:5) */ + +/* UDF base pattern with KCFI bit: cond=0xe, 0x7f, xxxx, 1xxx, 0xf, xxxx */ +#define CFI_UDF_IMM_BASE 0xe7f008f0 +#define CFI_UDF_IMM_MASK 0xfff008f0 /* Mask for UDF + KCFI bit matching */ + +static int cfi_udf_handler(struct pt_regs *regs, unsigned int instr) +{ + unsigned long target; + u32 target_reg, type_reg, type, imm16; + + /* Reconstruct 16-bit immediate from bits 19-8 and 3-0 */ + imm16 = ((instr >> 4) & 0xfff0) | (instr & 0x0f); + + target_reg = FIELD_GET(CFI_UDF_IMM_TARGET, imm16); + type_reg = FIELD_GET(CFI_UDF_IMM_TYPE, imm16); + + if (target_reg >= 16) { + pr_err("CFI UDF handler: invalid target register %u\n", target_reg); + return 1; + } + + target = regs->uregs[target_reg]; + + /* Type register field is set to all 1s (0x1F) when invalid due to stack spilling */ + if (type_reg == 0x1F) { + u32 *pc = (u32 *)regs->ARM_pc; + int i; + + type = 0; + /* Walk back up to 5 instructions to find movw/movt pair for type ID */ + for (i = 1; i <= 5; i++) { + u32 instr_prev = __mem_to_opcode_arm(pc[-i]); + + /* Check for movw: cccc 0011 0000 imm4 Rd imm12 */ + if ((instr_prev & 0x0ff00000) == 0x03000000) { + u32 imm16 = ((instr_prev >> 4) & 0xf000) | (instr_prev & 0xfff); + type |= imm16; + } + /* Check for movt: cccc 0011 0100 imm4 Rd imm12 */ + else if ((instr_prev & 0x0ff00000) == 0x03400000) { + u32 imm16 = ((instr_prev >> 4) & 0xf000) | (instr_prev & 0xfff); + type |= (imm16 << 16); + } + } + if (type == 0) + pr_err("CFI UDF handler: failed to find type value\n"); + } else { + if (type_reg >= 16) { + pr_err("CFI UDF handler: invalid type register %u\n", type_reg); + return 1; + } + + type = regs->uregs[type_reg]; + } + + switch (report_cfi_failure(regs, regs->ARM_pc, &target, type)) { + case BUG_TRAP_TYPE_BUG: + die("Oops - CFI", regs, 0); + break; + case BUG_TRAP_TYPE_WARN: + break; + default: + return 1; + } + + /* Skip the UDF instruction */ + regs->ARM_pc += 4; + return 0; +} + +static struct undef_hook cfi_undef_hook = { + .instr_mask = CFI_UDF_IMM_MASK, + .instr_val = CFI_UDF_IMM_BASE, + .cpsr_mask = 0, + .cpsr_val = 0, + .fn = cfi_udf_handler, +}; + +static int __init arm_cfi_init(void) +{ + register_undef_hook(&cfi_undef_hook); + return 0; +} + +early_initcall(arm_cfi_init); +#endif /* CONFIG_CFI */ + #ifdef CONFIG_TLS_REG_EMUL /* -- 2.34.1