| // Copyright 2012 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/handle_closer_agent.h" |
| |
| #include <Windows.h> |
| #include <winnls.h> |
| |
| #include <stddef.h> |
| |
| #include "base/check.h" |
| #include "base/containers/span.h" |
| #include "base/logging.h" |
| #include "base/win/static_constants.h" |
| #include "base/win/win_util.h" |
| #include "sandbox/win/src/heap_helper.h" |
| #include "sandbox/win/src/sandbox_nt_util.h" |
| #include "sandbox/win/src/win_utils.h" |
| |
| namespace sandbox { |
| namespace { |
| // Handle Types. |
| constexpr wchar_t kFile[] = L"File"; |
| constexpr wchar_t kSection[] = L"Section"; |
| constexpr wchar_t kALPCPort[] = L"ALPC Port"; |
| // Full paths of closeable handles. |
| constexpr wchar_t kDeviceApi[] = L"\\Device\\DeviceApi"; |
| constexpr wchar_t kDeviceKsecDD[] = L"\\Device\\KsecDD"; |
| // Leaf name of Section to close (matches with EndsWith). |
| constexpr wchar_t kWindowsShellGlobalCounters[] = |
| L"\\windows_shell_global_counters"; |
| |
| // Used by EnumSystemLocales for warming up. |
| static BOOL CALLBACK EnumLocalesProcEx(LPWSTR lpLocaleString, |
| DWORD dwFlags, |
| LPARAM lParam) { |
| return TRUE; |
| } |
| |
| // Additional warmup done just when CSRSS is being disconnected. |
| bool CsrssDisconnectWarmup() { |
| return ::EnumSystemLocalesEx(EnumLocalesProcEx, LOCALE_WINDOWS, 0, 0); |
| } |
| |
| // Cleans up this process if CSRSS will be disconnected, as this disconnection |
| // is not supported Windows behavior. |
| // Currently, this step requires closing a heap that this shared with csrss.exe. |
| // Closing the ALPC Port handle to csrss.exe leaves this heap in an invalid |
| // state. This causes problems if anyone enumerates the heap. |
| bool CsrssDisconnectCleanup() { |
| HANDLE csr_port_heap = FindCsrPortHeap(); |
| if (!csr_port_heap) { |
| DLOG(ERROR) << "Failed to find CSR Port heap handle"; |
| return false; |
| } |
| ::HeapDestroy(csr_port_heap); |
| return true; |
| } |
| } // namespace |
| |
| // Memory buffer mapped from the parent, with our configuration. |
| SANDBOX_INTERCEPT HandleCloserConfig g_handle_closer_info{}; |
| |
| bool HandleCloserAgent::NeedsHandlesClosed() { |
| return g_handle_closer_info.handle_closer_enabled; |
| } |
| |
| HandleCloserAgent::HandleCloserAgent() |
| : config_(g_handle_closer_info), |
| is_csrss_connected_(true), |
| dummy_handle_(::CreateEvent(nullptr, false, false, nullptr)) {} |
| |
| HandleCloserAgent::~HandleCloserAgent() {} |
| |
| // Attempts to stuff |closed_handle| with a duplicated handle for a dummy Event |
| // with no access. This should allow the handle to be closed, to avoid |
| // generating EXCEPTION_INVALID_HANDLE on shutdown, but nothing else. For now |
| // the only supported type is Event or File. |
| bool HandleCloserAgent::AttemptToStuffHandleSlot(HANDLE closed_handle) { |
| if (!dummy_handle_.is_valid()) { |
| return false; |
| } |
| |
| // This should never happen, as dummy_handle_ is created before closing |
| // to_stuff. |
| DCHECK(dummy_handle_.get() != closed_handle); |
| |
| std::vector<HANDLE> to_close; |
| |
| const DWORD original_proc_num = ::GetCurrentProcessorNumber(); |
| DWORD proc_num = original_proc_num; |
| DWORD_PTR original_affinity_mask = |
| ::SetThreadAffinityMask(GetCurrentThread(), DWORD_PTR{1} << proc_num); |
| bool found_handle = false; |
| BOOL result = FALSE; |
| |
| // There is per-processor based free list of handles entries. The free handle |
| // from current processor's freelist is preferred for reusing, so cycling |
| // through all possible processors to find closed_handle. |
| // Start searching from current processor which covers usual cases. |
| |
| do { |
| DWORD_PTR current_mask = DWORD_PTR{1} << proc_num; |
| |
| if (original_affinity_mask & current_mask) { |
| if (proc_num != original_proc_num) { |
| ::SetThreadAffinityMask(::GetCurrentThread(), current_mask); |
| } |
| |
| HANDLE dup_dummy = nullptr; |
| size_t count = 16; |
| |
| do { |
| result = |
| ::DuplicateHandle(::GetCurrentProcess(), dummy_handle_.get(), |
| ::GetCurrentProcess(), &dup_dummy, 0, false, 0); |
| if (!result) { |
| break; |
| } |
| if (dup_dummy != closed_handle) { |
| to_close.push_back(dup_dummy); |
| } else { |
| found_handle = true; |
| } |
| } while (count-- && reinterpret_cast<uintptr_t>(dup_dummy) < |
| reinterpret_cast<uintptr_t>(closed_handle)); |
| } |
| |
| proc_num++; |
| if (proc_num == sizeof(DWORD_PTR) * 8) { |
| proc_num = 0; |
| } |
| if (proc_num == original_proc_num) { |
| break; |
| } |
| } while (result && !found_handle); |
| |
| SetThreadAffinityMask(::GetCurrentThread(), original_affinity_mask); |
| |
| for (HANDLE h : to_close) { |
| ::CloseHandle(h); |
| } |
| |
| return found_handle; |
| } |
| |
| bool HandleCloserAgent::CloseHandles() { |
| CHECK(config_.handle_closer_enabled); |
| |
| // Skip closing these handles when Application Verifier is in use in order to |
| // avoid invalid-handle exceptions. |
| if (base::win::IsAppVerifierLoaded()) { |
| return true; |
| } |
| |
| DWORD handle_count; |
| if (!::GetProcessHandleCount(::GetCurrentProcess(), &handle_count)) { |
| return false; |
| } |
| |
| // The system call will return only handles up to the buffer size so add a |
| // margin of error of an additional 1000 handles. |
| std::vector<char> buffer((handle_count + 1000) * sizeof(uint32_t)); |
| DWORD return_length; |
| NTSTATUS status = GetNtExports()->QueryInformationProcess( |
| ::GetCurrentProcess(), ProcessHandleTable, buffer.data(), |
| static_cast<ULONG>(buffer.size()), &return_length); |
| |
| if (!NT_SUCCESS(status)) { |
| ::SetLastError(GetLastErrorFromNtStatus(status)); |
| return false; |
| } |
| DCHECK(buffer.size() >= return_length); |
| DCHECK((buffer.size() % sizeof(uint32_t)) == 0); |
| |
| base::span<uint32_t> handle_values(reinterpret_cast<uint32_t*>(buffer.data()), |
| return_length / sizeof(uint32_t)); |
| for (uint32_t handle_value : handle_values) { |
| HANDLE handle = base::win::Uint32ToHandle(handle_value); |
| auto type_name = GetTypeNameFromHandle(handle); |
| if (type_name) { |
| MaybeCloseHandle(type_name.value(), handle); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool HandleCloserAgent::MaybeCloseHandle(std::wstring& type_name, |
| HANDLE handle) { |
| bool bClose = false; |
| // Determine if the handle should be closed. Avoid matching the type unless |
| // we are configured to close it. |
| if (config_.section_windows_global_shell_counters && type_name == kSection) { |
| auto path = GetPathFromHandle(handle); |
| if (path && base::EndsWith(path.value(), kWindowsShellGlobalCounters)) { |
| bClose = true; |
| } |
| } else if ((config_.file_device_api || config_.file_ksecdd) && |
| type_name == kFile) { |
| auto path = GetPathFromHandle(handle); |
| if (path && config_.file_device_api && path.value() == kDeviceApi) { |
| bClose = true; |
| } |
| if (path && config_.file_ksecdd && path.value() == kDeviceKsecDD) { |
| bClose = true; |
| } |
| } else if (config_.disconnect_csrss && type_name == kALPCPort) { |
| // Do csrss extra warmup & heap closing but only once. Normally there is |
| // only one ALPC port to close. |
| if (is_csrss_connected_) { |
| if (!CsrssDisconnectWarmup() || !CsrssDisconnectCleanup()) { |
| return false; |
| } |
| is_csrss_connected_ = false; |
| } |
| bClose = true; |
| } |
| // Implement the close. |
| if (bClose) { |
| // If we can't unprotect or close the handle we should keep going. |
| if (!::SetHandleInformation(handle, HANDLE_FLAG_PROTECT_FROM_CLOSE, 0)) { |
| return false; |
| } |
| if (!::CloseHandle(handle)) { |
| return false; |
| } |
| // Attempt to stuff this handle with a new dummy Event. |
| if (type_name == kFile) { |
| // Note: could also stuff "Event" but do not support closing any Events |
| // at the moment. |
| return AttemptToStuffHandleSlot(handle); |
| } |
| } |
| return true; |
| } |
| |
| } // namespace sandbox |