mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-09 14:06:33 +00:00
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:
parent
d1e49bd221
commit
7de04b02f8
@ -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>(
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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_;
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
//! \}
|
||||
|
Loading…
x
Reference in New Issue
Block a user