A process can acquire a new descriptor that refers to the same open file description as a descriptor already held by another process, without asking that target process to send it. Linux provides this operation through pidfd_getfd(). The resulting descriptor is local to the caller, but the kernel object behind it is shared with the target descriptor.
That distinction matters because a descriptor number is only an entry in one process’s descriptor table. The open file description carries state such as the current file offset and file status flags. Duplicating across a process boundary therefore transfers access to an existing kernel file instance rather than reopening the pathname or constructing an independent instance.
The copied descriptor retains open-file state
The operation takes a PID file descriptor, a descriptor number from the referenced process, and a flags argument:
int fd = syscall(SYS_pidfd_getfd, pidfd, targetfd, 0);On success, fd is allocated in the caller. It refers to the same open file description as targetfd. If that object has a shared file offset, reads or writes through either descriptor can move the offset observed through the other. File status flags associated with the open file description are shared for the same reason.
Descriptor flags are a separate layer. Linux sets FD_CLOEXEC on the descriptor returned by pidfd_getfd(). That flag belongs to the caller’s descriptor entry and causes the descriptor to close across a successful execve() unless the caller changes it.
The current API requires flags to be zero. A nonzero value is rejected with EINVAL.
PID descriptors stabilize process selection
A numeric PID can be recycled after a process exits. pidfd_getfd() does not accept a bare PID as its process handle. It accepts a PID file descriptor, normally obtained with pidfd_open() or created through a suitable process-creation interface.
This design makes the target an object referenced by a descriptor rather than a number that an application repeatedly resolves. The descriptor identity does not turn access into an unconditional capability, however. The kernel still performs a ptrace access check for the duplication request.
The relevant check is PTRACE_MODE_ATTACH_REALCREDS. A caller that can hold a pidfd but fails this authorization check receives EPERM. Process identity and permission to extract one of its descriptors are separate concerns.
The target process does not participate
Descriptor passing through SCM_RIGHTS on a UNIX domain socket requires a communication channel and an active sender. pidfd_getfd() has a different coordination model. Once the caller has an appropriate pidfd and passes the access check, the target process does not need to execute a send operation.
That property fits supervision, inspection, checkpointing, and process-management designs in which the controller already has authority over a target but no cooperative descriptor-transfer protocol exists.
It does not make the operation equivalent to opening /proc/<pid>/fd/<n>. Reopening a procfs descriptor link can have object-specific restrictions and may create different open-file state depending on the object and operation. pidfd_getfd() explicitly duplicates the existing descriptor’s open file description.
Shared offsets create observable coupling
Consider a regular file whose target descriptor currently points at offset 4096. After a successful duplication, both descriptors refer to the same open file description:
target fd ----+
|
v
open file description
offset = 4096
^
|
caller fd ----+A read() through the caller descriptor can advance that shared offset. A later sequential read() through the target descriptor then starts from the updated position. The cross-process duplication has therefore created a state-sharing relationship, not a snapshot.
Operations that use explicit offsets, such as pread(), avoid changing the shared file position, but they do not make the descriptors independent. Other open-file state remains shared where the underlying API defines it that way.
For sockets and other descriptor-backed kernel objects, the duplicated descriptor similarly refers to the same underlying object. Actions performed through one reference can affect the object visible through the other.
Descriptor lifetime becomes independent after duplication
Sharing the open file description does not mean either process owns the other process’s descriptor-table entry. After pidfd_getfd() succeeds, the caller and target have separate descriptor entries that happen to reference the same kernel object.
Closing the target descriptor does not close the caller’s duplicate. Closing the caller’s descriptor does not remove the target entry. The open file description remains alive while references to it remain.
This lifetime property can intentionally extend the lifetime of resources. A controller that duplicates a socket, pipe endpoint, event descriptor, or regular file can keep the underlying object referenced after the target closes its own descriptor. Resource ownership policies must account for that additional reference.
Descriptor numbers remain process-local
The targetfd argument is interpreted in the descriptor table of the process referenced by pidfd. The returned integer is allocated in the caller’s descriptor table and can have an unrelated numeric value.
A monitoring system therefore cannot treat descriptor numbers as global object identifiers. The pair of process context and descriptor number identifies the table entry; the open file description behind that entry is a distinct kernel object.
If targetfd is no longer open when the kernel evaluates the request, the operation fails with EBADF. This can race with descriptor closure or replacement in a target that continues to mutate its descriptor table. A pidfd stabilizes which process is referenced, not the meaning of every descriptor slot inside that process for all future time.
Failure modes expose resource and lifecycle boundaries
pidfd_getfd() can fail with EMFILE when the caller has reached its per-process descriptor limit and with ENFILE when the system-wide open-file limit is exhausted. These failures occur even when the target descriptor itself remains valid.
ESRCH can indicate that the process referenced by the pidfd no longer exists in the state required by the operation. EBADF also covers an invalid pidfd or an invalid target descriptor. Code using the interface must therefore separate target lifecycle, descriptor-slot validity, authorization, and caller resource limits.
Those boundaries are especially relevant in process supervisors. A successful pidfd acquisition does not guarantee that a later descriptor extraction will succeed; the target can exit, close a descriptor, or change its descriptor topology between the two operations.
Cross-process duplication is shared-object acquisition
pidfd_getfd() is best modeled as acquiring another reference to an existing open file description. It does not copy file contents, clone open-file state, or reopen a resource by name. The caller receives its own descriptor entry while sharing the kernel object and associated open-file state with the target.
That model explains both its utility and its hazards. It can recover access to a live resource without cooperation from the target, but it can also couple offsets, extend resource lifetime, and expose operations on the same underlying object. The ptrace authorization check limits who may cross that process boundary, while the resulting descriptor follows ordinary descriptor and open-file-description lifetime rules.