MiniTransceiver.cpp (7778B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 2 /* vim: set ts=8 sts=4 et sw=4 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 #include "mozilla/ipc/MiniTransceiver.h" 7 #include "chrome/common/ipc_message.h" 8 #include "chrome/common/ipc_message_utils.h" 9 #include "base/eintr_wrapper.h" 10 #include "mozilla/UniquePtr.h" 11 #include "mozilla/DebugOnly.h" 12 #include "mozilla/Sprintf.h" 13 #include "mozilla/ScopeExit.h" 14 #include "nsDebug.h" 15 16 #include <sys/types.h> 17 #include <sys/socket.h> 18 #include <string.h> 19 #include <errno.h> 20 21 namespace mozilla::ipc { 22 23 static const size_t kMaxIOVecSize = 64; 24 static const size_t kMaxDataSize = 8 * 1024; 25 static const size_t kMaxNumFds = 16; 26 27 MiniTransceiver::MiniTransceiver(int aFd, DataBufferClear aDataBufClear) 28 : mFd(aFd), 29 #ifdef DEBUG 30 mState(STATE_NONE), 31 #endif 32 mDataBufClear(aDataBufClear) { 33 } 34 35 namespace { 36 37 /** 38 * Initialize the IO vector for sending data and the control buffer for sending 39 * FDs. 40 */ 41 static void InitMsgHdr(msghdr* aHdr, int aIOVSize, size_t aMaxNumFds) { 42 aHdr->msg_name = nullptr; 43 aHdr->msg_namelen = 0; 44 aHdr->msg_flags = 0; 45 46 // Prepare the IO vector to receive the content of message. 47 auto* iov = new iovec[aIOVSize]; 48 aHdr->msg_iov = iov; 49 aHdr->msg_iovlen = aIOVSize; 50 51 // Prepare the control buffer to receive file descriptors. 52 const size_t cbufSize = CMSG_SPACE(sizeof(int) * aMaxNumFds); 53 auto* cbuf = new char[cbufSize]; 54 // Avoid valgrind complaints about uninitialized padding (but also, 55 // fill with a value that isn't a valid fd, just in case). 56 memset(cbuf, 255, cbufSize); 57 aHdr->msg_control = cbuf; 58 aHdr->msg_controllen = cbufSize; 59 } 60 61 /** 62 * Delete resources allocated by InitMsgHdr(). 63 */ 64 static void DeinitMsgHdr(msghdr* aHdr) { 65 delete aHdr->msg_iov; 66 delete static_cast<char*>(aHdr->msg_control); 67 } 68 69 } // namespace 70 71 void MiniTransceiver::PrepareFDs(msghdr* aHdr, IPC::Message& aMsg) { 72 // Set control buffer to send file descriptors of the Message. 73 size_t num_fds = aMsg.attached_handles_.Length(); 74 75 cmsghdr* cmsg = CMSG_FIRSTHDR(aHdr); 76 cmsg->cmsg_level = SOL_SOCKET; 77 cmsg->cmsg_type = SCM_RIGHTS; 78 cmsg->cmsg_len = CMSG_LEN(sizeof(int) * num_fds); 79 for (size_t i = 0; i < num_fds; ++i) { 80 reinterpret_cast<int*>(CMSG_DATA(cmsg))[i] = 81 aMsg.attached_handles_[i].get(); 82 } 83 84 // This number will be sent in the header of the message. So, we 85 // can check it at the other side. 86 aMsg.header()->num_handles = num_fds; 87 } 88 89 size_t MiniTransceiver::PrepareBuffers(msghdr* aHdr, IPC::Message& aMsg) { 90 // Set iovec to send for all buffers of the Message. 91 iovec* iov = aHdr->msg_iov; 92 size_t iovlen = 0; 93 size_t bytes_to_send = 0; 94 for (Pickle::BufferList::IterImpl iter(aMsg.Buffers()); !iter.Done(); 95 iter.Advance(aMsg.Buffers(), iter.RemainingInSegment())) { 96 char* data = iter.Data(); 97 size_t size = iter.RemainingInSegment(); 98 iov[iovlen].iov_base = data; 99 iov[iovlen].iov_len = size; 100 iovlen++; 101 MOZ_ASSERT(iovlen <= kMaxIOVecSize); 102 bytes_to_send += size; 103 } 104 MOZ_ASSERT(bytes_to_send <= kMaxDataSize); 105 aHdr->msg_iovlen = iovlen; 106 107 return bytes_to_send; 108 } 109 110 bool MiniTransceiver::Send(IPC::Message& aMsg) { 111 #ifdef DEBUG 112 if (mState == STATE_SENDING) { 113 MOZ_CRASH( 114 "STATE_SENDING: It violates of request-response and no concurrent " 115 "rules"); 116 } 117 mState = STATE_SENDING; 118 #endif 119 120 auto clean_fdset = MakeScopeExit([&] { aMsg.attached_handles_.Clear(); }); 121 122 size_t num_fds = aMsg.attached_handles_.Length(); 123 msghdr hdr{}; 124 InitMsgHdr(&hdr, kMaxIOVecSize, num_fds); 125 126 UniquePtr<msghdr, decltype(&DeinitMsgHdr)> uniq(&hdr, &DeinitMsgHdr); 127 128 PrepareFDs(&hdr, aMsg); 129 DebugOnly<size_t> bytes_to_send = PrepareBuffers(&hdr, aMsg); 130 131 ssize_t bytes_written = HANDLE_EINTR(sendmsg(mFd, &hdr, 0)); 132 133 if (bytes_written < 0) { 134 char error[128]; 135 SprintfLiteral(error, "sendmsg: %s", strerror(errno)); 136 NS_WARNING(error); 137 return false; 138 } 139 MOZ_ASSERT(bytes_written == (ssize_t)bytes_to_send, 140 "The message is too big?!"); 141 142 return true; 143 } 144 145 unsigned MiniTransceiver::RecvFDs(msghdr* aHdr, int* aAllFds, 146 unsigned aMaxFds) { 147 if (aHdr->msg_controllen == 0) { 148 return 0; 149 } 150 151 unsigned num_all_fds = 0; 152 for (cmsghdr* cmsg = CMSG_FIRSTHDR(aHdr); cmsg; 153 cmsg = CMSG_NXTHDR(aHdr, cmsg)) { 154 MOZ_ASSERT(cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS, 155 "Accept only SCM_RIGHTS to receive file descriptors"); 156 157 unsigned payload_sz = cmsg->cmsg_len - CMSG_LEN(0); 158 MOZ_ASSERT(payload_sz % sizeof(int) == 0); 159 160 // Add fds to |aAllFds| 161 unsigned num_part_fds = payload_sz / sizeof(int); 162 int* part_fds = reinterpret_cast<int*>(CMSG_DATA(cmsg)); 163 MOZ_ASSERT(num_all_fds + num_part_fds <= aMaxFds); 164 165 memcpy(aAllFds + num_all_fds, part_fds, num_part_fds * sizeof(int)); 166 num_all_fds += num_part_fds; 167 } 168 return num_all_fds; 169 } 170 171 bool MiniTransceiver::RecvData(char* aDataBuf, size_t aBufSize, 172 uint32_t* aMsgSize, int* aFdsBuf, 173 unsigned aMaxFds, unsigned* aNumFds) { 174 msghdr hdr; 175 InitMsgHdr(&hdr, 1, aMaxFds); 176 177 UniquePtr<msghdr, decltype(&DeinitMsgHdr)> uniq(&hdr, &DeinitMsgHdr); 178 179 // The buffer to collect all fds received from the socket. 180 int* all_fds = aFdsBuf; 181 unsigned num_all_fds = 0; 182 183 size_t total_readed = 0; 184 uint32_t msgsz = 0; 185 while (msgsz == 0 || total_readed < msgsz) { 186 // Set IO vector with the begin of the unused buffer. 187 hdr.msg_iov->iov_base = aDataBuf + total_readed; 188 hdr.msg_iov->iov_len = (msgsz == 0 ? aBufSize : msgsz) - total_readed; 189 190 // Read the socket 191 ssize_t bytes_readed = HANDLE_EINTR(recvmsg(mFd, &hdr, 0)); 192 if (bytes_readed <= 0) { 193 // Closed or error! 194 return false; 195 } 196 total_readed += bytes_readed; 197 MOZ_ASSERT(total_readed <= aBufSize); 198 199 if (msgsz == 0) { 200 // Parse the size of the message. 201 // Get 0 if data in the buffer is no enough to get message size. 202 msgsz = IPC::Message::MessageSize(aDataBuf, aDataBuf + total_readed); 203 } 204 205 num_all_fds += RecvFDs(&hdr, all_fds + num_all_fds, aMaxFds - num_all_fds); 206 } 207 208 *aMsgSize = msgsz; 209 *aNumFds = num_all_fds; 210 return true; 211 } 212 213 bool MiniTransceiver::Recv(UniquePtr<IPC::Message>& aMsg) { 214 #ifdef DEBUG 215 if (mState == STATE_RECEIVING) { 216 MOZ_CRASH( 217 "STATE_RECEIVING: It violates of request-response and no concurrent " 218 "rules"); 219 } 220 mState = STATE_RECEIVING; 221 #endif 222 223 UniquePtr<char[]> databuf = MakeUnique<char[]>(kMaxDataSize); 224 uint32_t msgsz = 0; 225 int all_fds[kMaxNumFds]; 226 unsigned num_all_fds = 0; 227 228 if (!RecvData(databuf.get(), kMaxDataSize, &msgsz, all_fds, kMaxDataSize, 229 &num_all_fds)) { 230 return false; 231 } 232 233 // Create Message from databuf & all_fds. 234 UniquePtr<IPC::Message> msg = MakeUnique<IPC::Message>(databuf.get(), msgsz); 235 nsTArray<UniqueFileHandle> handles(num_all_fds); 236 for (unsigned i = 0; i < num_all_fds; ++i) { 237 handles.AppendElement(UniqueFileHandle(all_fds[i])); 238 } 239 msg->SetAttachedFileHandles(std::move(handles)); 240 241 if (mDataBufClear == DataBufferClear::AfterReceiving) { 242 // Avoid content processes from reading the content of 243 // messages. 244 memset(databuf.get(), 0, msgsz); 245 } 246 247 MOZ_ASSERT(msg->header()->num_handles == msg->attached_handles_.Length(), 248 "The number of file descriptors in the header is different from" 249 " the number actually received"); 250 251 aMsg = std::move(msg); 252 return true; 253 } 254 255 } // namespace mozilla::ipc