From 7934bcdfdcc2ad2581d977a7a5e9d41e46e3068a Mon Sep 17 00:00:00 2001 From: Michael Kerrisk Date: Thu, 12 Apr 2018 10:53:21 +0200 Subject: [PATCH] unix.7: ERRORS: add EBADF for sending closed file descriptor with SCM_RIGHTS As noted by Rusty Russell: I was really surprised that sendmsg() returned EBADF on a valid fd; turns out I was using sendmsg with SCM_RIGHTS to send a closed fd, which gives EBADF (see test program below). But this is only obliquely referenced in unix(7): SCM_RIGHTS Send or receive a set of open file descriptors from another process. The data portion contains an integer array of the file descriptors. The passed file descriptors behave as though they have been created with dup(2). EBADF is not mentioned in the unix(7) ERRORS (it's mentioned in dup(2)). int fdpass_send(int sockout, int fd) { /* From the cmsg(3) manpage: */ struct msghdr msg = { 0 }; struct cmsghdr *cmsg; struct iovec iov; char c = 0; union { /* Ancillary data buffer, wrapped in a union in order to ensure it is suitably aligned */ char buf[CMSG_SPACE(sizeof(fd))]; struct cmsghdr align; } u; msg.msg_control = u.buf; msg.msg_controllen = sizeof(u.buf); memset(&u, 0, sizeof(u)); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd)); msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_flags = 0; /* Keith Packard reports that 0-length sends don't work, so we * always send 1 byte. */ iov.iov_base = &c; iov.iov_len = 1; return sendmsg(sockout, &msg, 0); } int fdpass_recv(int sockin) { /* From the cmsg(3) manpage: */ struct msghdr msg = { 0 }; struct cmsghdr *cmsg; struct iovec iov; int fd; char c; union { /* Ancillary data buffer, wrapped in a union in order to ensure it is suitably aligned */ char buf[CMSG_SPACE(sizeof(fd))]; struct cmsghdr align; } u; msg.msg_control = u.buf; msg.msg_controllen = sizeof(u.buf); msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_flags = 0; iov.iov_base = &c; iov.iov_len = 1; if (recvmsg(sockin, &msg, 0) < 0) return -1; cmsg = CMSG_FIRSTHDR(&msg); if (!cmsg || cmsg->cmsg_len != CMSG_LEN(sizeof(fd)) || cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) { errno = -EINVAL; return -1; } memcpy(&fd, CMSG_DATA(cmsg), sizeof(fd)); return fd; } static void child(int sockfd) { int newfd = fdpass_recv(sockfd); assert(newfd < 0); exit(0); } int main(void) { int sv[2]; int pid, ret; assert(socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == 0); pid = fork(); if (pid == 0) { close(sv[1]); child(sv[0]); } close(sv[0]); ret = fdpass_send(sv[1], sv[0]); printf("fdpass of bad fd return %i (%s)\n", ret, strerror(errno)); return 0; } Reported-by: Rusty Russell Signed-off-by: Michael Kerrisk --- man7/unix.7 | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/man7/unix.7 b/man7/unix.7 index 79f93899c..a8efc8940 100644 --- a/man7/unix.7 +++ b/man7/unix.7 @@ -467,6 +467,15 @@ see The specified local address is already in use or the filesystem socket object already exists. .TP +.B EBADF +This error can occur for +.BR sendmsg (2) +when sending a file descriptor as ancillary data over +a UNIX domain socket (see the description of +.BR SCM_RIGHTS , +above), and indicates that the file descriptor number that +is being sent is not valid (e.g., it is not an open file descripror). +.TP .B ECONNREFUSED The remote address specified by .BR connect (2)