Thread (26 messages) 26 messages, 2 authors, 4d ago
COOLING4d

[RFC PATCH 24/24] Documentation: describe pidfd spawn builders

From: Li Chen <hidden>
Date: 2026-07-16 14:47:26
Also in: linux-api, linux-arch, linux-doc, linux-fsdevel, linux-kselftest, linux-mm, lkml
Subsystem: documentation, the rest · Maintainers: Jonathan Corbet, Linus Torvalds

Document the taskless pidfd lifecycle, source-state and authority
model, optional path configuration, run and file-action ABI, one-shot
failure classification, and embryonic process visibility.

Describe policy and audit interactions, current accounting behavior,
and the features still missing from the initial interface. Add the
document to the userspace API index and PIDFD API maintenance entry.

Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Li Chen <redacted>
---
 Documentation/userspace-api/index.rst       |   1 +
 Documentation/userspace-api/pidfd_spawn.rst | 247 ++++++++++++++++++++
 MAINTAINERS                                 |   1 +
 3 files changed, 249 insertions(+)
 create mode 100644 Documentation/userspace-api/pidfd_spawn.rst
diff --git a/Documentation/userspace-api/index.rst b/Documentation/userspace-api/index.rst
index a68b1bea57a85..28da6a4da7c78 100644
--- a/Documentation/userspace-api/index.rst
+++ b/Documentation/userspace-api/index.rst
@@ -21,6 +21,7 @@ System calls
    ebpf/index
    ioctl/index
    mseal
+   pidfd_spawn
    rseq
 
 Security-related interfaces
