root/usr/src/uts/common/os/spawn.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2026 Oxide Computer Company
 */

/*
 * spawn(2): in-kernel process creation for posix_spawn(3C)
 * --------------------------------------------------------
 *
 * posix_spawn(3C) and posix_spawnp(3C) create a new process running a
 * named program. They were historically implemented in libc on top of
 * vforkx() where the library borrowed the parent's address space,
 * stopped every other thread in the process, ran the file actions and
 * attribute changes in the borrowed image, and exec'd. This was
 * rather expensive in the face of a highly-threaded parent. Stopping
 * the other threads serialises a multi-threaded program around each
 * spawn, and a child that blocks (for example opening a FIFO as a
 * file action) blocks the whole process with it.
 *
 * spawn(2) moves the work into the kernel. It is a private system call,
 * used only by libc to implement the posix_spawn() family, and is not a
 * committed interface. The parent describes the whole operation up
 * front, the kernel creates a child with a single kernel-resident LWP,
 * and that LWP applies the requested changes to itself and execs the
 * target. No address space is copied and no threads are stopped. A
 * process can spawn(2) in all its threads and concurrency is limited
 * only by locks that need to be held shortly when processes are
 * created.
 *
 * The libc/kernel boundary
 * ------------------------
 *
 * Everything the child needs is marshalled by libc into two flat,
 * position-independent blobs and copied in as the spawn(2) arguments:
 *
 *    o spawn_param_t -- the attributes and the ordered file actions.
 *    o spawn_args_t  -- the argv and envp string vectors.
 *
 * Both have fixed headers that carry byte offsets and counts into a
 * trailing data[] region. Since the blobs come from userland they are
 * untrusted; spawn_param_verify() and spawn_args_verify() check every
 * offset, length and count for consistency and overflow before
 * anything is dereferenced. Anything malformed results in an EINVAL.
 *
 * Creating the child
 * ------------------
 *
 * Process creation reuses cfork(), which recognises a spawn by its
 * non-NULL kspawn_param_t. Unlike fork() it does not hold the other
 * LWPs and unlike both fork() and vfork() it neither duplicates nor
 * borrows the parent's address space. The child is born with a single
 * LWP, running in the kernel, with kas for its p_as. /proc and
 * relvm() already treat a process in this state as having no address
 * space, the same as the window during an ordinary exec. That single
 * LWP runs spawn_main().
 *
 * The child: spawn_main()
 * -----------------------
 *
 * spawn_main() first arranges for the LWP to appear as though it is
 * returning from an execve(2) call - that is in effect what it is
 * about to do. It then, in the order the old libc implementation used:
 *
 *    o applies the attributes - signal mask and dispositions, RESETIDS,
 *      SETSID, SETPGROUP, and last the scheduling class and priority.
 *      These are last so the credential checks see the post-RESETIDS
 *      ids;
 *    o applies the file actions in the caller's order - open, close,
 *      dup2, closefrom, chdir, fchdir;
 *    o execs the target. For posix_spawnp() the PATH search and the
 *      ENOEXEC "run it as a shell script" fallback happen here, in the
 *      child and after the file actions, so a relative PATH entry
 *      resolves against whatever directory the file actions chose.
 *
 * On success the LWP enters userland through lwp_rtt_initial() and
 * the new program runs.
 *
 * The handshake
 * -------------
 *
 * The parent's spawn(2) call must not return until the child has
 * either started its new program or failed, and in the failing case
 * it must learn the error. The two LWPs synchronise through the
 * kspawn_param_t (ksp). The parent owns ksp for the whole operation
 * in that it allocates it, and frees it once the child has signalled.
 * The child only sets the result and signals, and must not touch it
 * once it has signalled.
 *
 *   parent thread -- spawn()          child LWP -- spawn_main()
 *   ------------------------          ------------------------
 *   copyin + verify the blob
 *   mutex_enter(ksp_lock)
 *   cfork() ----------------------->  p_spawn_ksp = ksp; new LWP runs:
 *   while (!ksp_complete)               apply attributes, file actions
 *       cv_wait_sig / cv_wait            exec the target
 *                                      spawn_complete(ksp, err):
 *                                        ksp_error = err
 *                                        ksp_complete = true
 *   woken  <---------------------------- cv_signal(ksp_cv)
 *   ksp_complete now set, loop ends    then one of:
 *                                        ok        -> lwp_rtt_initial()
 *                                        NOEXECERR -> exit(127)
 *                                        failed    -> CLDEVAPORATE; exit
 *   return pid, or set_errno(ksp_error)
 *
 * If we didn't do anything else, a child killed before it reaches
 * spawn_complete() would leave the parent waiting forever.
 * proc_exit() deals with this by completing the handshake on the
 * child's behalf should it exit with p_spawn_ksp still set. Such a
 * child does not set CLDEVAPORATE, so it becomes an ordinary zombie,
 * matching a fork(2) child that is killed before it can exec.
 *
 * The parent waits with cv_wait_sig() so that job control and /proc
 * keep their usual effect on it. Once a signal is actually delivered
 * it switches to an uninterruptible cv_wait(), because it cannot
 * return and free ksp while the child may still reference it. A fatal
 * signal aimed only at the parent therefore does not unwedge it until
 * the child reports back. This is consistent with what happens with a
 * vfork() parent in vfwait().
 *
 * Running without holdlwps
 * ------------------------
 *
 * Skipping holdlwps() is what makes spawn(2) cheap, but it means the
 * parent's other threads keep running while the child is built. Each
 * thing the child copies out of the parent must therefore be taken
 * atomically with respect to those threads:
 *
 *    o the fd table is copied by flist_spawn(), which locks each
 *      entry as it copies it (see "Copying the file descriptor table"
 *      below);
 *    o the uarea, cwd/root vnodes and signal state are copied in
 *      under p_lock, so a concurrent chdir() or sigaction() cannot
 *      change them.
 *
 * That the parent is still running is the main thing to bear in mind
 * when reading the rest of this file.
 *
 * Copying the file descriptor table
 * ---------------------------------
 *
 * The child's descriptor table is built by flist_spawn() rather than
 * the flist_fork() that fork() and vfork() use. It differs for two
 * reasons.
 *
 * The first is locking, as above. flist_fork() copies the table
 * without per-entry locks, which is safe only because the other LWPs
 * are held. flist_spawn() takes each entry's lock as it reads and
 * copies it, and takes the hold on the underlying file while that
 * lock is still held, so a sibling thread cannot close the descriptor
 * and drop its last reference midway through.
 *
 * The second is that a spawn is logically equivalent to a fork
 * immediately followed by an exec, so a descriptor that cannot play
 * any part in the exec'd image need not be copied at all. Descriptors
 * are kept only if they would still be present in the new program, or
 * if a file action needs it first:
 *
 *    o an FD_CLOFORK descriptor is dropped, as it is by fork();
 *    o a descriptor that is not FD_CLOEXEC and lies below any
 *      closefrom() file action's bound is kept;
 *    o a descriptor named as the source of a dup2 or fchdir file action
 *      is kept even when it is FD_CLOEXEC or above the closefrom bound,
 *      because the action must still read it;
 *    o anything else is dropped.
 *
 * A descriptor kept only because an action references it, but which
 * is FD_CLOEXEC, is still closed by close_exec() at the exec. The set
 * of descriptors the new program sees is therefore unchanged, the
 * selective copy being purely an optimisation.
 *
 * Skipping descriptors also lets the child's table be sized to the
 * highest descriptor actually kept, rather than to the parent's
 * highest. A process that has at some point opened a high-numbered
 * descriptor (say fd 60000), with FD_CLOEXEC would otherwise force
 * every child it spawns to allocate a 60000-entry table only to close
 * it again at exec. Two passes of the table are made. The first finds
 * the highest kept descriptor and sizes the child's table to it. The
 * second walks that range and, under each entry's lock, re-evaluates the
 * keep decision and copies the descriptor. These two passes are not one
 * atomic snapshot of the whole table. A change a sibling makes within the
 * sized range is picked up by the second pass, but a descriptor opened
 * above that range between the passes is not copied by this spawn, the
 * same way an fd change made concurrently with fork(2) may or may not be
 * seen by the child. Either way no descriptor is ever torn, since each is
 * copied while its entry is locked.
 *
 * Exec'ing from kernel memory
 * ---------------------------
 *
 * A normal execve(2) reads its argv and envp from the calling
 * process's user address space. The spawn child has no user address
 * space yet, and its arguments sit in the marshalled blob in kernel
 * memory. exec_common() therefore takes a uio_seg_t saying where the
 * vectors live, and spawn_main() builds native char * arrays that
 * point into the blob. The standard exec code then copies the strings
 * onto the new user stack from kernel rather than user memory, but is
 * otherwise unchanged. Every other caller passes UIO_USERSPACE and
 * follows the original path.
 *
 * Privileges
 * ----------
 *
 * spawn(2) grants nothing that a fork(2) and exec(2) pair could not.
 * Entry is gated by the same secpolicy_basic_fork() check fork() uses,
 * and the child runs with the credentials it inherits from the parent.
 * POSIX_SPAWN_RESETIDS is the only attribute that touches credentials,
 * and it only ever drops privilege, resetting the effective uid and
 * gid to the real uid and gid. The exec itself performs the usual
 * checks on the target, including the handling of set-id binaries,
 * just as a direct execve(2) would.
 *
 * Observability
 * -------------
 *
 * To the proc DTrace provider and to /proc, a spawn looks like a fork
 * followed by an exec and fires the same probes. While it is being
 * built the child carries SSPAWNING, which makes /proc control
 * operations on it fail with EBUSY, as for a system process. A child
 * whose setup fails evaporates without ever being seen by its parent,
 * so the new sdt:::spawn-error probe records the failing stage and
 * errno, and the ::spawn mdb dcmd lists the spawns currently in
 * flight.
 *
 * Before it execs, a spawn child is in the same state as any process
 * caught partway through an ordinary exec(). In particular it has no
 * address space of its own (p_as == kas). /proc already knows how to
 * treat such a process, and the entry points divide into three groups by
 * how much of that existing handling already covers a spawn child:
 *
 *    o The observation paths and pr_set() already test p_as == kas and
 *      report a process with no address space as a system process. A
 *      half-built spawn child falls into that same part-exec'd category.
 *
 *    o The control entry points pr_control[32]() gate only on SSYS,
 *      never on kas. A spawn child is not SSYS, so we also need to
 *      test the SSPAWNING flag here so that an operation such as PCSTOP
 *      does not try to stop a process executing in the kernel.
 *
 *    o The legacy stop ioctls (PIOCSTOP, PIOCWSTOP) must test both kas
 *      and SSPAWNING, because the kas test has a gap at the tail of
 *      exec. The new address space is installed before SSPAWNING is
 *      cleared and SEXECED set in exec_common(). In that window the
 *      child has its own address space but has not finished exec'ing.
 *
 * We deliberately do not expose a dedicated "this is a spawning child"
 * flag through /proc. This is consistent with what is in place for
 * a vfork child that has not yet exec'd. If required in the future,
 * a PR_SPAWNING bit could be added to extend the committed procfs
 * interface.
 *
 * Auditing
 * --------
 *
 * A real fork() followed by an exec() produces two audit records in
 * two processes - the fork in the parent and the exec in the child. A
 * spawn is a single system call in the parent, and it audits as one
 * event, AUE_SPAWN.
 *
 * The record is assembled entirely in the parent's spawn(2) context.
 * The process-creation part is added by audit_newproc() during
 * getproc(), as it is for fork(). Once the child has reported back,
 * audit_spawn() adds the exec detail. The path is recorded as a text
 * token, the attributes of the exec'd file as an attribute token, and,
 * subject to the audit_argv and audit_arge policies, the argument and
 * environment vectors are included. The path is ksp_path, which for
 * posix_spawnp() is the name the child exec'd after the path search,
 * not necessarily the name the caller passed. In the ENOEXEC shell
 * fallback case the audited argv is the one the child built - "sh"
 * followed by the resolved script path and the caller's remaining
 * arguments - so the record reflects that the shell, not the script,
 * was the program loaded.
 */

