|  | // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "base/posix/unix_domain_socket.h" | 
|  |  | 
|  | #include <errno.h> | 
|  | #include <sys/socket.h> | 
|  | #if !defined(OS_NACL_NONSFI) | 
|  | #include <sys/un.h> | 
|  | #endif | 
|  | #include <unistd.h> | 
|  |  | 
|  | #include <vector> | 
|  |  | 
|  | #include "base/files/scoped_file.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/pickle.h" | 
|  | #include "base/posix/eintr_wrapper.h" | 
|  | #include "base/stl_util.h" | 
|  | #include "build_config.h" | 
|  |  | 
|  | #if !defined(OS_NACL_NONSFI) | 
|  | #include <sys/uio.h> | 
|  | #endif | 
|  |  | 
|  | namespace base { | 
|  |  | 
|  | const size_t UnixDomainSocket::kMaxFileDescriptors = 16; | 
|  |  | 
|  | #if !defined(OS_NACL_NONSFI) | 
|  | bool CreateSocketPair(ScopedFD* one, ScopedFD* two) { | 
|  | int raw_socks[2]; | 
|  | #if defined(OS_MACOSX) | 
|  | // macOS does not support SEQPACKET. | 
|  | const int flags = SOCK_STREAM; | 
|  | #else | 
|  | const int flags = SOCK_SEQPACKET; | 
|  | #endif | 
|  | if (socketpair(AF_UNIX, flags, 0, raw_socks) == -1) | 
|  | return false; | 
|  | #if defined(OS_MACOSX) | 
|  | // On macOS, preventing SIGPIPE is done with socket option. | 
|  | const int no_sigpipe = 1; | 
|  | if (setsockopt(raw_socks[0], SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, | 
|  | sizeof(no_sigpipe)) != 0) | 
|  | return false; | 
|  | if (setsockopt(raw_socks[1], SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, | 
|  | sizeof(no_sigpipe)) != 0) | 
|  | return false; | 
|  | #endif | 
|  | one->reset(raw_socks[0]); | 
|  | two->reset(raw_socks[1]); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // static | 
|  | bool UnixDomainSocket::EnableReceiveProcessId(int fd) { | 
|  | #if !defined(OS_MACOSX) | 
|  | const int enable = 1; | 
|  | return setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &enable, sizeof(enable)) == 0; | 
|  | #else | 
|  | // SO_PASSCRED is not supported on macOS. | 
|  | return true; | 
|  | #endif  // OS_MACOSX | 
|  | } | 
|  | #endif  // !defined(OS_NACL_NONSFI) | 
|  |  | 
|  | // static | 
|  | bool UnixDomainSocket::SendMsg(int fd, | 
|  | const void* buf, | 
|  | size_t length, | 
|  | const std::vector<int>& fds) { | 
|  | struct msghdr msg = {}; | 
|  | struct iovec iov = {const_cast<void*>(buf), length}; | 
|  | msg.msg_iov = &iov; | 
|  | msg.msg_iovlen = 1; | 
|  |  | 
|  | char* control_buffer = nullptr; | 
|  | if (fds.size()) { | 
|  | const unsigned control_len = CMSG_SPACE(sizeof(int) * fds.size()); | 
|  | control_buffer = new char[control_len]; | 
|  |  | 
|  | struct cmsghdr* cmsg; | 
|  | msg.msg_control = control_buffer; | 
|  | msg.msg_controllen = control_len; | 
|  | cmsg = CMSG_FIRSTHDR(&msg); | 
|  | cmsg->cmsg_level = SOL_SOCKET; | 
|  | cmsg->cmsg_type = SCM_RIGHTS; | 
|  | cmsg->cmsg_len = CMSG_LEN(sizeof(int) * fds.size()); | 
|  | memcpy(CMSG_DATA(cmsg), &fds[0], sizeof(int) * fds.size()); | 
|  | msg.msg_controllen = cmsg->cmsg_len; | 
|  | } | 
|  |  | 
|  | // Avoid a SIGPIPE if the other end breaks the connection. | 
|  | // Due to a bug in the Linux kernel (net/unix/af_unix.c) MSG_NOSIGNAL isn't | 
|  | // regarded for SOCK_SEQPACKET in the AF_UNIX domain, but it is mandated by | 
|  | // POSIX. On Mac MSG_NOSIGNAL is not supported, so we need to ensure that | 
|  | // SO_NOSIGPIPE is set during socket creation. | 
|  | #if defined(OS_MACOSX) | 
|  | const int flags = 0; | 
|  | int no_sigpipe = 0; | 
|  | socklen_t no_sigpipe_len = sizeof(no_sigpipe); | 
|  | DPCHECK(getsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, | 
|  | &no_sigpipe_len) == 0) | 
|  | << "Failed ot get socket option."; | 
|  | DCHECK(no_sigpipe) << "SO_NOSIGPIPE not set on the socket."; | 
|  | #else | 
|  | const int flags = MSG_NOSIGNAL; | 
|  | #endif  // OS_MACOSX | 
|  | const ssize_t r = HANDLE_EINTR(sendmsg(fd, &msg, flags)); | 
|  | const bool ret = static_cast<ssize_t>(length) == r; | 
|  | delete[] control_buffer; | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | // static | 
|  | ssize_t UnixDomainSocket::RecvMsg(int fd, | 
|  | void* buf, | 
|  | size_t length, | 
|  | std::vector<ScopedFD>* fds) { | 
|  | return UnixDomainSocket::RecvMsgWithPid(fd, buf, length, fds, nullptr); | 
|  | } | 
|  |  | 
|  | // static | 
|  | ssize_t UnixDomainSocket::RecvMsgWithPid(int fd, | 
|  | void* buf, | 
|  | size_t length, | 
|  | std::vector<ScopedFD>* fds, | 
|  | ProcessId* pid) { | 
|  | return UnixDomainSocket::RecvMsgWithFlags(fd, buf, length, 0, fds, pid); | 
|  | } | 
|  |  | 
|  | // static | 
|  | ssize_t UnixDomainSocket::RecvMsgWithFlags(int fd, | 
|  | void* buf, | 
|  | size_t length, | 
|  | int flags, | 
|  | std::vector<ScopedFD>* fds, | 
|  | ProcessId* out_pid) { | 
|  | fds->clear(); | 
|  |  | 
|  | struct msghdr msg = {}; | 
|  | struct iovec iov = {buf, length}; | 
|  | msg.msg_iov = &iov; | 
|  | msg.msg_iovlen = 1; | 
|  |  | 
|  | const size_t kControlBufferSize = | 
|  | CMSG_SPACE(sizeof(int) * kMaxFileDescriptors) | 
|  | #if !defined(OS_NACL_NONSFI) && !defined(OS_MACOSX) | 
|  | // The PNaCl toolchain for Non-SFI binary build and macOS do not support | 
|  | // ucred. macOS supports xucred, but this structure is insufficient. | 
|  | + CMSG_SPACE(sizeof(struct ucred)) | 
|  | #endif  // OS_NACL_NONSFI or OS_MACOSX | 
|  | ; | 
|  | char control_buffer[kControlBufferSize]; | 
|  | msg.msg_control = control_buffer; | 
|  | msg.msg_controllen = sizeof(control_buffer); | 
|  |  | 
|  | const ssize_t r = HANDLE_EINTR(recvmsg(fd, &msg, flags)); | 
|  | if (r == -1) | 
|  | return -1; | 
|  |  | 
|  | int* wire_fds = nullptr; | 
|  | unsigned wire_fds_len = 0; | 
|  | ProcessId pid = -1; | 
|  |  | 
|  | if (msg.msg_controllen > 0) { | 
|  | struct cmsghdr* cmsg; | 
|  | for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { | 
|  | const unsigned payload_len = cmsg->cmsg_len - CMSG_LEN(0); | 
|  | if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { | 
|  | DCHECK_EQ(payload_len % sizeof(int), 0u); | 
|  | DCHECK_EQ(wire_fds, static_cast<void*>(nullptr)); | 
|  | wire_fds = reinterpret_cast<int*>(CMSG_DATA(cmsg)); | 
|  | wire_fds_len = payload_len / sizeof(int); | 
|  | } | 
|  | #if !defined(OS_NACL_NONSFI) && !defined(OS_MACOSX) | 
|  | // The PNaCl toolchain for Non-SFI binary build and macOS do not support | 
|  | // SCM_CREDENTIALS. | 
|  | if (cmsg->cmsg_level == SOL_SOCKET && | 
|  | cmsg->cmsg_type == SCM_CREDENTIALS) { | 
|  | DCHECK_EQ(payload_len, sizeof(struct ucred)); | 
|  | DCHECK_EQ(pid, -1); | 
|  | pid = reinterpret_cast<struct ucred*>(CMSG_DATA(cmsg))->pid; | 
|  | } | 
|  | #endif  // !defined(OS_NACL_NONSFI) && !defined(OS_MACOSX) | 
|  | } | 
|  | } | 
|  |  | 
|  | if (msg.msg_flags & MSG_TRUNC || msg.msg_flags & MSG_CTRUNC) { | 
|  | if (msg.msg_flags & MSG_CTRUNC) { | 
|  | // Extraordinary case, not caller fixable. Log something. | 
|  | LOG(ERROR) << "recvmsg returned MSG_CTRUNC flag, buffer len is " | 
|  | << msg.msg_controllen; | 
|  | } | 
|  | for (unsigned i = 0; i < wire_fds_len; ++i) | 
|  | close(wire_fds[i]); | 
|  | errno = EMSGSIZE; | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (wire_fds) { | 
|  | for (unsigned i = 0; i < wire_fds_len; ++i) | 
|  | fds->push_back(ScopedFD(wire_fds[i]));  // TODO(mdempsky): emplace_back | 
|  | } | 
|  |  | 
|  | if (out_pid) { | 
|  | #if defined(OS_MACOSX) | 
|  | socklen_t pid_size = sizeof(pid); | 
|  | if (getsockopt(fd, SOL_LOCAL, LOCAL_PEERPID, &pid, &pid_size) != 0) | 
|  | pid = -1; | 
|  | #else | 
|  | // |pid| will legitimately be -1 if we read EOF, so only DCHECK if we | 
|  | // actually received a message.  Unfortunately, Linux allows sending zero | 
|  | // length messages, which are indistinguishable from EOF, so this check | 
|  | // has false negatives. | 
|  | if (r > 0 || msg.msg_controllen > 0) | 
|  | DCHECK_GE(pid, 0); | 
|  | #endif | 
|  |  | 
|  | *out_pid = pid; | 
|  | } | 
|  |  | 
|  | return r; | 
|  | } | 
|  |  | 
|  | #if !defined(OS_NACL_NONSFI) | 
|  | // static | 
|  | ssize_t UnixDomainSocket::SendRecvMsg(int fd, | 
|  | uint8_t* reply, | 
|  | unsigned max_reply_len, | 
|  | int* result_fd, | 
|  | const Pickle& request) { | 
|  | return UnixDomainSocket::SendRecvMsgWithFlags(fd, reply, max_reply_len, | 
|  | 0, /* recvmsg_flags */ | 
|  | result_fd, request); | 
|  | } | 
|  |  | 
|  | // static | 
|  | ssize_t UnixDomainSocket::SendRecvMsgWithFlags(int fd, | 
|  | uint8_t* reply, | 
|  | unsigned max_reply_len, | 
|  | int recvmsg_flags, | 
|  | int* result_fd, | 
|  | const Pickle& request) { | 
|  | // This socketpair is only used for the IPC and is cleaned up before | 
|  | // returning. | 
|  | ScopedFD recv_sock, send_sock; | 
|  | if (!CreateSocketPair(&recv_sock, &send_sock)) | 
|  | return -1; | 
|  |  | 
|  | { | 
|  | std::vector<int> send_fds; | 
|  | send_fds.push_back(send_sock.get()); | 
|  | if (!SendMsg(fd, request.data(), request.size(), send_fds)) | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | // Close the sending end of the socket right away so that if our peer closes | 
|  | // it before sending a response (e.g., from exiting), RecvMsgWithFlags() will | 
|  | // return EOF instead of hanging. | 
|  | send_sock.reset(); | 
|  |  | 
|  | std::vector<ScopedFD> recv_fds; | 
|  | // When porting to OSX keep in mind it doesn't support MSG_NOSIGNAL, so the | 
|  | // sender might get a SIGPIPE. | 
|  | const ssize_t reply_len = RecvMsgWithFlags( | 
|  | recv_sock.get(), reply, max_reply_len, recvmsg_flags, &recv_fds, nullptr); | 
|  | recv_sock.reset(); | 
|  | if (reply_len == -1) | 
|  | return -1; | 
|  |  | 
|  | // If we received more file descriptors than caller expected, then we treat | 
|  | // that as an error. | 
|  | if (recv_fds.size() > (result_fd != nullptr ? 1 : 0)) { | 
|  | NOTREACHED(); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (result_fd) | 
|  | *result_fd = recv_fds.empty() ? -1 : recv_fds[0].release(); | 
|  |  | 
|  | return reply_len; | 
|  | } | 
|  | #endif  // !defined(OS_NACL_NONSFI) | 
|  |  | 
|  | }  // namespace base |