root/arch/s390/kernel/stacktrace.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);
}