Root Cause Analysis
1. Relevant Files, Functions, and Data Structures
2. Technical Mechanism
When unwinding the kernel stack of a non-current task (in this report, oom_reaper on CPU 0 unwinding syz.1.97, which is running or preempted), the unwinder needs to safely read frame->fp and frame->ra from the target task's kernel stack. When a task executes kernel code on another CPU, KASAN poisons stack redzones around active local variables on that task's stack (marked with 0xf1 = KASAN_STACK_LEFT, 0xf3, etc.).
To prevent KASAN from generating false-positive warnings while reading another task's stack, READ_ONCE_TASK_STACK is supposed to use READ_ONCE_NOCHECK(x), which dereferences the address using __read_once_word_nocheck(&(x)) (annotated with __no_sanitize_or_inline to suppress KASAN checks).
However, in arch/riscv/kernel/stacktrace.c, READ_ONCE_TASK_STACK is incorrectly defined as follows:
#define READ_ONCE_TASK_STACK(task, x) \
({ \
unsigned long val; \
unsigned long addr = x; \
if ((task) == current) \
val = READ_ONCE(addr); \
else \
val = READ_ONCE_NOCHECK(addr); \
val; \
})
When walk_stackframe() invokes:
fp = READ_ONCE_TASK_STACK(task, frame->fp);
the macro expands with x being the lvalue frame->fp:
- The statement
unsigned long addr = x; expands to unsigned long addr = frame->fp;. This performs a direct memory dereference of frame->fp via a standard compiler-generated load.
- Because
walk_stackframe() is compiled with KASAN instrumentation enabled, this load is checked against KASAN shadow memory.
- If the frame pointer or return address falls in a KASAN-poisoned region (such as a stack redzone
0xf1 belonging to a function frame of the target task), KASAN immediately reports a stack-out-of-bounds violation at line 88.
- Furthermore,
val = READ_ONCE_NOCHECK(addr); erroneously passes addr (which is a local variable on the calling task's stack) to READ_ONCE_NOCHECK(), reading the already-evaluated local variable without KASAN checks while leaving the actual remote stack dereference fully instrumented.
3. Invariant Violation and Fix
The macro violates the invariant that unwinding another task's stack must not perform KASAN-instrumented memory accesses on that task's stack frames.
To fix this defect, READ_ONCE_TASK_STACK must pass the lvalue expression (x) directly to READ_ONCE or READ_ONCE_NOCHECK without evaluating it into an intermediate variable first (matching the implementation in arch/x86/include/asm/unwind.h):
#define READ_ONCE_TASK_STACK(task, x) \
({ \
unsigned long val; \
if ((task) == current) \
val = READ_ONCE(x); \
else \
val = READ_ONCE_NOCHECK(x); \
val; \
})