SandboxBrokerClient.cpp (10056B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 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 file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "SandboxBrokerClient.h" 8 #include "SandboxInfo.h" 9 #include "SandboxLogging.h" 10 11 #include <errno.h> 12 #include <fcntl.h> 13 #include <stdio.h> 14 #include <string.h> 15 #include <sys/socket.h> 16 #include <sys/stat.h> 17 #include <sys/types.h> 18 #include <sys/un.h> 19 #include <unistd.h> 20 21 #include "mozilla/Assertions.h" 22 #include "mozilla/Atomics.h" 23 #include "base/strings/safe_sprintf.h" 24 25 namespace mozilla { 26 27 SandboxBrokerClient::SandboxBrokerClient(int aFd) : mFileDesc(aFd) {} 28 29 SandboxBrokerClient::~SandboxBrokerClient() { close(mFileDesc); } 30 31 int SandboxBrokerClient::DoCall(const Request* aReq, const char* aPath, 32 const char* aPath2, void* aResponseBuff, 33 bool expectFd) { 34 // Remap /proc/self to the actual pid, so that the broker can open 35 // it. This happens here instead of in the broker to follow the 36 // principle of least privilege and keep the broker as simple as 37 // possible. (Note: when pid namespaces happen, this will also need 38 // to remap the inner pid to the outer pid.) 39 // We only remap the first path. 40 static const char kProcSelf[] = "/proc/self/"; 41 static const size_t kProcSelfLen = sizeof(kProcSelf) - 1; 42 const char* path = aPath; 43 // This buffer just needs to be large enough for any such path that 44 // the policy would actually allow. sizeof("/proc/2147483647/") == 18. 45 char rewrittenPath[64]; 46 if (strncmp(aPath, kProcSelf, kProcSelfLen) == 0) { 47 ssize_t len = base::strings::SafeSPrintf(rewrittenPath, "/proc/%d/%s", 48 getpid(), aPath + kProcSelfLen); 49 if (static_cast<size_t>(len) < sizeof(rewrittenPath)) { 50 if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) { 51 SANDBOX_LOG("rewriting %s -> %s", aPath, rewrittenPath); 52 } 53 path = rewrittenPath; 54 } else { 55 SANDBOX_LOG("not rewriting unexpectedly long path %s", aPath); 56 } 57 } 58 59 if (SandboxInfo::Get().Test(SandboxInfo::kVerboseTests)) { 60 // Dont use SANDBOX_LOG directly to not be too spammy, just make sure the 61 // ReportLog() works as expected 62 SandboxProfiler::ReportLog(OperationDescription[aReq->mOp]); 63 } 64 65 const void* top = CallerPC(); 66 SandboxProfiler::ReportRequest(top, aReq->mId, 67 OperationDescription[aReq->mOp], aReq->mFlags, 68 aPath, aPath2, getpid()); 69 70 struct iovec ios[3]; 71 int respFds[2]; 72 73 // Set up iovecs for request + path. 74 ios[0].iov_base = const_cast<Request*>(aReq); 75 ios[0].iov_len = sizeof(*aReq); 76 ios[1].iov_base = const_cast<char*>(path); 77 ios[1].iov_len = strlen(path) + 1; 78 if (aPath2 != nullptr) { 79 ios[2].iov_base = const_cast<char*>(aPath2); 80 ios[2].iov_len = strlen(aPath2) + 1; 81 } else { 82 ios[2].iov_base = nullptr; 83 ios[2].iov_len = 0; 84 } 85 if (ios[1].iov_len > kMaxPathLen) { 86 return -ENAMETOOLONG; 87 } 88 if (ios[2].iov_len > kMaxPathLen) { 89 return -ENAMETOOLONG; 90 } 91 92 // Create response socket and send request. 93 if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, respFds) < 0) { 94 return -errno; 95 } 96 const ssize_t sent = SendWithFd(mFileDesc, ios, 3, respFds[1]); 97 const int sendErrno = errno; 98 MOZ_ASSERT(sent < 0 || static_cast<size_t>(sent) == 99 ios[0].iov_len + ios[1].iov_len + ios[2].iov_len); 100 close(respFds[1]); 101 if (sent < 0) { 102 close(respFds[0]); 103 return -sendErrno; 104 } 105 106 // Set up iovecs for response. 107 Response resp; 108 ios[0].iov_base = &resp; 109 ios[0].iov_len = sizeof(resp); 110 if (aResponseBuff) { 111 ios[1].iov_base = aResponseBuff; 112 ios[1].iov_len = aReq->mBufSize; 113 } else { 114 ios[1].iov_base = nullptr; 115 ios[1].iov_len = 0; 116 } 117 118 // Wait for response and return appropriately. 119 int openedFd = -1; 120 const ssize_t recvd = RecvWithFd(respFds[0], ios, aResponseBuff ? 2 : 1, 121 expectFd ? &openedFd : nullptr); 122 const int recvErrno = errno; 123 close(respFds[0]); 124 if (recvd < 0) { 125 return -recvErrno; 126 } 127 if (recvd == 0) { 128 SANDBOX_LOG("Unexpected EOF, op %d flags 0%o path %s", aReq->mOp, 129 aReq->mFlags, path); 130 return -EIO; 131 } 132 MOZ_ASSERT(static_cast<size_t>(recvd) <= ios[0].iov_len + ios[1].iov_len); 133 // Some calls such as readlink return a size if successful 134 if (resp.mError >= 0) { 135 // Success! 136 if (expectFd) { 137 MOZ_ASSERT(openedFd >= 0); 138 return openedFd; 139 } 140 return resp.mError; 141 } 142 if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) { 143 // Keep in mind that "rejected" files can include ones that don't 144 // actually exist, if it's something that's optional or part of a 145 // search path (e.g., shared libraries). In those cases, this 146 // error message is expected. 147 SANDBOX_LOG("Failed errno %d op %s flags 0%o path %s", resp.mError, 148 OperationDescription[aReq->mOp], aReq->mFlags, path); 149 } 150 if (openedFd >= 0) { 151 close(openedFd); 152 } 153 return resp.mError; 154 } 155 156 SandboxBrokerCommon::Request MakeRequest( 157 const SandboxBrokerCommon::Operation aOp, const int aFlags, 158 const size_t aBufSize) { 159 static Atomic<uint64_t> reqId{0}; 160 SandboxBrokerCommon::Request req = {aOp, aFlags, reqId, aBufSize}; 161 reqId++; 162 return req; 163 } 164 165 int SandboxBrokerClient::Open(const char* aPath, int aFlags) { 166 Request req = MakeRequest(SANDBOX_FILE_OPEN, aFlags, 0); 167 int maybeFd = DoCall(&req, aPath, nullptr, nullptr, true); 168 if (maybeFd >= 0) { 169 // NSPR has opinions about file flags. Fix O_CLOEXEC. 170 if ((aFlags & O_CLOEXEC) == 0) { 171 fcntl(maybeFd, F_SETFD, 0); 172 } 173 } 174 return maybeFd; 175 } 176 177 int SandboxBrokerClient::Access(const char* aPath, int aMode) { 178 Request req = MakeRequest(SANDBOX_FILE_ACCESS, aMode, 0); 179 return DoCall(&req, aPath, nullptr, nullptr, false); 180 } 181 182 int SandboxBrokerClient::Stat(const char* aPath, statstruct* aStat) { 183 if (!aPath || !aStat) { 184 return -EFAULT; 185 } 186 187 Request req = MakeRequest(SANDBOX_FILE_STAT, 0, sizeof(statstruct)); 188 return DoCall(&req, aPath, nullptr, (void*)aStat, false); 189 } 190 191 int SandboxBrokerClient::LStat(const char* aPath, statstruct* aStat) { 192 if (!aPath || !aStat) { 193 return -EFAULT; 194 } 195 196 Request req = MakeRequest(SANDBOX_FILE_STAT, O_NOFOLLOW, sizeof(statstruct)); 197 return DoCall(&req, aPath, nullptr, (void*)aStat, false); 198 } 199 200 int SandboxBrokerClient::Chmod(const char* aPath, int aMode) { 201 Request req = MakeRequest(SANDBOX_FILE_CHMOD, aMode, 0); 202 return DoCall(&req, aPath, nullptr, nullptr, false); 203 } 204 205 int SandboxBrokerClient::Link(const char* aOldPath, const char* aNewPath) { 206 Request req = MakeRequest(SANDBOX_FILE_LINK, 0, 0); 207 return DoCall(&req, aOldPath, aNewPath, nullptr, false); 208 } 209 210 int SandboxBrokerClient::Symlink(const char* aOldPath, const char* aNewPath) { 211 Request req = MakeRequest(SANDBOX_FILE_SYMLINK, 0, 0); 212 return DoCall(&req, aOldPath, aNewPath, nullptr, false); 213 } 214 215 int SandboxBrokerClient::Rename(const char* aOldPath, const char* aNewPath) { 216 Request req = MakeRequest(SANDBOX_FILE_RENAME, 0, 0); 217 return DoCall(&req, aOldPath, aNewPath, nullptr, false); 218 } 219 220 int SandboxBrokerClient::Mkdir(const char* aPath, int aMode) { 221 Request req = MakeRequest(SANDBOX_FILE_MKDIR, aMode, 0); 222 return DoCall(&req, aPath, nullptr, nullptr, false); 223 } 224 225 int SandboxBrokerClient::Unlink(const char* aPath) { 226 Request req = MakeRequest(SANDBOX_FILE_UNLINK, 0, 0); 227 return DoCall(&req, aPath, nullptr, nullptr, false); 228 } 229 230 int SandboxBrokerClient::Rmdir(const char* aPath) { 231 Request req = MakeRequest(SANDBOX_FILE_RMDIR, 0, 0); 232 return DoCall(&req, aPath, nullptr, nullptr, false); 233 } 234 235 int SandboxBrokerClient::Readlink(const char* aPath, void* aBuff, 236 size_t aSize) { 237 Request req = MakeRequest(SANDBOX_FILE_READLINK, 0, aSize); 238 return DoCall(&req, aPath, nullptr, aBuff, false); 239 } 240 241 int SandboxBrokerClient::Connect(const sockaddr_un* aAddr, size_t aLen, 242 int aType) { 243 static constexpr size_t maxLen = sizeof(aAddr->sun_path); 244 const char* path = aAddr->sun_path; 245 const auto addrEnd = reinterpret_cast<const char*>(aAddr) + aLen; 246 // Ensure that the length isn't impossibly small. 247 if (addrEnd <= path) { 248 return -EINVAL; 249 } 250 // Unix domain only 251 if (aAddr->sun_family != AF_UNIX) { 252 return -EAFNOSUPPORT; 253 } 254 // How much of sun_path may be accessed? 255 auto bufLen = static_cast<size_t>(addrEnd - path); 256 if (bufLen > maxLen) { 257 bufLen = maxLen; 258 } 259 260 // Try to handle abstract addresses where the address (the part 261 // after the leading null byte) resembles a pathname: a leading 262 // slash and no embedded nulls. 263 // 264 // `DoCall` expects null-terminated strings, but in this case the 265 // "path" is terminated by the sockaddr length (without a null), so 266 // we need to make a copy. 267 if (bufLen >= 2 && path[0] == '\0' && path[1] == '/' && 268 !memchr(path + 1, '\0', bufLen - 1)) { 269 char tmpBuf[maxLen]; 270 MOZ_RELEASE_ASSERT(bufLen - 1 < maxLen); 271 memcpy(tmpBuf, path + 1, bufLen - 1); 272 tmpBuf[bufLen - 1] = '\0'; 273 274 const Request req = MakeRequest(SANDBOX_SOCKET_CONNECT_ABSTRACT, aType, 0); 275 return DoCall(&req, tmpBuf, nullptr, nullptr, true); 276 } 277 278 // Require null-termination. (Linux doesn't require it, but 279 // applications usually null-terminate for portability, and not 280 // handling unterminated strings means we don't have to copy the path.) 281 const size_t pathLen = strnlen(path, bufLen); 282 if (pathLen == bufLen) { 283 return -ENAMETOOLONG; 284 } 285 286 // Abstract addresses are handled only in some specific case, error in others 287 if (pathLen == 0) { 288 return -ENETUNREACH; 289 } 290 291 const Request req = MakeRequest(SANDBOX_SOCKET_CONNECT, aType, 0); 292 return DoCall(&req, path, nullptr, nullptr, true); 293 } 294 295 } // namespace mozilla