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