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 // Hacky code... replace with AllocAndCopyObjectAttributes.
219 NTSTATUS
AllocAndCopyName(const OBJECT_ATTRIBUTES
* in_object
,
220 wchar_t** out_name
, uint32
* attributes
,
223 return STATUS_NO_MEMORY
;
227 NTSTATUS ret
= STATUS_UNSUCCESSFUL
;
230 if (in_object
->RootDirectory
!= static_cast<HANDLE
>(0) && !root
)
232 if (NULL
== in_object
->ObjectName
)
234 if (NULL
== in_object
->ObjectName
->Buffer
)
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
)
242 ret
= CopyData(*out_name
, in_object
->ObjectName
->Buffer
,
243 size
- sizeof(wchar_t));
244 if (!NT_SUCCESS(ret
))
247 (*out_name
)[size
/ sizeof(wchar_t) - 1] = L
'\0';
250 *attributes
= in_object
->Attributes
;
253 *root
= in_object
->RootDirectory
;
254 ret
= STATUS_SUCCESS
;
256 } __except(EXCEPTION_EXECUTE_HANDLER
) {
257 ret
= GetExceptionCode();
260 if (!NT_SUCCESS(ret
) && *out_name
) {
261 operator delete(*out_name
, NT_ALLOC
);
268 NTSTATUS
GetProcessId(HANDLE process
, ULONG
*process_id
) {
269 PROCESS_BASIC_INFORMATION proc_info
;
270 ULONG bytes_returned
;
272 NTSTATUS ret
= g_nt
.QueryInformationProcess(process
, ProcessBasicInformation
,
273 &proc_info
, sizeof(proc_info
),
275 if (!NT_SUCCESS(ret
) || sizeof(proc_info
) != bytes_returned
)
278 *process_id
= proc_info
.UniqueProcessId
;
279 return STATUS_SUCCESS
;
282 bool IsSameProcess(HANDLE process
) {
283 if (NtCurrentProcess
== process
)
286 static ULONG s_process_id
= 0;
289 NTSTATUS ret
= GetProcessId(NtCurrentProcess
, &s_process_id
);
290 if (!NT_SUCCESS(ret
))
295 NTSTATUS ret
= GetProcessId(process
, &process_id
);
296 if (!NT_SUCCESS(ret
))
299 return (process_id
== s_process_id
);
302 bool IsValidImageSection(HANDLE section
, PVOID
*base
, PLARGE_INTEGER offset
,
304 if (!section
|| !base
|| !view_size
|| offset
)
307 HANDLE query_section
;
309 NTSTATUS ret
= g_nt
.DuplicateObject(NtCurrentProcess
, section
,
310 NtCurrentProcess
, &query_section
,
311 SECTION_QUERY
, 0, 0);
312 if (!NT_SUCCESS(ret
))
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
);
320 VERIFY_SUCCESS(g_nt
.Close(query_section
));
322 if (!NT_SUCCESS(ret
) || sizeof(basic_info
) != bytes_returned
)
325 if (!(basic_info
.Attributes
& SEC_IMAGE
))
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
);
337 if (ansi_string
.Length
> ansi_string
.MaximumLength
)
340 size_t name_bytes
= ansi_string
.MaximumLength
* sizeof(wchar_t) +
341 sizeof(UNICODE_STRING
);
343 UNICODE_STRING
* out_string
= reinterpret_cast<UNICODE_STRING
*>(
344 new(NT_ALLOC
) char[name_bytes
]);
348 out_string
->MaximumLength
= ansi_string
.MaximumLength
* sizeof(wchar_t);
349 out_string
->Buffer
= reinterpret_cast<wchar_t*>(&out_string
[1]);
351 BOOLEAN alloc_destination
= FALSE
;
352 NTSTATUS ret
= g_nt
.RtlAnsiStringToUnicodeString(out_string
, &ansi_string
,
354 DCHECK_NT(STATUS_BUFFER_OVERFLOW
!= ret
);
355 if (!NT_SUCCESS(ret
)) {
356 operator delete(out_string
, NT_ALLOC
);
363 UNICODE_STRING
* GetImageInfoFromModule(HMODULE module
, uint32
* flags
) {
364 // PEImage's dtor won't be run during SEH unwinding, but that's OK.
365 #pragma warning(push)
366 #pragma warning(disable: 4509)
367 UNICODE_STRING
* out_name
= NULL
;
371 base::win::PEImage
pe(module
);
373 if (!pe
.VerifyMagic())
375 *flags
|= MODULE_IS_PE_IMAGE
;
377 PIMAGE_EXPORT_DIRECTORY exports
= pe
.GetExportDirectory();
379 char* name
= reinterpret_cast<char*>(pe
.RVAToAddr(exports
->Name
));
380 out_name
= AnsiToUnicode(name
);
383 PIMAGE_NT_HEADERS headers
= pe
.GetNTHeaders();
385 if (headers
->OptionalHeader
.AddressOfEntryPoint
)
386 *flags
|= MODULE_HAS_ENTRY_POINT
;
387 if (headers
->OptionalHeader
.SizeOfCode
)
388 *flags
|= MODULE_HAS_CODE
;
391 } __except(EXCEPTION_EXECUTE_HANDLER
) {
398 UNICODE_STRING
* GetBackingFilePath(PVOID address
) {
399 // We'll start with something close to max_path charactes for the name.
400 ULONG buffer_bytes
= MAX_PATH
* 2;
403 MEMORY_SECTION_NAME
* section_name
= reinterpret_cast<MEMORY_SECTION_NAME
*>(
404 new(NT_ALLOC
) char[buffer_bytes
]);
409 ULONG returned_bytes
;
410 NTSTATUS ret
= g_nt
.QueryVirtualMemory(NtCurrentProcess
, address
,
411 MemorySectionName
, section_name
,
412 buffer_bytes
, &returned_bytes
);
414 if (STATUS_BUFFER_OVERFLOW
== ret
) {
415 // Retry the call with the given buffer size.
416 operator delete(section_name
, NT_ALLOC
);
418 buffer_bytes
= returned_bytes
;
421 if (!NT_SUCCESS(ret
)) {
422 operator delete(section_name
, NT_ALLOC
);
426 return reinterpret_cast<UNICODE_STRING
*>(section_name
);
430 UNICODE_STRING
* ExtractModuleName(const UNICODE_STRING
* module_path
) {
431 if ((!module_path
) || (!module_path
->Buffer
))
435 int start_pos
= module_path
->Length
/ sizeof(wchar_t) - 1;
438 for (; ix
>= 0; --ix
) {
439 if (module_path
->Buffer
[ix
] == L
'\\') {
440 sep
= &module_path
->Buffer
[ix
];
445 // Ends with path separator. Not a valid module name.
446 if ((ix
== start_pos
) && sep
)
449 // No path separator found. Use the entire name.
451 sep
= &module_path
->Buffer
[-1];
454 // Add one to the size so we can null terminate the string.
455 size_t size_bytes
= (start_pos
- ix
+ 1) * sizeof(wchar_t);
457 // Based on the code above, size_bytes should always be small enough
458 // to make the static_cast below safe.
459 DCHECK_NT(kuint16max
> size_bytes
);
460 char* str_buffer
= new(NT_ALLOC
) char[size_bytes
+ sizeof(UNICODE_STRING
)];
464 UNICODE_STRING
* out_string
= reinterpret_cast<UNICODE_STRING
*>(str_buffer
);
465 out_string
->Buffer
= reinterpret_cast<wchar_t*>(&out_string
[1]);
466 out_string
->Length
= static_cast<USHORT
>(size_bytes
- sizeof(wchar_t));
467 out_string
->MaximumLength
= static_cast<USHORT
>(size_bytes
);
469 NTSTATUS ret
= CopyData(out_string
->Buffer
, &sep
[1], out_string
->Length
);
470 if (!NT_SUCCESS(ret
)) {
471 operator delete(out_string
, NT_ALLOC
);
475 out_string
->Buffer
[out_string
->Length
/ sizeof(wchar_t)] = L
'\0';
479 NTSTATUS
AutoProtectMemory::ChangeProtection(void* address
, size_t bytes
,
481 DCHECK_NT(!changed_
);
482 SIZE_T new_bytes
= bytes
;
483 NTSTATUS ret
= g_nt
.ProtectVirtualMemory(NtCurrentProcess
, &address
,
484 &new_bytes
, protect
, &old_protect_
);
485 if (NT_SUCCESS(ret
)) {
494 NTSTATUS
AutoProtectMemory::RevertProtection() {
496 return STATUS_SUCCESS
;
501 SIZE_T new_bytes
= bytes_
;
502 NTSTATUS ret
= g_nt
.ProtectVirtualMemory(NtCurrentProcess
, &address_
,
503 &new_bytes
, old_protect_
,
505 DCHECK_NT(NT_SUCCESS(ret
));
515 bool IsSupportedRenameCall(FILE_RENAME_INFORMATION
* file_info
, DWORD length
,
516 uint32 file_info_class
) {
517 if (FileRenameInformation
!= file_info_class
)
520 if (length
< sizeof(FILE_RENAME_INFORMATION
))
523 // Make sure file name length doesn't exceed the message length
524 if (length
- offsetof(FILE_RENAME_INFORMATION
, FileName
) <
525 file_info
->FileNameLength
)
528 // We don't support a root directory.
529 if (file_info
->RootDirectory
)
532 static const wchar_t kPathPrefix
[] = { L
'\\', L
'?', L
'?', L
'\\'};
534 // Check if it starts with \\??\\. We don't support relative paths.
535 if (file_info
->FileNameLength
< sizeof(kPathPrefix
) ||
536 file_info
->FileNameLength
> kuint16max
)
539 if (file_info
->FileName
[0] != kPathPrefix
[0] ||
540 file_info
->FileName
[1] != kPathPrefix
[1] ||
541 file_info
->FileName
[2] != kPathPrefix
[2] ||
542 file_info
->FileName
[3] != kPathPrefix
[3])
548 } // namespace sandbox
550 void* operator new(size_t size
, sandbox::AllocationType type
,
552 using namespace sandbox
;
555 if (NT_ALLOC
== type
) {
557 // Use default flags for the allocation.
558 result
= g_nt
.RtlAllocateHeap(sandbox::g_heap
, 0, size
);
560 } else if (NT_PAGE
== type
) {
561 result
= AllocateNearTo(near_to
, size
);
566 // TODO: Returning NULL from operator new has undefined behavior, but
567 // the Allocate() functions called above can return NULL. Consider checking
568 // for NULL here and crashing or throwing.
573 void operator delete(void* memory
, sandbox::AllocationType type
) {
574 using namespace sandbox
;
576 if (NT_ALLOC
== type
) {
577 // Use default flags.
578 VERIFY(g_nt
.RtlFreeHeap(sandbox::g_heap
, 0, memory
));
579 } else if (NT_PAGE
== type
) {
582 VERIFY_SUCCESS(g_nt
.FreeVirtualMemory(NtCurrentProcess
, &base
, &size
,
589 void operator delete(void* memory
, sandbox::AllocationType type
,
591 UNREFERENCED_PARAMETER(near_to
);
592 operator delete(memory
, type
);
595 void* __cdecl
operator new(size_t size
, void* buffer
,
596 sandbox::AllocationType type
) {
597 UNREFERENCED_PARAMETER(size
);
598 UNREFERENCED_PARAMETER(type
);
602 void __cdecl
operator delete(void* memory
, void* buffer
,
603 sandbox::AllocationType type
) {
604 UNREFERENCED_PARAMETER(memory
);
605 UNREFERENCED_PARAMETER(buffer
);
606 UNREFERENCED_PARAMETER(type
);