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

ARM: 9391/2: hw_breakpoint: Handle CFI breakpoints

This registers a breakpoint handler for the new breakpoint type
(0x03) inserted by LLVM CLANG for CFI breakpoints.

If we are in permissive mode, just print a backtrace and continue.

Example with CONFIG_CFI_PERMISSIVE enabled:

> echo CFI_FORWARD_PROTO > /sys/kernel/debug/provoke-crash/DIRECT
lkdtm: Performing direct entry CFI_FORWARD_PROTO
lkdtm: Calling matched prototype ...
lkdtm: Calling mismatched prototype ...
CFI failure at lkdtm_indirect_call+0x40/0x4c (target: 0x0; expected type: 0x00000000)
WARNING: CPU: 1 PID: 112 at lkdtm_indirect_call+0x40/0x4c
CPU: 1 PID: 112 Comm: sh Not tainted 6.8.0-rc1+ #150
Hardware name: ARM-Versatile Express
(...)
lkdtm: FAIL: survived mismatched prototype function call!
lkdtm: Unexpected! This kernel (6.8.0-rc1+ armv7l) was built with CONFIG_CFI_CLANG=y

As you can see the LKDTM test fails, but I expect that this would be
expected behaviour in the permissive mode.

We are currently not implementing target and type for the CFI
breakpoint as this requires additional operand bundling compiler
extensions.

CPUs without breakpoint support cannot handle breakpoints naturally,
in these cases the permissive mode will not work, CFI will fall over
on an undefined instruction:

Internal error: Oops - undefined instruction: 0 [#1] PREEMPT ARM
CPU: 0 PID: 186 Comm: ash Tainted: G W 6.9.0-rc1+ #7
Hardware name: Gemini (Device Tree)
PC is at lkdtm_indirect_call+0x38/0x4c
LR is at lkdtm_CFI_FORWARD_PROTO+0x30/0x6c

This is reasonable I think: it's the best CFI can do to ascertain
the the control flow is not broken on these CPUs.

Reviewed-by: Kees Cook <keescook@chromium.org>
Tested-by: Kees Cook <keescook@chromium.org>
Reviewed-by: Sami Tolvanen <samitolvanen@google.com>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>

authored by

Linus Walleij and committed by
Russell King (Oracle)
c3f89986 7339fb11

+36
+1
arch/arm/include/asm/hw_breakpoint.h
··· 84 84 #define ARM_DSCR_MOE(x) ((x >> 2) & 0xf) 85 85 #define ARM_ENTRY_BREAKPOINT 0x1 86 86 #define ARM_ENTRY_ASYNC_WATCHPOINT 0x2 87 + #define ARM_ENTRY_CFI_BREAKPOINT 0x3 87 88 #define ARM_ENTRY_SYNC_WATCHPOINT 0xa 88 89 89 90 /* DSCR monitor/halting bits. */
+35
arch/arm/kernel/hw_breakpoint.c
··· 17 17 #include <linux/perf_event.h> 18 18 #include <linux/hw_breakpoint.h> 19 19 #include <linux/smp.h> 20 + #include <linux/cfi.h> 20 21 #include <linux/cpu_pm.h> 21 22 #include <linux/coresight.h> 22 23 ··· 904 903 watchpoint_single_step_handler(addr); 905 904 } 906 905 906 + #ifdef CONFIG_CFI_CLANG 907 + static void hw_breakpoint_cfi_handler(struct pt_regs *regs) 908 + { 909 + /* 910 + * TODO: implementing target and type to pass to CFI using the more 911 + * elaborate report_cfi_failure() requires compiler work. To be able 912 + * to properly extract target information the compiler needs to 913 + * emit a stable instructions sequence for the CFI checks so we can 914 + * decode the instructions preceding the trap and figure out which 915 + * registers were used. 916 + */ 917 + 918 + switch (report_cfi_failure_noaddr(regs, instruction_pointer(regs))) { 919 + case BUG_TRAP_TYPE_BUG: 920 + die("Oops - CFI", regs, 0); 921 + break; 922 + case BUG_TRAP_TYPE_WARN: 923 + /* Skip the breaking instruction */ 924 + instruction_pointer(regs) += 4; 925 + break; 926 + default: 927 + die("Unknown CFI error", regs, 0); 928 + break; 929 + } 930 + } 931 + #else 932 + static void hw_breakpoint_cfi_handler(struct pt_regs *regs) 933 + { 934 + } 935 + #endif 936 + 907 937 /* 908 938 * Called from either the Data Abort Handler [watchpoint] or the 909 939 * Prefetch Abort Handler [breakpoint] with interrupts disabled. ··· 963 931 fallthrough; 964 932 case ARM_ENTRY_SYNC_WATCHPOINT: 965 933 watchpoint_handler(addr, fsr, regs); 934 + break; 935 + case ARM_ENTRY_CFI_BREAKPOINT: 936 + hw_breakpoint_cfi_handler(regs); 966 937 break; 967 938 default: 968 939 ret = 1; /* Unhandled fault. */