This is an automated email from the ASF dual-hosted git repository.
xiaoxiang781216 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 3024b27e442 sched: guard task-exit recovery against corrupted user
lists.
3024b27e442 is described below
commit 3024b27e442f7541eda1c01a8353adc44f7a4be4
Author: Marco Casaroli <[email protected]>
AuthorDate: Mon Oct 26 19:57:06 2026 +0100
sched: guard task-exit recovery against corrupted user lists.
In kernel and protected builds the TLS block and the robust-mutex list it
anchors live in user memory. At task exit nxtask_recover() walked
tls->tl_mhead and wrote through each node (mutex_reset(), flink and flags)
in kernel mode, and task_uninit_info() likewise freed user-heap nodes. A
task whose TLS or list is corrupted, for example by a stack overflow, made
the kernel fault or follow a user-controlled pointer while privileged.
Validate every list node before it is dereferenced:
- pthread_mutex_inconsistent() moves from libs/libc/pthread to
sched/pthread: only nxtask_recover() calls it, and the checks below need
the TCB, which user space cannot use.
- pthread_mutex_inconsistent() takes the target tcb and checks that the
TLS and each mutex lie in that task's user memory (kernel builds also
require a mapped backing page and exclude the text region; protected
builds accept only user data, bss and heap), that nodes are aligned, and
that each mutex is robust and owned by the exiting thread; a bad node
ends the walk.
- nxtask_recover() selects the victim's address environment around recovery
so the TLS and an unmapped waitobj are read in the right space, and
restores it even when selection fails.
- task_uninit_info() skips traversing and freeing unprivileged user
task-info allocations in kernel builds; the address environment reclaims
the user heap, and group teardown still releases file descriptors.
- Implement up_addrenv_find_page() for armv7-a and x86_64: the page check
calls it in every kernel build, and these two had none.
Flat builds behave as before; the guards compile out.
Tested: rv-virt:knetnsh64 (kernel): a robust-mutex test passes (owner
death gives EOWNERDEAD; invalid, unmapped and cyclic lists do not panic;
the exit of another process leaves the parent's mutex and semaphore
alone), a program that overflows its stack and corrupts its TLS ends in
SIGSEGV and NSH continues, and ostest exits 0. rv-virt:pnsh64
(protected) and rv-virt:nsh64 (flat): ostest exits 0. qemu-armv7a:knsh:
a robust-mutex exit test passes and ostest exits 0. Builds (Make):
qemu-intel64 knsh_romfs and knsh_romfs_pci, qemu-armv8a:knsh,
rv-virt:knsh64, sabre-6quad:knsh, sama5d4-ek:knsh, beaglebone:knsh,
esp32s3-devkit:kernel_oct (kernel); qemu-armv7r:pnsh, mps2-an500:knsh,
stm32f4discovery:kostest, esp32s3-devkit:knsh, esp32-devkitc:knsh,
fvp-armv8r:pnsh (protected); rv-virt:nsh64 with PTHREAD_MUTEX_UNSAFE.
qemu-armv8a:nsh with CMake.
Assisted-by: Codex:gpt-6.1-sol
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marco Casaroli <[email protected]>
---
arch/arm/src/armv7-a/arm_addrenv_pgmap.c | 33 ++++-
arch/x86_64/src/common/x86_64_addrenv.c | 54 +++++++
libs/libc/pthread/CMakeLists.txt | 2 +-
libs/libc/pthread/Make.defs | 2 +-
libs/libc/pthread/pthread_mutex_inconsistent.c | 82 -----------
sched/pthread/CMakeLists.txt | 4 +
sched/pthread/Make.defs | 4 +
sched/pthread/pthread.h | 2 +-
sched/pthread/pthread_mutexinconsistent.c | 187 +++++++++++++++++++++++++
sched/task/task_recover.c | 27 +++-
sched/tls/task_uninitinfo.c | 12 ++
11 files changed, 321 insertions(+), 88 deletions(-)
diff --git a/arch/arm/src/armv7-a/arm_addrenv_pgmap.c
b/arch/arm/src/armv7-a/arm_addrenv_pgmap.c
index e889da1adde..5932049dbd5 100644
--- a/arch/arm/src/armv7-a/arm_addrenv_pgmap.c
+++ b/arch/arm/src/armv7-a/arm_addrenv_pgmap.c
@@ -31,6 +31,7 @@
#include <nuttx/pgalloc.h>
#include "pgalloc.h"
+#include "mmu.h"
/****************************************************************************
* Public Functions
@@ -54,7 +55,36 @@
uintptr_t up_addrenv_find_page(arch_addrenv_t *addrenv, uintptr_t vaddr)
{
- /* Implement it later */
+ uintptr_t entry;
+ uintptr_t l2table;
+
+ if (!arm_uservaddr(vaddr) || addrenv->l1table == NULL)
+ {
+ return 0;
+ }
+
+ entry = mmu_l1table_getentry(addrenv->l1table, vaddr);
+ if ((entry & PMD_TYPE_MASK) != PMD_TYPE_PTE)
+ {
+ return 0;
+ }
+
+ l2table = arm_pgvaddr(entry & PMD_PTE_PADDR_MASK);
+ if (l2table == 0)
+ {
+ return 0;
+ }
+
+ entry = mmu_l2_getentry(l2table, vaddr);
+ if ((entry & PTE_TYPE_SMALL) != 0)
+ {
+ return entry & PTE_SMALL_PADDR_MASK;
+ }
+
+ if ((entry & PTE_TYPE_MASK) == PTE_TYPE_LARGE)
+ {
+ return (entry & PTE_LARGE_PADDR_MASK) | (vaddr & 0xf000);
+ }
return 0;
}
@@ -115,5 +145,6 @@ uintptr_t up_addrenv_page_vaddr(uintptr_t page)
void up_addrenv_page_wipe(uintptr_t page)
{
uintptr_t vaddr = arm_pgvaddr(page);
+
memset((void *)vaddr, 0, MM_PGSIZE);
}
diff --git a/arch/x86_64/src/common/x86_64_addrenv.c
b/arch/x86_64/src/common/x86_64_addrenv.c
index de6296af984..81fd7e0bbcd 100644
--- a/arch/x86_64/src/common/x86_64_addrenv.c
+++ b/arch/x86_64/src/common/x86_64_addrenv.c
@@ -995,6 +995,60 @@ int up_addrenv_detach(struct tcb_s *tcb)
return OK;
}
+/****************************************************************************
+ * Name: up_addrenv_find_page
+ *
+ * Description:
+ * Find physical page mapped to user virtual address from the address
+ * environment page directory.
+ *
+ * Input Parameters:
+ * addrenv - The user address environment.
+ * vaddr - The user virtual address
+ *
+ * Returned Value:
+ * Page physical address on success; NULL on failure.
+ *
+ ****************************************************************************/
+
+uintptr_t up_addrenv_find_page(arch_addrenv_t *addrenv, uintptr_t vaddr)
+{
+ uintptr_t lnvaddr;
+ uintptr_t paddr;
+ uint32_t ptlevel;
+
+ /* If vaddr is not user space, get out */
+
+ if (!up_addrenv_user_vaddr(vaddr))
+ {
+ return 0;
+ }
+
+ /* Walk the tables as the MMU does, from the PML4 (level 0) to the PT
+ * (level 3). An entry that is not used is zero.
+ */
+
+ lnvaddr = x86_64_pgvaddr(addrenv->spgtables[0]);
+ if (!lnvaddr)
+ {
+ return 0;
+ }
+
+ for (ptlevel = 0; ptlevel < X86_MMU_PT_LEVELS - 1; ptlevel++)
+ {
+ paddr = mmu_pte_to_paddr(mmu_ln_getentry(ptlevel, lnvaddr, vaddr));
+ lnvaddr = x86_64_pgvaddr(paddr);
+ if (!lnvaddr)
+ {
+ return 0;
+ }
+ }
+
+ /* Got it, now convert it to physical page */
+
+ return mmu_pte_to_paddr(mmu_ln_getentry(ptlevel, lnvaddr, vaddr));
+}
+
/****************************************************************************
* Name: up_addrenv_user_vaddr
*
diff --git a/libs/libc/pthread/CMakeLists.txt b/libs/libc/pthread/CMakeLists.txt
index 752b35a69e0..b9d4288dae4 100644
--- a/libs/libc/pthread/CMakeLists.txt
+++ b/libs/libc/pthread/CMakeLists.txt
@@ -121,7 +121,7 @@ if(NOT CONFIG_DISABLE_PTHREAD)
thrd_exit.c)
if(NOT CONFIG_PTHREAD_MUTEX_UNSAFE)
- list(APPEND SRCS pthread_mutex_consistent.c pthread_mutex_inconsistent.c)
+ list(APPEND SRCS pthread_mutex_consistent.c)
endif()
if(CONFIG_SMP)
diff --git a/libs/libc/pthread/Make.defs b/libs/libc/pthread/Make.defs
index 876eb5ffd1f..129cd8db847 100644
--- a/libs/libc/pthread/Make.defs
+++ b/libs/libc/pthread/Make.defs
@@ -76,7 +76,7 @@ CSRCS += pthread_attr_getaffinity.c pthread_attr_setaffinity.c
endif
ifneq ($(CONFIG_PTHREAD_MUTEX_UNSAFE),y)
-CSRCS += pthread_mutex_consistent.c pthread_mutex_inconsistent.c
+CSRCS += pthread_mutex_consistent.c
endif
ifeq ($(CONFIG_PTHREAD_SPINLOCKS),y)
diff --git a/libs/libc/pthread/pthread_mutex_inconsistent.c
b/libs/libc/pthread/pthread_mutex_inconsistent.c
deleted file mode 100644
index d35a1914800..00000000000
--- a/libs/libc/pthread/pthread_mutex_inconsistent.c
+++ /dev/null
@@ -1,82 +0,0 @@
-/****************************************************************************
- * libs/libc/pthread/pthread_mutex_inconsistent.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 <pthread.h>
-#include <sched.h>
-#include <assert.h>
-#include <errno.h>
-
-#include <nuttx/sched.h>
-#include <nuttx/semaphore.h>
-#include <nuttx/pthread.h>
-#include <nuttx/atomic.h>
-
-#include "pthread.h"
-
-/****************************************************************************
- * Public Functions
- ****************************************************************************/
-
-/****************************************************************************
- * Name: pthread_mutex_inconsistent
- *
- * Description:
- * This function is called when a pthread is terminated via either
- * pthread_exit() or pthread_cancel(). It will check for any mutexes
- * held by exiting thread. It will mark them as inconsistent and
- * then wake up the highest priority waiter for the mutex. That
- * instance of pthread_mutex_lock() will then return EOWNERDEAD.
- *
- * Input Parameters:
- * tcb -- a reference to the TCB of the exiting pthread.
- *
- * Returned Value:
- * None.
- *
- ****************************************************************************/
-
-void pthread_mutex_inconsistent(FAR struct tls_info_s *tls)
-{
- FAR struct pthread_mutex_s *mutex;
-
- /* Remove and process each mutex held by this task */
-
- while (tls->tl_mhead != NULL)
- {
- /* Remove the mutex from the TCB list */
-
- mutex = tls->tl_mhead;
- tls->tl_mhead = mutex->flink;
- mutex->flink = NULL;
-
- /* Mark the mutex as INCONSISTENT and wake up any waiting thread */
-
- mutex->flags |= _PTHREAD_MFLAGS_INCONSISTENT;
- mutex_reset(&mutex->mutex);
- }
-}
diff --git a/sched/pthread/CMakeLists.txt b/sched/pthread/CMakeLists.txt
index 3ce9bb09c11..284fa9295a7 100644
--- a/sched/pthread/CMakeLists.txt
+++ b/sched/pthread/CMakeLists.txt
@@ -37,6 +37,10 @@ if(NOT CONFIG_DISABLE_PTHREAD)
pthread_release.c
pthread_setschedprio.c)
+ if(NOT CONFIG_PTHREAD_MUTEX_UNSAFE)
+ list(APPEND SRCS pthread_mutexinconsistent.c)
+ endif()
+
if(CONFIG_SMP)
list(APPEND SRCS pthread_setaffinity.c pthread_getaffinity.c)
endif()
diff --git a/sched/pthread/Make.defs b/sched/pthread/Make.defs
index 8e28702ac1a..be49d6e5152 100644
--- a/sched/pthread/Make.defs
+++ b/sched/pthread/Make.defs
@@ -28,6 +28,10 @@ CSRCS += pthread_sigmask.c pthread_sigqueue.c
pthread_cancel.c
CSRCS += pthread_completejoin.c pthread_findjoininfo.c
CSRCS += pthread_release.c pthread_setschedprio.c
+ifneq ($(CONFIG_PTHREAD_MUTEX_UNSAFE),y)
+CSRCS += pthread_mutexinconsistent.c
+endif
+
ifeq ($(CONFIG_SMP),y)
CSRCS += pthread_setaffinity.c pthread_getaffinity.c
endif
diff --git a/sched/pthread/pthread.h b/sched/pthread/pthread.h
index 7d8e10e1128..551945b939c 100644
--- a/sched/pthread/pthread.h
+++ b/sched/pthread/pthread.h
@@ -71,7 +71,7 @@ int pthread_findjoininfo(FAR struct task_group_s *group,
pid_t pid,
void pthread_release(FAR struct task_group_s *group);
#ifndef CONFIG_PTHREAD_MUTEX_UNSAFE
-void pthread_mutex_inconsistent(FAR struct tls_info_s *tls);
+void pthread_mutex_inconsistent(FAR struct tcb_s *tcb);
#endif
#ifdef CONFIG_PTHREAD_MUTEX_TYPES
diff --git a/sched/pthread/pthread_mutexinconsistent.c
b/sched/pthread/pthread_mutexinconsistent.c
new file mode 100644
index 00000000000..442d6f726fb
--- /dev/null
+++ b/sched/pthread/pthread_mutexinconsistent.c
@@ -0,0 +1,187 @@
+/****************************************************************************
+ * sched/pthread/pthread_mutexinconsistent.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 <pthread.h>
+#include <sched.h>
+#include <assert.h>
+#include <errno.h>
+
+#include <nuttx/sched.h>
+#include <nuttx/semaphore.h>
+#include <nuttx/pthread.h>
+#include <nuttx/atomic.h>
+
+#ifndef CONFIG_BUILD_FLAT
+# include <nuttx/addrenv.h>
+# include <nuttx/arch.h>
+# include <nuttx/mm/mm.h>
+# include <nuttx/pgalloc.h>
+# include <nuttx/userspace.h>
+#endif
+
+#include "pthread/pthread.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#ifdef CONFIG_PTHREAD_MUTEX_TYPES
+# define mutex_get_holder(m) nxrmutex_get_holder(m)
+# define mutex_reset(m) nxrmutex_reset(m)
+#else
+# define mutex_get_holder(m) nxmutex_get_holder(m)
+# define mutex_reset(m) nxmutex_reset(m)
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+#ifndef CONFIG_BUILD_FLAT
+static bool pthread_mutex_accessible(FAR struct tcb_s *tcb,
+ FAR const void *ptr, size_t size)
+{
+ uintptr_t start = (uintptr_t)ptr;
+ uintptr_t end = start + size - 1;
+
+ if ((tcb->flags & TCB_FLAG_TTYPE_MASK) == TCB_FLAG_TTYPE_KERNEL)
+ {
+ return true;
+ }
+
+ if (start == 0 || end < start)
+ {
+ return false;
+ }
+
+#ifdef CONFIG_BUILD_KERNEL
+ if (!uaccess_ok(ptr, size) || tcb->addrenv_own == NULL ||
+ (start < ARCH_TEXT_VEND && end >= CONFIG_ARCH_TEXT_VBASE))
+ {
+ return false;
+ }
+
+ /* A user virtual address need not have a backing page. In particular,
+ * do not fault again while recovering a task which has already faulted.
+ */
+
+ start = MM_PGALIGNDOWN(start);
+ end = MM_PGALIGNDOWN(end);
+ for (; ; start += MM_PGSIZE)
+ {
+ if (up_addrenv_find_page(&tcb->addrenv_own->addrenv, start) == 0)
+ {
+ return false;
+ }
+
+ if (start == end)
+ {
+ return true;
+ }
+ }
+#else
+ /* Protected builds share an address space, but user lists must still
+ * not lead recovery into kernel memory.
+ */
+
+ return (start >= USERSPACE->us_datastart &&
+ end < USERSPACE->us_dataend) ||
+ (start >= USERSPACE->us_bssstart &&
+ end < USERSPACE->us_bssend) ||
+ (umm_heapmember((FAR void *)start) &&
+ umm_heapmember((FAR void *)end));
+#endif
+}
+#endif
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: pthread_mutex_inconsistent
+ *
+ * Description:
+ * This function is called when a pthread is terminated via either
+ * pthread_exit() or pthread_cancel(). It will check for any mutexes
+ * held by exiting thread. It will mark them as inconsistent and
+ * then wake up the highest priority waiter for the mutex. That
+ * instance of pthread_mutex_lock() will then return EOWNERDEAD.
+ *
+ * Input Parameters:
+ * tcb -- a reference to the TCB of the exiting pthread.
+ *
+ * Returned Value:
+ * None.
+ *
+ ****************************************************************************/
+
+void pthread_mutex_inconsistent(FAR struct tcb_s *tcb)
+{
+ FAR struct tls_info_s *tls = tcb->stack_alloc_ptr;
+ FAR struct pthread_mutex_s *mutex;
+
+#ifndef CONFIG_BUILD_FLAT
+ if (!pthread_mutex_accessible(tcb, tls, sizeof(*tls)))
+ {
+ return;
+ }
+#endif
+
+ /* Remove and process each mutex held by this task */
+
+ while (tls->tl_mhead != NULL)
+ {
+ /* Remove the mutex from the TCB list */
+
+ mutex = tls->tl_mhead;
+#ifndef CONFIG_BUILD_FLAT
+ if (((uintptr_t)mutex & (sizeof(uintptr_t) - 1)) != 0 ||
+ !pthread_mutex_accessible(tcb, mutex, sizeof(*mutex)))
+ {
+ tls->tl_mhead = NULL;
+ break;
+ }
+#endif
+
+ if ((mutex->flags & _PTHREAD_MFLAGS_ROBUST) == 0 ||
+ mutex_get_holder(&mutex->mutex) != tcb->pid)
+ {
+ tls->tl_mhead = NULL;
+ break;
+ }
+
+ tls->tl_mhead = mutex->flink;
+ mutex->flink = NULL;
+
+ /* Mark the mutex as INCONSISTENT and wake up any waiting thread */
+
+ mutex->flags |= _PTHREAD_MFLAGS_INCONSISTENT;
+ mutex_reset(&mutex->mutex);
+ }
+}
diff --git a/sched/task/task_recover.c b/sched/task/task_recover.c
index ecdbb9d2dbf..58415db7f41 100644
--- a/sched/task/task_recover.c
+++ b/sched/task/task_recover.c
@@ -29,6 +29,7 @@
#include <assert.h>
#include <nuttx/arch.h>
+#include <nuttx/addrenv.h>
#include <nuttx/wdog.h>
#include <nuttx/sched.h>
@@ -64,10 +65,25 @@
void nxtask_recover(FAR struct tcb_s *tcb)
{
+#ifdef CONFIG_ARCH_ADDRENV
+ FAR struct addrenv_s *oldenv = NULL;
+
+ /* A task may be deleted by a task in another address environment. TLS
+ * and an unmapped waitobj must be accessed in the victim's environment.
+ */
+
+ if (tcb->addrenv_own != NULL &&
+ addrenv_select(tcb->addrenv_own, &oldenv) < 0)
+ {
+ addrenv_restore(oldenv);
+ return;
+ }
+#endif
+
#if !defined(CONFIG_DISABLE_PTHREAD) && !defined(CONFIG_PTHREAD_MUTEX_UNSAFE)
- /* Recover any mutexes still held by the canceled thread */
+ /* Recover any mutexes still held by the canceled thread. */
- pthread_mutex_inconsistent(nxsched_get_tls(tcb));
+ pthread_mutex_inconsistent(tcb);
#endif
/* The task is being deleted. Cancel in pending timeout events. */
@@ -94,4 +110,11 @@ void nxtask_recover(FAR struct tcb_s *tcb)
DEBUGVERIFY(nxsched_stop_sporadic(tcb));
}
#endif
+
+#ifdef CONFIG_ARCH_ADDRENV
+ if (tcb->addrenv_own != NULL)
+ {
+ addrenv_restore(oldenv);
+ }
+#endif
}
diff --git a/sched/tls/task_uninitinfo.c b/sched/tls/task_uninitinfo.c
index b83f2f2f151..9537722b1a8 100644
--- a/sched/tls/task_uninitinfo.c
+++ b/sched/tls/task_uninitinfo.c
@@ -125,6 +125,18 @@ void task_uninit_info(FAR struct task_group_s *group)
{
FAR struct task_info_s *info = group->tg_info;
+#ifdef CONFIG_BUILD_KERNEL
+ /* The user heap is reclaimed with the address environment. Its lists
+ * may have been corrupted by the exiting task; neither traversing them
+ * nor freeing their user allocations is necessary in the kernel.
+ */
+
+ if ((group->tg_flags & GROUP_FLAG_PRIVILEGED) == 0)
+ {
+ return;
+ }
+#endif
+
#ifdef CONFIG_PTHREAD_ATFORK
/* Remove the functions that registered with pthread_atfork() */