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 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 54 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 80 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 130 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 205 const char* BrokerFilePermission::GetErrorMessageForTests() { 206 static char kInvalidBrokerFileString[] = "Invalid BrokerFilePermission"; 207 return kInvalidBrokerFileString; 208 } 209 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