This is an automated email from the ASF dual-hosted git repository.

acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git


The following commit(s) were added to refs/heads/master by this push:
     new f8c0bc77fb8 arch/risc-v: Recover from a user fault without a kernel 
stack.
f8c0bc77fb8 is described below

commit f8c0bc77fb8f17afb02b8e0b2a33eb89d4ea4b1b
Author: Marco Casaroli <[email protected]>
AuthorDate: Mon Jul 27 12:48:10 2026 +0200

    arch/risc-v: Recover from a user fault without a kernel stack.
    
    In CONFIG_BUILD_PROTECTED, a user task that touches memory it does not
    own must be terminated on its own.  The rest of the system must keep
    running.  riscv_fault_handler() already does this: it checks for a fault
    taken from U-mode, sets TCB_FLAG_FORCED_CANCEL and changes the exception
    return to _exit(SIGSEGV) in privileged mode.  But the whole block was
    inside #ifdef CONFIG_ARCH_KERNEL_STACK.  Configurations that do not
    select that symbol, such as rv-virt:pnsh and rv-virt:pnsh64, fell
    through to PANIC_WITH_REGS().  A contained user-space bug stopped the
    whole system.
    
    Only the last line of the block needs a kernel stack:
    
        running_regs()[REG_SP] = tcb->xcp.ktopstk;
    
    because xcp.ktopstk exists only with one.  Narrow the guard to that
    assignment, so the rest compiles in all configurations.  arm64 already
    does the same in arm64_fatal_handler().
    
    It is correct to leave REG_SP unchanged.  In riscv_exception_common.S
    the switch to the kernel stack at exception entry is also inside
    #ifdef CONFIG_ARCH_KERNEL_STACK.  Without a kernel stack, the exception
    frame goes on the user stack and REG_SP holds the user SP.
    dispatch_syscall() already runs on that stack, so the kernel runs all
    system calls of this task there, exit() included.  Running _exit on it
    after a fault is the same case and adds no new exposure.  The stack also
    stays mapped until the scheduler switches away, because a build without
    a kernel stack cannot select CONFIG_ARCH_ADDRENV
    (riscv_exception_common.S has an #error for that combination).
    
    No behaviour change in other builds.  The recovery runs only when the
    saved STATUS_PPP is clear, that is, when the fault came from U-mode.  In
    CONFIG_BUILD_FLAT, tasks run at kernel privilege (M-mode, or S-mode on
    the nsbi configurations), so STATUS_PPP is set and the panic path stays
    the same.  CONFIG_BUILD_KERNEL configurations select ARCH_KERNEL_STACK
    through ARCH_ADDRENV, so their code does not change.
    
    Tested on QEMU with examples/sandbox and ostest.  On rv-virt:pnsh and
    rv-virt:pnsh64 a forbidden read or write of kernel memory now kills
    only the offending task, with status 2816 (SIGSEGV).  The shell and an
    unrelated thread keep running.  Before this change the same access
    caused a PANIC.  ostest exits with status 0 on both, before and after
    this change.  rv-virt:knsh still builds, and its riscv_exception.o
    differs only in the __LINE__ value of the PANIC call.
    
    Assisted-by: Claude Code:claude-opus-5-5
    Signed-off-by: Marco Casaroli <[email protected]>
---
 arch/risc-v/src/common/riscv_exception.c | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/arch/risc-v/src/common/riscv_exception.c 
b/arch/risc-v/src/common/riscv_exception.c
index 3d45d264996..1865af7d355 100644
--- a/arch/risc-v/src/common/riscv_exception.c
+++ b/arch/risc-v/src/common/riscv_exception.c
@@ -102,7 +102,6 @@ static const char *g_reasons_str[RISCV_MAX_EXCEPTION + 1] =
 
 static void riscv_fault_handler(uintreg_t cause, void *regs)
 {
-#ifdef CONFIG_ARCH_KERNEL_STACK
   struct tcb_s *tcb = this_task();
 
   /* The STATUS_PPP check alone is enough: any kernel-mode fault (kernel
@@ -131,14 +130,26 @@ static void riscv_fault_handler(uintreg_t cause, void 
*regs)
       running_regs()[REG_A0] = (void *)SIGSEGV;
       ((uintreg_t *)running_regs())[REG_INT_CTX] |= STATUS_PPP;
 
+#ifdef CONFIG_ARCH_KERNEL_STACK
       /* Continue with kernel stack in use. The frame(s) in kernel stack
        * are no longer needed, so just set it to top
        */
 
       running_regs()[REG_SP] = tcb->xcp.ktopstk;
+#endif
+
+      /* Without a per-process kernel stack REG_SP is left alone, and _exit
+       * runs on the user stack the task faulted on.  That is the same stack
+       * exception_common already pushed this frame onto, and the same one
+       * dispatch_syscall uses for this task's system calls, so it is no new
+       * exposure -- see the CONFIG_ARCH_KERNEL_STACK blocks in
+       * riscv_exception_common.S.  Such a configuration cannot have
+       * CONFIG_ARCH_ADDRENV either, so the stack stays mapped until the
+       * scheduler switches away from the dying task.
+       */
+
       return;
     }
-#endif
 
   _alert("PANIC!!! Exception = %" PRIxREG "\n", cause);
   up_irq_save();

Reply via email to