1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/security/sandbox/win/src/Wow64.cc Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,219 @@ 1.4 +// Copyright (c) 2012 The Chromium Authors. All rights reserved. 1.5 +// Use of this source code is governed by a BSD-style license that can be 1.6 +// found in the LICENSE file. 1.7 + 1.8 +#include "sandbox/win/src/wow64.h" 1.9 + 1.10 +#include <sstream> 1.11 + 1.12 +#include "base/logging.h" 1.13 +#include "base/memory/scoped_ptr.h" 1.14 +#include "base/win/scoped_process_information.h" 1.15 +#include "base/win/windows_version.h" 1.16 +#include "sandbox/win/src/target_process.h" 1.17 + 1.18 +namespace { 1.19 + 1.20 +// Holds the information needed for the interception of NtMapViewOfSection on 1.21 +// 64 bits. 1.22 +// Warning: do not modify this definition without changing also the code on the 1.23 +// 64 bit helper process. 1.24 +struct PatchInfo32 { 1.25 + HANDLE dll_load; // Event to signal the broker. 1.26 + ULONG pad1; 1.27 + HANDLE continue_load; // Event to wait for the broker. 1.28 + ULONG pad2; 1.29 + HANDLE section; // First argument of the call. 1.30 + ULONG pad3; 1.31 + void* orig_MapViewOfSection; 1.32 + ULONG original_high; 1.33 + void* signal_and_wait; 1.34 + ULONG pad4; 1.35 + void* patch_location; 1.36 + ULONG patch_high; 1.37 +}; 1.38 + 1.39 +// Size of the 64 bit service entry. 1.40 +const SIZE_T kServiceEntry64Size = 0x10; 1.41 + 1.42 +// Removes the interception of ntdll64. 1.43 +bool Restore64Code(HANDLE child, PatchInfo32* patch_info) { 1.44 + PatchInfo32 local_patch_info; 1.45 + SIZE_T actual; 1.46 + if (!::ReadProcessMemory(child, patch_info, &local_patch_info, 1.47 + sizeof(local_patch_info), &actual)) 1.48 + return false; 1.49 + if (sizeof(local_patch_info) != actual) 1.50 + return false; 1.51 + 1.52 + if (local_patch_info.original_high) 1.53 + return false; 1.54 + if (local_patch_info.patch_high) 1.55 + return false; 1.56 + 1.57 + char buffer[kServiceEntry64Size]; 1.58 + 1.59 + if (!::ReadProcessMemory(child, local_patch_info.orig_MapViewOfSection, 1.60 + &buffer, kServiceEntry64Size, &actual)) 1.61 + return false; 1.62 + if (kServiceEntry64Size != actual) 1.63 + return false; 1.64 + 1.65 + if (!::WriteProcessMemory(child, local_patch_info.patch_location, &buffer, 1.66 + kServiceEntry64Size, &actual)) 1.67 + return false; 1.68 + if (kServiceEntry64Size != actual) 1.69 + return false; 1.70 + return true; 1.71 +} 1.72 + 1.73 +typedef BOOL (WINAPI* IsWow64ProcessFunction)(HANDLE process, BOOL* wow64); 1.74 + 1.75 +} // namespace 1.76 + 1.77 +namespace sandbox { 1.78 + 1.79 +Wow64::~Wow64() { 1.80 + if (dll_load_) 1.81 + ::CloseHandle(dll_load_); 1.82 + 1.83 + if (continue_load_) 1.84 + ::CloseHandle(continue_load_); 1.85 +} 1.86 + 1.87 +// The basic idea is to allocate one page of memory on the child, and initialize 1.88 +// the first part of it with our version of PatchInfo32. Then launch the helper 1.89 +// process passing it that address on the child. The helper process will patch 1.90 +// the 64 bit version of NtMapViewOfFile, and the interception will signal the 1.91 +// first event on the buffer. We'll be waiting on that event and after the 32 1.92 +// bit version of ntdll is loaded, we'll remove the interception and return to 1.93 +// our caller. 1.94 +bool Wow64::WaitForNtdll() { 1.95 + if (base::win::OSInfo::GetInstance()->wow64_status() != 1.96 + base::win::OSInfo::WOW64_ENABLED) 1.97 + return true; 1.98 + 1.99 + const size_t page_size = 4096; 1.100 + 1.101 + // Create some default manual reset un-named events, not signaled. 1.102 + dll_load_ = ::CreateEvent(NULL, TRUE, FALSE, NULL); 1.103 + continue_load_ = ::CreateEvent(NULL, TRUE, FALSE, NULL); 1.104 + HANDLE current_process = ::GetCurrentProcess(); 1.105 + HANDLE remote_load, remote_continue; 1.106 + DWORD access = EVENT_MODIFY_STATE | SYNCHRONIZE; 1.107 + if (!::DuplicateHandle(current_process, dll_load_, child_->Process(), 1.108 + &remote_load, access, FALSE, 0)) 1.109 + return false; 1.110 + if (!::DuplicateHandle(current_process, continue_load_, child_->Process(), 1.111 + &remote_continue, access, FALSE, 0)) 1.112 + return false; 1.113 + 1.114 + void* buffer = ::VirtualAllocEx(child_->Process(), NULL, page_size, 1.115 + MEM_COMMIT, PAGE_EXECUTE_READWRITE); 1.116 + DCHECK(buffer); 1.117 + if (!buffer) 1.118 + return false; 1.119 + 1.120 + PatchInfo32* patch_info = reinterpret_cast<PatchInfo32*>(buffer); 1.121 + PatchInfo32 local_patch_info = {0}; 1.122 + local_patch_info.dll_load = remote_load; 1.123 + local_patch_info.continue_load = remote_continue; 1.124 + SIZE_T written; 1.125 + if (!::WriteProcessMemory(child_->Process(), patch_info, &local_patch_info, 1.126 + offsetof(PatchInfo32, section), &written)) 1.127 + return false; 1.128 + if (offsetof(PatchInfo32, section) != written) 1.129 + return false; 1.130 + 1.131 + if (!RunWowHelper(buffer)) 1.132 + return false; 1.133 + 1.134 + // The child is intercepted on 64 bit, go on and wait for our event. 1.135 + if (!DllMapped()) 1.136 + return false; 1.137 + 1.138 + // The 32 bit version is available, cleanup the child. 1.139 + return Restore64Code(child_->Process(), patch_info); 1.140 +} 1.141 + 1.142 +bool Wow64::RunWowHelper(void* buffer) { 1.143 + COMPILE_ASSERT(sizeof(buffer) <= sizeof(DWORD), unsupported_64_bits); 1.144 + 1.145 + // Get the path to the helper (beside the exe). 1.146 + wchar_t prog_name[MAX_PATH]; 1.147 + GetModuleFileNameW(NULL, prog_name, MAX_PATH); 1.148 + std::wstring path(prog_name); 1.149 + size_t name_pos = path.find_last_of(L"\\"); 1.150 + if (std::wstring::npos == name_pos) 1.151 + return false; 1.152 + path.resize(name_pos + 1); 1.153 + 1.154 + std::wstringstream command; 1.155 + command << std::hex << std::showbase << L"\"" << path << 1.156 + L"wow_helper.exe\" " << child_->ProcessId() << " " << 1.157 + bit_cast<ULONG>(buffer); 1.158 + 1.159 + scoped_ptr_malloc<wchar_t> writable_command(_wcsdup(command.str().c_str())); 1.160 + 1.161 + STARTUPINFO startup_info = {0}; 1.162 + startup_info.cb = sizeof(startup_info); 1.163 + base::win::ScopedProcessInformation process_info; 1.164 + if (!::CreateProcess(NULL, writable_command.get(), NULL, NULL, FALSE, 0, NULL, 1.165 + NULL, &startup_info, process_info.Receive())) 1.166 + return false; 1.167 + 1.168 + DWORD reason = ::WaitForSingleObject(process_info.process_handle(), INFINITE); 1.169 + 1.170 + DWORD code; 1.171 + bool ok = 1.172 + ::GetExitCodeProcess(process_info.process_handle(), &code) ? true : false; 1.173 + 1.174 + if (WAIT_TIMEOUT == reason) 1.175 + return false; 1.176 + 1.177 + return ok && (0 == code); 1.178 +} 1.179 + 1.180 +// First we must wake up the child, then wait for dll loads on the child until 1.181 +// the one we care is loaded; at that point we must suspend the child again. 1.182 +bool Wow64::DllMapped() { 1.183 + if (1 != ::ResumeThread(child_->MainThread())) { 1.184 + NOTREACHED(); 1.185 + return false; 1.186 + } 1.187 + 1.188 + for (;;) { 1.189 + DWORD reason = ::WaitForSingleObject(dll_load_, INFINITE); 1.190 + if (WAIT_TIMEOUT == reason || WAIT_ABANDONED == reason) 1.191 + return false; 1.192 + 1.193 + if (!::ResetEvent(dll_load_)) 1.194 + return false; 1.195 + 1.196 + bool found = NtdllPresent(); 1.197 + if (found) { 1.198 + if (::SuspendThread(child_->MainThread())) 1.199 + return false; 1.200 + } 1.201 + 1.202 + if (!::SetEvent(continue_load_)) 1.203 + return false; 1.204 + 1.205 + if (found) 1.206 + return true; 1.207 + } 1.208 +} 1.209 + 1.210 +bool Wow64::NtdllPresent() { 1.211 + const size_t kBufferSize = 512; 1.212 + char buffer[kBufferSize]; 1.213 + SIZE_T read; 1.214 + if (!::ReadProcessMemory(child_->Process(), ntdll_, &buffer, kBufferSize, 1.215 + &read)) 1.216 + return false; 1.217 + if (kBufferSize != read) 1.218 + return false; 1.219 + return true; 1.220 +} 1.221 + 1.222 +} // namespace sandbox