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

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

Powered by Google App Engine
This is Rietveld 408576698