diff options
Diffstat (limited to 'arch/arm/kernel/stacktrace.c')
| -rw-r--r-- | arch/arm/kernel/stacktrace.c | 84 | 
1 files changed, 63 insertions, 21 deletions
diff --git a/arch/arm/kernel/stacktrace.c b/arch/arm/kernel/stacktrace.c index d0fa2037460a..85443b5d1922 100644 --- a/arch/arm/kernel/stacktrace.c +++ b/arch/arm/kernel/stacktrace.c @@ -9,6 +9,8 @@  #include <asm/stacktrace.h>  #include <asm/traps.h> +#include "reboot.h" +  #if defined(CONFIG_FRAME_POINTER) && !defined(CONFIG_ARM_UNWIND)  /*   * Unwind the current stack frame and store the new register values in the @@ -39,29 +41,74 @@   * Note that with framepointer enabled, even the leaf functions have the same   * prologue and epilogue, therefore we can ignore the LR value in this case.   */ -int notrace unwind_frame(struct stackframe *frame) + +extern unsigned long call_with_stack_end; + +static int frame_pointer_check(struct stackframe *frame)  {  	unsigned long high, low;  	unsigned long fp = frame->fp; +	unsigned long pc = frame->pc; + +	/* +	 * call_with_stack() is the only place we allow SP to jump from one +	 * stack to another, with FP and SP pointing to different stacks, +	 * skipping the FP boundary check at this point. +	 */ +	if (pc >= (unsigned long)&call_with_stack && +			pc < (unsigned long)&call_with_stack_end) +		return 0;  	/* only go to a higher address on the stack */  	low = frame->sp;  	high = ALIGN(low, THREAD_SIZE); -#ifdef CONFIG_CC_IS_CLANG  	/* check current frame pointer is within bounds */ +#ifdef CONFIG_CC_IS_CLANG  	if (fp < low + 4 || fp > high - 4)  		return -EINVAL; - -	frame->sp = frame->fp; -	frame->fp = READ_ONCE_NOCHECK(*(unsigned long *)(fp)); -	frame->pc = READ_ONCE_NOCHECK(*(unsigned long *)(fp + 4));  #else -	/* check current frame pointer is within bounds */  	if (fp < low + 12 || fp > high - 4)  		return -EINVAL; +#endif + +	return 0; +} + +int notrace unwind_frame(struct stackframe *frame) +{ +	unsigned long fp = frame->fp; + +	if (frame_pointer_check(frame)) +		return -EINVAL; + +	/* +	 * When we unwind through an exception stack, include the saved PC +	 * value into the stack trace. +	 */ +	if (frame->ex_frame) { +		struct pt_regs *regs = (struct pt_regs *)frame->sp; + +		/* +		 * We check that 'regs + sizeof(struct pt_regs)' (that is, +		 * ®s[1]) does not exceed the bottom of the stack to avoid +		 * accessing data outside the task's stack. This may happen +		 * when frame->ex_frame is a false positive. +		 */ +		if ((unsigned long)®s[1] > ALIGN(frame->sp, THREAD_SIZE)) +			return -EINVAL; + +		frame->pc = regs->ARM_pc; +		frame->ex_frame = false; +		return 0; +	}  	/* restore the registers from the stack frame */ +#ifdef CONFIG_CC_IS_CLANG +	frame->sp = frame->fp; +	frame->fp = READ_ONCE_NOCHECK(*(unsigned long *)(fp)); +	frame->pc = READ_ONCE_NOCHECK(*(unsigned long *)(fp + 4)); +#else  	frame->fp = READ_ONCE_NOCHECK(*(unsigned long *)(fp - 12));  	frame->sp = READ_ONCE_NOCHECK(*(unsigned long *)(fp - 8));  	frame->pc = READ_ONCE_NOCHECK(*(unsigned long *)(fp - 4)); @@ -72,6 +119,9 @@ int notrace unwind_frame(struct stackframe *frame)  					(void *)frame->fp, &frame->kr_cur);  #endif +	if (in_entry_text(frame->pc)) +		frame->ex_frame = true; +  	return 0;  }  #endif @@ -102,7 +152,6 @@ static int save_trace(struct stackframe *frame, void *d)  {  	struct stack_trace_data *data = d;  	struct stack_trace *trace = data->trace; -	struct pt_regs *regs;  	unsigned long addr = frame->pc;  	if (data->no_sched_functions && in_sched_functions(addr)) @@ -113,19 +162,6 @@ static int save_trace(struct stackframe *frame, void *d)  	}  	trace->entries[trace->nr_entries++] = addr; - -	if (trace->nr_entries >= trace->max_entries) -		return 1; - -	if (!in_entry_text(frame->pc)) -		return 0; - -	regs = (struct pt_regs *)frame->sp; -	if ((unsigned long)®s[1] > ALIGN(frame->sp, THREAD_SIZE)) -		return 0; - -	trace->entries[trace->nr_entries++] = regs->ARM_pc; -  	return trace->nr_entries >= trace->max_entries;  } @@ -167,6 +203,9 @@ here:  	frame.kr_cur = NULL;  	frame.tsk = tsk;  #endif +#ifdef CONFIG_UNWINDER_FRAME_POINTER +	frame.ex_frame = false; +#endif  	walk_stackframe(&frame, save_trace, &data);  } @@ -188,6 +227,9 @@ void save_stack_trace_regs(struct pt_regs *regs, struct stack_trace *trace)  	frame.kr_cur = NULL;  	frame.tsk = current;  #endif +#ifdef CONFIG_UNWINDER_FRAME_POINTER +	frame.ex_frame = in_entry_text(frame.pc); +#endif  	walk_stackframe(&frame, save_trace, &data);  }  |