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// For information about interceptions as a whole see
6// http://dev.chromium.org/developers/design-documents/sandbox .
7
8#include <set>
9
10#include "sandbox/win/src/interception.h"
11
12#include "base/logging.h"
13#include "base/memory/scoped_ptr.h"
14#include "base/strings/string16.h"
15#include "base/win/pe_image.h"
16#include "base/win/windows_version.h"
17#include "sandbox/win/src/interception_internal.h"
18#include "sandbox/win/src/interceptors.h"
19#include "sandbox/win/src/sandbox.h"
20#include "sandbox/win/src/service_resolver.h"
21#include "sandbox/win/src/target_interceptions.h"
22#include "sandbox/win/src/target_process.h"
23#include "sandbox/win/src/wow64.h"
24
25namespace {
26
27const char kMapViewOfSectionName[] = "NtMapViewOfSection";
28const char kUnmapViewOfSectionName[] = "NtUnmapViewOfSection";
29
30// Standard allocation granularity and page size for Windows.
31const size_t kAllocGranularity = 65536;
32const size_t kPageSize = 4096;
33
34// Find a random offset within 64k and aligned to ceil(log2(size)).
35size_t GetGranularAlignedRandomOffset(size_t size) {
36  CHECK_LE(size, kAllocGranularity);
37  unsigned int offset;
38
39  do {
40    rand_s(&offset);
41    offset &= (kAllocGranularity - 1);
42  } while (offset > (kAllocGranularity - size));
43
44  // Find an alignment between 64 and the page size (4096).
45  size_t align_size = kPageSize;
46  for (size_t new_size = align_size / 2;  new_size >= size; new_size /= 2) {
47    align_size = new_size;
48  }
49  return offset & ~(align_size - 1);
50}
51
52}  // namespace
53
54namespace sandbox {
55
56SANDBOX_INTERCEPT SharedMemory* g_interceptions;
57
58// Table of the unpatched functions that we intercept. Mapped from the parent.
59SANDBOX_INTERCEPT OriginalFunctions g_originals = { NULL };
60
61// Magic constant that identifies that this function is not to be patched.
62const char kUnloadDLLDummyFunction[] = "@";
63
64InterceptionManager::InterceptionManager(TargetProcess* child_process,
65                                         bool relaxed)
66    : child_(child_process), names_used_(false), relaxed_(relaxed) {
67  child_->AddRef();
68}
69InterceptionManager::~InterceptionManager() {
70  child_->Release();
71}
72
73bool InterceptionManager::AddToPatchedFunctions(
74    const wchar_t* dll_name, const char* function_name,
75    InterceptionType interception_type, const void* replacement_code_address,
76    InterceptorId id) {
77  InterceptionData function;
78  function.type = interception_type;
79  function.id = id;
80  function.dll = dll_name;
81  function.function = function_name;
82  function.interceptor_address = replacement_code_address;
83
84  interceptions_.push_back(function);
85  return true;
86}
87
88bool InterceptionManager::AddToPatchedFunctions(
89    const wchar_t* dll_name, const char* function_name,
90    InterceptionType interception_type, const char* replacement_function_name,
91    InterceptorId id) {
92  InterceptionData function;
93  function.type = interception_type;
94  function.id = id;
95  function.dll = dll_name;
96  function.function = function_name;
97  function.interceptor = replacement_function_name;
98  function.interceptor_address = NULL;
99
100  interceptions_.push_back(function);
101  names_used_ = true;
102  return true;
103}
104
105bool InterceptionManager::AddToUnloadModules(const wchar_t* dll_name) {
106  InterceptionData module_to_unload;
107  module_to_unload.type = INTERCEPTION_UNLOAD_MODULE;
108  module_to_unload.dll = dll_name;
109  // The next two are dummy values that make the structures regular, instead
110  // of having special cases. They should not be used.
111  module_to_unload.function = kUnloadDLLDummyFunction;
112  module_to_unload.interceptor_address = reinterpret_cast<void*>(1);
113
114  interceptions_.push_back(module_to_unload);
115  return true;
116}
117
118bool InterceptionManager::InitializeInterceptions() {
119  if (interceptions_.empty())
120    return true;  // Nothing to do here
121
122  size_t buffer_bytes = GetBufferSize();
123  scoped_ptr<char[]> local_buffer(new char[buffer_bytes]);
124
125  if (!SetupConfigBuffer(local_buffer.get(), buffer_bytes))
126    return false;
127
128  void* remote_buffer;
129  if (!CopyDataToChild(local_buffer.get(), buffer_bytes, &remote_buffer))
130    return false;
131
132  bool hot_patch_needed = (0 != buffer_bytes);
133  if (!PatchNtdll(hot_patch_needed))
134    return false;
135
136  g_interceptions = reinterpret_cast<SharedMemory*>(remote_buffer);
137  ResultCode rc = child_->TransferVariable("g_interceptions",
138                                           &g_interceptions,
139                                           sizeof(g_interceptions));
140  return (SBOX_ALL_OK == rc);
141}
142
143size_t InterceptionManager::GetBufferSize() const {
144  std::set<base::string16> dlls;
145  size_t buffer_bytes = 0;
146
147  std::list<InterceptionData>::const_iterator it = interceptions_.begin();
148  for (; it != interceptions_.end(); ++it) {
149    // skip interceptions that are performed from the parent
150    if (!IsInterceptionPerformedByChild(*it))
151      continue;
152
153    if (!dlls.count(it->dll)) {
154      // NULL terminate the dll name on the structure
155      size_t dll_name_bytes = (it->dll.size() + 1) * sizeof(wchar_t);
156
157      // include the dll related size
158      buffer_bytes += RoundUpToMultiple(offsetof(DllPatchInfo, dll_name) +
159                                            dll_name_bytes, sizeof(size_t));
160      dlls.insert(it->dll);
161    }
162
163    // we have to NULL terminate the strings on the structure
164    size_t strings_chars = it->function.size() + it->interceptor.size() + 2;
165
166    // a new FunctionInfo is required per function
167    size_t record_bytes = offsetof(FunctionInfo, function) + strings_chars;
168    record_bytes = RoundUpToMultiple(record_bytes, sizeof(size_t));
169    buffer_bytes += record_bytes;
170  }
171
172  if (0 != buffer_bytes)
173    // add the part of SharedMemory that we have not counted yet
174    buffer_bytes += offsetof(SharedMemory, dll_list);
175
176  return buffer_bytes;
177}
178
179// Basically, walk the list of interceptions moving them to the config buffer,
180// but keeping together all interceptions that belong to the same dll.
181// The config buffer is a local buffer, not the one allocated on the child.
182bool InterceptionManager::SetupConfigBuffer(void* buffer, size_t buffer_bytes) {
183  if (0 == buffer_bytes)
184    return true;
185
186  DCHECK(buffer_bytes > sizeof(SharedMemory));
187
188  SharedMemory* shared_memory = reinterpret_cast<SharedMemory*>(buffer);
189  DllPatchInfo* dll_info = shared_memory->dll_list;
190  int num_dlls = 0;
191
192  shared_memory->interceptor_base = names_used_ ? child_->MainModule() : NULL;
193
194  buffer_bytes -= offsetof(SharedMemory, dll_list);
195  buffer = dll_info;
196
197  std::list<InterceptionData>::iterator it = interceptions_.begin();
198  for (; it != interceptions_.end();) {
199    // skip interceptions that are performed from the parent
200    if (!IsInterceptionPerformedByChild(*it)) {
201      ++it;
202      continue;
203    }
204
205    const base::string16 dll = it->dll;
206    if (!SetupDllInfo(*it, &buffer, &buffer_bytes))
207      return false;
208
209    // walk the interceptions from this point, saving the ones that are
210    // performed on this dll, and removing the entry from the list.
211    // advance the iterator before removing the element from the list
212    std::list<InterceptionData>::iterator rest = it;
213    for (; rest != interceptions_.end();) {
214      if (rest->dll == dll) {
215        if (!SetupInterceptionInfo(*rest, &buffer, &buffer_bytes, dll_info))
216          return false;
217        if (it == rest)
218          ++it;
219        rest = interceptions_.erase(rest);
220      } else {
221        ++rest;
222      }
223    }
224    dll_info = reinterpret_cast<DllPatchInfo*>(buffer);
225    ++num_dlls;
226  }
227
228  shared_memory->num_intercepted_dlls = num_dlls;
229  return true;
230}
231
232// Fills up just the part that depends on the dll, not the info that depends on
233// the actual interception.
234bool InterceptionManager::SetupDllInfo(const InterceptionData& data,
235                                       void** buffer,
236                                       size_t* buffer_bytes) const {
237  DCHECK(buffer_bytes);
238  DCHECK(buffer);
239  DCHECK(*buffer);
240
241  DllPatchInfo* dll_info = reinterpret_cast<DllPatchInfo*>(*buffer);
242
243  // the strings have to be zero terminated
244  size_t required = offsetof(DllPatchInfo, dll_name) +
245                    (data.dll.size() + 1) * sizeof(wchar_t);
246  required = RoundUpToMultiple(required, sizeof(size_t));
247  if (*buffer_bytes < required)
248    return false;
249
250  *buffer_bytes -= required;
251  *buffer = reinterpret_cast<char*>(*buffer) + required;
252
253  // set up the dll info to be what we know about it at this time
254  dll_info->unload_module = (data.type == INTERCEPTION_UNLOAD_MODULE);
255  dll_info->record_bytes = required;
256  dll_info->offset_to_functions = required;
257  dll_info->num_functions = 0;
258  data.dll._Copy_s(dll_info->dll_name, data.dll.size(), data.dll.size());
259  dll_info->dll_name[data.dll.size()] = L'\0';
260
261  return true;
262}
263
264bool InterceptionManager::SetupInterceptionInfo(const InterceptionData& data,
265                                                void** buffer,
266                                                size_t* buffer_bytes,
267                                                DllPatchInfo* dll_info) const {
268  DCHECK(buffer_bytes);
269  DCHECK(buffer);
270  DCHECK(*buffer);
271
272  if ((dll_info->unload_module) &&
273      (data.function != kUnloadDLLDummyFunction)) {
274    // Can't specify a dll for both patch and unload.
275    NOTREACHED();
276  }
277
278  FunctionInfo* function = reinterpret_cast<FunctionInfo*>(*buffer);
279
280  size_t name_bytes = data.function.size();
281  size_t interceptor_bytes = data.interceptor.size();
282
283  // the strings at the end of the structure are zero terminated
284  size_t required = offsetof(FunctionInfo, function) +
285                    name_bytes + interceptor_bytes + 2;
286  required = RoundUpToMultiple(required, sizeof(size_t));
287  if (*buffer_bytes < required)
288    return false;
289
290  // update the caller's values
291  *buffer_bytes -= required;
292  *buffer = reinterpret_cast<char*>(*buffer) + required;
293
294  function->record_bytes = required;
295  function->type = data.type;
296  function->id = data.id;
297  function->interceptor_address = data.interceptor_address;
298  char* names = function->function;
299
300  data.function._Copy_s(names, name_bytes, name_bytes);
301  names += name_bytes;
302  *names++ = '\0';
303
304  // interceptor follows the function_name
305  data.interceptor._Copy_s(names, interceptor_bytes, interceptor_bytes);
306  names += interceptor_bytes;
307  *names++ = '\0';
308
309  // update the dll table
310  dll_info->num_functions++;
311  dll_info->record_bytes += required;
312
313  return true;
314}
315
316bool InterceptionManager::CopyDataToChild(const void* local_buffer,
317                                          size_t buffer_bytes,
318                                          void** remote_buffer) const {
319  DCHECK(NULL != remote_buffer);
320  if (0 == buffer_bytes) {
321    *remote_buffer = NULL;
322    return true;
323  }
324
325  HANDLE child = child_->Process();
326
327  // Allocate memory on the target process without specifying the address
328  void* remote_data = ::VirtualAllocEx(child, NULL, buffer_bytes,
329                                       MEM_COMMIT, PAGE_READWRITE);
330  if (NULL == remote_data)
331    return false;
332
333  SIZE_T bytes_written;
334  BOOL success = ::WriteProcessMemory(child, remote_data, local_buffer,
335                                      buffer_bytes, &bytes_written);
336  if (FALSE == success || bytes_written != buffer_bytes) {
337    ::VirtualFreeEx(child, remote_data, 0, MEM_RELEASE);
338    return false;
339  }
340
341  *remote_buffer = remote_data;
342
343  return true;
344}
345
346// Only return true if the child should be able to perform this interception.
347bool InterceptionManager::IsInterceptionPerformedByChild(
348    const InterceptionData& data) const {
349  if (INTERCEPTION_INVALID == data.type)
350    return false;
351
352  if (INTERCEPTION_SERVICE_CALL == data.type)
353    return false;
354
355  if (data.type >= INTERCEPTION_LAST)
356    return false;
357
358  base::string16 ntdll(kNtdllName);
359  if (ntdll == data.dll)
360    return false;  // ntdll has to be intercepted from the parent
361
362  return true;
363}
364
365bool InterceptionManager::PatchNtdll(bool hot_patch_needed) {
366  // Maybe there is nothing to do
367  if (!hot_patch_needed && interceptions_.empty())
368    return true;
369
370  if (hot_patch_needed) {
371#if SANDBOX_EXPORTS
372    // Make sure the functions are not excluded by the linker.
373#if defined(_WIN64)
374    #pragma comment(linker, "/include:TargetNtMapViewOfSection64")
375    #pragma comment(linker, "/include:TargetNtUnmapViewOfSection64")
376#else
377    #pragma comment(linker, "/include:_TargetNtMapViewOfSection@44")
378    #pragma comment(linker, "/include:_TargetNtUnmapViewOfSection@12")
379#endif
380#endif
381    ADD_NT_INTERCEPTION(NtMapViewOfSection, MAP_VIEW_OF_SECTION_ID, 44);
382    ADD_NT_INTERCEPTION(NtUnmapViewOfSection, UNMAP_VIEW_OF_SECTION_ID, 12);
383  }
384
385  // Reserve a full 64k memory range in the child process.
386  HANDLE child = child_->Process();
387  BYTE* thunk_base = reinterpret_cast<BYTE*>(
388                         ::VirtualAllocEx(child, NULL, kAllocGranularity,
389                                          MEM_RESERVE, PAGE_NOACCESS));
390
391  // Find an aligned, random location within the reserved range.
392  size_t thunk_bytes = interceptions_.size() * sizeof(ThunkData) +
393                       sizeof(DllInterceptionData);
394  size_t thunk_offset = GetGranularAlignedRandomOffset(thunk_bytes);
395
396  // Split the base and offset along page boundaries.
397  thunk_base += thunk_offset & ~(kPageSize - 1);
398  thunk_offset &= kPageSize - 1;
399
400  // Make an aligned, padded allocation, and move the pointer to our chunk.
401  size_t thunk_bytes_padded = (thunk_bytes + kPageSize - 1) & ~(kPageSize - 1);
402  thunk_base = reinterpret_cast<BYTE*>(
403                   ::VirtualAllocEx(child, thunk_base, thunk_bytes_padded,
404                                    MEM_COMMIT, PAGE_EXECUTE_READWRITE));
405  CHECK(thunk_base);  // If this fails we'd crash anyway on an invalid access.
406  DllInterceptionData* thunks = reinterpret_cast<DllInterceptionData*>(
407                                    thunk_base + thunk_offset);
408
409  DllInterceptionData dll_data;
410  dll_data.data_bytes = thunk_bytes;
411  dll_data.num_thunks = 0;
412  dll_data.used_bytes = offsetof(DllInterceptionData, thunks);
413
414  // Reset all helpers for a new child.
415  memset(g_originals, 0, sizeof(g_originals));
416
417  // this should write all the individual thunks to the child's memory
418  if (!PatchClientFunctions(thunks, thunk_bytes, &dll_data))
419    return false;
420
421  // and now write the first part of the table to the child's memory
422  SIZE_T written;
423  bool ok = FALSE != ::WriteProcessMemory(child, thunks, &dll_data,
424                                          offsetof(DllInterceptionData, thunks),
425                                          &written);
426
427  if (!ok || (offsetof(DllInterceptionData, thunks) != written))
428    return false;
429
430  // Attempt to protect all the thunks, but ignore failure
431  DWORD old_protection;
432  ::VirtualProtectEx(child, thunks, thunk_bytes,
433                     PAGE_EXECUTE_READ, &old_protection);
434
435  ResultCode ret = child_->TransferVariable("g_originals", g_originals,
436                                            sizeof(g_originals));
437  return (SBOX_ALL_OK == ret);
438}
439
440bool InterceptionManager::PatchClientFunctions(DllInterceptionData* thunks,
441                                               size_t thunk_bytes,
442                                               DllInterceptionData* dll_data) {
443  DCHECK(NULL != thunks);
444  DCHECK(NULL != dll_data);
445
446  HMODULE ntdll_base = ::GetModuleHandle(kNtdllName);
447  if (!ntdll_base)
448    return false;
449
450  base::win::PEImage ntdll_image(ntdll_base);
451
452  // Bypass purify's interception.
453  wchar_t* loader_get = reinterpret_cast<wchar_t*>(
454                            ntdll_image.GetProcAddress("LdrGetDllHandle"));
455  if (loader_get) {
456    if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
457                               GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
458                           loader_get, &ntdll_base))
459      return false;
460  }
461
462  if (base::win::GetVersion() <= base::win::VERSION_VISTA) {
463    Wow64 WowHelper(child_, ntdll_base);
464    if (!WowHelper.WaitForNtdll())
465      return false;
466  }
467
468  char* interceptor_base = NULL;
469
470#if SANDBOX_EXPORTS
471  interceptor_base = reinterpret_cast<char*>(child_->MainModule());
472  HMODULE local_interceptor = ::LoadLibrary(child_->Name());
473#endif
474
475  ServiceResolverThunk* thunk;
476#if defined(_WIN64)
477  thunk = new ServiceResolverThunk(child_->Process(), relaxed_);
478#else
479  base::win::OSInfo* os_info = base::win::OSInfo::GetInstance();
480  if (os_info->wow64_status() == base::win::OSInfo::WOW64_ENABLED) {
481    if (os_info->version() >= base::win::VERSION_WIN8)
482      thunk = new Wow64W8ResolverThunk(child_->Process(), relaxed_);
483    else
484      thunk = new Wow64ResolverThunk(child_->Process(), relaxed_);
485  } else if (os_info->version() >= base::win::VERSION_WIN8) {
486    thunk = new Win8ResolverThunk(child_->Process(), relaxed_);
487  } else {
488    thunk = new ServiceResolverThunk(child_->Process(), relaxed_);
489  }
490#endif
491
492  std::list<InterceptionData>::iterator it = interceptions_.begin();
493  for (; it != interceptions_.end(); ++it) {
494    const base::string16 ntdll(kNtdllName);
495    if (it->dll != ntdll)
496      break;
497
498    if (INTERCEPTION_SERVICE_CALL != it->type)
499      break;
500
501#if SANDBOX_EXPORTS
502    // We may be trying to patch by function name.
503    if (NULL == it->interceptor_address) {
504      const char* address;
505      NTSTATUS ret = thunk->ResolveInterceptor(local_interceptor,
506                                               it->interceptor.c_str(),
507                                               reinterpret_cast<const void**>(
508                                               &address));
509      if (!NT_SUCCESS(ret))
510        break;
511
512      // Translate the local address to an address on the child.
513      it->interceptor_address = interceptor_base + (address -
514                                    reinterpret_cast<char*>(local_interceptor));
515    }
516#endif
517    NTSTATUS ret = thunk->Setup(ntdll_base,
518                                interceptor_base,
519                                it->function.c_str(),
520                                it->interceptor.c_str(),
521                                it->interceptor_address,
522                                &thunks->thunks[dll_data->num_thunks],
523                                thunk_bytes - dll_data->used_bytes,
524                                NULL);
525    if (!NT_SUCCESS(ret))
526      break;
527
528    DCHECK(!g_originals[it->id]);
529    g_originals[it->id] = &thunks->thunks[dll_data->num_thunks];
530
531    dll_data->num_thunks++;
532    dll_data->used_bytes += sizeof(ThunkData);
533  }
534
535  delete(thunk);
536
537#if SANDBOX_EXPORTS
538  if (NULL != local_interceptor)
539    ::FreeLibrary(local_interceptor);
540#endif
541
542  if (it != interceptions_.end())
543    return false;
544
545  return true;
546}
547
548}  // namespace sandbox
549