1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
|
// Copyright (c) 2012 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 "sandbox/linux/services/broker_process.h"
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <string>
#include <vector>
#include "base/basictypes.h"
#include "base/logging.h"
#include "base/pickle.h"
#include "base/posix/eintr_wrapper.h"
#include "base/posix/unix_domain_socket.h"
namespace {
static const int kCommandOpen = 'O';
static const size_t kMaxMessageLength = 4096;
// Some flags will need special treatment on the client side and are not
// supported for now.
int ForCurrentProcessFlagsMask() {
return O_CLOEXEC | O_NONBLOCK;
}
// Check whether |requested_filename| is in |allowed_file_names|.
// See GetFileNameIfAllowedAccess() for an explaination of |file_to_open|.
// async signal safe if |file_to_open| is NULL.
// TODO(jln): assert signal safety.
bool GetFileNameInWhitelist(const std::vector<std::string>& allowed_file_names,
const std::string& requested_filename,
const char** file_to_open) {
if (file_to_open && *file_to_open) {
// Make sure that callers never pass a non-empty string. In case callers
// wrongly forget to check the return value and look at the string
// instead, this could catch bugs.
RAW_LOG(FATAL, "*file_to_open should be NULL");
return false;
}
std::vector<std::string>::const_iterator it;
it = std::find(allowed_file_names.begin(), allowed_file_names.end(),
requested_filename);
if (it < allowed_file_names.end()) { // requested_filename was found?
if (file_to_open)
*file_to_open = it->c_str();
return true;
}
return false;
}
// We maintain a list of flags that have been reviewed for "sanity" and that
// we're ok to allow in the broker.
// I.e. here is where we wouldn't add O_RESET_FILE_SYSTEM.
bool IsAllowedOpenFlags(int flags) {
// First, check the access mode
const int access_mode = flags & O_ACCMODE;
if (access_mode != O_RDONLY && access_mode != O_WRONLY &&
access_mode != O_RDWR) {
return false;
}
// Some flags affect the behavior of the current process. We don't support
// them and don't allow them for now.
if (flags & ForCurrentProcessFlagsMask()) {
return false;
}
// Now check that all the flags are known to us.
const int creation_and_status_flags = flags & ~O_ACCMODE;
const int known_flags =
O_APPEND | O_ASYNC | O_CLOEXEC | O_CREAT | O_DIRECT |
O_DIRECTORY | O_EXCL | O_LARGEFILE | O_NOATIME | O_NOCTTY |
O_NOFOLLOW | O_NONBLOCK | O_NDELAY | O_SYNC | O_TRUNC;
const int unknown_flags = ~known_flags;
const bool has_unknown_flags = creation_and_status_flags & unknown_flags;
return !has_unknown_flags;
}
} // namespace
namespace sandbox {
BrokerProcess::BrokerProcess(const std::vector<std::string>& allowed_r_files,
const std::vector<std::string>& allowed_w_files,
bool fast_check_in_client,
bool quiet_failures_for_tests)
: initialized_(false),
is_child_(false),
fast_check_in_client_(fast_check_in_client),
quiet_failures_for_tests_(quiet_failures_for_tests),
broker_pid_(-1),
allowed_r_files_(allowed_r_files),
allowed_w_files_(allowed_w_files),
ipc_socketpair_(-1) {
}
BrokerProcess::~BrokerProcess() {
if (initialized_ && ipc_socketpair_ != -1) {
void (HANDLE_EINTR(close(ipc_socketpair_)));
}
}
bool BrokerProcess::Init(bool (*sandbox_callback)(void)) {
CHECK(!initialized_);
int socket_pair[2];
// Use SOCK_SEQPACKET, because we need to preserve message boundaries
// but we also want to be notified (recvmsg should return and not block)
// when the connection has been broken (one of the processes died).
if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, socket_pair)) {
LOG(ERROR) << "Failed to create socketpair";
return false;
}
int child_pid = fork();
if (child_pid == -1) {
(void) HANDLE_EINTR(close(socket_pair[0]));
(void) HANDLE_EINTR(close(socket_pair[1]));
return false;
}
if (child_pid) {
// We are the parent and we have just forked our broker process.
(void) HANDLE_EINTR(close(socket_pair[0]));
// We should only be able to write to the IPC channel. We'll always send
// a new file descriptor to receive the reply on.
shutdown(socket_pair[1], SHUT_RD);
ipc_socketpair_ = socket_pair[1];
is_child_ = false;
broker_pid_ = child_pid;
initialized_ = true;
return true;
} else {
// We are the broker.
(void) HANDLE_EINTR(close(socket_pair[1]));
// We should only be able to read from this IPC channel. We will send our
// replies on a new file descriptor attached to the requests.
shutdown(socket_pair[0], SHUT_WR);
ipc_socketpair_ = socket_pair[0];
is_child_ = true;
// Enable the sandbox if provided.
if (sandbox_callback) {
CHECK(sandbox_callback());
}
initialized_ = true;
for (;;) {
HandleRequest();
}
_exit(1);
}
NOTREACHED();
}
// This function needs to be async signal safe.
int BrokerProcess::Open(const char* pathname, int flags) const {
RAW_CHECK(initialized_); // async signal safe CHECK().
if (!pathname)
return -EFAULT;
// There is no point in forwarding a request that we know will be denied.
// Of course, the real security check needs to be on the other side of the
// IPC.
if (fast_check_in_client_) {
if (!GetFileNameIfAllowedAccess(pathname, flags, NULL))
return -EPERM;
}
Pickle write_pickle;
write_pickle.WriteInt(kCommandOpen);
write_pickle.WriteString(pathname);
write_pickle.WriteInt(flags);
RAW_CHECK(write_pickle.size() <= kMaxMessageLength);
int returned_fd = -1;
uint8_t reply_buf[kMaxMessageLength];
// Send a request (in write_pickle) as well that will include a new
// temporary socketpair (created internally by SendRecvMsg()).
// Then read the reply on this new socketpair in reply_buf and put an
// eventual attached file descriptor in |returned_fd|.
// TODO(jln): this API needs some rewriting and documentation.
ssize_t msg_len = UnixDomainSocket::SendRecvMsg(ipc_socketpair_,
reply_buf,
sizeof(reply_buf),
&returned_fd,
write_pickle);
if (msg_len <= 0) {
if (!quiet_failures_for_tests_)
RAW_LOG(ERROR, "Could not make request to broker process");
return -ENOMEM;
}
Pickle read_pickle(reinterpret_cast<char*>(reply_buf), msg_len);
PickleIterator iter(read_pickle);
int return_value = -1;
// Now deserialize the return value and eventually return the file
// descriptor.
if (read_pickle.ReadInt(&iter, &return_value)) {
if (return_value < 0) {
RAW_CHECK(returned_fd == -1);
return return_value;
} else {
// We have a real file descriptor to return.
RAW_CHECK(returned_fd >= 0);
return returned_fd;
}
} else {
RAW_LOG(ERROR, "Could not read pickle");
return -1;
}
}
// Handle a request on the IPC channel ipc_socketpair_.
// A request should have a file descriptor attached on which we will reply and
// that we will then close.
// A request should start with an int that will be used as the command type.
bool BrokerProcess::HandleRequest() const {
std::vector<int> fds;
char buf[kMaxMessageLength];
errno = 0;
const ssize_t msg_len = UnixDomainSocket::RecvMsg(ipc_socketpair_, buf,
sizeof(buf), &fds);
if (msg_len == 0 || (msg_len == -1 && errno == ECONNRESET)) {
// EOF from our parent, or our parent died, we should die.
_exit(0);
}
// The parent should send exactly one file descriptor, on which we
// will write the reply.
if (msg_len < 0 || fds.size() != 1 || fds.at(0) < 0) {
PLOG(ERROR) << "Error reading message from the client";
return false;
}
const int temporary_ipc = fds.at(0);
Pickle pickle(buf, msg_len);
PickleIterator iter(pickle);
int command_type;
if (pickle.ReadInt(&iter, &command_type)) {
bool r = false;
// Go through all the possible IPC messages.
switch (command_type) {
case kCommandOpen:
// We reply on the file descriptor sent to us via the IPC channel.
r = HandleOpenRequest(temporary_ipc, pickle, iter);
(void) HANDLE_EINTR(close(temporary_ipc));
return r;
default:
NOTREACHED();
return false;
}
}
LOG(ERROR) << "Error parsing IPC request";
return false;
}
// Handle an open request contained in |read_pickle| and send the reply
// on |reply_ipc|.
bool BrokerProcess::HandleOpenRequest(int reply_ipc,
const Pickle& read_pickle,
PickleIterator iter) const {
std::string requested_filename;
int flags = 0;
if (!read_pickle.ReadString(&iter, &requested_filename) ||
!read_pickle.ReadInt(&iter, &flags)) {
return -1;
}
Pickle write_pickle;
std::vector<int> opened_files;
const char* file_to_open = NULL;
const bool safe_to_open_file = GetFileNameIfAllowedAccess(
requested_filename.c_str(), flags, &file_to_open);
if (safe_to_open_file) {
CHECK(file_to_open);
// O_CLOEXEC doesn't hurt (even though we won't execve()), and this
// property won't be passed to the client.
// We may want to think about O_NONBLOCK as well.
int opened_fd = open(file_to_open, flags | O_CLOEXEC);
if (opened_fd < 0) {
write_pickle.WriteInt(-errno);
} else {
// Success.
opened_files.push_back(opened_fd);
write_pickle.WriteInt(0);
}
} else {
write_pickle.WriteInt(-EPERM);
}
CHECK_LE(write_pickle.size(), kMaxMessageLength);
ssize_t sent = UnixDomainSocket::SendMsg(reply_ipc, write_pickle.data(),
write_pickle.size(), opened_files);
// Close anything we have opened in this process.
for (std::vector<int>::iterator it = opened_files.begin();
it < opened_files.end(); ++it) {
(void) HANDLE_EINTR(close(*it));
}
if (sent <= 0) {
LOG(ERROR) << "Could not send IPC reply";
return false;
}
return true;
}
// For paranoia, if |file_to_open| is not NULL, we will return the matching
// string from the white list.
// Async signal safe only if |file_to_open| is NULL.
// Even if an attacker managed to fool the string comparison mechanism, we
// would not open an attacker-controlled file name.
// Return true if access should be allowed, false otherwise.
bool BrokerProcess::GetFileNameIfAllowedAccess(const char* requested_filename,
int requested_flags, const char** file_to_open) const {
if (!IsAllowedOpenFlags(requested_flags)) {
return false;
}
switch (requested_flags & O_ACCMODE) {
case O_RDONLY:
return GetFileNameInWhitelist(allowed_r_files_, requested_filename,
file_to_open);
case O_WRONLY:
return GetFileNameInWhitelist(allowed_w_files_, requested_filename,
file_to_open);
case O_RDWR:
{
bool allowed_for_read_and_write =
GetFileNameInWhitelist(allowed_r_files_, requested_filename, NULL) &&
GetFileNameInWhitelist(allowed_w_files_, requested_filename,
file_to_open);
return allowed_for_read_and_write;
}
default:
return false;
}
}
} // namespace sandbox.
|