From 802ccef86feb7a65772f40dda715ca11959ee8e2 Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Sat, 25 Jul 2026 00:14:05 +0200 Subject: [PATCH] xtensa/esp32s3: Abort the faulting user task on an unrecoverable fault. When an unprivileged task takes a fault the system cannot recover from, it now gets a fatal SIGSEGV and only that task ends. A fault in privileged code still panics. What decides it is the interrupted context, not the cause: the saved PS says whether the fault was taken in User Mode. A list of causes would leave every cause off the list as a way for a user task to stop the machine, and there are many -- a divide by zero, a privileged instruction, a load/store error, and an illegal instruction, which is how a refused fetch from kernel text arrives on this chip (TRM v1.8 p.699: a denied external-memory access is answered with 0xdeadbeaf instead of trapping). PS.UM is clear in a kernel thread, in a system call made on the user's behalf and in an interrupt handler, so those still panic. If the recoverable-fault dispatcher is enabled it still gets first refusal on causes 28, 29 and 20, the only ones re-executing can help. esp32s3_userfault_abort() records the exception frame as the task's context, dispatches SIGSEGV, and returns the redirected frame, so the vector's RFE resumes the task in the signal trampoline, whose default action exits it. CONFIG_ESP32S3_USERFAULT_ABORT enables it, default y wherever there is an unprivileged world, and selects SIG_DEFAULT and SIG_SIGKILL_ACTION. Verified on an ESP32-S3 DevKitC with a WROOM-2 module, esp32s3-devkit:kernel_oct: a user task that writes through NULL, reads a wild address, divides by zero, calls into a buffer of garbage or branches into kernel text is terminated on its own, while an unrelated task keeps running. Stack overflow is not contained. On the windowed ABI it faults inside the window overflow handler and arrives as a double exception with PS.UM already clear; guard pages are the answer, and separate work. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Marco Casaroli --- arch/xtensa/src/esp32s3/Kconfig | 21 ++++ arch/xtensa/src/esp32s3/Make.defs | 8 ++ arch/xtensa/src/esp32s3/esp32s3_isolation.c | 8 +- arch/xtensa/src/esp32s3/esp32s3_pagefault.c | 1 + arch/xtensa/src/esp32s3/esp32s3_user.c | 34 ++++++ arch/xtensa/src/esp32s3/esp32s3_userfault.c | 103 ++++++++++++++++++ arch/xtensa/src/esp32s3/esp32s3_userfault.h | 59 ++++++++++ .../configs/ksta_softap/defconfig | 1 - 8 files changed, 230 insertions(+), 5 deletions(-) create mode 100644 arch/xtensa/src/esp32s3/esp32s3_userfault.c create mode 100644 arch/xtensa/src/esp32s3/esp32s3_userfault.h diff --git a/arch/xtensa/src/esp32s3/Kconfig b/arch/xtensa/src/esp32s3/Kconfig index 8b941c160a2..58c185b5081 100644 --- a/arch/xtensa/src/esp32s3/Kconfig +++ b/arch/xtensa/src/esp32s3/Kconfig @@ -956,6 +956,27 @@ config ESP32S3_PAGEFAULT This is the foundation for guard pages, lazy stack/heap growth and, ultimately, demand paging / copy-on-write on the ESP32-S3. +config ESP32S3_USERFAULT_ABORT + bool "Abort a faulting user task instead of panicking" + default y + depends on !BUILD_FLAT + select SIG_DEFAULT + select SIG_SIGKILL_ACTION + ---help--- + When an unprivileged task takes a fault that cannot be serviced, + deliver SIGSEGV to it so its default action terminates only that task + and the rest of the system keeps running, instead of a whole-system + panic. Kernel-mode faults still panic: a kernel thread, a fault + inside a system call, and a fault while handling an interrupt all run + with PS.UM clear and there is no safe task to kill. + + This is deliberately independent of ESP32S3_PAGEFAULT. Servicing a + fault so the instruction can be re-executed and refusing to let a + rogue task halt the machine are separate capabilities, and the second + is one you always want: a user task can raise an unaligned load, a + divide by zero, a privileged instruction or a corrupt opcode, none of + which any dispatcher can service. + if ESP32S3_PAGEFAULT config ESP32S3_PAGEFAULT_SELFTEST diff --git a/arch/xtensa/src/esp32s3/Make.defs b/arch/xtensa/src/esp32s3/Make.defs index 574a99a599f..f299c2c6f54 100644 --- a/arch/xtensa/src/esp32s3/Make.defs +++ b/arch/xtensa/src/esp32s3/Make.defs @@ -50,6 +50,14 @@ ifneq ($(CONFIG_BUILD_FLAT),y) CHIP_CSRCS += esp32s3_isolation.c endif +# Terminating a faulting user task rather than panicking is independent of +# the recoverable-fault dispatcher, so it is built whenever there is an +# unprivileged world at all. + +ifeq ($(CONFIG_ESP32S3_USERFAULT_ABORT),y) +CHIP_CSRCS += esp32s3_userfault.c +endif + ifeq ($(CONFIG_BUILD_KERNEL),y) CHIP_ASRCS += esp32s3_world1_vectors.S endif diff --git a/arch/xtensa/src/esp32s3/esp32s3_isolation.c b/arch/xtensa/src/esp32s3/esp32s3_isolation.c index 9521ac2f42f..abd421de6f0 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_isolation.c +++ b/arch/xtensa/src/esp32s3/esp32s3_isolation.c @@ -37,7 +37,7 @@ #include #include -#ifdef CONFIG_ESP32S3_PAGEFAULT_ABORT +#ifdef CONFIG_ESP32S3_USERFAULT_ABORT #include #include #include @@ -61,7 +61,7 @@ #include "soc/extmem_reg.h" -#ifdef CONFIG_ESP32S3_PAGEFAULT_ABORT +#ifdef CONFIG_ESP32S3_USERFAULT_ABORT #include "sched/sched.h" #include "signal/signal.h" #endif @@ -107,7 +107,7 @@ extern void _xtensa_level3_vector(void); * Private Functions ****************************************************************************/ -#ifdef CONFIG_ESP32S3_PAGEFAULT_ABORT +#ifdef CONFIG_ESP32S3_USERFAULT_ABORT /**************************************************************************** * Name: pms_clear_violations @@ -169,7 +169,7 @@ static void IRAM_ATTR pms_clear_violations(void) static int IRAM_ATTR pms_violation_isr(int cpuint, void *context, void *arg) { -#ifdef CONFIG_ESP32S3_PAGEFAULT_ABORT +#ifdef CONFIG_ESP32S3_USERFAULT_ABORT uint32_t *regs = (uint32_t *)context; /* Acknowledge and re-arm the monitors first so the level-triggered diff --git a/arch/xtensa/src/esp32s3/esp32s3_pagefault.c b/arch/xtensa/src/esp32s3/esp32s3_pagefault.c index 69b356bc2e6..4d029d7a585 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_pagefault.c +++ b/arch/xtensa/src/esp32s3/esp32s3_pagefault.c @@ -30,6 +30,7 @@ #include #include +#include #include #include #include diff --git a/arch/xtensa/src/esp32s3/esp32s3_user.c b/arch/xtensa/src/esp32s3/esp32s3_user.c index 88050c5eb89..4a4183feb89 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_user.c +++ b/arch/xtensa/src/esp32s3/esp32s3_user.c @@ -31,6 +31,7 @@ #ifdef CONFIG_ESP32S3_PAGEFAULT #include "esp32s3_pagefault.h" #endif +#include "esp32s3_userfault.h" #ifdef CONFIG_ESPRESSIF_SPIFLASH #include "esp_private/cache_utils.h" #endif @@ -101,6 +102,39 @@ uint32_t *xtensa_user(int exccause, uint32_t *regs) } #endif +#ifdef CONFIG_ESP32S3_USERFAULT_ABORT + /* Nothing serviced it, so this fault is not going away by re-executing. + * If the interruptee was unprivileged, terminate just that task. + * + * The test is on the interruptee's User Mode bit and NOT on the cause, and + * that is the whole point. A user task has many ways to raise a + * synchronous exception that no dispatcher can service -- an unaligned + * load (EXCCAUSE 9), a divide by zero (6), a privileged instruction (8), a + * load/store error (3), a corrupt opcode (0) -- and gating on a list of + * causes means every cause left off the list is a way for an unprivileged + * task to stop the machine. Whichever way it arose, a user task running + * garbage must not take the system down with it. + * + * PS.UM also excludes the cases where there is no safe task to kill: a + * kernel thread, a fault inside a system call made on the user's behalf, + * and a fault while handling an interrupt all run with it clear, and fall + * through to the panic below. + * + * One cause deserves its own note. A *denied* external-memory access does + * not trap on this chip. TRM v1.8 p.699: an access without permission is + * "responded with 0 (for internal memory) or 0xdeadbeaf (for external + * memory)". So an unprivileged branch into kernel text in flash or PSRAM + * is refused silently, the CPU executes the dummy word it was handed + * instead, and the refusal surfaces here as EXCCAUSE_ILLEGAL at the + * address that was branched to -- never as EXCCAUSE_INSTR_PROHIBITED. + */ + + if ((regs[REG_PS] & PS_UM) != 0) + { + return esp32s3_userfault_abort(exccause, regs); + } +#endif + /* xtensa_user_panic never returns. */ xtensa_user_panic(exccause, regs); diff --git a/arch/xtensa/src/esp32s3/esp32s3_userfault.c b/arch/xtensa/src/esp32s3/esp32s3_userfault.c new file mode 100644 index 00000000000..becff2125b5 --- /dev/null +++ b/arch/xtensa/src/esp32s3/esp32s3_userfault.c @@ -0,0 +1,103 @@ +/**************************************************************************** + * arch/xtensa/src/esp32s3/esp32s3_userfault.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "xtensa.h" +#include "sched/sched.h" +#include "signal/signal.h" + +#include "esp32s3_userfault.h" + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: esp32s3_userfault_abort + * + * Description: + * Terminate just the faulting unprivileged task by delivering a fatal + * SIGSEGV, instead of panicking the whole system. + * + * This is deliberately independent of what the fault was. A user task + * must not be able to stop the machine, and it has many ways to raise a + * synchronous exception that no dispatcher can service: an unaligned + * load, a divide by zero, a privileged instruction, a corrupt opcode. All + * of them arrive here through the same vector with the same frame, so all + * of them get the same answer. Only the caller decides who is eligible -- + * see xtensa_user(), which gates on the interruptee's PS.UM. + * + * Mirrors the interrupt-dispatch handshake: record the exception frame as + * the task context, dispatch the signal -- which redirects the task to the + * signal trampoline via up_schedule_sigaction() -- then return the + * redirected frame so the vector's RFE resumes the task in the trampoline, + * whose SIGSEGV default action (_exit) tears the task down and resumes. + * + * Input Parameters: + * exccause - The EXCCAUSE of the user exception, for reporting + * regs - The register save area at the time of the exception + * + * Returned Value: + * The register frame to resume (the signal trampoline for the faulting + * task). + * + ****************************************************************************/ + +uint32_t *esp32s3_userfault_abort(int exccause, uint32_t *regs) +{ + struct tcb_s *tcb = this_task(); + siginfo_t info; + + _alert("SIGSEGV task %s: EXCCAUSE=%d EXCVADDR=%08x PC=%08x\n", + get_task_name(tcb), exccause, (unsigned)regs[REG_EXCVADDR], + (unsigned)regs[REG_PC]); + + up_set_interrupt_context(true); + tcb->xcp.regs = regs; + + info.si_signo = SIGSEGV; + info.si_code = SI_USER; + info.si_errno = 0; + info.si_value.sival_ptr = (FAR void *)regs[REG_EXCVADDR]; + + nxsig_tcbdispatch(tcb, &info, false); + + regs = tcb->xcp.regs; + tcb->xcp.regs = NULL; + up_set_interrupt_context(false); + return regs; +} diff --git a/arch/xtensa/src/esp32s3/esp32s3_userfault.h b/arch/xtensa/src/esp32s3/esp32s3_userfault.h new file mode 100644 index 00000000000..c3083e53e12 --- /dev/null +++ b/arch/xtensa/src/esp32s3/esp32s3_userfault.h @@ -0,0 +1,59 @@ +/**************************************************************************** + * arch/xtensa/src/esp32s3/esp32s3_userfault.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_USERFAULT_H +#define __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_USERFAULT_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +#ifdef CONFIG_ESP32S3_USERFAULT_ABORT + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: esp32s3_userfault_abort + * + * Description: + * Terminate the faulting unprivileged task with SIGSEGV rather than + * panicking the system. Cause-agnostic; the caller decides eligibility. + * + * Input Parameters: + * exccause - The EXCCAUSE of the user exception, for reporting + * regs - The register save area at the time of the exception + * + * Returned Value: + * The register frame to resume. + * + ****************************************************************************/ + +uint32_t *esp32s3_userfault_abort(int exccause, uint32_t *regs); + +#endif /* CONFIG_ESP32S3_USERFAULT_ABORT */ +#endif /* __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_USERFAULT_H */ diff --git a/boards/xtensa/esp32s3/esp32s3-devkit/configs/ksta_softap/defconfig b/boards/xtensa/esp32s3/esp32s3-devkit/configs/ksta_softap/defconfig index 0602d0f9b95..259f82d008b 100644 --- a/boards/xtensa/esp32s3/esp32s3-devkit/configs/ksta_softap/defconfig +++ b/boards/xtensa/esp32s3/esp32s3-devkit/configs/ksta_softap/defconfig @@ -88,7 +88,6 @@ CONFIG_SCHED_CHILD_STATUS=y CONFIG_SCHED_HAVE_PARENT=y CONFIG_SCHED_LPWORK=y CONFIG_SCHED_WAITPID=y -CONFIG_SIG_DEFAULT=y CONFIG_START_DAY=6 CONFIG_START_MONTH=12 CONFIG_START_YEAR=2011