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
>