From 25b1fcd20993986618bdf67cb6bf72fe910d9f41 Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Sun, 26 Jul 2026 11:59:48 +0200 Subject: [PATCH] sched/addrenv: do not dereference a NULL address environment A task does not necessarily own an address environment. tcb->addrenv_own is set only by addrenv_attach(), which is reached only from addrenv_allocate(); a kernel thread never allocates one, and in a protected build nothing does -- there is a single address space for the whole system and the architecture's up_addrenv_*() are stubs. addrenv_own is then NULL for every task, always. That a task may have no address environment is already an expected state. addrenv_switch() returns OK when tcb->addrenv_curr is NULL and addrenv_drop() returns early, and every caller of addrenv_select() checks addrenv_own != NULL before calling in: nxsched_get_stateinfo(), nxtask_argvstr(), proc_groupenv() and the arm, arm64, risc-v and tricore up_check_tcbstack(). addrenv_take() and addrenv_give() are the only two that dereference unconditionally. addrenv_join() calls addrenv_take(ptcb->addrenv_own) without a check, so pthread_create() faults on &((struct addrenv_s *)NULL)->refs whenever the calling task has no address environment. With CONFIG_DEBUG_ASSERTIONS off the same access silently corrupts low memory instead. Handle NULL in both, the way the rest of the file already does. addrenv_give() returns a non-zero count for the NULL case so that callers never conclude an absent address environment has become unreferenced and should be destroyed. Signed-off-by: Marco Casaroli Co-Authored-By: Claude Opus 5 (1M context) --- sched/addrenv/addrenv.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/sched/addrenv/addrenv.c b/sched/addrenv/addrenv.c index ba0ddb218b8..9936a105c77 100644 --- a/sched/addrenv/addrenv.c +++ b/sched/addrenv/addrenv.c @@ -395,7 +395,17 @@ int addrenv_restore(FAR struct addrenv_s *addrenv) void addrenv_take(FAR struct addrenv_s *addrenv) { - atomic_fetch_add(&addrenv->refs, 1); + /* A task can legitimately have no address environment -- addrenv_switch() + * and addrenv_drop() both treat that as "nothing to do". A kernel thread + * never owns one, and in a protected build no task does: there is a + * single address space for the whole system and the architecture's + * up_addrenv_*() are stubs. There is then nothing to reference count. + */ + + if (addrenv != NULL) + { + atomic_fetch_add(&addrenv->refs, 1); + } } /**************************************************************************** @@ -415,7 +425,12 @@ void addrenv_take(FAR struct addrenv_s *addrenv) int addrenv_give(FAR struct addrenv_s *addrenv) { - return atomic_fetch_sub(&addrenv->refs, 1) - 1; + /* See addrenv_take(): nothing was counted, so nothing is given back. A + * non-zero count is returned so that callers never conclude the (absent) + * address environment has become unreferenced and should be destroyed. + */ + + return addrenv ? atomic_fetch_sub(&addrenv->refs, 1) - 1 : 1; } /****************************************************************************