1 // Copyright (c) 2011 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 <string> 6 7 #include "sandbox/win/src/filesystem_policy.h" 8 9 #include "base/logging.h" 10 #include "base/win/scoped_handle.h" 11 #include "sandbox/win/src/ipc_tags.h" 12 #include "sandbox/win/src/policy_engine_opcodes.h" 13 #include "sandbox/win/src/policy_params.h" 14 #include "sandbox/win/src/sandbox_utils.h" 15 #include "sandbox/win/src/sandbox_types.h" 16 #include "sandbox/win/src/win_utils.h" 17 18 namespace { 19 20 NTSTATUS NtCreateFileInTarget(HANDLE* target_file_handle, 21 ACCESS_MASK desired_access, 22 OBJECT_ATTRIBUTES* obj_attributes, 23 IO_STATUS_BLOCK* io_status_block, 24 ULONG file_attributes, 25 ULONG share_access, 26 ULONG create_disposition, 27 ULONG create_options, 28 PVOID ea_buffer, 29 ULONG ea_lenght, 30 HANDLE target_process) { 31 NtCreateFileFunction NtCreateFile = NULL; 32 ResolveNTFunctionPtr("NtCreateFile", &NtCreateFile); 33 34 HANDLE local_handle = INVALID_HANDLE_VALUE; 35 NTSTATUS status = NtCreateFile(&local_handle, desired_access, obj_attributes, 36 io_status_block, NULL, file_attributes, 37 share_access, create_disposition, 38 create_options, ea_buffer, ea_lenght); 39 if (!NT_SUCCESS(status)) { 40 return status; 41 } 42 43 if (!sandbox::SameObject(local_handle, obj_attributes->ObjectName->Buffer)) { 44 // The handle points somewhere else. Fail the operation. 45 ::CloseHandle(local_handle); 46 return STATUS_ACCESS_DENIED; 47 } 48 49 if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, 50 target_process, target_file_handle, 0, FALSE, 51 DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { 52 return STATUS_ACCESS_DENIED; 53 } 54 return STATUS_SUCCESS; 55 } 56 57 } // namespace. 58 59 namespace sandbox { 60 61 bool FileSystemPolicy::GenerateRules(const wchar_t* name, 62 TargetPolicy::Semantics semantics, 63 LowLevelPolicy* policy) { 64 base::string16 mod_name(name); 65 if (mod_name.empty()) { 66 return false; 67 } 68 69 // Don't do any pre-processing if the name starts like the the native 70 // object manager style. 71 if (0 != _wcsnicmp(mod_name.c_str(), kNTObjManPrefix, kNTObjManPrefixLen)) { 72 // TODO(cpu) bug 32224: This prefix add is a hack because we don't have the 73 // infrastructure to normalize names. In any case we need to escape the 74 // question marks. 75 if (!PreProcessName(mod_name, &mod_name)) { 76 // The path to be added might contain a reparse point. 77 NOTREACHED(); 78 return false; 79 } 80 81 mod_name = FixNTPrefixForMatch(mod_name); 82 name = mod_name.c_str(); 83 } 84 85 EvalResult result = ASK_BROKER; 86 87 // List of supported calls for the filesystem. 88 const unsigned kCallNtCreateFile = 0x1; 89 const unsigned kCallNtOpenFile = 0x2; 90 const unsigned kCallNtQueryAttributesFile = 0x4; 91 const unsigned kCallNtQueryFullAttributesFile = 0x8; 92 const unsigned kCallNtSetInfoRename = 0x10; 93 94 DWORD rule_to_add = kCallNtOpenFile | kCallNtCreateFile | 95 kCallNtQueryAttributesFile | 96 kCallNtQueryFullAttributesFile | kCallNtSetInfoRename; 97 98 PolicyRule create(result); 99 PolicyRule open(result); 100 PolicyRule query(result); 101 PolicyRule query_full(result); 102 PolicyRule rename(result); 103 104 switch (semantics) { 105 case TargetPolicy::FILES_ALLOW_DIR_ANY: { 106 open.AddNumberMatch(IF, OpenFile::OPTIONS, FILE_DIRECTORY_FILE, AND); 107 create.AddNumberMatch(IF, OpenFile::OPTIONS, FILE_DIRECTORY_FILE, AND); 108 break; 109 } 110 case TargetPolicy::FILES_ALLOW_READONLY: { 111 // We consider all flags that are not known to be readonly as potentially 112 // used for write. 113 DWORD allowed_flags = FILE_READ_DATA | FILE_READ_ATTRIBUTES | 114 FILE_READ_EA | SYNCHRONIZE | FILE_EXECUTE | 115 GENERIC_READ | GENERIC_EXECUTE | READ_CONTROL; 116 DWORD restricted_flags = ~allowed_flags; 117 open.AddNumberMatch(IF_NOT, OpenFile::ACCESS, restricted_flags, AND); 118 create.AddNumberMatch(IF_NOT, OpenFile::ACCESS, restricted_flags, AND); 119 120 // Read only access don't work for rename. 121 rule_to_add &= ~kCallNtSetInfoRename; 122 break; 123 } 124 case TargetPolicy::FILES_ALLOW_QUERY: { 125 // Here we don't want to add policy for the open or the create. 126 rule_to_add &= ~(kCallNtOpenFile | kCallNtCreateFile | 127 kCallNtSetInfoRename); 128 break; 129 } 130 case TargetPolicy::FILES_ALLOW_ANY: { 131 break; 132 } 133 default: { 134 NOTREACHED(); 135 return false; 136 } 137 } 138 139 if ((rule_to_add & kCallNtCreateFile) && 140 (!create.AddStringMatch(IF, OpenFile::NAME, name, CASE_INSENSITIVE) || 141 !policy->AddRule(IPC_NTCREATEFILE_TAG, &create))) { 142 return false; 143 } 144 145 if ((rule_to_add & kCallNtOpenFile) && 146 (!open.AddStringMatch(IF, OpenFile::NAME, name, CASE_INSENSITIVE) || 147 !policy->AddRule(IPC_NTOPENFILE_TAG, &open))) { 148 return false; 149 } 150 151 if ((rule_to_add & kCallNtQueryAttributesFile) && 152 (!query.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) || 153 !policy->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG, &query))) { 154 return false; 155 } 156 157 if ((rule_to_add & kCallNtQueryFullAttributesFile) && 158 (!query_full.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) 159 || !policy->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG, 160 &query_full))) { 161 return false; 162 } 163 164 if ((rule_to_add & kCallNtSetInfoRename) && 165 (!rename.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) || 166 !policy->AddRule(IPC_NTSETINFO_RENAME_TAG, &rename))) { 167 return false; 168 } 169 170 return true; 171 } 172 173 // Right now we insert two rules, to be evaluated before any user supplied rule: 174 // - go to the broker if the path doesn't look like the paths that we push on 175 // the policy (namely \??\something). 176 // - go to the broker if it looks like this is a short-name path. 177 // 178 // It is possible to add a rule to go to the broker in any case; it would look 179 // something like: 180 // rule = new PolicyRule(ASK_BROKER); 181 // rule->AddNumberMatch(IF_NOT, FileName::BROKER, TRUE, AND); 182 // policy->AddRule(service, rule); 183 bool FileSystemPolicy::SetInitialRules(LowLevelPolicy* policy) { 184 PolicyRule format(ASK_BROKER); 185 PolicyRule short_name(ASK_BROKER); 186 187 bool rv = format.AddNumberMatch(IF_NOT, FileName::BROKER, TRUE, AND); 188 rv &= format.AddStringMatch(IF_NOT, FileName::NAME, L"\\/?/?\\*", 189 CASE_SENSITIVE); 190 191 rv &= short_name.AddNumberMatch(IF_NOT, FileName::BROKER, TRUE, AND); 192 rv &= short_name.AddStringMatch(IF, FileName::NAME, L"*~*", CASE_SENSITIVE); 193 194 if (!rv || !policy->AddRule(IPC_NTCREATEFILE_TAG, &format)) 195 return false; 196 197 if (!policy->AddRule(IPC_NTCREATEFILE_TAG, &short_name)) 198 return false; 199 200 if (!policy->AddRule(IPC_NTOPENFILE_TAG, &format)) 201 return false; 202 203 if (!policy->AddRule(IPC_NTOPENFILE_TAG, &short_name)) 204 return false; 205 206 if (!policy->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG, &format)) 207 return false; 208 209 if (!policy->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG, &short_name)) 210 return false; 211 212 if (!policy->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG, &format)) 213 return false; 214 215 if (!policy->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG, &short_name)) 216 return false; 217 218 if (!policy->AddRule(IPC_NTSETINFO_RENAME_TAG, &format)) 219 return false; 220 221 if (!policy->AddRule(IPC_NTSETINFO_RENAME_TAG, &short_name)) 222 return false; 223 224 return true; 225 } 226 227 bool FileSystemPolicy::CreateFileAction(EvalResult eval_result, 228 const ClientInfo& client_info, 229 const base::string16 &file, 230 uint32 attributes, 231 uint32 desired_access, 232 uint32 file_attributes, 233 uint32 share_access, 234 uint32 create_disposition, 235 uint32 create_options, 236 HANDLE *handle, 237 NTSTATUS* nt_status, 238 ULONG_PTR *io_information) { 239 // The only action supported is ASK_BROKER which means create the requested 240 // file as specified. 241 if (ASK_BROKER != eval_result) { 242 *nt_status = STATUS_ACCESS_DENIED; 243 return false; 244 } 245 IO_STATUS_BLOCK io_block = {0}; 246 UNICODE_STRING uni_name = {0}; 247 OBJECT_ATTRIBUTES obj_attributes = {0}; 248 InitObjectAttribs(file, attributes, NULL, &obj_attributes, &uni_name); 249 *nt_status = NtCreateFileInTarget(handle, desired_access, &obj_attributes, 250 &io_block, file_attributes, share_access, 251 create_disposition, create_options, NULL, 252 0, client_info.process); 253 254 *io_information = io_block.Information; 255 return true; 256 } 257 258 bool FileSystemPolicy::OpenFileAction(EvalResult eval_result, 259 const ClientInfo& client_info, 260 const base::string16 &file, 261 uint32 attributes, 262 uint32 desired_access, 263 uint32 share_access, 264 uint32 open_options, 265 HANDLE *handle, 266 NTSTATUS* nt_status, 267 ULONG_PTR *io_information) { 268 // The only action supported is ASK_BROKER which means open the requested 269 // file as specified. 270 if (ASK_BROKER != eval_result) { 271 *nt_status = STATUS_ACCESS_DENIED; 272 return true; 273 } 274 // An NtOpen is equivalent to an NtCreate with FileAttributes = 0 and 275 // CreateDisposition = FILE_OPEN. 276 IO_STATUS_BLOCK io_block = {0}; 277 UNICODE_STRING uni_name = {0}; 278 OBJECT_ATTRIBUTES obj_attributes = {0}; 279 InitObjectAttribs(file, attributes, NULL, &obj_attributes, &uni_name); 280 *nt_status = NtCreateFileInTarget(handle, desired_access, &obj_attributes, 281 &io_block, 0, share_access, FILE_OPEN, 282 open_options, NULL, 0, 283 client_info.process); 284 285 *io_information = io_block.Information; 286 return true; 287 } 288 289 bool FileSystemPolicy::QueryAttributesFileAction( 290 EvalResult eval_result, 291 const ClientInfo& client_info, 292 const base::string16 &file, 293 uint32 attributes, 294 FILE_BASIC_INFORMATION* file_info, 295 NTSTATUS* nt_status) { 296 // The only action supported is ASK_BROKER which means query the requested 297 // file as specified. 298 if (ASK_BROKER != eval_result) { 299 *nt_status = STATUS_ACCESS_DENIED; 300 return true; 301 } 302 303 NtQueryAttributesFileFunction NtQueryAttributesFile = NULL; 304 ResolveNTFunctionPtr("NtQueryAttributesFile", &NtQueryAttributesFile); 305 306 UNICODE_STRING uni_name = {0}; 307 OBJECT_ATTRIBUTES obj_attributes = {0}; 308 InitObjectAttribs(file, attributes, NULL, &obj_attributes, &uni_name); 309 *nt_status = NtQueryAttributesFile(&obj_attributes, file_info); 310 311 return true; 312 } 313 314 bool FileSystemPolicy::QueryFullAttributesFileAction( 315 EvalResult eval_result, 316 const ClientInfo& client_info, 317 const base::string16 &file, 318 uint32 attributes, 319 FILE_NETWORK_OPEN_INFORMATION* file_info, 320 NTSTATUS* nt_status) { 321 // The only action supported is ASK_BROKER which means query the requested 322 // file as specified. 323 if (ASK_BROKER != eval_result) { 324 *nt_status = STATUS_ACCESS_DENIED; 325 return true; 326 } 327 328 NtQueryFullAttributesFileFunction NtQueryFullAttributesFile = NULL; 329 ResolveNTFunctionPtr("NtQueryFullAttributesFile", &NtQueryFullAttributesFile); 330 331 UNICODE_STRING uni_name = {0}; 332 OBJECT_ATTRIBUTES obj_attributes = {0}; 333 InitObjectAttribs(file, attributes, NULL, &obj_attributes, &uni_name); 334 *nt_status = NtQueryFullAttributesFile(&obj_attributes, file_info); 335 336 return true; 337 } 338 339 bool FileSystemPolicy::SetInformationFileAction( 340 EvalResult eval_result, const ClientInfo& client_info, 341 HANDLE target_file_handle, void* file_info, uint32 length, 342 uint32 info_class, IO_STATUS_BLOCK* io_block, 343 NTSTATUS* nt_status) { 344 // The only action supported is ASK_BROKER which means open the requested 345 // file as specified. 346 if (ASK_BROKER != eval_result) { 347 *nt_status = STATUS_ACCESS_DENIED; 348 return true; 349 } 350 351 NtSetInformationFileFunction NtSetInformationFile = NULL; 352 ResolveNTFunctionPtr("NtSetInformationFile", &NtSetInformationFile); 353 354 HANDLE local_handle = NULL; 355 if (!::DuplicateHandle(client_info.process, target_file_handle, 356 ::GetCurrentProcess(), &local_handle, 0, FALSE, 357 DUPLICATE_SAME_ACCESS)) { 358 *nt_status = STATUS_ACCESS_DENIED; 359 return true; 360 } 361 362 base::win::ScopedHandle handle(local_handle); 363 364 FILE_INFORMATION_CLASS file_info_class = 365 static_cast<FILE_INFORMATION_CLASS>(info_class); 366 *nt_status = NtSetInformationFile(local_handle, io_block, file_info, length, 367 file_info_class); 368 369 return true; 370 } 371 372 bool PreProcessName(const base::string16& path, base::string16* new_path) { 373 ConvertToLongPath(path, new_path); 374 375 bool reparsed = false; 376 if (ERROR_SUCCESS != IsReparsePoint(*new_path, &reparsed)) 377 return false; 378 379 // We can't process reparsed file. 380 return !reparsed; 381 } 382 383 base::string16 FixNTPrefixForMatch(const base::string16& name) { 384 base::string16 mod_name = name; 385 386 // NT prefix escaped for rule matcher 387 const wchar_t kNTPrefixEscaped[] = L"\\/?/?\\"; 388 const int kNTPrefixEscapedLen = arraysize(kNTPrefixEscaped) - 1; 389 390 if (0 != mod_name.compare(0, kNTPrefixLen, kNTPrefix)) { 391 if (0 != mod_name.compare(0, kNTPrefixEscapedLen, kNTPrefixEscaped)) { 392 // TODO(nsylvain): Find a better way to do name resolution. Right now we 393 // take the name and we expand it. 394 mod_name.insert(0, kNTPrefixEscaped); 395 } 396 } else { 397 // Start of name matches NT prefix, replace with escaped format 398 // Fixes bug: 334882 399 mod_name.replace(0, kNTPrefixLen, kNTPrefixEscaped); 400 } 401 402 return mod_name; 403 } 404 405 } // namespace sandbox 406