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 <marco.casaroli@gmail.com>
This commit is contained in:
Marco Casaroli 2026-07-25 00:14:05 +02:00 • committed by Alan C. Assis
parent c6b23e3e21
commit 802ccef86f
8 changed files with 230 additions and 5 deletions

View file

@ -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

View file

@ -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

View file

@ -37,7 +37,7 @@
#include <nuttx/nuttx.h>
#include <nuttx/sched.h>
#ifdef CONFIG_ESP32S3_PAGEFAULT_ABORT
#ifdef CONFIG_ESP32S3_USERFAULT_ABORT
#include <signal.h>
#include <arch/irq.h>
#include <arch/xtensa/xtensa_corebits.h>
@ -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

View file

@ -30,6 +30,7 @@
#include <debug.h>
#include <errno.h>
#include <nuttx/irq.h>
#include <nuttx/sched.h>
#include <arch/irq.h>
#include <arch/xtensa/xtensa_corebits.h>

View file

@ -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);

View file

@ -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 <nuttx/config.h>
#include <stdbool.h>
#include <stdint.h>
#include <debug.h>
#include <signal.h>
#include <nuttx/irq.h>
#include <nuttx/sched.h>
#include <arch/irq.h>
#include <arch/xtensa/xtensa_corebits.h>
#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;
}

View file

@ -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 <nuttx/config.h>
#include <stdint.h>
#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 */

View file

@ -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