security/sandbox/win/src/interception.cc

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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.
     5 // For information about interceptions as a whole see
     6 // http://dev.chromium.org/developers/design-documents/sandbox .
     8 #include <set>
    10 #include "sandbox/win/src/interception.h"
    12 #include "base/logging.h"
    13 #include "base/memory/scoped_ptr.h"
    14 #include "base/win/pe_image.h"
    15 #include "base/win/windows_version.h"
    16 #include "sandbox/win/src/interception_internal.h"
    17 #include "sandbox/win/src/interceptors.h"
    18 #include "sandbox/win/src/sandbox.h"
    19 #include "sandbox/win/src/sandbox_utils.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"
    25 namespace {
    27 const char kMapViewOfSectionName[] = "NtMapViewOfSection";
    28 const char kUnmapViewOfSectionName[] = "NtUnmapViewOfSection";
    30 // Standard allocation granularity and page size for Windows.
    31 const size_t kAllocGranularity = 65536;
    32 const size_t kPageSize = 4096;
    34 // Find a random offset within 64k and aligned to ceil(log2(size)).
    35 size_t GetGranularAlignedRandomOffset(size_t size) {
    36   CHECK_LE(size, kAllocGranularity);
    37   unsigned int offset;
    39   do {
    40     rand_s(&offset);
    41     offset &= (kAllocGranularity - 1);
    42   } while (offset > (kAllocGranularity - size));
    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 }
    52 }  // namespace
    54 namespace sandbox {
    56 SANDBOX_INTERCEPT SharedMemory* g_interceptions;
    58 // Table of the unpatched functions that we intercept. Mapped from the parent.
    59 SANDBOX_INTERCEPT OriginalFunctions g_originals = { NULL };
    61 // Magic constant that identifies that this function is not to be patched.
    62 const char kUnloadDLLDummyFunction[] = "@";
    64 InterceptionManager::InterceptionManager(TargetProcess* child_process,
    65                                          bool relaxed)
    66     : child_(child_process), names_used_(false), relaxed_(relaxed) {
    67   child_->AddRef();
    68 }
    69 InterceptionManager::~InterceptionManager() {
    70   child_->Release();
    71 }
    73 bool 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;
    84   interceptions_.push_back(function);
    85   return true;
    86 }
    88 bool 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;
   100   interceptions_.push_back(function);
   101   names_used_ = true;
   102   return true;
   103 }
   105 bool 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);
   114   interceptions_.push_back(module_to_unload);
   115   return true;
   116 }
   118 bool InterceptionManager::InitializeInterceptions() {
   119   if (interceptions_.empty())
   120     return true;  // Nothing to do here
   122   size_t buffer_bytes = GetBufferSize();
   123   scoped_ptr<char[]> local_buffer(new char[buffer_bytes]);
   125   if (!SetupConfigBuffer(local_buffer.get(), buffer_bytes))
   126     return false;
   128   void* remote_buffer;
   129   if (!CopyDataToChild(local_buffer.get(), buffer_bytes, &remote_buffer))
   130     return false;
   132   bool hot_patch_needed = (0 != buffer_bytes);
   133   if (!PatchNtdll(hot_patch_needed))
   134     return false;
   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 }
   143 size_t InterceptionManager::GetBufferSize() const {
   144   std::set<std::wstring> dlls;
   145   size_t buffer_bytes = 0;
   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;
   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);
   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     }
   163     // we have to NULL terminate the strings on the structure
   164     size_t strings_chars = it->function.size() + it->interceptor.size() + 2;
   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   }
   172   if (0 != buffer_bytes)
   173     // add the part of SharedMemory that we have not counted yet
   174     buffer_bytes += offsetof(SharedMemory, dll_list);
   176   return buffer_bytes;
   177 }
   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.
   182 bool InterceptionManager::SetupConfigBuffer(void* buffer, size_t buffer_bytes) {
   183   if (0 == buffer_bytes)
   184     return true;
   186   DCHECK(buffer_bytes > sizeof(SharedMemory));
   188   SharedMemory* shared_memory = reinterpret_cast<SharedMemory*>(buffer);
   189   DllPatchInfo* dll_info = shared_memory->dll_list;
   190   int num_dlls = 0;
   192   shared_memory->interceptor_base = names_used_ ? child_->MainModule() : NULL;
   194   buffer_bytes -= offsetof(SharedMemory, dll_list);
   195   buffer = dll_info;
   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     }
   205     const std::wstring dll = it->dll;
   206     if (!SetupDllInfo(*it, &buffer, &buffer_bytes))
   207       return false;
   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   }
   228   shared_memory->num_intercepted_dlls = num_dlls;
   229   return true;
   230 }
   232 // Fills up just the part that depends on the dll, not the info that depends on
   233 // the actual interception.
   234 bool InterceptionManager::SetupDllInfo(const InterceptionData& data,
   235                                        void** buffer,
   236                                        size_t* buffer_bytes) const {
   237   DCHECK(buffer_bytes);
   238   DCHECK(buffer);
   239   DCHECK(*buffer);
   241   DllPatchInfo* dll_info = reinterpret_cast<DllPatchInfo*>(*buffer);
   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;
   250   *buffer_bytes -= required;
   251   *buffer = reinterpret_cast<char*>(*buffer) + required;
   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';
   261   return true;
   262 }
   264 bool 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);
   272   if ((dll_info->unload_module) &&
   273       (data.function != kUnloadDLLDummyFunction)) {
   274     // Can't specify a dll for both patch and unload.
   275     NOTREACHED();
   276   }
   278   FunctionInfo* function = reinterpret_cast<FunctionInfo*>(*buffer);
   280   size_t name_bytes = data.function.size();
   281   size_t interceptor_bytes = data.interceptor.size();
   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;
   290   // update the caller's values
   291   *buffer_bytes -= required;
   292   *buffer = reinterpret_cast<char*>(*buffer) + required;
   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;
   300   data.function._Copy_s(names, name_bytes, name_bytes);
   301   names += name_bytes;
   302   *names++ = '\0';
   304   // interceptor follows the function_name
   305   data.interceptor._Copy_s(names, interceptor_bytes, interceptor_bytes);
   306   names += interceptor_bytes;
   307   *names++ = '\0';
   309   // update the dll table
   310   dll_info->num_functions++;
   311   dll_info->record_bytes += required;
   313   return true;
   314 }
   316 bool 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   }
   325   HANDLE child = child_->Process();
   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;
   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   }
   341   *remote_buffer = remote_data;
   343   return true;
   344 }
   346 // Only return true if the child should be able to perform this interception.
   347 bool InterceptionManager::IsInterceptionPerformedByChild(
   348     const InterceptionData& data) const {
   349   if (INTERCEPTION_INVALID == data.type)
   350     return false;
   352   if (INTERCEPTION_SERVICE_CALL == data.type)
   353     return false;
   355   if (data.type >= INTERCEPTION_LAST)
   356     return false;
   358   std::wstring ntdll(kNtdllName);
   359   if (ntdll == data.dll)
   360     return false;  // ntdll has to be intercepted from the parent
   362   return true;
   363 }
   365 bool InterceptionManager::PatchNtdll(bool hot_patch_needed) {
   366   // Maybe there is nothing to do
   367   if (!hot_patch_needed && interceptions_.empty())
   368     return true;
   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   }
   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));
   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);
   396   // Split the base and offset along page boundaries.
   397   thunk_base += thunk_offset & ~(kPageSize - 1);
   398   thunk_offset &= kPageSize - 1;
   400   // Make an aligned, padded allocation, and move the pointer to our chunk.
   401   size_t thunk_bytes_padded = (thunk_bytes + kPageSize - 1) & kPageSize;
   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);
   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);
   414   // Reset all helpers for a new child.
   415   memset(g_originals, 0, sizeof(g_originals));
   417   // this should write all the individual thunks to the child's memory
   418   if (!PatchClientFunctions(thunks, thunk_bytes, &dll_data))
   419     return false;
   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);
   427   if (!ok || (offsetof(DllInterceptionData, thunks) != written))
   428     return false;
   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);
   435   ResultCode ret = child_->TransferVariable("g_originals", g_originals,
   436                                             sizeof(g_originals));
   437   return (SBOX_ALL_OK == ret);
   438 }
   440 bool InterceptionManager::PatchClientFunctions(DllInterceptionData* thunks,
   441                                                size_t thunk_bytes,
   442                                                DllInterceptionData* dll_data) {
   443   DCHECK(NULL != thunks);
   444   DCHECK(NULL != dll_data);
   446   HMODULE ntdll_base = ::GetModuleHandle(kNtdllName);
   447   if (!ntdll_base)
   448     return false;
   450   base::win::PEImage ntdll_image(ntdll_base);
   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   }
   462   if (base::win::GetVersion() <= base::win::VERSION_VISTA) {
   463     Wow64 WowHelper(child_, ntdll_base);
   464     if (!WowHelper.WaitForNtdll())
   465       return false;
   466   }
   468   char* interceptor_base = NULL;
   470 #if SANDBOX_EXPORTS
   471   interceptor_base = reinterpret_cast<char*>(child_->MainModule());
   472   HMODULE local_interceptor = ::LoadLibrary(child_->Name());
   473 #endif
   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 (!IsXPSP2OrLater()) {
   486     thunk = new Win2kResolverThunk(child_->Process(), relaxed_);
   487   } else if (os_info->version() >= base::win::VERSION_WIN8) {
   488     thunk = new Win8ResolverThunk(child_->Process(), relaxed_);
   489   } else {
   490     thunk = new ServiceResolverThunk(child_->Process(), relaxed_);
   491   }
   492 #endif
   494   std::list<InterceptionData>::iterator it = interceptions_.begin();
   495   for (; it != interceptions_.end(); ++it) {
   496     const std::wstring ntdll(kNtdllName);
   497     if (it->dll != ntdll)
   498       break;
   500     if (INTERCEPTION_SERVICE_CALL != it->type)
   501       break;
   503 #if SANDBOX_EXPORTS
   504     // We may be trying to patch by function name.
   505     if (NULL == it->interceptor_address) {
   506       const char* address;
   507       NTSTATUS ret = thunk->ResolveInterceptor(local_interceptor,
   508                                                it->interceptor.c_str(),
   509                                                reinterpret_cast<const void**>(
   510                                                &address));
   511       if (!NT_SUCCESS(ret))
   512         break;
   514       // Translate the local address to an address on the child.
   515       it->interceptor_address = interceptor_base + (address -
   516                                     reinterpret_cast<char*>(local_interceptor));
   517     }
   518 #endif
   519     NTSTATUS ret = thunk->Setup(ntdll_base,
   520                                 interceptor_base,
   521                                 it->function.c_str(),
   522                                 it->interceptor.c_str(),
   523                                 it->interceptor_address,
   524                                 &thunks->thunks[dll_data->num_thunks],
   525                                 thunk_bytes - dll_data->used_bytes,
   526                                 NULL);
   527     if (!NT_SUCCESS(ret))
   528       break;
   530     DCHECK(!g_originals[it->id]);
   531     g_originals[it->id] = &thunks->thunks[dll_data->num_thunks];
   533     dll_data->num_thunks++;
   534     dll_data->used_bytes += sizeof(ThunkData);
   535   }
   537   delete(thunk);
   539 #if SANDBOX_EXPORTS
   540   if (NULL != local_interceptor)
   541     ::FreeLibrary(local_interceptor);
   542 #endif
   544   if (it != interceptions_.end())
   545     return false;
   547   return true;
   548 }
   550 }  // namespace sandbox

mercurial