Skip to content

Commit 7f7d3ea

Browse files
Merge patch series "riscv: KCFI support"
Sami Tolvanen <samitolvanen@google.com> says: This series adds KCFI support for RISC-V. KCFI is a fine-grained forward-edge control-flow integrity scheme supported in Clang >=16, which ensures indirect calls in instrumented code can only branch to functions whose type matches the function pointer type, thus making code reuse attacks more difficult. Patch 1 implements a pt_regs based syscall wrapper to address function pointer type mismatches in syscall handling. Patches 2 and 3 annotate indirectly called assembly functions with CFI types. Patch 4 implements error handling for indirect call checks. Patch 5 disables CFI for arch/riscv/purgatory. Patch 6 finally allows CONFIG_CFI_CLANG to be enabled for RISC-V. Note that Clang 16 has a generic architecture-agnostic KCFI implementation, which does work with the kernel, but doesn't produce a stable code sequence for indirect call checks, which means potential failures just trap and won't result in informative error messages. Clang 17 includes a RISC-V specific back-end implementation for KCFI, which emits a predictable code sequence for the checks and a .kcfi_traps section with locations of the traps, which patch 5 uses to produce more useful errors. The type mismatch fixes and annotations in the first three patches also become necessary in future if the kernel decides to support fine-grained CFI implemented using the hardware landing pad feature proposed in the in-progress Zicfisslp extension. Once the specification is ratified and hardware support emerges, implementing runtime patching support that replaces KCFI instrumentation with Zicfisslp landing pads might also be feasible (similarly to KCFI to FineIBT patching on x86_64), allowing distributions to ship a unified kernel binary for all devices. * b4-shazam-merge: riscv: Allow CONFIG_CFI_CLANG to be selected riscv/purgatory: Disable CFI riscv: Add CFI error handling riscv: Add ftrace_stub_graph riscv: Add types to indirectly called assembly functions riscv: Implement syscall wrappers Link: https://lore.kernel.org/r/20230710183544.999540-8-samitolvanen@google.com Signed-off-by: Palmer Dabbelt <palmer@rivosinc.com>
2 parents 9bdd924 + 74f8fc3 commit 7f7d3ea

File tree

14 files changed

+238
-12
lines changed

14 files changed

+238
-12
lines changed

arch/riscv/Kconfig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,22 @@ config RISCV
3535
select ARCH_HAS_SET_MEMORY if MMU
3636
select ARCH_HAS_STRICT_KERNEL_RWX if MMU && !XIP_KERNEL
3737
select ARCH_HAS_STRICT_MODULE_RWX if MMU && !XIP_KERNEL
38+
select ARCH_HAS_SYSCALL_WRAPPER
3839
select ARCH_HAS_TICK_BROADCAST if GENERIC_CLOCKEVENTS_BROADCAST
3940
select ARCH_HAS_UBSAN_SANITIZE_ALL
4041
select ARCH_HAS_VDSO_DATA
4142
select ARCH_OPTIONAL_KERNEL_RWX if ARCH_HAS_STRICT_KERNEL_RWX
4243
select ARCH_OPTIONAL_KERNEL_RWX_DEFAULT
4344
select ARCH_STACKWALK
4445
select ARCH_SUPPORTS_ATOMIC_RMW
46+
select ARCH_SUPPORTS_CFI_CLANG
4547
select ARCH_SUPPORTS_DEBUG_PAGEALLOC if MMU
4648
select ARCH_SUPPORTS_HUGETLBFS if MMU
4749
select ARCH_SUPPORTS_PAGE_TABLE_CHECK if MMU
4850
select ARCH_SUPPORTS_PER_VMA_LOCK if MMU
4951
select ARCH_USE_MEMTEST
5052
select ARCH_USE_QUEUED_RWLOCKS
53+
select ARCH_USES_CFI_TRAPS if CFI_CLANG
5154
select ARCH_WANT_DEFAULT_TOPDOWN_MMAP_LAYOUT if MMU
5255
select ARCH_WANT_FRAME_POINTERS
5356
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/include/asm/syscall.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ static inline int syscall_get_arch(struct task_struct *task)
7575
#endif
7676
}
7777

