mirror of
https://github.com/torvalds/linux.git
synced 2026-03-14 00:56:20 +01:00
Add basic checks to identify invalid instruction pointers when walking
stack frames:
Instruction pointers must
- have even addresses
- be larger than mmap_min_addr
- lower than the asce_limit of the process
Alternatively it would also be possible to walk page tables similar to fast
GUP and verify that the mapping of the corresponding page is executable,
however that seems to be overkill.
Fixes: aa44433ac4 ("s390: add USER_STACKTRACE support")
Reviewed-by: Jens Remus <jremus@linux.ibm.com>
Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
Signed-off-by: Alexander Gordeev <agordeev@linux.ibm.com>
163 lines
3.6 KiB
C
163 lines
3.6 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 <linux/compat.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;
|
|
}
|
|
|
|
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_user __user *sf;
|
|
unsigned long ip, sp;
|
|
bool first = true;
|
|
|
|
if (is_compat_task())
|
|
return;
|
|
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;
|
|
/* Sanity check: ABI requires SP to be 8 byte aligned. */
|
|
if (!sp || sp & 0x7)
|
|
break;
|
|
sf = (void __user *)sp;
|
|
if (__get_user(ip, &sf->gprs[8]))
|
|
break;
|
|
if (ip_invalid(ip)) {
|
|
/*
|
|
* If the instruction address is invalid, and this
|
|
* is the first stack frame, assume r14 has not
|
|
* been written to the stack yet. Otherwise exit.
|
|
*/
|
|
if (!first)
|
|
break;
|
|
ip = regs->gprs[14];
|
|
if (ip_invalid(ip))
|
|
break;
|
|
}
|
|
if (!store_ip(consume_entry, cookie, entry, perf, ip))
|
|
return;
|
|
first = false;
|
|
}
|
|
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);
|
|
}
|
|
|
|
unsigned long return_address(unsigned int n)
|
|
{
|
|
struct unwind_state state;
|
|
unsigned long addr;
|
|
|
|
/* Increment to skip current stack entry */
|
|
n++;
|
|
|
|
unwind_for_each_frame(&state, NULL, NULL, 0) {
|
|
addr = unwind_get_return_address(&state);
|
|
if (!addr)
|
|
break;
|
|
if (!n--)
|
|
return addr;
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(return_address);
|