mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-01-12 09:32:12 +00:00
The logic to fallback to the return address (RA) register value in the topmost frame when stack tracing using back chain is broken in multiple ways: When assuming the RA register 14 has not been saved yet one must assume that a new user stack frame has not been allocated either. Therefore the back chain would not contain the stack pointer (SP) at entry, but the caller's SP at its entry instead. Therefore when falling back to the RA register 14 value it would also be necessary to fallback to the SP register 15 value. Otherwise an invalid combination of RA register 14 and caller's SP at its entry (from the back chain) is used. In the topmost frame the back chain contains either the caller's SP at its entry (before having allocated a new stack frame in the prologue), the SP at entry (after having allocated a new stack frame), or an uninitialized value (during static/dynamic stack allocation). In both cases where the back chain is valid either the caller or prologue must have saved its respective RA to the respective frame. Therefore, if the RA obtained from the frame pointed to by the back chain is invalid, this does not indicate that the IP in the topmost frame is still early in the prologue and the RA has not been saved. Reviewed-by: Heiko Carstens <hca@linux.ibm.com> Signed-off-by: Jens Remus <jremus@linux.ibm.com> Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
149 lines
3.5 KiB
C
149 lines
3.5 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Stack trace management functions
|
|
*
|
|
* Copyright IBM Corp. 2006
|
|
*/
|
|
|
|
#include <linux/perf_event.h>
|
|
#include <linux/stacktrace.h>
|
|
#include <linux/uaccess.h>
|
|
#include <asm/asm-offsets.h>
|
|
#include <asm/stacktrace.h>
|
|
#include <asm/unwind.h>
|
|
#include <asm/kprobes.h>
|
|
#include <asm/ptrace.h>
|
|
|
|
void arch_stack_walk(stack_trace_consume_fn consume_entry, void *cookie,
|
|
struct task_struct *task, struct pt_regs *regs)
|
|
{
|
|
struct unwind_state state;
|
|
unsigned long addr;
|
|
|
|
unwind_for_each_frame(&state, task, regs, 0) {
|
|
addr = unwind_get_return_address(&state);
|
|
if (!addr || !consume_entry(cookie, addr))
|
|
break;
|
|
}
|
|
}
|
|
|
|
int arch_stack_walk_reliable(stack_trace_consume_fn consume_entry,
|
|
void *cookie, struct task_struct *task)
|
|
{
|
|
struct unwind_state state;
|
|
unsigned long addr;
|
|
|
|
unwind_for_each_frame(&state, task, NULL, 0) {
|
|
if (state.stack_info.type != STACK_TYPE_TASK)
|
|
return -EINVAL;
|
|
|
|
if (state.regs)
|
|
return -EINVAL;
|
|
|
|
addr = unwind_get_return_address(&state);
|
|
if (!addr)
|
|
return -EINVAL;
|
|
|
|
#ifdef CONFIG_RETHOOK
|
|
/*
|
|
* Mark stacktraces with krethook functions on them
|
|
* as unreliable.
|
|
*/
|
|
if (state.ip == (unsigned long)arch_rethook_trampoline)
|
|
return -EINVAL;
|
|
#endif
|
|
|
|
if (!consume_entry(cookie, addr))
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Check for stack corruption */
|
|
if (unwind_error(&state))
|
|
return -EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
static inline bool store_ip(stack_trace_consume_fn consume_entry, void *cookie,
|
|
struct perf_callchain_entry_ctx *entry, bool perf,
|
|
unsigned long ip)
|
|
{
|
|
#ifdef CONFIG_PERF_EVENTS
|
|
if (perf) {
|
|
if (perf_callchain_store(entry, ip))
|
|
return false;
|
|
return true;
|
|
}
|
|
#endif
|
|
return consume_entry(cookie, ip);
|
|
}
|
|
|
|
static inline bool ip_invalid(unsigned long ip)
|
|
{
|
|
/*
|
|
* Perform some basic checks if an instruction address taken
|
|
* from unreliable source is invalid.
|
|
*/
|
|
if (ip & 1)
|
|
return true;
|
|
if (ip < mmap_min_addr)
|
|
return true;
|
|
if (ip >= current->mm->context.asce_limit)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static inline bool ip_within_vdso(unsigned long ip)
|
|
{
|
|
return in_range(ip, current->mm->context.vdso_base, vdso_text_size());
|
|
}
|
|
|
|
void arch_stack_walk_user_common(stack_trace_consume_fn consume_entry, void *cookie,
|
|
struct perf_callchain_entry_ctx *entry,
|
|
const struct pt_regs *regs, bool perf)
|
|
{
|
|
struct stack_frame_vdso_wrapper __user *sf_vdso;
|
|
struct stack_frame_user __user *sf;
|
|
unsigned long ip, sp;
|
|
|
|
if (!current->mm)
|
|
return;
|
|
ip = instruction_pointer(regs);
|
|
if (!store_ip(consume_entry, cookie, entry, perf, ip))
|
|
return;
|
|
sf = (void __user *)user_stack_pointer(regs);
|
|
pagefault_disable();
|
|
while (1) {
|
|
if (__get_user(sp, &sf->back_chain))
|
|
break;
|
|
/*
|
|
* VDSO entry code has a non-standard stack frame layout.
|
|
* See VDSO user wrapper code for details.
|
|
*/
|
|
if (!sp && ip_within_vdso(ip)) {
|
|
sf_vdso = (void __user *)sf;
|
|
if (__get_user(ip, &sf_vdso->return_address))
|
|
break;
|
|
sp = (unsigned long)sf + STACK_FRAME_VDSO_OVERHEAD;
|
|
sf = (void __user *)sp;
|
|
if (__get_user(sp, &sf->back_chain))
|
|
break;
|
|
} else {
|
|
sf = (void __user *)sp;
|
|
if (__get_user(ip, &sf->gprs[8]))
|
|
break;
|
|
}
|
|
/* Validate SP and RA (ABI requires SP to be 8 byte aligned). */
|
|
if (sp & 0x7 || ip_invalid(ip))
|
|
break;
|
|
if (!store_ip(consume_entry, cookie, entry, perf, ip))
|
|
break;
|
|
}
|
|
pagefault_enable();
|
|
}
|
|
|
|
void arch_stack_walk_user(stack_trace_consume_fn consume_entry, void *cookie,
|
|
const struct pt_regs *regs)
|
|
{
|
|
arch_stack_walk_user_common(consume_entry, cookie, NULL, regs, false);
|
|
}
|