michael@0: // Copyright (c) 2012 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 "sandbox/win/src/wow64.h" michael@0: michael@0: #include michael@0: michael@0: #include "base/logging.h" michael@0: #include "base/memory/scoped_ptr.h" michael@0: #include "base/win/scoped_process_information.h" michael@0: #include "base/win/windows_version.h" michael@0: #include "sandbox/win/src/target_process.h" michael@0: michael@0: namespace { michael@0: michael@0: // Holds the information needed for the interception of NtMapViewOfSection on michael@0: // 64 bits. michael@0: // Warning: do not modify this definition without changing also the code on the michael@0: // 64 bit helper process. michael@0: struct PatchInfo32 { michael@0: HANDLE dll_load; // Event to signal the broker. michael@0: ULONG pad1; michael@0: HANDLE continue_load; // Event to wait for the broker. michael@0: ULONG pad2; michael@0: HANDLE section; // First argument of the call. michael@0: ULONG pad3; michael@0: void* orig_MapViewOfSection; michael@0: ULONG original_high; michael@0: void* signal_and_wait; michael@0: ULONG pad4; michael@0: void* patch_location; michael@0: ULONG patch_high; michael@0: }; michael@0: michael@0: // Size of the 64 bit service entry. michael@0: const SIZE_T kServiceEntry64Size = 0x10; michael@0: michael@0: // Removes the interception of ntdll64. michael@0: bool Restore64Code(HANDLE child, PatchInfo32* patch_info) { michael@0: PatchInfo32 local_patch_info; michael@0: SIZE_T actual; michael@0: if (!::ReadProcessMemory(child, patch_info, &local_patch_info, michael@0: sizeof(local_patch_info), &actual)) michael@0: return false; michael@0: if (sizeof(local_patch_info) != actual) michael@0: return false; michael@0: michael@0: if (local_patch_info.original_high) michael@0: return false; michael@0: if (local_patch_info.patch_high) michael@0: return false; michael@0: michael@0: char buffer[kServiceEntry64Size]; michael@0: michael@0: if (!::ReadProcessMemory(child, local_patch_info.orig_MapViewOfSection, michael@0: &buffer, kServiceEntry64Size, &actual)) michael@0: return false; michael@0: if (kServiceEntry64Size != actual) michael@0: return false; michael@0: michael@0: if (!::WriteProcessMemory(child, local_patch_info.patch_location, &buffer, michael@0: kServiceEntry64Size, &actual)) michael@0: return false; michael@0: if (kServiceEntry64Size != actual) michael@0: return false; michael@0: return true; michael@0: } michael@0: michael@0: typedef BOOL (WINAPI* IsWow64ProcessFunction)(HANDLE process, BOOL* wow64); michael@0: michael@0: } // namespace michael@0: michael@0: namespace sandbox { michael@0: michael@0: Wow64::~Wow64() { michael@0: if (dll_load_) michael@0: ::CloseHandle(dll_load_); michael@0: michael@0: if (continue_load_) michael@0: ::CloseHandle(continue_load_); michael@0: } michael@0: michael@0: // The basic idea is to allocate one page of memory on the child, and initialize michael@0: // the first part of it with our version of PatchInfo32. Then launch the helper michael@0: // process passing it that address on the child. The helper process will patch michael@0: // the 64 bit version of NtMapViewOfFile, and the interception will signal the michael@0: // first event on the buffer. We'll be waiting on that event and after the 32 michael@0: // bit version of ntdll is loaded, we'll remove the interception and return to michael@0: // our caller. michael@0: bool Wow64::WaitForNtdll() { michael@0: if (base::win::OSInfo::GetInstance()->wow64_status() != michael@0: base::win::OSInfo::WOW64_ENABLED) michael@0: return true; michael@0: michael@0: const size_t page_size = 4096; michael@0: michael@0: // Create some default manual reset un-named events, not signaled. michael@0: dll_load_ = ::CreateEvent(NULL, TRUE, FALSE, NULL); michael@0: continue_load_ = ::CreateEvent(NULL, TRUE, FALSE, NULL); michael@0: HANDLE current_process = ::GetCurrentProcess(); michael@0: HANDLE remote_load, remote_continue; michael@0: DWORD access = EVENT_MODIFY_STATE | SYNCHRONIZE; michael@0: if (!::DuplicateHandle(current_process, dll_load_, child_->Process(), michael@0: &remote_load, access, FALSE, 0)) michael@0: return false; michael@0: if (!::DuplicateHandle(current_process, continue_load_, child_->Process(), michael@0: &remote_continue, access, FALSE, 0)) michael@0: return false; michael@0: michael@0: void* buffer = ::VirtualAllocEx(child_->Process(), NULL, page_size, michael@0: MEM_COMMIT, PAGE_EXECUTE_READWRITE); michael@0: DCHECK(buffer); michael@0: if (!buffer) michael@0: return false; michael@0: michael@0: PatchInfo32* patch_info = reinterpret_cast(buffer); michael@0: PatchInfo32 local_patch_info = {0}; michael@0: local_patch_info.dll_load = remote_load; michael@0: local_patch_info.continue_load = remote_continue; michael@0: SIZE_T written; michael@0: if (!::WriteProcessMemory(child_->Process(), patch_info, &local_patch_info, michael@0: offsetof(PatchInfo32, section), &written)) michael@0: return false; michael@0: if (offsetof(PatchInfo32, section) != written) michael@0: return false; michael@0: michael@0: if (!RunWowHelper(buffer)) michael@0: return false; michael@0: michael@0: // The child is intercepted on 64 bit, go on and wait for our event. michael@0: if (!DllMapped()) michael@0: return false; michael@0: michael@0: // The 32 bit version is available, cleanup the child. michael@0: return Restore64Code(child_->Process(), patch_info); michael@0: } michael@0: michael@0: bool Wow64::RunWowHelper(void* buffer) { michael@0: COMPILE_ASSERT(sizeof(buffer) <= sizeof(DWORD), unsupported_64_bits); michael@0: michael@0: // Get the path to the helper (beside the exe). michael@0: wchar_t prog_name[MAX_PATH]; michael@0: GetModuleFileNameW(NULL, prog_name, MAX_PATH); michael@0: std::wstring path(prog_name); michael@0: size_t name_pos = path.find_last_of(L"\\"); michael@0: if (std::wstring::npos == name_pos) michael@0: return false; michael@0: path.resize(name_pos + 1); michael@0: michael@0: std::wstringstream command; michael@0: command << std::hex << std::showbase << L"\"" << path << michael@0: L"wow_helper.exe\" " << child_->ProcessId() << " " << michael@0: bit_cast(buffer); michael@0: michael@0: scoped_ptr_malloc writable_command(_wcsdup(command.str().c_str())); michael@0: michael@0: STARTUPINFO startup_info = {0}; michael@0: startup_info.cb = sizeof(startup_info); michael@0: base::win::ScopedProcessInformation process_info; michael@0: if (!::CreateProcess(NULL, writable_command.get(), NULL, NULL, FALSE, 0, NULL, michael@0: NULL, &startup_info, process_info.Receive())) michael@0: return false; michael@0: michael@0: DWORD reason = ::WaitForSingleObject(process_info.process_handle(), INFINITE); michael@0: michael@0: DWORD code; michael@0: bool ok = michael@0: ::GetExitCodeProcess(process_info.process_handle(), &code) ? true : false; michael@0: michael@0: if (WAIT_TIMEOUT == reason) michael@0: return false; michael@0: michael@0: return ok && (0 == code); michael@0: } michael@0: michael@0: // First we must wake up the child, then wait for dll loads on the child until michael@0: // the one we care is loaded; at that point we must suspend the child again. michael@0: bool Wow64::DllMapped() { michael@0: if (1 != ::ResumeThread(child_->MainThread())) { michael@0: NOTREACHED(); michael@0: return false; michael@0: } michael@0: michael@0: for (;;) { michael@0: DWORD reason = ::WaitForSingleObject(dll_load_, INFINITE); michael@0: if (WAIT_TIMEOUT == reason || WAIT_ABANDONED == reason) michael@0: return false; michael@0: michael@0: if (!::ResetEvent(dll_load_)) michael@0: return false; michael@0: michael@0: bool found = NtdllPresent(); michael@0: if (found) { michael@0: if (::SuspendThread(child_->MainThread())) michael@0: return false; michael@0: } michael@0: michael@0: if (!::SetEvent(continue_load_)) michael@0: return false; michael@0: michael@0: if (found) michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: bool Wow64::NtdllPresent() { michael@0: const size_t kBufferSize = 512; michael@0: char buffer[kBufferSize]; michael@0: SIZE_T read; michael@0: if (!::ReadProcessMemory(child_->Process(), ntdll_, &buffer, kBufferSize, michael@0: &read)) michael@0: return false; michael@0: if (kBufferSize != read) michael@0: return false; michael@0: return true; michael@0: } michael@0: michael@0: } // namespace sandbox