#include <sys/class.h>
#include <sys/cmn_err.h>
#include <sys/cred.h>
#include <sys/ddi.h>
#include <sys/debug.h>
#include <sys/errno.h>
#include <sys/exec.h>
#include <sys/fcntl.h>
#include <sys/file.h>
#include <sys/fork.h>
#include <sys/kmem.h>
#include <sys/param.h>
#include <sys/pgrpsys.h>
#include <sys/proc.h>
#include <sys/sdt.h>
#include <sys/signal.h>
#include <sys/spawn.h>
#include <sys/spawn_impl.h>
#include <sys/sunddi.h>
#include <sys/syscall.h>
#include <sys/sysmacros.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/vnode.h>

#include <c2/audit.h>

extern int64_t cfork(int, int, kspawn_param_t *, int);

extern int setpgrp(int, int, int);
extern int setuid(uid_t);
extern int setgid(gid_t);
extern int fchdir(int);
extern int kchdir(const char *);
extern int64_t lwp_sigmask(int, uint_t, uint_t, uint_t, uint_t);
extern int setthreadprio(pcprio_t *, kthread_t *);

static const spawn_attr_t *
spawn_param_attr(const spawn_param_t *sp)
{
        if (sp == NULL || sp->sp_attr_len == 0)
                return (NULL);
        return ((const spawn_attr_t *)&sp->sp_data[sp->sp_attr_off]);
}