78-
typedef long (*syscall_t)(ulong, ulong, ulong, ulong, ulong, ulong, ulong);
78+
typedef long (*syscall_t)(const struct pt_regs *);
7979
static inline void syscall_handler(struct pt_regs *regs, ulong syscall)
8080
{
8181
syscall_t fn;
@@ -87,8 +87,7 @@ static inline void syscall_handler(struct pt_regs *regs, ulong syscall)
8787
#endif
8888
fn = sys_call_table[syscall];
8989

90-
regs->a0 = fn(regs->orig_a0, regs->a1, regs->a2,
91-
regs->a3, regs->a4, regs->a5, regs->a6);
90+
regs->a0 = fn(regs);
9291
}
9392

9493
static inline bool arch_syscall_is_vdso_sigreturn(struct pt_regs *regs)
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/* SPDX-License-Identifier: GPL-2.0 */
2+
/*
3+
* syscall_wrapper.h - riscv specific wrappers to syscall definitions
4+
*
5+
* Based on arch/arm64/include/syscall_wrapper.h
6+
*/
7+
8+
#ifndef __ASM_SYSCALL_WRAPPER_H
9+
#define __ASM_SYSCALL_WRAPPER_H
10+
11+
#include <asm/ptrace.h>
12+
13+
asmlinkage long __riscv_sys_ni_syscall(const struct pt_regs *);
14+
15+
#define SC_RISCV_REGS_TO_ARGS(x, ...) \
16+
__MAP(x,__SC_ARGS \
17+
,,regs->orig_a0,,regs->a1,,regs->a2 \
18+
,,regs->a3,,regs->a4,,regs->a5,,regs->a6)
19+
20+
#ifdef CONFIG_COMPAT
21+
22+
#define COMPAT_SYSCALL_DEFINEx(x, name, ...) \
23+
asmlinkage long __riscv_compat_sys##name(const struct pt_regs *regs); \
24+
ALLOW_ERROR_INJECTION(__riscv_compat_sys##name, ERRNO); \
25+
static long __se_compat_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \
26+
static inline long __do_compat_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)); \
27+
asmlinkage long __riscv_compat_sys##name(const struct pt_regs *regs) \
28+
{ \
29+
return __se_compat_sys##name(SC_RISCV_REGS_TO_ARGS(x,__VA_ARGS__)); \
30+
} \
31+
static long __se_compat_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \
32+
{ \
33+
return __do_compat_sys##name(__MAP(x,__SC_DELOUSE,__VA_ARGS__)); \
34+
} \
35+
static inline long __do_compat_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
36+
37+
#define COMPAT_SYSCALL_DEFINE0(sname) \
38+
asmlinkage long __riscv_compat_sys_##sname(const struct pt_regs *__unused); \
39+
ALLOW_ERROR_INJECTION(__riscv_compat_sys_##sname, ERRNO); \
40+
asmlinkage long __riscv_compat_sys_##sname(const struct pt_regs *__unused)
41+
42+
#define COND_SYSCALL_COMPAT(name) \
43+
asmlinkage long __weak __riscv_compat_sys_##name(const struct pt_regs *regs); \
44+
asmlinkage long __weak __riscv_compat_sys_##name(const struct pt_regs *regs) \
45+
{ \
46+
return sys_ni_syscall(); \
47+
}
48+
49+
#define COMPAT_SYS_NI(name) \
50+
SYSCALL_ALIAS(__riscv_compat_sys_##name, sys_ni_posix_timers);
51+
52+
#endif /* CONFIG_COMPAT */
53+
54+
#define __SYSCALL_DEFINEx(x, name, ...) \
55+
asmlinkage long __riscv_sys##name(const struct pt_regs *regs); \
56+
ALLOW_ERROR_INJECTION(__riscv_sys##name, ERRNO); \
57+
static long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \
58+
static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)); \
59+
asmlinkage long __riscv_sys##name(const struct pt_regs *regs) \
60+
{ \
61+
return __se_sys##name(SC_RISCV_REGS_TO_ARGS(x,__VA_ARGS__)); \
62+
} \
63+
static long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \
64+
{ \
65+
long ret = __do_sys##name(__MAP(x,__SC_CAST,__VA_ARGS__)); \
66+
__MAP(x,__SC_TEST,__VA_ARGS__); \
67+
__PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__)); \
68+
return ret; \
69+
} \
70+
static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
71+
72+
#define SYSCALL_DEFINE0(sname) \
73+
SYSCALL_METADATA(_##sname, 0); \
74+
asmlinkage long __riscv_sys_##sname(const struct pt_regs *__unused); \
75+
ALLOW_ERROR_INJECTION(__riscv_sys_##sname, ERRNO); \
76+
asmlinkage long __riscv_sys_##sname(const struct pt_regs *__unused)
77+
78+
#define COND_SYSCALL(name) \
79+
asmlinkage long __weak __riscv_sys_##name(const struct pt_regs *regs); \
80+
asmlinkage long __weak __riscv_sys_##name(const struct pt_regs *regs) \
81+
{ \
82+
return sys_ni_syscall(); \
83+
}
84+
85+
#define SYS_NI(name) SYSCALL_ALIAS(__riscv_sys_##name, sys_ni_posix_timers);
86+
87+
#endif /* __ASM_SYSCALL_WRAPPER_H */

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/compat_syscall_table.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,15 @@
99
#include <asm/syscall.h>
1010

1111
#undef __SYSCALL
12-
#define __SYSCALL(nr, call) [nr] = (call),
12+
#define __SYSCALL(nr, call) asmlinkage long __riscv_##call(const struct pt_regs *);
13+
#include <asm/unistd.h>
14+
15+
#undef __SYSCALL
16+
#define __SYSCALL(nr, call) [nr] = __riscv_##call,
1317

1418
asmlinkage long compat_sys_rt_sigreturn(void);
1519

1620
void * const compat_sys_call_table[__NR_syscalls] = {
17-
[0 ... __NR_syscalls - 1] = sys_ni_syscall,
21+
[0 ... __NR_syscalls - 1] = __riscv_sys_ni_syscall,
1822
#include <asm/unistd.h>
1923
};

arch/riscv/kernel/mcount.S

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include <linux/init.h>
55
#include <linux/linkage.h>
6+
#include <linux/cfi_types.h>
67
#include <asm/asm.h>
78
#include <asm/csr.h>
89
#include <asm/unistd.h>
@@ -47,15 +48,19 @@
4748
addi sp, sp, 4*SZREG
4849
.endm
4950

50-
ENTRY(ftrace_stub)
51+
SYM_TYPED_FUNC_START(ftrace_stub)
5152
#ifdef CONFIG_DYNAMIC_FTRACE
5253
.global MCOUNT_NAME
5354
.set MCOUNT_NAME, ftrace_stub
5455
#endif
5556
ret
56-
ENDPROC(ftrace_stub)
57+
SYM_FUNC_END(ftrace_stub)
5758

5859
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
60+
SYM_TYPED_FUNC_START(ftrace_stub_graph)
61+
ret
62+
SYM_FUNC_END(ftrace_stub_graph)
63+
5964
ENTRY(return_to_handler)
6065
/*
6166
* On implementing the frame point test, the ideal way is to compare the

arch/riscv/kernel/suspend_entry.S

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
#include <linux/linkage.h>
8+
#include <linux/cfi_types.h>
89
#include <asm/asm.h>
910
#include <asm/asm-offsets.h>
1011
#include <asm/assembler.h>
@@ -58,7 +59,7 @@ ENTRY(__cpu_suspend_enter)
5859
ret
5960
END(__cpu_suspend_enter)
6061

61-
ENTRY(__cpu_resume_enter)
62+
SYM_TYPED_FUNC_START(__cpu_resume_enter)
6263
/* Load the global pointer */
6364
.option push
6465
.option norelax
@@ -94,4 +95,4 @@ ENTRY(__cpu_resume_enter)
9495

9596
/* Return to C code */
9697
ret
97-
END(__cpu_resume_enter)
98+
SYM_FUNC_END(__cpu_resume_enter)

0 commit comments

Comments
 (0)