security/sandbox/win/src/sandbox_nt_util.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 #include "sandbox/win/src/sandbox_nt_util.h"
     7 #include "base/win/pe_image.h"
     8 #include "sandbox/win/src/sandbox_factory.h"
     9 #include "sandbox/win/src/target_services.h"
    11 namespace sandbox {
    13 // This is the list of all imported symbols from ntdll.dll.
    14 SANDBOX_INTERCEPT NtExports g_nt = { NULL };
    16 }  // namespace sandbox
    18 namespace {
    20 #if defined(_WIN64)
    21 void* AllocateNearTo(void* source, size_t size) {
    22   using sandbox::g_nt;
    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;
    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     }
    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     }
    56     // Try 100 MB higher.
    57     base = reinterpret_cast<char*>(base) + 100 * 0x100000;
    58   };
    60   if (attempts == 41)
    61     return NULL;
    63   ret = g_nt.AllocateVirtualMemory(NtCurrentProcess, &base, zero_bits,
    64                                    &actual_size, MEM_COMMIT, PAGE_READWRITE);
    66   if (!NT_SUCCESS(ret)) {
    67     VERIFY_SUCCESS(g_nt.FreeVirtualMemory(NtCurrentProcess, &base, &size,
    68                                           MEM_RELEASE));
    69     base = NULL;
    70   }
    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);
    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);
    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).
   103 }  // namespace.
   105 namespace sandbox {
   107 // Handle for our private heap.
   108 void* g_heap = NULL;
   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;
   114 void* volatile g_shared_policy_memory = NULL;
   115 void* volatile g_shared_IPC_memory = NULL;
   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);
   128     if (!NT_SUCCESS(ret) || NULL == memory) {
   129       NOTREACHED_NT();
   130       return false;
   131     }
   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 }
   148 void* GetGlobalIPCMemory() {
   149   if (!MapGlobalMemory())
   150     return NULL;
   151   return g_shared_IPC_memory;
   152 }
   154 void* GetGlobalPolicyMemory() {
   155   if (!MapGlobalMemory())
   156     return NULL;
   157   return g_shared_policy_memory;
   158 }
   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;
   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 }
   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;
   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   }
   195   return dummy;
   196 }
   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 }
   208 NTSTATUS CopyData(void* destination, const void* source, size_t bytes) {
   209   NTSTATUS ret = STATUS_SUCCESS;
   210   __try {
   211     if (SandboxFactory::GetTargetServices()->GetState()->InitCalled()) {
   212       memcpy(destination, source, bytes);
   213     } else {
   214       const char* from = reinterpret_cast<const char*>(source);
   215       char* to = reinterpret_cast<char*>(destination);
   216       for (size_t i = 0; i < bytes; i++) {
   217         to[i] = from[i];
   218       }
   219     }
   220   } __except(EXCEPTION_EXECUTE_HANDLER) {
   221     ret = GetExceptionCode();
   222   }
   223   return ret;
   224 }
   226 // Hacky code... replace with AllocAndCopyObjectAttributes.
   227 NTSTATUS AllocAndCopyName(const OBJECT_ATTRIBUTES* in_object,
   228                           wchar_t** out_name, uint32* attributes,
   229                           HANDLE* root) {
   230   if (!InitHeap())
   231     return STATUS_NO_MEMORY;
   233   DCHECK_NT(out_name);
   234   *out_name = NULL;
   235   NTSTATUS ret = STATUS_UNSUCCESSFUL;
   236   __try {
   237     do {
   238       if (in_object->RootDirectory != static_cast<HANDLE>(0) && !root)
   239         break;
   240       if (NULL == in_object->ObjectName)
   241         break;
   242       if (NULL == in_object->ObjectName->Buffer)
   243         break;
   245       size_t size = in_object->ObjectName->Length + sizeof(wchar_t);
   246       *out_name = new(NT_ALLOC) wchar_t[size/sizeof(wchar_t)];
   247       if (NULL == *out_name)
   248         break;
   250       ret = CopyData(*out_name, in_object->ObjectName->Buffer,
   251                      size - sizeof(wchar_t));
   252       if (!NT_SUCCESS(ret))
   253         break;
   255       (*out_name)[size / sizeof(wchar_t) - 1] = L'\0';
   257       if (attributes)
   258         *attributes = in_object->Attributes;
   260       if (root)
   261         *root = in_object->RootDirectory;
   262       ret = STATUS_SUCCESS;
   263     } while (false);
   264   } __except(EXCEPTION_EXECUTE_HANDLER) {
   265     ret = GetExceptionCode();
   266   }
   268   if (!NT_SUCCESS(ret) && *out_name) {
   269     operator delete(*out_name, NT_ALLOC);
   270     *out_name = NULL;
   271   }
   273   return ret;
   274 }
   276 NTSTATUS GetProcessId(HANDLE process, ULONG *process_id) {
   277   PROCESS_BASIC_INFORMATION proc_info;
   278   ULONG bytes_returned;
   280   NTSTATUS ret = g_nt.QueryInformationProcess(process, ProcessBasicInformation,
   281                                               &proc_info, sizeof(proc_info),
   282                                               &bytes_returned);
   283   if (!NT_SUCCESS(ret) || sizeof(proc_info) != bytes_returned)
   284     return ret;
   286   *process_id = proc_info.UniqueProcessId;
   287   return STATUS_SUCCESS;
   288 }
   290 bool IsSameProcess(HANDLE process) {
   291   if (NtCurrentProcess == process)
   292     return true;
   294   static ULONG s_process_id = 0;
   296   if (!s_process_id) {
   297     NTSTATUS ret = GetProcessId(NtCurrentProcess, &s_process_id);
   298     if (!NT_SUCCESS(ret))
   299       return false;
   300   }
   302   ULONG process_id;
   303   NTSTATUS ret = GetProcessId(process, &process_id);
   304   if (!NT_SUCCESS(ret))
   305     return false;
   307   return (process_id == s_process_id);
   308 }
   310 bool IsValidImageSection(HANDLE section, PVOID *base, PLARGE_INTEGER offset,
   311                          PSIZE_T view_size) {
   312   if (!section || !base || !view_size || offset)
   313     return false;
   315   HANDLE query_section;
   317   NTSTATUS ret = g_nt.DuplicateObject(NtCurrentProcess, section,
   318                                       NtCurrentProcess, &query_section,
   319                                       SECTION_QUERY, 0, 0);
   320   if (!NT_SUCCESS(ret))
   321     return false;
   323   SECTION_BASIC_INFORMATION basic_info;
   324   SIZE_T bytes_returned;
   325   ret = g_nt.QuerySection(query_section, SectionBasicInformation, &basic_info,
   326                           sizeof(basic_info), &bytes_returned);
   328   VERIFY_SUCCESS(g_nt.Close(query_section));
   330   if (!NT_SUCCESS(ret) || sizeof(basic_info) != bytes_returned)
   331     return false;
   333   if (!(basic_info.Attributes & SEC_IMAGE))
   334     return false;
   336   return true;
   337 }
   339 UNICODE_STRING* AnsiToUnicode(const char* string) {
   340   ANSI_STRING ansi_string;
   341   ansi_string.Length = static_cast<USHORT>(g_nt.strlen(string));
   342   ansi_string.MaximumLength = ansi_string.Length + 1;
   343   ansi_string.Buffer = const_cast<char*>(string);
   345   if (ansi_string.Length > ansi_string.MaximumLength)
   346     return NULL;
   348   size_t name_bytes = ansi_string.MaximumLength * sizeof(wchar_t) +
   349                       sizeof(UNICODE_STRING);
   351   UNICODE_STRING* out_string = reinterpret_cast<UNICODE_STRING*>(
   352                                    new(NT_ALLOC) char[name_bytes]);
   353   if (!out_string)
   354     return NULL;
   356   out_string->MaximumLength = ansi_string.MaximumLength *  sizeof(wchar_t);
   357   out_string->Buffer = reinterpret_cast<wchar_t*>(&out_string[1]);
   359   BOOLEAN alloc_destination = FALSE;
   360   NTSTATUS ret = g_nt.RtlAnsiStringToUnicodeString(out_string, &ansi_string,
   361                                                    alloc_destination);
   362   DCHECK_NT(STATUS_BUFFER_OVERFLOW != ret);
   363   if (!NT_SUCCESS(ret)) {
   364     operator delete(out_string, NT_ALLOC);
   365     return NULL;
   366   }
   368   return out_string;
   369 }
   371 UNICODE_STRING* GetImageInfoFromModule(HMODULE module, uint32* flags) {
   372   UNICODE_STRING* out_name = NULL;
   373   __try {
   374     do {
   375       *flags = 0;
   376       base::win::PEImage pe(module);
   378       if (!pe.VerifyMagic())
   379         break;
   380       *flags |= MODULE_IS_PE_IMAGE;
   382       PIMAGE_EXPORT_DIRECTORY exports = pe.GetExportDirectory();
   383       if (exports) {
   384         char* name = reinterpret_cast<char*>(pe.RVAToAddr(exports->Name));
   385         out_name = AnsiToUnicode(name);
   386       }
   388       PIMAGE_NT_HEADERS headers = pe.GetNTHeaders();
   389       if (headers) {
   390         if (headers->OptionalHeader.AddressOfEntryPoint)
   391           *flags |= MODULE_HAS_ENTRY_POINT;
   392         if (headers->OptionalHeader.SizeOfCode)
   393           *flags |= MODULE_HAS_CODE;
   394       }
   395     } while (false);
   396   } __except(EXCEPTION_EXECUTE_HANDLER) {
   397   }
   399   return out_name;
   400 }
   402 UNICODE_STRING* GetBackingFilePath(PVOID address) {
   403   // We'll start with something close to max_path charactes for the name.
   404   ULONG buffer_bytes = MAX_PATH * 2;
   406   for (;;) {
   407     MEMORY_SECTION_NAME* section_name = reinterpret_cast<MEMORY_SECTION_NAME*>(
   408         new(NT_ALLOC) char[buffer_bytes]);
   410     if (!section_name)
   411       return NULL;
   413     ULONG returned_bytes;
   414     NTSTATUS ret = g_nt.QueryVirtualMemory(NtCurrentProcess, address,
   415                                            MemorySectionName, section_name,
   416                                            buffer_bytes, &returned_bytes);
   418     if (STATUS_BUFFER_OVERFLOW == ret) {
   419       // Retry the call with the given buffer size.
   420       operator delete(section_name, NT_ALLOC);
   421       section_name = NULL;
   422       buffer_bytes = returned_bytes;
   423       continue;
   424     }
   425     if (!NT_SUCCESS(ret)) {
   426       operator delete(section_name, NT_ALLOC);
   427       return NULL;
   428     }
   430     return reinterpret_cast<UNICODE_STRING*>(section_name);
   431   }
   432 }
   434 UNICODE_STRING* ExtractModuleName(const UNICODE_STRING* module_path) {
   435   if ((!module_path) || (!module_path->Buffer))
   436     return NULL;
   438   wchar_t* sep = NULL;
   439   int start_pos = module_path->Length / sizeof(wchar_t) - 1;
   440   int ix = start_pos;
   442   for (; ix >= 0; --ix) {
   443     if (module_path->Buffer[ix] == L'\\') {
   444       sep = &module_path->Buffer[ix];
   445       break;
   446     }
   447   }
   449   // Ends with path separator. Not a valid module name.
   450   if ((ix == start_pos) && sep)
   451     return NULL;
   453   // No path separator found. Use the entire name.
   454   if (!sep) {
   455     sep = &module_path->Buffer[-1];
   456   }
   458   // Add one to the size so we can null terminate the string.
   459   size_t size_bytes = (start_pos - ix + 1) * sizeof(wchar_t);
   461   // Based on the code above, size_bytes should always be small enough
   462   // to make the static_cast below safe.
   463   DCHECK_NT(kuint16max > size_bytes);
   464   char* str_buffer = new(NT_ALLOC) char[size_bytes + sizeof(UNICODE_STRING)];
   465   if (!str_buffer)
   466     return NULL;
   468   UNICODE_STRING* out_string = reinterpret_cast<UNICODE_STRING*>(str_buffer);
   469   out_string->Buffer = reinterpret_cast<wchar_t*>(&out_string[1]);
   470   out_string->Length = static_cast<USHORT>(size_bytes - sizeof(wchar_t));
   471   out_string->MaximumLength = static_cast<USHORT>(size_bytes);
   473   NTSTATUS ret = CopyData(out_string->Buffer, &sep[1], out_string->Length);
   474   if (!NT_SUCCESS(ret)) {
   475     operator delete(out_string, NT_ALLOC);
   476     return NULL;
   477   }
   479   out_string->Buffer[out_string->Length / sizeof(wchar_t)] = L'\0';
   480   return out_string;
   481 }
   483 NTSTATUS AutoProtectMemory::ChangeProtection(void* address, size_t bytes,
   484                                              ULONG protect) {
   485   DCHECK_NT(!changed_);
   486   SIZE_T new_bytes = bytes;
   487   NTSTATUS ret = g_nt.ProtectVirtualMemory(NtCurrentProcess, &address,
   488                                            &new_bytes, protect, &old_protect_);
   489   if (NT_SUCCESS(ret)) {
   490     changed_ = true;
   491     address_ = address;
   492     bytes_ = new_bytes;
   493   }
   495   return ret;
   496 }
   498 NTSTATUS AutoProtectMemory::RevertProtection() {
   499   if (!changed_)
   500     return STATUS_SUCCESS;
   502   DCHECK_NT(address_);
   503   DCHECK_NT(bytes_);
   505   SIZE_T new_bytes = bytes_;
   506   NTSTATUS ret = g_nt.ProtectVirtualMemory(NtCurrentProcess, &address_,
   507                                            &new_bytes, old_protect_,
   508                                            &old_protect_);
   509   DCHECK_NT(NT_SUCCESS(ret));
   511   changed_ = false;
   512   address_ = NULL;
   513   bytes_ = 0;
   514   old_protect_ = 0;
   516   return ret;
   517 }
   519 bool IsSupportedRenameCall(FILE_RENAME_INFORMATION* file_info, DWORD length,
   520                            uint32 file_info_class) {
   521   if (FileRenameInformation != file_info_class)
   522     return false;
   524   if (length < sizeof(FILE_RENAME_INFORMATION))
   525     return false;
   527   // Make sure file name length doesn't exceed the message length
   528   if (length - offsetof(FILE_RENAME_INFORMATION, FileName) <
   529       file_info->FileNameLength)
   530     return false;
   532   // We don't support a root directory.
   533   if (file_info->RootDirectory)
   534     return false;
   536   // Check if it starts with \\??\\. We don't support relative paths.
   537   if (file_info->FileNameLength < 4 || file_info->FileNameLength > kuint16max)
   538     return false;
   540   if (file_info->FileName[0] != L'\\' ||
   541       file_info->FileName[1] != L'?' ||
   542       file_info->FileName[2] != L'?' ||
   543       file_info->FileName[3] != L'\\')
   544     return false;
   546   return true;
   547 }
   549 }  // namespace sandbox
   551 void* operator new(size_t size, sandbox::AllocationType type,
   552                    void* near_to) {
   553   using namespace sandbox;
   555   if (NT_ALLOC == type) {
   556     if (!InitHeap())
   557       return NULL;
   559     // Use default flags for the allocation.
   560     return g_nt.RtlAllocateHeap(sandbox::g_heap, 0, size);
   561   } else if (NT_PAGE == type) {
   562     return AllocateNearTo(near_to, size);
   563   }
   564   NOTREACHED_NT();
   565   return NULL;
   566 }
   568 void operator delete(void* memory, sandbox::AllocationType type) {
   569   using namespace sandbox;
   571   if (NT_ALLOC == type) {
   572     // Use default flags.
   573     VERIFY(g_nt.RtlFreeHeap(sandbox::g_heap, 0, memory));
   574   } else if (NT_PAGE == type) {
   575     void* base = memory;
   576     SIZE_T size = 0;
   577     VERIFY_SUCCESS(g_nt.FreeVirtualMemory(NtCurrentProcess, &base, &size,
   578                                           MEM_RELEASE));
   579   } else {
   580     NOTREACHED_NT();
   581   }
   582 }
   584 void operator delete(void* memory, sandbox::AllocationType type,
   585                      void* near_to) {
   586   UNREFERENCED_PARAMETER(near_to);
   587   operator delete(memory, type);
   588 }
   590 void* __cdecl operator new(size_t size, void* buffer,
   591                            sandbox::AllocationType type) {
   592   UNREFERENCED_PARAMETER(size);
   593   UNREFERENCED_PARAMETER(type);
   594   return buffer;
   595 }
   597 void __cdecl operator delete(void* memory, void* buffer,
   598                              sandbox::AllocationType type) {
   599   UNREFERENCED_PARAMETER(memory);
   600   UNREFERENCED_PARAMETER(buffer);
   601   UNREFERENCED_PARAMETER(type);
   602 }

mercurial