/*
 * Signal completion to the parent which is waiting in spawn(2).
 */
void
spawn_complete(kspawn_param_t *ksp, int err)
{
        curproc->p_spawn_ksp = NULL;

        mutex_enter(&ksp->ksp_lock);
        ksp->ksp_error = err;
        ksp->ksp_complete = true;
        cv_signal(&ksp->ksp_cv);
        mutex_exit(&ksp->ksp_lock);
}

/*
 * Apply the spawn attributes in the child.
 */
static int
spawn_attrs_apply(const spawn_param_t *sp)
{
        const spawn_attr_t *spa = spawn_param_attr(sp);
        klwp_t *lwp = ttolwp(curthread);
        proc_t *p = curproc;
        int sig;

        if (spa == NULL)
                return (0);

        if (spa->sa_psflags & POSIX_SPAWN_SETSIGMASK) {
                (void) lwp_sigmask(SIG_SETMASK,
                    spa->sa_sigmask.__sigbits[0],
                    spa->sa_sigmask.__sigbits[1],
                    spa->sa_sigmask.__sigbits[2],
                    spa->sa_sigmask.__sigbits[3]);
        }

        if (spa->sa_psflags & POSIX_SPAWN_SETSIGIGN_NP) {
                k_sigset_t kset;

                sigutok(&spa->sa_sigignore, &kset);
                for (sig = 1; sig < NSIG; sig++) {
                        if (sigismember(&kset, sig) &&
                            !sigismember(&cantmask, sig)) {
                                mutex_enter(&p->p_lock);
                                setsigact(sig, SIG_IGN, &nullsmask, 0);
                                mutex_exit(&p->p_lock);
                        }
                }
        }

        if (spa->sa_psflags & POSIX_SPAWN_SETSIGDEF) {
                k_sigset_t kset;

                sigutok(&spa->sa_sigdefault, &kset);
                for (sig = 1; sig < NSIG; sig++) {
                        if (sigismember(&kset, sig) &&
                            !sigismember(&cantmask, sig)) {
                                mutex_enter(&p->p_lock);
                                setsigact(sig, SIG_DFL, &nullsmask, 0);
                                mutex_exit(&p->p_lock);
                        }
                }
        }

        if (spa->sa_psflags & POSIX_SPAWN_RESETIDS) {
                lwp->lwp_errno = 0;
                if (setgid(crgetrgid(CRED())) != 0 ||
                    setuid(crgetruid(CRED())) != 0) {
                        return (lwp->lwp_errno);
                }
        }

        if (spa->sa_psflags & POSIX_SPAWN_SETSID) {
                /*
                 * setpgrp() reports failure through lwp_errno. Its return
                 * value with the SETSID subcommand is a session ID.
                 */
                lwp->lwp_errno = 0;
                (void) setpgrp(PGRPSYS_SETSID, 0, 0);
                if (lwp->lwp_errno != 0)
                        return (lwp->lwp_errno);
        }

        if (spa->sa_psflags & POSIX_SPAWN_SETPGROUP) {
                lwp->lwp_errno = 0;
                if (setpgrp(PGRPSYS_SETPGID, 0, spa->sa_pgroup) != 0)
                        return (lwp->lwp_errno);
        }

        /*
         * The scheduling attributes are applied last, once any RESETIDS,
         * SETSID and SETPGROUP changes are in place. RESETIDS in particular
         * must come first so that the privilege checks made while setting the
         * scheduling parameters see the child's final credentials.
         */
        if ((spa->sa_psflags &
            (POSIX_SPAWN_SETSCHEDULER | POSIX_SPAWN_SETSCHEDPARAM)) != 0) {
                kspawn_sched_t ks;
                int err = 0;

                bcopy(&sp->sp_data[sp->sp_sched_off], &ks, sizeof (ks));

                switch (ks.ksched_op) {
                case KSCHED_PARMS:
                        err = parmsin(&ks.ksched_parms, NULL);
                        break;
                case KSCHED_PRIO:
                        /* The same check that doprio() applies */
                        if (ks.ksched_prio.pc_cid >= loaded_classes ||
                            ks.ksched_prio.pc_cid < 1) {
                                err = EINVAL;
                        }
                        break;
                }

                if (err != 0)
                        return (err);

                mutex_enter(&pidlock);
                mutex_enter(&p->p_lock);
                if (ks.ksched_op == KSCHED_PARMS)
                        err = parmsset(&ks.ksched_parms, curthread);
                else
                        err = setthreadprio(&ks.ksched_prio, curthread);
                mutex_exit(&p->p_lock);
                mutex_exit(&pidlock);

                if (err != 0)
                        return (err);
        }

        return (0);
}

