michael@0: // Copyright (c) 2012 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: #include "sandbox/win/src/sandbox_nt_util.h" michael@0: michael@0: #include "base/win/pe_image.h" michael@0: #include "sandbox/win/src/sandbox_factory.h" michael@0: #include "sandbox/win/src/target_services.h" michael@0: michael@0: namespace sandbox { michael@0: michael@0: // This is the list of all imported symbols from ntdll.dll. michael@0: SANDBOX_INTERCEPT NtExports g_nt = { NULL }; michael@0: michael@0: } // namespace sandbox michael@0: michael@0: namespace { michael@0: michael@0: #if defined(_WIN64) michael@0: void* AllocateNearTo(void* source, size_t size) { michael@0: using sandbox::g_nt; michael@0: michael@0: // Start with 1 GB above the source. michael@0: const size_t kOneGB = 0x40000000; michael@0: void* base = reinterpret_cast(source) + kOneGB; michael@0: SIZE_T actual_size = size; michael@0: ULONG_PTR zero_bits = 0; // Not the correct type if used. michael@0: ULONG type = MEM_RESERVE; michael@0: michael@0: NTSTATUS ret; michael@0: int attempts = 0; michael@0: for (; attempts < 41; attempts++) { michael@0: ret = g_nt.AllocateVirtualMemory(NtCurrentProcess, &base, zero_bits, michael@0: &actual_size, type, PAGE_READWRITE); michael@0: if (NT_SUCCESS(ret)) { michael@0: if (base < source || michael@0: base >= reinterpret_cast(source) + 4 * kOneGB) { michael@0: // We won't be able to patch this dll. michael@0: VERIFY_SUCCESS(g_nt.FreeVirtualMemory(NtCurrentProcess, &base, &size, michael@0: MEM_RELEASE)); michael@0: return NULL; michael@0: } michael@0: break; michael@0: } michael@0: michael@0: if (attempts == 30) { michael@0: // Try the first GB. michael@0: base = reinterpret_cast(source); michael@0: } else if (attempts == 40) { michael@0: // Try the highest available address. michael@0: base = NULL; michael@0: type |= MEM_TOP_DOWN; michael@0: } michael@0: michael@0: // Try 100 MB higher. michael@0: base = reinterpret_cast(base) + 100 * 0x100000; michael@0: }; michael@0: michael@0: if (attempts == 41) michael@0: return NULL; michael@0: michael@0: ret = g_nt.AllocateVirtualMemory(NtCurrentProcess, &base, zero_bits, michael@0: &actual_size, MEM_COMMIT, PAGE_READWRITE); michael@0: michael@0: if (!NT_SUCCESS(ret)) { michael@0: VERIFY_SUCCESS(g_nt.FreeVirtualMemory(NtCurrentProcess, &base, &size, michael@0: MEM_RELEASE)); michael@0: base = NULL; michael@0: } michael@0: michael@0: return base; michael@0: } michael@0: #else // defined(_WIN64). michael@0: void* AllocateNearTo(void* source, size_t size) { michael@0: using sandbox::g_nt; michael@0: UNREFERENCED_PARAMETER(source); michael@0: michael@0: // In 32-bit processes allocations below 512k are predictable, so mark michael@0: // anything in that range as reserved and retry until we get a good address. michael@0: const void* const kMinAddress = reinterpret_cast(512 * 1024); michael@0: NTSTATUS ret; michael@0: SIZE_T actual_size; michael@0: void* base; michael@0: do { michael@0: base = NULL; michael@0: actual_size = 64 * 1024; michael@0: ret = g_nt.AllocateVirtualMemory(NtCurrentProcess, &base, 0, &actual_size, michael@0: MEM_RESERVE, PAGE_NOACCESS); michael@0: if (!NT_SUCCESS(ret)) michael@0: return NULL; michael@0: } while (base < kMinAddress); michael@0: michael@0: actual_size = size; michael@0: ret = g_nt.AllocateVirtualMemory(NtCurrentProcess, &base, 0, &actual_size, michael@0: MEM_COMMIT, PAGE_READWRITE); michael@0: if (!NT_SUCCESS(ret)) michael@0: return NULL; michael@0: return base; michael@0: } michael@0: #endif // defined(_WIN64). michael@0: michael@0: } // namespace. michael@0: michael@0: namespace sandbox { michael@0: michael@0: // Handle for our private heap. michael@0: void* g_heap = NULL; michael@0: michael@0: SANDBOX_INTERCEPT HANDLE g_shared_section; michael@0: SANDBOX_INTERCEPT size_t g_shared_IPC_size = 0; michael@0: SANDBOX_INTERCEPT size_t g_shared_policy_size = 0; michael@0: michael@0: void* volatile g_shared_policy_memory = NULL; michael@0: void* volatile g_shared_IPC_memory = NULL; michael@0: michael@0: // Both the IPC and the policy share a single region of memory in which the IPC michael@0: // memory is first and the policy memory is last. michael@0: bool MapGlobalMemory() { michael@0: if (NULL == g_shared_IPC_memory) { michael@0: void* memory = NULL; michael@0: SIZE_T size = 0; michael@0: // Map the entire shared section from the start. michael@0: NTSTATUS ret = g_nt.MapViewOfSection(g_shared_section, NtCurrentProcess, michael@0: &memory, 0, 0, NULL, &size, ViewUnmap, michael@0: 0, PAGE_READWRITE); michael@0: michael@0: if (!NT_SUCCESS(ret) || NULL == memory) { michael@0: NOTREACHED_NT(); michael@0: return false; michael@0: } michael@0: michael@0: if (NULL != _InterlockedCompareExchangePointer(&g_shared_IPC_memory, michael@0: memory, NULL)) { michael@0: // Somebody beat us to the memory setup. michael@0: ret = g_nt.UnmapViewOfSection(NtCurrentProcess, memory); michael@0: VERIFY_SUCCESS(ret); michael@0: } michael@0: DCHECK_NT(g_shared_IPC_size > 0); michael@0: g_shared_policy_memory = reinterpret_cast(g_shared_IPC_memory) michael@0: + g_shared_IPC_size; michael@0: } michael@0: DCHECK_NT(g_shared_policy_memory); michael@0: DCHECK_NT(g_shared_policy_size > 0); michael@0: return true; michael@0: } michael@0: michael@0: void* GetGlobalIPCMemory() { michael@0: if (!MapGlobalMemory()) michael@0: return NULL; michael@0: return g_shared_IPC_memory; michael@0: } michael@0: michael@0: void* GetGlobalPolicyMemory() { michael@0: if (!MapGlobalMemory()) michael@0: return NULL; michael@0: return g_shared_policy_memory; michael@0: } michael@0: michael@0: bool InitHeap() { michael@0: if (!g_heap) { michael@0: // Create a new heap using default values for everything. michael@0: void* heap = g_nt.RtlCreateHeap(HEAP_GROWABLE, NULL, 0, 0, NULL, NULL); michael@0: if (!heap) michael@0: return false; michael@0: michael@0: if (NULL != _InterlockedCompareExchangePointer(&g_heap, heap, NULL)) { michael@0: // Somebody beat us to the memory setup. michael@0: g_nt.RtlDestroyHeap(heap); michael@0: } michael@0: } michael@0: return (g_heap != NULL); michael@0: } michael@0: michael@0: // Physically reads or writes from memory to verify that (at this time), it is michael@0: // valid. Returns a dummy value. michael@0: int TouchMemory(void* buffer, size_t size_bytes, RequiredAccess intent) { michael@0: const int kPageSize = 4096; michael@0: int dummy = 0; michael@0: char* start = reinterpret_cast(buffer); michael@0: char* end = start + size_bytes - 1; michael@0: michael@0: if (WRITE == intent) { michael@0: for (; start < end; start += kPageSize) { michael@0: *start = 0; michael@0: } michael@0: *end = 0; michael@0: } else { michael@0: for (; start < end; start += kPageSize) { michael@0: dummy += *start; michael@0: } michael@0: dummy += *end; michael@0: } michael@0: michael@0: return dummy; michael@0: } michael@0: michael@0: bool ValidParameter(void* buffer, size_t size, RequiredAccess intent) { michael@0: DCHECK_NT(size); michael@0: __try { michael@0: TouchMemory(buffer, size, intent); michael@0: } __except(EXCEPTION_EXECUTE_HANDLER) { michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: NTSTATUS CopyData(void* destination, const void* source, size_t bytes) { michael@0: NTSTATUS ret = STATUS_SUCCESS; michael@0: __try { michael@0: if (SandboxFactory::GetTargetServices()->GetState()->InitCalled()) { michael@0: memcpy(destination, source, bytes); michael@0: } else { michael@0: const char* from = reinterpret_cast(source); michael@0: char* to = reinterpret_cast(destination); michael@0: for (size_t i = 0; i < bytes; i++) { michael@0: to[i] = from[i]; michael@0: } michael@0: } michael@0: } __except(EXCEPTION_EXECUTE_HANDLER) { michael@0: ret = GetExceptionCode(); michael@0: } michael@0: return ret; michael@0: } michael@0: michael@0: // Hacky code... replace with AllocAndCopyObjectAttributes. michael@0: NTSTATUS AllocAndCopyName(const OBJECT_ATTRIBUTES* in_object, michael@0: wchar_t** out_name, uint32* attributes, michael@0: HANDLE* root) { michael@0: if (!InitHeap()) michael@0: return STATUS_NO_MEMORY; michael@0: michael@0: DCHECK_NT(out_name); michael@0: *out_name = NULL; michael@0: NTSTATUS ret = STATUS_UNSUCCESSFUL; michael@0: __try { michael@0: do { michael@0: if (in_object->RootDirectory != static_cast(0) && !root) michael@0: break; michael@0: if (NULL == in_object->ObjectName) michael@0: break; michael@0: if (NULL == in_object->ObjectName->Buffer) michael@0: break; michael@0: michael@0: size_t size = in_object->ObjectName->Length + sizeof(wchar_t); michael@0: *out_name = new(NT_ALLOC) wchar_t[size/sizeof(wchar_t)]; michael@0: if (NULL == *out_name) michael@0: break; michael@0: michael@0: ret = CopyData(*out_name, in_object->ObjectName->Buffer, michael@0: size - sizeof(wchar_t)); michael@0: if (!NT_SUCCESS(ret)) michael@0: break; michael@0: michael@0: (*out_name)[size / sizeof(wchar_t) - 1] = L'\0'; michael@0: michael@0: if (attributes) michael@0: *attributes = in_object->Attributes; michael@0: michael@0: if (root) michael@0: *root = in_object->RootDirectory; michael@0: ret = STATUS_SUCCESS; michael@0: } while (false); michael@0: } __except(EXCEPTION_EXECUTE_HANDLER) { michael@0: ret = GetExceptionCode(); michael@0: } michael@0: michael@0: if (!NT_SUCCESS(ret) && *out_name) { michael@0: operator delete(*out_name, NT_ALLOC); michael@0: *out_name = NULL; michael@0: } michael@0: michael@0: return ret; michael@0: } michael@0: michael@0: NTSTATUS GetProcessId(HANDLE process, ULONG *process_id) { michael@0: PROCESS_BASIC_INFORMATION proc_info; michael@0: ULONG bytes_returned; michael@0: michael@0: NTSTATUS ret = g_nt.QueryInformationProcess(process, ProcessBasicInformation, michael@0: &proc_info, sizeof(proc_info), michael@0: &bytes_returned); michael@0: if (!NT_SUCCESS(ret) || sizeof(proc_info) != bytes_returned) michael@0: return ret; michael@0: michael@0: *process_id = proc_info.UniqueProcessId; michael@0: return STATUS_SUCCESS; michael@0: } michael@0: michael@0: bool IsSameProcess(HANDLE process) { michael@0: if (NtCurrentProcess == process) michael@0: return true; michael@0: michael@0: static ULONG s_process_id = 0; michael@0: michael@0: if (!s_process_id) { michael@0: NTSTATUS ret = GetProcessId(NtCurrentProcess, &s_process_id); michael@0: if (!NT_SUCCESS(ret)) michael@0: return false; michael@0: } michael@0: michael@0: ULONG process_id; michael@0: NTSTATUS ret = GetProcessId(process, &process_id); michael@0: if (!NT_SUCCESS(ret)) michael@0: return false; michael@0: michael@0: return (process_id == s_process_id); michael@0: } michael@0: michael@0: bool IsValidImageSection(HANDLE section, PVOID *base, PLARGE_INTEGER offset, michael@0: PSIZE_T view_size) { michael@0: if (!section || !base || !view_size || offset) michael@0: return false; michael@0: michael@0: HANDLE query_section; michael@0: michael@0: NTSTATUS ret = g_nt.DuplicateObject(NtCurrentProcess, section, michael@0: NtCurrentProcess, &query_section, michael@0: SECTION_QUERY, 0, 0); michael@0: if (!NT_SUCCESS(ret)) michael@0: return false; michael@0: michael@0: SECTION_BASIC_INFORMATION basic_info; michael@0: SIZE_T bytes_returned; michael@0: ret = g_nt.QuerySection(query_section, SectionBasicInformation, &basic_info, michael@0: sizeof(basic_info), &bytes_returned); michael@0: michael@0: VERIFY_SUCCESS(g_nt.Close(query_section)); michael@0: michael@0: if (!NT_SUCCESS(ret) || sizeof(basic_info) != bytes_returned) michael@0: return false; michael@0: michael@0: if (!(basic_info.Attributes & SEC_IMAGE)) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: UNICODE_STRING* AnsiToUnicode(const char* string) { michael@0: ANSI_STRING ansi_string; michael@0: ansi_string.Length = static_cast(g_nt.strlen(string)); michael@0: ansi_string.MaximumLength = ansi_string.Length + 1; michael@0: ansi_string.Buffer = const_cast(string); michael@0: michael@0: if (ansi_string.Length > ansi_string.MaximumLength) michael@0: return NULL; michael@0: michael@0: size_t name_bytes = ansi_string.MaximumLength * sizeof(wchar_t) + michael@0: sizeof(UNICODE_STRING); michael@0: michael@0: UNICODE_STRING* out_string = reinterpret_cast( michael@0: new(NT_ALLOC) char[name_bytes]); michael@0: if (!out_string) michael@0: return NULL; michael@0: michael@0: out_string->MaximumLength = ansi_string.MaximumLength * sizeof(wchar_t); michael@0: out_string->Buffer = reinterpret_cast(&out_string[1]); michael@0: michael@0: BOOLEAN alloc_destination = FALSE; michael@0: NTSTATUS ret = g_nt.RtlAnsiStringToUnicodeString(out_string, &ansi_string, michael@0: alloc_destination); michael@0: DCHECK_NT(STATUS_BUFFER_OVERFLOW != ret); michael@0: if (!NT_SUCCESS(ret)) { michael@0: operator delete(out_string, NT_ALLOC); michael@0: return NULL; michael@0: } michael@0: michael@0: return out_string; michael@0: } michael@0: michael@0: UNICODE_STRING* GetImageInfoFromModule(HMODULE module, uint32* flags) { michael@0: UNICODE_STRING* out_name = NULL; michael@0: __try { michael@0: do { michael@0: *flags = 0; michael@0: base::win::PEImage pe(module); michael@0: michael@0: if (!pe.VerifyMagic()) michael@0: break; michael@0: *flags |= MODULE_IS_PE_IMAGE; michael@0: michael@0: PIMAGE_EXPORT_DIRECTORY exports = pe.GetExportDirectory(); michael@0: if (exports) { michael@0: char* name = reinterpret_cast(pe.RVAToAddr(exports->Name)); michael@0: out_name = AnsiToUnicode(name); michael@0: } michael@0: michael@0: PIMAGE_NT_HEADERS headers = pe.GetNTHeaders(); michael@0: if (headers) { michael@0: if (headers->OptionalHeader.AddressOfEntryPoint) michael@0: *flags |= MODULE_HAS_ENTRY_POINT; michael@0: if (headers->OptionalHeader.SizeOfCode) michael@0: *flags |= MODULE_HAS_CODE; michael@0: } michael@0: } while (false); michael@0: } __except(EXCEPTION_EXECUTE_HANDLER) { michael@0: } michael@0: michael@0: return out_name; michael@0: } michael@0: michael@0: UNICODE_STRING* GetBackingFilePath(PVOID address) { michael@0: // We'll start with something close to max_path charactes for the name. michael@0: ULONG buffer_bytes = MAX_PATH * 2; michael@0: michael@0: for (;;) { michael@0: MEMORY_SECTION_NAME* section_name = reinterpret_cast( michael@0: new(NT_ALLOC) char[buffer_bytes]); michael@0: michael@0: if (!section_name) michael@0: return NULL; michael@0: michael@0: ULONG returned_bytes; michael@0: NTSTATUS ret = g_nt.QueryVirtualMemory(NtCurrentProcess, address, michael@0: MemorySectionName, section_name, michael@0: buffer_bytes, &returned_bytes); michael@0: michael@0: if (STATUS_BUFFER_OVERFLOW == ret) { michael@0: // Retry the call with the given buffer size. michael@0: operator delete(section_name, NT_ALLOC); michael@0: section_name = NULL; michael@0: buffer_bytes = returned_bytes; michael@0: continue; michael@0: } michael@0: if (!NT_SUCCESS(ret)) { michael@0: operator delete(section_name, NT_ALLOC); michael@0: return NULL; michael@0: } michael@0: michael@0: return reinterpret_cast(section_name); michael@0: } michael@0: } michael@0: michael@0: UNICODE_STRING* ExtractModuleName(const UNICODE_STRING* module_path) { michael@0: if ((!module_path) || (!module_path->Buffer)) michael@0: return NULL; michael@0: michael@0: wchar_t* sep = NULL; michael@0: int start_pos = module_path->Length / sizeof(wchar_t) - 1; michael@0: int ix = start_pos; michael@0: michael@0: for (; ix >= 0; --ix) { michael@0: if (module_path->Buffer[ix] == L'\\') { michael@0: sep = &module_path->Buffer[ix]; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Ends with path separator. Not a valid module name. michael@0: if ((ix == start_pos) && sep) michael@0: return NULL; michael@0: michael@0: // No path separator found. Use the entire name. michael@0: if (!sep) { michael@0: sep = &module_path->Buffer[-1]; michael@0: } michael@0: michael@0: // Add one to the size so we can null terminate the string. michael@0: size_t size_bytes = (start_pos - ix + 1) * sizeof(wchar_t); michael@0: michael@0: // Based on the code above, size_bytes should always be small enough michael@0: // to make the static_cast below safe. michael@0: DCHECK_NT(kuint16max > size_bytes); michael@0: char* str_buffer = new(NT_ALLOC) char[size_bytes + sizeof(UNICODE_STRING)]; michael@0: if (!str_buffer) michael@0: return NULL; michael@0: michael@0: UNICODE_STRING* out_string = reinterpret_cast(str_buffer); michael@0: out_string->Buffer = reinterpret_cast(&out_string[1]); michael@0: out_string->Length = static_cast(size_bytes - sizeof(wchar_t)); michael@0: out_string->MaximumLength = static_cast(size_bytes); michael@0: michael@0: NTSTATUS ret = CopyData(out_string->Buffer, &sep[1], out_string->Length); michael@0: if (!NT_SUCCESS(ret)) { michael@0: operator delete(out_string, NT_ALLOC); michael@0: return NULL; michael@0: } michael@0: michael@0: out_string->Buffer[out_string->Length / sizeof(wchar_t)] = L'\0'; michael@0: return out_string; michael@0: } michael@0: michael@0: NTSTATUS AutoProtectMemory::ChangeProtection(void* address, size_t bytes, michael@0: ULONG protect) { michael@0: DCHECK_NT(!changed_); michael@0: SIZE_T new_bytes = bytes; michael@0: NTSTATUS ret = g_nt.ProtectVirtualMemory(NtCurrentProcess, &address, michael@0: &new_bytes, protect, &old_protect_); michael@0: if (NT_SUCCESS(ret)) { michael@0: changed_ = true; michael@0: address_ = address; michael@0: bytes_ = new_bytes; michael@0: } michael@0: michael@0: return ret; michael@0: } michael@0: michael@0: NTSTATUS AutoProtectMemory::RevertProtection() { michael@0: if (!changed_) michael@0: return STATUS_SUCCESS; michael@0: michael@0: DCHECK_NT(address_); michael@0: DCHECK_NT(bytes_); michael@0: michael@0: SIZE_T new_bytes = bytes_; michael@0: NTSTATUS ret = g_nt.ProtectVirtualMemory(NtCurrentProcess, &address_, michael@0: &new_bytes, old_protect_, michael@0: &old_protect_); michael@0: DCHECK_NT(NT_SUCCESS(ret)); michael@0: michael@0: changed_ = false; michael@0: address_ = NULL; michael@0: bytes_ = 0; michael@0: old_protect_ = 0; michael@0: michael@0: return ret; michael@0: } michael@0: michael@0: bool IsSupportedRenameCall(FILE_RENAME_INFORMATION* file_info, DWORD length, michael@0: uint32 file_info_class) { michael@0: if (FileRenameInformation != file_info_class) michael@0: return false; michael@0: michael@0: if (length < sizeof(FILE_RENAME_INFORMATION)) michael@0: return false; michael@0: michael@0: // Make sure file name length doesn't exceed the message length michael@0: if (length - offsetof(FILE_RENAME_INFORMATION, FileName) < michael@0: file_info->FileNameLength) michael@0: return false; michael@0: michael@0: // We don't support a root directory. michael@0: if (file_info->RootDirectory) michael@0: return false; michael@0: michael@0: // Check if it starts with \\??\\. We don't support relative paths. michael@0: if (file_info->FileNameLength < 4 || file_info->FileNameLength > kuint16max) michael@0: return false; michael@0: michael@0: if (file_info->FileName[0] != L'\\' || michael@0: file_info->FileName[1] != L'?' || michael@0: file_info->FileName[2] != L'?' || michael@0: file_info->FileName[3] != L'\\') michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: } // namespace sandbox michael@0: michael@0: void* operator new(size_t size, sandbox::AllocationType type, michael@0: void* near_to) { michael@0: using namespace sandbox; michael@0: michael@0: if (NT_ALLOC == type) { michael@0: if (!InitHeap()) michael@0: return NULL; michael@0: michael@0: // Use default flags for the allocation. michael@0: return g_nt.RtlAllocateHeap(sandbox::g_heap, 0, size); michael@0: } else if (NT_PAGE == type) { michael@0: return AllocateNearTo(near_to, size); michael@0: } michael@0: NOTREACHED_NT(); michael@0: return NULL; michael@0: } michael@0: michael@0: void operator delete(void* memory, sandbox::AllocationType type) { michael@0: using namespace sandbox; michael@0: michael@0: if (NT_ALLOC == type) { michael@0: // Use default flags. michael@0: VERIFY(g_nt.RtlFreeHeap(sandbox::g_heap, 0, memory)); michael@0: } else if (NT_PAGE == type) { michael@0: void* base = memory; michael@0: SIZE_T size = 0; michael@0: VERIFY_SUCCESS(g_nt.FreeVirtualMemory(NtCurrentProcess, &base, &size, michael@0: MEM_RELEASE)); michael@0: } else { michael@0: NOTREACHED_NT(); michael@0: } michael@0: } michael@0: michael@0: void operator delete(void* memory, sandbox::AllocationType type, michael@0: void* near_to) { michael@0: UNREFERENCED_PARAMETER(near_to); michael@0: operator delete(memory, type); michael@0: } michael@0: michael@0: void* __cdecl operator new(size_t size, void* buffer, michael@0: sandbox::AllocationType type) { michael@0: UNREFERENCED_PARAMETER(size); michael@0: UNREFERENCED_PARAMETER(type); michael@0: return buffer; michael@0: } michael@0: michael@0: void __cdecl operator delete(void* memory, void* buffer, michael@0: sandbox::AllocationType type) { michael@0: UNREFERENCED_PARAMETER(memory); michael@0: UNREFERENCED_PARAMETER(buffer); michael@0: UNREFERENCED_PARAMETER(type); michael@0: }