Make TaskMemory a child class of ProcessMemory

Currently TaskMemory re-implements a number of Read* routines that are
implemented in a platform-independent way in ProcessMemory with access
to a single platform-specific ReadUpTo method. Implement the ReadUpTo
method for TaskMemory and subclass it from ProcessMemory to inherit the
remaining methods.

The ProcessMemoryTests didn't work on macOS because MultiprocessExec
can not access the child process' task port without root privileges or
the task_for_pid entitlement. Create an adaptor class for those tests to
use MachMultiprocess so that the child process sends its task port to
the parent.

Bug: crashpad:263
Change-Id: Id8e1788a74fe957f05703a5eb569ca3bf9870369
Reviewed-on: https://chromium-review.googlesource.com/c/1387265
Commit-Queue: Vlad Tsyrklevich <vtsyrklevich@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
Vlad Tsyrklevich 2018-12-20 11:12:51 -08:00 committed by Commit Bot
parent 7018a80b36
commit 8b2ec2aae4
9 changed files with 141 additions and 168 deletions

View File

@ -122,7 +122,9 @@ class MultiprocessExec : public Multiprocess {
//!
//! This method is only valid during the body of MultiprocessParent().
//!
//! \return A platform-specific type representing the child process.
//! \return A platform-specific type representing the child process. This
//! method can fail on macOS because access to a child's task port
//! requires the task_for_pid entitlement.
ProcessType ChildProcess();
protected:

View File

@ -29,6 +29,10 @@
#include <stdio_ext.h>
#endif
#if defined(OS_MACOSX)
#include "util/mach/task_for_pid.h"
#endif
namespace crashpad {
namespace test {
@ -149,7 +153,11 @@ void MultiprocessExec::MultiprocessChild() {
}
ProcessType MultiprocessExec::ChildProcess() {
#if defined(OS_MACOSX)
return TaskForPID(ChildPID());
#else
return ChildPID();
#endif
}
} // namespace test

View File

@ -16,7 +16,7 @@
#if defined(OS_FUCHSIA)
#include <lib/zx/process.h>
#elif defined(OS_POSIX)
#elif defined(OS_LINUX) || defined(OS_ANDROID)
#include <unistd.h>
#endif
@ -26,10 +26,12 @@ namespace test {
ProcessType GetSelfProcess() {
#if defined(OS_FUCHSIA)
return zx::process::self();
#elif defined(OS_POSIX)
#elif defined(OS_LINUX) || defined(OS_ANDROID)
return getpid();
#elif defined(OS_WIN)
return GetCurrentProcess();
#elif defined(OS_MACOSX)
return mach_task_self();
#endif
}

View File

@ -19,10 +19,12 @@
#if defined(OS_FUCHSIA)
#include <lib/zx/process.h>
#elif defined(OS_POSIX)
#elif defined(OS_LINUX) || defined(OS_ANDROID)
#include <sys/types.h>
#elif defined(OS_WIN)
#include <windows.h>
#elif defined(OS_MACOSX)
#include <mach/mach.h>
#endif
namespace crashpad {
@ -30,11 +32,13 @@ namespace test {
#if defined(OS_FUCHSIA)
using ProcessType = zx::unowned_process;
#elif defined(OS_POSIX) || DOXYGEN
#elif defined(OS_LINUX) || defined(OS_ANDROID) || DOXYGEN
//! \brief Alias for platform-specific type to represent a process.
using ProcessType = pid_t;
#elif defined(OS_WIN)
using ProcessType = HANDLE;
#elif defined(OS_MACOSX)
using ProcessType = task_t;
#else
#error Port.
#endif

View File

@ -137,6 +137,11 @@ static_library("util") {
"numeric/in_range_cast.h",
"numeric/int128.h",
"numeric/safe_assignment.h",
"process/process_memory.cc",
"process/process_memory.h",
"process/process_memory_native.h",
"process/process_memory_range.cc",
"process/process_memory_range.h",
"stdlib/aligned_allocator.cc",
"stdlib/aligned_allocator.h",
"stdlib/map_insert.h",
@ -313,19 +318,6 @@ static_library("util") {
]
}
if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia ||
crashpad_is_win) {
sources += [
"process/process_memory.cc",
"process/process_memory.h",
"process/process_memory_native.h",
# TODO: Port to all platforms.
"process/process_memory_range.cc",
"process/process_memory_range.h",
]
}
if (crashpad_is_win) {
sources += [
"file/directory_reader_win.cc",
@ -562,6 +554,8 @@ source_set("util_test") {
"numeric/checked_range_test.cc",
"numeric/in_range_cast_test.cc",
"numeric/int128_test.cc",
"process/process_memory_range_test.cc",
"process/process_memory_test.cc",
"stdlib/aligned_allocator_test.cc",
"stdlib/map_insert_test.cc",
"stdlib/string_number_conversion_test.cc",
@ -633,15 +627,6 @@ source_set("util_test") {
sources += [ "misc/capture_context_test_util_fuchsia.cc" ]
}
if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia ||
crashpad_is_win) {
sources += [
# TODO: Port to all platforms.
"process/process_memory_range_test.cc",
"process/process_memory_test.cc",
]
}
if (crashpad_is_win) {
sources += [
"misc/capture_context_test_util_win.cc",

View File

@ -73,21 +73,9 @@ bool TaskMemory::Initialize(task_t task) {
return true;
}
bool TaskMemory::Read(mach_vm_address_t address, size_t size, void* buffer) {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
std::unique_ptr<MappedMemory> memory = ReadMapped(address, size);
if (!memory) {
return false;
}
memcpy(buffer, memory->data(), size);
return true;
}
std::unique_ptr<TaskMemory::MappedMemory> TaskMemory::ReadMapped(
mach_vm_address_t address,
size_t size) {
size_t size) const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
if (size == 0) {
@ -113,58 +101,29 @@ std::unique_ptr<TaskMemory::MappedMemory> TaskMemory::ReadMapped(
new MappedMemory(region, region_size, address - region_address, size));
}
bool TaskMemory::ReadCString(mach_vm_address_t address, std::string* string) {
ssize_t TaskMemory::ReadUpTo(VMAddress address,
size_t size,
void* buffer) const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
DCHECK_LE(size, (size_t)std::numeric_limits<ssize_t>::max());
return ReadCStringInternal(address, false, 0, string);
}
std::unique_ptr<MappedMemory> memory = ReadMapped(address, size);
if (!memory) {
// If we can not read the entire mapping, try to perform a short read of the
// first page instead. This is necessary to support ReadCString().
size_t short_read = PAGE_SIZE - (address % PAGE_SIZE);
if (short_read >= size)
return -1;
bool TaskMemory::ReadCStringSizeLimited(mach_vm_address_t address,
mach_vm_size_t size,
std::string* string) {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
memory = ReadMapped(address, short_read);
if (!memory)
return -1;
return ReadCStringInternal(address, true, size, string);
}
bool TaskMemory::ReadCStringInternal(mach_vm_address_t address,
bool has_size,
mach_vm_size_t size,
std::string* string) {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
if (!has_size) {
size = PAGE_SIZE;
size = short_read;
}
std::string local_string;
mach_vm_address_t read_address = address;
do {
mach_vm_size_t read_length =
std::min(size, PAGE_SIZE - (read_address % PAGE_SIZE));
std::unique_ptr<MappedMemory> read_region =
ReadMapped(read_address, read_length);
if (!read_region) {
return false;
}
const char* read_region_data =
reinterpret_cast<const char*>(read_region->data());
size_t read_region_data_length = strnlen(read_region_data, read_length);
local_string.append(read_region_data, read_region_data_length);
if (read_region_data_length < read_length) {
string->swap(local_string);
return true;
}
if (has_size) {
size -= read_length;
}
read_address = mach_vm_trunc_page(read_address + read_length);
} while ((!has_size || size > 0) && read_address > address);
LOG(WARNING) << base::StringPrintf("unterminated string at 0x%llx", address);
return false;
memcpy(buffer, memory->data(), size);
return static_cast<ssize_t>(size);
}
} // namespace crashpad

View File

@ -23,12 +23,14 @@
#include "base/mac/scoped_mach_vm.h"
#include "base/macros.h"
#include "util/misc/address_types.h"
#include "util/misc/initialization_state_dcheck.h"
#include "util/process/process_memory.h"
namespace crashpad {
//! \brief Accesses the memory of another Mach task.
class TaskMemory {
class TaskMemory : public ProcessMemory {
public:
//! \brief A memory region mapped from another Mach task.
//!
@ -103,23 +105,6 @@ class TaskMemory {
//! \return `true` on success, `false` on failure with a message logged.
bool Initialize(task_t task);
//! \brief Copies memory from the target task into a caller-provided buffer in
//! the current task.
//!
//! \param[in] address The address, in the target tasks address space, of the
//! memory region to copy.
//! \param[in] size The size, in bytes, of the memory region to copy. \a
//! buffer must be at least this size.
//! \param[out] buffer The buffer into which the contents of the other tasks
//! memory will be copied.
//!
//! \return `true` on success, with \a buffer filled appropriately. `false` on
//! failure, with a warning logged. Failures can occur, for example, when
//! encountering unmapped or unreadable pages.
//!
//! \sa ReadMapped()
bool Read(mach_vm_address_t address, size_t size, void* buffer);
//! \brief Maps memory from the target task into the current task.
//!
//! This interface is an alternative to Read() that does not require the
@ -134,50 +119,10 @@ class TaskMemory {
//! requested. On faliure, `nullptr`, with a warning logged. Failures can
//! occur, for example, when encountering unmapped or unreadable pages.
std::unique_ptr<MappedMemory> ReadMapped(mach_vm_address_t address,
size_t size);
//! \brief Reads a `NUL`-terminated C string from the target task into a
//! string in the current task.
//!
//! The length of the string need not be known ahead of time. This method will
//! read contiguous memory until a `NUL` terminator is found.
//!
//! \param[in] address The address, in the target tasks address space, of the
//! string to copy.
//! \param[out] string The string read from the other task.
//!
//! \return `true` on success, with \a string set appropriately. `false` on
//! failure, with a warning logged. Failures can occur, for example, when
//! encountering unmapped or unreadable pages.
//!
//! \sa MappedMemory::ReadCString()
bool ReadCString(mach_vm_address_t address, std::string* string);
//! \brief Reads a `NUL`-terminated C string from the target task into a
//! string in the current task.
//!
//! \param[in] address The address, in the target tasks address space, of the
//! string to copy.
//! \param[in] size The maximum number of bytes to read. The string is
//! required to be `NUL`-terminated within this many bytes.
//! \param[out] string The string read from the other task.
//!
//! \return `true` on success, with \a string set appropriately. `false` on
//! failure, with a warning logged. Failures can occur, for example, when
//! a `NUL` terminator is not found within \a size bytes, or when
//! encountering unmapped or unreadable pages.
//!
//! \sa MappedMemory::ReadCString()
bool ReadCStringSizeLimited(mach_vm_address_t address,
mach_vm_size_t size,
std::string* string);
size_t size) const;
private:
// The common internal implementation shared by the ReadCString*() methods.
bool ReadCStringInternal(mach_vm_address_t address,
bool has_size,
mach_vm_size_t size,
std::string* string);
ssize_t ReadUpTo(VMAddress address, size_t size, void* buffer) const override;
task_t task_; // weak
InitializationStateDcheck initialized_;

View File

@ -20,6 +20,8 @@
#include "util/process/process_memory_linux.h"
#elif defined(OS_WIN)
#include "util/process/process_memory_win.h"
#elif defined(OS_MACOSX)
#include "util/mach/task_memory.h"
#endif
namespace crashpad {
@ -31,6 +33,8 @@ using ProcessMemoryNative = ProcessMemoryFuchsia;
using ProcessMemoryNative = ProcessMemoryLinux;
#elif defined(OS_WIN)
using ProcessMemoryNative = ProcessMemoryWin;
#elif defined(OS_MACOSX)
using ProcessMemoryNative = TaskMemory;
#else
#error Port.
#endif

View File

@ -29,10 +29,75 @@
#include "util/misc/from_pointer_cast.h"
#include "util/process/process_memory_native.h"
#if defined(OS_MACOSX)
#include "test/mac/mach_multiprocess.h"
#endif // defined(OS_MACOSX)
namespace crashpad {
namespace test {
namespace {
// On macOS the ProcessMemoryTests require accessing the child process' task
// port which requires root or a code signing entitlement. To account for this
// we implement an adaptor class that wraps MachMultiprocess on macOS, because
// it shares the child's task port, and makes it behave like MultiprocessExec.
#if defined(OS_MACOSX)
class MultiprocessAdaptor : public MachMultiprocess {
public:
void SetChildTestMainFunction(const std::string& function_name) {
test_function_ = function_name;
}
ProcessType ChildProcess() { return ChildTask(); }
// Helpers to get I/O handles in the child process
static FileHandle OutputHandle() {
CHECK_NE(write_pipe_handle_, -1);
return write_pipe_handle_;
}
static FileHandle InputHandle() {
CHECK_NE(read_pipe_handle_, -1);
return read_pipe_handle_;
}
private:
virtual void Parent() = 0;
void MachMultiprocessParent() override { Parent(); }
void MachMultiprocessChild() override {
read_pipe_handle_ = ReadPipeHandle();
write_pipe_handle_ = WritePipeHandle();
internal::CheckedInvokeMultiprocessChild(test_function_);
}
std::string test_function_;
static FileHandle read_pipe_handle_;
static FileHandle write_pipe_handle_;
};
FileHandle MultiprocessAdaptor::read_pipe_handle_ = -1;
FileHandle MultiprocessAdaptor::write_pipe_handle_ = -1;
#else
class MultiprocessAdaptor : public MultiprocessExec {
public:
static FileHandle OutputHandle() {
return StdioFileHandle(StdioStream::kStandardOutput);
}
static FileHandle InputHandle() {
return StdioFileHandle(StdioStream::kStandardInput);
}
private:
virtual void Parent() = 0;
void MultiprocessParent() override { Parent(); }
};
#endif // defined(OS_MACOSX)
void DoChildReadTestSetup(size_t* region_size,
std::unique_ptr<char[]>* region) {
*region_size = 4 * base::GetPageSize();
@ -46,17 +111,17 @@ CRASHPAD_CHILD_TEST_MAIN(ReadTestChild) {
size_t region_size;
std::unique_ptr<char[]> region;
DoChildReadTestSetup(&region_size, &region);
FileHandle out = StdioFileHandle(StdioStream::kStandardOutput);
FileHandle out = MultiprocessAdaptor::OutputHandle();
CheckedWriteFile(out, &region_size, sizeof(region_size));
VMAddress address = FromPointerCast<VMAddress>(region.get());
CheckedWriteFile(out, &address, sizeof(address));
CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput));
CheckedReadFileAtEOF(MultiprocessAdaptor::InputHandle());
return 0;
}
class ReadTest : public MultiprocessExec {
class ReadTest : public MultiprocessAdaptor {
public:
ReadTest() : MultiprocessExec() {
ReadTest() : MultiprocessAdaptor() {
SetChildTestMainFunction("ReadTestChild");
}
@ -72,7 +137,7 @@ class ReadTest : public MultiprocessExec {
void RunAgainstChild() { Run(); }
private:
void MultiprocessParent() override {
void Parent() override {
size_t region_size;
VMAddress region;
ASSERT_TRUE(
@ -183,23 +248,22 @@ CRASHPAD_CHILD_TEST_MAIN(ReadCStringTestChild) {
&const_empty, &const_short, &local_empty, &local_short, &long_string);
const auto write_address = [](const char* p) {
VMAddress address = FromPointerCast<VMAddress>(p);
CheckedWriteFile(StdioFileHandle(StdioStream::kStandardOutput),
&address,
sizeof(address));
CheckedWriteFile(
MultiprocessAdaptor::OutputHandle(), &address, sizeof(address));
};
write_address(const_empty);
write_address(const_short);
write_address(local_empty);
write_address(local_short);
write_address(long_string.c_str());
CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput));
CheckedReadFileAtEOF(MultiprocessAdaptor::InputHandle());
return 0;
}
class ReadCStringTest : public MultiprocessExec {
class ReadCStringTest : public MultiprocessAdaptor {
public:
ReadCStringTest(bool limit_size)
: MultiprocessExec(), limit_size_(limit_size) {
: MultiprocessAdaptor(), limit_size_(limit_size) {
SetChildTestMainFunction("ReadCStringTestChild");
}
@ -221,7 +285,7 @@ class ReadCStringTest : public MultiprocessExec {
void RunAgainstChild() { Run(); }
private:
void MultiprocessParent() override {
void Parent() override {
#define DECLARE_AND_READ_ADDRESS(name) \
VMAddress name; \
ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(), &name, sizeof(name)));
@ -332,24 +396,24 @@ CRASHPAD_CHILD_TEST_MAIN(ReadUnmappedChildMain) {
ScopedGuardedPage pages;
VMAddress address = reinterpret_cast<VMAddress>(pages.Pointer());
DoReadUnmappedChildMainSetup(pages.Pointer());
FileHandle out = StdioFileHandle(StdioStream::kStandardOutput);
FileHandle out = MultiprocessAdaptor::OutputHandle();
CheckedWriteFile(out, &address, sizeof(address));
CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput));
CheckedReadFileAtEOF(MultiprocessAdaptor::InputHandle());
return 0;
}
// This test only supports running against a child process because
// ScopedGuardedPage is not thread-safe.
class ReadUnmappedTest : public MultiprocessExec {
class ReadUnmappedTest : public MultiprocessAdaptor {
public:
ReadUnmappedTest() : MultiprocessExec() {
ReadUnmappedTest() : MultiprocessAdaptor() {
SetChildTestMainFunction("ReadUnmappedChildMain");
}
void RunAgainstChild() { Run(); }
private:
void MultiprocessParent() override {
void Parent() override {
VMAddress address = 0;
ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(), &address, sizeof(address)));
DoTest(ChildProcess(), address);
@ -450,28 +514,28 @@ CRASHPAD_CHILD_TEST_MAIN(ReadCStringUnmappedChildMain) {
ScopedGuardedPage pages;
std::vector<StringDataInChildProcess> strings;
DoCStringUnmappedTestSetup(pages.Pointer(), &strings);
FileHandle out = StdioFileHandle(StdioStream::kStandardOutput);
FileHandle out = MultiprocessAdaptor::OutputHandle();
strings[0].Write(out);
strings[1].Write(out);
strings[2].Write(out);
strings[3].Write(out);
CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput));
CheckedReadFileAtEOF(MultiprocessAdaptor::InputHandle());
return 0;
}
// This test only supports running against a child process because
// ScopedGuardedPage is not thread-safe.
class ReadCStringUnmappedTest : public MultiprocessExec {
class ReadCStringUnmappedTest : public MultiprocessAdaptor {
public:
ReadCStringUnmappedTest(bool limit_size)
: MultiprocessExec(), limit_size_(limit_size) {
: MultiprocessAdaptor(), limit_size_(limit_size) {
SetChildTestMainFunction("ReadCStringUnmappedChildMain");
}
void RunAgainstChild() { Run(); }
private:
void MultiprocessParent() override {
void Parent() override {
std::vector<StringDataInChildProcess> strings;
strings.push_back(StringDataInChildProcess::Read(ReadPipeHandle()));
strings.push_back(StringDataInChildProcess::Read(ReadPipeHandle()));