| // Copyright 2011 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "sandbox/win/src/filesystem_policy.h" |
| |
| #include <windows.h> |
| |
| #include <ntstatus.h> |
| #include <stdint.h> |
| #include <winternl.h> |
| |
| #include <string> |
| |
| #include "base/notreached.h" |
| #include "base/win/scoped_handle.h" |
| #include "sandbox/win/src/internal_types.h" |
| #include "sandbox/win/src/ipc_tags.h" |
| #include "sandbox/win/src/nt_internals.h" |
| #include "sandbox/win/src/policy_engine_opcodes.h" |
| #include "sandbox/win/src/policy_params.h" |
| #include "sandbox/win/src/sandbox_nt_util.h" |
| #include "sandbox/win/src/sandbox_types.h" |
| #include "sandbox/win/src/win_utils.h" |
| |
| namespace sandbox { |
| |
| namespace { |
| |
| struct ObjectAttribs : public OBJECT_ATTRIBUTES { |
| UNICODE_STRING uni_name; |
| SECURITY_QUALITY_OF_SERVICE security_qos; |
| ObjectAttribs(const std::wstring& name, ULONG attributes) { |
| ::RtlInitUnicodeString(&uni_name, name.c_str()); |
| InitializeObjectAttributes(this, &uni_name, attributes, nullptr, nullptr); |
| if (IsPipe(name)) { |
| security_qos.Length = sizeof(security_qos); |
| security_qos.ImpersonationLevel = SecurityAnonymous; |
| // Set dynamic tracking to not capture the broker's token |
| security_qos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; |
| security_qos.EffectiveOnly = TRUE; |
| SecurityQualityOfService = &security_qos; |
| } |
| } |
| }; |
| |
| 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_length, |
| HANDLE target_process) { |
| HANDLE local_handle = INVALID_HANDLE_VALUE; |
| NTSTATUS status = GetNtExports()->CreateFile( |
| &local_handle, desired_access, obj_attributes, io_status_block, nullptr, |
| file_attributes, share_access, create_disposition, create_options, |
| ea_buffer, ea_length); |
| if (!NT_SUCCESS(status)) { |
| return status; |
| } |
| |
| if (!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)) { |
| return STATUS_ACCESS_DENIED; |
| } |
| return STATUS_SUCCESS; |
| } |
| |
| } // namespace. |
| |
| bool FileSystemPolicy::GenerateRules(const wchar_t* name, |
| FileSemantics semantics, |
| LowLevelPolicy* policy) { |
| std::wstring mod_name(name); |
| if (mod_name.empty()) { |
| return false; |
| } |
| |
| bool is_pipe = IsPipe(mod_name); |
| if (!PreProcessName(&mod_name)) { |
| // The path to be added might contain a reparse point. |
| NOTREACHED(); |
| return false; |
| } |
| |
| // 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 (_wcsnicmp(mod_name.c_str(), kNTDevicePrefix, kNTDevicePrefixLen)) { |
| mod_name = FixNTPrefixForMatch(mod_name); |
| name = mod_name.c_str(); |
| } |
| |
| EvalResult result = ASK_BROKER; |
| |
| // Rules added for both read-only and write scenarios. |
| PolicyRule create(result); |
| PolicyRule open(result); |
| PolicyRule query(result); |
| PolicyRule query_full(result); |
| |
| if (semantics == FileSemantics::kAllowReadonly) { |
| // 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); |
| open.AddNumberMatch(IF, OpenFile::OPENONLY, true, EQUAL); |
| create.AddNumberMatch(IF_NOT, OpenFile::ACCESS, restricted_flags, AND); |
| create.AddNumberMatch(IF, OpenFile::OPENONLY, true, EQUAL); |
| } |
| |
| if (!create.AddStringMatch(IF, OpenFile::NAME, name) || |
| !policy->AddRule(IpcTag::NTCREATEFILE, &create)) { |
| return false; |
| } |
| |
| if (!open.AddStringMatch(IF, OpenFile::NAME, name) || |
| !policy->AddRule(IpcTag::NTOPENFILE, &open)) { |
| return false; |
| } |
| |
| if (!query.AddStringMatch(IF, OpenFile::NAME, name) || |
| !policy->AddRule(IpcTag::NTQUERYATTRIBUTESFILE, &query)) { |
| return false; |
| } |
| |
| if (!query_full.AddStringMatch(IF, OpenFile::NAME, name) || |
| !policy->AddRule(IpcTag::NTQUERYFULLATTRIBUTESFILE, &query_full)) { |
| return false; |
| } |
| |
| // Rename is not allowed for read-only and does not make sense for pipes. |
| if (semantics == FileSemantics::kAllowAny && !is_pipe) { |
| PolicyRule rename(result); |
| if (!rename.AddStringMatch(IF, OpenFile::NAME, name) || |
| !policy->AddRule(IpcTag::NTSETINFO_RENAME, &rename)) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool FileSystemPolicy::CreateFileAction(EvalResult eval_result, |
| const ClientInfo& client_info, |
| const std::wstring& file, |
| uint32_t attributes, |
| uint32_t desired_access, |
| uint32_t file_attributes, |
| uint32_t share_access, |
| uint32_t create_disposition, |
| uint32_t create_options, |
| HANDLE* handle, |
| NTSTATUS* nt_status, |
| ULONG_PTR* io_information) { |
| *handle = nullptr; |
| // 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 = {}; |
| ObjectAttribs obj_attributes(file, attributes); |
| *nt_status = |
| NtCreateFileInTarget(handle, desired_access, &obj_attributes, &io_block, |
| file_attributes, share_access, create_disposition, |
| create_options, nullptr, 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_t attributes, |
| uint32_t desired_access, |
| uint32_t share_access, |
| uint32_t open_options, |
| HANDLE* handle, |
| NTSTATUS* nt_status, |
| ULONG_PTR* io_information) { |
| *handle = nullptr; |
| // 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 false; |
| } |
| // An NtOpen is equivalent to an NtCreate with FileAttributes = 0 and |
| // CreateDisposition = FILE_OPEN. |
| IO_STATUS_BLOCK io_block = {}; |
| ObjectAttribs obj_attributes(file, attributes); |
| |
| *nt_status = NtCreateFileInTarget( |
| handle, desired_access, &obj_attributes, &io_block, 0, share_access, |
| FILE_OPEN, open_options, nullptr, 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_t 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 false; |
| } |
| |
| ObjectAttribs obj_attributes(file, attributes); |
| *nt_status = GetNtExports()->QueryAttributesFile(&obj_attributes, file_info); |
| |
| return true; |
| } |
| |
| bool FileSystemPolicy::QueryFullAttributesFileAction( |
| EvalResult eval_result, |
| const ClientInfo& client_info, |
| const std::wstring& file, |
| uint32_t 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 false; |
| } |
| ObjectAttribs obj_attributes(file, attributes); |
| *nt_status = |
| GetNtExports()->QueryFullAttributesFile(&obj_attributes, file_info); |
| |
| return true; |
| } |
| |
| bool FileSystemPolicy::SetInformationFileAction(EvalResult eval_result, |
| const ClientInfo& client_info, |
| HANDLE target_file_handle, |
| void* file_info, |
| uint32_t length, |
| uint32_t 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 false; |
| } |
| |
| HANDLE local_handle = nullptr; |
| if (!::DuplicateHandle(client_info.process, target_file_handle, |
| ::GetCurrentProcess(), &local_handle, 0, false, |
| DUPLICATE_SAME_ACCESS)) { |
| *nt_status = STATUS_ACCESS_DENIED; |
| return false; |
| } |
| |
| base::win::ScopedHandle handle(local_handle); |
| |
| FILE_INFORMATION_CLASS file_info_class = |
| static_cast<FILE_INFORMATION_CLASS>(info_class); |
| *nt_status = GetNtExports()->SetInformationFile( |
| local_handle, io_block, file_info, length, file_info_class); |
| |
| return true; |
| } |
| |
| bool PreProcessName(std::wstring* path) { |
| ConvertToLongPath(path); |
| |
| if (ERROR_NOT_A_REPARSE_POINT == IsReparsePoint(*path)) |
| return true; |
| |
| // We can't process a reparsed file. |
| return false; |
| } |
| |
| std::wstring FixNTPrefixForMatch(const std::wstring& name) { |
| std::wstring mod_name = name; |
| |
| // NT prefix escaped for rule matcher |
| const wchar_t kNTPrefixEscaped[] = L"\\/?/?\\"; |
| const int kNTPrefixEscapedLen = std::size(kNTPrefixEscaped) - 1; |
| |
| if (0 != mod_name.compare(0, kNTPrefixLen, kNTPrefix)) { |
| if (0 != mod_name.compare(0, kNTPrefixEscapedLen, kNTPrefixEscaped)) { |
| // TODO(nsylvain): Find a better way to do name resolution. Right now we |
| // take the name and we expand it. |
| mod_name.insert(0, kNTPrefixEscaped); |
| } |
| } else { |
| // Start of name matches NT prefix, replace with escaped format |
| // Fixes bug: 334882 |
| mod_name.replace(0, kNTPrefixLen, kNTPrefixEscaped); |
| } |
| |
| return mod_name; |
| } |
| |
| } // namespace sandbox |