$RodHat_
Syscall reference · IPC

SCM_RIGHTS

Pass open file descriptors to another process over a UNIX domain socket.

Since
unknown
glibc
none (ancillary message for sendmsg(2)/recvmsg(2), not a syscall)
System
Linux
Man page
unix(7)

Synopsis

#include <sys/socket.h>
#include <sys/un.h>

unix_socket = socket(AF_UNIX, type, 0);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
/* ancillary data: cmsg_level = SOL_SOCKET, cmsg_type = SCM_RIGHTS */

Rod's take

Your instinct is /proc/<pid>/fd/N. Wrong instinct. SCM_RIGHTS hands the live open file description across a UNIX socket: same offset, same flags, same access the original opener had, which is the entire point of privilege separation. Open the port as root, pass the fd, drop privileges. Receive with MSG_CMSG_CLOEXEC every time, size the control buffer with CMSG_SPACE, and check MSG_CTRUNC, because if the buffer's too small the kernel closes the extras and they're just gone. And close what you receive, unless a slow fd leak is your idea of fun.

Flags and arguments that matter

NameWhat it does
SCM_MAX_FDMax fds per message: 253 (255 before Linux 2.6.38). Exceeding it makes sendmsg(2) fail with EINVAL.
MSG_CMSG_CLOEXECLinux 2.6.23recvmsg(2) flag: set close-on-exec on every received descriptor.
MSG_CTRUNCSet in msg_flags when control data was truncated; the excess fds were closed by the kernel.
CMSG_SPACE / CMSG_LENSize the control buffer and cmsg_len for n descriptors: CMSG_SPACE(n * sizeof(int)).

Errors you will actually hit

errnoMeaning
EBADFsendmsg(2): a descriptor being sent is not an open file descriptor.
ETOOMANYREFSsendmsg(2): in-flight fds exceed RLIMIT_NOFILE and caller lacks CAP_SYS_RESOURCE (Linux 4.5+).
EINVALsendmsg(2): more than SCM_MAX_FD descriptors in one message.
ENOTCONNSocket operation needs a target address but the socket is not connected.

Example

char dummy;
struct iovec iov = { .iov_base = &dummy, .iov_len = 1 };
union { char buf[CMSG_SPACE(sizeof(int))]; struct cmsghdr align; } u;
struct msghdr msg = {
    .msg_iov = &iov, .msg_iovlen = 1,
    .msg_control = u.buf, .msg_controllen = sizeof(u.buf),
};
if (recvmsg(sock, &msg, MSG_CMSG_CLOEXEC) < 0 || (msg.msg_flags & MSG_CTRUNC))
    errx(1, "recvmsg failed or control data truncated");

int fd = -1;
struct cmsghdr *c = CMSG_FIRSTHDR(&msg);
if (c && c->cmsg_level == SOL_SOCKET && c->cmsg_type == SCM_RIGHTS)
    memcpy(&fd, CMSG_DATA(c), sizeof(fd));

Trimmed from the full walkthrough in SCM_RIGHTS: send an open file descriptor to another process over a Unix socket.

BSD equivalent

FreeBSD and OpenBSD: the same SCM_RIGHTS control message on unix(4) sockets.

The long version, from the console

Sources

  1. unix(7), man7.org
  2. recv(2), man7.org
  3. unix(4), FreeBSD
  4. unix(4), OpenBSD

Version numbers are read off the cited man pages, not memory. Spot one that's wrong? Tell Rod.