Re: [PATCH] riscv: entry: gate shadow call stack reload on sstatus.SPP, not tp

From: Sami Tolvanen

Date: Wed Oct 07 2026 - 18:49:47 EST


On Thu, Oct 1, 2026 at 6:57 AM Ziyi Guo <guoziyi114@xxxxxxxxx> wrote:
>
> Under CONFIG_SHADOW_CALL_STACK, handle_exception() reloads the kernel
> shadow call stack pointer (gp) only when the trap came from userspace.
> It made that decision with scs_load_current_if_task_changed, which
> compares the trap-time tp (read back from sscratch into s5) against the
> current tp:
>
> beq \prev, tp, _skip_scs
> scs_load_current
> _skip_scs:
>
> tp is a user-writable register. A user task that sets tp to the
> linear-map address of its own task_struct (&current) before trapping
> makes the comparison succeed (can achieve by brute force with KASLR
> enable under our test), so the kernel skips scs_load_current and
> keeps running with the gp that userspace left in place. Under
> CONFIG_SHADOW_CALL_STACK gp is the shadow call stack pointer and
> load_global_pointer is a no-op, so the compiler-emitted
> "sd ra, -8(gp)" / "ld ra, -8(gp)" in every non-leaf function then write
> to, and return through, an attacker-controlled address -- an
> unprivileged kernel arbitrary-write and control-flow-hijack primitive.
> The poisoned gp is saved to task_struct.scs_sp on return, so the reload
> is skipped on every subsequent entry as well.
>
> Decide "came from userspace" from the hardware sstatus.SPP bit (already
> in s1 at this point) instead of from tp, mirroring the check that
> ret_from_exception already uses a few instructions later. SPP is
> written by hardware on trap entry and cannot be forged from U-mode.
> Legitimate behaviour is unchanged: user entries (SPP=0) reload the task
> shadow call stack, nested kernel entries (SPP=1) skip it.
>
> Fixes: d1584d791a29 ("riscv: Implement Shadow Call Stack")
> Signed-off-by: Ziyi Guo <guoziyi114@xxxxxxxxx>

Reviewed-by: Sami Tolvanen <samitolvanen@xxxxxxxxxx>

Sami