| // Copyright 2012 The Chromium Authors |
| // 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 <ntstatus.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <string> |
| |
| #include <optional> |
| #include "base/compiler_specific.h" |
| #include "base/containers/span.h" |
| #include "base/win/pe_image.h" |
| #include "sandbox/win/src/internal_types.h" |
| #include "sandbox/win/src/nt_internals.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) { |
| // 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) { |
| // Avoid memset inserted by -ftrivial-auto-var-init=pattern. |
| STACK_UNINITIALIZED MEMORY_BASIC_INFORMATION mem_info; |
| NTSTATUS status = sandbox::GetNtExports()->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 = sandbox::GetNtExports()->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) { |
| // 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 = sandbox::GetNtExports()->AllocateVirtualMemory( |
| NtCurrentProcess, &base, 0, &actual_size, MEM_RESERVE, PAGE_NOACCESS); |
| if (!NT_SUCCESS(ret)) |
| return nullptr; |
| } while (base < kMinAddress); |
| |
| actual_size = size; |
| ret = sandbox::GetNtExports()->AllocateVirtualMemory( |
| NtCurrentProcess, &base, 0, &actual_size, MEM_COMMIT, PAGE_READWRITE); |
| if (!NT_SUCCESS(ret)) |
| return nullptr; |
| return base; |
| } |
| #endif // defined(_WIN64). |
| |
| template <typename T> |
| void InitFunc(const base::win::PEImage& image, T& member, const char* name) { |
| member = reinterpret_cast<T>(image.GetProcAddress(name)); |
| DCHECK(member); |
| } |
| |
| #define INIT_NT(member) InitFunc(image, sandbox::g_nt.member, "Nt" #member) |
| #define INIT_RTL(member) InitFunc(image, sandbox::g_nt.member, #member) |
| |
| void InitGlobalNt() { |
| HMODULE ntdll = ::GetModuleHandle(sandbox::kNtdllName); |
| base::win::PEImage image(ntdll); |
| INIT_NT(AllocateVirtualMemory); |
| INIT_NT(CreateFile); |
| INIT_NT(CreateSection); |
| INIT_NT(Close); |
| INIT_NT(DuplicateObject); |
| INIT_NT(FreeVirtualMemory); |
| INIT_NT(MapViewOfSection); |
| INIT_NT(OpenThread); |
| INIT_NT(OpenProcessTokenEx); |
| INIT_NT(ProtectVirtualMemory); |
| INIT_NT(QueryAttributesFile); |
| INIT_NT(QueryFullAttributesFile); |
| INIT_NT(QueryInformationProcess); |
| INIT_NT(QueryObject); |
| INIT_NT(QuerySection); |
| INIT_NT(QueryVirtualMemory); |
| INIT_NT(SetInformationFile); |
| INIT_NT(SignalAndWaitForSingleObject); |
| INIT_NT(UnmapViewOfSection); |
| INIT_NT(WaitForSingleObject); |
| INIT_RTL(RtlAllocateHeap); |
| INIT_RTL(RtlAnsiStringToUnicodeString); |
| INIT_RTL(RtlCompareUnicodeString); |
| INIT_RTL(RtlCreateHeap); |
| INIT_RTL(RtlDestroyHeap); |
| INIT_RTL(RtlFreeHeap); |
| INIT_RTL(RtlNtStatusToDosError); |
| INIT_RTL(_strnicmp); |
| INIT_RTL(strlen); |
| INIT_RTL(wcslen); |
| INIT_RTL(memcpy); |
| sandbox::g_nt.Initialized = true; |
| } |
| |
| // The TEB structure defined in winternl.h doesn't have the ClientId member. |
| // Provide a partial definition here. |
| struct PARTIAL_TEB { |
| PVOID NtTib[7]; |
| PVOID EnvironmentPointer; |
| CLIENT_ID ClientId; |
| PVOID ActiveRpcHandle; |
| PVOID ThreadLocalStoragePointer; |
| PPEB ProcessEnvironmentBlock; |
| }; |
| |
| // Check PEB offset between the partial definition and the public one. |
| static_assert(offsetof(PARTIAL_TEB, ProcessEnvironmentBlock) == |
| offsetof(TEB, ProcessEnvironmentBlock)); |
| |
| } // 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; |
| SANDBOX_INTERCEPT size_t g_delegate_data_size = 0; |
| |
| void* volatile g_shared_policy_memory = nullptr; |
| void* volatile g_shared_IPC_memory = nullptr; |
| void* volatile g_shared_delegate_data = nullptr; |
| |
| // The IPC, policy and delegate data share a single region of memory with blocks |
| // in that order. |
| bool MapGlobalMemory() { |
| if (!g_shared_IPC_memory) { |
| void* memory = nullptr; |
| SIZE_T size = 0; |
| // Map the entire shared section from the start. |
| NTSTATUS ret = GetNtExports()->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( |
| GetNtExports()->UnmapViewOfSection(NtCurrentProcess, memory)); |
| } |
| DCHECK_NT(g_shared_IPC_size > 0); |
| |
| if (g_shared_policy_size > 0) { |
| g_shared_policy_memory = |
| reinterpret_cast<char*>(g_shared_IPC_memory) + g_shared_IPC_size; |
| } |
| // TODO(crbug.com/40265190) make this a read-only mapping in the child, |
| // distinct from the IPC & policy memory as it should be const. |
| if (g_delegate_data_size > 0) { |
| g_shared_delegate_data = reinterpret_cast<char*>(g_shared_IPC_memory) + |
| g_shared_IPC_size + g_shared_policy_size; |
| } |
| } |
| |
| return true; |
| } |
| |
| void* GetGlobalIPCMemory() { |
| if (!MapGlobalMemory()) |
| return nullptr; |
| return g_shared_IPC_memory; |
| } |
| |
| void* GetGlobalPolicyMemoryForTesting() { |
| if (!MapGlobalMemory()) |
| return nullptr; |
| return g_shared_policy_memory; |
| } |
| |
| std::optional<base::span<const uint8_t>> GetGlobalDelegateData() { |
| if (!g_delegate_data_size) { |
| return std::nullopt; |
| } |
| if (!MapGlobalMemory()) { |
| return std::nullopt; |
| } |
| return base::make_span( |
| reinterpret_cast<const uint8_t*>(g_shared_delegate_data), |
| g_delegate_data_size); |
| } |
| |
| const NtExports* GetNtExports() { |
| if (!g_nt.Initialized) |
| InitGlobalNt(); |
| |
| return &g_nt; |
| } |
| |
| bool InitHeap() { |
| if (!g_heap) { |
| // Create a new heap using default values for everything. |
| void* heap = GetNtExports()->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. |
| GetNtExports()->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 { |
| GetNtExports()->memcpy(destination, source, bytes); |
| } __except (EXCEPTION_EXECUTE_HANDLER) { |
| ret = (NTSTATUS)GetExceptionCode(); |
| } |
| return ret; |
| } |
| |
| NTSTATUS CopyNameAndAttributes( |
| const OBJECT_ATTRIBUTES* in_object, |
| std::unique_ptr<wchar_t, NtAllocDeleter>* out_name, |
| size_t* out_name_len, |
| uint32_t* attributes) { |
| if (!InitHeap()) |
| return STATUS_NO_MEMORY; |
| |
| DCHECK_NT(out_name); |
| DCHECK_NT(out_name_len); |
| NTSTATUS ret = STATUS_UNSUCCESSFUL; |
| __try { |
| do { |
| if (in_object->RootDirectory != nullptr) |
| 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 + 1]); |
| if (!*out_name) |
| break; |
| |
| ret = CopyData(out_name->get(), in_object->ObjectName->Buffer, |
| size * sizeof(wchar_t)); |
| if (!NT_SUCCESS(ret)) |
| break; |
| |
| *out_name_len = size; |
| out_name->get()[size] = L'\0'; |
| if (attributes) |
| *attributes = in_object->Attributes; |
| |
| ret = STATUS_SUCCESS; |
| } while (false); |
| } __except (EXCEPTION_EXECUTE_HANDLER) { |
| ret = (NTSTATUS)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 = GetNtExports()->QueryInformationProcess( |
| process, ProcessBasicInformation, &proc_info, sizeof(proc_info), |
| &bytes_returned); |
| if (!NT_SUCCESS(ret) || sizeof(proc_info) != bytes_returned) |
| return ret; |
| |
| // https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess |
| // "UniqueProcessId Can be cast to a DWORD and contains a unique identifier |
| // for this process." |
| *process_id = static_cast<DWORD>(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 = GetNtExports()->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 = GetNtExports()->QuerySection(query_section, SectionBasicInformation, |
| &basic_info, sizeof(basic_info), |
| &bytes_returned); |
| |
| VERIFY_SUCCESS(GetNtExports()->Close(query_section)); |
| |
| if (!NT_SUCCESS(ret) || sizeof(basic_info) != bytes_returned) |
| return false; |
| |
| if (!(basic_info.Attributes & SEC_IMAGE)) |
| return false; |
| |
| // Windows 10 2009+ may open PEs as SEC_IMAGE_NO_EXECUTE in non-dll-loading |
| // paths which looks identical to dll-loading unless we check if the section |
| // handle has execute rights. |
| // Avoid memset inserted by -ftrivial-auto-var-init=pattern. |
| STACK_UNINITIALIZED OBJECT_BASIC_INFORMATION obj_info; |
| ULONG obj_size_returned; |
| ret = GetNtExports()->QueryObject(section, ObjectBasicInformation, &obj_info, |
| sizeof(obj_info), &obj_size_returned); |
| |
| if (!NT_SUCCESS(ret) || sizeof(obj_info) != obj_size_returned) |
| return false; |
| |
| if (!(obj_info.GrantedAccess & SECTION_MAP_EXECUTE)) |
| return false; |
| |
| return true; |
| } |
| |
| UNICODE_STRING* AnsiToUnicode(const char* string) { |
| ANSI_STRING ansi_string; |
| ansi_string.Length = static_cast<USHORT>(GetNtExports()->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 = GetNtExports()->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 = GetNtExports()->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* start_ptr = &module_path->Buffer[0]; |
| if (module_path->Length > 0) { |
| size_t last_char = module_path->Length / sizeof(wchar_t) - 1; |
| // Ends with path separator. Not a valid module name. |
| if (module_path->Buffer[last_char] == L'\\') |
| return nullptr; |
| // Search backwards for path separator. |
| for (size_t i = 0; i <= last_char; ++i) { |
| if (module_path->Buffer[last_char - i] == L'\\') { |
| start_ptr = &module_path->Buffer[last_char - i + 1]; |
| break; |
| } |
| } |
| } |
| |
| size_t skip_bytes = reinterpret_cast<uintptr_t>(start_ptr) - |
| reinterpret_cast<uintptr_t>(&module_path->Buffer[0]); |
| // We add a nul wchar to the buffer. |
| size_t size_bytes = module_path->Length - skip_bytes + sizeof(wchar_t); |
| |
| // Because module_path is a UNICODE_STRING, size_bytes will 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, start_ptr, 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 = GetNtExports()->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 = GetNtExports()->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; |
| } |
| |
| CLIENT_ID GetCurrentClientId() { |
| return reinterpret_cast<PARTIAL_TEB*>(NtCurrentTeb())->ClientId; |
| } |
| |
| } // 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::GetNtExports()->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::GetNtExports()->RtlFreeHeap(sandbox::g_heap, 0, memory)); |
| } else if (type == sandbox::NT_PAGE) { |
| void* base = memory; |
| SIZE_T size = 0; |
| VERIFY_SUCCESS(sandbox::GetNtExports()->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) {} |