Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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 <string.h> | |
| 9 | |
| 10 #include <string> | |
| 11 | |
| 12 #include "base/logging.h" | |
| 13 #include "base/pickle.h" | |
|
Jorge Lucangeli Obes
2014/11/20 00:13:20
not needed anymore.
leecam
2014/11/20 00:56:38
Done.
| |
| 14 #include "sandbox/linux/syscall_broker/broker_common.h" | |
| 15 | |
| 16 namespace sandbox { | |
| 17 | |
| 18 namespace syscall_broker { | |
| 19 | |
| 20 // Async signal safe | |
| 21 bool ValidatePath(const char* path) { | |
|
jln (very slow on Chromium)
2014/11/24 20:12:16
This absolutely needs unit tests.
Maybe move to a
leecam
2014/11/25 02:06:49
Done.
| |
| 22 if (!path) | |
| 23 return false; | |
| 24 | |
| 25 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 | |
| 54 bool BrokerFilePermission::IsPathCoveredByThisPermission( | |
| 55 const char* requested_filename) const { | |
| 56 const char* path = path_.c_str(); | |
| 57 if ((recursive_ && strncmp(requested_filename, path, strlen(path)) == 0)) { | |
| 58 // Note: This prefix match will allow any path under the whitelisted | |
| 59 // path, for any number of directory levels. E.g. if the whitelisted | |
| 60 // path is /good/ then the following will be permitted by the policy. | |
| 61 // /good/file1 | |
| 62 // /good/folder/file2 | |
| 63 // /good/folder/folder2/file3 | |
| 64 // If an attacker could make 'folder' a symlink to ../../ they would have | |
| 65 // access to the entire filesystem. | |
| 66 // Whitelisting with multiple depths is useful, e.g /proc/ but | |
| 67 // the system needs to ensure symlinks can not be created! | |
| 68 // That said if an attacker can convert any of the absolute paths | |
| 69 // to a symlink they can control any file on the system also. | |
| 70 return true; | |
| 71 } else if (strcmp(requested_filename, path) == 0) { | |
| 72 return true; | |
| 73 } | |
| 74 return false; | |
| 75 } | |
| 76 | |
| 77 // Async signal safe. | |
| 78 // External call to std::string::c_str() is | |
| 79 // called in IsPathCoveredByThisPermission. | |
| 80 // TODO(leecam): remove dependency on std::string | |
| 81 bool BrokerFilePermission::CheckAccess(const char* requested_filename, | |
| 82 int mode, | |
| 83 const char** file_to_access) const { | |
| 84 // First, check if |mode| is existence, ability to read or ability | |
| 85 // to write. We do not support X_OK. | |
| 86 if (mode != F_OK && mode & ~(R_OK | W_OK)) { | |
| 87 return false; | |
| 88 } | |
| 89 | |
| 90 if (!ValidatePath(requested_filename)) | |
| 91 return false; | |
| 92 | |
| 93 if (!IsPathCoveredByThisPermission(requested_filename)) { | |
| 94 return false; | |
| 95 } | |
| 96 bool allowed = false; | |
| 97 switch (mode) { | |
| 98 case F_OK: | |
| 99 if (allow_read_ || allow_write_) | |
| 100 allowed = true; | |
| 101 break; | |
| 102 case R_OK: | |
| 103 if (allow_read_) | |
| 104 allowed = true; | |
| 105 break; | |
| 106 case W_OK: | |
| 107 if (allow_write_) | |
| 108 allowed = true; | |
| 109 break; | |
| 110 case R_OK | W_OK: | |
| 111 if (allow_read_ && allow_write_) | |
| 112 allowed = true; | |
| 113 break; | |
| 114 default: | |
| 115 return false; | |
| 116 } | |
| 117 | |
| 118 if (allowed && file_to_access) { | |
| 119 if (!recursive_) | |
| 120 *file_to_access = path_.c_str(); | |
| 121 else | |
| 122 *file_to_access = requested_filename; | |
| 123 } | |
| 124 return allowed; | |
| 125 } | |
| 126 | |
| 127 // Async signal safe. | |
| 128 // External call to std::string::c_str() is | |
| 129 // called in IsPathCoveredByThisPermission. | |
| 130 // TODO(leecam): remove dependency on std::string | |
| 131 bool BrokerFilePermission::CheckOpen(const char* requested_filename, | |
| 132 int flags, | |
| 133 const char** file_to_open, | |
| 134 bool* unlink_after_open) const { | |
| 135 if (!ValidatePath(requested_filename)) | |
| 136 return false; | |
| 137 | |
| 138 if (!IsPathCoveredByThisPermission(requested_filename)) { | |
| 139 return false; | |
| 140 } | |
| 141 | |
| 142 // First, check the access mode is valid. | |
| 143 const int access_mode = flags & O_ACCMODE; | |
| 144 if (access_mode != O_RDONLY && access_mode != O_WRONLY && | |
| 145 access_mode != O_RDWR) { | |
| 146 return false; | |
| 147 } | |
| 148 | |
| 149 // Check if read is allowed | |
| 150 if (!allow_read_ && (access_mode == O_RDONLY || access_mode == O_RDWR)) { | |
| 151 return false; | |
| 152 } | |
| 153 | |
| 154 // Check if write is allowed | |
| 155 if (!allow_write_ && (access_mode == O_WRONLY || access_mode == O_RDWR)) { | |
| 156 return false; | |
| 157 } | |
| 158 | |
| 159 // Check if file creation is allowed. | |
| 160 if (!allow_create_ && (flags & O_CREAT)) { | |
| 161 return false; | |
| 162 } | |
| 163 | |
| 164 // If O_CREAT is present, ensure O_EXCL | |
| 165 if ((flags & O_CREAT) && !(flags & O_EXCL)) { | |
| 166 return false; | |
| 167 } | |
| 168 | |
| 169 // If this file is to be unlinked, ensure its created. | |
| 170 if (unlink_ && !(flags & O_CREAT)) { | |
| 171 return false; | |
| 172 } | |
| 173 | |
| 174 // Some flags affect the behavior of the current process. We don't support | |
| 175 // them and don't allow them for now. | |
| 176 if (flags & kCurrentProcessOpenFlagsMask) { | |
| 177 return false; | |
| 178 } | |
| 179 | |
| 180 // Now check that all the flags are known to us. | |
| 181 const int creation_and_status_flags = flags & ~O_ACCMODE; | |
| 182 | |
| 183 const int known_flags = O_APPEND | O_ASYNC | O_CLOEXEC | O_CREAT | O_DIRECT | | |
| 184 O_DIRECTORY | O_EXCL | O_LARGEFILE | O_NOATIME | | |
| 185 O_NOCTTY | O_NOFOLLOW | O_NONBLOCK | O_NDELAY | | |
| 186 O_SYNC | O_TRUNC; | |
| 187 | |
| 188 const int unknown_flags = ~known_flags; | |
| 189 const bool has_unknown_flags = creation_and_status_flags & unknown_flags; | |
| 190 | |
| 191 if (has_unknown_flags) | |
| 192 return false; | |
| 193 | |
| 194 if (file_to_open) { | |
| 195 if (!recursive_) | |
| 196 *file_to_open = path_.c_str(); | |
| 197 else | |
| 198 *file_to_open = requested_filename; | |
| 199 } | |
| 200 if (unlink_after_open) | |
| 201 *unlink_after_open = unlink_; | |
| 202 | |
| 203 return true; | |
| 204 } | |
| 205 | |
| 206 BrokerFilePermission::BrokerFilePermission(std::string path, | |
| 207 bool recursive, | |
| 208 bool unlink, | |
| 209 bool allow_read, | |
| 210 bool allow_write, | |
| 211 bool allow_create) | |
| 212 : path_(path), | |
| 213 recursive_(recursive), | |
| 214 unlink_(unlink), | |
| 215 allow_read_(allow_read), | |
| 216 allow_write_(allow_write), | |
| 217 allow_create_(allow_create) { | |
| 218 // Validate this permission and die if invalid! | |
| 219 | |
| 220 // Don't allow unlinking on creation without create permission | |
| 221 if (unlink_) | |
| 222 CHECK(allow_create); | |
|
Jorge Lucangeli Obes
2014/11/20 00:13:20
Add comments as strings to CHECK:
CHECK(...) << "
leecam
2014/11/20 00:56:38
Done.
| |
| 223 // Recursive paths must have a trailing slash | |
| 224 if (recursive_) | |
| 225 CHECK(path_.back() == '/'); | |
|
Jorge Lucangeli Obes
2014/11/20 00:13:20
Here too.
leecam
2014/11/20 00:56:38
Done.
| |
| 226 // Whitelisted paths must be absolute. | |
| 227 CHECK(path_.front() == '/'); | |
|
Jorge Lucangeli Obes
2014/11/20 00:13:20
And here.
leecam
2014/11/20 00:56:38
Done.
| |
| 228 } | |
| 229 | |
| 230 } // namespace syscall_broker | |
| 231 | |
| 232 } // namespace sandbox | |
| OLD | NEW |