static int
spawn_factions_apply(const kspawn_param_t *ksp)
{
        const spawn_param_t *sp = ksp->ksp_param;
        klwp_t *lwp = ttolwp(curthread);
        uint32_t off;

        if (sp == NULL || sp->sp_fattr_cnt == 0)
                return (0);

        off = sp->sp_fattr_off;
        for (uint32_t i = 0; i < sp->sp_fattr_cnt; i++) {
                const kfile_attr_t *kfa =
                    (const kfile_attr_t *)&sp->sp_data[off];
                int err = 0;
                int fd;

                switch (kfa->kfa_type) {
                case FA_OPEN:
                        fd = kopenat(AT_FDCWD, (char *)kfa->kfa_path,
                            kfa->kfa_oflag, kfa->kfa_mode,
                            ksp->ksp_parent_model);
                        if (fd < 0) {
                                err = lwp->lwp_errno;
                        } else if (fd != kfa->kfa_filedes) {
                                err = fdup2(fd, kfa->kfa_filedes);
                                (void) closeandsetf(fd, NULL);
                        }
                        break;
                case FA_CLOSE:
                        err = closeandsetf(kfa->kfa_filedes, NULL);
                        /* An already-closed descriptor is not an error */
                        if (err == EBADF)
                                err = 0;
                        break;
                case FA_DUP2:
                        err = fdup2(kfa->kfa_filedes, kfa->kfa_newfiledes);
                        break;
                case FA_CLOSEFROM:
                        closefrom_all(kfa->kfa_filedes);
                        break;
                case FA_CHDIR:
                        err = kchdir((const char *)kfa->kfa_path);
                        break;
                case FA_FCHDIR:
                        lwp->lwp_errno = 0;
                        if (fchdir(kfa->kfa_filedes) != 0)
                                err = lwp->lwp_errno;
                        break;
                }

                if (err != 0)
                        return (err);

                off += kfa->kfa_len;
        }

        return (0);
}

/*
 * Build a NULL-terminated vector of pointers to the packed, NUL-terminated
 * strings in the spawn args data area.
 */
static char **
spawn_vector(const spawn_args_t *sa, uint32_t off, uint32_t cnt)
{
        char **vec = kmem_alloc(((size_t)cnt + 1) * sizeof (char *), KM_SLEEP);

        for (uint32_t i = 0; i < cnt; i++) {
                vec[i] = (char *)&sa->sa_data[off];
                off += strlen(vec[i]) + 1;
        }
        vec[cnt] = NULL;

        return (vec);
}

/*
 * Build the path name for the next attempt in a PATH search by joining the
 * leading component of the search path with the program name. Returns the
 * remainder of the search path or NULL if we're done. Sets *fits to false
 * if the joined name would not fit in buf, in which case buf is not filled
 * and the caller must skip this candidate rather than exec a truncated path.
 */
static const char *
spawn_execat(const char *path, const char *name, char *buf, size_t bufl,
    bool *fits)
{
        const char *sep = strchr(path, ':');
        size_t dirlen = (sep == NULL) ? strlen(path) : (size_t)(sep - path);
        size_t namelen = strlen(name);
        size_t need = dirlen + namelen + 1;
        char *s = buf;

        if (dirlen > 0)
                need++;         /* for the '/' separator */

        *fits = (need <= bufl);
        if (*fits) {
                bcopy(path, s, dirlen);
                s += dirlen;
                if (dirlen > 0)
                        *s++ = '/';
                bcopy(name, s, namelen);
                s[namelen] = '\0';
        }

        return (sep != NULL ? sep + 1 : NULL);
}

/*
 * Record the attributes of the file we have just exec'd so that the parent can
 * include them in the spawn(2) audit record, the same attribute token an
 * ordinary exec(2) produces. The exec'd file is now the process's p_exec. This
 * runs in the child after a successful exec_common() and before it reports back
 * to the parent, so the parent sees the result. It is only worth the
 * vop_getattr() when auditing is active.
 */
static void
spawn_capture_vattr(kspawn_param_t *ksp)
{
        if (!ksp->ksp_audit)
                return;

        ksp->ksp_vattr.va_mask = AT_ALL;
        if (VOP_GETATTR(curproc->p_exec, &ksp->ksp_vattr, 0, CRED(), NULL) == 0)
                ksp->ksp_have_vattr = true;
}

/*
 * Marshal the argument vector the child actually exec'd into ksp_argv, as a run
 * of nul-terminated strings, so the parent audits it in place of the caller's
 * argv. This is only needed for the ENOEXEC shell fallback and only when
 * auditing is active.
 */
static void
spawn_capture_argv(kspawn_param_t *ksp, char *const *argv)
{
        size_t sz = 0;
        uint_t argc = 0;
        char *p;

        if (!ksp->ksp_audit)
                return;

        for (uint_t i = 0; argv[i] != NULL; i++) {
                sz += strlen(argv[i]) + 1;
                argc++;
        }
        if (sz == 0)
                return;

        p = kmem_alloc(sz, KM_SLEEP);
        ksp->ksp_argv = p;
        ksp->ksp_argvsz = sz;
        ksp->ksp_argc = argc;
        for (uint_t i = 0; argv[i] != NULL; i++) {
                size_t len = strlen(argv[i]) + 1;

                bcopy(argv[i], p, len);
                p += len;
        }
}

