A process can acquire a usable duplicate of a file descriptor that is already open in another process without asking that process to send it over a UNIX domain socket. Linux pidfd_getfd() performs that transfer through a PID file descriptor, subject to a ptrace access check.

The returned descriptor is new in the caller, but the kernel object behind it is not independent. It refers to the same open file description as the target descriptor. That distinction controls offset sharing, file status flags, and operations on the underlying object.

The copied descriptor shares open-file state

A file descriptor is an entry in a process descriptor table. For many file types, that entry points to an open file description maintained by the kernel. pidfd_getfd() creates another descriptor-table entry in the caller that points to the same open file description as targetfd.

target process                     caller
+-----------+                  +-----------+
| fd 7      |                  | new fd 11 |
+-----+-----+                  +-----+-----+
      |                              |
      +--------------+---------------+
                     |
                     v
             open file description
             offset / status flags
                     |
                     v
                file object

This is closer to dup() across a process boundary than to reopening a pathname. If the object has a shared file offset, reads or writes through either descriptor can move that offset. File status flags associated with the open file description are shared as well.

Descriptor flags are a separate layer. The descriptor returned by pidfd_getfd() has FD_CLOEXEC set, so an execve() does not retain it unless the caller explicitly changes that descriptor flag.

A pidfd selects the process without a bare-PID lookup

The first argument is a PID file descriptor, commonly obtained with pidfd_open(). The second argument is the numeric descriptor in the target process.

int remote = 7;
int local = syscall(SYS_pidfd_getfd, pidfd, remote, 0);

if (local == -1) {
    perror("pidfd_getfd");
}

The current flags argument is reserved and must be zero. Linux added the system call in version 5.6. The Linux man-pages interface documents direct use through syscall() because glibc does not provide a dedicated wrapper.

A pidfd provides a stable kernel handle for a process lifetime. That avoids building the operation around a PID that might later identify a different process after PID reuse. It does not freeze the target descriptor table, however. The requested targetfd must still be open in the process when the kernel performs pidfd_getfd().

If the target descriptor is no longer open, the operation fails with EBADF. If the process represented by the pidfd has terminated and has been waited on, the documented error is ESRCH.

Copying a descriptor does not require target cooperation

SCM_RIGHTS can also transfer access to an open file description between processes, but its control flow is cooperative. The sender and receiver need a UNIX domain socket, and the process holding the descriptor must send it.

pidfd_getfd() changes that coordination model. The target does not execute a send operation and does not need a socket connection to the caller. The caller identifies an existing target descriptor and asks the kernel for a duplicate.

That property is useful for process supervisors, debuggers, container infrastructure, and inspection tools that already have an authorized relationship with another process. It also means the operation cannot be treated as ordinary descriptor passing between mutually participating peers.

Ptrace access rules form the permission boundary

The kernel gates pidfd_getfd() with a PTRACE_MODE_ATTACH_REALCREDS access check. Failure of that check produces EPERM.

This check is more than a same-UID test. Ptrace access considers credentials, the target’s dumpable state, capabilities in the relevant user namespace, and Linux Security Module policy. Systems using the Yama LSM can impose additional restrictions through ptrace_scope.

As a result, possession of a pidfd alone does not grant authority to copy arbitrary descriptors from the referenced process. A pidfd identifies the process; the ptrace access check decides whether this cross-process operation is permitted.

That separation matters in privilege designs. Passing a pidfd to another component can provide a process handle without automatically giving that component every operation that accepts a pidfd.

The duplicate can operate on the same underlying object

Because both descriptors reference the same open file description, the caller can use the duplicate with operations supported by the underlying object. For a socket, for example, the duplicate refers to the same socket object rather than creating a new connection endpoint.

This is materially different from reconstructing state from /proc/<pid>/fd metadata. The result of pidfd_getfd() is an actual descriptor that can participate in normal descriptor-based system calls.

The shared identity also creates coordination concerns. An operation through the copied descriptor can affect state observed by the target process. Shared file offsets are the clearest example: a read by the caller may change the offset seen by a later read in the target. Socket operations can likewise act on the same socket object.

A supervisor that only intends to inspect an object therefore still needs to account for the semantics of each operation it performs. The syscall supplies access to the object; it does not create a read-only observational clone.

Descriptor lifetime becomes independent after duplication

Once pidfd_getfd() succeeds, closing the target process’s descriptor does not invalidate the caller’s duplicate. Each descriptor holds its own reference to the shared kernel object. The object remains alive while references remain.

The reverse is also true: closing the caller’s duplicate does not close the target’s descriptor. The descriptors occupy separate tables even though they converge on the same open file description.

This lifetime split is central to the API. pidfd_getfd() captures access to an existing open object at one point in time. After that successful duplication, the caller owns a normal descriptor whose lifetime it manages locally.

The boundary is object transfer, not process introspection

pidfd_getfd() does not expose an entire descriptor table or provide a generic remote-process control channel. The caller must already know the target descriptor number, hold a pidfd, and pass the required access check. Each successful call duplicates one selected descriptor.

Its narrow contract is useful precisely because it preserves normal file-descriptor semantics. The target and caller gain separate descriptor-table entries, while the open file description remains shared. Linux therefore provides cross-process access to a live kernel object without converting that object into a pathname, serialized representation, or cooperative socket message.