Skip to content

Commit af0ead4

Browse files
samitolvanenpalmer-dabbelt
authored andcommitted
riscv: Add CFI error handling
With CONFIG_CFI_CLANG, the compiler injects a type preamble immediately before each function and a check to validate the target function type before indirect calls: ; type preamble .word <id> function: ... ; indirect call check lw t1, -4(a0) lui t2, <hi20> addiw t2, t2, <lo12> beq t1, t2, .Ltmp0 ebreak .Ltmp0: jarl a0 Implement error handling code for the ebreak traps emitted for the checks. This produces the following oops on a CFI failure (generated using lkdtm): [ 21.177245] CFI failure at lkdtm_indirect_call+0x22/0x32 [lkdtm] (target: lkdtm_increment_int+0x0/0x18 [lkdtm]; expected type: 0x3ad55aca) [ 21.178483] Kernel BUG [#1] [ 21.178671] Modules linked in: lkdtm [ 21.179037] CPU: 1 PID: 104 Comm: sh Not tainted 6.3.0-rc6-00037-g37d5ec6297ab #1 [ 21.179511] Hardware name: riscv-virtio,qemu (DT) [ 21.179818] epc : lkdtm_indirect_call+0x22/0x32 [lkdtm] [ 21.180106] ra : lkdtm_CFI_FORWARD_PROTO+0x48/0x7c [lkdtm] [ 21.180426] epc : ffffffff01387092 ra : ffffffff01386f14 sp : ff20000000453cf0 [ 21.180792] gp : ffffffff81308c38 tp : ff6000000243f080 t0 : ff20000000453b78 [ 21.181157] t1 : 000000003ad55aca t2 : 000000007e0c52a5 s0 : ff20000000453d00 [ 21.181506] s1 : 0000000000000001 a0 : ffffffff0138d170 a1 : ffffffff013870bc [ 21.181819] a2 : b5fea48dd89aa700 a3 : 0000000000000001 a4 : 0000000000000fff [ 21.182169] a5 : 0000000000000004 a6 : 00000000000000b7 a7 : 0000000000000000 [ 21.182591] s2 : ff20000000453e78 s3 : ffffffffffffffea s4 : 0000000000000012 [ 21.183001] s5 : ff600000023c7000 s6 : 0000000000000006 s7 : ffffffff013882a0 [ 21.183653] s8 : 0000000000000008 s9 : 0000000000000002 s10: ffffffff0138d878 [ 21.184245] s11: ffffffff0138d878 t3 : 0000000000000003 t4 : 0000000000000000 [ 21.184591] t5 : ffffffff8133df08 t6 : ffffffff8133df07 [ 21.184858] status: 0000000000000120 badaddr: 0000000000000000 cause: 0000000000000003 [ 21.185415] [<ffffffff01387092>] lkdtm_indirect_call+0x22/0x32 [lkdtm] [ 21.185772] [<ffffffff01386f14>] lkdtm_CFI_FORWARD_PROTO+0x48/0x7c [lkdtm] [ 21.186093] [<ffffffff01383552>] lkdtm_do_action+0x22/0x34 [lkdtm] [ 21.186445] [<ffffffff0138350c>] direct_entry+0x128/0x13a [lkdtm] [ 21.186817] [<ffffffff8033ed8c>] full_proxy_write+0x58/0xb2 [ 21.187352] [<ffffffff801d4fe8>] vfs_write+0x14c/0x33a [ 21.187644] [<ffffffff801d5328>] ksys_write+0x64/0xd4 [ 21.187832] [<ffffffff801d53a6>] sys_write+0xe/0x1a [ 21.188171] [<ffffffff80003996>] ret_from_syscall+0x0/0x2 [ 21.188595] Code: 0513 0f65 a303 ffc5 53b7 7e0c 839b 2a53 0363 0073 (9002) 9582 [ 21.189178] ---[ end trace 0000000000000000 ]--- [ 21.189590] Kernel panic - not syncing: Fatal exception Reviewed-by: Kees Cook <keescook@chromium.org> Reviewed-by: Conor Dooley <conor.dooley@microchip.com> # ISA bits Tested-by: Nathan Chancellor <nathan@kernel.org> Signed-off-by: Sami Tolvanen <samitolvanen@google.com> Link: https://lore.kernel.org/r/20230710183544.999540-12-samitolvanen@google.com Signed-off-by: Palmer Dabbelt <palmer@rivosinc.com>
1 parent f3a0c23 commit af0ead4

File tree

6 files changed

+115
-1
lines changed

6 files changed

+115
-1
lines changed

arch/riscv/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ config RISCV
4949
select ARCH_SUPPORTS_PER_VMA_LOCK if MMU
5050
select ARCH_USE_MEMTEST
5151
select ARCH_USE_QUEUED_RWLOCKS
52+
select ARCH_USES_CFI_TRAPS if CFI_CLANG
5253
select ARCH_WANT_DEFAULT_TOPDOWN_MMAP_LAYOUT if MMU
5354
select ARCH_WANT_FRAME_POINTERS
5455
select ARCH_WANT_GENERAL_HUGETLB if !RISCV_ISA_SVNAPOT

arch/riscv/include/asm/cfi.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/* SPDX-License-Identifier: GPL-2.0 */
2+
#ifndef _ASM_RISCV_CFI_H
3+
#define _ASM_RISCV_CFI_H
4+
5+
/*
6+
* Clang Control Flow Integrity (CFI) support.
7+
*
8+
* Copyright (C) 2023 Google LLC
9+
*/
10+
11+
#include <linux/cfi.h>
12+
13+
#ifdef CONFIG_CFI_CLANG
14+
enum bug_trap_type handle_cfi_failure(struct pt_regs *regs);
15+
#else
16+
static inline enum bug_trap_type handle_cfi_failure(struct pt_regs *regs)
17+
{
18+
return BUG_TRAP_TYPE_NONE;
19+
}
20+
#endif /* CONFIG_CFI_CLANG */
21+
22+
#endif /* _ASM_RISCV_CFI_H */

arch/riscv/include/asm/insn.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
#define RVG_RS1_OPOFF 15
6464
#define RVG_RS2_OPOFF 20
6565
#define RVG_RD_OPOFF 7
66+
#define RVG_RS1_MASK GENMASK(4, 0)
6667
#define RVG_RD_MASK GENMASK(4, 0)
6768

6869
/* The bit field of immediate value in RVC J instruction */
@@ -129,6 +130,7 @@
129130
#define RVC_C2_RS1_OPOFF 7
130131
#define RVC_C2_RS2_OPOFF 2
131132
#define RVC_C2_RD_OPOFF 7
133+
#define RVC_C2_RS1_MASK GENMASK(4, 0)
132134

133135
/* parts of opcode for RVG*/
134136
#define RVG_OPCODE_FENCE 0x0f
@@ -278,6 +280,10 @@ static __always_inline bool riscv_insn_is_branch(u32 code)
278280
#define RV_X(X, s, mask) (((X) >> (s)) & (mask))
279281
#define RVC_X(X, s, mask) RV_X(X, s, mask)
280282

283+
#define RV_EXTRACT_RS1_REG(x) \
284+
({typeof(x) x_ = (x); \
285+
(RV_X(x_, RVG_RS1_OPOFF, RVG_RS1_MASK)); })
286+
281287
#define RV_EXTRACT_RD_REG(x) \
282288
({typeof(x) x_ = (x); \
283289
(RV_X(x_, RVG_RD_OPOFF, RVG_RD_MASK)); })
@@ -305,6 +311,10 @@ static __always_inline bool riscv_insn_is_branch(u32 code)
305311
(RV_X(x_, RV_B_IMM_11_OPOFF, RV_B_IMM_11_MASK) << RV_B_IMM_11_OFF) | \
306312
(RV_IMM_SIGN(x_) << RV_B_IMM_SIGN_OFF); })
307313