diff --git a/Documentation/userspace-api/pidfd_spawn.rst b/Documentation/userspace-api/pidfd_spawn.rst
new file mode 100644
index 0000000000000..2aaf5db85f13b
--- /dev/null
+++ b/Documentation/userspace-api/pidfd_spawn.rst
@@ -0,0 +1,247 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+====================
+Pidfd spawn builders
+====================
+
+Pidfd spawn builders create a new process image with kernel-mediated setup.
+They do not expose a userspace child stub between process creation and exec.
+
+The interface is::
+
+    int fd = pidfd_open(0, PIDFD_EMPTY);
+
+    /* Optional executable-path staging. */
+    pidfd_config(fd, PIDFD_CONFIG_SET_STRING,
+                 PIDFD_CONFIG_KEY_PATH, path, 0);
+
+    pidfd_spawn_run(fd, &args, sizeof(args));
+
+The same fd starts as a taskless pidfs object and becomes the child's pidfd
+when a task is created. The current implementation supports an executable
+path, argv, envp, and ordered ``DUP2``, ``CLOSE_RANGE``, and ``FCHDIR`` file
+actions.
+
+Opening a builder
+=================
+
+``pidfd_open(0, PIDFD_EMPTY)`` returns a close-on-exec builder fd.
+``PIDFD_NONBLOCK`` may be combined with ``PIDFD_EMPTY``. Other flags are
+rejected. ``PIDFD_EMPTY`` is rejected for a nonzero pid.
+
+Opening the builder allocates a pidfs inode and builder state. It does not
+allocate a ``struct pid`` or task, and does not charge ``RLIMIT_NPROC`` or the
+pids cgroup controller. PID allocation and process-count accounting occur when
+``pidfd_spawn_run()`` calls the normal task-creation path.
+
+The pidfs inode has a stable identity before and after task creation. A pidfd
+opened from the eventual numeric pid refers to the same pidfs inode. A poll or
+epoll registration made while the fd is taskless remains attached across
+publication and can report the child's eventual exit.
+
+Process-dependent pidfd operations return ``ESRCH`` while the builder is
+taskless. This includes signal delivery, ``pidfd_getfd()``,
+``PIDFD_GET_INFO``, namespace ioctls, ``setns()``, process memory operations,
+and process-dependent pidfs xattrs. Argument validation may fail before the
+taskless-state check. ``poll()`` reports no event while the builder may still
+publish a task. After a terminal taskless failure, ``poll()`` and ``epoll``
+report ``POLLERR | POLLHUP`` and wake registrations made before the failure.
+Inode-only operations such as ``FS_IOC_GETVERSION`` remain available.
+
+The builder state follows the lifetime of the pidfs inode. Duplicated or
+reopened descriptors and VFS references to that inode keep it alive. Releasing
+the final reference before a run discards the taskless builder without
+creating a process.
+
+Source state and authority
+==========================
+
+The current mode is source-based. The child is created from the calling
+process at run time. State changed after ``pidfd_open()`` but before
+``pidfd_spawn_run()``, including the current directory and file descriptor
+table, is therefore visible to normal process creation. Non-close-on-exec file
+descriptors remain available to the new image unless a file action changes
+them. The exec path applies ``FD_CLOEXEC`` normally.
+
+File and filesystem state are private child copies before file actions run.
+The current backend shares the address space only for its internal
+vfork-style setup and replaces it during exec. Applications must not depend on
+that backend detail.
+
+Configuration and run authority is bound to the mm, exact credential object,
+and child PID namespace recorded when the builder is opened. Passing the fd
+through ``SCM_RIGHTS`` or another fd-passing mechanism does not by itself
+delegate authority to configure or run it. A rejected authority check does not
+consume the builder.
+
+Configuring an executable path
+==============================
+
+The optional configuration command is::
+
+    pidfd_config(fd, PIDFD_CONFIG_SET_STRING,
+                 PIDFD_CONFIG_KEY_PATH, path, 0);
+
+The command copies ``path`` into builder state. Calling it again before a run
+replaces the staged string. The key and value must be valid NUL-terminated
+userspace strings, and ``aux`` must be zero. Unknown commands or keys return
+``EOPNOTSUPP``.
+
+Staging is not required when the run arguments provide ``path``. Supplying a
+run-time path when a path is already staged, or supplying neither form,
+returns ``EINVAL``. This selection is validated before a run-time path pointer
+is dereferenced, so a staged-path conflict takes precedence over a fault in
+that pointer. Relative paths are resolved after ordered file actions from the
+child's resulting working directory. Without an ``FCHDIR`` action, that is
+the working directory copied at run time. The kernel does not search ``PATH``.
+
+Run arguments
+=============
+
+Version 0 of ``struct pidfd_spawn_run_args`` contains:
+
+* ``path``: an optional executable-path pointer;
+* ``argv``: a required argument-vector pointer;
+* ``envp``: an environment-vector pointer, or zero for an empty environment;
+* ``actions``, ``nr_actions``, and ``action_size``: an optional ordered
+  file-action array.
+
+``flags`` and all reserved fields must be zero. The syscall size must be at
+least ``PIDFD_SPAWN_RUN_SIZE_VER0`` and no larger than ``PAGE_SIZE``. Unknown
+trailing bytes in a larger structure must be zero.
+
+``path``, ``argv``, ``envp``, and ``actions`` are full-width userspace virtual
+addresses stored in ``__aligned_u64`` fields on every ABI. Native calls must
+fit the native pointer width. Compat calls must fit ``compat_uptr_t``; nonzero
+upper bits are rejected with ``EFAULT`` before task creation. Entries read
+from compat argv and envp arrays are compat pointers.
+
+File actions
+============
+
+When ``nr_actions`` is zero, ``actions`` and ``action_size`` must also be zero.
+Otherwise ``actions`` must be nonzero and ``action_size`` must be at least
+``PIDFD_SPAWN_ACTION_SIZE_VER0`` and aligned to eight bytes. A larger element
+is accepted only when its unknown tail is zero. The complete action payload is
+limited to 64 KiB; the interface does not impose a fixed action count below
+that byte limit.
+
+Actions execute in array order against child-private file and filesystem
+state before exec:
+
+``PIDFD_SPAWN_ACTION_DUP2``
+    Duplicate ``fd`` onto ``newfd``. If both numbers are equal, clear
+    ``FD_CLOEXEC`` on that descriptor. ``flags`` and reserved fields must be
+    zero.
+
+``PIDFD_SPAWN_ACTION_CLOSE_RANGE``
+    Apply ``close_range(fd, newfd, flags)``. ``newfd`` must not be less than
+    ``fd``. Only ``CLOSE_RANGE_UNSHARE`` and ``CLOSE_RANGE_CLOEXEC`` are
+    accepted.
+
+``PIDFD_SPAWN_ACTION_FCHDIR``
+    Change the child working directory to ``fd``. ``flags``, ``newfd``, and
+    reserved fields must be zero.
+
+An unknown action type returns ``EOPNOTSUPP``. Invalid fields or arguments
+return ``EINVAL``. Errors from applying an otherwise valid action are reported
+as run failures after task publication.
+
+One-shot execution and results
+==============================
+
+After fd type, creator authority, and source ptrace checks succeed, the first
+``pidfd_spawn_run()`` attempt atomically claims the builder before copying its
+run arguments. Argument faults, validation errors, memory-allocation failures,
+process limits, and task-creation failures after that claim are terminal.
+Later configuration and run attempts return ``EBUSY``.
+
+Checks that fail before the claim do not consume the builder. These include an
+invalid fd type, creator-authority mismatch, or an already traced source.
+
+On successful setup and exec, ``pidfd_spawn_run()`` returns the positive child
+pid as seen in the caller's PID namespace. The pidfd remains the stable process
+identity.
+
+A failure before task creation returns a negative error and leaves the fd
+terminal but taskless. Ordinary pidfd operations continue to return ``ESRCH``.
+``poll()`` and ``epoll`` report ``POLLERR | POLLHUP``. The builder cannot be
+retried; a caller must open a new builder.
+
+A setup or exec failure after publication returns a negative error, leaves the
+same fd as the child's pidfd, and makes the child exit with status 127. Normal
+``SIGCHLD``, ``SIG_IGN``, and ``SA_NOCLDWAIT`` rules determine whether it must
+be reaped. ``PIDFD_GET_INFO`` with ``PIDFD_INFO_EXIT`` distinguishes this case
+from a terminal taskless failure.
+
+Internal restart errors after the claim are returned to userspace as
+``EINTR``. The kernel does not automatically restart a consumed run and
+replace its first result with ``EBUSY``.
+
+A fatal signal delivered to the published child determines the child's exit
+status if it wins during setup. It is not reported as an exec setup error, so
+the run can return the positive child pid even when that child exits from the
+signal.
+
+Embryonic process state
+=======================
+
+After task publication and before successful exec completion, the child is in
+an embryonic exec state. It is not yet a normal userspace process image and
+does not have a valid userspace register frame on every architecture.
+
+During this interval:
+
+* direct ptrace access and ``pidfd_getfd()`` are denied;
+* procfs PID lookup and iteration treat the task as absent to other tasks,
+  preventing access to the source address space temporarily shared by the
+  implementation; the embryonic task may resolve its own procfs entries for
+  executable and interpreter lookup;
+* ``PIDFD_GET_INFO`` reports ``PIDFD_COREDUMP_SKIP`` when coredump state is
+  requested;
+* ordinary pidfd identity, signal, wait, and poll mechanisms operate on the
+  published task as their normal permission checks allow.
+
+Successful exec releases the embryonic state before exec event publication. A
+failed setup never exposes the borrowed pre-exec frame to userspace; the child
+exits instead.
+
+Security policy and audit
+=========================
+
+File actions and exec run inside the child kernel path. They do not appear to
+seccomp as separate ``dup2()``, ``close_range()``, ``fchdir()``, or
+``execve()`` syscalls. A source-side seccomp filter can reject
+``pidfd_spawn_run()`` before the builder is claimed. A policy that intends to
+prevent this operation must filter ``pidfd_spawn_run()`` itself; denying only
+the syscall numbers corresponding to its file actions or exec does not mediate
+the equivalent in-kernel work. In particular, an exec-only denylist that
+allows unknown syscalls does not block the initial spawn exec; it must add
+``pidfd_spawn_run()``. A syscall allowlist does not permit the new operation
+until it adds that syscall. Seccomp state inherited by the child remains active
+after exec. LSM checks, including Landlock executable access, still mediate the
+underlying exec.
+
+Audit records the source syscall transaction and a separate
+``AUDIT_PIDFD_SPAWN`` child transaction for executable lookup and exec
+arguments. The child does not emit a synthetic second ``AUDIT_SYSCALL``
+record. The child transaction is selected using the originating
+``pidfd_spawn_run()`` syscall number, so an existing exit rule that names only
+``execve()`` or ``execveat()`` does not select it. Such rules must also select
+``pidfd_spawn_run()`` with the current audit model. File actions are not
+emitted as decoded per-action audit records.
+
+An already traced source is rejected before the claim. The current backend
+does not implement ptrace auto-attach events for the created child.
+
+Current limitations
+===================
+
+The interface does not yet implement the full ``posix_spawn()`` surface. It
+lacks open and close file actions, resetids, signal mask and default controls,
+scheduler attributes, affinity, process groups, sessions, explicit cgroup
+placement, ``PATH`` search, and exec by fd.
+
+It also does not provide pristine or no-source process creation, executable
+metadata caching, or reusable spawn templates. The current implementation is
+source-based and uses the existing vfork/exec machinery internally.
diff --git a/MAINTAINERS b/MAINTAINERS
index 85b1306cb2ff3..0b6d466787a60 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21288,6 +21288,7 @@ M:	Christian Brauner <christian@brauner.io>
 L:	linux-kernel@vger.kernel.org
 S:	Maintained
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/brauner/linux.git
+F:	Documentation/userspace-api/pidfd_spawn.rst
 F:	fs/pidfd_spawn.c
 F:	include/linux/pidfd_spawn.h
 F:	include/uapi/linux/pidfd_spawn.h
-- 
2.52.0
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help