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