mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-16 16:13:52 +00:00
When the /proc/pid/maps file is not read atomically and the target process is actively mapping memory, entries can be read multiple times or missed entirely. This change makes MemoryMap read the whole contents of the maps file before attempting to parse it as well as check for duplication/overlap errors, retrying on failure. This change also adds ptrace attachements to unit tests to reflect actual intended usage. Bug: crashpad:30 Change-Id: Ie8549548e25c47baa418ee7439d82743f84ff41e Reviewed-on: https://chromium-review.googlesource.com/491950 Reviewed-by: Joshua Peraza <jperaza@chromium.org> Reviewed-by: Mark Mentovai <mark@chromium.org> Commit-Queue: Joshua Peraza <jperaza@chromium.org>
259 lines
8.5 KiB
C++
259 lines
8.5 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 <linux/kdev_t.h>
|
|
#include <stdio.h>
|
|
#include <string.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 base::StringPiece& 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;
|
|
}
|
|
|
|
// TODO(jperaza): set bitness properly
|
|
#if defined(ARCH_CPU_64_BITS)
|
|
const bool is_64_bit = true;
|
|
#else
|
|
const 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;
|
|
}
|
|
|
|
int major, minor;
|
|
if (maps_file_reader->GetDelim(' ', &field) !=
|
|
DelimitedFileReader::Result::kSuccess ||
|
|
(field.pop_back(), field.size() != 5) ||
|
|
!HexStringToNumber(field.substr(0, 2), &major) ||
|
|
!HexStringToNumber(field.substr(3, 2), &minor)) {
|
|
LOG(ERROR) << "format error";
|
|
return ParseResult::kError;
|
|
}
|
|
mapping.device = MKDEV(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::Initialize(pid_t pid) {
|
|
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", pid);
|
|
if (!LoggingReadEntireFile(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;
|
|
}
|
|
|
|
} // namespace crashpad
|