diff options
Diffstat (limited to 'src/processor')
-rw-r--r-- | src/processor/exploitability_linux.cc | 160 | ||||
-rw-r--r-- | src/processor/exploitability_linux.h | 51 | ||||
-rw-r--r-- | src/processor/exploitability_unittest.cc | 4 | ||||
-rw-r--r-- | src/processor/minidump.cc | 143 | ||||
-rw-r--r-- | src/processor/proc_maps_linux.cc | 103 | ||||
-rw-r--r-- | src/processor/proc_maps_linux_unittest.cc | 250 | ||||
-rw-r--r-- | src/processor/testdata/linux_in_module_outside_executable_part.dmp | bin | 49096 -> 0 bytes | |||
-rw-r--r-- | src/processor/testdata/linux_inside_elf_header.dmp | bin | 52616 -> 0 bytes |
8 files changed, 506 insertions, 205 deletions
diff --git a/src/processor/exploitability_linux.cc b/src/processor/exploitability_linux.cc index c1c291ce..3e99f89e 100644 --- a/src/processor/exploitability_linux.cc +++ b/src/processor/exploitability_linux.cc @@ -36,8 +36,6 @@ #include "processor/exploitability_linux.h" -#include <elf.h> - #include "google_breakpad/common/minidump_exception_linux.h" #include "google_breakpad/processor/call_stack.h" #include "google_breakpad/processor/process_state.h" @@ -111,10 +109,6 @@ ExploitabilityRating ExploitabilityLinux::CheckPlatformExploitability() { return EXPLOITABILITY_ERR_PROCESSING; } - if (this->ArchitectureType() == UNSUPPORTED_ARCHITECTURE) { - BPLOG(INFO) << "Unsupported architecture."; - return EXPLOITABILITY_ERR_PROCESSING; - } // Getting the instruction pointer. if (!context->GetInstructionPointer(&instruction_ptr)) { BPLOG(INFO) << "Failed to retrieve instruction pointer."; @@ -131,151 +125,17 @@ ExploitabilityRating ExploitabilityLinux::CheckPlatformExploitability() { return EXPLOITABILITY_INTERESTING; } -LinuxArchitectureType ExploitabilityLinux::ArchitectureType() { - // GetContextCPU() should have already been successfully called before - // calling this method. Thus there should be a raw exception stream for - // the minidump. - MinidumpException *exception = dump_->GetException(); - const DumpContext *dump_context = - exception ? - exception->GetContext() : NULL; - if (dump_context == NULL) { - BPLOG(INFO) << "No raw dump context."; - return UNSUPPORTED_ARCHITECTURE; - } - - // Check the architecture type. - switch (dump_context->GetContextCPU()) { - case MD_CONTEXT_ARM: - case MD_CONTEXT_X86: - return LINUX_32_BIT; - case MD_CONTEXT_ARM64: - case MD_CONTEXT_AMD64: - return LINUX_64_BIT; - default: - // This should not happen. The four architectures above should be - // the only Linux architectures. - BPLOG(INFO) << "Unsupported architecture."; - return UNSUPPORTED_ARCHITECTURE; - } -} - bool ExploitabilityLinux::InstructionPointerInCode(uint64_t instruction_ptr) { - // Get memory mapping. Most minidumps will not contain a memory - // mapping, so processing will commonly resort to checking modules. - MinidumpMemoryInfoList *mem_info_list = dump_->GetMemoryInfoList(); - const MinidumpMemoryInfo *mem_info = - mem_info_list ? - mem_info_list->GetMemoryInfoForAddress(instruction_ptr) : NULL; - - // Check if the memory mapping at the instruction pointer is executable. - // If there is no memory mapping, processing will use modules as reference. - if (mem_info != NULL) { - return mem_info->IsExecutable(); - } - - // If the memory mapping retrieval fails, check the modules - // to see if the instruction pointer is inside a module. - MinidumpModuleList *minidump_module_list = dump_->GetModuleList(); - const MinidumpModule *minidump_module = - minidump_module_list ? - minidump_module_list->GetModuleForAddress(instruction_ptr) : NULL; - - // If the instruction pointer isn't in a module, return false. - if (minidump_module == NULL) { - return false; - } - - // Get ELF header data from the instruction pointer's module. - const uint64_t base_address = minidump_module->base_address(); - MinidumpMemoryList *memory_list = dump_->GetMemoryList(); - MinidumpMemoryRegion *memory_region = - memory_list ? - memory_list->GetMemoryRegionForAddress(base_address) : NULL; - - // The minidump does not have the correct memory region. - // This returns true because even though there is no memory data available, - // the evidence so far suggests that the instruction pointer is not at a - // bad location. - if (memory_region == NULL) { - return true; - } - - // Examine ELF headers. Depending on the architecture, the size of the - // ELF headers can differ. - LinuxArchitectureType architecture = this->ArchitectureType(); - if (architecture == LINUX_32_BIT) { - // Check if the ELF header is within the memory region and if the - // instruction pointer lies within the ELF header. - if (memory_region->GetSize() < sizeof(Elf32_Ehdr) || - instruction_ptr < base_address + sizeof(Elf32_Ehdr)) { - return false; - } - // Load 32-bit ELF header. - Elf32_Ehdr header; - this->LoadElfHeader(memory_region, base_address, &header); - // Check if the program header table is within the memory region, and - // validate that the program header entry size is correct. - if (header.e_phentsize != sizeof(Elf32_Phdr) || - memory_region->GetSize() < - header.e_phoff + - ((uint64_t) header.e_phentsize * (uint64_t) header.e_phnum)) { - return false; - } - // Load 32-bit Program Header Table. - scoped_array<Elf32_Phdr> program_headers(new Elf32_Phdr[header.e_phnum]); - this->LoadElfHeaderTable(memory_region, - base_address + header.e_phoff, - header.e_phnum, - program_headers.get()); - // Find correct program header that corresponds to the instruction pointer. - for (int i = 0; i < header.e_phnum; i++) { - const Elf32_Phdr& program_header = program_headers[i]; - // Check if instruction pointer lies within this program header's region. - if (instruction_ptr >= program_header.p_vaddr && - instruction_ptr < program_header.p_vaddr + program_header.p_memsz) { - // Return whether this program header region is executable. - return program_header.p_flags & PF_X; - } - } - } else if (architecture == LINUX_64_BIT) { - // Check if the ELF header is within the memory region and if the - // instruction pointer lies within the ELF header. - if (memory_region->GetSize() < sizeof(Elf64_Ehdr) || - instruction_ptr < base_address + sizeof(Elf64_Ehdr)) { - return false; - } - // Load 64-bit ELF header. - Elf64_Ehdr header; - this->LoadElfHeader(memory_region, base_address, &header); - // Check if the program header table is within the memory region, and - // validate that the program header entry size is correct. - if (header.e_phentsize != sizeof(Elf64_Phdr) || - memory_region->GetSize() < - header.e_phoff + - ((uint64_t) header.e_phentsize * (uint64_t) header.e_phnum)) { - return false; - } - // Load 64-bit Program Header Table. - scoped_array<Elf64_Phdr> program_headers(new Elf64_Phdr[header.e_phnum]); - this->LoadElfHeaderTable(memory_region, - base_address + header.e_phoff, - header.e_phnum, - program_headers.get()); - // Find correct program header that corresponds to the instruction pointer. - for (int i = 0; i < header.e_phnum; i++) { - const Elf64_Phdr& program_header = program_headers[i]; - // Check if instruction pointer lies within this program header's region. - if (instruction_ptr >= program_header.p_vaddr && - instruction_ptr < program_header.p_vaddr + program_header.p_memsz) { - // Return whether this program header region is executable. - return program_header.p_flags & PF_X; - } - } - } - - // The instruction pointer was not in an area identified by the ELF headers. - return false; + // Get Linux memory mapping from /proc/self/maps. Checking whether the + // region the instruction pointer is in has executable permission can tell + // whether it is in a valid code region. If there is no mapping for the + // instruction pointer, it is indicative that the instruction pointer is + // not within a module, which implies that it is outside a valid area. + MinidumpLinuxMapsList *linux_maps_list = dump_->GetLinuxMapsList(); + const MinidumpLinuxMaps *linux_maps = + linux_maps_list ? + linux_maps_list->GetLinuxMapsForAddress(instruction_ptr) : NULL; + return linux_maps ? linux_maps->IsExecutable() : false; } bool ExploitabilityLinux::BenignCrashTrigger(const MDRawExceptionStream diff --git a/src/processor/exploitability_linux.h b/src/processor/exploitability_linux.h index 2deb4991..6332b074 100644 --- a/src/processor/exploitability_linux.h +++ b/src/processor/exploitability_linux.h @@ -43,17 +43,6 @@ namespace google_breakpad { -enum LinuxArchitectureType { - // A 32-bit Linux architecture. - LINUX_32_BIT, - - // A 64-bit Linux architecture. - LINUX_64_BIT, - - // Some other architecture that is not Linux. - UNSUPPORTED_ARCHITECTURE -}; - class ExploitabilityLinux : public Exploitability { public: ExploitabilityLinux(Minidump *dump, @@ -69,46 +58,6 @@ class ExploitabilityLinux : public Exploitability { // This method checks the exception that triggered the creation of the // minidump and reports whether the exception suggests no exploitability. bool BenignCrashTrigger(const MDRawExceptionStream *raw_exception_stream); - - // Checks if the minidump architecture is 32-bit or 64-bit. - LinuxArchitectureType ArchitectureType(); - - // Loads ELF header data of the module present in the given memory - // region into the scoped pointer. - // This method takes a scoped pointer in which the ELF header data is - // loaded, the memory region containing the ELF header, and the base - // address of the ELF header. - template<typename T> - void LoadElfHeader(MinidumpMemoryRegion *memory, - uint64_t base_address, - T *header) { - for (size_t i = 0; i < sizeof(T); i++) { - uint8_t my_byte = 0; - memory->GetMemoryAtAddress(base_address + i, &my_byte); - *(reinterpret_cast<char *>(header) + i) = my_byte; - } - } - - // Loads the Program Header Table of the module present in the given - // memory region into the scoped array. - // This method takes a scoped array in which the header table data is - // loaded, the memory region containing the table, the base address of - // the program header table, and the number of entries in the table. - template<typename T> - void LoadElfHeaderTable(MinidumpMemoryRegion *memory, - uint64_t base_address, - uint16_t e_phnum, - T table[]) { - uint64_t offset = 0; - for (size_t i = 0; i < e_phnum; i++) { - T *entry = &table[i]; - for (size_t j = 0; j < sizeof(T); j++) { - uint8_t my_byte = 0; - memory->GetMemoryAtAddress(base_address + offset++, &my_byte); - *(reinterpret_cast<char *>(entry) + j) = my_byte; - } - } - } }; } // namespace google_breakpad diff --git a/src/processor/exploitability_unittest.cc b/src/processor/exploitability_unittest.cc index abb5b3ba..8a42baa0 100644 --- a/src/processor/exploitability_unittest.cc +++ b/src/processor/exploitability_unittest.cc @@ -120,13 +120,9 @@ TEST(ExploitabilityTest, TestLinuxEngine) { ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH, ExploitabilityFor("linux_jmp_to_0.dmp")); ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH, - ExploitabilityFor("linux_inside_elf_header.dmp")); - ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH, ExploitabilityFor("linux_outside_module.dmp")); ASSERT_EQ(google_breakpad::EXPLOITABILITY_NONE, ExploitabilityFor("linux_raise_sigabrt.dmp")); - ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH, - ExploitabilityFor("linux_in_module_outside_executable_part.dmp")); ASSERT_EQ(google_breakpad::EXPLOITABILITY_INTERESTING, ExploitabilityFor("linux_inside_module_exe_region1.dmp")); ASSERT_EQ(google_breakpad::EXPLOITABILITY_INTERESTING, diff --git a/src/processor/minidump.cc b/src/processor/minidump.cc index 08f64652..1a997d6a 100644 --- a/src/processor/minidump.cc +++ b/src/processor/minidump.cc @@ -55,6 +55,7 @@ #include <iostream> #include <limits> #include <map> +#include <sstream> #include <vector> #include "processor/range_map-inl.h" @@ -71,6 +72,7 @@ namespace google_breakpad { using std::istream; using std::ifstream; using std::numeric_limits; +using std::stringstream; using std::vector; // Returns true iff |context_size| matches exactly one of the sizes of the @@ -3972,6 +3974,142 @@ void MinidumpMemoryInfoList::Print() { } } +// +// MinidumpLinuxMaps +// + +MinidumpLinuxMaps::MinidumpLinuxMaps(Minidump *minidump) + : MinidumpObject(minidump) { +} + +void MinidumpLinuxMaps::Print() { + if (!valid_) { + BPLOG(ERROR) << "MinidumpLinuxMaps cannot print invalid data"; + return; + } + std::cout << GetPathname() << std::endl; +} + +// +// MinidumpLinuxMapsList +// + +MinidumpLinuxMapsList::MinidumpLinuxMapsList(Minidump *minidump) + : MinidumpStream(minidump), + maps_(NULL), + maps_count_(0) { +} + +MinidumpLinuxMapsList::~MinidumpLinuxMapsList() { + for (unsigned int i = 0; i < maps_->size(); i++) { + delete (*maps_)[i]; + } + delete maps_; +} + +const MinidumpLinuxMaps *MinidumpLinuxMapsList::GetLinuxMapsForAddress( + uint64_t address) const { + if (!valid_) { + BPLOG(ERROR) << "Invalid MinidumpLinuxMapsList for GetLinuxMapsForAddress"; + return NULL; + } + + // Search every memory mapping. + for (unsigned int index = 0; index < maps_count_; index++) { + // Check if address is within bounds of the current memory region. + if ((*maps_)[index]->GetBase() <= address && + (*maps_)[index]->GetBase() + (*maps_)[index]->GetSize() > address) { + return (*maps_)[index]; + } + } + + // No mapping encloses the memory address. + BPLOG(ERROR) << "MinidumpLinuxMapsList has no mapping at " + << HexString(address); + return NULL; +} + +const MinidumpLinuxMaps *MinidumpLinuxMapsList::GetLinuxMapsAtIndex( + unsigned int index) const { + if (!valid_) { + BPLOG(ERROR) << "Invalid MinidumpLinuxMapsList for GetLinuxMapsAtIndex"; + return NULL; + } + + // Index out of bounds. + if (index >= maps_count_) { + BPLOG(ERROR) << "MinidumpLinuxMapsList index of out range: " + << index + << "/" + << maps_count_; + return NULL; + } + return (*maps_)[index]; +} + +bool MinidumpLinuxMapsList::Read(uint32_t expected_size) { + // Invalidate cached data. + delete maps_; + maps_ = NULL; + maps_count_ = 0; + + valid_ = false; + + // Load and check expected stream length. + uint32_t length = 0; + if (!minidump_->SeekToStreamType(MD_LINUX_MAPS, &length)) { + BPLOG(ERROR) << "MinidumpLinuxMapsList stream type not found"; + return false; + } + if (expected_size != length) { + BPLOG(ERROR) << "MinidumpLinuxMapsList size mismatch: " + << expected_size + << " != " + << length; + return false; + } + + // Create a vector to read stream data. The vector needs to have + // at least enough capacity to read all the data. + vector<char> mapping_bytes(length); + if (!minidump_->ReadBytes(&mapping_bytes[0], length)) { + BPLOG(ERROR) << "MinidumpLinuxMapsList failed to read bytes"; + return false; + } + string map_string(mapping_bytes.begin(), mapping_bytes.end()); + vector<MappedMemoryRegion> all_regions; + + // Parse string into mapping data. + if (!ParseProcMaps(map_string, &all_regions)) { + return false; + } + + scoped_ptr<MinidumpLinuxMappings> maps(new MinidumpLinuxMappings()); + + // Push mapping data into wrapper classes. + for (size_t i = 0; i < all_regions.size(); i++) { + scoped_ptr<MinidumpLinuxMaps> ele(new MinidumpLinuxMaps(minidump_)); + ele->region_ = all_regions[i]; + ele->valid_ = true; + maps->push_back(ele.release()); + } + + // Set instance variables. + maps_ = maps.release(); + maps_count_ = maps_->size(); + valid_ = true; + return true; +} + +void MinidumpLinuxMapsList::Print() { + if (!valid_) { + BPLOG(ERROR) << "MinidumpLinuxMapsList cannot print valid data"; + return; + } + for (size_t i = 0; i < maps_->size(); i++) { + (*maps_)[i]->Print(); + } +} // // Minidump @@ -4292,6 +4430,11 @@ MinidumpMemoryInfoList* Minidump::GetMemoryInfoList() { return GetStream(&memory_info_list); } +MinidumpLinuxMapsList *Minidump::GetLinuxMapsList() { + MinidumpLinuxMapsList *linux_maps_list; + return GetStream(&linux_maps_list); +} + static const char* get_stream_name(uint32_t stream_type) { switch (stream_type) { case MD_UNUSED_STREAM: diff --git a/src/processor/proc_maps_linux.cc b/src/processor/proc_maps_linux.cc new file mode 100644 index 00000000..72947286 --- /dev/null +++ b/src/processor/proc_maps_linux.cc @@ -0,0 +1,103 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "google_breakpad/processor/proc_maps_linux.h" + +#include <fcntl.h> +#include <stdio.h> + +#if defined(OS_LINUX) || defined(OS_ANDROID) +#include <inttypes.h> +#endif + +#include "processor/logging.h" + +#if defined(OS_ANDROID) && !defined(__LP64__) +// In 32-bit mode, Bionic's inttypes.h defines PRI/SCNxPTR as an +// unsigned long int, which is incompatible with Bionic's stdint.h +// defining uintptr_t as an unsigned int: +// https://code.google.com/p/android/issues/detail?id=57218 +#undef SCNxPTR +#define SCNxPTR "x" +#endif + +namespace google_breakpad { + +bool ParseProcMaps(const std::string& input, + std::vector<MappedMemoryRegion>* regions_out) { + std::vector<MappedMemoryRegion> regions; + + // This isn't async safe nor terribly efficient, but it doesn't need to be at + // this point in time. + + // Split the string by newlines. + std::vector<std::string> lines; + std::string line = ""; + for (size_t i = 0; i < input.size(); i++) { + if (input[i] != '\n' && input[i] != '\r') { + line.push_back(input[i]); + } else if (line.size() > 0) { + lines.push_back(line); + line.clear(); + } + } + if (line.size() > 0) { + BPLOG(ERROR) << "Input doesn't end in newline"; + return false; + } + + for (size_t i = 0; i < lines.size(); ++i) { + MappedMemoryRegion region; + const char* line = lines[i].c_str(); + char permissions[5] = {'\0'}; // Ensure NUL-terminated string. + int path_index = 0; + + // Sample format from man 5 proc: + // + // address perms offset dev inode pathname + // 08048000-08056000 r-xp 00000000 03:0c 64593 /usr/sbin/gpm + // + // The final %n term captures the offset in the input string, which is used + // to determine the path name. It *does not* increment the return value. + // Refer to man 3 sscanf for details. + if (sscanf(line, "%" SCNxPTR "-%" SCNxPTR " %4c %lx %hhx:%hhx %ld %n", + ®ion.start, ®ion.end, permissions, ®ion.offset, + ®ion.major_device, ®ion.minor_device, ®ion.inode, + &path_index) < 7) { + BPLOG(ERROR) << "sscanf failed for line: " << line; + return false; + } + + region.permissions = 0; + + if (permissions[0] == 'r') + region.permissions |= MappedMemoryRegion::READ; + else if (permissions[0] != '-') + return false; + + if (permissions[1] == 'w') + region.permissions |= MappedMemoryRegion::WRITE; + else if (permissions[1] != '-') + return false; + + if (permissions[2] == 'x') + region.permissions |= MappedMemoryRegion::EXECUTE; + else if (permissions[2] != '-') + return false; + + if (permissions[3] == 'p') + region.permissions |= MappedMemoryRegion::PRIVATE; + else if (permissions[3] != 's' && permissions[3] != 'S') // Shared memory. + return false; + + // Pushing then assigning saves us a string copy. + regions.push_back(region); + regions.back().path.assign(line + path_index); + } + + regions_out->swap(regions); + return true; +} + +} // namespace google_breakpad diff --git a/src/processor/proc_maps_linux_unittest.cc b/src/processor/proc_maps_linux_unittest.cc new file mode 100644 index 00000000..1ff4b031 --- /dev/null +++ b/src/processor/proc_maps_linux_unittest.cc @@ -0,0 +1,250 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "breakpad_googletest_includes.h" +#include "google_breakpad/processor/proc_maps_linux.h" + +namespace { + +TEST(ProcMapsTest, Empty) { + std::vector<google_breakpad::MappedMemoryRegion> regions; + EXPECT_TRUE(ParseProcMaps("", ®ions)); + EXPECT_EQ(0u, regions.size()); +} + +TEST(ProcMapsTest, NoSpaces) { + static const char kNoSpaces[] = + "00400000-0040b000 r-xp 00002200 fc:00 794418 /bin/cat\n"; + + std::vector<google_breakpad::MappedMemoryRegion> regions; + ASSERT_TRUE(ParseProcMaps(kNoSpaces, ®ions)); + ASSERT_EQ(1u, regions.size()); + + EXPECT_EQ(0x00400000u, regions[0].start); + EXPECT_EQ(0x0040b000u, regions[0].end); + EXPECT_EQ(0x00002200u, regions[0].offset); + EXPECT_EQ("/bin/cat", regions[0].path); +} + +TEST(ProcMapsTest, Spaces) { + static const char kSpaces[] = + "00400000-0040b000 r-xp 00002200 fc:00 794418 /bin/space cat\n"; + + std::vector<google_breakpad::MappedMemoryRegion> regions; + ASSERT_TRUE(ParseProcMaps(kSpaces, ®ions)); + ASSERT_EQ(1u, regions.size()); + + EXPECT_EQ(0x00400000u, regions[0].start); + EXPECT_EQ(0x0040b000u, regions[0].end); + EXPECT_EQ(0x00002200u, regions[0].offset); + EXPECT_EQ("/bin/space cat", regions[0].path); +} + +TEST(ProcMapsTest, NoNewline) { + static const char kNoSpaces[] = + "00400000-0040b000 r-xp 00002200 fc:00 794418 /bin/cat"; + + std::vector<google_breakpad::MappedMemoryRegion> regions; + ASSERT_FALSE(ParseProcMaps(kNoSpaces, ®ions)); +} + +TEST(ProcMapsTest, NoPath) { + static const char kNoPath[] = + "00400000-0040b000 rw-p 00000000 00:00 0 \n"; + + std::vector<google_breakpad::MappedMemoryRegion> regions; + ASSERT_TRUE(ParseProcMaps(kNoPath, ®ions)); + ASSERT_EQ(1u, regions.size()); + + EXPECT_EQ(0x00400000u, regions[0].start); + EXPECT_EQ(0x0040b000u, regions[0].end); + EXPECT_EQ(0x00000000u, regions[0].offset); + EXPECT_EQ("", regions[0].path); +} + +TEST(ProcMapsTest, Heap) { + static const char kHeap[] = + "022ac000-022cd000 rw-p 00000000 00:00 0 [heap]\n"; + + std::vector<google_breakpad::MappedMemoryRegion> regions; + ASSERT_TRUE(ParseProcMaps(kHeap, ®ions)); + ASSERT_EQ(1u, regions.size()); + + EXPECT_EQ(0x022ac000u, regions[0].start); + EXPECT_EQ(0x022cd000u, regions[0].end); + EXPECT_EQ(0x00000000u, regions[0].offset); + EXPECT_EQ("[heap]", regions[0].path); +} + +#if defined(ARCH_CPU_32_BITS) +TEST(ProcMapsTest, Stack32) { + static const char kStack[] = + "beb04000-beb25000 rw-p 00000000 00:00 0 [stack]\n"; + + std::vector<google_breakpad::MappedMemoryRegion> regions; + ASSERT_TRUE(ParseProcMaps(kStack, ®ions)); + ASSERT_EQ(1u, regions.size()); + + EXPECT_EQ(0xbeb04000u, regions[0].start); + EXPECT_EQ(0xbeb25000u, regions[0].end); + EXPECT_EQ(0x00000000u, regions[0].offset); + EXPECT_EQ("[stack]", regions[0].path); +} +#elif defined(ARCH_CPU_64_BITS) +TEST(ProcMapsTest, Stack64) { + static const char kStack[] = + "7fff69c5b000-7fff69c7d000 rw-p 00000000 00:00 0 [stack]\n"; + + std::vector<google_breakpad::MappedMemoryRegion> regions; + ASSERT_TRUE(ParseProcMaps(kStack, ®ions)); + ASSERT_EQ(1u, regions.size()); + + EXPECT_EQ(0x7fff69c5b000u, regions[0].start); + EXPECT_EQ(0x7fff69c7d000u, regions[0].end); + EXPECT_EQ(0x00000000u, regions[0].offset); + EXPECT_EQ("[stack]", regions[0].path); +} +#endif + +TEST(ProcMapsTest, Multiple) { + static const char kMultiple[] = + "00400000-0040b000 r-xp 00000000 fc:00 794418 /bin/cat\n" + "0060a000-0060b000 r--p 0000a000 fc:00 794418 /bin/cat\n" + "0060b000-0060c000 rw-p 0000b000 fc:00 794418 /bin/cat\n"; + + std::vector<google_breakpad::MappedMemoryRegion> regions; + ASSERT_TRUE(ParseProcMaps(kMultiple, ®ions)); + ASSERT_EQ(3u, regions.size()); + + EXPECT_EQ(0x00400000u, regions[0].start); + EXPECT_EQ(0x0040b000u, regions[0].end); + EXPECT_EQ(0x00000000u, regions[0].offset); + EXPECT_EQ("/bin/cat", regions[0].path); + + EXPECT_EQ(0x0060a000u, regions[1].start); + EXPECT_EQ(0x0060b000u, regions[1].end); + EXPECT_EQ(0x0000a000u, regions[1].offset); + EXPECT_EQ("/bin/cat", regions[1].path); + + EXPECT_EQ(0x0060b000u, regions[2].start); + EXPECT_EQ(0x0060c000u, regions[2].end); + EXPECT_EQ(0x0000b000u, regions[2].offset); + EXPECT_EQ("/bin/cat", regions[2].path); +} + +TEST(ProcMapsTest, Permissions) { + static struct { + const char* input; + uint8_t permissions; + } kTestCases[] = { + {"00400000-0040b000 ---s 00000000 fc:00 794418 /bin/cat\n", 0}, + {"00400000-0040b000 ---S 00000000 fc:00 794418 /bin/cat\n", 0}, + {"00400000-0040b000 r--s 00000000 fc:00 794418 /bin/cat\n", + google_breakpad::MappedMemoryRegion::READ}, + {"00400000-0040b000 -w-s 00000000 fc:00 794418 /bin/cat\n", + google_breakpad::MappedMemoryRegion::WRITE}, + {"00400000-0040b000 --xs 00000000 fc:00 794418 /bin/cat\n", + google_breakpad::MappedMemoryRegion::EXECUTE}, + {"00400000-0040b000 rwxs 00000000 fc:00 794418 /bin/cat\n", + google_breakpad::MappedMemoryRegion::READ + | google_breakpad::MappedMemoryRegion::WRITE + | google_breakpad::MappedMemoryRegion::EXECUTE}, + {"00400000-0040b000 ---p 00000000 fc:00 794418 /bin/cat\n", + google_breakpad::MappedMemoryRegion::PRIVATE}, + {"00400000-0040b000 r--p 00000000 fc:00 794418 /bin/cat\n", + google_breakpad::MappedMemoryRegion::READ + | google_breakpad::MappedMemoryRegion::PRIVATE}, + {"00400000-0040b000 -w-p 00000000 fc:00 794418 /bin/cat\n", + google_breakpad::MappedMemoryRegion::WRITE + | google_breakpad::MappedMemoryRegion::PRIVATE}, + {"00400000-0040b000 --xp 00000000 fc:00 794418 /bin/cat\n", + google_breakpad::MappedMemoryRegion::EXECUTE + | google_breakpad::MappedMemoryRegion::PRIVATE}, + {"00400000-0040b000 rwxp 00000000 fc:00 794418 /bin/cat\n", + google_breakpad::MappedMemoryRegion::READ + | google_breakpad::MappedMemoryRegion::WRITE + | google_breakpad::MappedMemoryRegion::EXECUTE + | google_breakpad::MappedMemoryRegion::PRIVATE}, + }; + + for (size_t i = 0; i < sizeof(kTestCases) / sizeof(kTestCases[0]); ++i) { + std::vector<google_breakpad::MappedMemoryRegion> regions; + EXPECT_TRUE(ParseProcMaps(kTestCases[i].input, ®ions)); + EXPECT_EQ(1u, regions.size()); + if (regions.empty()) + continue; + EXPECT_EQ(kTestCases[i].permissions, regions[0].permissions); + } +} + +TEST(ProcMapsTest, MissingFields) { + static const char* kTestCases[] = { + "00400000\n", // Missing end + beyond. + "00400000-0040b000\n", // Missing perms + beyond. + "00400000-0040b000 r-xp\n", // Missing offset + beyond. + "00400000-0040b000 r-xp 00000000\n", // Missing device + beyond. + "00400000-0040b000 r-xp 00000000 fc:00\n", // Missing inode + beyond. + "00400000-0040b000 00000000 fc:00 794418 /bin/cat\n", // Missing perms. + "00400000-0040b000 r-xp fc:00 794418 /bin/cat\n", // Missing offset. + "00400000-0040b000 r-xp 00000000 fc:00 /bin/cat\n", // Missing inode. + "00400000 r-xp 00000000 fc:00 794418 /bin/cat\n", // Missing end. + "-0040b000 r-xp 00000000 fc:00 794418 /bin/cat\n", // Missing start. + "00400000-0040b000 r-xp 00000000 794418 /bin/cat\n", // Missing device. + }; + + for (size_t i = 0; i < sizeof(kTestCases) / sizeof(kTestCases[0]); ++i) { + std::vector<google_breakpad::MappedMemoryRegion> regions; + EXPECT_FALSE(ParseProcMaps(kTestCases[i], ®ions)); + } +} + +TEST(ProcMapsTest, InvalidInput) { + static const char* kTestCases[] = { + "thisisal-0040b000 rwxp 00000000 fc:00 794418 /bin/cat\n", + "0040000d-linvalid rwxp 00000000 fc:00 794418 /bin/cat\n", + "00400000-0040b000 inpu 00000000 fc:00 794418 /bin/cat\n", + "00400000-0040b000 rwxp tforproc fc:00 794418 /bin/cat\n", + "00400000-0040b000 rwxp 00000000 ma:ps 794418 /bin/cat\n", + "00400000-0040b000 rwxp 00000000 fc:00 parse! /bin/cat\n", + }; + + for (size_t i = 0; i < sizeof(kTestCases) / sizeof(kTestCases[0]); ++i) { + std::vector<google_breakpad::MappedMemoryRegion> regions; + EXPECT_FALSE(ParseProcMaps(kTestCases[i], ®ions)); + } +} + +TEST(ProcMapsTest, ParseProcMapsEmptyString) { + std::vector<google_breakpad::MappedMemoryRegion> regions; + EXPECT_TRUE(ParseProcMaps("", ®ions)); + EXPECT_EQ(0ULL, regions.size()); +} + +// Testing a couple of remotely possible weird things in the input: +// - Line ending with \r\n or \n\r. +// - File name contains quotes. +// - File name has whitespaces. +TEST(ProcMapsTest, ParseProcMapsWeirdCorrectInput) { + std::vector<google_breakpad::MappedMemoryRegion> regions; + const std::string kContents = + "00400000-0040b000 r-xp 00000000 fc:00 2106562 " + " /bin/cat\r\n" + "7f53b7dad000-7f53b7f62000 r-xp 00000000 fc:00 263011 " + " /lib/x86_64-linux-gnu/libc-2.15.so\n\r" + "7f53b816d000-7f53b818f000 r-xp 00000000 fc:00 264284 " + " /lib/x86_64-linux-gnu/ld-2.15.so\n" + "7fff9c7ff000-7fff9c800000 r-xp 00000000 00:00 0 " + " \"vd so\"\n" + "ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 " + " [vsys call]\n"; + EXPECT_TRUE(ParseProcMaps(kContents, ®ions)); + EXPECT_EQ(5ULL, regions.size()); + EXPECT_EQ("/bin/cat", regions[0].path); + EXPECT_EQ("/lib/x86_64-linux-gnu/libc-2.15.so", regions[1].path); + EXPECT_EQ("/lib/x86_64-linux-gnu/ld-2.15.so", regions[2].path); + EXPECT_EQ("\"vd so\"", regions[3].path); + EXPECT_EQ("[vsys call]", regions[4].path); +} + +} // namespace diff --git a/src/processor/testdata/linux_in_module_outside_executable_part.dmp b/src/processor/testdata/linux_in_module_outside_executable_part.dmp Binary files differdeleted file mode 100644 index 23fcc505..00000000 --- a/src/processor/testdata/linux_in_module_outside_executable_part.dmp +++ /dev/null diff --git a/src/processor/testdata/linux_inside_elf_header.dmp b/src/processor/testdata/linux_inside_elf_header.dmp Binary files differdeleted file mode 100644 index 96b6acef..00000000 --- a/src/processor/testdata/linux_inside_elf_header.dmp +++ /dev/null |