linux: Fix locating modules with multiple mappings from offset 0

The general strategy used by Crashpad to determine loaded modules is to
read the link_map to get the addresses of the dynamic arrays for all
loaded modules. Those addresses can then be used to query the MemoryMap
to locate the module's mappings, and in particular the base mapping
from which Crashpad can parse the entire loaded ELF file.

ELF modules are typically loaded in several mappings with varying
permissions for different segments. The previous strategy used to find
the base mapping for a module was to search backwards from the mapping
for the dynamic array until a mapping from file offset 0 was found for
the same file. This fails when the file is mapped multiple times from
file offset 0, which can happen if the first page of the file contains
a GNU_RELRO segment.

This new strategy queries the MemoryMap for ALL mappings associated
with the dynamic array's mapping, mapped from offset 0. The consumer
(process_reader_linux.cc) can then determine which mapping is the
correct base by attempting to parse a module at that address and
corroborating the PT_DYNAMIC or program header table address from the
parsed module with the values Crashpad gets from the link_map or
auxiliary vector.

Bug: crashpad:30
Change-Id: Ibfcbba512e8fccc8c65afef734ea5640b71e9f70
Reviewed-on: https://chromium-review.googlesource.com/1139396
Commit-Queue: Joshua Peraza <jperaza@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
Joshua Peraza 2018-07-26 08:27:31 -07:00 committed by Commit Bot
parent 20294e79cc
commit 52ff1accbb
11 changed files with 592 additions and 123 deletions

View File

