Skip to content

RFC pidfd: add a minimal process spawn builder

Li Chen audit linux-api linux-arch linux-doc linux-fsdevel linux-kselftest linux-mm linux-security-module lkml 1 version In review

Get series with b4
b4 am [email protected]

Versions newest first

  1. v1 RFC Latest24 patches 6 repliesIn review

Patches in v1 No applies recorded yet

#Subject and filesSizeRepliesApplied
1/24pidfd: add spawn builder uapiMAINTAINERS include/uapi/linux/pidfd.h include/uapi/linux/pidfd_spawn.h +1 more+101 -00 replies
2/24libfs: allow custom validation of stashed inode datafs/internal.h fs/libfs.c+5 -10 replies
3/24pidfs: add taskless future pidfd inodesfs/pidfs.c include/linux/pid.h include/linux/pidfs.h+262 -230 replies
4/24pidfd: create taskless spawn buildersMAINTAINERS fs/Makefile fs/pidfd_spawn.c +4 more+106 -60 replies
5/24pidfd: add spawn builder path configurationfs/pidfd_spawn.c+155 -00 replies
6/24exec: expose execveat internals to process buildersMAINTAINERS fs/exec.c fs/exec_internal.h+45 -240 replies
7/24fork: expose vfork completion helperinclude/linux/sched/task.h kernel/fork.c+4 -20 replies
8/24pidfs: attach pids to future pidfd filesfs/pidfs.c include/linux/pidfs.h+87 -40 replies
9/24fork: let process builders supply preallocated pidsinclude/linux/pid.h kernel/fork.c kernel/pid.c+37 -40 replies
10/24pidfs: publish future pidfd filesfs/pidfs.c include/linux/pidfs.h+130 -20 replies
11/24pidfd: add spawn builder state trackingfs/pidfd_spawn.c+49 -10 replies
12/24fork: let kernel callers create embryonic tasksinclude/linux/sched.h include/linux/sched/task.h kernel/fork.c +1 more+27 -02 replies
13/24fork: let new tasks start with task workinclude/linux/sched/task.h kernel/fork.c+8 -00 replies
14/24pidfd: create and execute spawn builder tasksfs/pidfd_spawn.c+402 -40 replies
15/24fork: keep embryonic tasks hidden until exec completesfs/coredump.c fs/exec.c fs/pidfd_spawn.c +4 more+38 -80 replies
16/24audit: add pidfd spawn child contextsinclude/linux/audit.h include/uapi/linux/audit.h kernel/audit.h +1 more+136 -20 replies
17/24pidfd: audit child spawn executionfs/pidfd_spawn.c+29 -00 replies
18/24pidfd: make spawn builder execution signal-safefs/pidfd_spawn.c+183 -60 replies
19/24file: expose spawn file-action helpersfs/file.c fs/internal.h fs/open.c+16 -40 replies
20/24pidfd: add initial spawn file actionsfs/pidfd_spawn.c+163 -80 replies
21/24pidfd: consume spawn builders on the first run attemptfs/pidfd_spawn.c+109 -550 replies
22/24pidfd: expose spawn builder system callsarch/alpha/kernel/syscalls/syscall.tbl arch/arm/tools/syscall.tbl arch/arm64/tools/syscall_32.tbl +31 more+108 -30 replies
23/24selftests/pidfd: cover pidfd spawn builderstools/testing/selftests/landlock/audit.h tools/testing/selftests/pidfd/.gitignore tools/testing/selftests/pidfd/Makefile +12 more+5568 -30 replies
24/24Documentation: describe pidfd spawn buildersDocumentation/userspace-api/index.rst Documentation/userspace-api/pidfd_spawn.rst MAINTAINERS+249 -00 replies

Reviews trailers per patch

No Reviewed-by, Acked-by or Tested-by trailers for v1 yet.

Read the v1 thread

Cover letter [RFC PATCH 00/24] pidfd: add a minimal process spawn builder

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",
        .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/
[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
Signature · 1 line
2.52.0