SCM_RIGHTS lets one process send a reference to an open file through a Unix domain socket. The receiver obtains a file descriptor in its own descriptor table, but the transfer does not reopen the pathname or copy the kernel object. On Linux, the resulting reference has semantics equivalent to duplicating the sender’s descriptor into the receiving process.

That distinction matters whenever a process boundary is also an authority boundary. A supervisor can open a socket, file, pipe, device, or other descriptor-backed object and pass the established reference to a worker. The worker receives access to the already-open object, including open-file state that can remain shared with the sender.

The transferred value is not the descriptor number

A file descriptor is a process-local integer indexing an entry in that process’s descriptor table. Sending integer 7 as ordinary payload has no special effect; descriptor 7 in another process can identify a completely different object or no object at all.

SCM_RIGHTS is ancillary data attached to a Unix domain socket message. The kernel interprets the supplied descriptor numbers in the sending process, acquires references to the corresponding open objects, and installs descriptors for those references in the receiver. The numeric values selected for the receiver are local to that receiver.

A compact send-side shape in C is:

char control[CMSG_SPACE(sizeof(int))] = {0};
struct msghdr msg = {0};
struct cmsghdr *cmsg;

msg.msg_control = control;
msg.msg_controllen = sizeof(control);

cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));

The actual transfer occurs through sendmsg(). The receiver uses recvmsg() and inspects ancillary records rather than treating the control buffer as ordinary application bytes.

Open-file state can remain shared after transfer

On Linux, a descriptor refers through the descriptor table to an open file description. That kernel object carries state such as the current file offset and file status flags. A descriptor received through SCM_RIGHTS refers to the same open file description as the transmitted descriptor, in the same sense as a descriptor produced by dup().

For a seekable regular file, an offset change through one reference can therefore affect subsequent I/O through the other. If the sender reads 128 bytes and advances the shared offset, a receiver using its transferred descriptor can start from that resulting offset unless positioned I/O or an explicit seek changes the interaction.

File status flags associated with the open file description are shared as well. Descriptor flags are different: they belong to the descriptor-table entry. This separation is especially relevant to close-on-exec state.

The transfer therefore does not imply independent file position or independent open-file status. Software that requires independent state generally needs a separate open file description, subject to the semantics of the object and the available API.

Close-on-exec belongs to the receiving descriptor

FD_CLOEXEC is a descriptor flag, not a property of the shared open file description. A transferred descriptor must therefore be treated as a new descriptor-table entry with its own close-on-exec state.

On Linux, recvmsg() accepts MSG_CMSG_CLOEXEC. When supported and supplied, the kernel sets close-on-exec on descriptors received via SCM_RIGHTS. This closes a race that can exist when multithreaded code receives a descriptor and only later calls fcntl() to set FD_CLOEXEC: another thread could execute a new program in the interval.

This is an API-level synchronization property, not merely a convenience flag. The atomic installation of the descriptor with close-on-exec state determines whether an unrelated execve() can inherit the received capability.

Message boundaries and ancillary data form one receive operation

Descriptor transfer is associated with a socket message, but the application still has to validate the ordinary payload and control data returned by recvmsg(). The control buffer has finite capacity. If ancillary data is truncated, MSG_CTRUNC can be reported.

Robust receiving code cannot assume that every control record is SCM_RIGHTS. It needs to inspect cmsg_level, cmsg_type, and record lengths before extracting descriptor values. The receiver also owns any successfully installed descriptors and must close references it does not retain.

The ordinary payload often carries application metadata identifying the role of the transferred object. That metadata is not a kernel guarantee about the descriptor’s type or state. If the protocol requires a socket, directory, or particular access mode, the receiver needs an appropriate validation strategy rather than trusting a label in the message.

Passing a descriptor delegates existing access

A pathname-based handoff asks the receiving process to resolve a name and open an object under its own credentials, namespace view, and timing. Descriptor passing moves a reference that has already crossed those checks at open time, although later operations can still have their own permission checks.

This changes the interface boundary. A privileged broker can bind a listening socket and transfer it to a less privileged service. A parent can create a pipe endpoint and pass only one side. A process can open a directory under controlled resolution rules and transfer that directory reference without requiring the receiver to repeat pathname traversal.

The transferred descriptor is therefore a capability-like handle to a kernel object, bounded by the operations that object and descriptor permit. SCM_RIGHTS does not copy the sender’s process credentials, and it does not grant arbitrary access outside the transferred references.

Lifetime extends beyond the sender’s descriptor entry

Kernel references, rather than descriptor numbers, determine object lifetime. After a successful transfer, closing the sender’s descriptor does not invalidate the receiver’s installed descriptor. Each descriptor entry holds its own reference to the shared open file description.

Conversely, retaining descriptors accidentally can extend resource lifetime. A pipe does not present end-of-file to readers while a write-end reference remains open somewhere. A listening socket remains referenced while transferred descriptors remain installed. Descriptor-passing protocols therefore need explicit ownership rules for success, rejection, partial processing, and shutdown paths.

This is also a reason to close unexpected ancillary descriptors immediately. Ignoring an unwanted descriptor without closing it is not a neutral action; it keeps a kernel reference alive.

Transfer preserves an object boundary, not application intent

SCM_RIGHTS is strongest when the protocol treats descriptor ownership as part of its data model. The kernel can preserve the reference and its open-file semantics across a process boundary, but it cannot encode the application’s intended role for that reference.

The receiver still has to decide which descriptor types are accepted, whether shared offset or status state is appropriate, which descriptors survive future program execution, and when ownership ends. Those decisions sit above the transport mechanism.

The resulting boundary is precise: Unix domain socket ancillary data transfers kernel-backed references, descriptor tables remain process-local, and selected open-file state can remain shared. Designs that preserve those distinctions can delegate already-established resources without pretending that a descriptor is merely an integer or that transfer creates an independent open instance.