• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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)
AllocateNearTo(void * source,size_t size)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.
MapGlobalMemory()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 
GetGlobalIPCMemory()148 void* GetGlobalIPCMemory() {
149   if (!MapGlobalMemory())
150     return NULL;
151   return g_shared_IPC_memory;
152 }
153 
GetGlobalPolicyMemory()154 void* GetGlobalPolicyMemory() {
155   if (!MapGlobalMemory())
156     return NULL;
157   return g_shared_policy_memory;
158 }
159 
InitHeap()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.
TouchMemory(void * buffer,size_t size_bytes,RequiredAccess intent)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 
ValidParameter(void * buffer,size_t size,RequiredAccess intent)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 
CopyData(void * destination,const void * source,size_t bytes)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.
AllocAndCopyName(const OBJECT_ATTRIBUTES * in_object,wchar_t ** out_name,uint32 * attributes,HANDLE * root)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 
GetProcessId(HANDLE process,ULONG * process_id)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 
IsSameProcess(HANDLE process)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 
IsValidImageSection(HANDLE section,PVOID * base,PLARGE_INTEGER offset,PSIZE_T view_size)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 
AnsiToUnicode(const char * string)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 
GetImageInfoFromModule(HMODULE module,uint32 * flags)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 
GetBackingFilePath(PVOID address)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 
ExtractModuleName(const UNICODE_STRING * module_path)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 
ChangeProtection(void * address,size_t bytes,ULONG protect)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 
RevertProtection()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 
IsSupportedRenameCall(FILE_RENAME_INFORMATION * file_info,DWORD length,uint32 file_info_class)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 
operator new(size_t size,sandbox::AllocationType type,void * near_to)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 
operator delete(void * memory,sandbox::AllocationType type)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 
operator delete(void * memory,sandbox::AllocationType type,void * near_to)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 
operator new(size_t size,void * buffer,sandbox::AllocationType type)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 
operator delete(void * memory,void * buffer,sandbox::AllocationType type)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