Skip to content

Commit 31da94c

Browse files
Tong Tiangenpalmer-dabbelt
authored andcommitted
riscv: add VMAP_STACK overflow detection
This patch adds stack overflow detection to riscv, usable when CONFIG_VMAP_STACK=y. Overflow is detected in kernel exception entry(kernel/entry.S), if the kernel stack is overflow and been detected, the overflow handler is invoked on a per-cpu overflow stack. This approach preserves GPRs and the original exception information. The overflow detect is performed before any attempt is made to access the stack and the principle of stack overflow detection: kernel stacks are aligned to double their size, enabling overflow to be detected with a single bit test. For example, a 16K stack is aligned to 32K, ensuring that bit 14 of the SP must be zero. On an overflow (or underflow), this bit is flipped. Thus, overflow (of less than the size of the stack) can be detected by testing whether this bit is set. This gives us a useful error message on stack overflow, as can be trigger with the LKDTM overflow test: [ 388.053267] lkdtm: Performing direct entry EXHAUST_STACK [ 388.053663] lkdtm: Calling function with 1024 frame size to depth 32 ... [ 388.054016] lkdtm: loop 32/32 ... [ 388.054186] lkdtm: loop 31/32 ... [ 388.054491] lkdtm: loop 30/32 ... [ 388.054672] lkdtm: loop 29/32 ... [ 388.054859] lkdtm: loop 28/32 ... [ 388.055010] lkdtm: loop 27/32 ... [ 388.055163] lkdtm: loop 26/32 ... [ 388.055309] lkdtm: loop 25/32 ... [ 388.055481] lkdtm: loop 24/32 ... [ 388.055653] lkdtm: loop 23/32 ... [ 388.055837] lkdtm: loop 22/32 ... [ 388.056015] lkdtm: loop 21/32 ... [ 388.056188] lkdtm: loop 20/32 ... [ 388.058145] Insufficient stack space to handle exception! [ 388.058153] Task stack: [0xffffffd014260000..0xffffffd014264000] [ 388.058160] Overflow stack: [0xffffffe1f8d2c220..0xffffffe1f8d2d220] [ 388.058168] CPU: 0 PID: 89 Comm: bash Not tainted 5.12.0-rc8-dirty #90 [ 388.058175] Hardware name: riscv-virtio,qemu (DT) [ 388.058187] epc : number+0x32/0x2c0 [ 388.058247] ra : vsnprintf+0x2ae/0x3f0 [ 388.058255] epc : ffffffe0002d38f6 ra : ffffffe0002d814e sp : ffffffd01425ffc0 [ 388.058263] gp : ffffffe0012e4010 tp : ffffffe08014da00 t0 : ffffffd0142606e8 [ 388.058271] t1 : 0000000000000000 t2 : 0000000000000000 s0 : ffffffd014260070 [ 388.058303] s1 : ffffffd014260158 a0 : ffffffd01426015e a1 : ffffffd014260158 [ 388.058311] a2 : 0000000000000013 a3 : ffff0a01ffffff10 a4 : ffffffe000c398e0 [ 388.058319] a5 : 511b02ec65f3e300 a6 : 0000000000a1749a a7 : 0000000000000000 [ 388.058327] s2 : ffffffff000000ff s3 : 00000000ffff0a01 s4 : ffffffe0012e50a8 [ 388.058335] s5 : 0000000000ffff0a s6 : ffffffe0012e50a8 s7 : ffffffe000da1cc0 [ 388.058343] s8 : ffffffffffffffff s9 : ffffffd0142602b0 s10: ffffffd0142602a8 [ 388.058351] s11: ffffffd01426015e t3 : 00000000000f0000 t4 : ffffffffffffffff [ 388.058359] t5 : 000000000000002f t6 : ffffffd014260158 [ 388.058366] status: 0000000000000100 badaddr: ffffffd01425fff8 cause: 000000000000000f [ 388.058374] Kernel panic - not syncing: Kernel stack overflow [ 388.058381] CPU: 0 PID: 89 Comm: bash Not tainted 5.12.0-rc8-dirty #90 [ 388.058387] Hardware name: riscv-virtio,qemu (DT) [ 388.058393] Call Trace: [ 388.058400] [<ffffffe000004944>] walk_stackframe+0x0/0xce [ 388.058406] [<ffffffe0006f0b28>] dump_backtrace+0x38/0x46 [ 388.058412] [<ffffffe0006f0b46>] show_stack+0x10/0x18 [ 388.058418] [<ffffffe0006f3690>] dump_stack+0x74/0x8e [ 388.058424] [<ffffffe0006f0d52>] panic+0xfc/0x2b2 [ 388.058430] [<ffffffe0006f0acc>] print_trace_address+0x0/0x24 [ 388.058436] [<ffffffe0002d814e>] vsnprintf+0x2ae/0x3f0 [ 388.058956] SMP: stopping secondary CPUs Signed-off-by: Tong Tiangen <[email protected]> Reviewed-by: Kefeng Wang <[email protected]> Signed-off-by: Palmer Dabbelt <[email protected]>
1 parent 70eee55 commit 31da94c

