nuttx/sched/task/task_spawn.c
Marco Casaroli b7faaed58f libc, sched: Resolve FDPIC descriptors at module callback entry points.
A module's function pointer is the address of a two-word descriptor in its
writable segment, not a code address.  Firmware that accepts one, stores it,
and later branches to it therefore jumps into the module's RAM data.  There
is no diagnostic: the board takes a HardFault with a dead console.

qsort and bsearch have resolved the descriptor since FDPIC support landed.
Nothing else did, and nothing noticed, because qsort's comparison function
was the only callback anything exercised.  That left every other entry point
looking perfectly usable -- a module could call one, it would return success,
and the fault arrived later from somewhere else.  Resolution is opt-in per
call site via fdpic_callback(), and this covers the rest of them:
pthread_create, signal, task_create, pthread_once, task_spawn, scandir and
sigaction.

Resolution happens once, in the innermost routine the paths share, for the
reason qsort already documents: resolving twice would treat a code address as
a descriptor.  task_create_with_stack resolves and task_create forwards to
it; nxsig_action() resolves for both signal() and a direct sigaction(), on
the local copy it already makes, so the caller's const struct is untouched
and the SA_SIGINFO form is covered through the union.

Two entry points need care beyond that pattern:

signal() has to exclude the dispositions by hand.  SIG_IGN, SIG_DFL, SIG_HOLD
and SIG_ERR are the integers 0, 1, 2 and -1 rather than addresses, and
fdpic_callback() declines to dereference NULL and nothing else -- handing it
SIG_ERR would read through (void *)-1.

scandir takes two pointers.  Its filter is called by scandir itself and is
resolved here; its comparison function is handed to qsort(), whose own entry
point resolves it, so resolving it here as well would resolve twice.  The
test passes both at once, which is what exercises that distinction.

Resolving the code address is sufficient at all of these.  A thread or task
created from a module inherits its D-Space -- nxtask_dup_dspace() runs before
up_initial_state() installs it in the FDPIC register -- and a signal handler
or pthread_once init routine runs in a context that already holds the
module's data base.

Verified on an RP2350 with a module that hands its own function to each entry
point.  Each assertion was also checked against a deliberate breakage:
removing the scandir filter resolution, and adding a second resolution of the
comparison function, each take the board down with a HardFault rather than
printing a FAIL line -- which is how a broken callback resolution manifests on
Cortex-M, and confirms the assertions test what they name.

mq_notify and timer_create with SIGEV_THREAD remain, and are harder: their
callback runs on a work-queue worker that carries no module data base at all.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-08-01 12:23:27 +02:00

363 lines
12 KiB
C

