// 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 "sandbox/src/handle_closer.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/win/windows_version.h" #include "sandbox/src/interceptors.h" #include "sandbox/src/internal_types.h" #include "sandbox/src/nt_internals.h" #include "sandbox/src/process_thread_interception.h" #include "sandbox/src/win_utils.h" namespace { template T RoundUpToWordSize(T v) { if (size_t mod = v % sizeof(size_t)) v += sizeof(size_t) - mod; return v; } template T* RoundUpToWordSize(T* v) { return reinterpret_cast(RoundUpToWordSize(reinterpret_cast(v))); } } // namespace namespace sandbox { // Memory buffer mapped from the parent, with the list of handles. SANDBOX_INTERCEPT HandleCloserInfo* g_handles_to_close; HandleCloser::HandleCloser() {} ResultCode HandleCloser::AddHandle(const char16* handle_type, const char16* handle_name) { if (!handle_type) return SBOX_ERROR_BAD_PARAMS; HandleMap::iterator names = handles_to_close_.find(handle_type); if (names == handles_to_close_.end()) { // We have no entries for this type. std::pair result = handles_to_close_.insert( HandleMap::value_type(handle_type, HandleMap::mapped_type())); names = result.first; if (handle_name) names->second.insert(handle_name); } else if (!handle_name) { // Now we need to close all handles of this type. names->second.clear(); } else if (!names->second.empty()) { // Add another name for this type. names->second.insert(handle_name); } // If we're already closing all handles of type then we're done. return SBOX_ALL_OK; } size_t HandleCloser::GetBufferSize() { size_t bytes_total = offsetof(HandleCloserInfo, handle_entries); for (HandleMap::iterator i = handles_to_close_.begin(); i != handles_to_close_.end(); ++i) { size_t bytes_entry = offsetof(HandleListEntry, handle_type) + (i->first.size() + 1) * sizeof(char16); for (HandleMap::mapped_type::iterator j = i->second.begin(); j != i->second.end(); ++j) { bytes_entry += ((*j).size() + 1) * sizeof(char16); } // Round up to the nearest multiple of word size. bytes_entry = RoundUpToWordSize(bytes_entry); bytes_total += bytes_entry; } return bytes_total; } bool HandleCloser::InitializeTargetHandles(TargetProcess* target) { // Do nothing on an empty list (global pointer already initialized to NULL). if (handles_to_close_.empty()) return true; size_t bytes_needed = GetBufferSize(); scoped_array local_buffer( new size_t[bytes_needed / sizeof(size_t)]); if (!SetupHandleList(local_buffer.get(), bytes_needed)) return false; HANDLE child = target->Process(); // Allocate memory in the target process without specifying the address void* remote_data = ::VirtualAllocEx(child, NULL, bytes_needed, MEM_COMMIT, PAGE_READWRITE); if (NULL == remote_data) return false; // Copy the handle buffer over. SIZE_T bytes_written; BOOL result = ::WriteProcessMemory(child, remote_data, local_buffer.get(), bytes_needed, &bytes_written); if (!result || bytes_written != bytes_needed) { ::VirtualFreeEx(child, remote_data, 0, MEM_RELEASE); return false; } g_handles_to_close = reinterpret_cast(remote_data); ResultCode rc = target->TransferVariable("g_handles_to_close", &g_handles_to_close, sizeof(g_handles_to_close)); return (SBOX_ALL_OK == rc); } bool HandleCloser::SetupHandleList(void* buffer, size_t buffer_bytes) { ::ZeroMemory(buffer, buffer_bytes); HandleCloserInfo* handle_info = reinterpret_cast(buffer); handle_info->record_bytes = buffer_bytes; handle_info->num_handle_types = handles_to_close_.size(); char16* output = reinterpret_cast(&handle_info->handle_entries[0]); char16* end = reinterpret_cast( reinterpret_cast(buffer) + buffer_bytes); for (HandleMap::iterator i = handles_to_close_.begin(); i != handles_to_close_.end(); ++i) { if (output >= end) return false; HandleListEntry* list_entry = reinterpret_cast(output); output = &list_entry->handle_type[0]; // Copy the typename and set the offset and count. i->first._Copy_s(output, i->first.size(), i->first.size()); *(output += i->first.size()) = L'\0'; output++; list_entry->offset_to_names = reinterpret_cast(output) - reinterpret_cast(list_entry); list_entry->name_count = i->second.size(); // Copy the handle names. for (HandleMap::mapped_type::iterator j = i->second.begin(); j != i->second.end(); ++j) { output = std::copy((*j).begin(), (*j).end(), output) + 1; } // Round up to the nearest multiple of sizeof(size_t). output = RoundUpToWordSize(output); list_entry->record_bytes = reinterpret_cast(output) - reinterpret_cast(list_entry); } DCHECK_EQ(reinterpret_cast(output), reinterpret_cast(end)); return output <= end; } bool HandleCloser::SetupHandleInterceptions(InterceptionManager* manager) { // We need to intercept CreateThread if we're closing ALPC port clients. HandleMap::iterator names = handles_to_close_.find(L"ALPC Port"); if (base::win::GetVersion() >= base::win::VERSION_VISTA && names != handles_to_close_.end() && (names->second.empty() || names->second.size() == 0)) { if (!INTERCEPT_EAT(manager, kKerneldllName, CreateThread, CREATE_THREAD_ID, 28)) { return false; } if (!INTERCEPT_EAT(manager, kKerneldllName, GetUserDefaultLCID, GET_USER_DEFAULT_LCID_ID, 4)) { return false; } return true; } return true; } bool GetHandleName(HANDLE handle, string16* handle_name) { static NtQueryObject QueryObject = NULL; if (!QueryObject) ResolveNTFunctionPtr("NtQueryObject", &QueryObject); ULONG size = MAX_PATH; scoped_ptr name; NTSTATUS result; do { name.reset(reinterpret_cast(new BYTE[size])); result = QueryObject(handle, ObjectNameInformation, name.get(), size, &size); } while (result == STATUS_INFO_LENGTH_MISMATCH || result == STATUS_BUFFER_OVERFLOW); if (NT_SUCCESS(result) && name->Buffer && name->Length) handle_name->assign(name->Buffer, name->Length / sizeof(wchar_t)); else handle_name->clear(); return NT_SUCCESS(result); } } // namespace sandbox