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 #include <unistd.h>
11 
12 #include <ostream>
13 #include <string>
14 
15 #include "base/check.h"
16 #include "sandbox/linux/syscall_broker/broker_command.h"
17 
18 namespace sandbox {
19 namespace syscall_broker {
20 
21 // Async signal safe
ValidatePath(const char * path)22 bool BrokerFilePermission::ValidatePath(const char* path) {
23   if (!path)
24     return false;
25 
26   const size_t len = strlen(path);
27   // No empty paths
28   if (len == 0)
29     return false;
30   // Paths must be absolute and not relative
31   if (path[0] != '/')
32     return false;
33   // No trailing / (but "/" is valid)
34   if (len > 1 && path[len - 1] == '/')
35     return false;
36   // No trailing /..
37   if (len >= 3 && path[len - 3] == '/' && path[len - 2] == '.' &&
38       path[len - 1] == '.')
39     return false;
40   // No /../ anywhere
41   for (size_t i = 0; i < len; i++) {
42     if (path[i] == '/' && (len - i) > 3) {
43       if (path[i + 1] == '.' && path[i + 2] == '.' && path[i + 3] == '/') {
44         return false;
45       }
46     }
47   }
48   return true;
49 }
50 
51 // Async signal safe
52 // Calls std::string::c_str(), strncmp and strlen. All these
53 // methods are async signal safe in common standard libs.
54 // TODO(leecam): remove dependency on std::string
MatchPath(const char * requested_filename) const55 bool BrokerFilePermission::MatchPath(const char* requested_filename) const {
56   // Note: This recursive match will allow any path under the allowlisted
57   // path, for any number of directory levels. E.g. if the allowlisted
58   // path is /good/ then the following will be permitted by the policy.
59   //   /good/file1
60   //   /good/folder/file2
61   //   /good/folder/folder2/file3
62   // If an attacker could make 'folder' a symlink to ../../ they would have
63   // access to the entire filesystem.
64   // Allowlisting with multiple depths is useful, e.g /proc/ but
65   // the system needs to ensure symlinks can not be created!
66   // That said if an attacker can convert any of the absolute paths
67   // to a symlink they can control any file on the system also.
68   return recursive_
69              ? strncmp(requested_filename, path_.c_str(), path_.length()) == 0
70              : strcmp(requested_filename, path_.c_str()) == 0;
71 }
72 
73 // Async signal safe.
74 // External call to std::string::c_str() is
75 // called in MatchPath.
76 // TODO(leecam): remove dependency on std::string
CheckAccess(const char * requested_filename,int mode,const char ** file_to_access) const77 bool BrokerFilePermission::CheckAccess(const char* requested_filename,
78                                        int mode,
79                                        const char** file_to_access) const {
80   // First, check if |mode| is existence, ability to read or ability
81   // to write. We do not support X_OK.
82   if (mode != F_OK && mode & ~(R_OK | W_OK))
83     return false;
84 
85   if (!ValidatePath(requested_filename))
86     return false;
87 
88   return CheckAccessInternal(requested_filename, mode, file_to_access);
89 }
90 
CheckAccessInternal(const char * requested_filename,int mode,const char ** file_to_access) const91 bool BrokerFilePermission::CheckAccessInternal(
92     const char* requested_filename,
93     int mode,
94     const char** file_to_access) const {
95   if (!MatchPath(requested_filename))
96     return false;
97 
98   bool allowed = false;
99   switch (mode) {
100     case F_OK:
101       allowed = allow_read_ || allow_write_;
102       break;
103     case R_OK:
104       allowed = allow_read_;
105       break;
106     case W_OK:
107       allowed = allow_write_;
108       break;
109     case R_OK | W_OK:
110       allowed = allow_read_ && allow_write_;
111       break;
112     default:
113       break;
114   }
115   if (!allowed)
116     return false;
117 
118   if (file_to_access)
119     *file_to_access = recursive_ ? requested_filename : path_.c_str();
120 
121   return true;
122 }
123 
124 // Async signal safe.
125 // External call to std::string::c_str() is
126 // called in MatchPath.
127 // TODO(leecam): remove dependency on std::string
CheckOpen(const char * requested_filename,int flags,const char ** file_to_open,bool * unlink_after_open) const128 bool BrokerFilePermission::CheckOpen(const char* requested_filename,
129                                      int flags,
130                                      const char** file_to_open,
131                                      bool* unlink_after_open) const {
132   if (!ValidatePath(requested_filename))
133     return false;
134 
135   if (!MatchPath(requested_filename))
136     return false;
137 
138   // First, check the access mode is valid.
139   const int access_mode = flags & O_ACCMODE;
140   if (access_mode != O_RDONLY && access_mode != O_WRONLY &&
141       access_mode != O_RDWR) {
142     return false;
143   }
144 
145   // Check if read is allowed.
146   if (!allow_read_ && (access_mode == O_RDONLY || access_mode == O_RDWR)) {
147     return false;
148   }
149 
150   // Check if write is allowed.
151   if (!allow_write_ && (access_mode == O_WRONLY || access_mode == O_RDWR)) {
152     return false;
153   }
154 
155   // Check if file creation is allowed.
156   if (!allow_create_ && (flags & O_CREAT)) {
157     return false;
158   }
159 
160   // If this file is to be temporary, ensure it is created, not pre-existing.
161   // See https://crbug.com/415681#c17
162   if (temporary_only_ && (!(flags & O_CREAT) || !(flags & O_EXCL))) {
163     return false;
164   }
165 
166   // Some flags affect the behavior of the current process. We don't support
167   // them and don't allow them for now.
168   if (flags & kCurrentProcessOpenFlagsMask) {
169     return false;
170   }
171 
172   // The effect of (O_RDONLY | O_TRUNC) is undefined, and in some cases it
173   // actually truncates, so deny.
174   if (access_mode == O_RDONLY && (flags & O_TRUNC) != 0) {
175     return false;
176   }
177 
178   // Now check that all the flags are known to us.
179   const int creation_and_status_flags = flags & ~O_ACCMODE;
180   const int known_flags = O_APPEND | O_ASYNC | O_CLOEXEC | O_CREAT | O_DIRECT |
181                           O_DIRECTORY | O_EXCL | O_LARGEFILE | O_NOATIME |
182                           O_NOCTTY | O_NOFOLLOW | O_NONBLOCK | O_NDELAY |
183                           O_SYNC | O_TRUNC;
184 
185   const int unknown_flags = ~known_flags;
186   const bool has_unknown_flags = creation_and_status_flags & unknown_flags;
187   if (has_unknown_flags)
188     return false;
189 
190   if (file_to_open)
191     *file_to_open = recursive_ ? requested_filename : path_.c_str();
192 
193   if (unlink_after_open)
194     *unlink_after_open = temporary_only_;
195 
196   return true;
197 }
198 
CheckStat(const char * requested_filename,const char ** file_to_access) const199 bool BrokerFilePermission::CheckStat(const char* requested_filename,
200                                      const char** file_to_access) const {
201   if (!ValidatePath(requested_filename))
202     return false;
203 
204   // Ability to access implies ability to stat().
205   if (CheckAccessInternal(requested_filename, F_OK, file_to_access))
206     return true;
207 
208   // Allow stat() on leading directories if have create or stat() permission.
209   if (!(allow_create_ || allow_stat_with_intermediates_))
210     return false;
211 
212   // NOTE: ValidatePath proved requested_length != 0;
213   size_t requested_length = strlen(requested_filename);
214   CHECK(requested_length);
215 
216   // Special case for root: only one slash, otherwise must have a second
217   // slash in the right spot to avoid substring matches.
218   // |allow_stat_with_intermediates_| can match on the full path, and
219   // |allow_create_| only matches a leading directory.
220   if ((requested_length == 1 && requested_filename[0] == '/') ||
221       (allow_stat_with_intermediates_ && path_ == requested_filename) ||
222       (requested_length < path_.length() &&
223        memcmp(path_.c_str(), requested_filename, requested_length) == 0 &&
224        path_.c_str()[requested_length] == '/')) {
225     if (file_to_access)
226       *file_to_access = requested_filename;
227 
228     return true;
229   }
230 
231   return false;
232 }
233 
GetErrorMessageForTests()234 const char* BrokerFilePermission::GetErrorMessageForTests() {
235   return "Invalid BrokerFilePermission";
236 }
237 
BrokerFilePermission(const std::string & path,RecursionOption recurse_opt,PersistenceOption persist_opt,ReadPermission read_perm,WritePermission write_perm,CreatePermission create_perm,StatWithIntermediatesPermission stat_perm)238 BrokerFilePermission::BrokerFilePermission(
239     const std::string& path,
240     RecursionOption recurse_opt,
241     PersistenceOption persist_opt,
242     ReadPermission read_perm,
243     WritePermission write_perm,
244     CreatePermission create_perm,
245     StatWithIntermediatesPermission stat_perm)
246     : path_(path),
247       recursive_(recurse_opt == RecursionOption::kRecursive),
248       temporary_only_(persist_opt == PersistenceOption::kTemporaryOnly),
249       allow_read_(read_perm == ReadPermission::kAllowRead),
250       allow_write_(write_perm == WritePermission::kAllowWrite),
251       allow_create_(create_perm == CreatePermission::kAllowCreate),
252       allow_stat_with_intermediates_(
253           stat_perm ==
254           StatWithIntermediatesPermission::kAllowStatWithIntermediates) {
255   // Must have enough length for a '/'
256   CHECK(path_.length() > 0) << GetErrorMessageForTests();
257 
258   // Allowlisted paths must be absolute.
259   CHECK(path_[0] == '/') << GetErrorMessageForTests();
260 
261   // Don't allow temporary creation without create permission
262   if (temporary_only_)
263     CHECK(allow_create_) << GetErrorMessageForTests();
264 
265   // Recursive paths must have a trailing slash, absolutes must not.
266   const char last_char = *(path_.rbegin());
267   if (recursive_)
268     CHECK(last_char == '/') << GetErrorMessageForTests();
269   else
270     CHECK(last_char != '/') << GetErrorMessageForTests();
271 }
272 
273 }  // namespace syscall_broker
274 }  // namespace sandbox
275