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