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"
13 // This is the list of all imported symbols from ntdll.dll.
14 SANDBOX_INTERCEPT NtExports g_nt
;
16 } // namespace sandbox
21 void* AllocateNearTo(void* source
, size_t size
) {
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
;
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
)) {
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
,
49 base
= reinterpret_cast<char*>(source
);
50 } else if (attempts
== 40) {
51 // Try the highest available address.
57 base
= reinterpret_cast<char*>(base
) + 100 * 0x100000;
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
,
74 #else // defined(_WIN64).
75 void* AllocateNearTo(void* source
, size_t size
) {
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);
87 actual_size
= 64 * 1024;
88 ret
= g_nt
.AllocateVirtualMemory(NtCurrentProcess
, &base
, 0, &actual_size
,
89 MEM_RESERVE
, PAGE_NOACCESS
);
92 } while (base
< kMinAddress
);
95 ret
= g_nt
.AllocateVirtualMemory(NtCurrentProcess
, &base
, 0, &actual_size
,
96 MEM_COMMIT
, PAGE_READWRITE
);
101 #endif // defined(_WIN64).
107 // Handle for our private heap.
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
) {
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
,
128 if (!NT_SUCCESS(ret
) || NULL
== memory
) {
133 if (NULL
!= _InterlockedCompareExchangePointer(&g_shared_IPC_memory
,
135 // Somebody beat us to the memory setup.
136 ret
= g_nt
.UnmapViewOfSection(NtCurrentProcess
, memory
);
139 DCHECK_NT(g_shared_IPC_size
> 0);
140 g_shared_policy_memory
= reinterpret_cast<char*>(g_shared_IPC_memory
)
143 DCHECK_NT(g_shared_policy_memory
);
144 DCHECK_NT(g_shared_policy_size
> 0);
148 void* GetGlobalIPCMemory() {
149 if (!MapGlobalMemory())
151 return g_shared_IPC_memory
;
154 void* GetGlobalPolicyMemory() {
155 if (!MapGlobalMemory())
157 return g_shared_policy_memory
;
162 // Create a new heap using default values for everything.
163 void* heap
= g_nt
.RtlCreateHeap(HEAP_GROWABLE
, NULL
, 0, 0, NULL
, NULL
);
167 if (NULL
!= _InterlockedCompareExchangePointer(&g_heap
, heap
, NULL
)) {
168 // Somebody beat us to the memory setup.
169 g_nt
.RtlDestroyHeap(heap
);
172 return (g_heap
!= NULL
);
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;
180 char* start
= reinterpret_cast<char*>(buffer
);
181 char* end
= start
+ size_bytes
- 1;
183 if (WRITE
== intent
) {
184 for (; start
< end
; start
+= kPageSize
) {
189 for (; start
< end
; start
+= kPageSize
) {
198 bool ValidParameter(void* buffer
, size_t size
, RequiredAccess intent
) {
201 TouchMemory(buffer
, size
, intent
);
202 } __except(EXCEPTION_EXECUTE_HANDLER
) {
208 NTSTATUS
CopyData(void* destination
, const void* source
, size_t bytes
) {
209 NTSTATUS ret
= STATUS_SUCCESS
;
211 g_nt
.memcpy(destination
, source
, bytes
);
212 } __except(EXCEPTION_EXECUTE_HANDLER
) {
213 ret
= GetExceptionCode();
218 NTSTATUS
AllocAndGetFullPath(HANDLE root
,
220 wchar_t** full_path
) {
222 return STATUS_NO_MEMORY
;
224 DCHECK_NT(full_path
);
227 OBJECT_NAME_INFORMATION
* handle_name
= NULL
;
228 NTSTATUS ret
= STATUS_UNSUCCESSFUL
;
231 static NtQueryObjectFunction NtQueryObject
= NULL
;
233 ResolveNTFunctionPtr("NtQueryObject", &NtQueryObject
);
236 // Query the name information a first time to get the size of the name.
237 ret
= NtQueryObject(root
, ObjectNameInformation
, NULL
, 0, &size
);
240 handle_name
= reinterpret_cast<OBJECT_NAME_INFORMATION
*>(
241 new(NT_ALLOC
) BYTE
[size
]);
243 // Query the name information a second time to get the name of the
244 // object referenced by the handle.
245 ret
= NtQueryObject(root
, ObjectNameInformation
, handle_name
, size
,
249 if (STATUS_SUCCESS
!= ret
)
252 // Space for path + '\' + name + '\0'.
253 size_t name_length
= handle_name
->ObjectName
.Length
+
254 (wcslen(path
) + 2) * sizeof(wchar_t);
255 *full_path
= new(NT_ALLOC
) wchar_t[name_length
/sizeof(wchar_t)];
256 if (NULL
== *full_path
)
258 wchar_t* off
= *full_path
;
259 ret
= CopyData(off
, handle_name
->ObjectName
.Buffer
,
260 handle_name
->ObjectName
.Length
);
261 if (!NT_SUCCESS(ret
))
263 off
+= handle_name
->ObjectName
.Length
/ sizeof(wchar_t);
266 ret
= CopyData(off
, path
, wcslen(path
) * sizeof(wchar_t));
267 if (!NT_SUCCESS(ret
))
272 } __except(EXCEPTION_EXECUTE_HANDLER
) {
273 ret
= GetExceptionCode();
276 if (!NT_SUCCESS(ret
)) {
278 operator delete(*full_path
, NT_ALLOC
);
282 operator delete(handle_name
, NT_ALLOC
);
290 // Hacky code... replace with AllocAndCopyObjectAttributes.
291 NTSTATUS
AllocAndCopyName(const OBJECT_ATTRIBUTES
* in_object
,
292 wchar_t** out_name
, uint32
* attributes
,
295 return STATUS_NO_MEMORY
;
299 NTSTATUS ret
= STATUS_UNSUCCESSFUL
;
302 if (in_object
->RootDirectory
!= static_cast<HANDLE
>(0) && !root
)
304 if (NULL
== in_object
->ObjectName
)
306 if (NULL
== in_object
->ObjectName
->Buffer
)
309 size_t size
= in_object
->ObjectName
->Length
+ sizeof(wchar_t);
310 *out_name
= new(NT_ALLOC
) wchar_t[size
/sizeof(wchar_t)];
311 if (NULL
== *out_name
)
314 ret
= CopyData(*out_name
, in_object
->ObjectName
->Buffer
,
315 size
- sizeof(wchar_t));
316 if (!NT_SUCCESS(ret
))
319 (*out_name
)[size
/ sizeof(wchar_t) - 1] = L
'\0';
322 *attributes
= in_object
->Attributes
;
325 *root
= in_object
->RootDirectory
;
326 ret
= STATUS_SUCCESS
;
328 } __except(EXCEPTION_EXECUTE_HANDLER
) {
329 ret
= GetExceptionCode();
332 if (!NT_SUCCESS(ret
) && *out_name
) {
333 operator delete(*out_name
, NT_ALLOC
);
340 NTSTATUS
GetProcessId(HANDLE process
, ULONG
*process_id
) {
341 PROCESS_BASIC_INFORMATION proc_info
;
342 ULONG bytes_returned
;
344 NTSTATUS ret
= g_nt
.QueryInformationProcess(process
, ProcessBasicInformation
,
345 &proc_info
, sizeof(proc_info
),
347 if (!NT_SUCCESS(ret
) || sizeof(proc_info
) != bytes_returned
)
350 *process_id
= proc_info
.UniqueProcessId
;
351 return STATUS_SUCCESS
;
354 bool IsSameProcess(HANDLE process
) {
355 if (NtCurrentProcess
== process
)
358 static ULONG s_process_id
= 0;
361 NTSTATUS ret
= GetProcessId(NtCurrentProcess
, &s_process_id
);
362 if (!NT_SUCCESS(ret
))
367 NTSTATUS ret
= GetProcessId(process
, &process_id
);
368 if (!NT_SUCCESS(ret
))
371 return (process_id
== s_process_id
);
374 bool IsValidImageSection(HANDLE section
, PVOID
*base
, PLARGE_INTEGER offset
,
376 if (!section
|| !base
|| !view_size
|| offset
)
379 HANDLE query_section
;
381 NTSTATUS ret
= g_nt
.DuplicateObject(NtCurrentProcess
, section
,
382 NtCurrentProcess
, &query_section
,
383 SECTION_QUERY
, 0, 0);
384 if (!NT_SUCCESS(ret
))
387 SECTION_BASIC_INFORMATION basic_info
;
388 SIZE_T bytes_returned
;
389 ret
= g_nt
.QuerySection(query_section
, SectionBasicInformation
, &basic_info
,
390 sizeof(basic_info
), &bytes_returned
);
392 VERIFY_SUCCESS(g_nt
.Close(query_section
));
394 if (!NT_SUCCESS(ret
) || sizeof(basic_info
) != bytes_returned
)
397 if (!(basic_info
.Attributes
& SEC_IMAGE
))
403 UNICODE_STRING
* AnsiToUnicode(const char* string
) {
404 ANSI_STRING ansi_string
;
405 ansi_string
.Length
= static_cast<USHORT
>(g_nt
.strlen(string
));
406 ansi_string
.MaximumLength
= ansi_string
.Length
+ 1;
407 ansi_string
.Buffer
= const_cast<char*>(string
);
409 if (ansi_string
.Length
> ansi_string
.MaximumLength
)
412 size_t name_bytes
= ansi_string
.MaximumLength
* sizeof(wchar_t) +
413 sizeof(UNICODE_STRING
);
415 UNICODE_STRING
* out_string
= reinterpret_cast<UNICODE_STRING
*>(
416 new(NT_ALLOC
) char[name_bytes
]);
420 out_string
->MaximumLength
= ansi_string
.MaximumLength
* sizeof(wchar_t);
421 out_string
->Buffer
= reinterpret_cast<wchar_t*>(&out_string
[1]);
423 BOOLEAN alloc_destination
= FALSE
;
424 NTSTATUS ret
= g_nt
.RtlAnsiStringToUnicodeString(out_string
, &ansi_string
,
426 DCHECK_NT(STATUS_BUFFER_OVERFLOW
!= ret
);
427 if (!NT_SUCCESS(ret
)) {
428 operator delete(out_string
, NT_ALLOC
);
435 UNICODE_STRING
* GetImageInfoFromModule(HMODULE module
, uint32
* flags
) {
436 // PEImage's dtor won't be run during SEH unwinding, but that's OK.
437 #pragma warning(push)
438 #pragma warning(disable: 4509)
439 UNICODE_STRING
* out_name
= NULL
;
443 base::win::PEImage
pe(module
);
445 if (!pe
.VerifyMagic())
447 *flags
|= MODULE_IS_PE_IMAGE
;
449 PIMAGE_EXPORT_DIRECTORY exports
= pe
.GetExportDirectory();
451 char* name
= reinterpret_cast<char*>(pe
.RVAToAddr(exports
->Name
));
452 out_name
= AnsiToUnicode(name
);
455 PIMAGE_NT_HEADERS headers
= pe
.GetNTHeaders();
457 if (headers
->OptionalHeader
.AddressOfEntryPoint
)
458 *flags
|= MODULE_HAS_ENTRY_POINT
;
459 if (headers
->OptionalHeader
.SizeOfCode
)
460 *flags
|= MODULE_HAS_CODE
;
463 } __except(EXCEPTION_EXECUTE_HANDLER
) {
470 UNICODE_STRING
* GetBackingFilePath(PVOID address
) {
471 // We'll start with something close to max_path charactes for the name.
472 SIZE_T buffer_bytes
= MAX_PATH
* 2;
475 MEMORY_SECTION_NAME
* section_name
= reinterpret_cast<MEMORY_SECTION_NAME
*>(
476 new(NT_ALLOC
) char[buffer_bytes
]);
481 SIZE_T returned_bytes
;
482 NTSTATUS ret
= g_nt
.QueryVirtualMemory(NtCurrentProcess
, address
,
483 MemorySectionName
, section_name
,
484 buffer_bytes
, &returned_bytes
);
486 if (STATUS_BUFFER_OVERFLOW
== ret
) {
487 // Retry the call with the given buffer size.
488 operator delete(section_name
, NT_ALLOC
);
490 buffer_bytes
= returned_bytes
;
493 if (!NT_SUCCESS(ret
)) {
494 operator delete(section_name
, NT_ALLOC
);
498 return reinterpret_cast<UNICODE_STRING
*>(section_name
);
502 UNICODE_STRING
* ExtractModuleName(const UNICODE_STRING
* module_path
) {
503 if ((!module_path
) || (!module_path
->Buffer
))
507 int start_pos
= module_path
->Length
/ sizeof(wchar_t) - 1;
510 for (; ix
>= 0; --ix
) {
511 if (module_path
->Buffer
[ix
] == L
'\\') {
512 sep
= &module_path
->Buffer
[ix
];
517 // Ends with path separator. Not a valid module name.
518 if ((ix
== start_pos
) && sep
)
521 // No path separator found. Use the entire name.
523 sep
= &module_path
->Buffer
[-1];
526 // Add one to the size so we can null terminate the string.
527 size_t size_bytes
= (start_pos
- ix
+ 1) * sizeof(wchar_t);
529 // Based on the code above, size_bytes should always be small enough
530 // to make the static_cast below safe.
531 DCHECK_NT(kuint16max
> size_bytes
);
532 char* str_buffer
= new(NT_ALLOC
) char[size_bytes
+ sizeof(UNICODE_STRING
)];
536 UNICODE_STRING
* out_string
= reinterpret_cast<UNICODE_STRING
*>(str_buffer
);
537 out_string
->Buffer
= reinterpret_cast<wchar_t*>(&out_string
[1]);
538 out_string
->Length
= static_cast<USHORT
>(size_bytes
- sizeof(wchar_t));
539 out_string
->MaximumLength
= static_cast<USHORT
>(size_bytes
);
541 NTSTATUS ret
= CopyData(out_string
->Buffer
, &sep
[1], out_string
->Length
);
542 if (!NT_SUCCESS(ret
)) {
543 operator delete(out_string
, NT_ALLOC
);
547 out_string
->Buffer
[out_string
->Length
/ sizeof(wchar_t)] = L
'\0';
551 NTSTATUS
AutoProtectMemory::ChangeProtection(void* address
, size_t bytes
,
553 DCHECK_NT(!changed_
);
554 SIZE_T new_bytes
= bytes
;
555 NTSTATUS ret
= g_nt
.ProtectVirtualMemory(NtCurrentProcess
, &address
,
556 &new_bytes
, protect
, &old_protect_
);
557 if (NT_SUCCESS(ret
)) {
566 NTSTATUS
AutoProtectMemory::RevertProtection() {
568 return STATUS_SUCCESS
;
573 SIZE_T new_bytes
= bytes_
;
574 NTSTATUS ret
= g_nt
.ProtectVirtualMemory(NtCurrentProcess
, &address_
,
575 &new_bytes
, old_protect_
,
577 DCHECK_NT(NT_SUCCESS(ret
));
587 bool IsSupportedRenameCall(FILE_RENAME_INFORMATION
* file_info
, DWORD length
,
588 uint32 file_info_class
) {
589 if (FileRenameInformation
!= file_info_class
)
592 if (length
< sizeof(FILE_RENAME_INFORMATION
))
595 // Make sure file name length doesn't exceed the message length
596 if (length
- offsetof(FILE_RENAME_INFORMATION
, FileName
) <
597 file_info
->FileNameLength
)
600 // We don't support a root directory.
601 if (file_info
->RootDirectory
)
604 static const wchar_t kPathPrefix
[] = { L
'\\', L
'?', L
'?', L
'\\'};
606 // Check if it starts with \\??\\. We don't support relative paths.
607 if (file_info
->FileNameLength
< sizeof(kPathPrefix
) ||
608 file_info
->FileNameLength
> kuint16max
)
611 if (file_info
->FileName
[0] != kPathPrefix
[0] ||
612 file_info
->FileName
[1] != kPathPrefix
[1] ||
613 file_info
->FileName
[2] != kPathPrefix
[2] ||
614 file_info
->FileName
[3] != kPathPrefix
[3])
620 } // namespace sandbox
622 void* operator new(size_t size
, sandbox::AllocationType type
,
624 using namespace sandbox
;
627 if (NT_ALLOC
== type
) {
629 // Use default flags for the allocation.
630 result
= g_nt
.RtlAllocateHeap(sandbox::g_heap
, 0, size
);
632 } else if (NT_PAGE
== type
) {
633 result
= AllocateNearTo(near_to
, size
);
638 // TODO: Returning NULL from operator new has undefined behavior, but
639 // the Allocate() functions called above can return NULL. Consider checking
640 // for NULL here and crashing or throwing.
645 void operator delete(void* memory
, sandbox::AllocationType type
) {
646 using namespace sandbox
;
648 if (NT_ALLOC
== type
) {
649 // Use default flags.
650 VERIFY(g_nt
.RtlFreeHeap(sandbox::g_heap
, 0, memory
));
651 } else if (NT_PAGE
== type
) {
654 VERIFY_SUCCESS(g_nt
.FreeVirtualMemory(NtCurrentProcess
, &base
, &size
,
661 void operator delete(void* memory
, sandbox::AllocationType type
,
663 UNREFERENCED_PARAMETER(near_to
);
664 operator delete(memory
, type
);
667 void* __cdecl
operator new(size_t size
, void* buffer
,
668 sandbox::AllocationType type
) {
669 UNREFERENCED_PARAMETER(size
);
670 UNREFERENCED_PARAMETER(type
);
674 void __cdecl
operator delete(void* memory
, void* buffer
,
675 sandbox::AllocationType type
) {
676 UNREFERENCED_PARAMETER(memory
);
677 UNREFERENCED_PARAMETER(buffer
);
678 UNREFERENCED_PARAMETER(type
);