|  | // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include <string> | 
|  |  | 
|  | #include "sandbox/win/src/filesystem_policy.h" | 
|  |  | 
|  | #include "base/logging.h" | 
|  | #include "base/win/scoped_handle.h" | 
|  | #include "sandbox/win/src/ipc_tags.h" | 
|  | #include "sandbox/win/src/policy_engine_opcodes.h" | 
|  | #include "sandbox/win/src/policy_params.h" | 
|  | #include "sandbox/win/src/sandbox_utils.h" | 
|  | #include "sandbox/win/src/sandbox_types.h" | 
|  | #include "sandbox/win/src/win_utils.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | NTSTATUS NtCreateFileInTarget(HANDLE* target_file_handle, | 
|  | ACCESS_MASK desired_access, | 
|  | OBJECT_ATTRIBUTES* obj_attributes, | 
|  | IO_STATUS_BLOCK* io_status_block, | 
|  | ULONG file_attributes, | 
|  | ULONG share_access, | 
|  | ULONG create_disposition, | 
|  | ULONG create_options, | 
|  | PVOID ea_buffer, | 
|  | ULONG ea_lenght, | 
|  | HANDLE target_process) { | 
|  | NtCreateFileFunction NtCreateFile = NULL; | 
|  | ResolveNTFunctionPtr("NtCreateFile", &NtCreateFile); | 
|  |  | 
|  | HANDLE local_handle = INVALID_HANDLE_VALUE; | 
|  | NTSTATUS status = NtCreateFile(&local_handle, desired_access, obj_attributes, | 
|  | io_status_block, NULL, file_attributes, | 
|  | share_access, create_disposition, | 
|  | create_options, ea_buffer, ea_lenght); | 
|  | if (!NT_SUCCESS(status)) { | 
|  | return status; | 
|  | } | 
|  |  | 
|  | if (!sandbox::SameObject(local_handle, obj_attributes->ObjectName->Buffer)) { | 
|  | // The handle points somewhere else. Fail the operation. | 
|  | ::CloseHandle(local_handle); | 
|  | return STATUS_ACCESS_DENIED; | 
|  | } | 
|  |  | 
|  | if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, | 
|  | target_process, target_file_handle, 0, FALSE, | 
|  | DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { | 
|  | ::CloseHandle(local_handle); | 
|  | return STATUS_ACCESS_DENIED; | 
|  | } | 
|  | return STATUS_SUCCESS; | 
|  | } | 
|  |  | 
|  | }  // namespace. | 
|  |  | 
|  | namespace sandbox { | 
|  |  | 
|  | bool FileSystemPolicy::GenerateRules(const wchar_t* name, | 
|  | TargetPolicy::Semantics semantics, | 
|  | LowLevelPolicy* policy) { | 
|  | std::wstring mod_name(name); | 
|  | if (mod_name.empty()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Don't do any pre-processing if the name starts like the the native | 
|  | // object manager style. | 
|  | if (0 != _wcsnicmp(mod_name.c_str(), kNTObjManPrefix, kNTObjManPrefixLen)) { | 
|  | // TODO(cpu) bug 32224: This prefix add is a hack because we don't have the | 
|  | // infrastructure to normalize names. In any case we need to escape the | 
|  | // question marks. | 
|  | if (!PreProcessName(mod_name, &mod_name)) { | 
|  | // The path to be added might contain a reparse point. | 
|  | NOTREACHED(); | 
|  | return false; | 
|  | } | 
|  | if (0 != mod_name.compare(0, kNTPrefixLen, kNTPrefix)) { | 
|  | // TODO(nsylvain): Find a better way to do name resolution. Right now we | 
|  | // take the name and we expand it. | 
|  | mod_name.insert(0, L"\\/?/?\\"); | 
|  | name = mod_name.c_str(); | 
|  | } | 
|  | } | 
|  |  | 
|  | EvalResult result = ASK_BROKER; | 
|  |  | 
|  | // List of supported calls for the filesystem. | 
|  | const unsigned kCallNtCreateFile = 0x1; | 
|  | const unsigned kCallNtOpenFile = 0x2; | 
|  | const unsigned kCallNtQueryAttributesFile = 0x4; | 
|  | const unsigned kCallNtQueryFullAttributesFile = 0x8; | 
|  | const unsigned kCallNtSetInfoRename = 0x10; | 
|  |  | 
|  | DWORD  rule_to_add = kCallNtOpenFile | kCallNtCreateFile | | 
|  | kCallNtQueryAttributesFile | | 
|  | kCallNtQueryFullAttributesFile | kCallNtSetInfoRename; | 
|  |  | 
|  | PolicyRule create(result); | 
|  | PolicyRule open(result); | 
|  | PolicyRule query(result); | 
|  | PolicyRule query_full(result); | 
|  | PolicyRule rename(result); | 
|  |  | 
|  | switch (semantics) { | 
|  | case TargetPolicy::FILES_ALLOW_DIR_ANY: { | 
|  | open.AddNumberMatch(IF, OpenFile::OPTIONS, FILE_DIRECTORY_FILE, AND); | 
|  | create.AddNumberMatch(IF, OpenFile::OPTIONS, FILE_DIRECTORY_FILE, AND); | 
|  | break; | 
|  | } | 
|  | case TargetPolicy::FILES_ALLOW_READONLY: { | 
|  | // We consider all flags that are not known to be readonly as potentially | 
|  | // used for write. | 
|  | DWORD allowed_flags = FILE_READ_DATA | FILE_READ_ATTRIBUTES | | 
|  | FILE_READ_EA | SYNCHRONIZE | FILE_EXECUTE | | 
|  | GENERIC_READ | GENERIC_EXECUTE | READ_CONTROL; | 
|  | DWORD restricted_flags = ~allowed_flags; | 
|  | open.AddNumberMatch(IF_NOT, OpenFile::ACCESS, restricted_flags, AND); | 
|  | create.AddNumberMatch(IF_NOT, OpenFile::ACCESS, restricted_flags, AND); | 
|  |  | 
|  | // Read only access don't work for rename. | 
|  | rule_to_add &= ~kCallNtSetInfoRename; | 
|  | break; | 
|  | } | 
|  | case TargetPolicy::FILES_ALLOW_QUERY: { | 
|  | // Here we don't want to add policy for the open or the create. | 
|  | rule_to_add &= ~(kCallNtOpenFile | kCallNtCreateFile | | 
|  | kCallNtSetInfoRename); | 
|  | break; | 
|  | } | 
|  | case TargetPolicy::FILES_ALLOW_ANY: { | 
|  | break; | 
|  | } | 
|  | default: { | 
|  | NOTREACHED(); | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | if ((rule_to_add & kCallNtCreateFile) && | 
|  | (!create.AddStringMatch(IF, OpenFile::NAME, name, CASE_INSENSITIVE) || | 
|  | !policy->AddRule(IPC_NTCREATEFILE_TAG, &create))) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if ((rule_to_add & kCallNtOpenFile) && | 
|  | (!open.AddStringMatch(IF, OpenFile::NAME, name, CASE_INSENSITIVE) || | 
|  | !policy->AddRule(IPC_NTOPENFILE_TAG, &open))) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if ((rule_to_add & kCallNtQueryAttributesFile) && | 
|  | (!query.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) || | 
|  | !policy->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG, &query))) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if ((rule_to_add & kCallNtQueryFullAttributesFile) && | 
|  | (!query_full.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) | 
|  | || !policy->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG, | 
|  | &query_full))) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if ((rule_to_add & kCallNtSetInfoRename) && | 
|  | (!rename.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) || | 
|  | !policy->AddRule(IPC_NTSETINFO_RENAME_TAG, &rename))) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Right now we insert two rules, to be evaluated before any user supplied rule: | 
|  | // - go to the broker if the path doesn't look like the paths that we push on | 
|  | //    the policy (namely \??\something). | 
|  | // - go to the broker if it looks like this is a short-name path. | 
|  | // | 
|  | // It is possible to add a rule to go to the broker in any case; it would look | 
|  | // something like: | 
|  | //    rule = new PolicyRule(ASK_BROKER); | 
|  | //    rule->AddNumberMatch(IF_NOT, FileName::BROKER, TRUE, AND); | 
|  | //    policy->AddRule(service, rule); | 
|  | bool FileSystemPolicy::SetInitialRules(LowLevelPolicy* policy) { | 
|  | PolicyRule format(ASK_BROKER); | 
|  | PolicyRule short_name(ASK_BROKER); | 
|  |  | 
|  | bool rv = format.AddNumberMatch(IF_NOT, FileName::BROKER, TRUE, AND); | 
|  | rv &= format.AddStringMatch(IF_NOT, FileName::NAME, L"\\/?/?\\*", | 
|  | CASE_SENSITIVE); | 
|  |  | 
|  | rv &= short_name.AddNumberMatch(IF_NOT, FileName::BROKER, TRUE, AND); | 
|  | rv &= short_name.AddStringMatch(IF, FileName::NAME, L"*~*", CASE_SENSITIVE); | 
|  |  | 
|  | if (!rv || !policy->AddRule(IPC_NTCREATEFILE_TAG, &format)) | 
|  | return false; | 
|  |  | 
|  | if (!policy->AddRule(IPC_NTCREATEFILE_TAG, &short_name)) | 
|  | return false; | 
|  |  | 
|  | if (!policy->AddRule(IPC_NTOPENFILE_TAG, &format)) | 
|  | return false; | 
|  |  | 
|  | if (!policy->AddRule(IPC_NTOPENFILE_TAG, &short_name)) | 
|  | return false; | 
|  |  | 
|  | if (!policy->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG, &format)) | 
|  | return false; | 
|  |  | 
|  | if (!policy->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG, &short_name)) | 
|  | return false; | 
|  |  | 
|  | if (!policy->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG, &format)) | 
|  | return false; | 
|  |  | 
|  | if (!policy->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG, &short_name)) | 
|  | return false; | 
|  |  | 
|  | if (!policy->AddRule(IPC_NTSETINFO_RENAME_TAG, &format)) | 
|  | return false; | 
|  |  | 
|  | if (!policy->AddRule(IPC_NTSETINFO_RENAME_TAG, &short_name)) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool FileSystemPolicy::CreateFileAction(EvalResult eval_result, | 
|  | const ClientInfo& client_info, | 
|  | const std::wstring &file, | 
|  | uint32 attributes, | 
|  | uint32 desired_access, | 
|  | uint32 file_attributes, | 
|  | uint32 share_access, | 
|  | uint32 create_disposition, | 
|  | uint32 create_options, | 
|  | HANDLE *handle, | 
|  | NTSTATUS* nt_status, | 
|  | ULONG_PTR *io_information) { | 
|  | // The only action supported is ASK_BROKER which means create the requested | 
|  | // file as specified. | 
|  | if (ASK_BROKER != eval_result) { | 
|  | *nt_status = STATUS_ACCESS_DENIED; | 
|  | return false; | 
|  | } | 
|  | IO_STATUS_BLOCK io_block = {0}; | 
|  | UNICODE_STRING uni_name = {0}; | 
|  | OBJECT_ATTRIBUTES obj_attributes = {0}; | 
|  | InitObjectAttribs(file, attributes, NULL, &obj_attributes, &uni_name); | 
|  | *nt_status = NtCreateFileInTarget(handle, desired_access, &obj_attributes, | 
|  | &io_block, file_attributes, share_access, | 
|  | create_disposition, create_options, NULL, | 
|  | 0, client_info.process); | 
|  |  | 
|  | *io_information = io_block.Information; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool FileSystemPolicy::OpenFileAction(EvalResult eval_result, | 
|  | const ClientInfo& client_info, | 
|  | const std::wstring &file, | 
|  | uint32 attributes, | 
|  | uint32 desired_access, | 
|  | uint32 share_access, | 
|  | uint32 open_options, | 
|  | HANDLE *handle, | 
|  | NTSTATUS* nt_status, | 
|  | ULONG_PTR *io_information) { | 
|  | // The only action supported is ASK_BROKER which means open the requested | 
|  | // file as specified. | 
|  | if (ASK_BROKER != eval_result) { | 
|  | *nt_status = STATUS_ACCESS_DENIED; | 
|  | return true; | 
|  | } | 
|  | // An NtOpen is equivalent to an NtCreate with FileAttributes = 0 and | 
|  | // CreateDisposition = FILE_OPEN. | 
|  | IO_STATUS_BLOCK io_block = {0}; | 
|  | UNICODE_STRING uni_name = {0}; | 
|  | OBJECT_ATTRIBUTES obj_attributes = {0}; | 
|  | InitObjectAttribs(file, attributes, NULL, &obj_attributes, &uni_name); | 
|  | *nt_status = NtCreateFileInTarget(handle, desired_access, &obj_attributes, | 
|  | &io_block, 0, share_access, FILE_OPEN, | 
|  | open_options, NULL, 0, | 
|  | client_info.process); | 
|  |  | 
|  | *io_information = io_block.Information; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool FileSystemPolicy::QueryAttributesFileAction( | 
|  | EvalResult eval_result, | 
|  | const ClientInfo& client_info, | 
|  | const std::wstring &file, | 
|  | uint32 attributes, | 
|  | FILE_BASIC_INFORMATION* file_info, | 
|  | NTSTATUS* nt_status) { | 
|  | // The only action supported is ASK_BROKER which means query the requested | 
|  | // file as specified. | 
|  | if (ASK_BROKER != eval_result) { | 
|  | *nt_status = STATUS_ACCESS_DENIED; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | NtQueryAttributesFileFunction NtQueryAttributesFile = NULL; | 
|  | ResolveNTFunctionPtr("NtQueryAttributesFile", &NtQueryAttributesFile); | 
|  |  | 
|  | UNICODE_STRING uni_name = {0}; | 
|  | OBJECT_ATTRIBUTES obj_attributes = {0}; | 
|  | InitObjectAttribs(file, attributes, NULL, &obj_attributes, &uni_name); | 
|  | *nt_status = NtQueryAttributesFile(&obj_attributes, file_info); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool FileSystemPolicy::QueryFullAttributesFileAction( | 
|  | EvalResult eval_result, | 
|  | const ClientInfo& client_info, | 
|  | const std::wstring &file, | 
|  | uint32 attributes, | 
|  | FILE_NETWORK_OPEN_INFORMATION* file_info, | 
|  | NTSTATUS* nt_status) { | 
|  | // The only action supported is ASK_BROKER which means query the requested | 
|  | // file as specified. | 
|  | if (ASK_BROKER != eval_result) { | 
|  | *nt_status = STATUS_ACCESS_DENIED; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | NtQueryFullAttributesFileFunction NtQueryFullAttributesFile = NULL; | 
|  | ResolveNTFunctionPtr("NtQueryFullAttributesFile", &NtQueryFullAttributesFile); | 
|  |  | 
|  | UNICODE_STRING uni_name = {0}; | 
|  | OBJECT_ATTRIBUTES obj_attributes = {0}; | 
|  | InitObjectAttribs(file, attributes, NULL, &obj_attributes, &uni_name); | 
|  | *nt_status = NtQueryFullAttributesFile(&obj_attributes, file_info); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool FileSystemPolicy::SetInformationFileAction( | 
|  | EvalResult eval_result, const ClientInfo& client_info, | 
|  | HANDLE target_file_handle, void* file_info, uint32 length, | 
|  | uint32 info_class, IO_STATUS_BLOCK* io_block, | 
|  | NTSTATUS* nt_status) { | 
|  | // The only action supported is ASK_BROKER which means open the requested | 
|  | // file as specified. | 
|  | if (ASK_BROKER != eval_result) { | 
|  | *nt_status = STATUS_ACCESS_DENIED; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | NtSetInformationFileFunction NtSetInformationFile = NULL; | 
|  | ResolveNTFunctionPtr("NtSetInformationFile", &NtSetInformationFile); | 
|  |  | 
|  | HANDLE local_handle = NULL; | 
|  | if (!::DuplicateHandle(client_info.process, target_file_handle, | 
|  | ::GetCurrentProcess(), &local_handle, 0, FALSE, | 
|  | DUPLICATE_SAME_ACCESS)) { | 
|  | *nt_status = STATUS_ACCESS_DENIED; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | base::win::ScopedHandle handle(local_handle); | 
|  |  | 
|  | FILE_INFORMATION_CLASS file_info_class = | 
|  | static_cast<FILE_INFORMATION_CLASS>(info_class); | 
|  | *nt_status = NtSetInformationFile(local_handle, io_block, file_info, length, | 
|  | file_info_class); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool PreProcessName(const std::wstring& path, std::wstring* new_path) { | 
|  | ConvertToLongPath(path, new_path); | 
|  |  | 
|  | bool reparsed = false; | 
|  | if (ERROR_SUCCESS != IsReparsePoint(*new_path, &reparsed)) | 
|  | return false; | 
|  |  | 
|  | // We can't process reparsed file. | 
|  | return !reparsed; | 
|  | } | 
|  |  | 
|  | }  // namespace sandbox |