File tree

6 files changed

+163
-1
lines changed

6 files changed

+163
-1
lines changed

arch/riscv/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ config RISCV
7070
select HAVE_ARCH_MMAP_RND_BITS if MMU
7171
select HAVE_ARCH_SECCOMP_FILTER
7272
select HAVE_ARCH_TRACEHOOK
73+
select HAVE_ARCH_VMAP_STACK if MMU && 64BIT
7374
select HAVE_ASM_MODVERSIONS
7475
select HAVE_CONTEXT_TRACKING
7576
select HAVE_DEBUG_KMEMLEAK

arch/riscv/include/asm/asm-prototypes.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,7 @@ DECLARE_DO_ERROR_INFO(do_trap_ecall_s);
2525
DECLARE_DO_ERROR_INFO(do_trap_ecall_m);
2626
DECLARE_DO_ERROR_INFO(do_trap_break);
2727

28+
asmlinkage unsigned long get_overflow_stack(void);
29+
asmlinkage void handle_bad_stack(struct pt_regs *regs);
30+
2831
#endif /* _ASM_RISCV_PROTOTYPES_H */

arch/riscv/include/asm/thread_info.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,21 @@
1919
#endif
2020
#define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER)
2121

22+
/*
23+
* By aligning VMAP'd stacks to 2 * THREAD_SIZE, we can detect overflow by
24+
* checking sp & (1 << THREAD_SHIFT), which we can do cheaply in the entry
25+
* assembly.
26+
*/
27+
#ifdef CONFIG_VMAP_STACK
28+
#define THREAD_ALIGN (2 * THREAD_SIZE)
29+
#else
30+
#define THREAD_ALIGN THREAD_SIZE
31+
#endif
32+
33+
#define THREAD_SHIFT (PAGE_SHIFT + THREAD_SIZE_ORDER)
34+
#define OVERFLOW_STACK_SIZE SZ_4K
35+
#define SHADOW_OVERFLOW_STACK_SIZE (1024)
36+
2237
#ifndef __ASSEMBLY__
2338

2439
#include <asm/processor.h>

arch/riscv/kernel/entry.S

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ ENTRY(handle_exception)
3030
_restore_kernel_tpsp:
3131
csrr tp, CSR_SCRATCH
3232
REG_S sp, TASK_TI_KERNEL_SP(tp)
33+
34+
#ifdef CONFIG_VMAP_STACK
35+
addi sp, sp, -(PT_SIZE_ON_STACK)
36+
srli sp, sp, THREAD_SHIFT
37+
andi sp, sp, 0x1
38+
bnez sp, handle_kernel_stack_overflow
39+
REG_L sp, TASK_TI_KERNEL_SP(tp)
40+
#endif
41+
3342
_save_context:
3443
REG_S sp, TASK_TI_USER_SP(tp)
3544
REG_L sp, TASK_TI_KERNEL_SP(tp)
@@ -376,6 +385,105 @@ handle_syscall_trace_exit:
376385
call do_syscall_trace_exit
377386
j ret_from_exception
378387