@ -31,12 +31,14 @@ class ElfImageReader::ProgramHeaderTable {
public:
virtual ~ProgramHeaderTable() {}
virtual bool VerifyLoadSegments() const = 0;
virtual bool VerifyLoadSegments(bool verbose) const = 0;
virtual size_t Size() const = 0;
virtual bool GetDynamicSegment(VMAddress* address, VMSize* size) const = 0;
virtual bool GetPreferredElfHeaderAddress(VMAddress* address) const = 0;
virtual bool GetPreferredElfHeaderAddress(VMAddress* address,
bool verbose) const = 0;
virtual bool GetPreferredLoadedMemoryRange(VMAddress* address,
VMSize* size) const = 0;
VMSize* size,
bool verbose) const = 0;
// Locate the next PT_NOTE segment starting at segment index start_index. If a
// PT_NOTE segment is found, start_index is set to the next index after the
@ -58,14 +60,15 @@ class ElfImageReader::ProgramHeaderTableSpecific
bool Initialize(const ProcessMemoryRange& memory,
VMAddress address,
VMSize num_segments) {
VMSize num_segments,
bool verbose) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
table_.resize(num_segments);
if (!memory.Read(address, sizeof(PhdrType) * num_segments, table_.data())) {
return false;
}
if (!VerifyLoadSegments()) {
if (!VerifyLoadSegments(verbose)) {
return false;
}
@ -73,7 +76,7 @@ class ElfImageReader::ProgramHeaderTableSpecific
return true;
}
bool VerifyLoadSegments() const override {
bool VerifyLoadSegments(bool verbose) const override {
constexpr bool is_64_bit = std::is_same<PhdrType, Elf64_Phdr>::value;
VMAddress last_vaddr;
bool load_found = false;
@ -83,12 +86,12 @@ class ElfImageReader::ProgramHeaderTableSpecific
is_64_bit, header.p_vaddr, header.p_memsz);
if (!load_range.IsValid()) {
LOG(ERROR) << "bad load range";
LOG_IF(ERROR, verbose) << "bad load range";
return false;
}
if (load_found && header.p_vaddr <= last_vaddr) {
LOG(ERROR) << "out of order load segments";
LOG_IF(ERROR, verbose) << "out of order load segments";
return false;
}
load_found = true;
@ -100,7 +103,8 @@ class ElfImageReader::ProgramHeaderTableSpecific
size_t Size() const override { return sizeof(PhdrType) * table_.size(); }
bool GetPreferredElfHeaderAddress(VMAddress* address) const override {
bool GetPreferredElfHeaderAddress(VMAddress* address,
bool verbose) const override {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
for (const auto& header : table_) {
if (header.p_type == PT_LOAD && header.p_offset == 0) {
@ -108,12 +112,13 @@ class ElfImageReader::ProgramHeaderTableSpecific
return true;
}
}
LOG(ERROR) << "no preferred header address";
LOG_IF(ERROR, verbose) << "no preferred header address";
return false;
}
bool GetPreferredLoadedMemoryRange(VMAddress* base,
VMSize* size) const override {
VMSize* size,
bool verbose) const override {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
VMAddress preferred_base = 0;
@ -133,7 +138,7 @@ class ElfImageReader::ProgramHeaderTableSpecific
*size = preferred_end - preferred_base;
return true;
}
LOG(ERROR) << "no load segments";
LOG_IF(ERROR, verbose) << "no load segments";
return false;
}
@ -340,7 +345,8 @@ ElfImageReader::ElfImageReader()
ElfImageReader::~ElfImageReader() {}
bool ElfImageReader::Initialize(const ProcessMemoryRange& memory,
VMAddress address) {
VMAddress address,
bool verbose) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
ehdr_address_ = address;
if (!memory_.Initialize(memory)) {
@ -354,13 +360,13 @@ bool ElfImageReader::Initialize(const ProcessMemoryRange& memory,
if (e_ident[EI_MAG0] != ELFMAG0 || e_ident[EI_MAG1] != ELFMAG1 ||
e_ident[EI_MAG2] != ELFMAG2 || e_ident[EI_MAG3] != ELFMAG3) {
LOG(ERROR) << "Incorrect ELF magic number";
LOG_IF(ERROR, verbose) << "Incorrect ELF magic number";
return false;
}
if (!(memory_.Is64Bit() && e_ident[EI_CLASS] == ELFCLASS64) &&
!(!memory_.Is64Bit() && e_ident[EI_CLASS] == ELFCLASS32)) {
LOG(ERROR) << "unexpected bitness";
LOG_IF(ERROR, verbose) << "unexpected bitness";
return false;
}
@ -370,12 +376,12 @@ bool ElfImageReader::Initialize(const ProcessMemoryRange& memory,
constexpr uint8_t expected_encoding = ELFDATA2MSB;
#endif
if (e_ident[EI_DATA] != expected_encoding) {
LOG(ERROR) << "unexpected encoding";
LOG_IF(ERROR, verbose) << "unexpected encoding";
return false;
}
if (e_ident[EI_VERSION] != EV_CURRENT) {
LOG(ERROR) << "unexpected version";
LOG_IF(ERROR, verbose) << "unexpected version";
return false;
}
@ -388,15 +394,15 @@ bool ElfImageReader::Initialize(const ProcessMemoryRange& memory,
#define VERIFY_HEADER(header) \
do { \
if (header.e_type != ET_EXEC && header.e_type != ET_DYN) { \
LOG(ERROR) << "unexpected image type"; \
LOG_IF(ERROR, verbose) << "unexpected image type"; \
return false; \
} \
if (header.e_version != EV_CURRENT) { \
LOG(ERROR) << "unexpected version"; \
LOG_IF(ERROR, verbose) << "unexpected version"; \
return false; \
} \
if (header.e_ehsize != sizeof(header)) { \
LOG(ERROR) << "unexpected header size"; \
LOG_IF(ERROR, verbose) << "unexpected header size"; \
return false; \
} \
} while (false);
@ -407,21 +413,21 @@ bool ElfImageReader::Initialize(const ProcessMemoryRange& memory,
VERIFY_HEADER(header_32_);
}
if (!InitializeProgramHeaders()) {
if (!InitializeProgramHeaders(verbose)) {
return false;
}
VMAddress preferred_ehdr_address;
if (!program_headers_.get()->GetPreferredElfHeaderAddress(
&preferred_ehdr_address)) {
&preferred_ehdr_address, verbose)) {
return false;
}
load_bias_ = ehdr_address_ - preferred_ehdr_address;
VMAddress base_address;
VMSize loaded_size;
if (!program_headers_.get()->GetPreferredLoadedMemoryRange(&base_address,
&loaded_size)) {
if (!program_headers_.get()->GetPreferredLoadedMemoryRange(
&base_address, &loaded_size, verbose)) {
return false;
}
base_address += load_bias_;
@ -443,12 +449,12 @@ bool ElfImageReader::Initialize(const ProcessMemoryRange& memory,
CheckedVMAddressRange range(memory_.Is64Bit(), base_address, loaded_size);
if (!range.ContainsRange(
CheckedVMAddressRange(memory_.Is64Bit(), ehdr_address_, ehdr_size))) {
LOG(ERROR) << "ehdr out of range";
LOG_IF(ERROR, verbose) << "ehdr out of range";
return false;
}
if (!range.ContainsRange(CheckedVMAddressRange(
memory.Is64Bit(), phdr_address, program_headers_->Size()))) {
LOG(ERROR) << "phdrs out of range";
LOG_IF(ERROR, verbose) << "phdrs out of range";
return false;
}
@ -545,19 +551,40 @@ bool ElfImageReader::GetDebugAddress(VMAddress* debug) {
return GetAddressFromDynamicArray(DT_DEBUG, true, debug);
}
bool ElfImageReader::InitializeProgramHeaders() {
#define INITIALIZE_PROGRAM_HEADERS(PhdrType, header) \
do { \
if (header.e_phentsize != sizeof(PhdrType)) { \
LOG(ERROR) << "unexpected phdr size"; \
return false; \
} \
auto phdrs = new ProgramHeaderTableSpecific<PhdrType>(); \
program_headers_.reset(phdrs); \
if (!phdrs->Initialize( \
memory_, ehdr_address_ + header.e_phoff, header.e_phnum)) { \
return false; \
} \
bool ElfImageReader::GetDynamicArrayAddress(VMAddress* address) {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
VMAddress dyn_segment_address;
VMSize dyn_segment_size;
if (!program_headers_.get()->GetDynamicSegment(&dyn_segment_address,
&dyn_segment_size)) {
LOG(ERROR) << "no dynamic segment";
return false;
}
*address = dyn_segment_address + GetLoadBias();
return true;
}
VMAddress ElfImageReader::GetProgramHeaderTableAddress() {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return ehdr_address_ +
(memory_.Is64Bit() ? header_64_.e_phoff : header_32_.e_phoff);
}
bool ElfImageReader::InitializeProgramHeaders(bool verbose) {
#define INITIALIZE_PROGRAM_HEADERS(PhdrType, header) \
do { \
if (header.e_phentsize != sizeof(PhdrType)) { \
LOG_IF(ERROR, verbose) << "unexpected phdr size"; \
return false; \
} \
auto phdrs = new ProgramHeaderTableSpecific<PhdrType>(); \
program_headers_.reset(phdrs); \
if (!phdrs->Initialize(memory_, \
ehdr_address_ + header.e_phoff, \
header.e_phnum, \
verbose)) { \
return false; \
} \
} while (false);
if (memory_.Is64Bit()) {

View File

@ -118,7 +118,13 @@ class ElfImageReader {
//! \param[in] memory A memory reader for the remote process.
//! \param[in] address The address in the remote process' address space where
//! the ELF image is loaded.
bool Initialize(const ProcessMemoryRange& memory, VMAddress address);
//! \param[in] verbose `true` if this method should log error messages during
//! initialization. Setting this value to `false` will reduce the error
//! messages relating to verifying the ELF image, but may not suppress
//! logging entirely.
bool Initialize(const ProcessMemoryRange& memory,
VMAddress address,
bool verbose = true);
//! \brief Returns the base address of the image's memory range.
//!
@ -172,6 +178,16 @@ class ElfImageReader {
//! \return `true` if the debug address was found.
bool GetDebugAddress(VMAddress* debug);
//! \brief Determine the address of `PT_DYNAMIC` segment.
//!
//! \param[out] address The address of the array, valid if this method returns
//! `true`.
//! \return `true` on success. Otherwise `false` with a message logged.
bool GetDynamicArrayAddress(VMAddress* address);
//! \brief Return the address of the program header table.
VMAddress GetProgramHeaderTableAddress();
//! \brief Return a NoteReader for this image, which scans all PT_NOTE
//! segments in the image.
//!
@ -238,7 +254,7 @@ class ElfImageReader {
template <typename PhdrType>
class ProgramHeaderTableSpecific;
bool InitializeProgramHeaders();
bool InitializeProgramHeaders(bool verbose);
bool InitializeDynamicArray();
bool InitializeDynamicSymbolTable();
bool GetAddressFromDynamicArray(uint64_t tag, bool log, VMAddress* address);

View File

@ -103,10 +103,10 @@ void LocateExecutable(PtraceConnection* connection,
ASSERT_TRUE(memory_map.Initialize(connection));
const MemoryMap::Mapping* phdr_mapping = memory_map.FindMapping(phdrs);
ASSERT_TRUE(phdr_mapping);
const MemoryMap::Mapping* exe_mapping =
memory_map.FindFileMmapStart(*phdr_mapping);
ASSERT_TRUE(exe_mapping);
*elf_address = exe_mapping->range.Base();
std::vector<const MemoryMap::Mapping*> possible_mappings =
memory_map.FindFilePossibleMmapStarts(*phdr_mapping);
ASSERT_EQ(possible_mappings.size(), 1u);
*elf_address = possible_mappings[0]->range.Base();
}
#endif // OS_FUCHSIA

View File

@ -74,9 +74,10 @@ void TestAgainstTarget(PtraceConnection* connection) {
const MemoryMap::Mapping* phdr_mapping = mappings.FindMapping(phdrs);
ASSERT_TRUE(phdr_mapping);
const MemoryMap::Mapping* exe_mapping =
mappings.FindFileMmapStart(*phdr_mapping);
LinuxVMAddress elf_address = exe_mapping->range.Base();
std::vector<const MemoryMap::Mapping*> exe_mappings =
mappings.FindFilePossibleMmapStarts(*phdr_mapping);
ASSERT_EQ(exe_mappings.size(), 1u);
LinuxVMAddress elf_address = exe_mappings[0]->range.Base();
ProcessMemoryLinux memory;
ASSERT_TRUE(memory.Initialize(connection->GetProcessID()));
@ -142,9 +143,24 @@ void TestAgainstTarget(PtraceConnection* connection) {
mappings.FindMapping(module.dynamic_array);
ASSERT_TRUE(dyn_mapping);
const MemoryMap::Mapping* module_mapping =
mappings.FindFileMmapStart(*dyn_mapping);
ASSERT_TRUE(module_mapping);
std::vector<const MemoryMap::Mapping*> possible_mappings =
mappings.FindFilePossibleMmapStarts(*dyn_mapping);
ASSERT_GE(possible_mappings.size(), 1u);
std::unique_ptr<ElfImageReader> module_reader;
const MemoryMap::Mapping* module_mapping = nullptr;
for (const auto mapping : possible_mappings) {
auto parsed_module = std::make_unique<ElfImageReader>();
VMAddress dynamic_address;
if (parsed_module->Initialize(range, mapping->range.Base()) &&
parsed_module->GetDynamicArrayAddress(&dynamic_address) &&
dynamic_address == module.dynamic_array) {
module_reader = std::move(parsed_module);
module_mapping = mapping;
break;
}
}
ASSERT_TRUE(module_reader.get());
#if defined(OS_ANDROID)
EXPECT_FALSE(module.name.empty());
@ -168,20 +184,17 @@ void TestAgainstTarget(PtraceConnection* connection) {
module.name);
#endif // OS_ANDROID
ElfImageReader module_reader;
ASSERT_TRUE(module_reader.Initialize(range, module_mapping->range.Base()));
// Android's loader stops setting its own load bias after Android 4.4.4
// (API 20) until Android 6.0 (API 23).
if (is_android_loader && android_runtime_api > 20 &&
android_runtime_api < 23) {
EXPECT_EQ(module.load_bias, 0);
} else {
EXPECT_EQ(module.load_bias, module_reader.GetLoadBias());
EXPECT_EQ(module.load_bias, module_reader->GetLoadBias());
}
CheckedLinuxAddressRange module_range(
connection->Is64Bit(), module_reader.Address(), module_reader.Size());
connection->Is64Bit(), module_reader->Address(), module_reader->Size());
EXPECT_TRUE(module_range.ContainsValue(module.dynamic_array));
}
}

View File

@ -347,20 +347,45 @@ void ProcessReaderLinux::InitializeModules() {
return;
}
const MemoryMap::Mapping* exe_mapping;
if (!(exe_mapping = GetMemoryMap()->FindMapping(phdrs)) ||
!(exe_mapping = GetMemoryMap()->FindFileMmapStart(*exe_mapping))) {
return;
}
ProcessMemoryRange range;
if (!range.Initialize(Memory(), is_64_bit_)) {
return;
}
auto exe_reader = std::make_unique<ElfImageReader>();
if (!exe_reader->Initialize(range, exe_mapping->range.Base())) {
return;
// The strategy used for identifying loaded modules depends on ELF files
// conventionally loading their header and program headers into memory.
// Locating the correct module could fail if the headers aren't mapped, are
// mapped at an unexpected location, or if there are other mappings
// constructed to look like the ELF module being searched for.
const MemoryMap::Mapping* exe_mapping = nullptr;
std::unique_ptr<ElfImageReader> exe_reader;
{
const MemoryMap::Mapping* phdr_mapping = memory_map_.FindMapping(phdrs);
if (!phdr_mapping) {
return;
}
std::vector<const MemoryMap::Mapping*> possible_mappings =
memory_map_.FindFilePossibleMmapStarts(*phdr_mapping);
for (auto riter = possible_mappings.rbegin();
riter != possible_mappings.rend();
++riter) {
auto mapping = *riter;
auto parsed_exe = std::make_unique<ElfImageReader>();
if (parsed_exe->Initialize(
range,
mapping->range.Base(),
/* verbose= */ possible_mappings.size() == 1) &&
parsed_exe->GetProgramHeaderTableAddress() == phdrs) {
exe_mapping = mapping;
exe_reader = std::move(parsed_exe);
break;
}
}
if (!exe_mapping) {
LOG(ERROR) << "no exe mappings " << possible_mappings.size();
return;
}
}
LinuxVMAddress debug_address;
@ -386,19 +411,42 @@ void ProcessReaderLinux::InitializeModules() {
aux.GetValue(AT_BASE, &loader_base);
for (const DebugRendezvous::LinkEntry& entry : debug.Modules()) {
const MemoryMap::Mapping* mapping;
if (!(mapping = memory_map_.FindMapping(entry.dynamic_array)) ||
!(mapping = memory_map_.FindFileMmapStart(*mapping))) {
continue;
}
const MemoryMap::Mapping* module_mapping = nullptr;
std::unique_ptr<ElfImageReader> elf_reader;
{
const MemoryMap::Mapping* dyn_mapping =
memory_map_.FindMapping(entry.dynamic_array);
if (!dyn_mapping) {
continue;
}
auto elf_reader = std::make_unique<ElfImageReader>();
if (!elf_reader->Initialize(range, mapping->range.Base())) {
continue;
std::vector<const MemoryMap::Mapping*> possible_mappings =
memory_map_.FindFilePossibleMmapStarts(*dyn_mapping);
for (auto riter = possible_mappings.rbegin();
riter != possible_mappings.rend();
++riter) {
auto mapping = *riter;
auto parsed_module = std::make_unique<ElfImageReader>();
VMAddress dynamic_address;
if (parsed_module->Initialize(
range,
mapping->range.Base(),
/* verbose= */ possible_mappings.size() == 1) &&
parsed_module->GetDynamicArrayAddress(&dynamic_address) &&
dynamic_address == entry.dynamic_array) {
module_mapping = mapping;
elf_reader = std::move(parsed_module);
break;
}
}
if (!module_mapping) {
LOG(ERROR) << "no module mappings " << possible_mappings.size();
continue;
}
}
Module module = {};
module.name = !entry.name.empty() ? entry.name : mapping->name;
module.name = !entry.name.empty() ? entry.name : module_mapping->name;
module.elf_reader = elf_reader.get();
module.type = loader_base && elf_reader->Address() == loader_base
? ModuleSnapshot::kModuleTypeDynamicLoader

View File

@ -14,6 +14,8 @@
#include "snapshot/linux/process_reader_linux.h"
#include <dlfcn.h>
#include <elf.h>
#include <errno.h>
#include <link.h>
#include <pthread.h>
@ -38,7 +40,11 @@
#include "test/linux/fake_ptrace_connection.h"
#include "test/linux/get_tls.h"
#include "test/multiprocess.h"
#include "test/scoped_module_handle.h"
#include "test/test_paths.h"
#include "util/file/file_io.h"
#include "util/file/file_writer.h"
#include "util/file/filesystem.h"
#include "util/linux/direct_ptrace_connection.h"
#include "util/misc/address_sanitizer.h"
#include "util/misc/from_pointer_cast.h"
@ -507,7 +513,220 @@ void ExpectModulesFromSelf(
#endif // !OS_ANDROID || !ARCH_CPU_ARMEL || __ANDROID_API__ >= 21
}
bool WriteTestModule(const base::FilePath& module_path) {
#if defined(ARCH_CPU_64_BITS)
using Ehdr = Elf64_Ehdr;
using Phdr = Elf64_Phdr;
using Shdr = Elf64_Shdr;
using Dyn = Elf64_Dyn;
using Sym = Elf64_Sym;
unsigned char elf_class = ELFCLASS64;
#else
using Ehdr = Elf32_Ehdr;
using Phdr = Elf32_Phdr;
using Shdr = Elf32_Shdr;
using Dyn = Elf32_Dyn;
using Sym = Elf32_Sym;
unsigned char elf_class = ELFCLASS32;
#endif
struct {
Ehdr ehdr;
struct {
Phdr load1;
Phdr load2;
Phdr dynamic;
} phdr_table;
struct {
Dyn hash;
Dyn strtab;
Dyn symtab;
Dyn strsz;
Dyn syment;
Dyn null;
} dynamic_array;
struct {
Elf32_Word nbucket;
Elf32_Word nchain;
Elf32_Word bucket;
Elf32_Word chain;
} hash_table;
struct {
} string_table;
struct {
Sym und_symbol;
} symbol_table;
struct {
Shdr null;
Shdr dynamic;
Shdr string_table;
} shdr_table;
} module = {};
module.ehdr.e_ident[EI_MAG0] = ELFMAG0;
module.ehdr.e_ident[EI_MAG1] = ELFMAG1;
module.ehdr.e_ident[EI_MAG2] = ELFMAG2;
module.ehdr.e_ident[EI_MAG3] = ELFMAG3;
module.ehdr.e_ident[EI_CLASS] = elf_class;
#if defined(ARCH_CPU_LITTLE_ENDIAN)
module.ehdr.e_ident[EI_DATA] = ELFDATA2LSB;
#else
module.ehdr.e_ident[EI_DATA] = ELFDATA2MSB;
#endif // ARCH_CPU_LITTLE_ENDIAN
module.ehdr.e_ident[EI_VERSION] = EV_CURRENT;
module.ehdr.e_type = ET_DYN;
#if defined(ARCH_CPU_X86)
module.ehdr.e_machine = EM_386;
#elif defined(ARCH_CPU_X86_64)
module.ehdr.e_machine = EM_X86_64;
#elif defined(ARCH_CPU_ARMEL)
module.ehdr.e_machine = EM_ARM;
#elif defined(ARCH_CPU_ARM64)
module.ehdr.e_machine = EM_AARCH64;
#elif defined(ARCH_CPU_MIPSEL) || defined(ARCH_CPU_MIPS64EL)
module.ehdr.e_machine = EM_MIPS;
#endif
module.ehdr.e_version = EV_CURRENT;
module.ehdr.e_ehsize = sizeof(module.ehdr);
module.ehdr.e_phoff = offsetof(decltype(module), phdr_table);
module.ehdr.e_phnum = sizeof(module.phdr_table) / sizeof(Phdr);
module.ehdr.e_phentsize = sizeof(Phdr);
module.ehdr.e_shoff = offsetof(decltype(module), shdr_table);
module.ehdr.e_shentsize = sizeof(Shdr);
module.ehdr.e_shnum = sizeof(module.shdr_table) / sizeof(Shdr);
module.ehdr.e_shstrndx = SHN_UNDEF;
constexpr size_t load2_vaddr = 0x200000;
module.phdr_table.load1.p_type = PT_LOAD;
module.phdr_table.load1.p_offset = 0;
module.phdr_table.load1.p_vaddr = 0;
module.phdr_table.load1.p_filesz = sizeof(module);
module.phdr_table.load1.p_memsz = sizeof(module);
module.phdr_table.load1.p_flags = PF_R;
module.phdr_table.load1.p_align = load2_vaddr;
module.phdr_table.load2.p_type = PT_LOAD;
module.phdr_table.load2.p_offset = 0;
module.phdr_table.load2.p_vaddr = load2_vaddr;
module.phdr_table.load2.p_filesz = sizeof(module);
module.phdr_table.load2.p_memsz = sizeof(module);
module.phdr_table.load2.p_flags = PF_R | PF_W;
module.phdr_table.load2.p_align = load2_vaddr;
module.phdr_table.dynamic.p_type = PT_DYNAMIC;
module.phdr_table.dynamic.p_offset =
offsetof(decltype(module), dynamic_array);
module.phdr_table.dynamic.p_vaddr =
load2_vaddr + module.phdr_table.dynamic.p_offset;
module.phdr_table.dynamic.p_filesz = sizeof(module.dynamic_array);
module.phdr_table.dynamic.p_memsz = sizeof(module.dynamic_array);
module.phdr_table.dynamic.p_flags = PF_R | PF_W;
module.phdr_table.dynamic.p_align = 8;
module.dynamic_array.hash.d_tag = DT_HASH;
module.dynamic_array.hash.d_un.d_ptr = offsetof(decltype(module), hash_table);
module.dynamic_array.strtab.d_tag = DT_STRTAB;
module.dynamic_array.strtab.d_un.d_ptr =
offsetof(decltype(module), string_table);
module.dynamic_array.symtab.d_tag = DT_SYMTAB;
module.dynamic_array.symtab.d_un.d_ptr =
offsetof(decltype(module), symbol_table);
module.dynamic_array.strsz.d_tag = DT_STRSZ;
module.dynamic_array.strsz.d_un.d_val = sizeof(module.string_table);
module.dynamic_array.syment.d_tag = DT_SYMENT;
module.dynamic_array.syment.d_un.d_val = sizeof(Sym);
module.dynamic_array.null.d_tag = DT_NULL;
module.hash_table.nbucket = 1;
module.hash_table.nchain = 1;
module.hash_table.bucket = 0;
module.hash_table.chain = 0;
module.shdr_table.null.sh_type = SHT_NULL;
module.shdr_table.dynamic.sh_name = 0;
module.shdr_table.dynamic.sh_type = SHT_DYNAMIC;
module.shdr_table.dynamic.sh_flags = SHF_WRITE | SHF_ALLOC;
module.shdr_table.dynamic.sh_addr = module.phdr_table.dynamic.p_vaddr;
module.shdr_table.dynamic.sh_offset = module.phdr_table.dynamic.p_offset;
module.shdr_table.dynamic.sh_size = module.phdr_table.dynamic.p_filesz;
module.shdr_table.dynamic.sh_link =
offsetof(decltype(module.shdr_table), string_table) / sizeof(Shdr);
module.shdr_table.string_table.sh_name = 0;
module.shdr_table.string_table.sh_type = SHT_STRTAB;
module.shdr_table.string_table.sh_offset =
offsetof(decltype(module), string_table);
FileWriter writer;
if (!writer.Open(module_path,
FileWriteMode::kCreateOrFail,
FilePermissions::kWorldReadable)) {
ADD_FAILURE();
return false;
}
if (!writer.Write(&module, sizeof(module))) {
ADD_FAILURE();
return false;
}
return true;
}
ScopedModuleHandle LoadTestModule(const std::string& module_name) {
base::FilePath module_path(
TestPaths::Executable().DirName().Append(module_name));
if (!WriteTestModule(module_path)) {
return ScopedModuleHandle(nullptr);
}
EXPECT_TRUE(IsRegularFile(module_path));
ScopedModuleHandle handle(
dlopen(module_path.value().c_str(), RTLD_LAZY | RTLD_LOCAL));
EXPECT_TRUE(handle.valid())
<< "dlopen: " << module_path.value() << " " << dlerror();
EXPECT_TRUE(LoggingRemoveFile(module_path));
return handle;
}
void ExpectTestModule(ProcessReaderLinux* reader,
const std::string& module_name) {
for (const auto& module : reader->Modules()) {
if (module.name.find(module_name) != std::string::npos) {
ASSERT_TRUE(module.elf_reader);
VMAddress dynamic_addr;
ASSERT_TRUE(module.elf_reader->GetDynamicArrayAddress(&dynamic_addr));
auto dynamic_mapping = reader->GetMemoryMap()->FindMapping(dynamic_addr);
auto mappings =
reader->GetMemoryMap()->FindFilePossibleMmapStarts(*dynamic_mapping);
EXPECT_EQ(mappings.size(), 2u);
return;
}
}
ADD_FAILURE() << "Test module not found";
}
TEST(ProcessReaderLinux, SelfModules) {
const std::string module_name = "test_module.so";
ScopedModuleHandle empty_test_module(LoadTestModule(module_name));
ASSERT_TRUE(empty_test_module.valid());
FakePtraceConnection connection;
connection.Initialize(getpid());
@ -515,15 +734,19 @@ TEST(ProcessReaderLinux, SelfModules) {
ASSERT_TRUE(process_reader.Initialize(&connection));
ExpectModulesFromSelf(process_reader.Modules());
ExpectTestModule(&process_reader, module_name);
}
class ChildModuleTest : public Multiprocess {
public:
ChildModuleTest() : Multiprocess() {}
ChildModuleTest() : Multiprocess(), module_name_("test_module.so") {}
~ChildModuleTest() = default;
private:
void MultiprocessParent() override {
char c;
ASSERT_TRUE(LoggingReadFileExactly(ReadPipeHandle(), &c, sizeof(c)));
DirectPtraceConnection connection;
ASSERT_TRUE(connection.Initialize(ChildPID()));
@ -531,9 +754,20 @@ class ChildModuleTest : public Multiprocess {
ASSERT_TRUE(process_reader.Initialize(&connection));
ExpectModulesFromSelf(process_reader.Modules());
ExpectTestModule(&process_reader, module_name_);
}
void MultiprocessChild() override { CheckedReadFileAtEOF(ReadPipeHandle()); }
void MultiprocessChild() override {
ScopedModuleHandle empty_test_module(LoadTestModule(module_name_));
ASSERT_TRUE(empty_test_module.valid());
char c;
ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), &c, sizeof(c)));
CheckedReadFileAtEOF(ReadPipeHandle());
}
const std::string module_name_;
DISALLOW_COPY_AND_ASSIGN(ChildModuleTest);
};

View File

@ -36,6 +36,11 @@ void ScopedModuleHandle::Impl::Close(ModuleHandle handle) {
ScopedModuleHandle::ScopedModuleHandle(ModuleHandle handle) : handle_(handle) {}
ScopedModuleHandle::ScopedModuleHandle(ScopedModuleHandle&& other)
: handle_(other.handle_) {
other.handle_ = nullptr;
}
ScopedModuleHandle::~ScopedModuleHandle() {
if (valid()) {
Impl::Close(handle_);

View File

@ -57,6 +57,7 @@ class ScopedModuleHandle {
using ModuleHandle = Impl::ModuleHandle;
explicit ScopedModuleHandle(ModuleHandle handle);
ScopedModuleHandle(ScopedModuleHandle&& handle);
~ScopedModuleHandle();
//! \return The module handle being managed.

View File

@ -296,40 +296,39 @@ const MemoryMap::Mapping* MemoryMap::FindMappingWithName(
return nullptr;
}
const MemoryMap::Mapping* MemoryMap::FindFileMmapStart(
std::vector<const MemoryMap::Mapping*> MemoryMap::FindFilePossibleMmapStarts(
const Mapping& mapping) const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
size_t index = 0;
for (; index < mappings_.size(); ++index) {
if (mappings_[index].Equals(mapping)) {
break;
}
}
if (index >= mappings_.size()) {
LOG(ERROR) << "mapping not found";
return nullptr;
}
std::vector<const Mapping*> possible_starts;
// If the mapping is anonymous, as is for the VDSO, there is no mapped file to
// find the start of, so just return the input mapping.
if (mapping.device == 0 && mapping.inode == 0) {
return &mappings_[index];
for (const auto& candidate : mappings_) {
if (mapping.Equals(candidate)) {
possible_starts.push_back(&candidate);
return possible_starts;
}
}
LOG(ERROR) << "mapping not found";
return std::vector<const Mapping*>();
}
do {
// There may by anonymous mappings or other files mapped into the holes,
// so check that the mapping uses the same file as the input, but keep
// searching if it doesn't.
if (mappings_[index].device == mapping.device &&
mappings_[index].inode == mapping.inode &&
mappings_[index].offset == 0) {
return &mappings_[index];
for (const auto& candidate : mappings_) {
if (candidate.device == mapping.device &&
candidate.inode == mapping.inode &&
candidate.offset == 0) {
possible_starts.push_back(&candidate);
}
} while (index--);
if (mapping.Equals(candidate)) {
return possible_starts;
}
}
LOG(ERROR) << "mapping not found";
return nullptr;
return std::vector<const Mapping*>();
}
} // namespace crashpad

View File

@ -76,20 +76,27 @@ class MemoryMap {
//! it was obtained from.
const Mapping* FindMappingWithName(const std::string& name) const;
//! \brief Find the first Mapping in a series of mappings for the same file.
//! \brief Find Mappings that share a Mapping's file, mapped from offset 0.
//!
//! Executables and libaries are typically loaded into several mappings with
//! varying permissions for different segments. This method searches for the
//! mapping with the highest address at or below \a mapping, which maps the
//! same file as \a mapping from file offset 0.
//! varying permissions for different segments. Portions of an ELF file may
//! be mapped multiple times as part of loading the file, for example, when
//! initializing GNU_RELRO segments. This method searches for mappings at or
//! below \a mapping in memory that are mapped from the same file as \a
//! mapping from offset 0.
//!
//! If \a mapping is not found, `nullptr` is returned. If \a mapping is found
//! but does not map a file, \a mapping is returned.
//! This method is intended to help identify the possible base address for
//! loaded modules, but it is the caller's responsibility to determine which
//! returned mapping is correct.
//!
//! If \a mapping does not refer to a valid mapping, an empty vector will be
//! returned and a message will be logged. If \a mapping is found but does not
//! map a file, \a mapping is returned in \a possible_starts.
//!
//! \param[in] mapping A Mapping whose series to find the start of.
//! \return The first Mapping in the series or `nullptr` on failure with a
//! message logged.
const Mapping* FindFileMmapStart(const Mapping& mapping) const;
//! \return a vector of the possible mapping starts.
std::vector<const Mapping*> FindFilePossibleMmapStarts(
const Mapping& mapping) const;
private:
std::vector<Mapping> mappings_;

View File

@ -355,8 +355,8 @@ TEST(MemoryMap, MapRunningChild) {
// Expects first and third pages from mapping_start to refer to the same mapped
// file. The second page should not.
void ExpectFindFileMmapStart(LinuxVMAddress mapping_start,
LinuxVMSize page_size) {
void ExpectFindFilePossibleMmapStarts(LinuxVMAddress mapping_start,
LinuxVMSize page_size) {
FakePtraceConnection connection;
ASSERT_TRUE(connection.Initialize(getpid()));
@ -373,17 +373,27 @@ void ExpectFindFileMmapStart(LinuxVMAddress mapping_start,
ASSERT_NE(mapping1, mapping2);
ASSERT_NE(mapping2, mapping3);
EXPECT_EQ(map.FindFileMmapStart(*mapping1), mapping1);
EXPECT_EQ(map.FindFileMmapStart(*mapping2), mapping2);
EXPECT_EQ(map.FindFileMmapStart(*mapping3), mapping1);
std::vector<const MemoryMap::Mapping*> mappings;
mappings = map.FindFilePossibleMmapStarts(*mapping1);
ASSERT_EQ(mappings.size(), 1u);
EXPECT_EQ(mappings[0], mapping1);
mappings = map.FindFilePossibleMmapStarts(*mapping2);
ASSERT_EQ(mappings.size(), 1u);
EXPECT_EQ(mappings[0], mapping2);
mappings = map.FindFilePossibleMmapStarts(*mapping3);
ASSERT_EQ(mappings.size(), 1u);
EXPECT_EQ(mappings[0], mapping1);
}
TEST(MemoryMap, FindFileMmapStart) {
TEST(MemoryMap, FindFilePossibleMmapStarts) {
const size_t page_size = getpagesize();
ScopedTempDir temp_dir;
base::FilePath path =
temp_dir.path().Append(FILE_PATH_LITERAL("FindFileMmapStartTestFile"));
base::FilePath path = temp_dir.path().Append(
FILE_PATH_LITERAL("FindFilePossibleMmapStartsTestFile"));
ScopedFileHandle handle;
size_t file_length = page_size * 3;
ASSERT_NO_FATAL_FAILURE(InitializeFile(path, file_length, &handle));
@ -418,9 +428,19 @@ TEST(MemoryMap, FindFileMmapStart) {
ASSERT_NE(mapping1, mapping2);
ASSERT_NE(mapping2, mapping3);
EXPECT_EQ(map.FindFileMmapStart(*mapping1), mapping1);
EXPECT_EQ(map.FindFileMmapStart(*mapping2), mapping1);
EXPECT_EQ(map.FindFileMmapStart(*mapping3), mapping1);
std::vector<const MemoryMap::Mapping*> mappings;
mappings = map.FindFilePossibleMmapStarts(*mapping1);
ASSERT_EQ(mappings.size(), 1u);
EXPECT_EQ(mappings[0], mapping1);
mappings = map.FindFilePossibleMmapStarts(*mapping2);
ASSERT_EQ(mappings.size(), 1u);
EXPECT_EQ(mappings[0], mapping1);
mappings = map.FindFilePossibleMmapStarts(*mapping3);
ASSERT_EQ(mappings.size(), 1u);
EXPECT_EQ(mappings[0], mapping1);
#if defined(ARCH_CPU_64_BITS)
constexpr bool is_64_bit = true;
@ -429,7 +449,7 @@ TEST(MemoryMap, FindFileMmapStart) {
#endif
MemoryMap::Mapping bad_mapping;
bad_mapping.range.SetRange(is_64_bit, 0, 1);
EXPECT_EQ(map.FindFileMmapStart(bad_mapping), nullptr);
EXPECT_EQ(map.FindFilePossibleMmapStarts(bad_mapping).size(), 0u);
}
// Make the second page an anonymous mapping
@ -449,12 +469,12 @@ TEST(MemoryMap, FindFileMmapStart) {
MAP_PRIVATE | MAP_FIXED,
handle.get(),
page_size * 2));
ExpectFindFileMmapStart(mapping_start, page_size);
ExpectFindFilePossibleMmapStarts(mapping_start, page_size);
// Map the second page to another file.
ScopedFileHandle handle2;
base::FilePath path2 =
temp_dir.path().Append(FILE_PATH_LITERAL("FindFileMmapStartTestFile2"));
base::FilePath path2 = temp_dir.path().Append(
FILE_PATH_LITERAL("FindFilePossibleMmapStartsTestFile2"));
ASSERT_NO_FATAL_FAILURE(InitializeFile(path2, page_size, &handle2));
page2_mapping.ResetMmap(file_mapping.addr_as<char*>() + page_size,
@ -463,7 +483,106 @@ TEST(MemoryMap, FindFileMmapStart) {
MAP_PRIVATE | MAP_FIXED,
handle2.get(),
0);
ExpectFindFileMmapStart(mapping_start, page_size);
ExpectFindFilePossibleMmapStarts(mapping_start, page_size);
}
TEST(MemoryMap, FindFilePossibleMmapStarts_MultipleStarts) {
ScopedTempDir temp_dir;
base::FilePath path =
temp_dir.path().Append(FILE_PATH_LITERAL("MultipleStartsTestFile"));
const size_t page_size = getpagesize();
ScopedFileHandle handle;
ASSERT_NO_FATAL_FAILURE(InitializeFile(path, page_size * 2, &handle));
// Locate a sequence of pages to setup a test in.
char* seq_addr;
{
ScopedMmap whole_mapping;
ASSERT_TRUE(whole_mapping.ResetMmap(
nullptr, page_size * 8, PROT_READ, MAP_PRIVATE | MAP_ANON, -1, 0));
seq_addr = whole_mapping.addr_as<char*>();
}
// Arrange file and anonymous mappings in the sequence.
ScopedMmap file_mapping0;
ASSERT_TRUE(file_mapping0.ResetMmap(seq_addr,
page_size,
PROT_READ,
MAP_PRIVATE | MAP_FIXED,
handle.get(),
page_size));
ScopedMmap file_mapping1;
ASSERT_TRUE(file_mapping1.ResetMmap(seq_addr + page_size,
page_size * 2,
PROT_READ,
MAP_PRIVATE | MAP_FIXED,
handle.get(),
0));
ScopedMmap file_mapping2;
ASSERT_TRUE(file_mapping2.ResetMmap(seq_addr + page_size * 3,
page_size,
PROT_READ,
MAP_PRIVATE | MAP_FIXED,
handle.get(),
0));
// Skip a page
ScopedMmap file_mapping3;
ASSERT_TRUE(file_mapping3.ResetMmap(seq_addr + page_size * 5,
page_size,
PROT_READ,
MAP_PRIVATE | MAP_FIXED,
handle.get(),
0));
ScopedMmap anon_mapping;
ASSERT_TRUE(anon_mapping.ResetMmap(seq_addr + page_size * 6,
page_size,
PROT_READ,
MAP_PRIVATE | MAP_ANON | MAP_FIXED,
-1,
0));
ScopedMmap file_mapping4;
ASSERT_TRUE(file_mapping4.ResetMmap(seq_addr + page_size * 7,
page_size,
PROT_READ,
MAP_PRIVATE | MAP_FIXED,
handle.get(),
0));
FakePtraceConnection connection;
ASSERT_TRUE(connection.Initialize(getpid()));
MemoryMap map;
ASSERT_TRUE(map.Initialize(&connection));
auto mapping = map.FindMapping(file_mapping0.addr_as<VMAddress>());
ASSERT_TRUE(mapping);
auto possible_starts = map.FindFilePossibleMmapStarts(*mapping);
EXPECT_EQ(possible_starts.size(), 0u);
mapping = map.FindMapping(file_mapping1.addr_as<VMAddress>());
ASSERT_TRUE(mapping);
possible_starts = map.FindFilePossibleMmapStarts(*mapping);
EXPECT_EQ(possible_starts.size(), 1u);
mapping = map.FindMapping(file_mapping2.addr_as<VMAddress>());
ASSERT_TRUE(mapping);
possible_starts = map.FindFilePossibleMmapStarts(*mapping);
EXPECT_EQ(possible_starts.size(), 2u);
mapping = map.FindMapping(file_mapping3.addr_as<VMAddress>());
ASSERT_TRUE(mapping);
possible_starts = map.FindFilePossibleMmapStarts(*mapping);
EXPECT_EQ(possible_starts.size(), 3u);
mapping = map.FindMapping(file_mapping4.addr_as<VMAddress>());
ASSERT_TRUE(mapping);
possible_starts = map.FindFilePossibleMmapStarts(*mapping);
EXPECT_EQ(possible_starts.size(), 4u);
}
} // namespace