From a5034e35725f1090c74cfca95ef234a1db50b495 Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Tue, 3 Jan 2023 12:15:15 +0000 Subject: [PATCH] Implement g_sck_send_fd_set() and g_sck_recv_fd_set() --- common/os_calls.c | 139 ++++++++++++++++++++++++++++++++++++++++++++++ common/os_calls.h | 36 ++++++++++++ 2 files changed, 175 insertions(+) diff --git a/common/os_calls.c b/common/os_calls.c index bb352a4d..3ee3bb32 100644 --- a/common/os_calls.c +++ b/common/os_calls.c @@ -1428,6 +1428,145 @@ g_sck_send(int sck, const void *ptr, unsigned int len, int flags) #endif } +/*****************************************************************************/ +int +g_sck_recv_fd_set(int sck, void *ptr, unsigned int len, + int fds[], unsigned int maxfd, + unsigned int *fdcount) +{ + int rv = -1; +#if !defined(_WIN32) + // The POSIX API gives us no way to see how much ancillary data is + // present for recvmsg() - just use a big buffer. + // + // Use a union, so control_un.control is properly aligned. + union + { + struct cmsghdr cm; + unsigned char control[8192]; + } control_un; + struct msghdr msg = {0}; + + *fdcount = 0; + + /* Set up descriptor for vanilla data */ + struct iovec iov[1] = { {ptr, len} }; + msg.msg_iov = &iov[0]; + msg.msg_iovlen = 1; + + /* Add in the ancillary data buffer */ + msg.msg_control = control_un.control; + msg.msg_controllen = sizeof(control_un.control); + + if ((rv = recvmsg(sck, &msg, 0)) > 0) + { + struct cmsghdr *cmsg; + if ((msg.msg_flags & MSG_CTRUNC) != 0) + { + LOG(LOG_LEVEL_WARNING, "Ancillary data on recvmsg() was truncated"); + } + + // Iterate over the cmsghdr structures in the ancillary data + for (cmsg = CMSG_FIRSTHDR(&msg); + cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) + { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) + { + const unsigned char *data = CMSG_DATA(cmsg); + unsigned int data_len = cmsg->cmsg_len - CMSG_LEN(0); + + // Check the data length doesn't point past the end of + // control_un.control (see below). This shouldn't happen, + // but is conceivable if the ancillary data is truncated + // and the OS doesn't handle that properly. + // + // <-- (sizeof(control_un.control) --> + // +------------------------------------+ + // | | + // +------------------------------------+ + // ^ ^ + // | | <- data_len -> + // | | + // control_un.control data + unsigned int max_data_len = + sizeof(control_un.control) - (data - control_un.control); + if (len > max_data_len) + { + len = max_data_len; + } + + // Process all the file descriptors in the structure + while (data_len >= sizeof(int)) + { + int fd; + memcpy(&fd, data, sizeof(int)); + data += sizeof(int); + data_len -= sizeof(int); + + if (*fdcount < maxfd) + { + fds[(*fdcount)++] = fd; + } + else + { + // No room in the user's buffer for this fd + close(fd); + } + } + } + } + } +#endif /* !WIN32 */ + + return rv; +} + +/*****************************************************************************/ +int +g_sck_send_fd_set(int sck, const void *ptr, unsigned int len, + int fds[], unsigned int fdcount) +{ + int rv = -1; +#if !defined(_WIN32) + struct msghdr msg = {0}; + + /* Set up descriptor for vanilla data */ + struct iovec iov[1] = { {(void *)ptr, len} }; + msg.msg_iov = &iov[0]; + msg.msg_iovlen = 1; + + if (fdcount > 0) + { + unsigned int fdsize = sizeof(fds[0]) * fdcount; /* Payload size */ + /* Allocate ancillary data structure */ + msg.msg_controllen = CMSG_SPACE(fdsize); + msg.msg_control = (struct cmsghdr *)g_malloc(msg.msg_controllen, 1); + if (msg.msg_control == NULL) + { + /* Memory allocation failure */ + LOG(LOG_LEVEL_ERROR, "Error allocating buffer for %u fds", + fdcount); + return -1; + } + + /* Fill in the ancillary data structure */ + struct cmsghdr *cmptr = CMSG_FIRSTHDR(&msg); + cmptr->cmsg_len = CMSG_LEN(fdsize); + cmptr->cmsg_level = SOL_SOCKET; + cmptr->cmsg_type = SCM_RIGHTS; + memcpy(CMSG_DATA(cmptr), fds, fdsize); + } + + rv = sendmsg(sck, &msg, 0); + g_free(msg.msg_control); + +#endif /* !WIN32 */ + + return rv; +} + /*****************************************************************************/ /* returns boolean */ int diff --git a/common/os_calls.h b/common/os_calls.h index f01bd6fc..ba4e3bcd 100644 --- a/common/os_calls.h +++ b/common/os_calls.h @@ -88,6 +88,42 @@ int g_sck_listen(int sck); int g_sck_accept(int sck); int g_sck_recv(int sck, void *ptr, unsigned int len, int flags); int g_sck_send(int sck, const void *ptr, unsigned int len, int flags); +/** + * Receives data and file descriptors on a unix domain socket + * + * @param sck - Socket to receive data + file descriptors from + * @param ptr - Pointer to buffer for incoming data + * @param len - Length of data. Must be > 0 + * @param[out] fds - Array of file descriptors + * @param [in] maxfd - Max number of elements in fds + * @param[out] fdcount - Actual number of file descriptors received + * @return Bytes received, or < 0 for error. + * + * If the result is > 0 but less than len, the file descriptors have + * been received. Get the rest of the data with normal g_sck_recv() calls. + * + * fdcount may be more that maxfd. This indicates that more file descriptors + * were received than there was space for. The excess file descriptors + * are closed and discarded. + */ +int g_sck_recv_fd_set(int sck, void *ptr, unsigned int len, + int fds[], unsigned int maxfd, + unsigned int *fdcount); +/** + * Sends data and file descriptors on a unix domain socket + * + * @param sck - Socket to send data + file descriptors on + * @param ptr - Data to send + * @param len - Length of data. Must be > 0 + * @param fds - Array of file descriptors + * @param fdcount - Number of file descriptors + * @return Bytes sent, or < 0 for error. + * + * If the result is > 0 but less than len, the file descriptors have + * been sent. Send the rest of the data with normal g_sck_send() calls. + */ +int g_sck_send_fd_set(int sck, const void *ptr, unsigned int len, + int fds[], unsigned int fdcount); int g_sck_last_error_would_block(int sck); int g_sck_socket_ok(int sck); int g_sck_can_send(int sck, int millis);