This function checks if the given address range crosses frame boundary. It is based on the existing x86 algorithm, but implemented via stacktrace. This can be tested by USERCOPY_STACK_FRAME_FROM and USERCOPY_STACK_FRAME_TO in lkdtm. Signed-off-by: He Zhe <zhe.he@xxxxxxxxxxxxx> --- arch/arm64/Kconfig | 1 + arch/arm64/include/asm/thread_info.h | 12 +++++ arch/arm64/kernel/stacktrace.c | 76 ++++++++++++++++++++++++++-- 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 57c4c995965f..0f52a83d7771 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -165,6 +165,7 @@ config ARM64 select HAVE_ARCH_TRACEHOOK select HAVE_ARCH_TRANSPARENT_HUGEPAGE select HAVE_ARCH_VMAP_STACK + select HAVE_ARCH_WITHIN_STACK_FRAMES select HAVE_ARM_SMCCC select HAVE_ASM_MODVERSIONS select HAVE_EBPF_JIT diff --git a/arch/arm64/include/asm/thread_info.h b/arch/arm64/include/asm/thread_info.h index e1317b7c4525..b839ad9f2248 100644 --- a/arch/arm64/include/asm/thread_info.h +++ b/arch/arm64/include/asm/thread_info.h @@ -58,6 +58,18 @@ void arch_setup_new_exec(void); void arch_release_task_struct(struct task_struct *tsk); int arch_dup_task_struct(struct task_struct *dst, struct task_struct *src); +/* + * Walks up the stack frames to make sure that the specified object is + * entirely contained by a single stack frame. + * + * Returns: + * GOOD_FRAME if within a frame + * BAD_STACK if placed across a frame boundary (or outside stack) + * NOT_STACK unable to determine (no frame pointers, etc) + */ +int arch_within_stack_frames(const void * const stack, + const void * const stackend, + const void *obj, unsigned long len); #endif diff --git a/arch/arm64/kernel/stacktrace.c b/arch/arm64/kernel/stacktrace.c index e4103e085681..219b90c1de12 100644 --- a/arch/arm64/kernel/stacktrace.c +++ b/arch/arm64/kernel/stacktrace.c @@ -145,12 +145,17 @@ NOKPROBE_SYMBOL(unwind_frame); static void notrace walk_stackframe(struct task_struct *tsk, struct stackframe *frame, - bool (*fn)(void *, unsigned long), void *data) + stack_trace_consume_fn fn, void *data) { + struct frame_info fi; + while (1) { int ret; - if (!fn(data, frame->pc)) + fi.pc = frame->pc; + fi.fp = frame->fp; + fi.prev_fp = frame->prev_fp; + if (!fn(data, &fi)) break; ret = unwind_frame(tsk, frame); if (ret < 0) @@ -159,10 +164,10 @@ static void notrace walk_stackframe(struct task_struct *tsk, } NOKPROBE_SYMBOL(walk_stackframe); -static bool dump_backtrace_entry(void *arg, unsigned long where) +static bool dump_backtrace_entry(void *arg, struct frame_info *fi) { char *loglvl = arg; - printk("%s %pSb\n", loglvl, (void *)where); + printk("%s %pSb\n", loglvl, (void *)fi->pc); return true; } @@ -210,3 +215,66 @@ noinline notrace void arch_stack_walk(stack_trace_consume_fn consume_entry, walk_stackframe(task, &frame, consume_entry, cookie); } + +struct arch_stack_object { + unsigned long start; + unsigned long len; + int flag; +}; + +static bool arch_stack_object_check(void *data, struct frame_info *fi) +{ + struct arch_stack_object *obj = (struct arch_stack_object *)data; + + /* Skip the frame of arch_within_stack_frames itself */ + if (fi->prev_fp == 0) + return true; + + /* + * low ----------------------------------------------> high + * [saved bp][saved ip][args][local vars][saved bp][saved ip] + * ^----------------^ + * allow copies only within here + */ + if (obj->start + obj->len <= fi->fp) { + obj->flag = obj->start >= + fi->prev_fp + 2 * sizeof(void *) ? + GOOD_FRAME : BAD_STACK; + return false; + } else + return true; +} + +/* + * Walks up the stack frames to make sure that the specified object is + * entirely contained by a single stack frame. + * + * Returns: + * GOOD_FRAME if within a frame + * BAD_STACK if placed across a frame boundary (or outside stack) + * NOT_STACK unable to determine (no frame pointers, etc) + */ +int arch_within_stack_frames(const void * const stack, + const void * const stackend, + const void *obj, unsigned long len) +{ +#if defined(CONFIG_FRAME_POINTER) + struct arch_stack_object object; + struct pt_regs regs; + + if (__builtin_frame_address(1) == 0) + return NOT_STACK; + + object.start = (unsigned long)obj; + object.len = len; + object.flag = NOT_STACK; + + regs.regs[29] = (u64)__builtin_frame_address(1); + + arch_stack_walk(arch_stack_object_check, (void *)&object, NULL, ®s); + + return object.flag; +#else + return NOT_STACK; +#endif +} -- 2.25.1