/*
 * Exec the target program. For posix_spawn() this is a single attempt at the
 * given path. For posix_spawnp(), libc supplies the search path and shell in
 * the spawn parameters, and we need to walk the path.
 *
 * On success the process is running the new image and this returns 0.
 */
static int
spawn_exec(kspawn_param_t *ksp)
{
        const spawn_args_t *sa = ksp->ksp_args;
        const spawn_param_t *sp = ksp->ksp_param;
        const char *pathstr = NULL, *shell = NULL, *cp;
        /*
         * Allow for prepending "./" below, should the resulting filename begin
         * with a '-'.
         */
        const size_t pathl = MAXPATHLEN + sizeof ("./");
        char **argv, **envp;
        char *path = NULL;
        int err = ENOENT;
        int saved_err = 0;

        argv = spawn_vector(sa, sa->sa_arg_off, sa->sa_arg_cnt);
        envp = spawn_vector(sa, sa->sa_env_off, sa->sa_env_cnt);

        if (sp != NULL && sp->sp_path_len != 0) {
                pathstr = (const char *)&sp->sp_data[sp->sp_path_off];
                if (sp->sp_shell_len != 0)
                        shell = (const char *)&sp->sp_data[sp->sp_shell_off];
        }

        if (pathstr == NULL) {
                /* posix_spawn() - the simple case with the given path */
                err = exec_common(ksp->ksp_path, (const char **)argv,
                    (const char **)envp, NULL, EBA_NONE, UIO_SYSSPACE);
                if (err == 0)
                        spawn_capture_vattr(ksp);
                goto out;
        }

        path = kmem_alloc(pathl, KM_SLEEP);

        cp = pathstr;
        do {
                bool fits;

                cp = spawn_execat(cp, ksp->ksp_path, path, MAXPATHLEN + 1,
                    &fits);
                if (!fits) {
                        /*
                         * This candidate does not fit in the buffer. Skip it
                         * rather than exec a truncated path, remembering the
                         * error in case the search finds nothing better.
                         */
                        err = ENAMETOOLONG;
                        if (saved_err == 0)
                                saved_err = ENAMETOOLONG;
                        continue;
                }

                /*
                 * If the resulting filename begins with a '-', prepend "./"
                 * so that the shell cannot interpret it as an option.
                 */
                if (*path == '-') {
                        memmove(path + 2, path, strlen(path) + 1);
                        path[0] = '.';
                        path[1] = '/';
                }

                err = exec_common(path, (const char **)argv,
                    (const char **)envp, NULL, EBA_NONE, UIO_SYSSPACE);
                if (err == 0) {
                        /*
                         * Record the path the search resolved to so
                         * the parent can audit it. The copy will fit
                         * in ksp_path since our call to exec_common()
                         * succeeded with this path.
                         */
                        (void) strlcpy(ksp->ksp_path, path,
                            sizeof (ksp->ksp_path));
                        spawn_capture_vattr(ksp);
                        goto out;
                }

                /*
                 * Remember the most meaningful error seen during the search
                 * (matching execvp). A candidate that existed but could not be
                 * executed (EACCES) outranks both a later "not found" and an
                 * over-long candidate that we had to skip.
                 */
                if (err == EACCES)
                        saved_err = EACCES;

                /*
                 * The candidate has execute permission but is not in a
                 * format the kernel recognises. That is, it is neither a
                 * binary nor a "#!" script, both of which the kernel's exec
                 * would run directly. We treat it as a bare shell script
                 * and re-exec it through the shell interpreter passed in
                 * from userland ("sh"), reproducing the historical
                 * execvp()-based posix_spawnp(). The PATH search stops
                 * here.
                 *
                 * Note that POSIX.1-2024 (Issue 8) now requires
                 * posix_spawnp() to fail with ENOEXEC here rather than fall
                 * back to sh (see Austin Group defect 1674). However, we
                 * keep the fallback for now to preserve the execvp()
                 * behaviour that callers may rely on.
                 */
                if (err == ENOEXEC) {
                        size_t nargs = (size_t)sa->sa_arg_cnt + 3;
                        char **newargs;
                        uint32_t i;

                        if (shell == NULL)
                                goto out;

                        newargs = kmem_alloc(nargs * sizeof (char *),
                            KM_SLEEP);
                        /*
                         * argv[0] is always the literal "sh", regardless of
                         * the shell path supplied by libc, matching the
                         * behaviour of execvp().
                         */
                        newargs[0] = "sh";
                        newargs[1] = path;
                        for (i = 1; i < sa->sa_arg_cnt; i++)
                                newargs[i + 1] = argv[i];
                        newargs[i + 1] = NULL;

                        err = exec_common(shell, (const char **)newargs,
                            (const char **)envp, NULL, EBA_NONE,
                            UIO_SYSSPACE);
                        if (err == 0) {
                                (void) strlcpy(ksp->ksp_path, shell,
                                    sizeof (ksp->ksp_path));
                                spawn_capture_vattr(ksp);
                                spawn_capture_argv(ksp, newargs);
                        }

                        kmem_free(newargs, nargs * sizeof (char *));
                        goto out;
                }
        } while (cp != NULL);

        /*
         * The search is exhausted without an exec. Prefer the most
         * meaningful error we saw over whichever happened to be last.
         */
        if (saved_err != 0)
                err = saved_err;

out:
        if (path != NULL)
                kmem_free(path, pathl);
        kmem_free(argv, ((size_t)sa->sa_arg_cnt + 1) * sizeof (char *));
        kmem_free(envp, ((size_t)sa->sa_env_cnt + 1) * sizeof (char *));

        return (err);
}

