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 #include "sandbox/win/src/sandbox_nt_util.h"
      6 
      7 #include "base/win/pe_image.h"
      8 #include "sandbox/win/src/sandbox_factory.h"
      9 #include "sandbox/win/src/target_services.h"
     10 
     11 namespace sandbox {
     12 
     13 // This is the list of all imported symbols from ntdll.dll.
     14 SANDBOX_INTERCEPT NtExports g_nt;
     15 
     16 }  // namespace sandbox
     17 
     18 namespace {
     19 
     20 #if defined(_WIN64)
     21 void* AllocateNearTo(void* source, size_t size) {
     22   using sandbox::g_nt;
     23 
     24   // Start with 1 GB above the source.
     25   const size_t kOneGB = 0x40000000;
     26   void* base = reinterpret_cast<char*>(source) + kOneGB;
     27   SIZE_T actual_size = size;
     28   ULONG_PTR zero_bits = 0;  // Not the correct type if used.
     29   ULONG type = MEM_RESERVE;
     30 
     31   NTSTATUS ret;
     32   int attempts = 0;
     33   for (; attempts < 41; attempts++) {
     34     ret = g_nt.AllocateVirtualMemory(NtCurrentProcess, &base, zero_bits,
     35                                      &actual_size, type, PAGE_READWRITE);
     36     if (NT_SUCCESS(ret)) {
     37       if (base < source ||
     38           base >= reinterpret_cast<char*>(source) + 4 * kOneGB) {
     39         // We won't be able to patch this dll.
     40         VERIFY_SUCCESS(g_nt.FreeVirtualMemory(NtCurrentProcess, &base, &size,
     41                                               MEM_RELEASE));
     42         return NULL;
     43       }
     44       break;
     45     }
     46 
     47     if (attempts == 30) {
     48       // Try the first GB.
     49       base = reinterpret_cast<char*>(source);
     50     } else if (attempts == 40) {
     51       // Try the highest available address.
     52       base = NULL;
     53       type |= MEM_TOP_DOWN;
     54     }
     55 
     56     // Try 100 MB higher.
     57     base = reinterpret_cast<char*>(base) + 100 * 0x100000;
     58   };
     59 
     60   if (attempts == 41)
     61     return NULL;
     62 
     63   ret = g_nt.AllocateVirtualMemory(NtCurrentProcess, &base, zero_bits,
     64                                    &actual_size, MEM_COMMIT, PAGE_READWRITE);
     65 
     66   if (!NT_SUCCESS(ret)) {
     67     VERIFY_SUCCESS(g_nt.FreeVirtualMemory(NtCurrentProcess, &base, &size,
     68                                           MEM_RELEASE));
     69     base = NULL;
     70   }
     71 
     72   return base;
     73 }
     74 #else  // defined(_WIN64).
     75 void* AllocateNearTo(void* source, size_t size) {
     76   using sandbox::g_nt;
     77   UNREFERENCED_PARAMETER(source);
     78 
     79   // In 32-bit processes allocations below 512k are predictable, so mark
     80   // anything in that range as reserved and retry until we get a good address.
     81   const void* const kMinAddress = reinterpret_cast<void*>(512 * 1024);
     82   NTSTATUS ret;
     83   SIZE_T actual_size;
     84   void* base;
     85   do {
     86     base = NULL;
     87     actual_size = 64 * 1024;
     88     ret = g_nt.AllocateVirtualMemory(NtCurrentProcess, &base, 0, &actual_size,
     89                                      MEM_RESERVE, PAGE_NOACCESS);
     90     if (!NT_SUCCESS(ret))
     91       return NULL;
     92   } while (base < kMinAddress);
     93 
     94   actual_size = size;
     95   ret = g_nt.AllocateVirtualMemory(NtCurrentProcess, &base, 0, &actual_size,
     96                                    MEM_COMMIT, PAGE_READWRITE);
     97   if (!NT_SUCCESS(ret))
     98     return NULL;
     99   return base;
    100 }
    101 #endif  // defined(_WIN64).
    102 
    103 }  // namespace.
    104 
    105 namespace sandbox {
    106 
    107 // Handle for our private heap.
    108 void* g_heap = NULL;
    109 
    110 SANDBOX_INTERCEPT HANDLE g_shared_section;
    111 SANDBOX_INTERCEPT size_t g_shared_IPC_size = 0;
    112 SANDBOX_INTERCEPT size_t g_shared_policy_size = 0;
    113 
    114 void* volatile g_shared_policy_memory = NULL;
    115 void* volatile g_shared_IPC_memory = NULL;
    116 
    117 // Both the IPC and the policy share a single region of memory in which the IPC
    118 // memory is first and the policy memory is last.
    119 bool MapGlobalMemory() {
    120   if (NULL == g_shared_IPC_memory) {
    121     void* memory = NULL;
    122     SIZE_T size = 0;
    123     // Map the entire shared section from the start.
    124     NTSTATUS ret = g_nt.MapViewOfSection(g_shared_section, NtCurrentProcess,
    125                                          &memory, 0, 0, NULL, &size, ViewUnmap,
    126                                          0, PAGE_READWRITE);
    127 
    128     if (!NT_SUCCESS(ret) || NULL == memory) {
    129       NOTREACHED_NT();
    130       return false;
    131     }
    132 
    133     if (NULL != _InterlockedCompareExchangePointer(&g_shared_IPC_memory,
    134                                                    memory, NULL)) {
    135         // Somebody beat us to the memory setup.
    136         ret = g_nt.UnmapViewOfSection(NtCurrentProcess, memory);
    137         VERIFY_SUCCESS(ret);
    138     }
    139     DCHECK_NT(g_shared_IPC_size > 0);
    140     g_shared_policy_memory = reinterpret_cast<char*>(g_shared_IPC_memory)
    141                              + g_shared_IPC_size;
    142   }
    143   DCHECK_NT(g_shared_policy_memory);
    144   DCHECK_NT(g_shared_policy_size > 0);
    145   return true;
    146 }
    147 
    148 void* GetGlobalIPCMemory() {
    149   if (!MapGlobalMemory())
    150     return NULL;
    151   return g_shared_IPC_memory;
    152 }
    153 
    154 void* GetGlobalPolicyMemory() {
    155   if (!MapGlobalMemory())
    156     return NULL;
    157   return g_shared_policy_memory;
    158 }
    159 
    160 bool InitHeap() {
    161   if (!g_heap) {
    162     // Create a new heap using default values for everything.
    163     void* heap = g_nt.RtlCreateHeap(HEAP_GROWABLE, NULL, 0, 0, NULL, NULL);
    164     if (!heap)
    165       return false;
    166 
    167     if (NULL != _InterlockedCompareExchangePointer(&g_heap, heap, NULL)) {
    168       // Somebody beat us to the memory setup.
    169       g_nt.RtlDestroyHeap(heap);
    170     }
    171   }
    172   return (g_heap != NULL);
    173 }
    174 
    175 // Physically reads or writes from memory to verify that (at this time), it is
    176 // valid. Returns a dummy value.
    177 int TouchMemory(void* buffer, size_t size_bytes, RequiredAccess intent) {
    178   const int kPageSize = 4096;
    179   int dummy = 0;
    180   char* start = reinterpret_cast<char*>(buffer);
    181   char* end = start + size_bytes - 1;
    182 
    183   if (WRITE == intent) {
    184     for (; start < end; start += kPageSize) {
    185       *start = 0;
    186     }
    187     *end = 0;
    188   } else {
    189     for (; start < end; start += kPageSize) {
    190       dummy += *start;
    191     }
    192     dummy += *end;
    193   }
    194 
    195   return dummy;
    196 }
    197 
    198 bool ValidParameter(void* buffer, size_t size, RequiredAccess intent) {
    199   DCHECK_NT(size);
    200   __try {
    201     TouchMemory(buffer, size, intent);
    202   } __except(EXCEPTION_EXECUTE_HANDLER) {
    203     return false;
    204   }
    205   return true;
    206 }
    207 
    208 NTSTATUS CopyData(void* destination, const void* source, size_t bytes) {
    209   NTSTATUS ret = STATUS_SUCCESS;
    210   __try {
    211     g_nt.memcpy(destination, source, bytes);
    212   } __except(EXCEPTION_EXECUTE_HANDLER) {
    213     ret = GetExceptionCode();
    214   }
    215   return ret;
    216 }
    217 
    218 // Hacky code... replace with AllocAndCopyObjectAttributes.
    219 NTSTATUS AllocAndCopyName(const OBJECT_ATTRIBUTES* in_object,
    220                           wchar_t** out_name, uint32* attributes,
    221                           HANDLE* root) {
    222   if (!InitHeap())
    223     return STATUS_NO_MEMORY;
    224 
    225   DCHECK_NT(out_name);
    226   *out_name = NULL;
    227   NTSTATUS ret = STATUS_UNSUCCESSFUL;
    228   __try {
    229     do {
    230       if (in_object->RootDirectory != static_cast<HANDLE>(0) && !root)
    231         break;
    232       if (NULL == in_object->ObjectName)
    233         break;
    234       if (NULL == in_object->ObjectName->Buffer)
    235         break;
    236 
    237       size_t size = in_object->ObjectName->Length + sizeof(wchar_t);
    238       *out_name = new(NT_ALLOC) wchar_t[size/sizeof(wchar_t)];
    239       if (NULL == *out_name)
    240         break;
    241 
    242       ret = CopyData(*out_name, in_object->ObjectName->Buffer,
    243                      size - sizeof(wchar_t));
    244       if (!NT_SUCCESS(ret))
    245         break;
    246 
    247       (*out_name)[size / sizeof(wchar_t) - 1] = L'\0';
    248 
    249       if (attributes)
    250         *attributes = in_object->Attributes;
    251 
    252       if (root)
    253         *root = in_object->RootDirectory;
    254       ret = STATUS_SUCCESS;
    255     } while (false);
    256   } __except(EXCEPTION_EXECUTE_HANDLER) {
    257     ret = GetExceptionCode();
    258   }
    259 
    260   if (!NT_SUCCESS(ret) && *out_name) {
    261     operator delete(*out_name, NT_ALLOC);
    262     *out_name = NULL;
    263   }
    264 
    265   return ret;
    266 }
    267 
    268 NTSTATUS GetProcessId(HANDLE process, ULONG *process_id) {
    269   PROCESS_BASIC_INFORMATION proc_info;
    270   ULONG bytes_returned;
    271 
    272   NTSTATUS ret = g_nt.QueryInformationProcess(process, ProcessBasicInformation,
    273                                               &proc_info, sizeof(proc_info),
    274                                               &bytes_returned);
    275   if (!NT_SUCCESS(ret) || sizeof(proc_info) != bytes_returned)
    276     return ret;
    277 
    278   *process_id = proc_info.UniqueProcessId;
    279   return STATUS_SUCCESS;
    280 }
    281 
    282 bool IsSameProcess(HANDLE process) {
    283   if (NtCurrentProcess == process)
    284     return true;
    285 
    286   static ULONG s_process_id = 0;
    287 
    288   if (!s_process_id) {
    289     NTSTATUS ret = GetProcessId(NtCurrentProcess, &s_process_id);
    290     if (!NT_SUCCESS(ret))
    291       return false;
    292   }
    293 
    294   ULONG process_id;
    295   NTSTATUS ret = GetProcessId(process, &process_id);
    296   if (!NT_SUCCESS(ret))
    297     return false;
    298 
    299   return (process_id == s_process_id);
    300 }
    301 
    302 bool IsValidImageSection(HANDLE section, PVOID *base, PLARGE_INTEGER offset,
    303                          PSIZE_T view_size) {
    304   if (!section || !base || !view_size || offset)
    305     return false;
    306 
    307   HANDLE query_section;
    308 
    309   NTSTATUS ret = g_nt.DuplicateObject(NtCurrentProcess, section,
    310                                       NtCurrentProcess, &query_section,
    311                                       SECTION_QUERY, 0, 0);
    312   if (!NT_SUCCESS(ret))
    313     return false;
    314 
    315   SECTION_BASIC_INFORMATION basic_info;
    316   SIZE_T bytes_returned;
    317   ret = g_nt.QuerySection(query_section, SectionBasicInformation, &basic_info,
    318                           sizeof(basic_info), &bytes_returned);
    319 
    320   VERIFY_SUCCESS(g_nt.Close(query_section));
    321 
    322   if (!NT_SUCCESS(ret) || sizeof(basic_info) != bytes_returned)
    323     return false;
    324 
    325   if (!(basic_info.Attributes & SEC_IMAGE))
    326     return false;
    327 
    328   return true;
    329 }
    330 
    331 UNICODE_STRING* AnsiToUnicode(const char* string) {
    332   ANSI_STRING ansi_string;
    333   ansi_string.Length = static_cast<USHORT>(g_nt.strlen(string));
    334   ansi_string.MaximumLength = ansi_string.Length + 1;
    335   ansi_string.Buffer = const_cast<char*>(string);
    336 
    337   if (ansi_string.Length > ansi_string.MaximumLength)
    338     return NULL;
    339 
    340   size_t name_bytes = ansi_string.MaximumLength * sizeof(wchar_t) +
    341                       sizeof(UNICODE_STRING);
    342 
    343   UNICODE_STRING* out_string = reinterpret_cast<UNICODE_STRING*>(
    344                                    new(NT_ALLOC) char[name_bytes]);
    345   if (!out_string)
    346     return NULL;
    347 
    348   out_string->MaximumLength = ansi_string.MaximumLength *  sizeof(wchar_t);
    349   out_string->Buffer = reinterpret_cast<wchar_t*>(&out_string[1]);
    350 
    351   BOOLEAN alloc_destination = FALSE;
    352   NTSTATUS ret = g_nt.RtlAnsiStringToUnicodeString(out_string, &ansi_string,
    353                                                    alloc_destination);
    354   DCHECK_NT(STATUS_BUFFER_OVERFLOW != ret);
    355   if (!NT_SUCCESS(ret)) {
    356     operator delete(out_string, NT_ALLOC);
    357     return NULL;
    358   }
    359 
    360   return out_string;
    361 }
    362 
    363 UNICODE_STRING* GetImageInfoFromModule(HMODULE module, uint32* flags) {
    364   UNICODE_STRING* out_name = NULL;
    365   __try {
    366     do {
    367       *flags = 0;
    368       base::win::PEImage pe(module);
    369 
    370       if (!pe.VerifyMagic())
    371         break;
    372       *flags |= MODULE_IS_PE_IMAGE;
    373 
    374       PIMAGE_EXPORT_DIRECTORY exports = pe.GetExportDirectory();
    375       if (exports) {
    376         char* name = reinterpret_cast<char*>(pe.RVAToAddr(exports->Name));
    377         out_name = AnsiToUnicode(name);
    378       }
    379 
    380       PIMAGE_NT_HEADERS headers = pe.GetNTHeaders();
    381       if (headers) {
    382         if (headers->OptionalHeader.AddressOfEntryPoint)
    383           *flags |= MODULE_HAS_ENTRY_POINT;
    384         if (headers->OptionalHeader.SizeOfCode)
    385           *flags |= MODULE_HAS_CODE;
    386       }
    387     } while (false);
    388   } __except(EXCEPTION_EXECUTE_HANDLER) {
    389   }
    390 
    391   return out_name;
    392 }
    393 
    394 UNICODE_STRING* GetBackingFilePath(PVOID address) {
    395   // We'll start with something close to max_path charactes for the name.
    396   ULONG buffer_bytes = MAX_PATH * 2;
    397 
    398   for (;;) {
    399     MEMORY_SECTION_NAME* section_name = reinterpret_cast<MEMORY_SECTION_NAME*>(
    400         new(NT_ALLOC) char[buffer_bytes]);
    401 
    402     if (!section_name)
    403       return NULL;
    404 
    405     ULONG returned_bytes;
    406     NTSTATUS ret = g_nt.QueryVirtualMemory(NtCurrentProcess, address,
    407                                            MemorySectionName, section_name,
    408                                            buffer_bytes, &returned_bytes);
    409 
    410     if (STATUS_BUFFER_OVERFLOW == ret) {
    411       // Retry the call with the given buffer size.
    412       operator delete(section_name, NT_ALLOC);
    413       section_name = NULL;
    414       buffer_bytes = returned_bytes;
    415       continue;
    416     }
    417     if (!NT_SUCCESS(ret)) {
    418       operator delete(section_name, NT_ALLOC);
    419       return NULL;
    420     }
    421 
    422     return reinterpret_cast<UNICODE_STRING*>(section_name);
    423   }
    424 }
    425 
    426 UNICODE_STRING* ExtractModuleName(const UNICODE_STRING* module_path) {
    427   if ((!module_path) || (!module_path->Buffer))
    428     return NULL;
    429 
    430   wchar_t* sep = NULL;
    431   int start_pos = module_path->Length / sizeof(wchar_t) - 1;
    432   int ix = start_pos;
    433 
    434   for (; ix >= 0; --ix) {
    435     if (module_path->Buffer[ix] == L'\\') {
    436       sep = &module_path->Buffer[ix];
    437       break;
    438     }
    439   }
    440 
    441   // Ends with path separator. Not a valid module name.
    442   if ((ix == start_pos) && sep)
    443     return NULL;
    444 
    445   // No path separator found. Use the entire name.
    446   if (!sep) {
    447     sep = &module_path->Buffer[-1];
    448   }
    449 
    450   // Add one to the size so we can null terminate the string.
    451   size_t size_bytes = (start_pos - ix + 1) * sizeof(wchar_t);
    452 
    453   // Based on the code above, size_bytes should always be small enough
    454   // to make the static_cast below safe.
    455   DCHECK_NT(kuint16max > size_bytes);
    456   char* str_buffer = new(NT_ALLOC) char[size_bytes + sizeof(UNICODE_STRING)];
    457   if (!str_buffer)
    458     return NULL;
    459 
    460   UNICODE_STRING* out_string = reinterpret_cast<UNICODE_STRING*>(str_buffer);
    461   out_string->Buffer = reinterpret_cast<wchar_t*>(&out_string[1]);
    462   out_string->Length = static_cast<USHORT>(size_bytes - sizeof(wchar_t));
    463   out_string->MaximumLength = static_cast<USHORT>(size_bytes);
    464 
    465   NTSTATUS ret = CopyData(out_string->Buffer, &sep[1], out_string->Length);
    466   if (!NT_SUCCESS(ret)) {
    467     operator delete(out_string, NT_ALLOC);
    468     return NULL;
    469   }
    470 
    471   out_string->Buffer[out_string->Length / sizeof(wchar_t)] = L'\0';
    472   return out_string;
    473 }
    474 
    475 NTSTATUS AutoProtectMemory::ChangeProtection(void* address, size_t bytes,
    476                                              ULONG protect) {
    477   DCHECK_NT(!changed_);
    478   SIZE_T new_bytes = bytes;
    479   NTSTATUS ret = g_nt.ProtectVirtualMemory(NtCurrentProcess, &address,
    480                                            &new_bytes, protect, &old_protect_);
    481   if (NT_SUCCESS(ret)) {
    482     changed_ = true;
    483     address_ = address;
    484     bytes_ = new_bytes;
    485   }
    486 
    487   return ret;
    488 }
    489 
    490 NTSTATUS AutoProtectMemory::RevertProtection() {
    491   if (!changed_)
    492     return STATUS_SUCCESS;
    493 
    494   DCHECK_NT(address_);
    495   DCHECK_NT(bytes_);
    496 
    497   SIZE_T new_bytes = bytes_;
    498   NTSTATUS ret = g_nt.ProtectVirtualMemory(NtCurrentProcess, &address_,
    499                                            &new_bytes, old_protect_,
    500                                            &old_protect_);
    501   DCHECK_NT(NT_SUCCESS(ret));
    502 
    503   changed_ = false;
    504   address_ = NULL;
    505   bytes_ = 0;
    506   old_protect_ = 0;
    507 
    508   return ret;
    509 }
    510 
    511 bool IsSupportedRenameCall(FILE_RENAME_INFORMATION* file_info, DWORD length,
    512                            uint32 file_info_class) {
    513   if (FileRenameInformation != file_info_class)
    514     return false;
    515 
    516   if (length < sizeof(FILE_RENAME_INFORMATION))
    517     return false;
    518 
    519   // Make sure file name length doesn't exceed the message length
    520   if (length - offsetof(FILE_RENAME_INFORMATION, FileName) <
    521       file_info->FileNameLength)
    522     return false;
    523 
    524   // We don't support a root directory.
    525   if (file_info->RootDirectory)
    526     return false;
    527 
    528   static const wchar_t kPathPrefix[] = { L'\\', L'?', L'?', L'\\'};
    529 
    530   // Check if it starts with \\??\\. We don't support relative paths.
    531   if (file_info->FileNameLength < sizeof(kPathPrefix) ||
    532       file_info->FileNameLength > kuint16max)
    533     return false;
    534 
    535   if (file_info->FileName[0] != kPathPrefix[0] ||
    536       file_info->FileName[1] != kPathPrefix[1] ||
    537       file_info->FileName[2] != kPathPrefix[2] ||
    538       file_info->FileName[3] != kPathPrefix[3])
    539     return false;
    540 
    541   return true;
    542 }
    543 
    544 }  // namespace sandbox
    545 
    546 void* operator new(size_t size, sandbox::AllocationType type,
    547                    void* near_to) {
    548   using namespace sandbox;
    549 
    550   void* result = NULL;
    551   if (NT_ALLOC == type) {
    552     if (InitHeap()) {
    553       // Use default flags for the allocation.
    554       result = g_nt.RtlAllocateHeap(sandbox::g_heap, 0, size);
    555     }
    556   } else if (NT_PAGE == type) {
    557     result = AllocateNearTo(near_to, size);
    558   } else {
    559     NOTREACHED_NT();
    560   }
    561 
    562   // TODO: Returning NULL from operator new has undefined behavior, but
    563   // the Allocate() functions called above can return NULL. Consider checking
    564   // for NULL here and crashing or throwing.
    565 
    566   return result;
    567 }
    568 
    569 void operator delete(void* memory, sandbox::AllocationType type) {
    570   using namespace sandbox;
    571 
    572   if (NT_ALLOC == type) {
    573     // Use default flags.
    574     VERIFY(g_nt.RtlFreeHeap(sandbox::g_heap, 0, memory));
    575   } else if (NT_PAGE == type) {
    576     void* base = memory;
    577     SIZE_T size = 0;
    578     VERIFY_SUCCESS(g_nt.FreeVirtualMemory(NtCurrentProcess, &base, &size,
    579                                           MEM_RELEASE));
    580   } else {
    581     NOTREACHED_NT();
    582   }
    583 }
    584 
    585 void operator delete(void* memory, sandbox::AllocationType type,
    586                      void* near_to) {
    587   UNREFERENCED_PARAMETER(near_to);
    588   operator delete(memory, type);
    589 }
    590 
    591 void* __cdecl operator new(size_t size, void* buffer,
    592                            sandbox::AllocationType type) {
    593   UNREFERENCED_PARAMETER(size);
    594   UNREFERENCED_PARAMETER(type);
    595   return buffer;
    596 }
    597 
    598 void __cdecl operator delete(void* memory, void* buffer,
    599                              sandbox::AllocationType type) {
    600   UNREFERENCED_PARAMETER(memory);
    601   UNREFERENCED_PARAMETER(buffer);
    602   UNREFERENCED_PARAMETER(type);
    603 }
    604