When boot arm64 kernel with KASAN enabled, the below error is reported by kasan: BUG: KASAN: out-of-bounds in unwind_frame+0xec/0x260 at addr ffffffc064d57ba0 Read of size 8 by task pidof/499 page:ffffffbdc39355c0 count:0 mapcount:0 mapping: (null) index:0x0 flags: 0x0() page dumped because: kasan: bad access detected CPU: 2 PID: 499 Comm: pidof Not tainted 4.5.0-rc1 #119 Hardware name: Freescale Layerscape 2085a RDB Board (DT) Call trace: [<ffffffc00008d078>] dump_backtrace+0x0/0x290 [<ffffffc00008d32c>] show_stack+0x24/0x30 [<ffffffc0006a981c>] dump_stack+0x8c/0xd8 [<ffffffc0002e4400>] kasan_report_error+0x558/0x588 [<ffffffc0002e4958>] kasan_report+0x60/0x70 [<ffffffc0002e3188>] __asan_load8+0x60/0x78 [<ffffffc00008c92c>] unwind_frame+0xec/0x260 [<ffffffc000087e60>] get_wchan+0x110/0x160 [<ffffffc0003b647c>] do_task_stat+0xb44/0xb68 [<ffffffc0003b7730>] proc_tgid_stat+0x40/0x50 [<ffffffc0003ac840>] proc_single_show+0x88/0xd8 [<ffffffc000345be8>] seq_read+0x370/0x770 [<ffffffc00030aba0>] __vfs_read+0xc8/0x1d8 [<ffffffc00030c0ec>] vfs_read+0x94/0x168 [<ffffffc00030d458>] SyS_read+0xb8/0x128 [<ffffffc000086530>] el0_svc_naked+0x24/0x28 Memory state around the buggy address: ffffffc064d57a80: 00 00 00 00 00 00 00 00 f1 f1 f1 f1 00 00 f4 f4 ffffffc064d57b00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >ffffffc064d57b80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ^ ffffffc064d57c00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ffffffc064d57c80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Since the shadow byte pointed by the report is 0, so it may mean it is just hit oob in non-current task. So, disable the instrumentation to silence these warnings. Acked-by: Andrey Ryabinin <aryabinin@virtuozzo.com> Signed-off-by: Yang Shi <yang.shi@linaro.org> Signed-off-by: Will Deacon <will.deacon@arm.com>
190 lines
5.0 KiB
C
190 lines
5.0 KiB
C
/*
|
|
* Stack tracing support
|
|
*
|
|
* Copyright (C) 2012 ARM Ltd.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/export.h>
|
|
#include <linux/ftrace.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/stacktrace.h>
|
|
|
|
#include <asm/irq.h>
|
|
#include <asm/stacktrace.h>
|
|
|
|
/*
|
|
* AArch64 PCS assigns the frame pointer to x29.
|
|
*
|
|
* A simple function prologue looks like this:
|
|
* sub sp, sp, #0x10
|
|
* stp x29, x30, [sp]
|
|
* mov x29, sp
|
|
*
|
|
* A simple function epilogue looks like this:
|
|
* mov sp, x29
|
|
* ldp x29, x30, [sp]
|
|
* add sp, sp, #0x10
|
|
*/
|
|
int notrace unwind_frame(struct task_struct *tsk, struct stackframe *frame)
|
|
{
|
|
unsigned long high, low;
|
|
unsigned long fp = frame->fp;
|
|
unsigned long irq_stack_ptr;
|
|
|
|
/*
|
|
* Use raw_smp_processor_id() to avoid false-positives from
|
|
* CONFIG_DEBUG_PREEMPT. get_wchan() calls unwind_frame() on sleeping
|
|
* task stacks, we can be pre-empted in this case, so
|
|
* {raw_,}smp_processor_id() may give us the wrong value. Sleeping
|
|
* tasks can't ever be on an interrupt stack, so regardless of cpu,
|
|
* the checks will always fail.
|
|
*/
|
|
irq_stack_ptr = IRQ_STACK_PTR(raw_smp_processor_id());
|
|
|
|
low = frame->sp;
|
|
/* irq stacks are not THREAD_SIZE aligned */
|
|
if (on_irq_stack(frame->sp, raw_smp_processor_id()))
|
|
high = irq_stack_ptr;
|
|
else
|
|
high = ALIGN(low, THREAD_SIZE) - 0x20;
|
|
|
|
if (fp < low || fp > high || fp & 0xf)
|
|
return -EINVAL;
|
|
|
|
frame->sp = fp + 0x10;
|
|
frame->fp = READ_ONCE_NOCHECK(*(unsigned long *)(fp));
|
|
frame->pc = READ_ONCE_NOCHECK(*(unsigned long *)(fp + 8));
|
|
|
|
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
|
if (tsk && tsk->ret_stack &&
|
|
(frame->pc == (unsigned long)return_to_handler)) {
|
|
/*
|
|
* This is a case where function graph tracer has
|
|
* modified a return address (LR) in a stack frame
|
|
* to hook a function return.
|
|
* So replace it to an original value.
|
|
*/
|
|
frame->pc = tsk->ret_stack[frame->graph--].ret;
|
|
}
|
|
#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
|
|
|
|
/*
|
|
* Check whether we are going to walk through from interrupt stack
|
|
* to task stack.
|
|
* If we reach the end of the stack - and its an interrupt stack,
|
|
* unpack the dummy frame to find the original elr.
|
|
*
|
|
* Check the frame->fp we read from the bottom of the irq_stack,
|
|
* and the original task stack pointer are both in current->stack.
|
|
*/
|
|
if (frame->sp == irq_stack_ptr) {
|
|
struct pt_regs *irq_args;
|
|
unsigned long orig_sp = IRQ_STACK_TO_TASK_STACK(irq_stack_ptr);
|
|
|
|
if (object_is_on_stack((void *)orig_sp) &&
|
|
object_is_on_stack((void *)frame->fp)) {
|
|
frame->sp = orig_sp;
|
|
|
|
/* orig_sp is the saved pt_regs, find the elr */
|
|
irq_args = (struct pt_regs *)orig_sp;
|
|
frame->pc = irq_args->pc;
|
|
} else {
|
|
/*
|
|
* This frame has a non-standard format, and we
|
|
* didn't fix it, because the data looked wrong.
|
|
* Refuse to output this frame.
|
|
*/
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void notrace walk_stackframe(struct task_struct *tsk, struct stackframe *frame,
|
|
int (*fn)(struct stackframe *, void *), void *data)
|
|
{
|
|
while (1) {
|
|
int ret;
|
|
|
|
if (fn(frame, data))
|
|
break;
|
|
ret = unwind_frame(tsk, frame);
|
|
if (ret < 0)
|
|
break;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(walk_stackframe);
|
|
|
|
#ifdef CONFIG_STACKTRACE
|
|
struct stack_trace_data {
|
|
struct stack_trace *trace;
|
|
unsigned int no_sched_functions;
|
|
unsigned int skip;
|
|
};
|
|
|
|
static int save_trace(struct stackframe *frame, void *d)
|
|
{
|
|
struct stack_trace_data *data = d;
|
|
struct stack_trace *trace = data->trace;
|
|
unsigned long addr = frame->pc;
|
|
|
|
if (data->no_sched_functions && in_sched_functions(addr))
|
|
return 0;
|
|
if (data->skip) {
|
|
data->skip--;
|
|
return 0;
|
|
}
|
|
|
|
trace->entries[trace->nr_entries++] = addr;
|
|
|
|
return trace->nr_entries >= trace->max_entries;
|
|
}
|
|
|
|
void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace)
|
|
{
|
|
struct stack_trace_data data;
|
|
struct stackframe frame;
|
|
|
|
data.trace = trace;
|
|
data.skip = trace->skip;
|
|
|
|
if (tsk != current) {
|
|
data.no_sched_functions = 1;
|
|
frame.fp = thread_saved_fp(tsk);
|
|
frame.sp = thread_saved_sp(tsk);
|
|
frame.pc = thread_saved_pc(tsk);
|
|
} else {
|
|
data.no_sched_functions = 0;
|
|
frame.fp = (unsigned long)__builtin_frame_address(0);
|
|
frame.sp = current_stack_pointer;
|
|
frame.pc = (unsigned long)save_stack_trace_tsk;
|
|
}
|
|
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
|
frame.graph = tsk->curr_ret_stack;
|
|
#endif
|
|
|
|
walk_stackframe(tsk, &frame, save_trace, &data);
|
|
if (trace->nr_entries < trace->max_entries)
|
|
trace->entries[trace->nr_entries++] = ULONG_MAX;
|
|
}
|
|
|
|
void save_stack_trace(struct stack_trace *trace)
|
|
{
|
|
save_stack_trace_tsk(current, trace);
|
|
}
|
|
EXPORT_SYMBOL_GPL(save_stack_trace);
|
|
#endif
|