Wait for child setup in a killable and freezable state. If the caller is interrupted after task creation, cancel a child that is still setting up and normalize restart errors because the spawn operation is not idempotent. Preserve externally delivered fatal signals over setup errors. Complete the builder and audit transaction before entering the signal core directly, so the callback never returns through an invalid userspace frame. The task work can run from an outer get_signal(), so fatal delivery deliberately nests get_signal(); the inner path exits and never returns to the outer call. The child remains embryonic and nondumpable until exec installs a valid frame and mm. Reject an already traced caller while holding cred_guard_mutex across the spawn operation. This prevents CLONE_UNTRACED from becoming an escape from an existing ptrace supervisor. Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Li Chen --- fs/pidfd_spawn.c | 189 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 183 insertions(+), 6 deletions(-) diff --git a/fs/pidfd_spawn.c b/fs/pidfd_spawn.c index 5586926988406..0e52503976c0c 100644 --- a/fs/pidfd_spawn.c +++ b/fs/pidfd_spawn.c @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -212,8 +213,10 @@ static void pidfd_spawn_drop_run_data(struct pidfd_spawn_state *state, state->envp = native_arg(NULL); memset(state->audit_args, 0, sizeof(state->audit_args)); state->audit_syscall = 0; - state->result = result; - pidfd_spawn_set_status(state, PIDFD_SPAWN_SETUP_DONE); + if (pidfd_spawn_get_status(state) != PIDFD_SPAWN_CANCELLED) { + state->result = result; + pidfd_spawn_set_status(state, PIDFD_SPAWN_SETUP_DONE); + } mutex_unlock(&state->lock); putname(filename); @@ -226,6 +229,32 @@ static void pidfd_spawn_drop_run_data(struct pidfd_spawn_state *state, kfree(staged_path); } +static bool pidfd_spawn_cancel(struct pidfd_spawn_state *state, + struct task_struct *task) +{ + bool cancelled = false; + + mutex_lock(&state->lock); + if (pidfd_spawn_get_status(state) == PIDFD_SPAWN_STARTING) { + /* + * Establish SIGKILL group-exit state before publishing + * cancellation. The child also takes state->lock before committing + * its setup result, so it cannot return from task work before the + * signal is pending. + */ + if (WARN_ON_ONCE(do_send_sig_info(SIGKILL, SEND_SIG_PRIV, task, + PIDTYPE_PID))) + goto out_unlock; + state->result = -ECANCELED; + pidfd_spawn_set_status(state, PIDFD_SPAWN_CANCELLED); + cancelled = true; + } +out_unlock: + mutex_unlock(&state->lock); + + return cancelled; +} + static bool pidfd_spawn_same_pid_ns(struct pidfd_spawn_state *state) { return current->nsproxy->pid_ns_for_children == state->creator_pid_ns; @@ -383,6 +412,35 @@ SYSCALL_DEFINE5(pidfd_config, int, fd, unsigned int, cmd, return ret; } +static bool pidfd_spawn_child_cancelled(struct pidfd_spawn_state *state) +{ + bool cancelled; + + mutex_lock(&state->lock); + cancelled = pidfd_spawn_get_status(state) == PIDFD_SPAWN_CANCELLED; + mutex_unlock(&state->lock); + + return cancelled; +} + +static bool pidfd_spawn_setup_done(struct pidfd_spawn_state *state) +{ + bool done = false; + + mutex_lock(&state->lock); + switch (pidfd_spawn_get_status(state)) { + case PIDFD_SPAWN_SETUP_DONE: + case PIDFD_SPAWN_STARTED: + done = true; + break; + default: + break; + } + mutex_unlock(&state->lock); + + return done; +} + static void pidfd_spawn_save_audit_context(struct pidfd_spawn_state *state) { struct pt_regs *regs = current_pt_regs(); @@ -399,6 +457,52 @@ static void pidfd_spawn_audit_entry(struct pidfd_spawn_state *state) state->audit_args[1], state->audit_args[2], state->audit_args[3]); } + +static int pidfd_spawn_normalize_result(int result) +{ + if (result == -ERESTARTSYS || result == -ERESTARTNOINTR || + result == -ERESTARTNOHAND || result == -ERESTART_RESTARTBLOCK) + return -EINTR; + return result; +} + +static int pidfd_spawn_pending_fatal_signal(void) +{ + struct sighand_struct *sighand; + sigset_t pending; + unsigned long flags; + int fatal = 0; + int sig; + + /* + * fatal_signal_pending() only recognizes pending SIGKILL. A default-fatal + * coredump signal remains pending as itself, but must still win over a + * setup error. Inspect the same pending sets and default dispositions as + * get_signal() without dequeuing the signal. + */ + sighand = lock_task_sighand(current, &flags); + if (WARN_ON_ONCE(!sighand)) + return 0; + + sigorsets(&pending, ¤t->pending.signal, + ¤t->signal->shared_pending.signal); + sigandnsets(&pending, &pending, ¤t->blocked); + + for (sig = 1; sig < _NSIG; sig++) { + if (!sigismember(&pending, sig) || sig_kernel_ignore(sig) || + sig_kernel_stop(sig) || + sighand->action[sig - 1].sa.sa_handler != SIG_DFL) + continue; + if ((current->signal->flags & SIGNAL_UNKILLABLE) && + !sig_kernel_only(sig)) + continue; + fatal = sig; + break; + } + spin_unlock_irqrestore(&sighand->siglock, flags); + return fatal; +} + static void pidfd_spawn_finish_child(struct pidfd_spawn_state *state, struct filename *filename, int result) { @@ -406,11 +510,40 @@ static void pidfd_spawn_finish_child(struct pidfd_spawn_state *state, complete_all(&state->done); } +static __noreturn void +pidfd_spawn_deliver_fatal_signal(struct pidfd_spawn_state *state, + struct filename *filename, int sig) +{ + struct ksignal ksig; + sigset_t blocked; + + /* + * The callback frame is not a valid userspace signal frame on every + * architecture. Complete the spawn transaction, then enter the signal + * core directly instead of returning through the callback trampoline. + * + * The embryonic task state prevents ptrace access and coredumps until exec + * installs a valid userspace frame and private mm. Block other catchable + * signals so the selected default-fatal signal remains terminal. If the + * signal core unexpectedly returns, exit without exposing the frame. + */ + pidfd_spawn_finish_child(state, filename, 0); + audit_pidfd_spawn_exit(0, -EINTR); + pidfd_spawn_state_put(state); + + sigfillset(&blocked); + sigdelset(&blocked, sig); + set_current_blocked(&blocked); + get_signal(&ksig); + do_group_exit(PIDFD_SPAWN_EXIT_FAILURE); +} + static void pidfd_spawn_child(struct callback_head *work) { struct pidfd_spawn_state *state = container_of(work, struct pidfd_spawn_state, task_work); struct filename *filename; + int fatal_sig; int ret; pidfd_spawn_audit_entry(state); @@ -420,9 +553,27 @@ static void pidfd_spawn_child(struct callback_head *work) else ret = 0; + if (pidfd_spawn_child_cancelled(state)) { + ret = -ECANCELED; + pidfd_spawn_finish_child(state, filename, ret); + audit_pidfd_spawn_exit(0, ret); + pidfd_spawn_state_put(state); + do_group_exit(SIGKILL); + } + fatal_sig = pidfd_spawn_pending_fatal_signal(); + if (fatal_sig) + pidfd_spawn_deliver_fatal_signal(state, filename, fatal_sig); + if (!ret) ret = do_execveat_common(AT_FDCWD, filename, state->argv, state->envp, 0); + ret = pidfd_spawn_normalize_result(ret); + if (ret) { + fatal_sig = pidfd_spawn_pending_fatal_signal(); + if (fatal_sig) + pidfd_spawn_deliver_fatal_signal(state, filename, + fatal_sig); + } pidfd_spawn_finish_child(state, filename, ret); if (ret) { @@ -531,9 +682,16 @@ static void pidfd_spawn_attach_vfork_done(struct task_struct *task, task_unlock(task); } -static void pidfd_spawn_wait_for_child(struct pidfd_spawn_state *state) +static int pidfd_spawn_wait_for_child(struct pidfd_spawn_state *state) { - wait_for_completion(&state->done); + unsigned int wait_state = TASK_KILLABLE | TASK_FREEZABLE; + int ret; + + cgroup_enter_frozen(); + ret = wait_for_completion_state(&state->done, wait_state); + cgroup_leave_frozen(false); + + return pidfd_spawn_normalize_result(ret); } static int pidfd_spawn_finish_vfork(struct pidfd_spawn_state *state, @@ -544,7 +702,7 @@ static int pidfd_spawn_finish_vfork(struct pidfd_spawn_state *state, ret = wait_for_vfork_done(task, vfork); if (ret) - return ret; + return pidfd_spawn_normalize_result(ret); mutex_lock(&state->lock); ret = state->result; @@ -624,7 +782,26 @@ static int pidfd_spawn_start(struct file *file, } wake_up_new_task(task); - pidfd_spawn_wait_for_child(state); + ret = pidfd_spawn_wait_for_child(state); + if (ret) { + int interrupt = ret; + + if (pidfd_spawn_setup_done(state)) { + if (pidfd_spawn_finish_vfork(state, task, &vfork)) + return interrupt; + return 0; + } + if (!pidfd_spawn_cancel(state, task) && + pidfd_spawn_setup_done(state)) { + if (pidfd_spawn_finish_vfork(state, task, &vfork)) + return interrupt; + return 0; + } + + /* Drop the stack-based completion without waiting for child setup. */ + wait_for_vfork_done(task, &vfork); + return interrupt; + } return pidfd_spawn_finish_vfork(state, task, &vfork); } -- 2.52.0