[RFC PATCH 00/24] pidfd: add a minimal process spawn builder
From: Li Chen <hidden>
Date: 2026-07-16 14:32:57
Also in:
linux-arch, linux-doc, linux-fsdevel, linux-kselftest, linux-mm, linux-security-module, lkml
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/20260716052726.1032092-1-me@linux.beauty/ (local)
* pidfs: handle FS_IOC32_GETVERSION in compat ioctl
https://lore.kernel.org/all/20260716052822.1034228-1-me@linux.beauty/ (local)
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",
.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);
pidfd_spawn_run(fd, &run, sizeof(run));
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/ (local)
[3]: https://lore.kernel.org/all/202606011254.5FCBD65@keescook/ (local)
[4]: https://lore.kernel.org/all/vealb52tv5suireenkke4lul2l3wbnaul2rp3ea545ly5wa5ty@yk3aksvp7skt/ (local)
[5]: https://lore.kernel.org/all/ce71d6df-6851-4e4b-9603-1d55d8d522b8@app.fastmail.com/ (local)
[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