/*
 * The entry point for the single LWP of a spawned child, which begins life
 * here in the kernel. Apply the spawn attributes and file actions, exec the
 * target program, report the outcome to the waiting parent and, if
 * everything's ok, enter userland via lwp_rtt_initial().
 */
void
spawn_main(void *arg)
{
        kspawn_param_t *ksp = arg;
        klwp_t *lwp = ttolwp(curthread);
        proc_t *p = curproc;
        const spawn_attr_t *spa = spawn_param_attr(ksp->ksp_param);
        bool execfail = false;
        int err;

        ASSERT(p->p_spawn_ksp == ksp);

        /*
         * Make this LWP look as if it is completing an execve() system
         * call. /proc and post_syscall() rely on this.
         */
        bzero(lwp->lwp_arg, sizeof (lwp->lwp_arg));
        lwp->lwp_ap = lwp->lwp_arg;
        curthread->t_sysnum = SYS_execve;
        curthread->t_post_sys = 1;

        /*
         * The spawn-error probes identify the spawn parameters, the stage
         * at which the spawn failed and the error. A failed spawn child
         * usually evaporates without ever running in userland, and its
         * image is still the parent's, so these probes are the observable
         * record of what went wrong inside it.
         */
        if ((err = spawn_attrs_apply(ksp->ksp_param)) != 0) {
                DTRACE_PROBE3(spawn__error, kspawn_param_t *, ksp,
                    char *, "attributes", int, err);
        } else if ((err = spawn_factions_apply(ksp)) != 0) {
                DTRACE_PROBE3(spawn__error, kspawn_param_t *, ksp,
                    char *, "file-actions", int, err);
        } else if ((err = spawn_exec(ksp)) != 0) {
                DTRACE_PROBE3(spawn__error, kspawn_param_t *, ksp,
                    char *, "exec", int, err);
                execfail = true;
        }

        if (err == 0) {
                /*
                 * The exec succeeded. Release the parent and enter userland
                 * in the new program.
                 */
                spawn_complete(ksp, 0);
                lwp_rtt_initial();
                /* NOTREACHED */
        }

        if (execfail && spa != NULL &&
            (spa->sa_psflags & POSIX_SPAWN_NOEXECERR_NP) != 0) {
                /*
                 * POSIX_SPAWN_NOEXECERR_NP: an exec failure is not reported
                 * to the parent. It is told that the spawn succeeded, and
                 * the child exits with status 127 for the parent to observe
                 * via wait().
                 */
                spawn_complete(ksp, 0);
                exit(CLD_EXITED, SPAWN_NOEXECERR_STATUS);
                /* NOTREACHED */
        }

        /*
         * The error is reported to the parent and the parent never learns
         * this child's pid - it disappears without a trace and without
         * raising SIGCHLD.
         */
        mutex_enter(&pidlock);
        p->p_pidflag |= CLDEVAPORATE;
        mutex_exit(&pidlock);

        spawn_complete(ksp, err);
        exit(CLD_EXITED, 0);
        /* NOTREACHED */
}

static bool
spawn_region_ok(const spawn_param_t *sp, uint32_t off, uint32_t len)
{
        return (off <= sp->sp_datalen && len <= sp->sp_datalen - off);
}

/*
 * As spawn_region_ok(), additionally requiring that the region holds a
 * NUL-terminated string.
 */
static bool
spawn_str_ok(const spawn_param_t *sp, uint32_t off, uint32_t len)
{
        return (len != 0 && spawn_region_ok(sp, off, len) &&
            sp->sp_data[off + len - 1] == '\0');
}

