OLD | NEW |
---|---|
(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. | |
OLD | NEW |