/****************************************************************************
* sched/task/task_spawn.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 <sys/wait.h>
#include <sched.h>
#include <spawn.h>
#include <assert.h>
#include <nuttx/debug.h>
#include <errno.h>
#include <nuttx/fs/fs.h>
#include <nuttx/sched.h>
#include <nuttx/kthread.h>
#include <nuttx/spawn.h>
#ifdef CONFIG_FDPIC
# include <nuttx/binfmt/fdpic.h>
#endif
#include "sched/sched.h"
#include "group/group.h"
#include "task/spawn.h"
#include "task/task.h"
#ifndef CONFIG_BUILD_KERNEL
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: nxtask_spawn_create
*
* Description:
* This function creates and activates a new thread of the specified type
* with a specified priority and returns its system-assigned ID. It is the
* internal, common implementation of task_create() and kthread_create().
* See comments with task_create() for further information.
*
* Input Parameters:
* name - Name of the new task
* ttype - Type of the new task
* priority - Priority of the new task
* stack_addr - Address of the stack needed
* stack_size - Size (in bytes) of the stack needed
* entry - Entry point of a new task
* arg - A pointer to an array of input parameters. The array
* should be terminated with a NULL argv[] value. If no
* parameters are required, argv may be NULL.
* envp - A pointer to an array of environment strings. Terminated
* with a NULL entry.
* actions - The spawn file actions
*
* Returned Value:
* Returns the positive, non-zero process ID of the new task or a negated
* errno value to indicate the nature of any failure. If memory is
* insufficient or the task cannot be created -ENOMEM will be returned.
*
****************************************************************************/
static int nxtask_spawn_create(FAR const char *name, int priority,
FAR void *stack_addr, int stack_size,
main_t entry, FAR char * const argv[],
FAR char * const envp[],
FAR const posix_spawn_file_actions_t *actions,
FAR const posix_spawnattr_t *attr)
{
FAR struct tcb_s *tcb;
pid_t pid;
int ret;
/* Allocate a TCB for the new task. */
tcb = kmm_zalloc(sizeof(struct tcb_s));
if (tcb == NULL)
{
serr("ERROR: Failed to allocate TCB\n");
return -ENOMEM;
}
/* Setup the task type */
tcb->flags = TCB_FLAG_TTYPE_TASK | TCB_FLAG_FREE_TCB;
/* Initialize the task */
ret = nxtask_init(tcb, name, priority, stack_addr, stack_size,
entry, argv, envp, actions);
if (ret < OK)
{
kmm_free(tcb);
return ret;
}
/* Get the assigned pid before we start the task */
pid = tcb->pid;
/* Set the attributes */
if (attr)
{
ret = spawn_execattrs(pid, attr);
if (ret < 0)
{
goto errout_with_taskinit;
}
}
/* Activate the task */
nxtask_activate(tcb);
return pid;
errout_with_taskinit:
nxtask_uninit(tcb);
return ret;
}
/****************************************************************************
* Name: nxtask_spawn_exec
*
* Description:
* Execute the task from the file system.
*
* Input Parameters:
*
* pidp - Upon successful completion, this will return the task ID of the
* child task in the variable pointed to by a non-NULL 'pid' argument.|
*
* name - The name to assign to the child task.
*
* entry - The child task's entry point (an address in memory)
*
* actions - The spawn file actions
*
* attr - If the value of the 'attr' parameter is NULL, the all default
* values for the POSIX spawn attributes will be used. Otherwise, the
* attributes will be set according to the spawn flags. The
* following spawn flags are supported:
*
* - POSIX_SPAWN_SETSCHEDPARAM: Set new tasks priority to the sched_param
* value.
* - POSIX_SPAWN_SETSCHEDULER: Set the new tasks scheduler priority to
* the sched_policy value.
*
* NOTE: POSIX_SPAWN_SETSIGMASK is handled in nxtask_spawn_proxy().
*
* argv - argv[] is the argument list for the new task. argv[] is an
* array of pointers to null-terminated strings. The list is terminated
* with a null pointer.
*
* envp - A pointer to an array of environment strings. Terminated with
* a NULL entry.
*
* Returned Value:
* This function will return zero on success. Otherwise, an error number
* will be returned as the function return value to indicate the error.
* This errno value may be that set by execv(), sched_setpolicy(), or
* sched_setparam().
*
****************************************************************************/
static int nxtask_spawn_exec(FAR pid_t *pidp, FAR const char *name,
main_t entry,
FAR const posix_spawn_file_actions_t *actions,
FAR const posix_spawnattr_t *attr,
FAR char * const *argv, FAR char * const envp[])
{
FAR void *stackaddr = NULL;
size_t stacksize;
int priority;
int pid;
int ret = OK;
/* Use the default priority and stack size if no attributes are provided */
if (attr)
{
priority = attr->priority;
stacksize = attr->stacksize;
stackaddr = attr->stackaddr;
}
else
{
struct sched_param param;
/* Set the default priority to the same priority as this task */
ret = nxsched_get_param(0, &param);
if (ret < 0)
{
return ret;
}
priority = param.sched_priority;
stacksize = CONFIG_POSIX_SPAWN_DEFAULT_STACKSIZE;
}
/* A zero priority/stacksize means "use the default": inherit the parent
* task's priority and fall back to CONFIG_POSIX_SPAWN_DEFAULT_STACKSIZE.
* This lets posix_spawnattr_init() leave these fields zero so that the
* binary loader can supply them from the loaded ELF (binp->priority /
* binp->stacksize) instead.
*/
if (priority == 0)
{
struct sched_param param;
nxsched_get_param(0, &param);
priority = param.sched_priority;
}
if (stacksize == 0)
{
stacksize = CONFIG_POSIX_SPAWN_DEFAULT_STACKSIZE;
}
/* Start the task */
pid = nxtask_spawn_create(name, priority, stackaddr,
stacksize, entry, argv,
envp ? envp : environ, actions, attr);
if (pid < 0)
{
ret = pid;
serr("ERROR: nxtask_spawn_create failed: %d\n", ret);
return ret;
}
/* Return the task ID to the caller */
if (pid)
{
*pidp = pid;
}
return ret;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: task_spawn
*
* Description:
* The task_spawn() function will create a new, child task, where the
* entry point to the task is an address in memory.
*
* Input Parameters:
*
* name - The name to assign to the child task.
*
* entry - The child task's entry point (an address in memory)
*
* file_actions - If 'file_actions' is a null pointer, then file
* descriptors open in the calling process will remain open in the
* child process (unless CONFIG_FDCLONE_STDIO is defined). If
* 'file_actions' is not NULL, then the file descriptors open in the
* child process will be those open in the calling process as modified
* by the spawn file actions object pointed to by file_actions.
*
* attr - If the value of the 'attr' parameter is NULL, the all default
* values for the POSIX spawn attributes will be used. Otherwise, the
* attributes will be set according to the spawn flags. The
* posix_spawnattr_t spawn attributes object type is defined in spawn.h.
* It will contains these attributes, not all of which are supported by
* NuttX:
*
* - POSIX_SPAWN_SETPGROUP: Setting of the new task's process group is
* not supported. NuttX does not support process groups.
* - POSIX_SPAWN_SETSCHEDPARAM: Set new tasks priority to the sched_param
* value.
* - POSIX_SPAWN_SETSCHEDULER: Set the new task's scheduler policy to
* the sched_policy value.
* - POSIX_SPAWN_RESETIDS: Resetting of the effective user ID of the
* child process is not supported. NuttX does not support effective
* user IDs.
* - POSIX_SPAWN_SETSIGMASK: Set the new task's signal mask.
* - POSIX_SPAWN_SETSIGDEF: Resetting signal default actions is not
* supported. NuttX does not support default signal actions.
*
* And the non-standard:
*
* - TASK_SPAWN_SETSTACKSIZE: Set the stack size for the new task.
*
* argv - argv[] is the argument list for the new task. argv[] is an
* array of pointers to null-terminated strings. The list is terminated
* with a null pointer.
*
* envp - envp[] is an array of character pointers to null-terminated
* strings that provide the environment for the new process image.
*
* Returned Value:
* task_spawn() will return process ID of new task on success.
* Otherwise, a negative number will be returned as the function return
* value to indicate the error:
*
* - EINVAL: The value specified by 'file_actions' or 'attr' is invalid.
* - Any errors that might have been return if vfork() and excec[l|v]()
* had been called.
*
****************************************************************************/
int task_spawn(FAR const char *name, main_t entry,
FAR const posix_spawn_file_actions_t *file_actions,
FAR const posix_spawnattr_t *attr,
FAR char * const argv[], FAR char * const envp[])
{
pid_t pid = INVALID_PROCESS_ID;
int ret;
#ifdef CONFIG_FDPIC
/* An FDPIC module passes the address of a function descriptor, not a code
* address. Resolve it here, once, in the public entry point.
*
* The new task inherits the creator's D-Space, so it starts with the
* module's own data base installed and needs only the code address.
*/
entry = (main_t)fdpic_callback((FAR void *)entry);
#endif
sinfo("name=%s entry=%p file_actions=%p attr=%p argv=%p\n",
name, entry, file_actions, attr, argv);
ret = nxtask_spawn_exec(&pid, name, entry,
file_actions != NULL ? *file_actions : NULL,
attr, argv, envp);
return ret >= 0 ? pid : ret;
}
#endif /* CONFIG_BUILD_KERNEL */