314+
#define RVC_EXTRACT_C2_RS1_REG(x) \
315+
({typeof(x) x_ = (x); \
316+
(RV_X(x_, RVC_C2_RS1_OPOFF, RVC_C2_RS1_MASK)); })
317+
308318
#define RVC_EXTRACT_JTYPE_IMM(x) \
309319
({typeof(x) x_ = (x); \
310320
(RVC_X(x_, RVC_J_IMM_3_1_OPOFF, RVC_J_IMM_3_1_MASK) << RVC_J_IMM_3_1_OFF) | \

arch/riscv/kernel/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ obj-$(CONFIG_CRASH_CORE) += crash_core.o
9191

9292
obj-$(CONFIG_JUMP_LABEL) += jump_label.o
9393

94+
obj-$(CONFIG_CFI_CLANG) += cfi.o
95+
9496
obj-$(CONFIG_EFI) += efi.o
9597
obj-$(CONFIG_COMPAT) += compat_syscall_table.o
9698
obj-$(CONFIG_COMPAT) += compat_signal.o

arch/riscv/kernel/cfi.c

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* Clang Control Flow Integrity (CFI) support.
4+
*
5+
* Copyright (C) 2023 Google LLC
6+
*/
7+
#include <asm/cfi.h>
8+
#include <asm/insn.h>
9+
10+
/*
11+
* Returns the target address and the expected type when regs->epc points
12+
* to a compiler-generated CFI trap.
13+
*/
14+
static bool decode_cfi_insn(struct pt_regs *regs, unsigned long *target,
15+
u32 *type)
16+
{
17+
unsigned long *regs_ptr = (unsigned long *)regs;
18+
int rs1_num;
19+
u32 insn;
20+
21+
*target = *type = 0;
22+
23+
/*
24+
* The compiler generates the following instruction sequence
25+
* for indirect call checks:
26+
*
27+
*   lw t1, -4(<reg>)
28+
* lui t2, <hi20>
29+
* addiw t2, t2, <lo12>
30+
* beq t1, t2, .Ltmp1
31+
* ebreak ; <- regs->epc
32+
* .Ltmp1:
33+
* jalr <reg>
34+
*
35+
* We can read the expected type and the target address from the
36+
* registers passed to the beq/jalr instructions.
37+
*/
38+
if (get_kernel_nofault(insn, (void *)regs->epc - 4))
39+
return false;
40+
if (!riscv_insn_is_beq(insn))
41+
return false;
42+
43+
*type = (u32)regs_ptr[RV_EXTRACT_RS1_REG(insn)];
44+
45+
if (get_kernel_nofault(insn, (void *)regs->epc) ||
46+
get_kernel_nofault(insn, (void *)regs->epc + GET_INSN_LENGTH(insn)))
47+
return false;
48+
49+
if (riscv_insn_is_jalr(insn))
50+
rs1_num = RV_EXTRACT_RS1_REG(insn);
51+
else if (riscv_insn_is_c_jalr(insn))
52+
rs1_num = RVC_EXTRACT_C2_RS1_REG(insn);
53+
else
54+
return false;
55+
56+
*target = regs_ptr[rs1_num];
57+
58+
return true;
59+
}
60+
61+
/*
62+
* Checks if the ebreak trap is because of a CFI failure, and handles the trap
63+
* if needed. Returns a bug_trap_type value similarly to report_bug.
64+
*/
65+
enum bug_trap_type handle_cfi_failure(struct pt_regs *regs)
66+
{
67+
unsigned long target;
68+
u32 type;
69+
70+
if (!is_cfi_trap(regs->epc))
71+
return BUG_TRAP_TYPE_NONE;
72+
73+
if (!decode_cfi_insn(regs, &target, &type))
74+
return report_cfi_failure_noaddr(regs, regs->epc);
75+
76+
return report_cfi_failure(regs, regs->epc, &target, type);
77+
}

arch/riscv/kernel/traps.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
#include <asm/asm-prototypes.h>
2323
#include <asm/bug.h>
24+
#include <asm/cfi.h>
2425
#include <asm/csr.h>
2526
#include <asm/processor.h>
2627
#include <asm/ptrace.h>
@@ -271,7 +272,8 @@ void handle_break(struct pt_regs *regs)
271272
== NOTIFY_STOP)
272273
return;
273274
#endif
274-
else if (report_bug(regs->epc, regs) == BUG_TRAP_TYPE_WARN)
275+
else if (report_bug(regs->epc, regs) == BUG_TRAP_TYPE_WARN ||
276+
handle_cfi_failure(regs) == BUG_TRAP_TYPE_WARN)
275277
regs->epc += get_break_insn_length(regs->epc);
276278
else
277279
die(regs, "Kernel BUG");

0 commit comments

Comments
 (0)