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
| Name | What it does |
|---|---|
| SCM_MAX_FD | Max fds per message: 253 (255 before Linux 2.6.38). Exceeding it makes sendmsg(2) fail with EINVAL. |
| MSG_CMSG_CLOEXECLinux 2.6.23 | recvmsg(2) flag: set close-on-exec on every received descriptor. |
| MSG_CTRUNC | Set in msg_flags when control data was truncated; the excess fds were closed by the kernel. |
| CMSG_SPACE / CMSG_LEN | Size the control buffer and cmsg_len for n descriptors: CMSG_SPACE(n * sizeof(int)). |
Errors you will actually hit
| errno | Meaning |
|---|---|
| EBADF | sendmsg(2): a descriptor being sent is not an open file descriptor. |
| ETOOMANYREFS | sendmsg(2): in-flight fds exceed RLIMIT_NOFILE and caller lacks CAP_SYS_RESOURCE (Linux 4.5+). |
| EINVAL | sendmsg(2): more than SCM_MAX_FD descriptors in one message. |
| ENOTCONN | Socket 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
Version numbers are read off the cited man pages, not memory. Spot one that's wrong? Tell Rod.
