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