crashpad/util/linux/memory_map.cc
Joshua Peraza 52ff1accbb 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>
2018-07-26 15:33:15 +00:00

335 lines
11 KiB
C++

// Copyright 2017 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 "util/linux/memory_map.h"
#include <stdio.h>
#include <string.h>
#include <sys/sysmacros.h>
#include "base/files/file_path.h"
#include "base/logging.h"
#include "build/build_config.h"
#include "util/file/delimited_file_reader.h"
#include "util/file/file_io.h"
#include "util/file/string_file.h"
#include "util/stdlib/string_number_conversion.h"
namespace crashpad {
namespace {
// This function is used in this file specfically for signed or unsigned longs.
// longs are typically either int or int64 sized, but pointers to longs are not
// automatically coerced to pointers to ints when they are the same size.
// Simply adding a StringToNumber for longs doesn't work since sometimes long
// and int64_t are actually the same type, resulting in a redefinition error.
template <typename Type>
bool LocalStringToNumber(const std::string& string, Type* number) {
static_assert(sizeof(Type) == sizeof(int) || sizeof(Type) == sizeof(int64_t),
"Unexpected Type size");
if (sizeof(Type) == sizeof(int)) {
return std::numeric_limits<Type>::is_signed
? StringToNumber(string, reinterpret_cast<int*>(number))
: StringToNumber(string,
reinterpret_cast<unsigned int*>(number));
} else {
return std::numeric_limits<Type>::is_signed
? StringToNumber(string, reinterpret_cast<int64_t*>(number))
: StringToNumber(string, reinterpret_cast<uint64_t*>(number));
}
}
template <typename Type>
bool HexStringToNumber(const std::string& string, Type* number) {
return LocalStringToNumber("0x" + string, number);
}
// The result from parsing a line from the maps file.
enum class ParseResult {
// A line was successfully parsed.
kSuccess = 0,
// The end of the file was successfully reached.
kEndOfFile,
// There was an error in the file, likely because it was read non-atmoically.
// We should try to read it again.
kRetry,
// An error with a message logged.
kError
};
// Reads a line from a maps file being read by maps_file_reader and extends
// mappings with a new MemoryMap::Mapping describing the line.
ParseResult ParseMapsLine(DelimitedFileReader* maps_file_reader,
std::vector<MemoryMap::Mapping>* mappings) {
std::string field;
LinuxVMAddress start_address;
switch (maps_file_reader->GetDelim('-', &field)) {
case DelimitedFileReader::Result::kError:
return ParseResult::kError;
case DelimitedFileReader::Result::kEndOfFile:
return ParseResult::kEndOfFile;
case DelimitedFileReader::Result::kSuccess:
field.pop_back();
if (!HexStringToNumber(field, &start_address)) {
LOG(ERROR) << "format error";
return ParseResult::kError;
}
if (!mappings->empty() && start_address < mappings->back().range.End()) {
return ParseResult::kRetry;
}
}
LinuxVMAddress end_address;
if (maps_file_reader->GetDelim(' ', &field) !=
DelimitedFileReader::Result::kSuccess ||
(field.pop_back(), !HexStringToNumber(field, &end_address))) {
LOG(ERROR) << "format error";
return ParseResult::kError;
}
if (end_address < start_address) {
LOG(ERROR) << "format error";
return ParseResult::kError;
}
// Skip zero-length mappings.
if (end_address == start_address) {
std::string rest_of_line;
if (maps_file_reader->GetLine(&rest_of_line) !=
DelimitedFileReader::Result::kSuccess) {
LOG(ERROR) << "format error";
return ParseResult::kError;
}
return ParseResult::kSuccess;
}
// TODO(jperaza): set bitness properly
#if defined(ARCH_CPU_64_BITS)
constexpr bool is_64_bit = true;
#else
constexpr bool is_64_bit = false;
#endif
MemoryMap::Mapping mapping;
mapping.range.SetRange(is_64_bit, start_address, end_address - start_address);
if (maps_file_reader->GetDelim(' ', &field) !=
DelimitedFileReader::Result::kSuccess ||
(field.pop_back(), field.size() != 4)) {
LOG(ERROR) << "format error";
return ParseResult::kError;
}
#define SET_FIELD(actual_c, outval, true_chars, false_chars) \
do { \
if (strchr(true_chars, actual_c)) { \
*outval = true; \
} else if (strchr(false_chars, actual_c)) { \
*outval = false; \
} else { \
LOG(ERROR) << "format error"; \
return ParseResult::kError; \
} \
} while (false)
SET_FIELD(field[0], &mapping.readable, "r", "-");
SET_FIELD(field[1], &mapping.writable, "w", "-");
SET_FIELD(field[2], &mapping.executable, "x", "-");
SET_FIELD(field[3], &mapping.shareable, "sS", "p");
#undef SET_FIELD
if (maps_file_reader->GetDelim(' ', &field) !=
DelimitedFileReader::Result::kSuccess ||
(field.pop_back(), !HexStringToNumber(field, &mapping.offset))) {
LOG(ERROR) << "format error";
return ParseResult::kError;
}
uint32_t major;
if (maps_file_reader->GetDelim(':', &field) !=
DelimitedFileReader::Result::kSuccess ||
(field.pop_back(), field.size()) < 2 ||
!HexStringToNumber(field, &major)) {
LOG(ERROR) << "format error";
return ParseResult::kError;
}
uint32_t minor;
if (maps_file_reader->GetDelim(' ', &field) !=
DelimitedFileReader::Result::kSuccess ||
(field.pop_back(), field.size()) < 2 ||
!HexStringToNumber(field, &minor)) {
LOG(ERROR) << "format error";
return ParseResult::kError;
}
mapping.device = makedev(major, minor);
if (maps_file_reader->GetDelim(' ', &field) !=
DelimitedFileReader::Result::kSuccess ||
(field.pop_back(), !LocalStringToNumber(field, &mapping.inode))) {
LOG(ERROR) << "format error";
return ParseResult::kError;
}
if (maps_file_reader->GetDelim('\n', &field) !=
DelimitedFileReader::Result::kSuccess) {
LOG(ERROR) << "format error";
return ParseResult::kError;
}
if (field.back() != '\n') {
LOG(ERROR) << "format error";
return ParseResult::kError;
}
field.pop_back();
mappings->push_back(mapping);
size_t path_start = field.find_first_not_of(' ');
if (path_start != std::string::npos) {
mappings->back().name = field.substr(path_start);
}
return ParseResult::kSuccess;
}
} // namespace
MemoryMap::Mapping::Mapping()
: name(),
range(false, 0, 0),
offset(0),
device(0),
inode(0),
readable(false),
writable(false),
executable(false),
shareable(false) {}
MemoryMap::MemoryMap() : mappings_(), initialized_() {}
MemoryMap::~MemoryMap() {}
bool MemoryMap::Mapping::Equals(const Mapping& other) const {
DCHECK_EQ(range.Is64Bit(), other.range.Is64Bit());
return range.Base() == other.range.Base() &&
range.Size() == other.range.Size() && name == other.name &&
offset == other.offset && device == other.device &&
inode == other.inode && readable == other.readable &&
writable == other.writable && executable == other.executable &&
shareable == other.shareable;
}
bool MemoryMap::Initialize(PtraceConnection* connection) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
// If the maps file is not read atomically, entries can be read multiple times
// or missed entirely. The kernel reads entries from this file into a page
// sized buffer, so maps files larger than a page require multiple reads.
// Attempt to reduce the time between reads by reading the entire file into a
// StringFile before attempting to parse it. If ParseMapsLine detects
// duplicate, overlapping, or out-of-order entries, it will trigger restarting
// the read up to |attempts| times.
int attempts = 3;
do {
std::string contents;
char path[32];
snprintf(path, sizeof(path), "/proc/%d/maps", connection->GetProcessID());
if (!connection->ReadFileContents(base::FilePath(path), &contents)) {
return false;
}
StringFile maps_file;
maps_file.SetString(contents);
DelimitedFileReader maps_file_reader(&maps_file);
ParseResult result;
while ((result = ParseMapsLine(&maps_file_reader, &mappings_)) ==
ParseResult::kSuccess) {
}
if (result == ParseResult::kEndOfFile) {
INITIALIZATION_STATE_SET_VALID(initialized_);
return true;
}
if (result == ParseResult::kError) {
return false;
}
DCHECK(result == ParseResult::kRetry);
} while (--attempts > 0);
LOG(ERROR) << "retry count exceeded";
return false;
}
const MemoryMap::Mapping* MemoryMap::FindMapping(LinuxVMAddress address) const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
for (const auto& mapping : mappings_) {
if (mapping.range.Base() <= address && mapping.range.End() > address) {
return &mapping;
}
}
return nullptr;
}
const MemoryMap::Mapping* MemoryMap::FindMappingWithName(
const std::string& name) const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
for (const auto& mapping : mappings_) {
if (mapping.name == name) {
return &mapping;
}
}
return nullptr;
}
std::vector<const MemoryMap::Mapping*> MemoryMap::FindFilePossibleMmapStarts(
const Mapping& mapping) const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
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) {
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*>();
}
for (const auto& candidate : mappings_) {
if (candidate.device == mapping.device &&
candidate.inode == mapping.inode &&
candidate.offset == 0) {
possible_starts.push_back(&candidate);
}
if (mapping.Equals(candidate)) {
return possible_starts;
}
}
LOG(ERROR) << "mapping not found";
return std::vector<const Mapping*>();
}
} // namespace crashpad