static int
spawn_param_verify(const spawn_param_t *sp, uint32_t spsize)
{
        int schedflags = 0;

        if (sp->sp_size != spsize ||
            sp->sp_datalen != spsize - offsetof(spawn_param_t, sp_data)) {
                return (EINVAL);
        }

        if (sp->sp_attr_len != 0) {
                const spawn_attr_t *spa;

                if (sp->sp_attr_len != sizeof (spawn_attr_t) ||
                    !IS_P2ALIGNED(sp->sp_attr_off, sizeof (uint32_t)) ||
                    !spawn_region_ok(sp, sp->sp_attr_off, sp->sp_attr_len)) {
                        return (EINVAL);
                }

                spa = spawn_param_attr(sp);

                if ((spa->sa_psflags & ~ALL_POSIX_SPAWN_FLAGS) != 0)
                        return (EINVAL);
                if (spa->sa_pgroup < 0)
                        return (EINVAL);

                schedflags = spa->sa_psflags &
                    (POSIX_SPAWN_SETSCHEDULER | POSIX_SPAWN_SETSCHEDPARAM);
        } else if (sp->sp_attr_off != 0) {
                return (EINVAL);
        }

        /*
         * The resolved scheduling attributes are required when one of the
         * scheduling flags is set, and must not be present otherwise.
         */
        if (schedflags != 0) {
                const kspawn_sched_t *ks;

                if (sp->sp_sched_len != sizeof (kspawn_sched_t) ||
                    !IS_P2ALIGNED(sp->sp_sched_off, sizeof (uint32_t)) ||
                    !spawn_region_ok(sp, sp->sp_sched_off, sp->sp_sched_len)) {
                        return (EINVAL);
                }

                ks = (const kspawn_sched_t *)&sp->sp_data[sp->sp_sched_off];

                switch (ks->ksched_op) {
                case KSCHED_PARMS:
                        break;
                case KSCHED_PRIO:
                        if (ks->ksched_prio.pc_op != PC_SETPRIO)
                                return (EINVAL);
                        break;
                default:
                        return (EINVAL);
                }
        } else if (sp->sp_sched_len != 0 || sp->sp_sched_off != 0) {
                return (EINVAL);
        }

        if (sp->sp_fattr_cnt != 0) {
                uint32_t off = sp->sp_fattr_off;

                if (!IS_P2ALIGNED(off, sizeof (uint32_t)))
                        return (EINVAL);

                for (uint32_t i = 0; i < sp->sp_fattr_cnt; i++) {
                        const kfile_attr_t *kfa;
                        uint64_t reclen;

                        if (!spawn_region_ok(sp, off, sizeof (kfile_attr_t)))
                                return (EINVAL);

                        kfa = (const kfile_attr_t *)&sp->sp_data[off];

                        /*
                         * Each record is padded so that the next one remains
                         * 32-bit aligned.
                         */
                        reclen = P2ROUNDUP((uint64_t)sizeof (kfile_attr_t) +
                            kfa->kfa_pathsize, sizeof (uint32_t));
                        if (kfa->kfa_len != reclen ||
                            !spawn_region_ok(sp, off, kfa->kfa_len)) {
                                return (EINVAL);
                        }

                        /*
                         * Each action uses only some of these fields. The
                         * rest may hold arbitrary values, so an action's
                         * consumer must read only the fields for its type.
                         */
                        switch (kfa->kfa_type) {
                        case FA_OPEN:
                                /*
                                 * kfa_oflag and kfa_mode are not checked
                                 * here. kopenat() interprets them when the
                                 * action runs, as open(2) would.
                                 */
                                if (kfa->kfa_filedes < 0)
                                        return (EINVAL);
                                /* FALLTHROUGH */
                        case FA_CHDIR:
                                if (kfa->kfa_pathsize == 0 ||
                                    kfa->kfa_path[kfa->kfa_pathsize - 1] !=
                                    '\0') {
                                        return (EINVAL);
                                }
                                break;
                        case FA_CLOSE:
                        case FA_CLOSEFROM:
                        case FA_FCHDIR:
                                if (kfa->kfa_pathsize != 0 ||
                                    kfa->kfa_filedes < 0) {
                                        return (EINVAL);
                                }
                                break;
                        case FA_DUP2:
                                if (kfa->kfa_pathsize != 0 ||
                                    kfa->kfa_filedes < 0 ||
                                    kfa->kfa_newfiledes < 0) {
                                        return (EINVAL);
                                }
                                break;
                        default:
                                return (EINVAL);
                        }

                        off += kfa->kfa_len;
                }
        } else if (sp->sp_fattr_off != 0) {
                return (EINVAL);
        }

        if (sp->sp_shell_len != 0) {
                if (!spawn_str_ok(sp, sp->sp_shell_off, sp->sp_shell_len))
                        return (EINVAL);
        } else if (sp->sp_shell_off != 0) {
                return (EINVAL);
        }

        if (sp->sp_path_len != 0) {
                if (!spawn_str_ok(sp, sp->sp_path_off, sp->sp_path_len))
                        return (EINVAL);
        } else if (sp->sp_path_off != 0) {
                return (EINVAL);
        }

        return (0);
}

static int
spawn_args_verify(const spawn_args_t *sa, uint32_t sasize)
{
        uint32_t off;

        if (sa->sa_size != sasize ||
            sa->sa_datalen != sasize - offsetof(spawn_args_t, sa_data)) {
                return (EINVAL);
        }

        if (sa->sa_env_off > sa->sa_datalen ||
            sa->sa_arg_off > sa->sa_env_off) {
                return (EINVAL);
        }

        off = sa->sa_arg_off;
        for (uint32_t i = 0; i < sa->sa_arg_cnt; i++) {
                const char *s = (const char *)&sa->sa_data[off];
                const char *e = memchr(s, '\0', sa->sa_env_off - off);

                if (e == NULL)
                        return (EINVAL);
                off += (uint32_t)(e - s) + 1;
        }
        if (off != sa->sa_env_off)
                return (EINVAL);

        for (uint32_t i = 0; i < sa->sa_env_cnt; i++) {
                const char *s = (const char *)&sa->sa_data[off];
                const char *e = memchr(s, '\0', sa->sa_datalen - off);

                if (e == NULL)
                        return (EINVAL);
                off += (uint32_t)(e - s) + 1;
        }
        if (off != sa->sa_datalen)
                return (EINVAL);

        return (0);
}

/*
 * Pre-scan the file actions to determine which of the parent's file
 * descriptors the child actually needs, so that flist_spawn() can limit its
 * copy of the descriptor table:
 *
 *  - ksp_closefrom is the lowest closefrom() bound. Descriptors at or above
 *    it would be closed by the closefrom action anyway, so they need not be
 *    copied unless an action consumes them as a source.
 *  - ksp_reffds lists the descriptors that actions consume as sources -
 *    dup2() and fchdir() - which must be copied even if they carry
 *    FD_CLOEXEC or sit above the closefrom bound.
 *
 * This is purely an optimisation. Copying too much is harmless since
 * the file actions and close_exec() still run in the child.
 */
static void
spawn_prescan(const spawn_param_t *sp, kspawn_param_t *ksp)
{
        const kfile_attr_t *kfa;
        uint32_t off, i, n;

        ksp->ksp_closefrom = INT_MAX;

        if (sp == NULL || sp->sp_fattr_cnt == 0)
                return;

        n = 0;
        off = sp->sp_fattr_off;
        for (i = 0; i < sp->sp_fattr_cnt; i++) {
                kfa = (const kfile_attr_t *)&sp->sp_data[off];
                switch (kfa->kfa_type) {
                case FA_CLOSEFROM:
                        ksp->ksp_closefrom =
                            MIN(ksp->ksp_closefrom, kfa->kfa_filedes);
                        break;
                case FA_DUP2:
                case FA_FCHDIR:
                        n++;
                        break;
                default:
                        break;
                }
                off += kfa->kfa_len;
        }

        if (n == 0)
                return;

        /* We saw at least one dup2 or chdir. Build a list of source fds */

        ksp->ksp_reffds = kmem_alloc(n * sizeof (int), KM_SLEEP);
        ksp->ksp_nreffds = n;

        n = 0;
        off = sp->sp_fattr_off;
        for (i = 0; i < sp->sp_fattr_cnt; i++) {
                kfa = (const kfile_attr_t *)&sp->sp_data[off];
                if (kfa->kfa_type == FA_DUP2 || kfa->kfa_type == FA_FCHDIR) {
                        VERIFY3U(n, <, ksp->ksp_nreffds);
                        ksp->ksp_reffds[n++] = kfa->kfa_filedes;
                }
                off += kfa->kfa_len;
        }
}

