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_file_permission.h"
6 
7 #include <fcntl.h>
8 #include <stddef.h>
9 #include <string.h>
10 
11 #include <string>
12 
13 #include "base/logging.h"
14 #include "sandbox/linux/syscall_broker/broker_common.h"
15 
16 namespace sandbox {
17 
18 namespace syscall_broker {
19 
20 // Async signal safe
ValidatePath(const char * path)21 bool BrokerFilePermission::ValidatePath(const char* path) {
22   if (!path)
23     return false;
24 
25   const size_t len = strlen(path);
26   // No empty paths
27   if (len == 0)
28     return false;
29   // Paths must be absolute and not relative
30   if (path[0] != '/')
31     return false;
32   // No trailing / (but "/" is valid)
33   if (len > 1 && path[len - 1] == '/')
34     return false;
35   // No trailing /..
36   if (len >= 3 && path[len - 3] == '/' && path[len - 2] == '.' &&
37       path[len - 1] == '.')
38     return false;
39   // No /../ anywhere
40   for (size_t i = 0; i < len; i++) {
41     if (path[i] == '/' && (len - i) > 3) {
42       if (path[i + 1] == '.' && path[i + 2] == '.' && path[i + 3] == '/') {
43         return false;
44       }
45     }
46   }
47   return true;
48 }
49 
50 // Async signal safe
51 // Calls std::string::c_str(), strncmp and strlen. All these
52 // methods are async signal safe in common standard libs.
53 // TODO(leecam): remove dependency on std::string
MatchPath(const char * requested_filename) const54 bool BrokerFilePermission::MatchPath(const char* requested_filename) const {
55   const char* path = path_.c_str();
56   if ((recursive_ && strncmp(requested_filename, path, strlen(path)) == 0)) {
57     // Note: This prefix match will allow any path under the whitelisted
58     // path, for any number of directory levels. E.g. if the whitelisted
59     // path is /good/ then the following will be permitted by the policy.
60     //   /good/file1
61     //   /good/folder/file2
62     //   /good/folder/folder2/file3
63     // If an attacker could make 'folder' a symlink to ../../ they would have
64     // access to the entire filesystem.
65     // Whitelisting with multiple depths is useful, e.g /proc/ but
66     // the system needs to ensure symlinks can not be created!
67     // That said if an attacker can convert any of the absolute paths
68     // to a symlink they can control any file on the system also.
69     return true;
70   } else if (strcmp(requested_filename, path) == 0) {
71     return true;
72   }
73   return false;
74 }
75 
76 // Async signal safe.
77 // External call to std::string::c_str() is
78 // called in MatchPath.
79 // TODO(leecam): remove dependency on std::string
CheckAccess(const char * requested_filename,int mode,const char ** file_to_access) const80 bool BrokerFilePermission::CheckAccess(const char* requested_filename,
81                                        int mode,
82                                        const char** file_to_access) const {
83   // First, check if |mode| is existence, ability to read or ability
84   // to write. We do not support X_OK.
85   if (mode != F_OK && mode & ~(R_OK | W_OK)) {
86     return false;
87   }
88 
89   if (!ValidatePath(requested_filename))
90     return false;
91 
92   if (!MatchPath(requested_filename)) {
93     return false;
94   }
95   bool allowed = false;
96   switch (mode) {
97     case F_OK:
98       if (allow_read_ || allow_write_)
99         allowed = true;
100       break;
101     case R_OK:
102       if (allow_read_)
103         allowed = true;
104       break;
105     case W_OK:
106       if (allow_write_)
107         allowed = true;
108       break;
109     case R_OK | W_OK:
110       if (allow_read_ && allow_write_)
111         allowed = true;
112       break;
113     default:
114       return false;
115   }
116 
117   if (allowed && file_to_access) {
118     if (!recursive_)
119       *file_to_access = path_.c_str();
120     else
121       *file_to_access = requested_filename;
122   }
123   return allowed;
124 }
125 
126 // Async signal safe.
127 // External call to std::string::c_str() is
128 // called in MatchPath.
129 // TODO(leecam): remove dependency on std::string
CheckOpen(const char * requested_filename,int flags,const char ** file_to_open,bool * unlink_after_open) const130 bool BrokerFilePermission::CheckOpen(const char* requested_filename,
131                                      int flags,
132                                      const char** file_to_open,
133                                      bool* unlink_after_open) const {
134   if (!ValidatePath(requested_filename))
135     return false;
136 
137   if (!MatchPath(requested_filename)) {
138     return false;
139   }
140 
141   // First, check the access mode is valid.
142   const int access_mode = flags & O_ACCMODE;
143   if (access_mode != O_RDONLY && access_mode != O_WRONLY &&
144       access_mode != O_RDWR) {
145     return false;
146   }
147 
148   // Check if read is allowed
149   if (!allow_read_ && (access_mode == O_RDONLY || access_mode == O_RDWR)) {
150     return false;
151   }
152 
153   // Check if write is allowed
154   if (!allow_write_ && (access_mode == O_WRONLY || access_mode == O_RDWR)) {
155     return false;
156   }
157 
158   // Check if file creation is allowed.
159   if (!allow_create_ && (flags & O_CREAT)) {
160     return false;
161   }
162 
163   // If O_CREAT is present, ensure O_EXCL
164   if ((flags & O_CREAT) && !(flags & O_EXCL)) {
165     return false;
166   }
167 
168   // If this file is to be unlinked, ensure it's created.
169   if (unlink_ && !(flags & O_CREAT)) {
170     return false;
171   }
172 
173   // Some flags affect the behavior of the current process. We don't support
174   // them and don't allow them for now.
175   if (flags & kCurrentProcessOpenFlagsMask) {
176     return false;
177   }
178 
179   // Now check that all the flags are known to us.
180   const int creation_and_status_flags = flags & ~O_ACCMODE;
181 
182   const int known_flags = O_APPEND | O_ASYNC | O_CLOEXEC | O_CREAT | O_DIRECT |
183                           O_DIRECTORY | O_EXCL | O_LARGEFILE | O_NOATIME |
184                           O_NOCTTY | O_NOFOLLOW | O_NONBLOCK | O_NDELAY |
185                           O_SYNC | O_TRUNC;
186 
187   const int unknown_flags = ~known_flags;
188   const bool has_unknown_flags = creation_and_status_flags & unknown_flags;
189 
190   if (has_unknown_flags)
191     return false;
192 
193   if (file_to_open) {
194     if (!recursive_)
195       *file_to_open = path_.c_str();
196     else
197       *file_to_open = requested_filename;
198   }
199   if (unlink_after_open)
200     *unlink_after_open = unlink_;
201 
202   return true;
203 }
204 
GetErrorMessageForTests()205 const char* BrokerFilePermission::GetErrorMessageForTests() {
206   static char kInvalidBrokerFileString[] = "Invalid BrokerFilePermission";
207   return kInvalidBrokerFileString;
208 }
209 
BrokerFilePermission(const std::string & path,bool recursive,bool unlink,bool allow_read,bool allow_write,bool allow_create)210 BrokerFilePermission::BrokerFilePermission(const std::string& path,
211                                            bool recursive,
212                                            bool unlink,
213                                            bool allow_read,
214                                            bool allow_write,
215                                            bool allow_create)
216     : path_(path),
217       recursive_(recursive),
218       unlink_(unlink),
219       allow_read_(allow_read),
220       allow_write_(allow_write),
221       allow_create_(allow_create) {
222   // Validate this permission and die if invalid!
223 
224   // Must have enough length for a '/'
225   CHECK(path_.length() > 0) << GetErrorMessageForTests();
226   // Whitelisted paths must be absolute.
227   CHECK(path_[0] == '/') << GetErrorMessageForTests();
228 
229   // Don't allow unlinking on creation without create permission
230   if (unlink_) {
231     CHECK(allow_create) << GetErrorMessageForTests();
232   }
233   const char last_char = *(path_.rbegin());
234   // Recursive paths must have a trailing slash
235   if (recursive_) {
236     CHECK(last_char == '/') << GetErrorMessageForTests();
237   } else {
238     CHECK(last_char != '/') << GetErrorMessageForTests();
239   }
240 }
241 
242 }  // namespace syscall_broker
243 
244 }  // namespace sandbox