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