Add a pidfs-backed constructor for taskless spawn-builder files. The inode owns the builder state, so procfd reopens and bind mounts retain it without creating per-file lifetime rules. The constructor allocates no task, struct pid, numeric PID, or process-count charge. Until a later run operation publishes a pid, ordinary pidfd operations resolve the file as -ESRCH. Preserve that error through setns() and pidfd_send_signal() instead of translating a valid future pidfd into an unrelated descriptor error. Assert that PIDFD_EMPTY does not overlap a valid open flag, so future collisions fail the build instead of silently aliasing builder creation. Keep the public pidfd_open() entry point disabled until the complete spawn state machine is wired later in the series. Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Li Chen --- MAINTAINERS | 2 + fs/Makefile | 2 +- fs/pidfd_spawn.c | 81 +++++++++++++++++++++++++++++++++++++ fs/pidfs.c | 5 ++- include/linux/pidfd_spawn.h | 9 +++++ kernel/nsproxy.c | 11 +++-- kernel/signal.c | 2 +- 7 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 fs/pidfd_spawn.c create mode 100644 include/linux/pidfd_spawn.h diff --git a/MAINTAINERS b/MAINTAINERS index b63bc1ee0171f..8d7f94f09f414 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21287,6 +21287,8 @@ M: Christian Brauner L: linux-kernel@vger.kernel.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/brauner/linux.git +F: fs/pidfd_spawn.c +F: include/linux/pidfd_spawn.h F: include/uapi/linux/pidfd_spawn.h F: samples/pidfd/ F: tools/include/uapi/linux/pidfd_spawn.h diff --git a/fs/Makefile b/fs/Makefile index aa847be93bc6f..f24beb7c9b7dc 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -8,7 +8,7 @@ obj-y := open.o read_write.o file_table.o super.o \ - char_dev.o stat.o exec.o pipe.o namei.o fcntl.o \ + char_dev.o stat.o exec.o pidfd_spawn.o pipe.o namei.o fcntl.o \ ioctl.o readdir.o select.o dcache.o inode.o \ attr.o bad_inode.o file.o filesystems.o namespace.o \ seq_file.o xattr.o libfs.o fs-writeback.o \ diff --git a/fs/pidfd_spawn.c b/fs/pidfd_spawn.c new file mode 100644 index 0000000000000..ff2b0cb6365a5 --- /dev/null +++ b/fs/pidfd_spawn.c @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * pidfd-backed process spawn builders + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct pidfd_spawn_state { + refcount_t count; + struct pid *pid; +}; + +static void pidfd_spawn_state_put(struct pidfd_spawn_state *state); + +static void pidfd_spawn_free_state(struct pidfd_spawn_state *state) +{ + put_pid(state->pid); + kfree(state); +} + +static void pidfd_spawn_state_put(struct pidfd_spawn_state *state) +{ + if (refcount_dec_and_test(&state->count)) + pidfd_spawn_free_state(state); +} + +static void pidfd_spawn_state_release(void *data) +{ + pidfd_spawn_state_put(data); +} + +static struct pid *pidfd_spawn_file_pid(void *data) +{ + struct pidfd_spawn_state *state = data; + struct pid *pid; + + pid = READ_ONCE(state->pid); + return pid ? pid : ERR_PTR(-ESRCH); +} + +static const struct pidfs_future_file_ops pidfd_spawn_future_ops = { + .get_pid = pidfd_spawn_file_pid, + .release = pidfd_spawn_state_release, +}; + +int pidfd_empty_open(unsigned int flags) +{ + struct pidfd_spawn_state *state; + struct file *pidfile; + int pidfd; + + state = kzalloc_obj(*state, GFP_KERNEL_ACCOUNT); + if (!state) + return -ENOMEM; + + refcount_set(&state->count, 1); + + pidfile = pidfs_alloc_future_file("[pidfd_spawn]", state, + &pidfd_spawn_future_ops, + O_RDWR | flags); + if (IS_ERR(pidfile)) { + pidfd_spawn_state_put(state); + return PTR_ERR(pidfile); + } + + pidfd = get_unused_fd_flags(O_CLOEXEC); + if (pidfd < 0) { + fput(pidfile); + return pidfd; + } + + fd_install(pidfd, pidfile); + return pidfd; +} diff --git a/fs/pidfs.c b/fs/pidfs.c index ebd8cc463811b..28464fe274c9e 100644 --- a/fs/pidfs.c +++ b/fs/pidfs.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -1261,7 +1262,9 @@ struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags) * other or with uapi pidfd flags. */ BUILD_BUG_ON(hweight32(PIDFD_THREAD | PIDFD_NONBLOCK | - PIDFD_STALE | PIDFD_AUTOKILL) != 4); + PIDFD_EMPTY | PIDFD_STALE | + PIDFD_AUTOKILL) != 5); + BUILD_BUG_ON(PIDFD_EMPTY & VALID_OPEN_FLAGS); ret = path_from_stashed(&pid->stashed, pidfs_mnt, get_pid(pid), &path); if (ret < 0) diff --git a/include/linux/pidfd_spawn.h b/include/linux/pidfd_spawn.h new file mode 100644 index 0000000000000..8df168cf0c2f9 --- /dev/null +++ b/include/linux/pidfd_spawn.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_PIDFD_SPAWN_H +#define _LINUX_PIDFD_SPAWN_H + +#include + +int pidfd_empty_open(unsigned int flags); + +#endif /* _LINUX_PIDFD_SPAWN_H */ diff --git a/kernel/nsproxy.c b/kernel/nsproxy.c index d9d3d5973bf52..20befb6185e2d 100644 --- a/kernel/nsproxy.c +++ b/kernel/nsproxy.c @@ -581,10 +581,15 @@ SYSCALL_DEFINE2(setns, int, fd, int, flags) if (flags && (ns->ns_type != flags)) err = -EINVAL; flags = ns->ns_type; - } else if (!IS_ERR(pidfd_pid(fd_file(f)))) { - err = check_setns_flags(flags); } else { - err = -EINVAL; + struct pid *pid = pidfd_pid(fd_file(f)); + + if (!IS_ERR(pid)) + err = check_setns_flags(flags); + else if (PTR_ERR(pid) == -ESRCH) + err = -ESRCH; + else + err = -EINVAL; } if (err) goto out; diff --git a/kernel/signal.c b/kernel/signal.c index fdee0b012a117..4c7d95981263e 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -3996,7 +3996,7 @@ static struct pid *pidfd_to_pid(const struct file *file) struct pid *pid; pid = pidfd_pid(file); - if (!IS_ERR(pid)) + if (!IS_ERR(pid) || PTR_ERR(pid) != -EBADF) return pid; return tgid_pidfd_to_pid(file); -- 2.52.0