[PATCH v2 9/9] ARM: traps: Implement KCFI trap handler for ARM32

Kees Cook
2025-09-04T05:11:02Z

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@kernel.org>
---
Cc: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
Cc: Zhen Lei <thunder.leizhen@huawei.com>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: "Michał Pecio" <michal.pecio@gmail.com>
Cc: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Cc: Sami Tolvanen <samitolvanen@google.com>
Cc: <linux-arm-kernel@lists.infradead.org>
---
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