388+
#ifdef CONFIG_VMAP_STACK
389+
handle_kernel_stack_overflow:
390+
la sp, shadow_stack
391+
addi sp, sp, SHADOW_OVERFLOW_STACK_SIZE
392+
393+
//save caller register to shadow stack
394+
addi sp, sp, -(PT_SIZE_ON_STACK)
395+
REG_S x1, PT_RA(sp)
396+
REG_S x5, PT_T0(sp)
397+
REG_S x6, PT_T1(sp)
398+
REG_S x7, PT_T2(sp)
399+
REG_S x10, PT_A0(sp)
400+
REG_S x11, PT_A1(sp)
401+
REG_S x12, PT_A2(sp)
402+
REG_S x13, PT_A3(sp)
403+
REG_S x14, PT_A4(sp)
404+
REG_S x15, PT_A5(sp)
405+
REG_S x16, PT_A6(sp)
406+
REG_S x17, PT_A7(sp)
407+
REG_S x28, PT_T3(sp)
408+
REG_S x29, PT_T4(sp)
409+
REG_S x30, PT_T5(sp)
410+
REG_S x31, PT_T6(sp)
411+
412+
la ra, restore_caller_reg
413+
tail get_overflow_stack
414+
415+
restore_caller_reg:
416+
//save per-cpu overflow stack
417+
REG_S a0, -8(sp)
418+
//restore caller register from shadow_stack
419+
REG_L x1, PT_RA(sp)
420+
REG_L x5, PT_T0(sp)
421+
REG_L x6, PT_T1(sp)
422+
REG_L x7, PT_T2(sp)
423+
REG_L x10, PT_A0(sp)
424+
REG_L x11, PT_A1(sp)
425+
REG_L x12, PT_A2(sp)
426+
REG_L x13, PT_A3(sp)
427+
REG_L x14, PT_A4(sp)
428+
REG_L x15, PT_A5(sp)
429+
REG_L x16, PT_A6(sp)
430+
REG_L x17, PT_A7(sp)
431+
REG_L x28, PT_T3(sp)
432+
REG_L x29, PT_T4(sp)
433+
REG_L x30, PT_T5(sp)
434+
REG_L x31, PT_T6(sp)
435+
436+
//load per-cpu overflow stack
437+
REG_L sp, -8(sp)
438+
addi sp, sp, -(PT_SIZE_ON_STACK)
439+
440+
//save context to overflow stack
441+
REG_S x1, PT_RA(sp)
442+
REG_S x3, PT_GP(sp)
443+
REG_S x5, PT_T0(sp)
444+
REG_S x6, PT_T1(sp)
445+
REG_S x7, PT_T2(sp)
446+
REG_S x8, PT_S0(sp)
447+
REG_S x9, PT_S1(sp)
448+
REG_S x10, PT_A0(sp)
449+
REG_S x11, PT_A1(sp)
450+
REG_S x12, PT_A2(sp)
451+
REG_S x13, PT_A3(sp)
452+
REG_S x14, PT_A4(sp)
453+
REG_S x15, PT_A5(sp)
454+
REG_S x16, PT_A6(sp)
455+
REG_S x17, PT_A7(sp)
456+
REG_S x18, PT_S2(sp)
457+
REG_S x19, PT_S3(sp)
458+
REG_S x20, PT_S4(sp)
459+
REG_S x21, PT_S5(sp)
460+
REG_S x22, PT_S6(sp)
461+
REG_S x23, PT_S7(sp)
462+
REG_S x24, PT_S8(sp)
463+
REG_S x25, PT_S9(sp)
464+
REG_S x26, PT_S10(sp)
465+
REG_S x27, PT_S11(sp)
466+
REG_S x28, PT_T3(sp)
467+
REG_S x29, PT_T4(sp)
468+
REG_S x30, PT_T5(sp)
469+
REG_S x31, PT_T6(sp)
470+
471+
REG_L s0, TASK_TI_KERNEL_SP(tp)
472+
csrr s1, CSR_STATUS
473+
csrr s2, CSR_EPC
474+
csrr s3, CSR_TVAL
475+
csrr s4, CSR_CAUSE
476+
csrr s5, CSR_SCRATCH
477+
REG_S s0, PT_SP(sp)
478+
REG_S s1, PT_STATUS(sp)
479+
REG_S s2, PT_EPC(sp)
480+
REG_S s3, PT_BADADDR(sp)
481+
REG_S s4, PT_CAUSE(sp)
482+
REG_S s5, PT_TP(sp)
483+
move a0, sp
484+
tail handle_bad_stack
485+
#endif
486+
379487
END(handle_exception)
380488

381489
ENTRY(ret_from_fork)

arch/riscv/kernel/traps.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,38 @@ int is_valid_bugaddr(unsigned long pc)
198198
void __init trap_init(void)
199199
{
200200
}
201+
202+
#ifdef CONFIG_VMAP_STACK
203+
static DEFINE_PER_CPU(unsigned long [OVERFLOW_STACK_SIZE/sizeof(long)],
204+
overflow_stack)__aligned(16);
205+
/*
206+
* shadow stack, handled_ kernel_ stack_ overflow(in kernel/entry.S) is used
207+
* to get per-cpu overflow stack(get_overflow_stack).
208+
*/
209+
long shadow_stack[SHADOW_OVERFLOW_STACK_SIZE/sizeof(long)];
210+
asmlinkage unsigned long get_overflow_stack(void)
211+
{
212+
return (unsigned long)this_cpu_ptr(overflow_stack) +
213+
OVERFLOW_STACK_SIZE;
214+
}
215+
216+
asmlinkage void handle_bad_stack(struct pt_regs *regs)
217+
{
218+
unsigned long tsk_stk = (unsigned long)current->stack;
219+
unsigned long ovf_stk = (unsigned long)this_cpu_ptr(overflow_stack);
220+
221+
console_verbose();
222+
223+
pr_emerg("Insufficient stack space to handle exception!\n");
224+
pr_emerg("Task stack: [0x%016lx..0x%016lx]\n",
225+
tsk_stk, tsk_stk + THREAD_SIZE);
226+
pr_emerg("Overflow stack: [0x%016lx..0x%016lx]\n",
227+
ovf_stk, ovf_stk + OVERFLOW_STACK_SIZE);
228+
229+
__show_regs(regs);
230+
panic("Kernel stack overflow");
231+
232+
for (;;)
233+
wait_for_interrupt();
234+
}
235+
#endif

arch/riscv/kernel/vmlinux.lds.S

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ SECTIONS
117117
. = ALIGN(SECTION_ALIGN);
118118
_data = .;
119119

120-
RW_DATA(L1_CACHE_BYTES, PAGE_SIZE, THREAD_SIZE)
120+
RW_DATA(L1_CACHE_BYTES, PAGE_SIZE, THREAD_ALIGN)
121121
.sdata : {
122122
__global_pointer$ = . + 0x800;
123123
*(.sdata*)

0 commit comments

Comments
 (0)