From 5be8ce4ea0fe2345ce6c843c53432e07a22cca41 Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Tue, 1 Dec 2015 17:06:37 -0500 Subject: [PATCH] Get module versions and types from in-memory images Don't call GetFileVersionInfo(), which calls LoadLibrary() to be able to access the module's resources. Loading modules from the crashy process into the handler process can cause trouble. The Crashpad handler definitely doesn't want to run arbitrary modules' module initializer code. Since the VS_FIXEDFILEINFO needed is already in memory in the remote process' address space, just access it from there. BUG=crashpad:78 R=scottmg@chromium.org Review URL: https://codereview.chromium.org/1475023004 . --- snapshot/snapshot.gyp | 4 + snapshot/win/module_snapshot_win.cc | 3 +- snapshot/win/pe_image_reader.cc | 259 +++++++++++++-------- snapshot/win/pe_image_reader.h | 73 +++--- snapshot/win/pe_image_reader_test.cc | 65 +++++- snapshot/win/pe_image_resource_reader.cc | 279 +++++++++++++++++++++++ snapshot/win/pe_image_resource_reader.h | 179 +++++++++++++++ snapshot/win/process_subrange_reader.cc | 104 +++++++++ snapshot/win/process_subrange_reader.h | 114 +++++++++ snapshot/win/system_snapshot_win.cc | 50 ++-- util/numeric/checked_address_range.cc | 8 + util/numeric/checked_address_range.h | 8 + util/win/module_version.h | 10 +- 13 files changed, 1002 insertions(+), 154 deletions(-) create mode 100644 snapshot/win/pe_image_resource_reader.cc create mode 100644 snapshot/win/pe_image_resource_reader.h create mode 100644 snapshot/win/process_subrange_reader.cc create mode 100644 snapshot/win/process_subrange_reader.h diff --git a/snapshot/snapshot.gyp b/snapshot/snapshot.gyp index 0a78e90c..3f7c63ee 100644 --- a/snapshot/snapshot.gyp +++ b/snapshot/snapshot.gyp @@ -103,10 +103,14 @@ 'win/pe_image_annotations_reader.h', 'win/pe_image_reader.cc', 'win/pe_image_reader.h', + 'win/pe_image_resource_reader.cc', + 'win/pe_image_resource_reader.h', 'win/process_reader_win.cc', 'win/process_reader_win.h', 'win/process_snapshot_win.cc', 'win/process_snapshot_win.h', + 'win/process_subrange_reader.cc', + 'win/process_subrange_reader.h', 'win/system_snapshot_win.cc', 'win/system_snapshot_win.h', 'win/thread_snapshot_win.cc', diff --git a/snapshot/win/module_snapshot_win.cc b/snapshot/win/module_snapshot_win.cc index 49478880..0553f35b 100644 --- a/snapshot/win/module_snapshot_win.cc +++ b/snapshot/win/module_snapshot_win.cc @@ -19,7 +19,6 @@ #include "snapshot/win/pe_image_reader.h" #include "util/misc/tri_state.h" #include "util/misc/uuid.h" -#include "util/win/module_version.h" namespace crashpad { namespace internal { @@ -210,7 +209,7 @@ const VS_FIXEDFILEINFO* ModuleSnapshotWin::VSFixedFileInfo() const { if (initialized_vs_fixed_file_info_.is_uninitialized()) { initialized_vs_fixed_file_info_.set_invalid(); - if (GetModuleVersionAndType(base::FilePath(name_), &vs_fixed_file_info_)) { + if (pe_image_reader_->VSFixedFileInfo(&vs_fixed_file_info_)) { initialized_vs_fixed_file_info_.set_valid(); } } diff --git a/snapshot/win/pe_image_reader.cc b/snapshot/win/pe_image_reader.cc index f408fb18..9abf0b13 100644 --- a/snapshot/win/pe_image_reader.cc +++ b/snapshot/win/pe_image_reader.cc @@ -18,9 +18,8 @@ #include "base/logging.h" #include "base/memory/scoped_ptr.h" -#include "base/strings/stringprintf.h" #include "client/crashpad_info.h" -#include "snapshot/win/process_reader_win.h" +#include "snapshot/win/pe_image_resource_reader.h" #include "util/misc/pdb_structures.h" #include "util/win/process_structs.h" @@ -28,13 +27,6 @@ namespace crashpad { namespace { -std::string RangeToString(const CheckedWinAddressRange& range) { - return base::StringPrintf("[0x%llx + 0x%llx (%s)]", - range.Base(), - range.Size(), - range.Is64Bit() ? "64" : "32"); -} - // Map from Traits to an IMAGE_NT_HEADERSxx. template struct NtHeadersForTraits; @@ -52,9 +44,7 @@ struct NtHeadersForTraits { } // namespace PEImageReader::PEImageReader() - : process_reader_(nullptr), - module_range_(), - module_name_(), + : module_subrange_reader_(), initialized_() { } @@ -67,14 +57,10 @@ bool PEImageReader::Initialize(ProcessReaderWin* process_reader, const std::string& module_name) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); - process_reader_ = process_reader; - module_range_.SetRange(process_reader_->Is64Bit(), address, size); - if (!module_range_.IsValid()) { - LOG(WARNING) << "invalid module range for " << module_name << ": " - << RangeToString(module_range_); + if (!module_subrange_reader_.Initialize( + process_reader, address, size, module_name)) { return false; } - module_name_ = module_name; INITIALIZATION_STATE_SET_VALID(initialized_); return true; @@ -93,36 +79,34 @@ bool PEImageReader::GetCrashpadInfo( if (section.Misc.VirtualSize < sizeof(process_types::CrashpadInfo)) { LOG(WARNING) << "small crashpad info section size " - << section.Misc.VirtualSize << ", " << module_name_; + << section.Misc.VirtualSize << ", " + << module_subrange_reader_.name(); return false; } - WinVMAddress crashpad_info_address = Address() + section.VirtualAddress; - CheckedWinAddressRange crashpad_info_range(process_reader_->Is64Bit(), - crashpad_info_address, - section.Misc.VirtualSize); - if (!crashpad_info_range.IsValid()) { - LOG(WARNING) << "invalid range for crashpad info: " - << RangeToString(crashpad_info_range); + ProcessSubrangeReader crashpad_info_subrange_reader; + const WinVMAddress crashpad_info_address = Address() + section.VirtualAddress; + if (!crashpad_info_subrange_reader.InitializeSubrange( + module_subrange_reader_, + crashpad_info_address, + section.Misc.VirtualSize, + "crashpad_info")) { return false; } - if (!module_range_.ContainsRange(crashpad_info_range)) { - LOG(WARNING) << "crashpad info does not fall inside module " - << module_name_; - return false; - } - - if (!process_reader_->ReadMemory(crashpad_info_address, - sizeof(process_types::CrashpadInfo), - crashpad_info)) { - LOG(WARNING) << "could not read crashpad info " << module_name_; + if (!crashpad_info_subrange_reader.ReadMemory( + crashpad_info_address, + sizeof(process_types::CrashpadInfo), + crashpad_info)) { + LOG(WARNING) << "could not read crashpad info from " + << module_subrange_reader_.name(); return false; } if (crashpad_info->signature != CrashpadInfo::kSignature || crashpad_info->version < 1) { - LOG(WARNING) << "unexpected crashpad info data " << module_name_; + LOG(WARNING) << "unexpected crashpad info data in " + << module_subrange_reader_.name(); return false; } @@ -132,46 +116,23 @@ bool PEImageReader::GetCrashpadInfo( bool PEImageReader::DebugDirectoryInformation(UUID* uuid, DWORD* age, std::string* pdbname) const { - if (process_reader_->Is64Bit()) { - return ReadDebugDirectoryInformation( - uuid, age, pdbname); - } else { - return ReadDebugDirectoryInformation( - uuid, age, pdbname); - } -} + INITIALIZATION_STATE_DCHECK_VALID(initialized_); -template -bool PEImageReader::ReadDebugDirectoryInformation(UUID* uuid, - DWORD* age, - std::string* pdbname) const { - NtHeadersType nt_headers; - if (!ReadNtHeaders(&nt_headers, nullptr)) + IMAGE_DATA_DIRECTORY data_directory; + if (!ImageDataDirectoryEntry(IMAGE_DIRECTORY_ENTRY_DEBUG, &data_directory)) return false; - if (nt_headers.FileHeader.SizeOfOptionalHeader < - offsetof(decltype(nt_headers.OptionalHeader), - DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG]) + - sizeof(nt_headers.OptionalHeader - .DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG]) || - nt_headers.OptionalHeader.NumberOfRvaAndSizes <= - IMAGE_DIRECTORY_ENTRY_DEBUG) { - return false; - } - - const IMAGE_DATA_DIRECTORY& data_directory = - nt_headers.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG]; - if (data_directory.VirtualAddress == 0 || data_directory.Size == 0) - return false; IMAGE_DEBUG_DIRECTORY debug_directory; if (data_directory.Size % sizeof(debug_directory) != 0) return false; for (size_t offset = 0; offset < data_directory.Size; offset += sizeof(debug_directory)) { - if (!CheckedReadMemory(Address() + data_directory.VirtualAddress + offset, - sizeof(debug_directory), - &debug_directory)) { - LOG(WARNING) << "could not read data directory"; + if (!module_subrange_reader_.ReadMemory( + Address() + data_directory.VirtualAddress + offset, + sizeof(debug_directory), + &debug_directory)) { + LOG(WARNING) << "could not read data directory from " + << module_subrange_reader_.name(); return false; } @@ -180,21 +141,25 @@ bool PEImageReader::ReadDebugDirectoryInformation(UUID* uuid, if (debug_directory.AddressOfRawData) { if (debug_directory.SizeOfData < sizeof(CodeViewRecordPDB70)) { - LOG(WARNING) << "CodeView debug entry of unexpected size"; + LOG(WARNING) << "CodeView debug entry of unexpected size in " + << module_subrange_reader_.name(); continue; } scoped_ptr data(new char[debug_directory.SizeOfData]); - if (!CheckedReadMemory(Address() + debug_directory.AddressOfRawData, - debug_directory.SizeOfData, - data.get())) { - LOG(WARNING) << "could not read debug directory"; + if (!module_subrange_reader_.ReadMemory( + Address() + debug_directory.AddressOfRawData, + debug_directory.SizeOfData, + data.get())) { + LOG(WARNING) << "could not read debug directory from " + << module_subrange_reader_.name(); return false; } if (*reinterpret_cast(data.get()) != CodeViewRecordPDB70::kSignature) { - LOG(WARNING) << "encountered non-7.0 CodeView debug record"; + LOG(WARNING) << "encountered non-7.0 CodeView debug record in " + << module_subrange_reader_.name(); continue; } @@ -218,29 +183,120 @@ bool PEImageReader::ReadDebugDirectoryInformation(UUID* uuid, return false; } +bool PEImageReader::VSFixedFileInfo( + VS_FIXEDFILEINFO* vs_fixed_file_info) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + IMAGE_DATA_DIRECTORY data_directory; + if (!ImageDataDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE, + &data_directory)) { + return false; + } + + PEImageResourceReader resource_reader; + if (!resource_reader.Initialize(module_subrange_reader_, data_directory)) { + return false; + } + + WinVMAddress address; + WinVMSize size; + if (!resource_reader.FindResourceByID( + reinterpret_cast(VS_FILE_INFO), // RT_VERSION + VS_VERSION_INFO, + MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), + &address, + &size, + nullptr)) { + return false; + } + + // This structure is not declared anywhere in the SDK, but is documented at + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms647001.aspx. + struct VS_VERSIONINFO { + WORD wLength; + WORD wValueLength; + WORD wType; + + // The structure documentation on MSDN doesn’t show the [16], but it does + // say that it’s supposed to be L"VS_VERSION_INFO", which is is in fact a + // 16-character string (including its NUL terminator). + WCHAR szKey[16]; + + WORD Padding1; + VS_FIXEDFILEINFO Value; + + // Don’t include Children or the Padding2 that precedes it, because they may + // not be present. + // WORD Padding2; + // WORD Children; + }; + VS_VERSIONINFO version_info; + + if (size < sizeof(version_info)) { + LOG(WARNING) << "version info size " << size + << " too small for structure of size " << sizeof(version_info) + << " in " << module_subrange_reader_.name(); + return false; + } + + if (!module_subrange_reader_.ReadMemory( + address, sizeof(version_info), &version_info)) { + LOG(WARNING) << "could not read version info from " + << module_subrange_reader_.name(); + return false; + } + + if (version_info.wLength < sizeof(version_info) || + version_info.wValueLength != sizeof(version_info.Value) || + version_info.wType != 0 || + wcsncmp(version_info.szKey, + L"VS_VERSION_INFO", + arraysize(version_info.szKey)) != 0) { + LOG(WARNING) << "unexpected VS_VERSIONINFO in " + << module_subrange_reader_.name(); + return false; + } + + if (version_info.Value.dwSignature != VS_FFI_SIGNATURE || + version_info.Value.dwStrucVersion != VS_FFI_STRUCVERSION) { + LOG(WARNING) << "unexpected VS_FIXEDFILEINFO in " + << module_subrange_reader_.name(); + return false; + } + + *vs_fixed_file_info = version_info.Value; + vs_fixed_file_info->dwFileFlags &= vs_fixed_file_info->dwFileFlagsMask; + return true; +} + template bool PEImageReader::ReadNtHeaders(NtHeadersType* nt_headers, WinVMAddress* nt_headers_address) const { IMAGE_DOS_HEADER dos_header; - if (!CheckedReadMemory(Address(), sizeof(IMAGE_DOS_HEADER), &dos_header)) { - LOG(WARNING) << "could not read dos header of " << module_name_; + if (!module_subrange_reader_.ReadMemory( + Address(), sizeof(IMAGE_DOS_HEADER), &dos_header)) { + LOG(WARNING) << "could not read dos header from " + << module_subrange_reader_.name(); return false; } if (dos_header.e_magic != IMAGE_DOS_SIGNATURE) { - LOG(WARNING) << "invalid e_magic in dos header of " << module_name_; + LOG(WARNING) << "invalid e_magic in dos header of " + << module_subrange_reader_.name(); return false; } WinVMAddress local_nt_headers_address = Address() + dos_header.e_lfanew; - if (!CheckedReadMemory( + if (!module_subrange_reader_.ReadMemory( local_nt_headers_address, sizeof(NtHeadersType), nt_headers)) { - LOG(WARNING) << "could not read nt headers of " << module_name_; + LOG(WARNING) << "could not read nt headers from " + << module_subrange_reader_.name(); return false; } if (nt_headers->Signature != IMAGE_NT_SIGNATURE) { - LOG(WARNING) << "invalid signature in nt headers of " << module_name_; + LOG(WARNING) << "invalid signature in nt headers of " + << module_subrange_reader_.name(); return false; } @@ -269,9 +325,10 @@ bool PEImageReader::GetSectionByName(const std::string& name, for (DWORD i = 0; i < nt_headers.FileHeader.NumberOfSections; ++i) { WinVMAddress section_address = first_section_address + sizeof(IMAGE_SECTION_HEADER) * i; - if (!CheckedReadMemory( + if (!module_subrange_reader_.ReadMemory( section_address, sizeof(IMAGE_SECTION_HEADER), section)) { - LOG(WARNING) << "could not read section " << i << " of " << module_name_; + LOG(WARNING) << "could not read section " << i << " from " + << module_subrange_reader_.name(); return false; } if (strncmp(reinterpret_cast(section->Name), @@ -284,20 +341,36 @@ bool PEImageReader::GetSectionByName(const std::string& name, return false; } -bool PEImageReader::CheckedReadMemory(WinVMAddress address, - WinVMSize size, - void* into) const { - CheckedWinAddressRange read_range(process_reader_->Is64Bit(), address, size); - if (!read_range.IsValid()) { - LOG(WARNING) << "invalid read range: " << RangeToString(read_range); +bool PEImageReader::ImageDataDirectoryEntry(size_t index, + IMAGE_DATA_DIRECTORY* entry) const { + bool rv; + if (module_subrange_reader_.Is64Bit()) { + rv = ImageDataDirectoryEntryT(index, entry); + } else { + rv = ImageDataDirectoryEntryT(index, entry); + } + + return rv && entry->VirtualAddress != 0 && entry->Size != 0; +} + +template +bool PEImageReader::ImageDataDirectoryEntryT( + size_t index, + IMAGE_DATA_DIRECTORY* entry) const { + NtHeadersType nt_headers; + if (!ReadNtHeaders(&nt_headers, nullptr)) { return false; } - if (!module_range_.ContainsRange(read_range)) { - LOG(WARNING) << "attempt to read outside of module " << module_name_ - << " at range: " << RangeToString(read_range); + + if (nt_headers.FileHeader.SizeOfOptionalHeader < + offsetof(decltype(nt_headers.OptionalHeader), DataDirectory[index]) + + sizeof(nt_headers.OptionalHeader.DataDirectory[index]) || + nt_headers.OptionalHeader.NumberOfRvaAndSizes <= index) { return false; } - return process_reader_->ReadMemory(address, size, into); + + *entry = nt_headers.OptionalHeader.DataDirectory[index]; + return true; } // Explicit instantiations with the only 2 valid template arguments to avoid diff --git a/snapshot/win/pe_image_reader.h b/snapshot/win/pe_image_reader.h index f6364d99..87e3a8f4 100644 --- a/snapshot/win/pe_image_reader.h +++ b/snapshot/win/pe_image_reader.h @@ -20,10 +20,10 @@ #include #include "base/basictypes.h" +#include "snapshot/win/process_subrange_reader.h" #include "util/misc/initialization_state_dcheck.h" #include "util/misc/uuid.h" #include "util/win/address_types.h" -#include "util/win/checked_win_address_range.h" #include "util/win/process_structs.h" namespace crashpad { @@ -51,6 +51,7 @@ struct CrashpadInfo { //! bitness of the remote process. //! //! \sa PEImageAnnotationsReader +//! \sa PEImageResourceReader class PEImageReader { public: PEImageReader(); @@ -65,8 +66,8 @@ class PEImageReader { //! \param[in] address The address, in the remote process' address space, //! where the `IMAGE_DOS_HEADER` is located. //! \param[in] size The size of the image. - //! \param[in] name The module's name, a string to be used in logged messages. - //! This string is for diagnostic purposes. + //! \param[in] module_name The module's name, a string to be used in logged + //! messages. This string is for diagnostic purposes. //! //! \return `true` if the image was read successfully, `false` otherwise, with //! an appropriate message logged. @@ -78,12 +79,12 @@ class PEImageReader { //! \brief Returns the image's load address. //! //! This is the value passed as \a address to Initialize(). - WinVMAddress Address() const { return module_range_.Base(); } + WinVMAddress Address() const { return module_subrange_reader_.Base(); } //! \brief Returns the image's size. //! //! This is the value passed as \a size to Initialize(). - WinVMSize Size() const { return module_range_.Size(); } + WinVMSize Size() const { return module_subrange_reader_.Size(); } //! \brief Obtains the module's CrashpadInfo structure. //! @@ -100,25 +101,34 @@ class PEImageReader { //! \param[out] age The age field for the pdb (the number of times it's been //! relinked). //! \param[out] pdbname Name of the pdb file. - //! \return `true` on success, or `false` if the module has no debug directory - //! entry. - bool DebugDirectoryInformation(UUID* uuid, - DWORD* age, - std::string* pdbname) const; - - private: - //! \brief Implementation helper for DebugDirectoryInformation() templated by - //! `IMAGE_NT_HEADERS` type for different bitnesses. //! //! \return `true` on success, with the parameters set appropriately. `false` //! on failure. This method may return `false` without logging anything in //! the case of a module that does not contain relevant debugging //! information but is otherwise properly structured. - template - bool ReadDebugDirectoryInformation(UUID* uuid, - DWORD* age, - std::string* pdbname) const; + bool DebugDirectoryInformation(UUID* uuid, + DWORD* age, + std::string* pdbname) const; + //! \brief Obtains the module’s `VS_FIXEDFILEINFO`, containing its version and + //! type information. + //! + //! The data obtained from this method should be equivalent to what could be + //! obtained by calling GetModuleVersionAndType(). Avoiding that function + //! ensures that the data in the module loaded into the remote process will be + //! used as-is, without the risks associated with loading the module into the + //! reading process. + //! + //! \param[out] vs_fixed_file_info The VS_FIXEDFILEINFO on success. + //! VS_FIXEDFILEINFO::dwFileFlags will have been masked with + //! VS_FIXEDFILEINFO::dwFileFlagsMask already. + //! + //! \return `true` on success. `false` if the module does not contain this + //! information, without logging any messages. `false` on failure, with + //! a message logged. + bool VSFixedFileInfo(VS_FIXEDFILEINFO* vs_fixed_file_info) const; + + private: //! \brief Reads the `IMAGE_NT_HEADERS` from the beginning of the image. //! //! \param[out] nt_headers The contents of the templated NtHeadersType @@ -139,18 +149,25 @@ class PEImageReader { bool GetSectionByName(const std::string& name, IMAGE_SECTION_HEADER* section) const; - //! \brief Reads memory from target process, first checking whether the range - //! requested falls inside module_range_. + //! \brief Finds the `IMAGE_DATA_DIRECTORY` in + //! `IMAGE_OPTIONAL_HEADER::DataDirectory` at the specified \a index. //! - //! \return `true` on success, with \a into filled out, otherwise `false` and - //! a message will be logged. - bool CheckedReadMemory(WinVMAddress address, - WinVMSize size, - void* into) const; + //! \param[in] index An `IMAGE_DIRECTORY_ENTRY_*` constant specifying the + //! data to be returned. + //! \param[out] entry The `IMAGE_DATA_DIRECTORY` found within the module. + //! + //! \return `true` on success, with \a entry set appropriately. `false` if the + //! module does not contain the specified information, without logging a + //! message. `false` on failure, with a message logged. + bool ImageDataDirectoryEntry(size_t index, IMAGE_DATA_DIRECTORY* entry) const; - ProcessReaderWin* process_reader_; // weak - CheckedWinAddressRange module_range_; - std::string module_name_; + //! \brief A templatized helper for ImageDataDirectoryEntry() to account for + //! differences in \a NtHeadersType. + template + bool ImageDataDirectoryEntryT(size_t index, + IMAGE_DATA_DIRECTORY* entry) const; + + ProcessSubrangeReader module_subrange_reader_; InitializationStateDcheck initialized_; DISALLOW_COPY_AND_ASSIGN(PEImageReader); diff --git a/snapshot/win/pe_image_reader_test.cc b/snapshot/win/pe_image_reader_test.cc index 3928fee9..47ba2115 100644 --- a/snapshot/win/pe_image_reader_test.cc +++ b/snapshot/win/pe_image_reader_test.cc @@ -17,9 +17,13 @@ #define PSAPI_VERSION 1 #include +#include "base/files/file_path.h" +#include "base/strings/utf_string_conversions.h" #include "gtest/gtest.h" #include "snapshot/win/process_reader_win.h" +#include "test/errors.h" #include "util/win/get_function.h" +#include "util/win/module_version.h" extern "C" IMAGE_DOS_HEADER __ImageBase; @@ -28,9 +32,9 @@ namespace test { namespace { BOOL CrashpadGetModuleInformation(HANDLE process, - HMODULE module, - MODULEINFO* module_info, - DWORD cb) { + HMODULE module, + MODULEINFO* module_info, + DWORD cb) { static const auto get_module_information = GET_FUNCTION_REQUIRED(L"psapi.dll", ::GetModuleInformation); return get_module_information(process, module, module_info, cb); @@ -44,9 +48,10 @@ TEST(PEImageReader, DebugDirectory) { HMODULE self = reinterpret_cast(&__ImageBase); MODULEINFO module_info; ASSERT_TRUE(CrashpadGetModuleInformation( - GetCurrentProcess(), self, &module_info, sizeof(module_info))); + GetCurrentProcess(), self, &module_info, sizeof(module_info))) + << ErrorMessage("GetModuleInformation"); EXPECT_EQ(self, module_info.lpBaseOfDll); - EXPECT_TRUE(pe_image_reader.Initialize(&process_reader, + ASSERT_TRUE(pe_image_reader.Initialize(&process_reader, reinterpret_cast(self), module_info.SizeOfImage, "self")); @@ -61,6 +66,56 @@ TEST(PEImageReader, DebugDirectory) { pdbname.compare(pdbname.size() - suffix.size(), suffix.size(), suffix)); } +TEST(PEImageReader, VSFixedFileInfo) { + ProcessReaderWin process_reader; + ASSERT_TRUE(process_reader.Initialize(GetCurrentProcess(), + ProcessSuspensionState::kRunning)); + + const wchar_t kModuleName[] = L"kernel32.dll"; + + HMODULE module_handle = GetModuleHandle(kModuleName); + ASSERT_TRUE(module_handle) << ErrorMessage("GetModuleHandle"); + + MODULEINFO module_info; + ASSERT_TRUE(CrashpadGetModuleInformation( + GetCurrentProcess(), module_handle, &module_info, sizeof(module_info))) + << ErrorMessage("GetModuleInformation"); + EXPECT_EQ(module_handle, module_info.lpBaseOfDll); + + PEImageReader pe_image_reader; + ASSERT_TRUE( + pe_image_reader.Initialize(&process_reader, + reinterpret_cast(module_handle), + module_info.SizeOfImage, + base::UTF16ToUTF8(kModuleName))); + + VS_FIXEDFILEINFO observed; + ASSERT_TRUE(pe_image_reader.VSFixedFileInfo(&observed)); + + EXPECT_EQ(VS_FFI_SIGNATURE, observed.dwSignature); + EXPECT_EQ(VS_FFI_STRUCVERSION, observed.dwStrucVersion); + EXPECT_EQ(0, observed.dwFileFlags & ~observed.dwFileFlagsMask); + EXPECT_EQ(VOS_NT_WINDOWS32, observed.dwFileOS); + EXPECT_EQ(VFT_DLL, observed.dwFileType); + + VS_FIXEDFILEINFO expected; + ASSERT_TRUE(GetModuleVersionAndType(base::FilePath(kModuleName), &expected)); + + EXPECT_EQ(expected.dwSignature, observed.dwSignature); + EXPECT_EQ(expected.dwStrucVersion, observed.dwStrucVersion); + EXPECT_EQ(expected.dwFileVersionMS, observed.dwFileVersionMS); + EXPECT_EQ(expected.dwFileVersionLS, observed.dwFileVersionLS); + EXPECT_EQ(expected.dwProductVersionMS, observed.dwProductVersionMS); + EXPECT_EQ(expected.dwProductVersionLS, observed.dwProductVersionLS); + EXPECT_EQ(expected.dwFileFlagsMask, observed.dwFileFlagsMask); + EXPECT_EQ(expected.dwFileFlags, observed.dwFileFlags); + EXPECT_EQ(expected.dwFileOS, observed.dwFileOS); + EXPECT_EQ(expected.dwFileType, observed.dwFileType); + EXPECT_EQ(expected.dwFileSubtype, observed.dwFileSubtype); + EXPECT_EQ(expected.dwFileDateMS, observed.dwFileDateMS); + EXPECT_EQ(expected.dwFileDateLS, observed.dwFileDateLS); +} + } // namespace } // namespace test } // namespace crashpad diff --git a/snapshot/win/pe_image_resource_reader.cc b/snapshot/win/pe_image_resource_reader.cc new file mode 100644 index 00000000..3a754a97 --- /dev/null +++ b/snapshot/win/pe_image_resource_reader.cc @@ -0,0 +1,279 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "snapshot/win/pe_image_resource_reader.h" + +#include + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" + +namespace { + +void AddLanguageAndNeutralSublanguage(std::vector* languages, + uint16_t language) { + languages->push_back(language); + if (SUBLANGID(language) != SUBLANG_NEUTRAL) { + languages->push_back(MAKELANGID(PRIMARYLANGID(language), SUBLANG_NEUTRAL)); + } +} + +} // namespace + +namespace crashpad { + +PEImageResourceReader::PEImageResourceReader() + : resources_subrange_reader_(), + module_base_(0), + initialized_() { +} + +PEImageResourceReader::~PEImageResourceReader() {} + +bool PEImageResourceReader::Initialize( + const ProcessSubrangeReader& module_subrange_reader, + const IMAGE_DATA_DIRECTORY& resources_directory_entry) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + module_base_ = module_subrange_reader.Base(); + + if (!resources_subrange_reader_.InitializeSubrange( + module_subrange_reader, + module_base_ + resources_directory_entry.VirtualAddress, + resources_directory_entry.Size, + "resources")) { + return false; + } + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +bool PEImageResourceReader::FindResourceByID(uint16_t type, + uint16_t name, + uint16_t language, + WinVMAddress* address, + WinVMSize* size, + uint32_t* code_page) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + // The root resource directory is at the beginning of the resources area + // within the module. + const uint32_t name_directory_offset = + GetEntryFromResourceDirectoryByID(0, type, true); + if (!name_directory_offset) { + return false; + } + + const uint32_t language_directory_offset = + GetEntryFromResourceDirectoryByID(name_directory_offset, name, true); + if (!language_directory_offset) { + return false; + } + + // The definition of IMAGE_RESOURCE_DIRECTORY_ENTRY in has a comment + // saying that its offsets are relative to “the resource directory of the data + // associated with this directory entry”. That could be interpreted to mean + // that language_directory_offset is relative to name_directory_offset, since + // the language directory entry is found within the name directory. This is + // not correct. All resource offsets are relative to the resources area within + // the module. + const uint32_t data_offset = GetEntryFromResourceDirectoryByLanguage( + language_directory_offset, language); + if (!data_offset) { + return false; + } + + IMAGE_RESOURCE_DATA_ENTRY data_entry; + if (!resources_subrange_reader_.ReadMemory( + resources_subrange_reader_.Base() + data_offset, + sizeof(data_entry), + &data_entry)) { + LOG(WARNING) << "could not read resource data entry from " + << resources_subrange_reader_.name(); + return false; + } + + // The definition of IMAGE_RESOURCE_DATA_ENTRY in has a comment + // saying that OffsetToData is relative to the beginning of the resource data. + // This is not correct. It’s module-relative. + *address = module_base_ + data_entry.OffsetToData; + *size = data_entry.Size; + if (code_page) { + *code_page = data_entry.CodePage; + } + + return true; +} + +uint32_t PEImageResourceReader::GetEntryFromResourceDirectoryByID( + uint32_t language_directory_offset, + uint16_t id, + bool want_subdirectory) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + std::vector entries_by_id; + if (!ReadResourceDirectory( + language_directory_offset, nullptr, nullptr, &entries_by_id)) { + return 0; + } + + const auto entry_it = + std::find_if(entries_by_id.begin(), + entries_by_id.end(), + [id](const IMAGE_RESOURCE_DIRECTORY_ENTRY& entry) { + return !entry.NameIsString && entry.Id == id; + }); + if (entry_it != entries_by_id.end()) { + if ((entry_it->DataIsDirectory != 0) != want_subdirectory) { + LOG(WARNING) << "expected " << (want_subdirectory ? "" : "non-") + << "directory for entry id " << id << " in " + << resources_subrange_reader_.name(); + return 0; + } + + return entry_it->DataIsDirectory ? entry_it->OffsetToDirectory + : entry_it->OffsetToData; + } + + return 0; +} + +uint32_t PEImageResourceReader::GetEntryFromResourceDirectoryByLanguage( + uint32_t resource_directory_offset, + uint16_t language) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + std::vector entries_by_language; + if (!ReadResourceDirectory( + resource_directory_offset, nullptr, nullptr, &entries_by_language)) { + return 0; + } + + if (entries_by_language.empty()) { + return 0; + } + + // https://msdn.microsoft.com/en-us/library/cc194810.aspx + // + // TODO(mark): It seems like FindResourceEx() might do something more complex. + // It would be best to mimic its behavior. + std::vector try_languages; + if (PRIMARYLANGID(language) != LANG_NEUTRAL) { + AddLanguageAndNeutralSublanguage(&try_languages, language); + } else { + if (SUBLANGID(language) != SUBLANG_SYS_DEFAULT) { + AddLanguageAndNeutralSublanguage(&try_languages, + LANGIDFROMLCID(GetThreadLocale())); + AddLanguageAndNeutralSublanguage(&try_languages, + LANGIDFROMLCID(GetUserDefaultLCID())); + } + if (SUBLANGID(language) != SUBLANG_DEFAULT) { + AddLanguageAndNeutralSublanguage(&try_languages, + LANGIDFROMLCID(GetSystemDefaultLCID())); + } + } + + try_languages.push_back(MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)); + try_languages.push_back(MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT)); + + for (const auto try_language : try_languages) { + const auto entry_it = std::find_if( + entries_by_language.begin(), + entries_by_language.end(), + [try_language](const IMAGE_RESOURCE_DIRECTORY_ENTRY& entry) { + return !entry.NameIsString && entry.Id == try_language; + }); + if (entry_it != entries_by_language.end()) { + if (entry_it->DataIsDirectory) { + LOG(WARNING) << "expected non-directory for entry language " + << try_language << " in " + << resources_subrange_reader_.name(); + return 0; + } + + return entry_it->OffsetToData; + } + } + + // Fall back to the first entry in the list. + const auto& entry = entries_by_language.front(); + if (entry.DataIsDirectory) { + LOG(WARNING) << "expected non-directory for entry in " + << resources_subrange_reader_.name(); + return 0; + } + + return entry.OffsetToData; +} + +bool PEImageResourceReader::ReadResourceDirectory( + uint32_t resource_directory_offset, + IMAGE_RESOURCE_DIRECTORY* resource_directory, + std::vector* named_entries, + std::vector* id_entries) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + // resource_directory is optional, but it’s still needed locally even if the + // caller isn’t interested in it. + scoped_ptr local_resource_directory; + if (!resource_directory) { + local_resource_directory.reset(new IMAGE_RESOURCE_DIRECTORY); + resource_directory = local_resource_directory.get(); + } + + const WinVMAddress address = + resources_subrange_reader_.Base() + resource_directory_offset; + + if (!resources_subrange_reader_.ReadMemory( + address, sizeof(*resource_directory), resource_directory)) { + LOG(WARNING) << "could not read resource directory from " + << resources_subrange_reader_.name(); + return false; + } + + if (named_entries) { + named_entries->clear(); + named_entries->resize(resource_directory->NumberOfNamedEntries); + if (!named_entries->empty() && + !resources_subrange_reader_.ReadMemory( + address + sizeof(*resource_directory), + named_entries->size() * sizeof((*named_entries)[0]), + &(*named_entries)[0])) { + LOG(WARNING) << "could not read resource directory named entries from " + << resources_subrange_reader_.name(); + return false; + } + } + + if (id_entries) { + id_entries->clear(); + id_entries->resize(resource_directory->NumberOfIdEntries); + if (!id_entries->empty() && + !resources_subrange_reader_.ReadMemory( + address + sizeof(*resource_directory) + + resource_directory->NumberOfNamedEntries * + sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY), + id_entries->size() * sizeof((*id_entries)[0]), + &(*id_entries)[0])) { + LOG(WARNING) << "could not read resource directory ID entries from " + << resources_subrange_reader_.name(); + return false; + } + } + + return true; +} + +} // namespace crashpad diff --git a/snapshot/win/pe_image_resource_reader.h b/snapshot/win/pe_image_resource_reader.h new file mode 100644 index 00000000..59a6d954 --- /dev/null +++ b/snapshot/win/pe_image_resource_reader.h @@ -0,0 +1,179 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_SNAPSHOT_WIN_PE_IMAGE_RESOURCE_READER_H_ +#define CRASHPAD_SNAPSHOT_WIN_PE_IMAGE_RESOURCE_READER_H_ + +#include +#include + +#include + +#include "base/basictypes.h" +#include "snapshot/win/process_subrange_reader.h" +#include "util/misc/initialization_state_dcheck.h" +#include "util/win/address_types.h" + +namespace crashpad { + +//! \brief A reader for resources stored in PE images mapped into another +//! process. +//! +//! \sa PEImageReader +class PEImageResourceReader { + public: + PEImageResourceReader(); + ~PEImageResourceReader(); + + //! \brief Initializes the resource reader. + //! + //! \param[in] module_subrange_reader The reader for the module. + //! \param[in] resources_directory_entry The module’s `IMAGE_DATA_DIRECTORY` + //! for its resources area. This is taken from the module’s + //! `IMAGE_OPTIONAL_HEADER::DataDirectory` at index + //! `IMAGE_DIRECTORY_ENTRY_RESOURCE`. + //! + //! \return `true` on success, `false` on failure with a message logged. + bool Initialize(const ProcessSubrangeReader& module_subrange_reader, + const IMAGE_DATA_DIRECTORY& resources_directory_entry); + + //! \brief Locates a resource in a module by its ID. + //! + //! This method is similar to `FindResourceEx()`, but it operates on modules + //! loaded in a remote process’ address space. It is not necessary to + //! `LoadLibrary()` a module into a process in order to use this method. + //! + //! No support is provided at present for locating resources by \a type or \a + //! name using strings as opposed to integer identifiers. + //! + //! Languages are scanned in the order determined by + //! GetEntryFromResourceDirectoryByLanguage(). + //! + //! \param[in] type The integer identifier of the resource type, as in the + //! `lpType` parameter of `FindResourceEx()`. + //! \param[in] name The integer identifier of the resource, as in the `lpName` + //! parameter of `FindResourceEx()`. + //! \param[in] language The language of the resource, as in the `wLanguage` + //! parameter of `FindResourceEx()`. + //! \param[out] address The address, in the remote process’ address space, of + //! the resource data. + //! \param[out] size The size of the resource data. + //! \param[out] code_page The code page used to encode textual resource data. + //! This parameter is optional. + //! + //! \return `true` on success, with the out parameters set appropriately. + //! `false` if the resource was not found, without logging any messages. + //! `false` on failure, with a message logged. + bool FindResourceByID(uint16_t type, + uint16_t name, + uint16_t language, + WinVMAddress* address, + WinVMSize* size, + uint32_t* code_page) const; + + private: + //! \brief Locates a resource directory entry within a resource directory by + //! integer ID. + //! + //! \param[in] resource_directory_offset The offset, in the module’s resources + //! area, of the resource directory to search. + //! \param[in] id The integer identifier of the resource to search for. + //! \param[in] want_subdirectory `true` if the resource directory entry is + //! expected to be a resource directory itself, `false` otherwise. + //! + //! \return The offset, in the module’s resources area, of the entry that was + //! found. On failure, `0`. `0` is technically a valid offset, but it + //! corresponds to the root resource directory, which should never be the + //! offset of another resource directory entry. If \a id was not found, + //! `0` will be returned without logging anything. For other failures, a + //! message will be logged. + uint32_t GetEntryFromResourceDirectoryByID(uint32_t resource_directory_offset, + uint16_t id, + bool want_subdirectory) const; + + //! \brief Locates a resource directory entry within a resource directory by + //! language. + //! + //! This method is similar to GetEntryFromResourceDirectoryByID() with \a + //! want_subdirectory set to `false`. Attempts are made to locate the resource + //! by using these languages: + //!
    + //!
  • If \a language is `LANG_NEUTRAL`:
  • + //!
      + //!
    • Unless `SUBLANG_SYS_DEFAULT` is specified, the language of the + //! thread’s locale, with its normal sublanguage and with + //! `SUBLANG_NEUTRAL`.
    • + //!
    • Unless `SUBLANG_SYS_DEFAULT` is specified, the language of the + //! user’s default locale, with its normal sublanguage and with + //! `SUBLANG_NEUTRAL`.
    • + //!
    • Unless `SUBLANG_DEFAULT` is specified, the language of the + //! system’s default locale, with its normal sublanguage and with + //! `SUBLANG_NEUTRAL`.
    • + //!
    + //!
  • If \a language is not `LANG_NEUTRAL`:
  • + //!
      + //!
    • \a language
    • + //!
    • \a language, with `SUBLANG_NEUTRAL`
    • + //!
    + //!
  • `LANG_NEUTRAL` with `SUBLANG_NEUTRAL`
  • + //!
  • `LANG_ENGLISH` with `SUBLANG_DEFAULT`
  • + //!
  • If none of the above match, the first language found
  • + //!
