summaryrefslogtreecommitdiffstats
path: root/sandbox/win/src/sandbox_nt_util.cc
diff options
context:
space:
mode:
authorjln@chromium.org <jln@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-18 00:59:15 +0000
committerjln@chromium.org <jln@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-18 00:59:15 +0000
commit18149178646e45f3d7dde865efbeabbab431799a (patch)
treecc19ce0fc5cc1927695c789212fab93a195b9d9f /sandbox/win/src/sandbox_nt_util.cc
parent4633cdb53427b31197d3c6f991f07bee2a04e0df (diff)
downloadchromium_src-18149178646e45f3d7dde865efbeabbab431799a.zip
chromium_src-18149178646e45f3d7dde865efbeabbab431799a.tar.gz
chromium_src-18149178646e45f3d7dde865efbeabbab431799a.tar.bz2
Move the Windows sandbox to sandbox/win
This is a rather large refactor to move the Windows sandbox to the right place. BUG= TEST= NOTRY=true TBR=sky@chromium.org Review URL: https://chromiumcodereview.appspot.com/10689170 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@147151 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'sandbox/win/src/sandbox_nt_util.cc')
-rw-r--r--sandbox/win/src/sandbox_nt_util.cc599
1 files changed, 599 insertions, 0 deletions
diff --git a/sandbox/win/src/sandbox_nt_util.cc b/sandbox/win/src/sandbox_nt_util.cc
new file mode 100644
index 0000000..4c937bd
--- /dev/null
+++ b/sandbox/win/src/sandbox_nt_util.cc
@@ -0,0 +1,599 @@
+// 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 "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 = { NULL };
+
+}
+
+namespace {
+
+#if defined(_WIN64)
+void* AllocateNearTo(void* source, size_t size) {
+ using sandbox::g_nt;
+
+ // Start with 1 GB above the source.
+ const unsigned int kOneGB = 0x40000000;
+ void* base = reinterpret_cast<char*>(source) + kOneGB;
+ SIZE_T actual_size = size;
+ ULONG_PTR zero_bits = 0; // Not the correct type if used.
+ ULONG type = MEM_RESERVE;
+
+ if (reinterpret_cast<SIZE_T>(source) > 0x7ff80000000) {
+ // We are at the top of the address space. Let's try the highest available
+ // address.
+ base = NULL;
+ type |= MEM_TOP_DOWN;
+ }
+
+ NTSTATUS ret;
+ int attempts = 0;
+ for (; attempts < 20; attempts++) {
+ ret = g_nt.AllocateVirtualMemory(NtCurrentProcess, &base, zero_bits,
+ &actual_size, type, PAGE_READWRITE);
+ if (NT_SUCCESS(ret)) {
+ if (base < source) {
+ // We won't be able to patch this dll.
+ VERIFY_SUCCESS(g_nt.FreeVirtualMemory(NtCurrentProcess, &base, &size,
+ MEM_RELEASE));
+ return NULL;
+ }
+ break;
+ }
+
+ // Try 100 MB higher.
+ base = reinterpret_cast<char*>(base) + 100 * 0x100000;
+ };
+
+ if (attempts == 20)
+ return NULL;
+
+ ret = g_nt.AllocateVirtualMemory(NtCurrentProcess, &base, zero_bits,
+ &actual_size, MEM_COMMIT, PAGE_READWRITE);
+
+ if (!NT_SUCCESS(ret)) {
+ VERIFY_SUCCESS(g_nt.FreeVirtualMemory(NtCurrentProcess, &base, &size,
+ MEM_RELEASE));
+ base = NULL;
+ }
+
+ return base;
+}
+#else // defined(_WIN64).
+void* AllocateNearTo(void* source, size_t size) {
+ using sandbox::g_nt;
+ UNREFERENCED_PARAMETER(source);
+
+ // 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 = NULL;
+ actual_size = 64 * 1024;
+ ret = g_nt.AllocateVirtualMemory(NtCurrentProcess, &base, 0, &actual_size,
+ MEM_RESERVE, PAGE_NOACCESS);
+ if (!NT_SUCCESS(ret))
+ return NULL;
+ } while (base < kMinAddress);
+
+ actual_size = size;
+ ret = g_nt.AllocateVirtualMemory(NtCurrentProcess, &base, 0, &actual_size,
+ MEM_COMMIT, PAGE_READWRITE);
+ if (!NT_SUCCESS(ret))
+ return NULL;
+ return base;
+}
+#endif // defined(_WIN64).
+
+} // namespace.
+
+namespace sandbox {
+
+// Handle for our private heap.
+void* g_heap = NULL;
+
+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 = NULL;
+void* volatile g_shared_IPC_memory = NULL;
+
+// 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 (NULL == g_shared_IPC_memory) {
+ void* memory = NULL;
+ SIZE_T size = 0;
+ // Map the entire shared section from the start.
+ NTSTATUS ret = g_nt.MapViewOfSection(g_shared_section, NtCurrentProcess,
+ &memory, 0, 0, NULL, &size, ViewUnmap,
+ 0, PAGE_READWRITE);
+
+ if (!NT_SUCCESS(ret) || NULL == memory) {
+ NOTREACHED_NT();
+ return false;
+ }
+
+ if (NULL != _InterlockedCompareExchangePointer(&g_shared_IPC_memory,
+ memory, NULL)) {
+ // Somebody beat us to the memory setup.
+ ret = g_nt.UnmapViewOfSection(NtCurrentProcess, memory);
+ VERIFY_SUCCESS(ret);
+ }
+ 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 NULL;
+ return g_shared_IPC_memory;
+}
+
+void* GetGlobalPolicyMemory() {
+ if (!MapGlobalMemory())
+ return NULL;
+ 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, NULL, 0, 0, NULL, NULL);
+ if (!heap)
+ return false;
+
+ if (NULL != _InterlockedCompareExchangePointer(&g_heap, heap, NULL)) {
+ // Somebody beat us to the memory setup.
+ g_nt.RtlDestroyHeap(heap);
+ }
+ }
+ return (g_heap) ? true : false;
+}
+
+// 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;
+ char* start = reinterpret_cast<char*>(buffer);
+ char* end = start + size_bytes - 1;
+
+ if (WRITE == intent) {
+ for (; start < end; start += kPageSize) {
+ *start = 0;
+ }
+ *end = 0;
+ } 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 {
+ if (SandboxFactory::GetTargetServices()->GetState()->InitCalled()) {
+ memcpy(destination, source, bytes);
+ } else {
+ const char* from = reinterpret_cast<const char*>(source);
+ char* to = reinterpret_cast<char*>(destination);
+ for (size_t i = 0; i < bytes; i++) {
+ to[i] = from[i];
+ }
+ }
+ } __except(EXCEPTION_EXECUTE_HANDLER) {
+ ret = GetExceptionCode();
+ }
+ return ret;
+}
+
+// Hacky code... replace with AllocAndCopyObjectAttributes.
+NTSTATUS AllocAndCopyName(const OBJECT_ATTRIBUTES* in_object,
+ wchar_t** out_name, uint32* attributes,
+ HANDLE* root) {
+ if (!InitHeap())
+ return STATUS_NO_MEMORY;
+
+ DCHECK_NT(out_name);
+ *out_name = NULL;
+ NTSTATUS ret = STATUS_UNSUCCESSFUL;
+ __try {
+ do {
+ if (in_object->RootDirectory != static_cast<HANDLE>(0) && !root)
+ break;
+ if (NULL == in_object->ObjectName)
+ break;
+ if (NULL == in_object->ObjectName->Buffer)
+ break;
+
+ size_t size = in_object->ObjectName->Length + sizeof(wchar_t);
+ *out_name = new(NT_ALLOC) wchar_t[size/sizeof(wchar_t)];
+ if (NULL == *out_name)
+ break;
+
+ ret = CopyData(*out_name, in_object->ObjectName->Buffer,
+ size - sizeof(wchar_t));
+ if (!NT_SUCCESS(ret))
+ break;
+
+ (*out_name)[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) {
+ operator delete(*out_name, NT_ALLOC);
+ *out_name = NULL;
+ }
+
+ return ret;
+}
+
+NTSTATUS GetProcessId(HANDLE process, ULONG *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 ULONG s_process_id = 0;
+
+ if (!s_process_id) {
+ NTSTATUS ret = GetProcessId(NtCurrentProcess, &s_process_id);
+ if (!NT_SUCCESS(ret))
+ return false;
+ }
+
+ ULONG 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 NULL;
+
+ 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 NULL;
+
+ 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 NULL;
+ }
+
+ return out_string;
+}
+
+UNICODE_STRING* GetImageInfoFromModule(HMODULE module, uint32* flags) {
+ UNICODE_STRING* out_name = NULL;
+ __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;
+}
+
+UNICODE_STRING* GetBackingFilePath(PVOID address) {
+ // We'll start with something close to max_path charactes for the name.
+ ULONG 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 NULL;
+
+ ULONG 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 = NULL;
+ buffer_bytes = returned_bytes;
+ continue;
+ }
+ if (!NT_SUCCESS(ret)) {
+ operator delete(section_name, NT_ALLOC);
+ return NULL;
+ }
+
+ return reinterpret_cast<UNICODE_STRING*>(section_name);
+ }
+}
+
+UNICODE_STRING* ExtractModuleName(const UNICODE_STRING* module_path) {
+ if ((!module_path) || (!module_path->Buffer))
+ return NULL;
+
+ wchar_t* sep = NULL;
+ 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 NULL;
+
+ // 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(kuint16max > size_bytes);
+ char* str_buffer = new(NT_ALLOC) char[size_bytes + sizeof(UNICODE_STRING)];
+ if (!str_buffer)
+ return NULL;
+
+ 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 NULL;
+ }
+
+ 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_ = NULL;
+ bytes_ = 0;
+ old_protect_ = 0;
+
+ return ret;
+}
+
+bool IsSupportedRenameCall(FILE_RENAME_INFORMATION* file_info, DWORD length,
+ uint32 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;
+
+ // Check if it starts with \\??\\. We don't support relative paths.
+ if (file_info->FileNameLength < 4 || file_info->FileNameLength > kuint16max)
+ return false;
+
+ if (file_info->FileName[0] != L'\\' ||
+ file_info->FileName[1] != L'?' ||
+ file_info->FileName[2] != L'?' ||
+ file_info->FileName[3] != L'\\')
+ return false;
+
+ return true;
+}
+
+} // namespace sandbox
+
+void* operator new(size_t size, sandbox::AllocationType type,
+ void* near_to) {
+ using namespace sandbox;
+
+ if (NT_ALLOC == type) {
+ if (!InitHeap())
+ return NULL;
+
+ // Use default flags for the allocation.
+ return g_nt.RtlAllocateHeap(sandbox::g_heap, 0, size);
+ } else if (NT_PAGE == type) {
+ return AllocateNearTo(near_to, size);
+ }
+ NOTREACHED_NT();
+ return NULL;
+}
+
+void operator delete(void* memory, sandbox::AllocationType type) {
+ using namespace sandbox;
+
+ if (NT_ALLOC == type) {
+ // Use default flags.
+ VERIFY(g_nt.RtlFreeHeap(sandbox::g_heap, 0, memory));
+ } else if (NT_PAGE == type) {
+ void* base = memory;
+ SIZE_T size = 0;
+ VERIFY_SUCCESS(g_nt.FreeVirtualMemory(NtCurrentProcess, &base, &size,
+ MEM_RELEASE));
+ } else {
+ NOTREACHED_NT();
+ }
+}
+
+void operator delete(void* memory, sandbox::AllocationType type,
+ void* near_to) {
+ UNREFERENCED_PARAMETER(near_to);
+ operator delete(memory, type);
+}
+
+void* __cdecl operator new(size_t size, void* buffer,
+ sandbox::AllocationType type) {
+ UNREFERENCED_PARAMETER(size);
+ UNREFERENCED_PARAMETER(type);
+ return buffer;
+}
+
+void __cdecl operator delete(void* memory, void* buffer,
+ sandbox::AllocationType type) {
+ UNREFERENCED_PARAMETER(memory);
+ UNREFERENCED_PARAMETER(buffer);
+ UNREFERENCED_PARAMETER(type);
+}