Bug 1110760 - Import Chromium Sandbox wow_helper code. r=aklotz
authorBob Owen <bobowencode@gmail.com>
Fri, 19 Dec 2014 10:16:59 +0000
changeset 239313 2d03b2b604af335c57d8c54491e210b1b0d75758
parent 239312 18557b56adbeb090e628ef48261da6d98425bc8a
child 239314 7df8d663997106d269d07c31d535824cbc585c8d
push id7472
push userraliiev@mozilla.com
push dateMon, 12 Jan 2015 20:36:27 +0000
treeherdermozilla-aurora@300ca104f8fb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaklotz
bugs1110760
milestone37.0a1
Bug 1110760 - Import Chromium Sandbox wow_helper code. r=aklotz
security/sandbox/chromium/sandbox/win/wow_helper/service64_resolver.cc
security/sandbox/chromium/sandbox/win/wow_helper/service64_resolver.h
security/sandbox/chromium/sandbox/win/wow_helper/target_code.cc
security/sandbox/chromium/sandbox/win/wow_helper/target_code.h
security/sandbox/chromium/sandbox/win/wow_helper/wow_helper.cc
new file mode 100644
--- /dev/null
+++ b/security/sandbox/chromium/sandbox/win/wow_helper/service64_resolver.cc
@@ -0,0 +1,342 @@
+// 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/win/wow_helper/service64_resolver.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "sandbox/win/wow_helper/target_code.h"
+
+namespace {
+#pragma pack(push, 1)
+
+const BYTE kMovEax = 0xB8;
+const BYTE kMovEdx = 0xBA;
+const USHORT kCallPtrEdx = 0x12FF;
+const BYTE kRet = 0xC2;
+const BYTE kNop = 0x90;
+const USHORT kJmpEdx = 0xE2FF;
+const USHORT kXorEcx = 0xC933;
+const ULONG kLeaEdx = 0x0424548D;
+const ULONG kCallFs1 = 0xC015FF64;
+const ULONG kCallFs2Ret = 0xC2000000;
+const BYTE kPopEdx = 0x5A;
+const BYTE kPushEdx = 0x52;
+const BYTE kPush32 = 0x68;
+
+const ULONG kMmovR10EcxMovEax = 0xB8D18B4C;
+const USHORT kSyscall = 0x050F;
+const BYTE kRetNp = 0xC3;
+const BYTE kPad = 0x66;
+const USHORT kNop16 = 0x9066;
+const BYTE kRelJmp = 0xE9;
+
+const ULONG kXorRaxMovEax = 0xB8C03148;
+const ULONG kSaveRcx = 0x10488948;
+const ULONG kMovRcxRaxJmp = 0xE9C88B48;
+
+// Service code for 64 bit systems.
+struct ServiceEntry {
+  // this struct contains roughly the following code:
+  // mov     r10,rcx
+  // mov     eax,52h
+  // syscall
+  // ret
+  // xchg    ax,ax
+  // xchg    ax,ax
+
+  ULONG mov_r10_ecx_mov_eax;  // = 4C 8B D1 B8
+  ULONG service_id;
+  USHORT syscall;             // = 0F 05
+  BYTE ret;                   // = C3
+  BYTE pad;                   // = 66
+  USHORT xchg_ax_ax1;         // = 66 90
+  USHORT xchg_ax_ax2;         // = 66 90
+};
+
+struct Redirected {
+  // this struct contains roughly the following code:
+  // jmp    relative_32
+  // xchg   ax,ax       // 3 byte nop
+
+  Redirected() {
+    jmp = kRelJmp;
+    relative = 0;
+    pad = kPad;
+    xchg_ax_ax = kNop16;
+  };
+  BYTE jmp;             // = E9
+  ULONG relative;
+  BYTE pad;             // = 66
+  USHORT xchg_ax_ax;    // = 66 90
+};
+
+struct InternalThunk {
+  // this struct contains roughly the following code:
+  // xor rax,rax
+  // mov eax, 0x00080000              // Thunk storage.
+  // mov [rax]PatchInfo.service, rcx  // Save first argument.
+  // mov rcx, rax
+  // jmp relative_to_interceptor
+
+  InternalThunk() {
+    xor_rax_mov_eax = kXorRaxMovEax;
+    patch_info = 0;
+    save_rcx = kSaveRcx;
+    mov_rcx_rax_jmp = kMovRcxRaxJmp;
+    relative = 0;
+  };
+  ULONG xor_rax_mov_eax;  // = 48 31 C0 B8
+  ULONG patch_info;
+  ULONG save_rcx;         // = 48 89 48 10
+  ULONG mov_rcx_rax_jmp;  // = 48 8b c8 e9
+  ULONG relative;
+};
+
+struct ServiceFullThunk {
+  sandbox::PatchInfo patch_info;
+  ServiceEntry original;
+  InternalThunk internal_thunk;
+};
+
+#pragma pack(pop)
+
+// Simple utility function to write to a buffer on the child, if the memery has
+// write protection attributes.
+// Arguments:
+// child_process (in): process to write to.
+// address (out): memory position on the child to write to.
+// buffer (in): local buffer with the data to write .
+// length (in): number of bytes to write.
+// Returns true on success.
+bool WriteProtectedChildMemory(HANDLE child_process,
+                               void* address,
+                               const void* buffer,
+                               size_t length) {
+  // first, remove the protections
+  DWORD old_protection;
+  if (!::VirtualProtectEx(child_process, address, length,
+                          PAGE_WRITECOPY, &old_protection))
+    return false;
+
+  SIZE_T written;
+  bool ok = ::WriteProcessMemory(child_process, address, buffer, length,
+                                 &written) && (length == written);
+
+  // always attempt to restore the original protection
+  if (!::VirtualProtectEx(child_process, address, length,
+                          old_protection, &old_protection))
+    return false;
+
+  return ok;
+}
+
+// Get pointers to the functions that we need from ntdll.dll.
+NTSTATUS ResolveNtdll(sandbox::PatchInfo* patch_info) {
+  wchar_t* ntdll_name = L"ntdll.dll";
+  HMODULE ntdll = ::GetModuleHandle(ntdll_name);
+  if (!ntdll)
+    return STATUS_PROCEDURE_NOT_FOUND;
+
+  void* signal = ::GetProcAddress(ntdll, "NtSignalAndWaitForSingleObject");
+  if (!signal)
+    return STATUS_PROCEDURE_NOT_FOUND;
+
+  patch_info->signal_and_wait =
+      reinterpret_cast<NtSignalAndWaitForSingleObjectFunction>(signal);
+
+  return STATUS_SUCCESS;
+}
+
+};  // namespace
+
+namespace sandbox {
+
+NTSTATUS ResolverThunk::Init(const void* target_module,
+                             const void* interceptor_module,
+                             const char* target_name,
+                             const char* interceptor_name,
+                             const void* interceptor_entry_point,
+                             void* thunk_storage,
+                             size_t storage_bytes) {
+  if (NULL == thunk_storage || 0 == storage_bytes ||
+      NULL == target_module || NULL == target_name)
+    return STATUS_INVALID_PARAMETER;
+
+  if (storage_bytes < GetThunkSize())
+    return STATUS_BUFFER_TOO_SMALL;
+
+  NTSTATUS ret = STATUS_SUCCESS;
+  if (NULL == interceptor_entry_point) {
+    ret = ResolveInterceptor(interceptor_module, interceptor_name,
+                             &interceptor_entry_point);
+    if (!NT_SUCCESS(ret))
+      return ret;
+  }
+
+  ret = ResolveTarget(target_module, target_name, &target_);
+  if (!NT_SUCCESS(ret))
+    return ret;
+
+  interceptor_ = interceptor_entry_point;
+
+  return ret;
+}
+
+NTSTATUS ResolverThunk::ResolveInterceptor(const void* interceptor_module,
+                                           const char* interceptor_name,
+                                           const void** address) {
+  return STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS ResolverThunk::ResolveTarget(const void* module,
+                                      const char* function_name,
+                                      void** address) {
+  return STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS Service64ResolverThunk::Setup(const void* target_module,
+                                       const void* interceptor_module,
+                                       const char* target_name,
+                                       const char* interceptor_name,
+                                       const void* interceptor_entry_point,
+                                       void* thunk_storage,
+                                       size_t storage_bytes,
+                                       size_t* storage_used) {
+  NTSTATUS ret = Init(target_module, interceptor_module, target_name,
+                      interceptor_name, interceptor_entry_point,
+                      thunk_storage, storage_bytes);
+  if (!NT_SUCCESS(ret))
+    return ret;
+
+  size_t thunk_bytes = GetThunkSize();
+  scoped_ptr<char[]> thunk_buffer(new char[thunk_bytes]);
+  ServiceFullThunk* thunk = reinterpret_cast<ServiceFullThunk*>(
+                                thunk_buffer.get());
+
+  if (!IsFunctionAService(&thunk->original))
+    return STATUS_UNSUCCESSFUL;
+
+  ret = PerformPatch(thunk, thunk_storage);
+
+  if (NULL != storage_used)
+    *storage_used = thunk_bytes;
+
+  return ret;
+}
+
+NTSTATUS Service64ResolverThunk::ResolveInterceptor(
+    const void* interceptor_module,
+    const char* interceptor_name,
+    const void** address) {
+  // After all, we are using a locally mapped version of the exe, so the
+  // action is the same as for a target function.
+  return ResolveTarget(interceptor_module, interceptor_name,
+                       const_cast<void**>(address));
+}
+
+// In this case all the work is done from the parent, so resolve is
+// just a simple GetProcAddress.
+NTSTATUS Service64ResolverThunk::ResolveTarget(const void* module,
+                                             const char* function_name,
+                                             void** address) {
+  if (NULL == module)
+    return STATUS_UNSUCCESSFUL;
+
+  *address = ::GetProcAddress(bit_cast<HMODULE>(module), function_name);
+
+  if (NULL == *address)
+    return STATUS_UNSUCCESSFUL;
+
+  return STATUS_SUCCESS;
+}
+
+size_t Service64ResolverThunk::GetThunkSize() const {
+  return sizeof(ServiceFullThunk);
+}
+
+bool Service64ResolverThunk::IsFunctionAService(void* local_thunk) const {
+  ServiceEntry function_code;
+  SIZE_T read;
+  if (!::ReadProcessMemory(process_, target_, &function_code,
+                           sizeof(function_code), &read))
+    return false;
+
+  if (sizeof(function_code) != read)
+    return false;
+
+  if (kMmovR10EcxMovEax != function_code.mov_r10_ecx_mov_eax ||
+      kSyscall != function_code.syscall || kRetNp != function_code.ret)
+    return false;
+
+  // Save the verified code
+  memcpy(local_thunk, &function_code, sizeof(function_code));
+
+  return true;
+}
+
+NTSTATUS Service64ResolverThunk::PerformPatch(void* local_thunk,
+                                              void* remote_thunk) {
+  ServiceFullThunk* full_local_thunk = reinterpret_cast<ServiceFullThunk*>(
+                                           local_thunk);
+  ServiceFullThunk* full_remote_thunk = reinterpret_cast<ServiceFullThunk*>(
+                                           remote_thunk);
+
+  // If the source or target are above 4GB we cannot do this relative jump.
+  if (reinterpret_cast<ULONG_PTR>(full_remote_thunk) >
+      static_cast<ULONG_PTR>(ULONG_MAX))
+    return STATUS_CONFLICTING_ADDRESSES;
+
+  if (reinterpret_cast<ULONG_PTR>(target_) > static_cast<ULONG_PTR>(ULONG_MAX))
+    return STATUS_CONFLICTING_ADDRESSES;
+
+  // Patch the original code.
+  Redirected local_service;
+  Redirected* remote_service = reinterpret_cast<Redirected*>(target_);
+  ULONG_PTR diff = reinterpret_cast<BYTE*>(&full_remote_thunk->internal_thunk) -
+                   &remote_service->pad;
+  local_service.relative = static_cast<ULONG>(diff);
+
+  // Setup the PatchInfo structure.
+  SIZE_T actual;
+  if (!::ReadProcessMemory(process_, remote_thunk, local_thunk,
+                           sizeof(PatchInfo), &actual))
+    return STATUS_UNSUCCESSFUL;
+  if (sizeof(PatchInfo) != actual)
+    return STATUS_UNSUCCESSFUL;
+
+  full_local_thunk->patch_info.orig_MapViewOfSection = reinterpret_cast<
+      NtMapViewOfSectionFunction>(&full_remote_thunk->original);
+  full_local_thunk->patch_info.patch_location = target_;
+  NTSTATUS ret = ResolveNtdll(&full_local_thunk->patch_info);
+  if (!NT_SUCCESS(ret))
+    return ret;
+
+  // Setup the thunk. The jump out is performed from right after the end of the
+  // thunk (full_remote_thunk + 1).
+  InternalThunk my_thunk;
+  ULONG_PTR patch_info = reinterpret_cast<ULONG_PTR>(remote_thunk);
+  my_thunk.patch_info = static_cast<ULONG>(patch_info);
+  diff = reinterpret_cast<const BYTE*>(interceptor_) -
+         reinterpret_cast<BYTE*>(full_remote_thunk + 1);
+  my_thunk.relative = static_cast<ULONG>(diff);
+
+  memcpy(&full_local_thunk->internal_thunk, &my_thunk, sizeof(my_thunk));
+
+  // copy the local thunk buffer to the child
+  if (!::WriteProcessMemory(process_, remote_thunk, local_thunk,
+                            sizeof(ServiceFullThunk), &actual))
+    return STATUS_UNSUCCESSFUL;
+
+  if (sizeof(ServiceFullThunk) != actual)
+    return STATUS_UNSUCCESSFUL;
+
+  // and now change the function to intercept, on the child
+  if (!::WriteProtectedChildMemory(process_, target_, &local_service,
+                                   sizeof(local_service)))
+    return STATUS_UNSUCCESSFUL;
+
+  return STATUS_SUCCESS;
+}
+
+}  // namespace sandbox
new file mode 100644
--- /dev/null
+++ b/security/sandbox/chromium/sandbox/win/wow_helper/service64_resolver.h
@@ -0,0 +1,72 @@
+// Copyright (c) 2010 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.
+
+#ifndef SANDBOX_WOW_HELPER_SERVICE64_RESOLVER_H__
+#define SANDBOX_WOW_HELPER_SERVICE64_RESOLVER_H__
+
+#include "sandbox/win/src/nt_internals.h"
+#include "sandbox/win/src/resolver.h"
+
+namespace sandbox {
+
+// This is the concrete resolver used to perform service-call type functions
+// inside ntdll.dll (64-bit).
+class Service64ResolverThunk : public ResolverThunk {
+ public:
+  // The service resolver needs a child process to write to.
+  explicit Service64ResolverThunk(HANDLE process)
+      : process_(process), ntdll_base_(NULL) {}
+  virtual ~Service64ResolverThunk() {}
+
+  // Implementation of Resolver::Setup.
+  virtual NTSTATUS Setup(const void* target_module,
+                         const void* interceptor_module,
+                         const char* target_name,
+                         const char* interceptor_name,
+                         const void* interceptor_entry_point,
+                         void* thunk_storage,
+                         size_t storage_bytes,
+                         size_t* storage_used);
+
+  // Implementation of Resolver::ResolveInterceptor.
+  virtual NTSTATUS ResolveInterceptor(const void* module,
+                                      const char* function_name,
+                                      const void** address);
+
+  // Implementation of Resolver::ResolveTarget.
+  virtual NTSTATUS ResolveTarget(const void* module,
+                                 const char* function_name,
+                                 void** address);
+
+  // Implementation of Resolver::GetThunkSize.
+  virtual size_t GetThunkSize() const;
+
+ protected:
+  // The unit test will use this member to allow local patch on a buffer.
+  HMODULE ntdll_base_;
+
+  // Handle of the child process.
+  HANDLE process_;
+
+ private:
+  // Returns true if the code pointer by target_ corresponds to the expected
+  // type of function. Saves that code on the first part of the thunk pointed
+  // by local_thunk (should be directly accessible from the parent).
+  virtual bool IsFunctionAService(void* local_thunk) const;
+
+  // Performs the actual patch of target_.
+  // local_thunk must be already fully initialized, and the first part must
+  // contain the original code. The real type of this buffer is ServiceFullThunk
+  // (yes, private). remote_thunk (real type ServiceFullThunk), must be
+  // allocated on the child, and will contain the thunk data, after this call.
+  // Returns the apropriate status code.
+  virtual NTSTATUS PerformPatch(void* local_thunk, void* remote_thunk);
+
+  DISALLOW_COPY_AND_ASSIGN(Service64ResolverThunk);
+};
+
+}  // namespace sandbox
+
+
+#endif  // SANDBOX_WOW_HELPER_SERVICE64_RESOLVER_H__
new file mode 100644
--- /dev/null
+++ b/security/sandbox/chromium/sandbox/win/wow_helper/target_code.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 2006-2008 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/win/wow_helper/target_code.h"
+
+namespace sandbox {
+
+// Hooks NtMapViewOfSection to detect the load of dlls.
+NTSTATUS WINAPI TargetNtMapViewOfSection(
+    PatchInfo *patch_info, HANDLE process, PVOID *base, ULONG_PTR zero_bits,
+    SIZE_T commit_size, PLARGE_INTEGER offset, PSIZE_T view_size,
+    SECTION_INHERIT inherit, ULONG allocation_type, ULONG protect) {
+  NTSTATUS ret = patch_info->orig_MapViewOfSection(patch_info->section, process,
+                                                   base, zero_bits, commit_size,
+                                                   offset, view_size, inherit,
+                                                   allocation_type, protect);
+
+  LARGE_INTEGER timeout;
+  timeout.QuadPart = -(5 * 10000000);  // 5 seconds.
+
+  // The wait is alertable.
+  patch_info->signal_and_wait(patch_info->dll_load, patch_info->continue_load,
+                              TRUE, &timeout);
+
+  return ret;
+}
+
+// Marks the end of the code to copy to the target process.
+NTSTATUS WINAPI TargetEnd() {
+  return STATUS_SUCCESS;
+}
+
+}  // namespace sandbox
new file mode 100644
--- /dev/null
+++ b/security/sandbox/chromium/sandbox/win/wow_helper/target_code.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2006-2008 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.
+
+#ifndef SANDBOX_WOW_HELPER_TARGET_CODE_H__
+#define SANDBOX_WOW_HELPER_TARGET_CODE_H__
+
+#include "sandbox/win/src/nt_internals.h"
+
+namespace sandbox {
+
+extern "C" {
+
+// Holds the information needed for the interception of NtMapViewOfSection.
+// Changes of this structure must be synchronized with changes of PatchInfo32
+// on sandbox/win/src/wow64.cc.
+struct PatchInfo {
+  HANDLE dll_load;  // Event to signal the broker.
+  HANDLE continue_load;  // Event to wait for the broker.
+  HANDLE section;  // First argument of the call.
+  NtMapViewOfSectionFunction orig_MapViewOfSection;
+  NtSignalAndWaitForSingleObjectFunction signal_and_wait;
+  void* patch_location;
+};
+
+// Interception of NtMapViewOfSection on the child process.
+// It should never be called directly. This function provides the means to
+// detect dlls being loaded, so we can patch them if needed.
+NTSTATUS WINAPI TargetNtMapViewOfSection(
+    PatchInfo* patch_info, HANDLE process, PVOID* base, ULONG_PTR zero_bits,
+    SIZE_T commit_size, PLARGE_INTEGER offset, PSIZE_T view_size,
+    SECTION_INHERIT inherit, ULONG allocation_type, ULONG protect);
+
+// Marker of the end of TargetNtMapViewOfSection.
+NTSTATUS WINAPI TargetEnd();
+
+} // extern "C"
+
+}  // namespace sandbox
+
+#endif  // SANDBOX_WOW_HELPER_TARGET_CODE_H__
new file mode 100644
--- /dev/null
+++ b/security/sandbox/chromium/sandbox/win/wow_helper/wow_helper.cc
@@ -0,0 +1,86 @@
+// Copyright (c) 2006-2008 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.
+
+// Wow_helper.exe is a simple Win32 64-bit executable designed to help to
+// sandbox a 32 bit application running on a 64 bit OS. The basic idea is to
+// perform a 64 bit interception of the target process and notify the 32-bit
+// broker process whenever a DLL is being loaded. This allows the broker to
+// setup the interceptions (32-bit) properly on the target.
+
+#include <windows.h>
+
+#include <string>
+
+#include "sandbox/win/wow_helper/service64_resolver.h"
+#include "sandbox/win/wow_helper/target_code.h"
+
+namespace sandbox {
+
+// Performs the interception of NtMapViewOfSection on the 64-bit version of
+// ntdll.dll. 'thunk' is the buffer on the address space of process 'child',
+// that will be used to store the information about the patch.
+int PatchNtdll(HANDLE child, void* thunk, size_t thunk_bytes) {
+  wchar_t* ntdll_name = L"ntdll.dll";
+  HMODULE ntdll_base = ::GetModuleHandle(ntdll_name);
+  if (!ntdll_base)
+    return 100;
+
+  Service64ResolverThunk resolver(child);
+  size_t used = resolver.GetThunkSize();
+  char* code = reinterpret_cast<char*>(thunk) + used;
+  NTSTATUS ret = resolver.Setup(ntdll_base, NULL, "NtMapViewOfSection", NULL,
+                                code, thunk, thunk_bytes, NULL);
+  if (!NT_SUCCESS(ret))
+    return 101;
+
+  size_t size = reinterpret_cast<char*>(&TargetEnd) -
+                reinterpret_cast<char*>(&TargetNtMapViewOfSection);
+
+  if (size + used > thunk_bytes)
+    return 102;
+
+  SIZE_T written;
+  if (!::WriteProcessMemory(child, code, &TargetNtMapViewOfSection, size,
+                            &written))
+    return 103;
+
+  if (size != written)
+    return 104;
+
+  return 0;
+}
+
+}  // namespace sandbox
+
+// We must receive two arguments: the process id of the target to intercept and
+// the address of a page of memory on that process that will be used for the
+// interception. We receive the address because the broker will cleanup the
+// patch when the work is performed.
+//
+// It should be noted that we don't wait until the real work is done; this
+// program quits as soon as the 64-bit interception is performed.
+int wWinMain(HINSTANCE, HINSTANCE, wchar_t* command_line, int) {
+  COMPILE_ASSERT(sizeof(void*) > sizeof(DWORD), unsupported_32_bits);
+  if (!command_line)
+    return 1;
+
+  wchar_t* next;
+  DWORD process_id = wcstoul(command_line, &next, 0);
+  if (!process_id)
+    return 2;
+
+  DWORD access = PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE;
+  HANDLE child = ::OpenProcess(access, FALSE, process_id);
+  if (!child)
+    return 3;
+
+  DWORD buffer = wcstoul(next, NULL, 0);
+  if (!buffer)
+    return 4;
+
+  void* thunk = reinterpret_cast<void*>(static_cast<ULONG_PTR>(buffer));
+
+  const size_t kPageSize = 4096;
+  return sandbox::PatchNtdll(child, thunk, kPageSize);
+}