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