+ //! + //! If only a specific language is desired without any fallbacks, call + //! GetEntryFromResourceDirectoryByID() with the language directory’s offset + //! instead, passing the desired language in the \a id parameter, and `false` + //! for \a want_subdirectory. + //! + //! \param[in] language_directory_offset The offset, in the module’s resources + //! area, of the resource directory to search. + //! \param[in] language The language of the resource to search for. + //! + //! \return The return value is as in GetEntryFromResourceDirectoryByID(). + uint32_t GetEntryFromResourceDirectoryByLanguage( + uint32_t language_directory_offset, + uint16_t language) const; + + //! \brief Reads a resource directory. + //! + //! \param[in] resource_directory_offset The offset, in the module’s resources + //! area, of the resource directory to read. + //! \param[out] resource_directory The `IMAGE_RESOURCE_DIRECTORY` structure. + //! This parameter is optional. + //! \param[out] named_entries A vector of \a + //! resource_directory->NumberOfNamedEntries + //! `IMAGE_RESOURCE_DIRECTORY_ENTRY` items that follow the resource + //! directory. This parameter is optional. + //! \param[out] id_entries A vector of \a + //! resource_directory->NumberOfIdEntries `IMAGE_RESOURCE_DIRECTORY_ENTRY` + //! items that follow the named entries. This parameter is optional. + //! + //! \return `true` on success, with the out parameters set appropriately. + //! `false` on failure with a message logged. + bool ReadResourceDirectory( + uint32_t resource_directory_offset, + IMAGE_RESOURCE_DIRECTORY* resource_directory, + std::vector* named_entries, + std::vector* id_entries) const; + + ProcessSubrangeReader resources_subrange_reader_; + WinVMAddress module_base_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(PEImageResourceReader); +}; + +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_WIN_PE_IMAGE_RESOURCE_READER_H_ diff --git a/snapshot/win/process_subrange_reader.cc b/snapshot/win/process_subrange_reader.cc new file mode 100644 index 00000000..996fbdf3 --- /dev/null +++ b/snapshot/win/process_subrange_reader.cc @@ -0,0 +1,104 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "snapshot/win/process_subrange_reader.h" + +#include "base/logging.h" +#include "snapshot/win/process_reader_win.h" + +namespace crashpad { + +ProcessSubrangeReader::ProcessSubrangeReader() + : name_(), + range_(), + process_reader_(nullptr) { +} + +ProcessSubrangeReader::~ProcessSubrangeReader() { +} + +bool ProcessSubrangeReader::Initialize(ProcessReaderWin* process_reader, + WinVMAddress base, + WinVMSize size, + const std::string& name) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + if (!InitializeInternal(process_reader, base, size, name)) { + return false; + } + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +bool ProcessSubrangeReader::InitializeSubrange( + const ProcessSubrangeReader& that, + WinVMAddress base, + WinVMSize size, + const std::string& sub_name) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + INITIALIZATION_STATE_DCHECK_VALID(that.initialized_); + + if (!InitializeInternal( + that.process_reader_, base, size, that.name_ + " " + sub_name)) { + return false; + } + + if (!that.range_.ContainsRange(range_)) { + LOG(WARNING) << "range " << range_.AsString() << " outside of range " + << that.range_.AsString() << " for " << name_; + return false; + } + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +bool ProcessSubrangeReader::ReadMemory(WinVMAddress address, + WinVMSize size, + void* into) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + CheckedWinAddressRange read_range(process_reader_->Is64Bit(), address, size); + if (!read_range.IsValid()) { + LOG(WARNING) << "invalid read range " << read_range.AsString(); + return false; + } + + if (!range_.ContainsRange(read_range)) { + LOG(WARNING) << "attempt to read outside of " << name_ << " range " + << range_.AsString() << " at range " << read_range.AsString(); + return false; + } + + return process_reader_->ReadMemory(address, size, into); +} + +bool ProcessSubrangeReader::InitializeInternal(ProcessReaderWin* process_reader, + WinVMAddress base, + WinVMSize size, + const std::string& name) { + range_.SetRange(process_reader->Is64Bit(), base, size); + if (!range_.IsValid()) { + LOG(WARNING) << "invalid range " << range_.AsString() << " for " << name; + return false; + } + + name_ = name; + process_reader_ = process_reader; + + return true; +} + +} // namespace crashpad diff --git a/snapshot/win/process_subrange_reader.h b/snapshot/win/process_subrange_reader.h new file mode 100644 index 00000000..e8da592c --- /dev/null +++ b/snapshot/win/process_subrange_reader.h @@ -0,0 +1,114 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_SNAPSHOT_WIN_PROCESS_SUBRANGE_READER_WIN_H_ +#define CRASHPAD_SNAPSHOT_WIN_PROCESS_SUBRANGE_READER_WIN_H_ + +#include + +#include "base/basictypes.h" +#include "util/misc/initialization_state_dcheck.h" +#include "util/win/address_types.h" +#include "util/win/checked_win_address_range.h" + +namespace crashpad { + +class ProcessReaderWin; + +//! \brief A wrapper for ProcessReaderWin that only allows a specific subrange +//! to be read from. +//! +//! This class is useful to restrict reads to a specific address range, such as +//! the address range occupied by a loaded module, or a specific section within +//! a module. +class ProcessSubrangeReader { + public: + ProcessSubrangeReader(); + ~ProcessSubrangeReader(); + + //! \brief Initializes the object. + //! + //! \param[in] process_reader A reader for a remote process. + //! \param[in] base The base address for the range that reads should be + //! restricted to. + //! \param[in] size The size of the range that reads should be restricted to. + //! \param[in] name The range’s name, a string to be used in logged messages. + //! This string is for diagnostic purposes. + //! + //! \return `true` on success, `false` on failure with a message logged. The + //! other methods in this class must not be called unless this method or + //! InitializeSubrange() has returned true. + bool Initialize(ProcessReaderWin* process_reader, + WinVMAddress base, + WinVMSize size, + const std::string& name); + + //! \brief Initializes the object to a subrange of an existing + //! ProcessSubrangeReader. + //! + //! The subrange identified by \a base and \a size must be contained within + //! the subrange in \a that. + //! + //! \param[in] that The existing ProcessSubrangeReader to base the new object + //! on. + //! \param[in] base The base address for the range that reads should be + //! restricted to. + //! \param[in] size The size of the range that reads should be restricted to. + //! \param[in] sub_name A description of the subrange, which will be appended + //! to the \a name in \a that and used in logged messages. This string is + //! for diagnostic purposes. + //! + //! \return `true` on success, `false` on failure with a message logged. The + //! other methods in this class must not be called unless this method or + //! Initialize() has returned true. + bool InitializeSubrange(const ProcessSubrangeReader& that, + WinVMAddress base, + WinVMSize size, + const std::string& sub_name); + + bool Is64Bit() const { return range_.Is64Bit(); } + WinVMAddress Base() const { return range_.Base(); } + WinVMAddress Size() const { return range_.Size(); } + const std::string& name() const { return name_; } + + //! \brief Reads memory from the remote process. + //! + //! The range specified by \a address and \a size must be contained within + //! the range that this object is permitted to read. + //! + //! \param[in] address The address to read from. + //! \param[in] size The size of data to read, in bytes. + //! \param[out] into The buffer to read data into. + //! + //! \return `true` on success, `false` on failure with a message logged. + bool ReadMemory(WinVMAddress address, WinVMSize size, void* into) const; + + private: + // Common helper for Initialize() and InitializeSubrange(). + bool InitializeInternal(ProcessReaderWin* process_reader, + WinVMAddress base, + WinVMSize size, + const std::string& name); + + std::string name_; + CheckedWinAddressRange range_; + ProcessReaderWin* process_reader_; // weak + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(ProcessSubrangeReader); +}; + +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_WIN_PROCESS_SUBRANGE_READER_WIN_H_ diff --git a/snapshot/win/system_snapshot_win.cc b/snapshot/win/system_snapshot_win.cc index 88827a5d..a62bf5ac 100644 --- a/snapshot/win/system_snapshot_win.cc +++ b/snapshot/win/system_snapshot_win.cc @@ -86,35 +86,37 @@ void SystemSnapshotWin::Initialize(ProcessReaderWin* process_reader) { process_reader_ = process_reader; - // We use both GetVersionEx and VerQueryValue. GetVersionEx is not trustworthy - // after Windows 8 (depending on the application manifest) so its data is used - // only to fill the os_server_ field, and the rest comes from the version + // We use both GetVersionEx() and GetModuleVersionAndType() (which uses + // VerQueryValue() internally). GetVersionEx() is not trustworthy after + // Windows 8 (depending on the application manifest) so its data is used only + // to fill the os_server_ field, and the rest comes from the version // information stamped on kernel32.dll. OSVERSIONINFOEX version_info = {sizeof(version_info)}; if (!GetVersionEx(reinterpret_cast(&version_info))) { PLOG(WARNING) << "GetVersionEx"; } else { - const wchar_t kSystemDll[] = L"kernel32.dll"; - VS_FIXEDFILEINFO ffi; - if (GetModuleVersionAndType(base::FilePath(kSystemDll), &ffi)) { - std::string flags_string = GetStringForFileFlags(ffi.dwFileFlags); - os_server_ = version_info.wProductType != VER_NT_WORKSTATION; - std::string os_name = GetStringForFileOS(ffi.dwFileOS); - os_version_major_ = ffi.dwFileVersionMS >> 16; - os_version_minor_ = ffi.dwFileVersionMS & 0xffff; - os_version_bugfix_ = ffi.dwFileVersionLS >> 16; - os_version_build_ = - base::StringPrintf("%d", ffi.dwFileVersionLS & 0xffff); - os_version_full_ = base::StringPrintf( - "%s %d.%d.%d.%s%s", - os_name.c_str(), - os_version_major_, - os_version_minor_, - os_version_bugfix_, - os_version_build_.c_str(), - flags_string.empty() ? "" : (std::string(" (") + flags_string + ")") - .c_str()); - } + os_server_ = version_info.wProductType != VER_NT_WORKSTATION; + } + + const wchar_t kSystemDll[] = L"kernel32.dll"; + VS_FIXEDFILEINFO ffi; + if (GetModuleVersionAndType(base::FilePath(kSystemDll), &ffi)) { + std::string flags_string = GetStringForFileFlags(ffi.dwFileFlags); + std::string os_name = GetStringForFileOS(ffi.dwFileOS); + os_version_major_ = ffi.dwFileVersionMS >> 16; + os_version_minor_ = ffi.dwFileVersionMS & 0xffff; + os_version_bugfix_ = ffi.dwFileVersionLS >> 16; + os_version_build_ = + base::StringPrintf("%d", ffi.dwFileVersionLS & 0xffff); + os_version_full_ = base::StringPrintf( + "%s %d.%d.%d.%s%s", + os_name.c_str(), + os_version_major_, + os_version_minor_, + os_version_bugfix_, + os_version_build_.c_str(), + flags_string.empty() ? "" : (std::string(" (") + flags_string + ")") + .c_str()); } INITIALIZATION_STATE_SET_VALID(initialized_); diff --git a/util/numeric/checked_address_range.cc b/util/numeric/checked_address_range.cc index dcbde23e..1d50696f 100644 --- a/util/numeric/checked_address_range.cc +++ b/util/numeric/checked_address_range.cc @@ -14,6 +14,8 @@ #include "util/numeric/checked_address_range.h" +#include "base/strings/stringprintf.h" + #if defined(OS_MACOSX) #include #elif defined(OS_WIN) @@ -109,6 +111,12 @@ bool CheckedAddressRangeGeneric::ContainsRange( : range_32_.ContainsRange(that.range_32_); } +template +std::string CheckedAddressRangeGeneric::AsString() const { + return base::StringPrintf( + "0x%llx + 0x%llx (%s)", Base(), Size(), Is64Bit() ? "64" : "32"); +} + // Explicit instantiations for the cases we use. #if defined(OS_MACOSX) template class CheckedAddressRangeGeneric; diff --git a/util/numeric/checked_address_range.h b/util/numeric/checked_address_range.h index e9514bbd..65f85fbd 100644 --- a/util/numeric/checked_address_range.h +++ b/util/numeric/checked_address_range.h @@ -17,6 +17,8 @@ #include +#include + #include "build/build_config.h" #include "util/numeric/checked_range.h" @@ -108,6 +110,12 @@ class CheckedAddressRangeGeneric { //! CheckedAddressRangeGeneric objects involved. bool ContainsRange(const CheckedAddressRangeGeneric& that) const; + //! \brief Returns a string describing the address range. + //! + //! The string will be formatted as `"0x123 + 0x45 (64)"`, where the + //! individual components are the address, size, and bitness. + std::string AsString() const; + private: #if defined(COMPILER_MSVC) // MSVC cannot handle a union containing CheckedRange (with constructor, etc.) diff --git a/util/win/module_version.h b/util/win/module_version.h index be56eb9d..afc2a508 100644 --- a/util/win/module_version.h +++ b/util/win/module_version.h @@ -24,9 +24,15 @@ namespace crashpad { //! \brief Retrieve the type and version information from a given module (exe, //! dll, etc.) //! +//! This function calls `GetFileVersionInfo()`, which can implicitly call +//! `LoadLibrary()` to load \a path into the calling process. Do not call this +//! function on an untrusted module, because there is a risk of executing the +//! module’s code. +//! //! \param[in] path The path to the module to be inspected. -//! \param[out] vs_fixedfileinfo The `VS_FIXEDFILEINFO` on success. -//! `dwFileFlags` will have been masked with `dwFileFlagsMask` already. +//! \param[out] vs_fixedfileinfo The VS_FIXEDFILEINFO on success. +//! VS_FIXEDFILEINFO::dwFileFlags will have been masked with +//! VS_FIXEDFILEINFO::dwFileFlagsMask already. //! //! \return `true` on success, or `false` on failure with a message logged. If //! the module has no `VERSIONINFO` resource, `false` will be returned