| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "sandbox/win/src/sandbox_nt_util.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <string> |
| |
| #include "base/win/pe_image.h" |
| #include "sandbox/win/src/sandbox_factory.h" |
| #include "sandbox/win/src/target_services.h" |
| |
| namespace sandbox { |
| |
| // This is the list of all imported symbols from ntdll.dll. |
| SANDBOX_INTERCEPT NtExports g_nt; |
| |
| } // namespace sandbox |
| |
| namespace { |
| |
| #if defined(_WIN64) |
| // Align a pointer to the next allocation granularity boundary. |
| inline char* AlignToBoundary(void* ptr, size_t increment) { |
| const size_t kAllocationGranularity = (64 * 1024) - 1; |
| uintptr_t ptr_int = reinterpret_cast<uintptr_t>(ptr); |
| uintptr_t ret_ptr = |
| (ptr_int + increment + kAllocationGranularity) & ~kAllocationGranularity; |
| // Check for overflow. |
| if (ret_ptr < ptr_int) |
| return nullptr; |
| return reinterpret_cast<char*>(ret_ptr); |
| } |
| |
| // Allocate a memory block somewhere within 2GiB of a specified base address. |
| // This is used for the DLL hooking code to get a valid trampoline location |
| // which must be within +/- 2GiB of the base. We only consider +2GiB for now. |
| void* AllocateNearTo(void* source, size_t size) { |
| using sandbox::g_nt; |
| // 2GiB, maximum upper bound the allocation address must be within. |
| const size_t kMaxSize = 0x80000000ULL; |
| // We don't support null as a base as this would just pick an arbitrary |
| // address when passed to NtAllocateVirtualMemory. |
| if (!source) |
| return nullptr; |
| // Ignore an allocation which is larger than the maximum. |
| if (size > kMaxSize) |
| return nullptr; |
| |
| // Ensure base address is aligned to the allocation granularity boundary. |
| char* base = AlignToBoundary(source, 0); |
| if (!base) |
| return nullptr; |
| // Set top address to be base + 2GiB. |
| const char* top_address = base + kMaxSize; |
| |
| while (base < top_address) { |
| MEMORY_BASIC_INFORMATION mem_info; |
| NTSTATUS status = |
| g_nt.QueryVirtualMemory(NtCurrentProcess, base, MemoryBasicInformation, |
| &mem_info, sizeof(mem_info), nullptr); |
| if (!NT_SUCCESS(status)) |
| break; |
| |
| if ((mem_info.State == MEM_FREE) && (mem_info.RegionSize >= size)) { |
| // We've found a valid free block, try and allocate it for use. |
| // Note that we need to both commit and reserve the block for the |
| // allocation to succeed as per Windows virtual memory requirements. |
| void* ret_base = mem_info.BaseAddress; |
| status = |
| g_nt.AllocateVirtualMemory(NtCurrentProcess, &ret_base, 0, &size, |
| MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); |
| // Shouldn't fail, but if it does we'll just continue and try next block. |
| if (NT_SUCCESS(status)) |
| return ret_base; |
| } |
| |
| // Update base past current allocation region. |
| base = AlignToBoundary(mem_info.BaseAddress, mem_info.RegionSize); |
| if (!base) |
| break; |
| } |
| return nullptr; |
| } |
| #else // defined(_WIN64). |
| void* AllocateNearTo(void* source, size_t size) { |
| using sandbox::g_nt; |
| |
| // In 32-bit processes allocations below 512k are predictable, so mark |
| // anything in that range as reserved and retry until we get a good address. |
| const void* const kMinAddress = reinterpret_cast<void*>(512 * 1024); |
| NTSTATUS ret; |
| SIZE_T actual_size; |
| void* base; |
| do { |
| base = nullptr; |
| actual_size = 64 * 1024; |
| ret = g_nt.AllocateVirtualMemory(NtCurrentProcess, &base, 0, &actual_size, |
| MEM_RESERVE, PAGE_NOACCESS); |
| if (!NT_SUCCESS(ret)) |
| return nullptr; |
| } while (base < kMinAddress); |
| |
| actual_size = size; |
| ret = g_nt.AllocateVirtualMemory(NtCurrentProcess, &base, 0, &actual_size, |
| MEM_COMMIT, PAGE_READWRITE); |
| if (!NT_SUCCESS(ret)) |
| return nullptr; |
| return base; |
| } |
| #endif // defined(_WIN64). |
| |
| } // namespace. |
| |
| namespace sandbox { |
| |
| // Handle for our private heap. |
| void* g_heap = nullptr; |
| |
| SANDBOX_INTERCEPT HANDLE g_shared_section; |
| SANDBOX_INTERCEPT size_t g_shared_IPC_size = 0; |
| SANDBOX_INTERCEPT size_t g_shared_policy_size = 0; |
| |
| void* volatile g_shared_policy_memory = nullptr; |
| void* volatile g_shared_IPC_memory = nullptr; |
| |
| // Both the IPC and the policy share a single region of memory in which the IPC |
| // memory is first and the policy memory is last. |
| bool MapGlobalMemory() { |
| if (!g_shared_IPC_memory) { |
| void* memory = nullptr; |
| SIZE_T size = 0; |
| // Map the entire shared section from the start. |
| NTSTATUS ret = |
| g_nt.MapViewOfSection(g_shared_section, NtCurrentProcess, &memory, 0, 0, |
| nullptr, &size, ViewUnmap, 0, PAGE_READWRITE); |
| |
| if (!NT_SUCCESS(ret) || !memory) { |
| NOTREACHED_NT(); |
| return false; |
| } |
| |
| if (_InterlockedCompareExchangePointer(&g_shared_IPC_memory, memory, |
| nullptr)) { |
| // Somebody beat us to the memory setup. |
| VERIFY_SUCCESS(g_nt.UnmapViewOfSection(NtCurrentProcess, memory)); |
| } |
| DCHECK_NT(g_shared_IPC_size > 0); |
| g_shared_policy_memory = |
| reinterpret_cast<char*>(g_shared_IPC_memory) + g_shared_IPC_size; |
| } |
| DCHECK_NT(g_shared_policy_memory); |
| DCHECK_NT(g_shared_policy_size > 0); |
| return true; |
| } |
| |
| void* GetGlobalIPCMemory() { |
| if (!MapGlobalMemory()) |
| return nullptr; |
| return g_shared_IPC_memory; |
| } |
| |
| void* GetGlobalPolicyMemory() { |
| if (!MapGlobalMemory()) |
| return nullptr; |
| return g_shared_policy_memory; |
| } |
| |
| bool InitHeap() { |
| if (!g_heap) { |
| // Create a new heap using default values for everything. |
| void* heap = |
| g_nt.RtlCreateHeap(HEAP_GROWABLE, nullptr, 0, 0, nullptr, nullptr); |
| if (!heap) |
| return false; |
| |
| if (_InterlockedCompareExchangePointer(&g_heap, heap, nullptr)) { |
| // Somebody beat us to the memory setup. |
| g_nt.RtlDestroyHeap(heap); |
| } |
| } |
| return !!g_heap; |
| } |
| |
| // Physically reads or writes from memory to verify that (at this time), it is |
| // valid. Returns a dummy value. |
| int TouchMemory(void* buffer, size_t size_bytes, RequiredAccess intent) { |
| const int kPageSize = 4096; |
| int dummy = 0; |
| volatile char* start = reinterpret_cast<char*>(buffer); |
| volatile char* end = start + size_bytes - 1; |
| |
| if (WRITE == intent) { |
| for (; start < end; start += kPageSize) { |
| *start = *start; |
| } |
| *end = *end; |
| } else { |
| for (; start < end; start += kPageSize) { |
| dummy += *start; |
| } |
| dummy += *end; |
| } |
| |
| return dummy; |
| } |
| |
| bool ValidParameter(void* buffer, size_t size, RequiredAccess intent) { |
| DCHECK_NT(size); |
| __try { |
| TouchMemory(buffer, size, intent); |
| } __except (EXCEPTION_EXECUTE_HANDLER) { |
| return false; |
| } |
| return true; |
| } |
| |
| NTSTATUS CopyData(void* destination, const void* source, size_t bytes) { |
| NTSTATUS ret = STATUS_SUCCESS; |
| __try { |
| g_nt.memcpy(destination, source, bytes); |
| } __except (EXCEPTION_EXECUTE_HANDLER) { |
| ret = GetExceptionCode(); |
| } |
| return ret; |
| } |
| |
| NTSTATUS AllocAndGetFullPath( |
| HANDLE root, |
| const wchar_t* path, |
| std::unique_ptr<wchar_t, NtAllocDeleter>* full_path) { |
| if (!InitHeap()) |
| return STATUS_NO_MEMORY; |
| |
| DCHECK_NT(full_path); |
| DCHECK_NT(path); |
| NTSTATUS ret = STATUS_UNSUCCESSFUL; |
| __try { |
| do { |
| static NtQueryObjectFunction NtQueryObject = nullptr; |
| if (!NtQueryObject) |
| ResolveNTFunctionPtr("NtQueryObject", &NtQueryObject); |
| |
| ULONG size = 0; |
| // Query the name information a first time to get the size of the name. |
| ret = NtQueryObject(root, ObjectNameInformation, nullptr, 0, &size); |
| |
| std::unique_ptr<OBJECT_NAME_INFORMATION, NtAllocDeleter> handle_name; |
| if (size) { |
| handle_name.reset(reinterpret_cast<OBJECT_NAME_INFORMATION*>( |
| new (NT_ALLOC) BYTE[size])); |
| |
| // Query the name information a second time to get the name of the |
| // object referenced by the handle. |
| ret = NtQueryObject(root, ObjectNameInformation, handle_name.get(), |
| size, &size); |
| } |
| |
| if (STATUS_SUCCESS != ret) |
| break; |
| |
| // Space for path + '\' + name + '\0'. |
| size_t name_length = |
| handle_name->ObjectName.Length + (wcslen(path) + 2) * sizeof(wchar_t); |
| full_path->reset(new (NT_ALLOC) wchar_t[name_length / sizeof(wchar_t)]); |
| if (!*full_path) |
| break; |
| wchar_t* off = full_path->get(); |
| ret = CopyData(off, handle_name->ObjectName.Buffer, |
| handle_name->ObjectName.Length); |
| if (!NT_SUCCESS(ret)) |
| break; |
| off += handle_name->ObjectName.Length / sizeof(wchar_t); |
| *off = L'\\'; |
| off += 1; |
| ret = CopyData(off, path, wcslen(path) * sizeof(wchar_t)); |
| if (!NT_SUCCESS(ret)) |
| break; |
| off += wcslen(path); |
| *off = L'\0'; |
| } while (false); |
| } __except (EXCEPTION_EXECUTE_HANDLER) { |
| ret = GetExceptionCode(); |
| } |
| |
| if (!NT_SUCCESS(ret) && *full_path) |
| full_path->reset(nullptr); |
| |
| return ret; |
| } |
| |
| // Hacky code... replace with AllocAndCopyObjectAttributes. |
| NTSTATUS AllocAndCopyName(const OBJECT_ATTRIBUTES* in_object, |
| std::unique_ptr<wchar_t, NtAllocDeleter>* out_name, |
| uint32_t* attributes, |
| HANDLE* root) { |
| if (!InitHeap()) |
| return STATUS_NO_MEMORY; |
| |
| DCHECK_NT(out_name); |
| NTSTATUS ret = STATUS_UNSUCCESSFUL; |
| __try { |
| do { |
| if (in_object->RootDirectory != static_cast<HANDLE>(0) && !root) |
| break; |
| if (!in_object->ObjectName) |
| break; |
| if (!in_object->ObjectName->Buffer) |
| break; |
| |
| size_t size = in_object->ObjectName->Length + sizeof(wchar_t); |
| out_name->reset(new (NT_ALLOC) wchar_t[size / sizeof(wchar_t)]); |
| if (!*out_name) |
| break; |
| |
| ret = CopyData(out_name->get(), in_object->ObjectName->Buffer, |
| size - sizeof(wchar_t)); |
| if (!NT_SUCCESS(ret)) |
| break; |
| |
| out_name->get()[size / sizeof(wchar_t) - 1] = L'\0'; |
| |
| if (attributes) |
| *attributes = in_object->Attributes; |
| |
| if (root) |
| *root = in_object->RootDirectory; |
| ret = STATUS_SUCCESS; |
| } while (false); |
| } __except (EXCEPTION_EXECUTE_HANDLER) { |
| ret = GetExceptionCode(); |
| } |
| |
| if (!NT_SUCCESS(ret) && *out_name) |
| out_name->reset(nullptr); |
| |
| return ret; |
| } |
| |
| NTSTATUS GetProcessId(HANDLE process, DWORD* process_id) { |
| PROCESS_BASIC_INFORMATION proc_info; |
| ULONG bytes_returned; |
| |
| NTSTATUS ret = |
| g_nt.QueryInformationProcess(process, ProcessBasicInformation, &proc_info, |
| sizeof(proc_info), &bytes_returned); |
| if (!NT_SUCCESS(ret) || sizeof(proc_info) != bytes_returned) |
| return ret; |
| |
| *process_id = proc_info.UniqueProcessId; |
| return STATUS_SUCCESS; |
| } |
| |
| bool IsSameProcess(HANDLE process) { |
| if (NtCurrentProcess == process) |
| return true; |
| |
| static DWORD s_process_id = 0; |
| |
| if (!s_process_id) { |
| NTSTATUS ret = GetProcessId(NtCurrentProcess, &s_process_id); |
| if (!NT_SUCCESS(ret)) |
| return false; |
| } |
| |
| DWORD process_id; |
| NTSTATUS ret = GetProcessId(process, &process_id); |
| if (!NT_SUCCESS(ret)) |
| return false; |
| |
| return (process_id == s_process_id); |
| } |
| |
| bool IsValidImageSection(HANDLE section, |
| PVOID* base, |
| PLARGE_INTEGER offset, |
| PSIZE_T view_size) { |
| if (!section || !base || !view_size || offset) |
| return false; |
| |
| HANDLE query_section; |
| |
| NTSTATUS ret = |
| g_nt.DuplicateObject(NtCurrentProcess, section, NtCurrentProcess, |
| &query_section, SECTION_QUERY, 0, 0); |
| if (!NT_SUCCESS(ret)) |
| return false; |
| |
| SECTION_BASIC_INFORMATION basic_info; |
| SIZE_T bytes_returned; |
| ret = g_nt.QuerySection(query_section, SectionBasicInformation, &basic_info, |
| sizeof(basic_info), &bytes_returned); |
| |
| VERIFY_SUCCESS(g_nt.Close(query_section)); |
| |
| if (!NT_SUCCESS(ret) || sizeof(basic_info) != bytes_returned) |
| return false; |
| |
| if (!(basic_info.Attributes & SEC_IMAGE)) |
| return false; |
| |
| return true; |
| } |
| |
| UNICODE_STRING* AnsiToUnicode(const char* string) { |
| ANSI_STRING ansi_string; |
| ansi_string.Length = static_cast<USHORT>(g_nt.strlen(string)); |
| ansi_string.MaximumLength = ansi_string.Length + 1; |
| ansi_string.Buffer = const_cast<char*>(string); |
| |
| if (ansi_string.Length > ansi_string.MaximumLength) |
| return nullptr; |
| |
| size_t name_bytes = |
| ansi_string.MaximumLength * sizeof(wchar_t) + sizeof(UNICODE_STRING); |
| |
| UNICODE_STRING* out_string = |
| reinterpret_cast<UNICODE_STRING*>(new (NT_ALLOC) char[name_bytes]); |
| if (!out_string) |
| return nullptr; |
| |
| out_string->MaximumLength = ansi_string.MaximumLength * sizeof(wchar_t); |
| out_string->Buffer = reinterpret_cast<wchar_t*>(&out_string[1]); |
| |
| BOOLEAN alloc_destination = false; |
| NTSTATUS ret = g_nt.RtlAnsiStringToUnicodeString(out_string, &ansi_string, |
| alloc_destination); |
| DCHECK_NT(STATUS_BUFFER_OVERFLOW != ret); |
| if (!NT_SUCCESS(ret)) { |
| operator delete(out_string, NT_ALLOC); |
| return nullptr; |
| } |
| |
| return out_string; |
| } |
| |
| UNICODE_STRING* GetImageInfoFromModule(HMODULE module, uint32_t* flags) { |
| // PEImage's dtor won't be run during SEH unwinding, but that's OK. |
| #pragma warning(push) |
| #pragma warning(disable : 4509) |
| UNICODE_STRING* out_name = nullptr; |
| __try { |
| do { |
| *flags = 0; |
| base::win::PEImage pe(module); |
| |
| if (!pe.VerifyMagic()) |
| break; |
| *flags |= MODULE_IS_PE_IMAGE; |
| |
| PIMAGE_EXPORT_DIRECTORY exports = pe.GetExportDirectory(); |
| if (exports) { |
| char* name = reinterpret_cast<char*>(pe.RVAToAddr(exports->Name)); |
| out_name = AnsiToUnicode(name); |
| } |
| |
| PIMAGE_NT_HEADERS headers = pe.GetNTHeaders(); |
| if (headers) { |
| if (headers->OptionalHeader.AddressOfEntryPoint) |
| *flags |= MODULE_HAS_ENTRY_POINT; |
| if (headers->OptionalHeader.SizeOfCode) |
| *flags |= MODULE_HAS_CODE; |
| } |
| } while (false); |
| } __except (EXCEPTION_EXECUTE_HANDLER) { |
| } |
| |
| return out_name; |
| #pragma warning(pop) |
| } |
| |
| const char* GetAnsiImageInfoFromModule(HMODULE module) { |
| // PEImage's dtor won't be run during SEH unwinding, but that's OK. |
| #pragma warning(push) |
| #pragma warning(disable : 4509) |
| const char* out_name = nullptr; |
| __try { |
| do { |
| base::win::PEImage pe(module); |
| |
| if (!pe.VerifyMagic()) |
| break; |
| |
| PIMAGE_EXPORT_DIRECTORY exports = pe.GetExportDirectory(); |
| if (exports) |
| out_name = static_cast<const char*>(pe.RVAToAddr(exports->Name)); |
| } while (false); |
| } __except (EXCEPTION_EXECUTE_HANDLER) { |
| } |
| |
| return out_name; |
| #pragma warning(pop) |
| } |
| |
| UNICODE_STRING* GetBackingFilePath(PVOID address) { |
| // We'll start with something close to max_path charactes for the name. |
| SIZE_T buffer_bytes = MAX_PATH * 2; |
| |
| for (;;) { |
| MEMORY_SECTION_NAME* section_name = reinterpret_cast<MEMORY_SECTION_NAME*>( |
| new (NT_ALLOC) char[buffer_bytes]); |
| |
| if (!section_name) |
| return nullptr; |
| |
| SIZE_T returned_bytes; |
| NTSTATUS ret = |
| g_nt.QueryVirtualMemory(NtCurrentProcess, address, MemorySectionName, |
| section_name, buffer_bytes, &returned_bytes); |
| |
| if (STATUS_BUFFER_OVERFLOW == ret) { |
| // Retry the call with the given buffer size. |
| operator delete(section_name, NT_ALLOC); |
| section_name = nullptr; |
| buffer_bytes = returned_bytes; |
| continue; |
| } |
| if (!NT_SUCCESS(ret)) { |
| operator delete(section_name, NT_ALLOC); |
| return nullptr; |
| } |
| |
| return reinterpret_cast<UNICODE_STRING*>(section_name); |
| } |
| } |
| |
| UNICODE_STRING* ExtractModuleName(const UNICODE_STRING* module_path) { |
| if ((!module_path) || (!module_path->Buffer)) |
| return nullptr; |
| |
| wchar_t* sep = nullptr; |
| int start_pos = module_path->Length / sizeof(wchar_t) - 1; |
| int ix = start_pos; |
| |
| for (; ix >= 0; --ix) { |
| if (module_path->Buffer[ix] == L'\\') { |
| sep = &module_path->Buffer[ix]; |
| break; |
| } |
| } |
| |
| // Ends with path separator. Not a valid module name. |
| if ((ix == start_pos) && sep) |
| return nullptr; |
| |
| // No path separator found. Use the entire name. |
| if (!sep) { |
| sep = &module_path->Buffer[-1]; |
| } |
| |
| // Add one to the size so we can null terminate the string. |
| size_t size_bytes = (start_pos - ix + 1) * sizeof(wchar_t); |
| |
| // Based on the code above, size_bytes should always be small enough |
| // to make the static_cast below safe. |
| DCHECK_NT(UINT16_MAX > size_bytes); |
| char* str_buffer = new (NT_ALLOC) char[size_bytes + sizeof(UNICODE_STRING)]; |
| if (!str_buffer) |
| return nullptr; |
| |
| UNICODE_STRING* out_string = reinterpret_cast<UNICODE_STRING*>(str_buffer); |
| out_string->Buffer = reinterpret_cast<wchar_t*>(&out_string[1]); |
| out_string->Length = static_cast<USHORT>(size_bytes - sizeof(wchar_t)); |
| out_string->MaximumLength = static_cast<USHORT>(size_bytes); |
| |
| NTSTATUS ret = CopyData(out_string->Buffer, &sep[1], out_string->Length); |
| if (!NT_SUCCESS(ret)) { |
| operator delete(out_string, NT_ALLOC); |
| return nullptr; |
| } |
| |
| out_string->Buffer[out_string->Length / sizeof(wchar_t)] = L'\0'; |
| return out_string; |
| } |
| |
| NTSTATUS AutoProtectMemory::ChangeProtection(void* address, |
| size_t bytes, |
| ULONG protect) { |
| DCHECK_NT(!changed_); |
| SIZE_T new_bytes = bytes; |
| NTSTATUS ret = g_nt.ProtectVirtualMemory(NtCurrentProcess, &address, |
| &new_bytes, protect, &old_protect_); |
| if (NT_SUCCESS(ret)) { |
| changed_ = true; |
| address_ = address; |
| bytes_ = new_bytes; |
| } |
| |
| return ret; |
| } |
| |
| NTSTATUS AutoProtectMemory::RevertProtection() { |
| if (!changed_) |
| return STATUS_SUCCESS; |
| |
| DCHECK_NT(address_); |
| DCHECK_NT(bytes_); |
| |
| SIZE_T new_bytes = bytes_; |
| NTSTATUS ret = g_nt.ProtectVirtualMemory( |
| NtCurrentProcess, &address_, &new_bytes, old_protect_, &old_protect_); |
| DCHECK_NT(NT_SUCCESS(ret)); |
| |
| changed_ = false; |
| address_ = nullptr; |
| bytes_ = 0; |
| old_protect_ = 0; |
| |
| return ret; |
| } |
| |
| bool IsSupportedRenameCall(FILE_RENAME_INFORMATION* file_info, |
| DWORD length, |
| uint32_t file_info_class) { |
| if (FileRenameInformation != file_info_class) |
| return false; |
| |
| if (length < sizeof(FILE_RENAME_INFORMATION)) |
| return false; |
| |
| // Make sure file name length doesn't exceed the message length |
| if (length - offsetof(FILE_RENAME_INFORMATION, FileName) < |
| file_info->FileNameLength) |
| return false; |
| |
| // We don't support a root directory. |
| if (file_info->RootDirectory) |
| return false; |
| |
| static const wchar_t kPathPrefix[] = {L'\\', L'?', L'?', L'\\'}; |
| |
| // Check if it starts with \\??\\. We don't support relative paths. |
| if (file_info->FileNameLength < sizeof(kPathPrefix) || |
| file_info->FileNameLength > UINT16_MAX) |
| return false; |
| |
| if (file_info->FileName[0] != kPathPrefix[0] || |
| file_info->FileName[1] != kPathPrefix[1] || |
| file_info->FileName[2] != kPathPrefix[2] || |
| file_info->FileName[3] != kPathPrefix[3]) |
| return false; |
| |
| return true; |
| } |
| |
| } // namespace sandbox |
| |
| void* operator new(size_t size, sandbox::AllocationType type, void* near_to) { |
| void* result = nullptr; |
| if (type == sandbox::NT_ALLOC) { |
| if (sandbox::InitHeap()) { |
| // Use default flags for the allocation. |
| result = sandbox::g_nt.RtlAllocateHeap(sandbox::g_heap, 0, size); |
| } |
| } else if (type == sandbox::NT_PAGE) { |
| result = AllocateNearTo(near_to, size); |
| } else { |
| NOTREACHED_NT(); |
| } |
| |
| // TODO: Returning nullptr from operator new has undefined behavior, but |
| // the Allocate() functions called above can return nullptr. Consider checking |
| // for nullptr here and crashing or throwing. |
| |
| return result; |
| } |
| |
| void operator delete(void* memory, sandbox::AllocationType type) { |
| if (type == sandbox::NT_ALLOC) { |
| // Use default flags. |
| VERIFY(sandbox::g_nt.RtlFreeHeap(sandbox::g_heap, 0, memory)); |
| } else if (type == sandbox::NT_PAGE) { |
| void* base = memory; |
| SIZE_T size = 0; |
| VERIFY_SUCCESS(sandbox::g_nt.FreeVirtualMemory(NtCurrentProcess, &base, |
| &size, MEM_RELEASE)); |
| } else { |
| NOTREACHED_NT(); |
| } |
| } |
| |
| void operator delete(void* memory, |
| sandbox::AllocationType type, |
| void* near_to) { |
| operator delete(memory, type); |
| } |
| |
| void* __cdecl operator new(size_t size, |
| void* buffer, |
| sandbox::AllocationType type) { |
| return buffer; |
| } |
| |
| void __cdecl operator delete(void* memory, |
| void* buffer, |
| sandbox::AllocationType type) {} |