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 .
This commit is contained in:
Scott Graham 2015-10-16 15:31:32 -07:00
parent d1e49bd221
commit 7de04b02f8
6 changed files with 364 additions and 0 deletions

View File

@ -72,6 +72,22 @@ NTSTATUS NtOpenThread(PHANDLE thread_handle,
static_cast<const void*>(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<decltype(::NtQueryObject)*>(
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<process_types::internal::Traits32>(

View File

@ -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<Traits>* client_id);
NTSTATUS NtQueryObject(HANDLE handle,
OBJECT_INFORMATION_CLASS object_information_class,
void* object_information,
ULONG object_information_length,
ULONG* return_length);
} // namespace crashpad

View File

@ -20,12 +20,15 @@
#include <limits>
#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<uint8_t[]> QueryObject(
HANDLE handle,
OBJECT_INFORMATION_CLASS object_information_class,
ULONG minimum_size) {
ULONG size = minimum_size;
ULONG return_length;
scoped_ptr<uint8_t[]> 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<uint8_t[]>();
}
DCHECK_GE(return_length, minimum_size);
return buffer.Pass();
}
} // namespace
template <class Traits>
@ -314,20 +345,150 @@ bool ReadMemoryInfo(HANDLE process, bool is_64_bit, ProcessInfo* process_info) {
return true;
}
std::vector<ProcessInfo::Handle> ProcessInfo::BuildHandleVector(
HANDLE process) const {
ULONG buffer_size = 2 * 1024 * 1024;
scoped_ptr<uint8_t[]> 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<SYSTEM_INFORMATION_CLASS>(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<Handle>();
}
const auto& system_handle_information_ex =
*reinterpret_cast<process_types::SYSTEM_HANDLE_INFORMATION_EX*>(
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<Handle> 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<uint32_t>(reinterpret_cast<uintptr_t>(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>(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<uint8_t[]> 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<PUBLIC_OBJECT_BASIC_INFORMATION*>(
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<uint8_t[]> 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<PUBLIC_OBJECT_TYPE_INFORMATION*>(
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::Handle>& ProcessInfo::Handles() {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
if (handles_.empty())
handles_ = BuildHandleVector(process_);
return handles_;
}
std::vector<CheckedRange<WinVMAddress, WinVMSize>> GetReadableRangesOfMemoryMap(
const CheckedRange<WinVMAddress, WinVMSize>& range,
const std::vector<MEMORY_BASIC_INFORMATION64>& memory_info) {

View File

@ -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<CheckedRange<WinVMAddress, WinVMSize>> GetReadableRanges(
const CheckedRange<WinVMAddress, WinVMSize>& range) const;
//! \brief Retrieves information about open handles in the target process.
const std::vector<Handle>& Handles();
private:
template <class Traits>
friend bool GetProcessBasicInformation(HANDLE process,
@ -122,13 +158,17 @@ class ProcessInfo {
bool is_64_bit,
ProcessInfo* process_info);
std::vector<Handle> 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<Module> modules_;
std::vector<MEMORY_BASIC_INFORMATION64> memory_info_;
std::vector<Handle> handles_;
bool is_64_bit_;
bool is_wow64_;
InitializationStateDcheck initialized_;

View File

@ -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<HKEY, ScopedRegistryKeyCloseTraits>;
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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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

View File

@ -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)
//! \}