Home | History | Annotate | Download | only in src
      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/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"
     24 
     25 namespace {
     26 
     27 const char kMapViewOfSectionName[] = "NtMapViewOfSection";
     28 const char kUnmapViewOfSectionName[] = "NtUnmapViewOfSection";
     29 
     30 // Standard allocation granularity and page size for Windows.
     31 const size_t kAllocGranularity = 65536;
     32 const size_t kPageSize = 4096;
     33 
     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;
     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 
     54 namespace sandbox {
     55 
     56 SANDBOX_INTERCEPT SharedMemory* g_interceptions;
     57 
     58 // Table of the unpatched functions that we intercept. Mapped from the parent.
     59 SANDBOX_INTERCEPT OriginalFunctions g_originals = { NULL };
     60 
     61 // Magic constant that identifies that this function is not to be patched.
     62 const char kUnloadDLLDummyFunction[] = "@";
     63 
     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 }
     72 
     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;
     83 
     84   interceptions_.push_back(function);
     85   return true;
     86 }
     87 
     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;
     99 
    100   interceptions_.push_back(function);
    101   names_used_ = true;
    102   return true;
    103 }
    104 
    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);
    113 
    114   interceptions_.push_back(module_to_unload);
    115   return true;
    116 }
    117 
    118 bool 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 
    143 size_t InterceptionManager::GetBufferSize() const {
    144   std::set<std::wstring> 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.
    182 bool 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 std::wstring 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.
    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);
    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 
    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);
    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 
    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   }
    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.
    347 bool 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   std::wstring ntdll(kNtdllName);
    359   if (ntdll == data.dll)
    360     return false;  // ntdll has to be intercepted from the parent
    361 
    362   return true;
    363 }
    364 
    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;
    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;
    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 
    440 bool 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 (!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
    493 
    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;
    499 
    500     if (INTERCEPTION_SERVICE_CALL != it->type)
    501       break;
    502 
    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;
    513 
    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;
    529 
    530     DCHECK(!g_originals[it->id]);
    531     g_originals[it->id] = &thunks->thunks[dll_data->num_thunks];
    532 
    533     dll_data->num_thunks++;
    534     dll_data->used_bytes += sizeof(ThunkData);
    535   }
    536 
    537   delete(thunk);
    538 
    539 #if SANDBOX_EXPORTS
    540   if (NULL != local_interceptor)
    541     ::FreeLibrary(local_interceptor);
    542 #endif
    543 
    544   if (it != interceptions_.end())
    545     return false;
    546 
    547   return true;
    548 }
    549 
    550 }  // namespace sandbox
    551