A pre-task run failure currently returns the builder to CONFIGURING. A second thread can then publish another invocation before the first caller probes the pidfd, making the observed process state ambiguous.
Claim the builder after type, authority, and ptrace checks but before run argument access. Any later validation, uaccess, allocation, or task-creation failure leaves a terminal taskless builder. Later config and run operations return EBUSY, while process-dependent pidfd operations return ESRCH. Normalize every internal restart error after the claim to EINTR so the architecture cannot restart a consumed invocation and replace its result with EBUSY. Preserve normal restart behavior before the claim. Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Li Chen <[email protected]> --- fs/pidfd_spawn.c | 164 +++++++++++++++++++++++++++++++---------------- 1 file changed, 109 insertions(+), 55 deletions(-) diff --git a/fs/pidfd_spawn.c b/fs/pidfd_spawn.c index 3e46a12c0ba07..1309fe99ec091 100644 --- a/fs/pidfd_spawn.c +++ b/fs/pidfd_spawn.c @@ -73,6 +73,7 @@ DEFINE_FREE(pidfd_spawn_dismiss_filename, struct delayed_filename, enum pidfd_spawn_status { PIDFD_SPAWN_CONFIGURING, PIDFD_SPAWN_STARTING, + PIDFD_SPAWN_FAILED_TASKLESS, PIDFD_SPAWN_SETUP_DONE, PIDFD_SPAWN_STARTED, PIDFD_SPAWN_CANCELLED, @@ -100,6 +101,14 @@ struct pidfd_spawn_state { enum pidfd_spawn_status status; }; +struct pidfd_spawn_resources { + struct pidfd_spawn_action *actions; + struct mm_struct *creator_mm; + const struct cred *creator_cred; + struct pid_namespace *creator_pid_ns; + char *staged_path; +}; + static struct pid * pidfd_spawn_load_pid(const struct pidfd_spawn_state *state) { @@ -183,8 +192,16 @@ static struct pid *pidfd_spawn_file_pid(void *data) return pid ? pid : ERR_PTR(-ESRCH); } +static bool pidfd_spawn_file_terminal(void *data) +{ + struct pidfd_spawn_state *state = data; + + return pidfd_spawn_get_status(state) == PIDFD_SPAWN_FAILED_TASKLESS; +} + static const struct pidfs_future_file_ops pidfd_spawn_future_ops = { .get_pid = pidfd_spawn_file_pid, + .is_terminal = pidfd_spawn_file_terminal, .release = pidfd_spawn_state_release, }; @@ -199,31 +216,47 @@ pidfd_spawn_state_get_file(struct file *file) return state; } +static void +pidfd_spawn_detach_resources(struct pidfd_spawn_state *state, + struct pidfd_spawn_resources *resources) +{ + resources->actions = state->actions; + resources->creator_mm = state->creator_mm; + resources->creator_cred = state->creator_cred; + resources->creator_pid_ns = state->creator_pid_ns; + resources->staged_path = state->staged_path; + state->actions = NULL; + state->creator_mm = NULL; + state->creator_cred = NULL; + state->creator_pid_ns = NULL; + state->staged_path = NULL; + state->nr_actions = 0; +} + +static void +pidfd_spawn_put_resources(struct pidfd_spawn_resources *resources) +{ + if (resources->creator_mm) + mmdrop(resources->creator_mm); + if (resources->creator_cred) + put_cred(resources->creator_cred); + if (resources->creator_pid_ns) + put_pid_ns(resources->creator_pid_ns); + kfree(resources->staged_path); + kvfree(resources->actions); +} + static void pidfd_spawn_drop_run_data(struct pidfd_spawn_state *state, struct filename *filename, int result) { - const struct cred *creator_cred; - struct pidfd_spawn_action *actions; - struct mm_struct *creator_mm; - struct pid_namespace *creator_pid_ns; - char *staged_path; + struct pidfd_spawn_resources resources = {}; /* * Run data is one-shot. Detach it under the lock, then drop refs * after the child no longer needs the shared setup payload. */ mutex_lock(&state->lock); - actions = state->actions; - creator_mm = state->creator_mm; - creator_cred = state->creator_cred; - creator_pid_ns = state->creator_pid_ns; - staged_path = state->staged_path; - state->actions = NULL; - state->creator_mm = NULL; - state->creator_cred = NULL; - state->creator_pid_ns = NULL; - state->staged_path = NULL; - state->nr_actions = 0; + pidfd_spawn_detach_resources(state, &resources); state->argv = native_arg(NULL); state->envp = native_arg(NULL); memset(state->audit_args, 0, sizeof(state->audit_args)); @@ -235,14 +268,7 @@ static void pidfd_spawn_drop_run_data(struct pidfd_spawn_state *state, mutex_unlock(&state->lock); putname(filename); - if (creator_mm) - mmdrop(creator_mm); - if (creator_cred) - put_cred(creator_cred); - if (creator_pid_ns) - put_pid_ns(creator_pid_ns); - kfree(staged_path); - kvfree(actions); + pidfd_spawn_put_resources(&resources); } static bool pidfd_spawn_cancel(struct pidfd_spawn_state *state, @@ -291,16 +317,44 @@ static bool pidfd_spawn_same_creator(struct pidfd_spawn_state *state) pidfd_spawn_same_pid_ns(state); } -static int pidfd_spawn_check_startable(struct pidfd_spawn_state *state) +static int pidfd_spawn_claim(struct pidfd_spawn_state *state) { int ret; - scoped_cond_guard(mutex_intr, return -EINTR, &state->lock) { + scoped_guard(mutex, &state->lock) { ret = pidfd_spawn_configuring_error(state); - if (!ret && !pidfd_spawn_same_creator(state)) - ret = -EPERM; + if (ret) + return ret; + if (!pidfd_spawn_same_creator(state)) + return -EPERM; + + pidfd_spawn_set_status(state, PIDFD_SPAWN_STARTING); } - return ret; + return 0; +} + +static void pidfd_spawn_fail_taskless(struct file *file, + struct pidfd_spawn_state *state, int result) +{ + struct pidfd_spawn_resources resources = {}; + + mutex_lock(&state->lock); + if (WARN_ON_ONCE(pidfd_spawn_get_status(state) != + PIDFD_SPAWN_STARTING || pidfd_spawn_load_pid(state))) { + mutex_unlock(&state->lock); + return; + } + state->result = result; + pidfd_spawn_detach_resources(state, &resources); + state->argv = native_arg(NULL); + state->envp = native_arg(NULL); + memset(state->audit_args, 0, sizeof(state->audit_args)); + state->audit_syscall = 0; + pidfd_spawn_set_status(state, PIDFD_SPAWN_FAILED_TASKLESS); + mutex_unlock(&state->lock); + + pidfd_spawn_put_resources(&resources); + pidfs_future_file_notify(file); } static struct filename *pidfd_spawn_get_path(const char __user *value) @@ -718,7 +772,7 @@ static void pidfd_spawn_child(struct callback_head *work) } static int pidfd_spawn_copy_run_args(struct pidfd_spawn_run_args *kargs, - struct pidfd_spawn_run_args __user *uargs, + const struct pidfd_spawn_run_args __user *uargs, size_t usize) { size_t actions_size; @@ -873,6 +927,13 @@ static int pidfd_spawn_start(struct file *file, struct task_struct *task; int ret; + scoped_cond_guard(mutex_intr, return -EINTR, &state->lock) { + if (pidfd_spawn_get_status(state) != PIDFD_SPAWN_STARTING) + return -EBUSY; + if (!!state->staged_path == !!kargs->path) + return -EINVAL; + } + if (kargs->path) { ret = pidfd_spawn_get_delayed_path(&filename, pidfd_spawn_user_ptr(kargs->path)); @@ -881,15 +942,8 @@ static int pidfd_spawn_start(struct file *file, } scoped_cond_guard(mutex_intr, return -EINTR, &state->lock) { - ret = pidfd_spawn_configuring_error(state); - if (ret) - return ret; - if (!pidfd_spawn_same_creator(state)) - return -EPERM; - if (state->staged_path && kargs->path) - return -EINVAL; - if (!state->staged_path && !kargs->path) - return -EINVAL; + if (pidfd_spawn_get_status(state) != PIDFD_SPAWN_STARTING) + return -EBUSY; if (state->staged_path) { ret = pidfd_spawn_get_delayed_kernel_path(&filename, state->staged_path); @@ -903,7 +957,6 @@ static int pidfd_spawn_start(struct file *file, state->envp = pidfd_spawn_user_arg(kargs->envp); state->actions = *actions; state->nr_actions = kargs->nr_actions; - pidfd_spawn_set_status(state, PIDFD_SPAWN_STARTING); pidfd_spawn_save_audit_context(state); *actions = NULL; reinit_completion(&state->done); @@ -921,9 +974,6 @@ static int pidfd_spawn_start(struct file *file, memset(state->audit_args, 0, sizeof(state->audit_args)); state->audit_syscall = 0; - state->result = 0; - pidfd_spawn_set_status(state, - PIDFD_SPAWN_CONFIGURING); return ret; } @@ -1002,17 +1052,13 @@ int pidfd_empty_open(unsigned int flags) } SYSCALL_DEFINE3(pidfd_spawn_run, int, fd, - struct pidfd_spawn_run_args __user *, uargs, size_t, usize) + const struct pidfd_spawn_run_args __user *, uargs, size_t, usize) { struct pidfd_spawn_action *actions __free(kvfree) = NULL; struct pidfd_spawn_run_args kargs; struct pidfd_spawn_state *state __free(pidfd_spawn_state) = NULL; int ret; - ret = pidfd_spawn_copy_run_args(&kargs, uargs, usize); - if (ret) - return ret; - CLASS(fd, f)(fd); if (fd_empty(f)) return -EBADF; @@ -1020,19 +1066,27 @@ SYSCALL_DEFINE3(pidfd_spawn_run, int, fd, state = pidfd_spawn_state_get_file(fd_file(f)); if (IS_ERR(state)) return PTR_ERR(state); - ret = pidfd_spawn_copy_actions(&actions, kargs.actions, - kargs.nr_actions, kargs.action_size); - if (ret) - return ret; - scoped_cond_guard(mutex_intr, return -ERESTARTNOINTR, ¤t->signal->cred_guard_mutex) { if (READ_ONCE(current->ptrace)) return -EPERM; - ret = pidfd_spawn_check_startable(state); + ret = pidfd_spawn_claim(state); if (ret) return ret; - ret = pidfd_spawn_start(fd_file(f), state, &kargs, &actions); + + ret = pidfd_spawn_copy_run_args(&kargs, uargs, usize); + if (!ret) + ret = pidfd_spawn_copy_actions(&actions, kargs.actions, + kargs.nr_actions, + kargs.action_size); + if (!ret) + ret = pidfd_spawn_start(fd_file(f), state, &kargs, + &actions); + if (ret) { + ret = pidfd_spawn_normalize_result(ret); + if (!pidfd_spawn_load_pid(state)) + pidfd_spawn_fail_taskless(fd_file(f), state, ret); + } } if (!ret) ret = pid_vnr(state->pid); -- 2.52.0

