1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "sandbox/linux/syscall_broker/broker_host.h"
6 
7 #include <errno.h>
8 #include <fcntl.h>
9 #include <limits.h>
10 #include <stddef.h>
11 #include <sys/socket.h>
12 #include <sys/stat.h>
13 #include <sys/syscall.h>
14 #include <sys/types.h>
15 #include <unistd.h>
16 
17 #include <string>
18 #include <utility>
19 
20 #include "base/files/scoped_file.h"
21 #include "base/logging.h"
22 #include "base/posix/eintr_wrapper.h"
23 #include "sandbox/linux/syscall_broker/broker_command.h"
24 #include "sandbox/linux/syscall_broker/broker_permission_list.h"
25 #include "sandbox/linux/syscall_broker/broker_simple_message.h"
26 #include "sandbox/linux/system_headers/linux_syscalls.h"
27 
28 namespace sandbox {
29 namespace syscall_broker {
30 
31 namespace {
32 
33 // A little open(2) wrapper to handle some oddities for us. In the general case
34 // make a direct system call since we want to keep in control of the broker
35 // process' system calls profile to be able to loosely sandbox it.
sys_open(const char * pathname,int flags)36 int sys_open(const char* pathname, int flags) {
37   // Hardcode mode to rw------- when creating files.
38   int mode = (flags & O_CREAT) ? 0600 : 0;
39   return syscall(__NR_openat, AT_FDCWD, pathname, flags, mode);
40 }
41 
GetPathAndFlags(BrokerSimpleMessage * message,const char ** pathname,int * flags)42 bool GetPathAndFlags(BrokerSimpleMessage* message,
43                      const char** pathname,
44                      int* flags) {
45   return message->ReadString(pathname) && message->ReadInt(flags);
46 }
47 
48 // Perform access(2) on |requested_filename| with mode |mode| if allowed by our
49 // permission_list. Write the syscall return value (-errno) to |reply|.
AccessFileForIPC(const BrokerCommandSet & allowed_command_set,const BrokerPermissionList & permission_list,const std::string & requested_filename,int mode,BrokerSimpleMessage * reply)50 void AccessFileForIPC(const BrokerCommandSet& allowed_command_set,
51                       const BrokerPermissionList& permission_list,
52                       const std::string& requested_filename,
53                       int mode,
54                       BrokerSimpleMessage* reply) {
55   const char* file_to_access = NULL;
56   if (!CommandAccessIsSafe(allowed_command_set, permission_list,
57                            requested_filename.c_str(), mode, &file_to_access)) {
58     RAW_CHECK(reply->AddIntToMessage(-permission_list.denied_errno()));
59     return;
60   }
61 
62   RAW_CHECK(file_to_access);
63   if (access(file_to_access, mode) < 0) {
64     RAW_CHECK(reply->AddIntToMessage(-errno));
65     return;
66   }
67 
68   RAW_CHECK(reply->AddIntToMessage(0));
69 }
70 
71 // Performs mkdir(2) on |filename| with mode |mode| if allowed by our
72 // permission_list. Write the syscall return value (-errno) to |reply|.
MkdirFileForIPC(const BrokerCommandSet & allowed_command_set,const BrokerPermissionList & permission_list,const std::string & filename,int mode,BrokerSimpleMessage * reply)73 void MkdirFileForIPC(const BrokerCommandSet& allowed_command_set,
74                      const BrokerPermissionList& permission_list,
75                      const std::string& filename,
76                      int mode,
77                      BrokerSimpleMessage* reply) {
78   const char* file_to_access = nullptr;
79   if (!CommandMkdirIsSafe(allowed_command_set, permission_list,
80                           filename.c_str(), &file_to_access)) {
81     RAW_CHECK(reply->AddIntToMessage(-permission_list.denied_errno()));
82     return;
83   }
84   if (mkdir(file_to_access, mode) < 0) {
85     RAW_CHECK(reply->AddIntToMessage(-errno));
86     return;
87   }
88   RAW_CHECK(reply->AddIntToMessage(0));
89 }
90 
91 // Open |requested_filename| with |flags| if allowed by our permission list.
92 // Write the syscall return value (-errno) to |reply| and return the
93 // file descriptor in the |opened_file| if relevant.
OpenFileForIPC(const BrokerCommandSet & allowed_command_set,const BrokerPermissionList & permission_list,const std::string & requested_filename,int flags,BrokerSimpleMessage * reply,int * opened_file)94 void OpenFileForIPC(const BrokerCommandSet& allowed_command_set,
95                     const BrokerPermissionList& permission_list,
96                     const std::string& requested_filename,
97                     int flags,
98                     BrokerSimpleMessage* reply,
99                     int* opened_file) {
100   const char* file_to_open = NULL;
101   bool unlink_after_open = false;
102   if (!CommandOpenIsSafe(allowed_command_set, permission_list,
103                          requested_filename.c_str(), flags, &file_to_open,
104                          &unlink_after_open)) {
105     RAW_CHECK(reply->AddIntToMessage(-permission_list.denied_errno()));
106     return;
107   }
108 
109   CHECK(file_to_open);
110   int opened_fd = sys_open(file_to_open, flags);
111   if (opened_fd < 0) {
112     RAW_CHECK(reply->AddIntToMessage(-errno));
113     return;
114   }
115 
116   if (unlink_after_open)
117     unlink(file_to_open);
118 
119   *opened_file = opened_fd;
120   RAW_CHECK(reply->AddIntToMessage(0));
121 }
122 
123 // Perform rename(2) on |old_filename| to |new_filename| and write the
124 // result to |return_val|.
RenameFileForIPC(const BrokerCommandSet & allowed_command_set,const BrokerPermissionList & permission_list,const std::string & old_filename,const std::string & new_filename,BrokerSimpleMessage * reply)125 void RenameFileForIPC(const BrokerCommandSet& allowed_command_set,
126                       const BrokerPermissionList& permission_list,
127                       const std::string& old_filename,
128                       const std::string& new_filename,
129                       BrokerSimpleMessage* reply) {
130   const char* old_file_to_access = nullptr;
131   const char* new_file_to_access = nullptr;
132   if (!CommandRenameIsSafe(allowed_command_set, permission_list,
133                            old_filename.c_str(), new_filename.c_str(),
134                            &old_file_to_access, &new_file_to_access)) {
135     RAW_CHECK(reply->AddIntToMessage(-permission_list.denied_errno()));
136     return;
137   }
138   if (rename(old_file_to_access, new_file_to_access) < 0) {
139     RAW_CHECK(reply->AddIntToMessage(-errno));
140     return;
141   }
142   RAW_CHECK(reply->AddIntToMessage(0));
143 }
144 
145 // Perform readlink(2) on |filename| using a buffer of MAX_PATH bytes.
ReadlinkFileForIPC(const BrokerCommandSet & allowed_command_set,const BrokerPermissionList & permission_list,const std::string & filename,BrokerSimpleMessage * reply)146 void ReadlinkFileForIPC(const BrokerCommandSet& allowed_command_set,
147                         const BrokerPermissionList& permission_list,
148                         const std::string& filename,
149                         BrokerSimpleMessage* reply) {
150   const char* file_to_access = nullptr;
151   if (!CommandReadlinkIsSafe(allowed_command_set, permission_list,
152                              filename.c_str(), &file_to_access)) {
153     RAW_CHECK(reply->AddIntToMessage(-permission_list.denied_errno()));
154     return;
155   }
156   char buf[PATH_MAX];
157   ssize_t result = readlink(file_to_access, buf, sizeof(buf));
158   if (result < 0) {
159     RAW_CHECK(reply->AddIntToMessage(-errno));
160     return;
161   }
162   RAW_CHECK(reply->AddIntToMessage(result));
163   RAW_CHECK(reply->AddDataToMessage(buf, result));
164 }
165 
RmdirFileForIPC(const BrokerCommandSet & allowed_command_set,const BrokerPermissionList & permission_list,const std::string & requested_filename,BrokerSimpleMessage * reply)166 void RmdirFileForIPC(const BrokerCommandSet& allowed_command_set,
167                      const BrokerPermissionList& permission_list,
168                      const std::string& requested_filename,
169                      BrokerSimpleMessage* reply) {
170   const char* file_to_access = nullptr;
171   if (!CommandRmdirIsSafe(allowed_command_set, permission_list,
172                           requested_filename.c_str(), &file_to_access)) {
173     RAW_CHECK(reply->AddIntToMessage(-permission_list.denied_errno()));
174     return;
175   }
176   if (rmdir(file_to_access) < 0) {
177     RAW_CHECK(reply->AddIntToMessage(-errno));
178     return;
179   }
180   RAW_CHECK(reply->AddIntToMessage(0));
181 }
182 
183 // Perform stat(2) on |requested_filename| and write the result to
184 // |return_val|.
StatFileForIPC(const BrokerCommandSet & allowed_command_set,const BrokerPermissionList & permission_list,BrokerCommand command_type,const std::string & requested_filename,bool follow_links,BrokerSimpleMessage * reply)185 void StatFileForIPC(const BrokerCommandSet& allowed_command_set,
186                     const BrokerPermissionList& permission_list,
187                     BrokerCommand command_type,
188                     const std::string& requested_filename,
189                     bool follow_links,
190                     BrokerSimpleMessage* reply) {
191   const char* file_to_access = nullptr;
192   if (!CommandStatIsSafe(allowed_command_set, permission_list,
193                          requested_filename.c_str(), &file_to_access)) {
194     RAW_CHECK(reply->AddIntToMessage(-permission_list.denied_errno()));
195     return;
196   }
197   if (command_type == COMMAND_STAT) {
198     struct stat sb;
199     int sts =
200         follow_links ? stat(file_to_access, &sb) : lstat(file_to_access, &sb);
201     if (sts < 0) {
202       RAW_CHECK(reply->AddIntToMessage(-errno));
203       return;
204     }
205     RAW_CHECK(reply->AddIntToMessage(0));
206     RAW_CHECK(
207         reply->AddDataToMessage(reinterpret_cast<char*>(&sb), sizeof(sb)));
208   } else {
209     DCHECK(command_type == COMMAND_STAT64);
210 #if defined(__ANDROID_API__) && __ANDROID_API__ < 21
211     // stat64 is not defined for older Android API versions in newer NDK
212     // versions.
213     RAW_CHECK(reply->AddIntToMessage(-ENOSYS));
214     return;
215 #else
216     struct stat64 sb;
217     int sts = follow_links ? stat64(file_to_access, &sb)
218                            : lstat64(file_to_access, &sb);
219     if (sts < 0) {
220       RAW_CHECK(reply->AddIntToMessage(-errno));
221       return;
222     }
223     RAW_CHECK(reply->AddIntToMessage(0));
224     RAW_CHECK(
225         reply->AddDataToMessage(reinterpret_cast<char*>(&sb), sizeof(sb)));
226 #endif  // defined(__ANDROID_API__) && __ANDROID_API__ < 21
227   }
228 }
229 
UnlinkFileForIPC(const BrokerCommandSet & allowed_command_set,const BrokerPermissionList & permission_list,const std::string & requested_filename,BrokerSimpleMessage * message)230 void UnlinkFileForIPC(const BrokerCommandSet& allowed_command_set,
231                       const BrokerPermissionList& permission_list,
232                       const std::string& requested_filename,
233                       BrokerSimpleMessage* message) {
234   const char* file_to_access = nullptr;
235   if (!CommandUnlinkIsSafe(allowed_command_set, permission_list,
236                            requested_filename.c_str(), &file_to_access)) {
237     RAW_CHECK(message->AddIntToMessage(-permission_list.denied_errno()));
238     return;
239   }
240   if (unlink(file_to_access) < 0) {
241     RAW_CHECK(message->AddIntToMessage(-errno));
242     return;
243   }
244   RAW_CHECK(message->AddIntToMessage(0));
245 }
246 
247 // Handle a |command_type| request contained in |iter| and write the reply
248 // to |reply|.
HandleRemoteCommand(const BrokerCommandSet & allowed_command_set,const BrokerPermissionList & permission_list,BrokerSimpleMessage * message,BrokerSimpleMessage * reply,int * opened_file)249 bool HandleRemoteCommand(const BrokerCommandSet& allowed_command_set,
250                          const BrokerPermissionList& permission_list,
251                          BrokerSimpleMessage* message,
252                          BrokerSimpleMessage* reply,
253                          int* opened_file) {
254   // Message structure:
255   //   int:    command type
256   //   char[]: pathname
257   //   int:    flags
258   int command_type;
259   if (!message->ReadInt(&command_type))
260     return false;
261 
262   switch (command_type) {
263     case COMMAND_ACCESS: {
264       const char* requested_filename;
265       int flags = 0;
266       if (!GetPathAndFlags(message, &requested_filename, &flags))
267         return false;
268       AccessFileForIPC(allowed_command_set, permission_list, requested_filename,
269                        flags, reply);
270       break;
271     }
272     case COMMAND_MKDIR: {
273       const char* requested_filename;
274       int mode = 0;
275       if (!GetPathAndFlags(message, &requested_filename, &mode))
276         return false;
277       MkdirFileForIPC(allowed_command_set, permission_list, requested_filename,
278                       mode, reply);
279       break;
280     }
281     case COMMAND_OPEN: {
282       const char* requested_filename;
283       int flags = 0;
284       if (!GetPathAndFlags(message, &requested_filename, &flags))
285         return false;
286       OpenFileForIPC(allowed_command_set, permission_list, requested_filename,
287                      flags, reply, opened_file);
288       break;
289     }
290     case COMMAND_READLINK: {
291       const char* filename;
292       if (!message->ReadString(&filename))
293         return false;
294 
295       ReadlinkFileForIPC(allowed_command_set, permission_list, filename, reply);
296       break;
297     }
298     case COMMAND_RENAME: {
299       const char* old_filename;
300       if (!message->ReadString(&old_filename))
301         return false;
302 
303       const char* new_filename;
304       if (!message->ReadString(&new_filename))
305         return false;
306 
307       RenameFileForIPC(allowed_command_set, permission_list, old_filename,
308                        new_filename, reply);
309       break;
310     }
311     case COMMAND_RMDIR: {
312       const char* requested_filename;
313       if (!message->ReadString(&requested_filename))
314         return false;
315       RmdirFileForIPC(allowed_command_set, permission_list, requested_filename,
316                       reply);
317       break;
318     }
319     case COMMAND_STAT:
320     case COMMAND_STAT64: {
321       const char* requested_filename;
322       if (!message->ReadString(&requested_filename))
323         return false;
324       int follow_links;
325       if (!message->ReadInt(&follow_links))
326         return false;
327       StatFileForIPC(allowed_command_set, permission_list,
328                      static_cast<BrokerCommand>(command_type),
329                      requested_filename, !!follow_links, reply);
330       break;
331     }
332     case COMMAND_UNLINK: {
333       const char* requested_filename;
334       if (!message->ReadString(&requested_filename))
335         return false;
336       UnlinkFileForIPC(allowed_command_set, permission_list, requested_filename,
337                        reply);
338       break;
339     }
340     default:
341       LOG(ERROR) << "Invalid IPC command";
342       return false;
343   }
344   return true;
345 }
346 
347 }  // namespace
348 
BrokerHost(const BrokerPermissionList & broker_permission_list,const BrokerCommandSet & allowed_command_set,BrokerChannel::EndPoint ipc_channel)349 BrokerHost::BrokerHost(const BrokerPermissionList& broker_permission_list,
350                        const BrokerCommandSet& allowed_command_set,
351                        BrokerChannel::EndPoint ipc_channel)
352     : broker_permission_list_(broker_permission_list),
353       allowed_command_set_(allowed_command_set),
354       ipc_channel_(std::move(ipc_channel)) {}
355 
~BrokerHost()356 BrokerHost::~BrokerHost() {}
357 
358 // Handle a request on the IPC channel ipc_channel_.
359 // A request should have a file descriptor attached on which we will reply and
360 // that we will then close.
361 // A request should start with an int that will be used as the command type.
HandleRequest() const362 BrokerHost::RequestStatus BrokerHost::HandleRequest() const {
363   BrokerSimpleMessage message;
364   errno = 0;
365   base::ScopedFD temporary_ipc;
366   const ssize_t msg_len =
367       message.RecvMsgWithFlags(ipc_channel_.get(), 0, &temporary_ipc);
368 
369   if (msg_len == 0 || (msg_len == -1 && errno == ECONNRESET)) {
370     // EOF from the client, or the client died, we should die.
371     return RequestStatus::LOST_CLIENT;
372   }
373 
374   // The client sends exactly one file descriptor, on which we
375   // will write the reply.
376   if (msg_len < 0) {
377     PLOG(ERROR) << "Error reading message from the client";
378     return RequestStatus::FAILURE;
379   }
380 
381   BrokerSimpleMessage reply;
382   int opened_file = -1;
383   bool result =
384       HandleRemoteCommand(allowed_command_set_, broker_permission_list_,
385                           &message, &reply, &opened_file);
386 
387   ssize_t sent = reply.SendMsg(temporary_ipc.get(), opened_file);
388   if (sent < 0)
389     LOG(ERROR) << "sent failed";
390 
391   if (opened_file >= 0) {
392     int ret = IGNORE_EINTR(close(opened_file));
393     DCHECK(!ret) << "Could not close file descriptor";
394   }
395 
396   return result ? RequestStatus::SUCCESS : RequestStatus::FAILURE;
397 }
398 
399 }  // namespace syscall_broker
400 
401 }  // namespace sandbox
402