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