From 7de04b02f85ddfeee778b71f804d4d30c971ccd8 Mon Sep 17 00:00:00 2001 From: Scott Graham Date: Fri, 16 Oct 2015 15:31:32 -0700 Subject: [PATCH] win: Add Handles() to ProcessInfo To eventually be used to fill out MINIDUMP_HANDLE_DESCRIPTOR. R=mark@chromium.org BUG=crashpad:21, crashpad:46, crashpad:52 Review URL: https://codereview.chromium.org/1400413002 . --- util/win/nt_internals.cc | 16 ++++ util/win/nt_internals.h | 9 ++ util/win/process_info.cc | 170 ++++++++++++++++++++++++++++++++++ util/win/process_info.h | 40 ++++++++ util/win/process_info_test.cc | 112 ++++++++++++++++++++++ util/win/process_structs.h | 17 ++++ 6 files changed, 364 insertions(+) diff --git a/util/win/nt_internals.cc b/util/win/nt_internals.cc index 01f889b9..3fb5ef20 100644 --- a/util/win/nt_internals.cc +++ b/util/win/nt_internals.cc @@ -72,6 +72,22 @@ NTSTATUS NtOpenThread(PHANDLE thread_handle, static_cast(client_id)); } +NTSTATUS NtQueryObject(HANDLE handle, + OBJECT_INFORMATION_CLASS object_information_class, + void* object_information, + ULONG object_information_length, + ULONG* return_length) { + static decltype(::NtQueryObject)* nt_query_object = + reinterpret_cast( + GetProcAddress(LoadLibrary(L"ntdll.dll"), "NtQueryObject")); + DCHECK(nt_query_object); + return nt_query_object(handle, + object_information_class, + object_information, + object_information_length, + return_length); +} + // Explicit instantiations with the only 2 valid template arguments to avoid // putting the body of the function in the header. template NTSTATUS NtOpenThread( diff --git a/util/win/nt_internals.h b/util/win/nt_internals.h index 4743c434..aab884f5 100644 --- a/util/win/nt_internals.h +++ b/util/win/nt_internals.h @@ -27,6 +27,9 @@ namespace crashpad { // winternal.h defines THREADINFOCLASS, but not all members. enum { ThreadBasicInformation = 0 }; +// winternal.h defines SYSTEM_INFORMATION_CLASS, but not all members. +enum { SystemExtendedHandleInformation = 64 }; + NTSTATUS NtQuerySystemInformation( SYSTEM_INFORMATION_CLASS system_information_class, PVOID system_information, @@ -45,4 +48,10 @@ NTSTATUS NtOpenThread(PHANDLE thread_handle, POBJECT_ATTRIBUTES object_attributes, const process_types::CLIENT_ID* client_id); +NTSTATUS NtQueryObject(HANDLE handle, + OBJECT_INFORMATION_CLASS object_information_class, + void* object_information, + ULONG object_information_length, + ULONG* return_length); + } // namespace crashpad diff --git a/util/win/process_info.cc b/util/win/process_info.cc index fa413cc3..01748ff4 100644 --- a/util/win/process_info.cc +++ b/util/win/process_info.cc @@ -20,12 +20,15 @@ #include #include "base/logging.h" +#include "base/memory/scoped_ptr.h" #include "base/strings/stringprintf.h" #include "base/template_util.h" #include "build/build_config.h" #include "util/numeric/safe_assignment.h" +#include "util/win/nt_internals.h" #include "util/win/ntstatus_logging.h" #include "util/win/process_structs.h" +#include "util/win/scoped_handle.h" namespace crashpad { @@ -127,6 +130,34 @@ MEMORY_BASIC_INFORMATION64 MemoryBasicInformationToMemoryBasicInformation64( return mbi64; } +// NtQueryObject with a retry for size mismatch as well as a minimum size to +// retrieve (and expect). +scoped_ptr QueryObject( + HANDLE handle, + OBJECT_INFORMATION_CLASS object_information_class, + ULONG minimum_size) { + ULONG size = minimum_size; + ULONG return_length; + scoped_ptr buffer(new uint8_t[size]); + NTSTATUS status = crashpad::NtQueryObject( + handle, object_information_class, buffer.get(), size, &return_length); + if (status == STATUS_INFO_LENGTH_MISMATCH) { + DCHECK_GT(return_length, size); + size = return_length; + buffer.reset(new uint8_t[size]); + status = crashpad::NtQueryObject( + handle, object_information_class, buffer.get(), size, &return_length); + } + + if (!NT_SUCCESS(status)) { + NTSTATUS_LOG(ERROR, status) << "NtQueryObject"; + return scoped_ptr(); + } + + DCHECK_GE(return_length, minimum_size); + return buffer.Pass(); +} + } // namespace template @@ -314,20 +345,150 @@ bool ReadMemoryInfo(HANDLE process, bool is_64_bit, ProcessInfo* process_info) { return true; } +std::vector ProcessInfo::BuildHandleVector( + HANDLE process) const { + ULONG buffer_size = 2 * 1024 * 1024; + scoped_ptr buffer(new uint8_t[buffer_size]); + + // Typically if the buffer were too small, STATUS_INFO_LENGTH_MISMATCH would + // return the correct size in the final argument, but it does not for + // SystemExtendedHandleInformation, so we loop and attempt larger sizes. + NTSTATUS status; + ULONG returned_length; + for (int tries = 0; tries < 5; ++tries) { + status = crashpad::NtQuerySystemInformation( + static_cast(SystemExtendedHandleInformation), + buffer.get(), + buffer_size, + &returned_length); + if (NT_SUCCESS(status) || status != STATUS_INFO_LENGTH_MISMATCH) + break; + + buffer_size *= 2; + buffer.reset(); + buffer.reset(new uint8_t[buffer_size]); + } + + if (!NT_SUCCESS(status)) { + NTSTATUS_LOG(ERROR, status) + << "NtQuerySystemInformation SystemExtendedHandleInformation"; + return std::vector(); + } + + const auto& system_handle_information_ex = + *reinterpret_cast( + buffer.get()); + + DCHECK_LE(offsetof(process_types::SYSTEM_HANDLE_INFORMATION_EX, Handles) + + system_handle_information_ex.NumberOfHandles * + sizeof(system_handle_information_ex.Handles[0]), + returned_length); + + std::vector handles; + + for (size_t i = 0; i < system_handle_information_ex.NumberOfHandles; ++i) { + const auto& handle = system_handle_information_ex.Handles[i]; + if (handle.UniqueProcessId != process_id_) + continue; + + Handle result_handle; + result_handle.handle = + static_cast(reinterpret_cast(handle.HandleValue)); + result_handle.attributes = handle.HandleAttributes; + result_handle.granted_access = handle.GrantedAccess; + + // TODO(scottmg): Could special case for self. + HANDLE dup_handle; + if (DuplicateHandle(process, + reinterpret_cast(handle.HandleValue), + GetCurrentProcess(), + &dup_handle, + 0, + false, + DUPLICATE_SAME_ACCESS)) { + // Some handles cannot be duplicated, for example, handles of type + // EtwRegistration. If we fail to duplicate, then we can't gather any more + // information, but include the information that we do have already. + ScopedKernelHANDLE scoped_dup_handle(dup_handle); + + scoped_ptr object_basic_information_buffer = + QueryObject(dup_handle, + ObjectBasicInformation, + sizeof(PUBLIC_OBJECT_BASIC_INFORMATION)); + if (object_basic_information_buffer) { + PUBLIC_OBJECT_BASIC_INFORMATION* object_basic_information = + reinterpret_cast( + object_basic_information_buffer.get()); + // The Attributes and GrantedAccess sometimes differ slightly between + // the data retrieved in SYSTEM_HANDLE_INFORMATION_EX and + // PUBLIC_OBJECT_TYPE_INFORMATION. We prefer the values in + // SYSTEM_HANDLE_INFORMATION_EX because they were retrieved from the + // target process, rather than on the duplicated handle, so don't use + // them here. + + // Subtract one to account for our DuplicateHandle() and another for + // NtQueryObject() while the query was being executed. + DCHECK_GT(object_basic_information->PointerCount, 2u); + result_handle.pointer_count = + object_basic_information->PointerCount - 2; + + // Subtract one to account for our DuplicateHandle(). + DCHECK_GT(object_basic_information->HandleCount, 1u); + result_handle.handle_count = object_basic_information->HandleCount - 1; + } + + scoped_ptr object_type_information_buffer = + QueryObject(dup_handle, + ObjectTypeInformation, + sizeof(PUBLIC_OBJECT_TYPE_INFORMATION)); + if (object_type_information_buffer) { + PUBLIC_OBJECT_TYPE_INFORMATION* object_type_information = + reinterpret_cast( + object_type_information_buffer.get()); + + DCHECK_EQ(object_type_information->TypeName.Length % + sizeof(result_handle.type_name[0]), + 0u); + result_handle.type_name = + std::wstring(object_type_information->TypeName.Buffer, + object_type_information->TypeName.Length / + sizeof(result_handle.type_name[0])); + } + } + + handles.push_back(result_handle); + } + return handles; +} + ProcessInfo::Module::Module() : name(), dll_base(0), size(0), timestamp() { } ProcessInfo::Module::~Module() { } +ProcessInfo::Handle::Handle() + : type_name(), + handle(0), + attributes(0), + granted_access(0), + pointer_count(0), + handle_count(0) { +} + +ProcessInfo::Handle::~Handle() { +} + ProcessInfo::ProcessInfo() : process_id_(), inherited_from_process_id_(), + process_(), command_line_(), peb_address_(0), peb_size_(0), modules_(), memory_info_(), + handles_(), is_64_bit_(false), is_wow64_(false), initialized_() { @@ -339,6 +500,8 @@ ProcessInfo::~ProcessInfo() { bool ProcessInfo::Initialize(HANDLE process) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + process_ = process; + is_wow64_ = IsProcessWow64(process); if (is_wow64_) { @@ -439,6 +602,13 @@ ProcessInfo::GetReadableRanges( return GetReadableRangesOfMemoryMap(range, MemoryInfo()); } +const std::vector& ProcessInfo::Handles() { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + if (handles_.empty()) + handles_ = BuildHandleVector(process_); + return handles_; +} + std::vector> GetReadableRangesOfMemoryMap( const CheckedRange& range, const std::vector& memory_info) { diff --git a/util/win/process_info.h b/util/win/process_info.h index dac7b7c9..019e945c 100644 --- a/util/win/process_info.h +++ b/util/win/process_info.h @@ -50,6 +50,39 @@ class ProcessInfo { time_t timestamp; }; + struct Handle { + Handle(); + ~Handle(); + + //! \brief A string representation of the handle's type. + std::wstring type_name; + + //! \brief The handle's value. + //! + //! See https://msdn.microsoft.com/en-us/library/windows/desktop/aa384203 on + //! 32 bits being the correct size for `HANDLE`s, even on Windows x64. + uint32_t handle; + + //! \brief The attributes for the handle, e.g. `OBJ_INHERIT`, + //! `OBJ_CASE_INSENSITIVE`, etc. + uint32_t attributes; + + //! \brief The `ACCESS_MASK` for the handle in this process. + //! + //! See + //! http://blogs.msdn.com/b/openspecification/archive/2010/04/01/about-the-access-mask-structure.aspx + //! for more information. + uint32_t granted_access; + + //! \brief The number of kernel references to the object that this handle + //! refers to. + uint32_t pointer_count; + + //! \brief The number of open handles to the object that this handle refers + //! to. + uint32_t handle_count; + }; + ProcessInfo(); ~ProcessInfo(); @@ -106,6 +139,9 @@ class ProcessInfo { std::vector> GetReadableRanges( const CheckedRange& range) const; + //! \brief Retrieves information about open handles in the target process. + const std::vector& Handles(); + private: template friend bool GetProcessBasicInformation(HANDLE process, @@ -122,13 +158,17 @@ class ProcessInfo { bool is_64_bit, ProcessInfo* process_info); + std::vector BuildHandleVector(HANDLE process) const; + pid_t process_id_; pid_t inherited_from_process_id_; + HANDLE process_; std::wstring command_line_; WinVMAddress peb_address_; WinVMSize peb_size_; std::vector modules_; std::vector memory_info_; + std::vector handles_; bool is_64_bit_; bool is_wow64_; InitializationStateDcheck initialized_; diff --git a/util/win/process_info_test.cc b/util/win/process_info_test.cc index ce9e8ac7..4a8a209f 100644 --- a/util/win/process_info_test.cc +++ b/util/win/process_info_test.cc @@ -20,8 +20,12 @@ #include "base/files/file_path.h" #include "base/memory/scoped_ptr.h" +#include "base/rand_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "gtest/gtest.h" +#include "test/scoped_temp_dir.h" #include "test/paths.h" #include "test/win/child_launcher.h" #include "util/file/file_io.h" @@ -510,6 +514,114 @@ TEST(ProcessInfo, ReadableRanges) { &bytes_read)); } +struct ScopedRegistryKeyCloseTraits { + static HKEY InvalidValue() { + return nullptr; + } + static void Free(HKEY key) { + RegCloseKey(key); + } +}; +using ScopedRegistryKey = + base::ScopedGeneric; + +TEST(ProcessInfo, Handles) { + ScopedTempDir temp_dir; + + ScopedFileHandle file(LoggingOpenFileForWrite( + temp_dir.path().Append(FILE_PATH_LITERAL("test_file")), + FileWriteMode::kTruncateOrCreate, + FilePermissions::kWorldReadable)); + ASSERT_TRUE(file.is_valid()); + + SECURITY_ATTRIBUTES security_attributes = {0}; + security_attributes.bInheritHandle = true; + ScopedFileHandle inherited_file(CreateFile(L"CONOUT$", + GENERIC_WRITE, + 0, + &security_attributes, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr)); + ASSERT_TRUE(inherited_file.is_valid()); + + HKEY key; + ASSERT_EQ(ERROR_SUCCESS, + RegOpenKeyEx( + HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft", 0, KEY_READ, &key)); + ScopedRegistryKey scoped_key(key); + ASSERT_TRUE(scoped_key.is_valid()); + + std::wstring mapping_name = + base::UTF8ToUTF16(base::StringPrintf("Global\\test_mapping_%d_%I64x", + GetCurrentProcessId(), + base::RandUint64())); + ScopedKernelHANDLE mapping(CreateFileMapping(INVALID_HANDLE_VALUE, + nullptr, + PAGE_READWRITE, + 0, + 1024, + mapping_name.c_str())); + ASSERT_TRUE(mapping.is_valid()); + + ProcessInfo info; + info.Initialize(GetCurrentProcess()); + bool found_file_handle = false; + bool found_inherited_file_handle = false; + bool found_key_handle = false; + bool found_mapping_handle = false; + for (auto handle : info.Handles()) { + if (reinterpret_cast(file.get()) == handle.handle) { + EXPECT_FALSE(found_file_handle); + found_file_handle = true; + EXPECT_EQ(L"File", handle.type_name); + EXPECT_EQ(1, handle.handle_count); + EXPECT_NE(0u, handle.pointer_count); + EXPECT_EQ(STANDARD_RIGHTS_READ | STANDARD_RIGHTS_WRITE | SYNCHRONIZE, + handle.granted_access & STANDARD_RIGHTS_ALL); + EXPECT_EQ(0, handle.attributes); + } + if (reinterpret_cast(inherited_file.get()) == handle.handle) { + EXPECT_FALSE(found_inherited_file_handle); + found_inherited_file_handle = true; + EXPECT_EQ(L"File", handle.type_name); + EXPECT_EQ(1, handle.handle_count); + EXPECT_NE(0u, handle.pointer_count); + EXPECT_EQ(STANDARD_RIGHTS_READ | STANDARD_RIGHTS_WRITE | SYNCHRONIZE, + handle.granted_access & STANDARD_RIGHTS_ALL); + + // OBJ_INHERIT from ntdef.h, but including that conflicts with other + // headers. + const int kObjInherit = 0x2; + EXPECT_EQ(kObjInherit, handle.attributes); + } + if (reinterpret_cast(scoped_key.get()) == handle.handle) { + EXPECT_FALSE(found_key_handle); + found_key_handle = true; + EXPECT_EQ(L"Key", handle.type_name); + EXPECT_EQ(1, handle.handle_count); + EXPECT_NE(0u, handle.pointer_count); + EXPECT_EQ(STANDARD_RIGHTS_READ, + handle.granted_access & STANDARD_RIGHTS_ALL); + EXPECT_EQ(0, handle.attributes); + } + if (reinterpret_cast(mapping.get()) == handle.handle) { + EXPECT_FALSE(found_mapping_handle); + found_mapping_handle = true; + EXPECT_EQ(L"Section", handle.type_name); + EXPECT_EQ(1, handle.handle_count); + EXPECT_NE(0u, handle.pointer_count); + EXPECT_EQ(DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER | + STANDARD_RIGHTS_READ | STANDARD_RIGHTS_WRITE, + handle.granted_access & STANDARD_RIGHTS_ALL); + EXPECT_EQ(0, handle.attributes); + } + } + EXPECT_TRUE(found_file_handle); + EXPECT_TRUE(found_key_handle); + EXPECT_TRUE(found_mapping_handle); +} + } // namespace } // namespace test } // namespace crashpad diff --git a/util/win/process_structs.h b/util/win/process_structs.h index 143431f0..5850ddec 100644 --- a/util/win/process_structs.h +++ b/util/win/process_structs.h @@ -484,6 +484,23 @@ struct RTL_CRITICAL_SECTION_DEBUG { WORD SpareWORD; }; +struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX { + void* Object; + ULONG_PTR UniqueProcessId; + HANDLE HandleValue; + ULONG GrantedAccess; + USHORT CreatorBackTraceIndex; + USHORT ObjectTypeIndex; + ULONG HandleAttributes; + ULONG Reserved; +}; + +struct SYSTEM_HANDLE_INFORMATION_EX { + ULONG_PTR NumberOfHandles; + ULONG_PTR Reserved; + SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles[1]; +}; + #pragma pack(pop) //! \}