1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "sandbox/win/src/wow64.h"
6
7#include <sstream>
8
9#include "base/logging.h"
10#include "base/memory/scoped_ptr.h"
11#include "base/win/scoped_process_information.h"
12#include "base/win/windows_version.h"
13#include "sandbox/win/src/target_process.h"
14
15namespace {
16
17// Holds the information needed for the interception of NtMapViewOfSection on
18// 64 bits.
19// Warning: do not modify this definition without changing also the code on the
20// 64 bit helper process.
21struct PatchInfo32 {
22  HANDLE dll_load;  // Event to signal the broker.
23  ULONG pad1;
24  HANDLE continue_load;  // Event to wait for the broker.
25  ULONG pad2;
26  HANDLE section;  // First argument of the call.
27  ULONG pad3;
28  void* orig_MapViewOfSection;
29  ULONG original_high;
30  void* signal_and_wait;
31  ULONG pad4;
32  void* patch_location;
33  ULONG patch_high;
34};
35
36// Size of the 64 bit service entry.
37const SIZE_T kServiceEntry64Size = 0x10;
38
39// Removes the interception of ntdll64.
40bool Restore64Code(HANDLE child, PatchInfo32* patch_info) {
41  PatchInfo32 local_patch_info;
42  SIZE_T actual;
43  if (!::ReadProcessMemory(child, patch_info, &local_patch_info,
44                           sizeof(local_patch_info), &actual))
45    return false;
46  if (sizeof(local_patch_info) != actual)
47    return false;
48
49  if (local_patch_info.original_high)
50    return false;
51  if (local_patch_info.patch_high)
52    return false;
53
54  char buffer[kServiceEntry64Size];
55
56  if (!::ReadProcessMemory(child, local_patch_info.orig_MapViewOfSection,
57                           &buffer, kServiceEntry64Size, &actual))
58    return false;
59  if (kServiceEntry64Size != actual)
60    return false;
61
62  if (!::WriteProcessMemory(child, local_patch_info.patch_location, &buffer,
63                            kServiceEntry64Size, &actual))
64    return false;
65  if (kServiceEntry64Size != actual)
66    return false;
67  return true;
68}
69
70typedef BOOL (WINAPI* IsWow64ProcessFunction)(HANDLE process, BOOL* wow64);
71
72}  // namespace
73
74namespace sandbox {
75
76Wow64::~Wow64() {
77  if (dll_load_)
78    ::CloseHandle(dll_load_);
79
80  if (continue_load_)
81    ::CloseHandle(continue_load_);
82}
83
84// The basic idea is to allocate one page of memory on the child, and initialize
85// the first part of it with our version of PatchInfo32. Then launch the helper
86// process passing it that address on the child. The helper process will patch
87// the 64 bit version of NtMapViewOfFile, and the interception will signal the
88// first event on the buffer. We'll be waiting on that event and after the 32
89// bit version of ntdll is loaded, we'll remove the interception and return to
90// our caller.
91bool Wow64::WaitForNtdll() {
92  if (base::win::OSInfo::GetInstance()->wow64_status() !=
93      base::win::OSInfo::WOW64_ENABLED)
94    return true;
95
96  const size_t page_size = 4096;
97
98  // Create some default manual reset un-named events, not signaled.
99  dll_load_ = ::CreateEvent(NULL, TRUE, FALSE, NULL);
100  continue_load_ = ::CreateEvent(NULL, TRUE, FALSE, NULL);
101  HANDLE current_process = ::GetCurrentProcess();
102  HANDLE remote_load, remote_continue;
103  DWORD access = EVENT_MODIFY_STATE | SYNCHRONIZE;
104  if (!::DuplicateHandle(current_process, dll_load_, child_->Process(),
105                         &remote_load, access, FALSE, 0))
106    return false;
107  if (!::DuplicateHandle(current_process, continue_load_, child_->Process(),
108                         &remote_continue, access, FALSE, 0))
109    return false;
110
111  void* buffer = ::VirtualAllocEx(child_->Process(), NULL, page_size,
112                                  MEM_COMMIT, PAGE_EXECUTE_READWRITE);
113  DCHECK(buffer);
114  if (!buffer)
115    return false;
116
117  PatchInfo32* patch_info = reinterpret_cast<PatchInfo32*>(buffer);
118  PatchInfo32 local_patch_info = {0};
119  local_patch_info.dll_load = remote_load;
120  local_patch_info.continue_load = remote_continue;
121  SIZE_T written;
122  if (!::WriteProcessMemory(child_->Process(), patch_info, &local_patch_info,
123                            offsetof(PatchInfo32, section), &written))
124    return false;
125  if (offsetof(PatchInfo32, section) != written)
126    return false;
127
128  if (!RunWowHelper(buffer))
129    return false;
130
131  // The child is intercepted on 64 bit, go on and wait for our event.
132  if (!DllMapped())
133    return false;
134
135  // The 32 bit version is available, cleanup the child.
136  return Restore64Code(child_->Process(), patch_info);
137}
138
139bool Wow64::RunWowHelper(void* buffer) {
140  COMPILE_ASSERT(sizeof(buffer) <= sizeof(DWORD), unsupported_64_bits);
141
142  // Get the path to the helper (beside the exe).
143  wchar_t prog_name[MAX_PATH];
144  GetModuleFileNameW(NULL, prog_name, MAX_PATH);
145  base::string16 path(prog_name);
146  size_t name_pos = path.find_last_of(L"\\");
147  if (base::string16::npos == name_pos)
148    return false;
149  path.resize(name_pos + 1);
150
151  std::basic_stringstream<base::char16> command;
152  command << std::hex << std::showbase << L"\"" << path <<
153               L"wow_helper.exe\" " << child_->ProcessId() << " " <<
154               bit_cast<ULONG>(buffer);
155
156  scoped_ptr<wchar_t, base::FreeDeleter>
157      writable_command(_wcsdup(command.str().c_str()));
158
159  STARTUPINFO startup_info = {0};
160  startup_info.cb = sizeof(startup_info);
161  PROCESS_INFORMATION temp_process_info = {};
162  if (!::CreateProcess(NULL, writable_command.get(), NULL, NULL, FALSE, 0, NULL,
163                       NULL, &startup_info, &temp_process_info))
164    return false;
165  base::win::ScopedProcessInformation process_info(temp_process_info);
166
167  DWORD reason = ::WaitForSingleObject(process_info.process_handle(), INFINITE);
168
169  DWORD code;
170  bool ok =
171      ::GetExitCodeProcess(process_info.process_handle(), &code) ? true : false;
172
173  if (WAIT_TIMEOUT == reason)
174    return false;
175
176  return ok && (0 == code);
177}
178
179// First we must wake up the child, then wait for dll loads on the child until
180// the one we care is loaded; at that point we must suspend the child again.
181bool Wow64::DllMapped() {
182  if (1 != ::ResumeThread(child_->MainThread())) {
183    NOTREACHED();
184    return false;
185  }
186
187  for (;;) {
188    DWORD reason = ::WaitForSingleObject(dll_load_, INFINITE);
189    if (WAIT_TIMEOUT == reason || WAIT_ABANDONED == reason)
190      return false;
191
192    if (!::ResetEvent(dll_load_))
193      return false;
194
195    bool found = NtdllPresent();
196    if (found) {
197      if (::SuspendThread(child_->MainThread()))
198        return false;
199    }
200
201    if (!::SetEvent(continue_load_))
202      return false;
203
204    if (found)
205      return true;
206  }
207}
208
209bool Wow64::NtdllPresent() {
210  const size_t kBufferSize = 512;
211  char buffer[kBufferSize];
212  SIZE_T read;
213  if (!::ReadProcessMemory(child_->Process(), ntdll_, &buffer, kBufferSize,
214                           &read))
215    return false;
216  if (kBufferSize != read)
217    return false;
218  return true;
219}
220
221}  // namespace sandbox
222