Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(362)

Side by Side Diff: sandbox/linux/services/broker_process.cc

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

Powered by Google App Engine
This is Rietveld 408576698