static int
spawn_forkflags(const spawn_param_t *sp)
{
        const spawn_attr_t *spa = spawn_param_attr(sp);
        int flags = 0;

        if (spa != NULL) {
                if ((spa->sa_psflags & POSIX_SPAWN_NOSIGCHLD_NP) != 0)
                        flags |= FORK_NOSIGCHLD;
                if ((spa->sa_psflags & POSIX_SPAWN_WAITPID_NP) != 0)
                        flags |= FORK_WAITPID;
        }

        return (flags);
}

int64_t
spawn(void *path, void *sparam, uint32_t spsize, void *sargs, uint32_t sasize)
{
        kspawn_param_t *ksp = NULL;
        spawn_param_t *sp = NULL;
        spawn_args_t *sa = NULL;
        int64_t ret = -1;
        int err = 0;

        if (path == NULL || sargs == NULL || sasize < sizeof (*sa))
                return ((int64_t)set_errno(EINVAL));

        if (spsize > NCARGS64 || sasize > NCARGS64)
                return ((int64_t)set_errno(E2BIG));

        if (spsize > 0) {
                if (spsize < sizeof (*sp))
                        return ((int64_t)set_errno(EINVAL));

                sp = kmem_alloc(spsize, KM_SLEEP);
                if (copyin(sparam, sp, spsize) != 0) {
                        err = EFAULT;
                        goto out;
                }
                if ((err = spawn_param_verify(sp, spsize)) != 0)
                        goto out;
        }

        sa = kmem_alloc(sasize, KM_SLEEP);
        if (copyin(sargs, sa, sasize) != 0) {
                err = EFAULT;
                goto out;
        }
        if ((err = spawn_args_verify(sa, sasize)) != 0)
                goto out;

        ksp = kmem_zalloc(sizeof (*ksp), KM_SLEEP);

        err = copyinstr(path, ksp->ksp_path, sizeof (ksp->ksp_path), NULL);
        if (err != 0)
                goto out;

        ksp->ksp_param = sp;
        ksp->ksp_args = sa;
        ksp->ksp_parent_model = get_udatamodel();
        ksp->ksp_audit = AU_AUDITING();
        spawn_prescan(sp, ksp);

        mutex_init(&ksp->ksp_lock, NULL, MUTEX_DEFAULT, NULL);
        cv_init(&ksp->ksp_cv, NULL, CV_DEFAULT, NULL);

        /*
         * If cfork() succeeds, wait for the child to apply the various spawn
         * attributes and attempt the exec. Every child exit path signals
         * completion, including abnormal termination, so the wait is bounded
         * by the child's lifetime.
         *
         * This logic is taken from vfwait(). We wait interruptibly with
         * cv_wait_sig() for its jobcontrol and /proc side effects. The
         * spawning thread can then be stopped or examined and does not block a
         * concurrent holdlwps() from another of the parent's threads while it
         * waits. Once a signal is pending we must switch to an uninterruptible
         * cv_wait(), since we cannot return and free ksp while the child may
         * still reference it, and cv_wait_sig() would otherwise spin returning
         * immediately.
         */
        mutex_enter(&ksp->ksp_lock);
        ret = cfork(0, 0, ksp, spawn_forkflags(sp));
        if (ttolwp(curthread)->lwp_errno == 0) {
                bool signalled = false;

                while (!ksp->ksp_complete) {
                        if (signalled) {
                                cv_wait(&ksp->ksp_cv, &ksp->ksp_lock);
                        } else {
                                signalled = !cv_wait_sig(&ksp->ksp_cv,
                                    &ksp->ksp_lock);
                        }
                }
                if (ksp->ksp_error != 0) {
                        err = ksp->ksp_error;
                        ret = -1;
                }
        }
        mutex_exit(&ksp->ksp_lock);

        mutex_destroy(&ksp->ksp_lock);
        cv_destroy(&ksp->ksp_cv);

        /*
         * Record the details of the spawn while the marshalled data is still
         * to hand. On success ksp_path holds the path that the child actually
         * exec'd, which for posix_spawnp() may differ from the caller-supplied
         * name. The target's attributes are in ksp_vattr and ksp_argv holds
         * the vector it exec'd when that differs from the caller's argv.
         */
        if (ksp->ksp_audit) {
                const char *argstr;
                ssize_t argc;

                if (ksp->ksp_argv != NULL) {
                        argstr = ksp->ksp_argv;
                        argc = (ssize_t)ksp->ksp_argc;
                } else {
                        argstr = (const char *)&sa->sa_data[sa->sa_arg_off];
                        argc = (ssize_t)sa->sa_arg_cnt;
                }

                audit_spawn(ksp->ksp_path,
                    ksp->ksp_have_vattr ? &ksp->ksp_vattr : NULL,
                    argstr, (const char *)&sa->sa_data[sa->sa_env_off],
                    argc, (ssize_t)sa->sa_env_cnt);
        }

out:
        if (sp != NULL)
                kmem_free(sp, spsize);
        if (sa != NULL)
                kmem_free(sa, sasize);
        if (ksp != NULL) {
                if (ksp->ksp_reffds != NULL) {
                        kmem_free(ksp->ksp_reffds,
                            ksp->ksp_nreffds * sizeof (int));
                }
                kmem_free(ksp, sizeof (*ksp));
        }

        if (err != 0)
                return ((int64_t)set_errno(err));
        return (ret);
}