Re: [RFC PATCH 00/24] pidfd: add a minimal process spawn builder
Justin Suess <[email protected]> Tue, 4 Aug 2026 16:25:25 -0400
| Newsgroups | org.kernel.vger.linux-arch,org.kernel.vger.audit,org.kernel.vger.linux-api,org.kernel.vger.linux-doc,org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel,org.kernel.vger.linux-kselftest,org.kernel.vger.linux-security-module,org.kvack.linux-mm |
|---|---|
| Message-ID | <anJE8SfBLtkh6QSa@zenbox> |
On Thu, Jul 16, 2026 at 10:31:26PM +0800, Li Chen wrote: > Hi, > > This RFC follows feedback on my earlier spawn_template RFC [1]. That > proposal made caching the primary interface; this one starts with general > process construction. Christian suggested a pidfd/pidfs exec builder > modeled after fsconfig(), with enough semantics for userspace to implement > posix_spawn() [2], and Kees agreed [3]. > > This RFC is based on linux-next next-20260710 and depends on two pidfs > fixes that I sent separately: > > * pidfs: preserve thread pidfds reopened by file handle > https://lore.kernel.org/all/[email protected]/ > * pidfs: handle FS_IOC32_GETVERSION in compat ioctl > https://lore.kernel.org/all/[email protected]/ > > The initial implementation is source-based. The executable path can be > provided with the final run request: > > struct pidfd_spawn_run_args run = { > .path = (unsigned long)"/usr/bin/rg", This should probably be an FD for the path. This way it prevents race conditions over multiple configuration steps. > .argv = (unsigned long)argv, > .envp = (unsigned long)envp, > }; > > fd = pidfd_open(0, PIDFD_EMPTY); > pidfd_spawn_run(fd, &run, sizeof(run)); > > Alternatively, the path can be staged before the final run step: > > struct pidfd_spawn_run_args run = { > .argv = (unsigned long)argv, > .envp = (unsigned long)envp, > }; > > fd = pidfd_open(0, PIDFD_EMPTY); > pidfd_config(fd, PIDFD_CONFIG_SET_STRING, > PIDFD_CONFIG_KEY_PATH, "/usr/bin/rg", 0); Same here. Should probably be an FD to the binary instead. > pidfd_spawn_run(fd, &run, sizeof(run)); I'm worried this pidfd_spawn_run just adds another varient to the existing myriad of exec* syscalls we already have. Would it be better to just have this work through execveat(fd, "", argv, envp, AT_EMPTY_PATH) instead? (i.e have execveat take a pidfd directly). Then you can get rid of pidfd_spawn_run which looks almost structurally identical to execveat (with the argv and envp collapsed). Justin > > pidfd_open(0, PIDFD_EMPTY) creates a taskless future pidfd with a stable > pidfs inode, but no task, PID, or process-count charge. pidfd_spawn_run() > creates the task and PID; after publication the same fd is the child pidfd, > and a numeric pidfd resolves to the same inode. Live-task operations return > -ESRCH before publication. A terminal pre-task failure wakes poll/epoll > with POLLERR | POLLHUP. > > Source-based mode follows posix_spawn-style defaults. At run time it > samples the caller's cwd, root, umask, fd table, signal dispositions and > blocked mask, and namespaces. > Non-FD_CLOEXEC descriptors remain unless an action changes them; file and > filesystem state are private child copies before actions. Configuration and > run stay bound to the creating mm_struct, exact credential object, and > child PID namespace, so SCM_RIGHTS does not delegate launch authority. > > The first authorized run claims the builder before copying its payload, so > later failures are terminal. Pre-task failure leaves the fd taskless; > setup or exec failure leaves it as the child pidfd and exits the child with > status 127. PIDFD_GET_INFO with PIDFD_INFO_EXIT distinguishes them. > Success returns the positive child PID in the caller's PID namespace. > > The RFC supports ordered DUP2, CLOSE_RANGE, and FCHDIR actions in > extensible UAPI records. This exercises child-private fd and cwd setup, but > is not the complete posix_spawn() action or attribute surface. > > Between task publication and successful exec, the child is explicitly > embryonic and may not have a valid userspace register frame. Ptrace and > pidfd_getfd() are denied, procfs treats the PID as absent to other tasks, > and coredump information reports PIDFD_COREDUMP_SKIP. The child can use its > own proc entries during executable lookup. Setup runs as initial child task > work, and successful exec releases this state before exec events are > published. > > Seccomp sees pidfd_spawn_run(), not separate file-action or exec syscalls, > and cannot inspect the path or action records behind the run pointer. An > exec-only denylist that allows unknown syscalls therefore does not block > this initial exec; policy must filter the builder syscall as a unit. Should > later expansion provide an immutable restriction profile or action mask > that seccomp can reason about, or is the coarse syscall boundary > preferable? > > LSM exec checks and inherited seccomp state remain active. Child setup uses > a dedicated AUDIT_PIDFD_SPAWN transaction, not a synthetic AUDIT_SYSCALL. > Should it be selected through the source pidfd_spawn_run() exit rule? An > existing rule naming only execve() or execveat() does not select it. > For source/child correlation, could an auxiliary record carry > the source PID plus the stable pidfs inode? An already traced source is > rejected before the claim; ptrace auto-attach is not implemented. > > The implementation still uses CLONE_VM | CLONE_VFORK plus exec internally. > Mateusz suggested that an initial implementation might start with vfork to > get the API off the ground [4]. That is what this RFC does. It does not yet > construct a pristine target process without first inheriting source state. > > Missing posix_spawn() pieces include open and close file actions, resetids, > signal masks/defaults, process groups, sessions, scheduler attributes, > affinity, cgroup placement, PATH lookup/posix_spawnp(), and exec by fd. The > RFC also does not include pristine/no-source creation or the executable > metadata/template cache from my earlier work. > > John Ericson described a real, partially initialized process that remains > unscheduled while callers install its state, and linked an exploratory > FreeBSD proc_new()/proc_setfd()/proc_start() refactoring [5]. This RFC > implements the source-based mode first; a lower-authority pristine/no-source > mode would be explicit follow-up work. > > Direct process construction is not unprecedented. XNU's posix_spawn path > does not inherit the parent's address space [6], and Windows > CreateProcess() accepts explicit startup state [7]. Josh's io_uring_spawn > LPC slides list "set up process from scratch" as future work and provide > useful performance context [8]; I am not using those numbers as a claim for > this RFC. > > If the direction is acceptable, I plan to continue toward: > > * the complete file-action and attribute set needed by posix_spawn(); > * pristine target-process construction and an explicit no-source mode; > * PATH/posix_spawnp support, if it belongs on the kernel side; and > * an optional executable/template layer for workloads such as agent tool > calling and compiler drivers. > > The exposed UAPI surface is intentionally limited so the state model and > kernel/userspace boundary can be reviewed first. If maintainers would > prefer more posix_spawn semantics or backend work in this RFC, please say > so and I will adjust the split. > > Codex GPT-5.5 and GPT-5.6-sol provided substantial assistance across design > conceptualization, implementation, patch splitting, code review, development > of self-test cases, and test planning and execution. > > Thanks to Christian, Kees, Mateusz, Gabriel, Josh, Andy, John, and others > for the review and direction. > > [1]: https://patchew.org/linux/20260528095235.2491226-1-me%40linux.beauty/ > [2]: https://lore.kernel.org/all/20260528-madig-fachrichtung-fehlinformation-61117ba640da@brauner/ > [3]: https://lore.kernel.org/all/202606011254.5FCBD65@keescook/ > [4]: https://lore.kernel.org/all/vealb52tv5suireenkke4lul2l3wbnaul2rp3ea545ly5wa5ty@yk3aksvp7skt/ > [5]: https://lore.kernel.org/all/[email protected]/ > [6]: https://github.com/apple-oss-distributions/xnu/blob/f6217f891ac0bb64f3d375211650a4c1ff8ca1ea/bsd/kern/kern_exec.c#L4039 > [7]: https://learn.microsoft.com/en-us/windows/win32/procthread/creating-processes > [8]: https://lpc.events/event/16/contributions/1213/attachments/1012/1945/io-uring-spawn.pdf > > > Li Chen (24): > pidfd: add spawn builder uapi > libfs: allow custom validation of stashed inode data > pidfs: add taskless future pidfd inodes > pidfd: create taskless spawn builders > pidfd: add spawn builder path configuration > exec: expose execveat internals to process builders > fork: expose vfork completion helper > pidfs: attach pids to future pidfd files > fork: let process builders supply preallocated pids > pidfs: publish future pidfd files > pidfd: add spawn builder state tracking > fork: let kernel callers create embryonic tasks > fork: let new tasks start with task work > pidfd: create and execute spawn builder tasks > fork: keep embryonic tasks hidden until exec completes > audit: add pidfd spawn child contexts > pidfd: audit child spawn execution > pidfd: make spawn builder execution signal-safe > file: expose spawn file-action helpers > pidfd: add initial spawn file actions > pidfd: consume spawn builders on the first run attempt > pidfd: expose spawn builder system calls > selftests/pidfd: cover pidfd spawn builders > Documentation: describe pidfd spawn builders > > Documentation/userspace-api/index.rst | 1 + > Documentation/userspace-api/pidfd_spawn.rst | 247 ++++ > MAINTAINERS | 6 + > arch/alpha/kernel/syscalls/syscall.tbl | 2 + > arch/arm/tools/syscall.tbl | 2 + > arch/arm64/tools/syscall_32.tbl | 2 + > arch/m68k/kernel/syscalls/syscall.tbl | 2 + > arch/microblaze/kernel/syscalls/syscall.tbl | 2 + > arch/mips/kernel/syscalls/syscall_n32.tbl | 2 + > arch/mips/kernel/syscalls/syscall_n64.tbl | 2 + > arch/mips/kernel/syscalls/syscall_o32.tbl | 2 + > arch/parisc/kernel/syscalls/syscall.tbl | 2 + > arch/powerpc/kernel/syscalls/syscall.tbl | 2 + > arch/s390/kernel/syscalls/syscall.tbl | 2 + > arch/sh/kernel/syscalls/syscall.tbl | 2 + > arch/sparc/kernel/syscalls/syscall.tbl | 2 + > arch/x86/entry/syscalls/syscall_32.tbl | 2 + > arch/x86/entry/syscalls/syscall_64.tbl | 2 + > arch/xtensa/kernel/syscalls/syscall.tbl | 2 + > fs/Makefile | 2 +- > fs/coredump.c | 4 +- > fs/exec.c | 30 +- > fs/exec_internal.h | 40 + > fs/file.c | 11 +- > fs/internal.h | 3 + > fs/libfs.c | 5 +- > fs/open.c | 7 +- > fs/pidfd_spawn.c | 1095 +++++++++++++++ > fs/pidfs.c | 478 ++++++- > fs/proc/base.c | 11 +- > fs/proc/internal.h | 18 +- > include/linux/audit.h | 31 + > include/linux/pid.h | 13 + > include/linux/pidfd_spawn.h | 9 + > include/linux/pidfs.h | 28 + > include/linux/sched.h | 21 + > include/linux/sched/task.h | 6 + > include/linux/syscalls.h | 7 + > include/uapi/asm-generic/unistd.h | 8 +- > include/uapi/linux/audit.h | 1 + > include/uapi/linux/pidfd.h | 1 + > include/uapi/linux/pidfd_spawn.h | 49 + > kernel/audit.h | 1 + > kernel/auditsc.c | 105 +- > kernel/fork.c | 26 +- > kernel/nsproxy.c | 11 +- > kernel/pid.c | 41 +- > kernel/ptrace.c | 4 + > kernel/signal.c | 2 +- > scripts/syscall.tbl | 2 + > tools/include/uapi/asm-generic/unistd.h | 8 +- > tools/include/uapi/linux/pidfd_spawn.h | 49 + > .../arch/alpha/entry/syscalls/syscall.tbl | 2 + > .../perf/arch/arm/entry/syscalls/syscall.tbl | 2 + > .../arch/arm64/entry/syscalls/syscall_32.tbl | 11 + > .../arch/mips/entry/syscalls/syscall_n64.tbl | 2 + > .../arch/parisc/entry/syscalls/syscall.tbl | 2 + > .../arch/powerpc/entry/syscalls/syscall.tbl | 2 + > .../perf/arch/s390/entry/syscalls/syscall.tbl | 2 + > tools/perf/arch/sh/entry/syscalls/syscall.tbl | 2 + > .../arch/sparc/entry/syscalls/syscall.tbl | 2 + > .../arch/x86/entry/syscalls/syscall_32.tbl | 2 + > .../arch/x86/entry/syscalls/syscall_64.tbl | 2 + > .../arch/xtensa/entry/syscalls/syscall.tbl | 2 + > tools/scripts/syscall.tbl | 2 + > tools/testing/selftests/landlock/audit.h | 6 +- > tools/testing/selftests/pidfd/.gitignore | 9 + > tools/testing/selftests/pidfd/Makefile | 25 +- > tools/testing/selftests/pidfd/config | 6 + > .../pidfd/pidfd_spawn_accounting_test.c | 428 ++++++ > .../pidfd/pidfd_spawn_actions_test.c | 474 +++++++ > .../selftests/pidfd/pidfd_spawn_audit_test.c | 521 +++++++ > .../selftests/pidfd/pidfd_spawn_common.c | 512 +++++++ > .../selftests/pidfd/pidfd_spawn_common.h | 59 + > .../selftests/pidfd/pidfd_spawn_compat.c | 221 +++ > .../selftests/pidfd/pidfd_spawn_exec_test.c | 301 ++++ > .../selftests/pidfd/pidfd_spawn_policy_test.c | 294 ++++ > .../selftests/pidfd/pidfd_spawn_race_test.c | 923 ++++++++++++ > .../pidfd/pidfd_spawn_security_test.c | 1242 +++++++++++++++++ > .../selftests/pidfd/pidfd_spawn_test.c | 550 ++++++++ > 80 files changed, 7938 insertions(+), 81 deletions(-) > create mode 100644 Documentation/userspace-api/pidfd_spawn.rst > create mode 100644 fs/exec_internal.h > create mode 100644 fs/pidfd_spawn.c > create mode 100644 include/linux/pidfd_spawn.h > create mode 100644 include/uapi/linux/pidfd_spawn.h > create mode 100644 tools/include/uapi/linux/pidfd_spawn.h > create mode 100644 tools/testing/selftests/pidfd/pidfd_spawn_accounting_test.c > create mode 100644 tools/testing/selftests/pidfd/pidfd_spawn_actions_test.c > create mode 100644 tools/testing/selftests/pidfd/pidfd_spawn_audit_test.c > create mode 100644 tools/testing/selftests/pidfd/pidfd_spawn_common.c > create mode 100644 tools/testing/selftests/pidfd/pidfd_spawn_common.h > create mode 100644 tools/testing/selftests/pidfd/pidfd_spawn_compat.c > create mode 100644 tools/testing/selftests/pidfd/pidfd_spawn_exec_test.c > create mode 100644 tools/testing/selftests/pidfd/pidfd_spawn_policy_test.c > create mode 100644 tools/testing/selftests/pidfd/pidfd_spawn_race_test.c > create mode 100644 tools/testing/selftests/pidfd/pidfd_spawn_security_test.c > create mode 100644 tools/testing/selftests/pidfd/pidfd_spawn_test.c > > -- > 2.52.0 >