linux: Add PtraceConnection::ReadFileContents

Some files, such as /proc/[pid]/maps, may not be accessible to the
handler. This enables the handler access to the contents of those files
via the broker.

This change reads maps and auxv using ReadFileContents.

Bug: crashpad:30
Change-Id: Ia19b498bae473c616ea794ab51c3f22afd5795be
Reviewed-on: https://chromium-review.googlesource.com/989406
Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
Joshua Peraza 2018-04-03 15:01:44 -07:00
parent 246ecc6686
commit d108fd04a5
20 changed files with 424 additions and 115 deletions

View File

@ -39,6 +39,7 @@
#elif defined(OS_LINUX) || defined(OS_ANDROID)
#include "test/linux/fake_ptrace_connection.h"
#include "util/linux/auxiliary_vector.h"
#include "util/linux/memory_map.h"
@ -62,7 +63,6 @@ namespace {
void LocateExecutable(ProcessType process,
ProcessMemory* memory,
bool is_64_bit,
VMAddress* elf_address) {
uintptr_t debug_address;
zx_status_t status = zx_object_get_property(process,
@ -90,18 +90,17 @@ void LocateExecutable(ProcessType process,
#elif defined(OS_LINUX) || defined(OS_ANDROID)
void LocateExecutable(ProcessType process,
void LocateExecutable(PtraceConnection* connection,
ProcessMemory* memory,
bool is_64_bit,
VMAddress* elf_address) {
AuxiliaryVector aux;
ASSERT_TRUE(aux.Initialize(process, is_64_bit));
ASSERT_TRUE(aux.Initialize(connection));
VMAddress phdrs;
ASSERT_TRUE(aux.GetValue(AT_PHDR, &phdrs));
MemoryMap memory_map;
ASSERT_TRUE(memory_map.Initialize(process));
ASSERT_TRUE(memory_map.Initialize(connection));
const MemoryMap::Mapping* phdr_mapping = memory_map.FindMapping(phdrs);
ASSERT_TRUE(phdr_mapping);
const MemoryMap::Mapping* exe_mapping =
@ -139,7 +138,13 @@ void ReadThisExecutableInTarget(ProcessType process,
ASSERT_TRUE(range.Initialize(&memory, am_64_bit));
VMAddress elf_address;
LocateExecutable(process, &memory, am_64_bit, &elf_address);
#if defined(OS_LINUX) || defined(OS_ANDROID)
FakePtraceConnection connection;
ASSERT_TRUE(connection.Initialize(process));
LocateExecutable(&connection, &memory, &elf_address);
#elif defined(OS_FUCHSIA)
LocateExecutable(process, &memory, &elf_address);
#endif
ASSERT_NO_FATAL_FAILURE();
ElfImageReader reader;

View File

@ -27,9 +27,11 @@
#include "build/build_config.h"
#include "gtest/gtest.h"
#include "snapshot/elf/elf_image_reader.h"
#include "test/linux/fake_ptrace_connection.h"
#include "test/multiprocess.h"
#include "util/linux/address_types.h"
#include "util/linux/auxiliary_vector.h"
#include "util/linux/direct_ptrace_connection.h"
#include "util/linux/memory_map.h"
#include "util/process/process_memory_linux.h"
#include "util/process/process_memory_range.h"
@ -57,18 +59,18 @@ int AndroidRuntimeAPI() {
}
#endif // OS_ANDROID
void TestAgainstTarget(pid_t pid, bool is_64_bit) {
void TestAgainstTarget(PtraceConnection* connection) {
// Use ElfImageReader on the main executable which can tell us the debug
// address. glibc declares the symbol _r_debug in link.h which we can use to
// get the address, but Android does not.
AuxiliaryVector aux;
ASSERT_TRUE(aux.Initialize(pid, is_64_bit));
ASSERT_TRUE(aux.Initialize(connection));
LinuxVMAddress phdrs;
ASSERT_TRUE(aux.GetValue(AT_PHDR, &phdrs));
MemoryMap mappings;
ASSERT_TRUE(mappings.Initialize(pid));
ASSERT_TRUE(mappings.Initialize(connection));
const MemoryMap::Mapping* phdr_mapping = mappings.FindMapping(phdrs);
ASSERT_TRUE(phdr_mapping);
@ -77,9 +79,9 @@ void TestAgainstTarget(pid_t pid, bool is_64_bit) {
LinuxVMAddress elf_address = exe_mapping->range.Base();
ProcessMemoryLinux memory;
ASSERT_TRUE(memory.Initialize(pid));
ASSERT_TRUE(memory.Initialize(connection->GetProcessID()));
ProcessMemoryRange range;
ASSERT_TRUE(range.Initialize(&memory, is_64_bit));
ASSERT_TRUE(range.Initialize(&memory, connection->Is64Bit()));
ElfImageReader exe_reader;
ASSERT_TRUE(exe_reader.Initialize(range, elf_address));
@ -107,7 +109,7 @@ void TestAgainstTarget(pid_t pid, bool is_64_bit) {
// glibc's loader does not set the name for the executable.
EXPECT_TRUE(debug.Executable()->name.empty());
CheckedLinuxAddressRange exe_range(
is_64_bit, exe_reader.Address(), exe_reader.Size());
connection->Is64Bit(), exe_reader.Address(), exe_reader.Size());
EXPECT_TRUE(exe_range.ContainsValue(debug.Executable()->dynamic_array));
#endif // OS_ANDROID
@ -179,19 +181,16 @@ void TestAgainstTarget(pid_t pid, bool is_64_bit) {
}
CheckedLinuxAddressRange module_range(
is_64_bit, module_reader.Address(), module_reader.Size());
connection->Is64Bit(), module_reader.Address(), module_reader.Size());
EXPECT_TRUE(module_range.ContainsValue(module.dynamic_array));
}
}
TEST(DebugRendezvous, Self) {
#if defined(ARCH_CPU_64_BITS)
constexpr bool is_64_bit = true;
#else
constexpr bool is_64_bit = false;
#endif
FakePtraceConnection connection;
ASSERT_TRUE(connection.Initialize(getpid()));
TestAgainstTarget(getpid(), is_64_bit);
TestAgainstTarget(&connection);
}
class ChildTest : public Multiprocess {
@ -201,13 +200,10 @@ class ChildTest : public Multiprocess {
private:
void MultiprocessParent() {
#if defined(ARCH_CPU_64_BITS)
constexpr bool is_64_bit = true;
#else
constexpr bool is_64_bit = false;
#endif
DirectPtraceConnection connection;
ASSERT_TRUE(connection.Initialize(ChildPID()));
TestAgainstTarget(ChildPID(), is_64_bit);
TestAgainstTarget(&connection);
}
void MultiprocessChild() { CheckedReadFileAtEOF(ReadPipeHandle()); }

View File

@ -199,11 +199,11 @@ bool ProcessReaderLinux::Initialize(PtraceConnection* connection) {
return false;
}
pid_t pid = connection->GetProcessID();
if (!memory_map_.Initialize(pid)) {
if (!memory_map_.Initialize(connection_)) {
return false;
}
pid_t pid = connection->GetProcessID();
if (!process_memory_.Initialize(pid)) {
return false;
}
@ -332,7 +332,7 @@ void ProcessReaderLinux::InitializeModules() {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
AuxiliaryVector aux;
if (!aux.Initialize(ProcessID(), is_64_bit_)) {
if (!aux.Initialize(connection_)) {
return;
}

View File

@ -242,10 +242,11 @@ using ThreadMap = std::map<pid_t, TestThreadPool::ThreadExpectation>;
void ExpectThreads(const ThreadMap& thread_map,
const std::vector<ProcessReaderLinux::Thread>& threads,
const pid_t pid) {
PtraceConnection* connection) {
ASSERT_EQ(threads.size(), thread_map.size());
MemoryMap memory_map;
ASSERT_TRUE(memory_map.Initialize(pid));
ASSERT_TRUE(memory_map.Initialize(connection));
for (const auto& thread : threads) {
SCOPED_TRACE(
@ -306,7 +307,7 @@ class ChildThreadTest : public Multiprocess {
ASSERT_TRUE(process_reader.Initialize(&connection));
const std::vector<ProcessReaderLinux::Thread>& threads =
process_reader.Threads();
ExpectThreads(thread_map, threads, ChildPID());
ExpectThreads(thread_map, threads, &connection);
}
void MultiprocessChild() override {

View File

@ -16,6 +16,7 @@
#include "build/build_config.h"
#include "gtest/gtest.h"
#include "util/file/file_io.h"
namespace crashpad {
namespace test {
@ -70,5 +71,11 @@ bool FakePtraceConnection::GetThreadInfo(pid_t tid, ThreadInfo* info) {
return attached;
}
bool FakePtraceConnection::ReadFileContents(const base::FilePath& path,
std::string* contents) {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return LoggingReadEntireFile(path, contents);
}
} // namespace test
} // namespace crashpad

View File

@ -53,6 +53,9 @@ class FakePtraceConnection : public PtraceConnection {
//! \brief Does not modify \a info.
bool GetThreadInfo(pid_t tid, ThreadInfo* info) override;
bool ReadFileContents(const base::FilePath& path,
std::string* contents) override;
private:
std::set<pid_t> attachments_;
pid_t pid_;

View File

@ -20,6 +20,7 @@
#include "base/files/file_path.h"
#include "base/logging.h"
#include "util/file/file_reader.h"
#include "util/file/string_file.h"
#include "util/stdlib/map_insert.h"
namespace crashpad {
@ -28,18 +29,22 @@ AuxiliaryVector::AuxiliaryVector() : values_() {}
AuxiliaryVector::~AuxiliaryVector() {}
bool AuxiliaryVector::Initialize(pid_t pid, bool is_64_bit) {
return is_64_bit ? Read<uint64_t>(pid) : Read<uint32_t>(pid);
bool AuxiliaryVector::Initialize(PtraceConnection* connection) {
return connection->Is64Bit() ? Read<uint64_t>(connection)
: Read<uint32_t>(connection);
}
template <typename ULong>
bool AuxiliaryVector::Read(pid_t pid) {
bool AuxiliaryVector::Read(PtraceConnection* connection) {
char path[32];
snprintf(path, sizeof(path), "/proc/%d/auxv", pid);
FileReader aux_file;
if (!aux_file.Open(base::FilePath(path))) {
snprintf(path, sizeof(path), "/proc/%d/auxv", connection->GetProcessID());
std::string contents;
if (!connection->ReadFileContents(base::FilePath(path), &contents)) {
return false;
}
StringFile aux_file;
aux_file.SetString(contents);
ULong type;
ULong value;

View File

@ -21,6 +21,7 @@
#include "base/logging.h"
#include "base/macros.h"
#include "util/linux/ptrace_connection.h"
#include "util/misc/reinterpret_bytes.h"
namespace crashpad {
@ -32,16 +33,15 @@ class AuxiliaryVector {
~AuxiliaryVector();
//! \brief Initializes this object with the auxiliary vector for the process
//! with process ID \a pid.
//! connected via \a connection.
//!
//! This method must be called successfully prior to calling any other method
//! in this class.
//!
//! \param[in] pid The process ID of a target process.
//! \param[in] is_64_bit Whether the target process is 64-bit.
//! \param[in] connection A connection to the target process.
//!
//! \return `true` on success, `false` on failure with a message logged.
bool Initialize(pid_t pid, bool is_64_bit);
bool Initialize(PtraceConnection* connection);
//! \brief Retrieve a value from the vector.
//!
@ -64,7 +64,7 @@ class AuxiliaryVector {
private:
template <typename ULong>
bool Read(pid_t pid);
bool Read(PtraceConnection* connection);
DISALLOW_COPY_AND_ASSIGN(AuxiliaryVector);
};

View File

@ -25,6 +25,7 @@
#include "build/build_config.h"
#include "gtest/gtest.h"
#include "test/errors.h"
#include "test/linux/fake_ptrace_connection.h"
#include "test/multiprocess.h"
#include "util/linux/address_types.h"
#include "util/linux/memory_map.h"
@ -45,16 +46,14 @@ namespace test {
namespace {
void TestAgainstCloneOrSelf(pid_t pid) {
#if defined(ARCH_CPU_64_BITS)
constexpr bool am_64_bit = true;
#else
constexpr bool am_64_bit = false;
#endif
FakePtraceConnection connection;
ASSERT_TRUE(connection.Initialize(pid));
AuxiliaryVector aux;
ASSERT_TRUE(aux.Initialize(pid, am_64_bit));
ASSERT_TRUE(aux.Initialize(&connection));
MemoryMap mappings;
ASSERT_TRUE(mappings.Initialize(pid));
ASSERT_TRUE(mappings.Initialize(&connection));
LinuxVMAddress phdrs;
ASSERT_TRUE(aux.GetValue(AT_PHDR, &phdrs));
@ -168,13 +167,11 @@ class AuxVecTester : public AuxiliaryVector {
};
TEST(AuxiliaryVector, SignedBit) {
#if defined(ARCH_CPU_64_BITS)
constexpr bool am_64_bit = true;
#else
constexpr bool am_64_bit = false;
#endif
FakePtraceConnection connection;
ASSERT_TRUE(connection.Initialize(getpid()));
AuxVecTester aux;
ASSERT_TRUE(aux.Initialize(getpid(), am_64_bit));
ASSERT_TRUE(&connection);
constexpr uint64_t type = 0x0000000012345678;
constexpr int32_t neg1_32 = -1;

View File

@ -16,6 +16,8 @@
#include <utility>
#include "util/file/file_io.h"
namespace crashpad {
DirectPtraceConnection::DirectPtraceConnection()
@ -63,4 +65,10 @@ bool DirectPtraceConnection::GetThreadInfo(pid_t tid, ThreadInfo* info) {
return ptracer_.GetThreadInfo(tid, info);
}
bool DirectPtraceConnection::ReadFileContents(const base::FilePath& path,
std::string* contents) {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return LoggingReadEntireFile(path, contents);
}
} // namespace crashpad

View File

@ -52,6 +52,8 @@ class DirectPtraceConnection : public PtraceConnection {
bool Attach(pid_t tid) override;
bool Is64Bit() override;
bool GetThreadInfo(pid_t tid, ThreadInfo* info) override;
bool ReadFileContents(const base::FilePath& path,
std::string* contents) override;
private:
std::vector<std::unique_ptr<ScopedPtraceAttach>> attachments_;

View File

@ -221,7 +221,7 @@ bool MemoryMap::Mapping::Equals(const Mapping& other) const {
shareable == other.shareable;
}
bool MemoryMap::Initialize(pid_t pid) {
bool MemoryMap::Initialize(PtraceConnection* connection) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
// If the maps file is not read atomically, entries can be read multiple times
@ -235,8 +235,8 @@ bool MemoryMap::Initialize(pid_t pid) {
do {
std::string contents;
char path[32];
snprintf(path, sizeof(path), "/proc/%d/maps", pid);
if (!LoggingReadEntireFile(base::FilePath(path), &contents)) {
snprintf(path, sizeof(path), "/proc/%d/maps", connection->GetProcessID());
if (!connection->ReadFileContents(base::FilePath(path), &contents)) {
return false;
}

View File

@ -22,6 +22,7 @@
#include "util/linux/address_types.h"
#include "util/linux/checked_linux_address_range.h"
#include "util/linux/ptrace_connection.h"
#include "util/misc/initialization_state_dcheck.h"
namespace crashpad {
@ -54,15 +55,15 @@ class MemoryMap {
~MemoryMap();
//! \brief Initializes this object with information about the mapped memory
//! regions in the process whose ID is \a pid.
//! regions in the process connected via \a connection.
//!
//! This method must be called successfully prior to calling any other method
//! in this class. This method may only be called once.
//!
//! \param[in] pid The process ID to obtain information for.
//! \param[in] connection A connection to the process create a map for.
//!
//! \return `true` on success, `false` on failure with a message logged.
bool Initialize(pid_t pid);
bool Initialize(PtraceConnection* connection);
//! \return The Mapping containing \a address or `nullptr` if no match is
//! found. The caller does not take ownership of this object. It is scoped

View File

@ -26,10 +26,11 @@
#include "gtest/gtest.h"
#include "test/errors.h"
#include "test/file.h"
#include "test/linux/fake_ptrace_connection.h"
#include "test/multiprocess.h"
#include "test/scoped_temp_dir.h"
#include "util/file/file_io.h"
#include "util/linux/scoped_ptrace_attach.h"
#include "util/linux/direct_ptrace_connection.h"
#include "util/misc/clock.h"
#include "util/misc/from_pointer_cast.h"
#include "util/posix/scoped_mmap.h"
@ -46,8 +47,12 @@ TEST(MemoryMap, SelfBasic) {
MAP_SHARED | MAP_ANON,
-1,
0));
FakePtraceConnection connection;
ASSERT_TRUE(connection.Initialize(getpid()));
MemoryMap map;
ASSERT_TRUE(map.Initialize(getpid()));
ASSERT_TRUE(map.Initialize(&connection));
auto stack_address = FromPointerCast<LinuxVMAddress>(&map);
const MemoryMap::Mapping* mapping = map.FindMapping(stack_address);
@ -118,11 +123,11 @@ class MapChildTest : public Multiprocess {
std::string mapped_file_name(path_length, std::string::value_type());
CheckedReadFileExactly(ReadPipeHandle(), &mapped_file_name[0], path_length);
ScopedPtraceAttach attachment;
attachment.ResetAttach(ChildPID());
DirectPtraceConnection connection;
ASSERT_TRUE(connection.Initialize(ChildPID()));
MemoryMap map;
ASSERT_TRUE(map.Initialize(ChildPID()));
ASSERT_TRUE(map.Initialize(&connection));
const MemoryMap::Mapping* mapping = map.FindMapping(code_address);
ASSERT_TRUE(mapping);
@ -268,8 +273,11 @@ TEST(MemoryMap, SelfLargeMapFile) {
ASSERT_NO_FATAL_FAILURE(
InitializeMappings(&mappings, kNumMappings, page_size));
FakePtraceConnection connection;
ASSERT_TRUE(connection.Initialize(getpid()));
MemoryMap map;
ASSERT_TRUE(map.Initialize(getpid()));
ASSERT_TRUE(map.Initialize(&connection));
ExpectMappings(
map, mappings.addr_as<LinuxVMAddress>(), kNumMappings, page_size);
@ -292,11 +300,11 @@ class MapRunningChildTest : public Multiprocess {
// Let the child get back to its work
SleepNanoseconds(1000);
ScopedPtraceAttach attachment;
attachment.ResetAttach(ChildPID());
DirectPtraceConnection connection;
ASSERT_TRUE(connection.Initialize(ChildPID()));
MemoryMap map;
ASSERT_TRUE(map.Initialize(ChildPID()));
ASSERT_TRUE(map.Initialize(&connection));
// We should at least find the original mappings. The extra mappings may
// or not be found depending on scheduling.
@ -349,8 +357,11 @@ TEST(MemoryMap, MapRunningChild) {
// file. The second page should not.
void ExpectFindFileMmapStart(LinuxVMAddress mapping_start,
LinuxVMSize page_size) {
FakePtraceConnection connection;
ASSERT_TRUE(connection.Initialize(getpid()));
MemoryMap map;
ASSERT_TRUE(map.Initialize(getpid()));
ASSERT_TRUE(map.Initialize(&connection));
auto mapping1 = map.FindMapping(mapping_start);
ASSERT_TRUE(mapping1);
@ -391,8 +402,11 @@ TEST(MemoryMap, FindFileMmapStart) {
// Basic
{
FakePtraceConnection connection;
ASSERT_TRUE(connection.Initialize(getpid()));
MemoryMap map;
ASSERT_TRUE(map.Initialize(getpid()));
ASSERT_TRUE(map.Initialize(&connection));
auto mapping1 = map.FindMapping(mapping_start);
ASSERT_TRUE(mapping1);

View File

@ -14,15 +14,22 @@
#include "util/linux/ptrace_broker.h"
#include <fcntl.h>
#include <limits.h>
#include <string.h>
#include <unistd.h>
#include <algorithm>
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "util/file/file_io.h"
namespace crashpad {
PtraceBroker::PtraceBroker(int sock, bool is_64_bit)
: ptracer_(is_64_bit, /* can_log= */ false),
file_root_("/proc/"),
attachments_(nullptr),
attach_count_(0),
attach_capacity_(0),
@ -32,12 +39,40 @@ PtraceBroker::PtraceBroker(int sock, bool is_64_bit)
PtraceBroker::~PtraceBroker() = default;
void PtraceBroker::SetFileRoot(const char* new_root) {
DCHECK_EQ(new_root[strlen(new_root) - 1], '/');
file_root_ = new_root;
}
int PtraceBroker::Run() {
int result = RunImpl();
ReleaseAttachments();
return result;
}
bool PtraceBroker::AllocateAttachments() {
constexpr size_t page_size = 4096;
constexpr size_t alloc_size =
(sizeof(ScopedPtraceAttach) + page_size - 1) & ~(page_size - 1);
void* alloc = sbrk(alloc_size);
if (reinterpret_cast<intptr_t>(alloc) == -1) {
return false;
}
if (attachments_ == nullptr) {
attachments_ = reinterpret_cast<ScopedPtraceAttach*>(alloc);
}
attach_capacity_ += alloc_size / sizeof(ScopedPtraceAttach);
return true;
}
void PtraceBroker::ReleaseAttachments() {
for (size_t index = 0; index < attach_count_; ++index) {
attachments_[index].Reset();
}
}
int PtraceBroker::RunImpl() {
while (true) {
Request request = {};
@ -114,6 +149,24 @@ int PtraceBroker::RunImpl() {
continue;
}
case Request::kTypeReadFile: {
ScopedFileHandle handle;
int result = ReceiveAndOpenFilePath(request.path.path_length, &handle);
if (result != 0) {
return result;
}
if (!handle.is_valid()) {
continue;
}
result = SendFileContents(handle.get());
if (result != 0) {
return result;
}
continue;
}
case Request::kTypeReadMemory: {
int result =
SendMemory(request.tid, request.iov.base, request.iov.size);
@ -132,6 +185,46 @@ int PtraceBroker::RunImpl() {
}
}
int PtraceBroker::SendError(Errno err) {
return WriteFile(sock_, &err, sizeof(err)) ? 0 : errno;
}
int PtraceBroker::SendReadError(Errno err) {
int32_t rv = -1;
if (!WriteFile(sock_, &rv, sizeof(rv))) {
return errno;
}
return SendError(err);
}
int PtraceBroker::SendOpenResult(OpenResult result) {
return WriteFile(sock_, &result, sizeof(result)) ? 0 : errno;
}
int PtraceBroker::SendFileContents(FileHandle handle) {
char buffer[4096];
int32_t rv;
do {
rv = ReadFile(handle, buffer, sizeof(buffer));
if (rv < 0) {
return SendReadError(errno);
}
if (!WriteFile(sock_, &rv, sizeof(rv))) {
return errno;
}
if (rv > 0) {
if (!WriteFile(sock_, buffer, static_cast<size_t>(rv))) {
return errno;
}
}
} while (rv > 0);
return 0;
}
int PtraceBroker::SendMemory(pid_t pid, VMAddress address, VMSize size) {
char buffer[4096];
while (size > 0) {
@ -161,27 +254,31 @@ int PtraceBroker::SendMemory(pid_t pid, VMAddress address, VMSize size) {
return 0;
}
bool PtraceBroker::AllocateAttachments() {
constexpr size_t page_size = 4096;
constexpr size_t alloc_size =
(sizeof(ScopedPtraceAttach) + page_size - 1) & ~(page_size - 1);
void* alloc = sbrk(alloc_size);
if (reinterpret_cast<intptr_t>(alloc) == -1) {
return false;
int PtraceBroker::ReceiveAndOpenFilePath(VMSize path_length,
ScopedFileHandle* handle) {
char path[std::max(4096, PATH_MAX)];
if (path_length >= sizeof(path)) {
return SendOpenResult(kOpenResultTooLong);
}
if (attachments_ == nullptr) {
attachments_ = reinterpret_cast<ScopedPtraceAttach*>(alloc);
if (!ReadFileExactly(sock_, path, path_length)) {
return errno;
}
path[path_length] = '\0';
if (strncmp(path, file_root_, strlen(file_root_)) != 0) {
return SendOpenResult(kOpenResultAccessDenied);
}
attach_capacity_ += alloc_size / sizeof(ScopedPtraceAttach);
return true;
}
void PtraceBroker::ReleaseAttachments() {
for (size_t index = 0; index < attach_count_; ++index) {
attachments_[index].Reset();
ScopedFileHandle local_handle(
HANDLE_EINTR(open(path, O_RDONLY | O_CLOEXEC | O_NOCTTY)));
if (!local_handle.is_valid()) {
return SendOpenResult(static_cast<OpenResult>(errno));
}
handle->reset(local_handle.release());
return SendOpenResult(kOpenResultSuccess);
}
} // namespace crashpad

View File

@ -20,6 +20,7 @@
#include <sys/types.h>
#include "base/macros.h"
#include "util/file/file_io.h"
#include "util/linux/exception_handler_protocol.h"
#include "util/linux/ptrace_connection.h"
#include "util/linux/ptracer.h"
@ -69,6 +70,14 @@ class PtraceBroker {
//! to zero, followed by an Errno.
kTypeReadMemory,
//! \brief Read a file's contents. The data is returned in a series of
//! messages. The first message is an OpenResult, indicating the
//! validity of the received file path. If the OpenResult is
//! kOpenResultSuccess, each subsequent message begins with an int32_t
//! indicating the number of bytes read, 0 for end-of-file, or -1 for
//! errors, followed by an Errno. On success, the bytes read follow.
kTypeReadFile,
//! \brief Causes the broker to return from Run(), detaching all attached
//! threads. Does not respond.
kTypeExit
@ -78,14 +87,41 @@ class PtraceBroker {
//! kTypeGetThreadInfo, and kTypeReadMemory.
pid_t tid;
//! \brief Specifies the memory region to read for a kTypeReadMemory request.
struct {
//! \brief The base address of the memory region.
VMAddress base;
union {
//! \brief Specifies the memory region to read for a kTypeReadMemory
//! request.
struct {
//! \brief The base address of the memory region.
VMAddress base;
//! \brief The size of the memory region.
VMSize size;
} iov;
//! \brief The size of the memory region.
VMSize size;
} iov;
// \brief Specifies the file path to read for a kTypeReadFile request.
struct {
//! \brief The number of bytes in #path. The path should not include a
//! `NUL`-terminator.
VMSize path_length;
//! \brief The file path to read.
char path[];
} path;
};
};
//! \brief A result used in operations that accept paths.
//!
//! Positive values of this enum are reserved for sending errno values.
enum OpenResult : int32_t {
//! \brief Access to the path is denied.
kOpenResultAccessDenied = -2,
//! \brief The path name is too long.
kOpenResultTooLong = -1,
//! \brief The file was successfully opened.
kOpenResultSuccess = 0,
};
//! \brief The response sent for a Request with type kTypeGetThreadInfo.
@ -109,6 +145,18 @@ class PtraceBroker {
~PtraceBroker();
//! \brief Restricts the broker to serving the contents of files under \a
//! root.
//!
//! If this method is not called, the broker defaults to only serving files
//! under "/proc/".
//!
//! \param[in] root A NUL-terminated c-string containing the path to the new
//! root. \a root must not be `nullptr`, must end in a '/', and the caller
//! should ensure that \a root remains valid for the lifetime of the
//! broker.
void SetFileRoot(const char* root);
//! \brief Begin serving requests on the configured socket.
//!
//! This method returns when a PtraceBrokerRequest with type kTypeExit is
@ -121,12 +169,18 @@ class PtraceBroker {
int Run();
private:
int RunImpl();
int SendMemory(pid_t pid, VMAddress address, VMSize size);
bool AllocateAttachments();
void ReleaseAttachments();
int RunImpl();
int SendError(Errno err);
int SendReadError(Errno err);
int SendOpenResult(OpenResult result);
int SendFileContents(FileHandle handle);
int SendMemory(pid_t pid, VMAddress address, VMSize size);
int ReceiveAndOpenFilePath(VMSize path_length, ScopedFileHandle* handle);
Ptracer ptracer_;
const char* file_root_;
ScopedPtraceAttach* attachments_;
size_t attach_count_;
size_t attach_capacity_;

View File

@ -23,8 +23,10 @@
#include "build/build_config.h"
#include "gtest/gtest.h"
#include "test/filesystem.h"
#include "test/linux/get_tls.h"
#include "test/multiprocess.h"
#include "test/scoped_temp_dir.h"
#include "util/file/file_io.h"
#include "util/linux/ptrace_client.h"
#include "util/posix/scoped_mmap.h"
@ -143,6 +145,23 @@ class SameBitnessTest : public Multiprocess {
ScopedFileHandle broker_sock(socks[0]);
ScopedFileHandle client_sock(socks[1]);
ScopedTempDir temp_dir;
base::FilePath file_path(temp_dir.path().Append("test_file"));
std::string expected_file_contents;
{
expected_file_contents.resize(4097);
for (size_t i = 0; i < expected_file_contents.size(); ++i) {
expected_file_contents[i] = static_cast<char>(i % 256);
}
ScopedFileHandle handle(
LoggingOpenFileForWrite(file_path,
FileWriteMode::kCreateOrFail,
FilePermissions::kWorldReadable));
ASSERT_TRUE(LoggingWriteFile(handle.get(),
expected_file_contents.data(),
expected_file_contents.size()));
}
#if defined(ARCH_CPU_64_BITS)
constexpr bool am_64_bit = true;
#else
@ -192,6 +211,17 @@ class SameBitnessTest : public Multiprocess {
EXPECT_FALSE(client.Read(mapping_.addr_as<VMAddress>() + mapping_.len(),
sizeof(unmapped),
&unmapped));
std::string file_root = temp_dir.path().value() + '/';
broker.SetFileRoot(file_root.c_str());
std::string file_contents;
ASSERT_TRUE(client.ReadFileContents(file_path, &file_contents));
EXPECT_EQ(file_contents, expected_file_contents);
ScopedTempDir temp_dir2;
base::FilePath test_file2(temp_dir2.path().Append("test_file2"));
ASSERT_TRUE(CreateFile(test_file2));
EXPECT_FALSE(client.ReadFileContents(test_file2, &file_contents));
}
}

View File

@ -101,6 +101,42 @@ bool PtraceClient::Initialize(int sock, pid_t pid) {
return true;
}
bool PtraceClient::Read(VMAddress address, size_t size, void* buffer) {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
char* buffer_c = reinterpret_cast<char*>(buffer);
PtraceBroker::Request request;
request.type = PtraceBroker::Request::kTypeReadMemory;
request.tid = pid_;
request.iov.base = address;
request.iov.size = size;
if (!LoggingWriteFile(sock_, &request, sizeof(request))) {
return false;
}
while (size > 0) {
VMSize bytes_read;
if (!LoggingReadFileExactly(sock_, &bytes_read, sizeof(bytes_read))) {
return false;
}
if (!bytes_read) {
ReceiveAndLogError(sock_, "PtraceBroker ReadMemory");
return false;
}
if (!LoggingReadFileExactly(sock_, buffer_c, bytes_read)) {
return false;
}
size -= bytes_read;
buffer_c += bytes_read;
}
return true;
}
pid_t PtraceClient::GetProcessID() {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return pid_;
@ -140,40 +176,77 @@ bool PtraceClient::GetThreadInfo(pid_t tid, ThreadInfo* info) {
return false;
}
bool PtraceClient::Read(VMAddress address, size_t size, void* buffer) {
bool PtraceClient::ReadFileContents(const base::FilePath& path,
std::string* contents) {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
char* buffer_c = reinterpret_cast<char*>(buffer);
PtraceBroker::Request request;
request.type = PtraceBroker::Request::kTypeReadMemory;
request.tid = pid_;
request.iov.base = address;
request.iov.size = size;
request.type = PtraceBroker::Request::kTypeReadFile;
request.path.path_length = path.value().size();
if (!LoggingWriteFile(sock_, &request, sizeof(request))) {
if (!LoggingWriteFile(sock_, &request, sizeof(request)) ||
!SendFilePath(path.value().c_str(), request.path.path_length)) {
return false;
}
while (size > 0) {
VMSize bytes_read;
if (!LoggingReadFileExactly(sock_, &bytes_read, sizeof(bytes_read))) {
std::string local_contents;
int32_t read_result;
do {
if (!LoggingReadFileExactly(sock_, &read_result, sizeof(read_result))) {
return false;
}
if (!bytes_read) {
ReceiveAndLogError(sock_, "PtraceBroker ReadMemory");
if (read_result < 0) {
ReceiveAndLogError(sock_, "ReadFileContents");
return false;
}
if (!LoggingReadFileExactly(sock_, buffer_c, bytes_read)) {
return false;
if (read_result > 0) {
size_t old_length = local_contents.size();
local_contents.resize(old_length + read_result);
if (!LoggingReadFileExactly(
sock_, &local_contents[old_length], read_result)) {
return false;
}
}
} while (read_result > 0);
size -= bytes_read;
buffer_c += bytes_read;
}
contents->swap(local_contents);
return true;
}
bool PtraceClient::SendFilePath(const char* path, size_t length) {
if (!LoggingWriteFile(sock_, path, length)) {
return false;
}
PtraceBroker::OpenResult result;
if (!LoggingReadFileExactly(sock_, &result, sizeof(result))) {
return false;
}
switch (result) {
case PtraceBroker::kOpenResultAccessDenied:
LOG(ERROR) << "Broker Open: access denied";
return false;
case PtraceBroker::kOpenResultTooLong:
LOG(ERROR) << "Broker Open: path too long";
return false;
case PtraceBroker::kOpenResultSuccess:
return true;
default:
if (result < 0) {
LOG(ERROR) << "Broker Open: invalid result " << result;
DCHECK(false);
} else {
errno = result;
PLOG(ERROR) << "Broker Open";
}
return false;
}
}
} // namespace crashpad

View File

@ -70,8 +70,12 @@ class PtraceClient : public PtraceConnection {
bool Attach(pid_t tid) override;
bool Is64Bit() override;
bool GetThreadInfo(pid_t tid, ThreadInfo* info) override;
bool ReadFileContents(const base::FilePath& path,
std::string* contents) override;
private:
bool SendFilePath(const char* path, size_t length);
int sock_;
pid_t pid_;
bool is_64_bit_;

View File

@ -17,6 +17,9 @@
#include <sys/types.h>
#include <string>
#include "base/files/file_path.h"
#include "util/linux/thread_info.h"
namespace crashpad {
@ -45,6 +48,15 @@ class PtraceConnection {
//! \param[out] info Information about the thread.
//! \return `true` on success. `false` on failure with a message logged.
virtual bool GetThreadInfo(pid_t tid, ThreadInfo* info) = 0;
//! \brief Reads the entire contents of a file.
//!
//! \param[in] path The path of the file to read.
//! \param[out] contents The file contents, valid if this method returns
//! `true`.
//! \return `true` on success. `false` on failure with a message logged.
virtual bool ReadFileContents(const base::FilePath& path,
std::string* contents) = 0;
};
} // namespace crashpad