blob: 7faa40041b44c2f58948285f3625f022399b2293 [file] [log] [blame]
// 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/process/process_memory.h"
#include <string.h>
#include <memory>
#include "base/memory/page_size.h"
#include "build/build_config.h"
#include "gtest/gtest.h"
#include "test/errors.h"
#include "test/multiprocess.h"
#include "test/multiprocess_exec.h"
#include "test/process_type.h"
#include "test/scoped_guarded_page.h"
#include "util/file/file_io.h"
#include "util/misc/from_pointer_cast.h"
#include "util/process/process_memory_native.h"
#if defined(OS_APPLE)
#include "test/mac/mach_multiprocess.h"
#endif // defined(OS_APPLE)
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_APPLE)
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_APPLE)
void DoChildReadTestSetup(size_t* region_size,
std::unique_ptr<char[]>* region) {
*region_size = 4 * base::GetPageSize();
region->reset(new char[*region_size]);
for (size_t index = 0; index < *region_size; ++index) {
(*region)[index] = static_cast<char>(index % 256);
}
}
CRASHPAD_CHILD_TEST_MAIN(ReadTestChild) {
size_t region_size;
std::unique_ptr<char[]> region;
DoChildReadTestSetup(&region_size, &region);
FileHandle out = MultiprocessAdaptor::OutputHandle();
CheckedWriteFile(out, &region_size, sizeof(region_size));
VMAddress address = FromPointerCast<VMAddress>(region.get());
CheckedWriteFile(out, &address, sizeof(address));
CheckedReadFileAtEOF(MultiprocessAdaptor::InputHandle());
return 0;
}
class ReadTest : public MultiprocessAdaptor {
public:
ReadTest() : MultiprocessAdaptor() {
SetChildTestMainFunction("ReadTestChild");
}
void RunAgainstSelf() {
size_t region_size;
std::unique_ptr<char[]> region;
DoChildReadTestSetup(&region_size, &region);
DoTest(GetSelfProcess(),
region_size,
FromPointerCast<VMAddress>(region.get()));
}
void RunAgainstChild() { Run(); }
private:
void Parent() override {
size_t region_size;
VMAddress region;
ASSERT_TRUE(
ReadFileExactly(ReadPipeHandle(), &region_size, sizeof(region_size)));
ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(), &region, sizeof(region)));
DoTest(ChildProcess(), region_size, region);
}
void DoTest(ProcessType process, size_t region_size, VMAddress address) {
ProcessMemoryNative memory;
ASSERT_TRUE(memory.Initialize(process));
std::unique_ptr<char[]> result(new char[region_size]);
// Ensure that the entire region can be read.
ASSERT_TRUE(memory.Read(address, region_size, result.get()));
for (size_t i = 0; i < region_size; ++i) {
EXPECT_EQ(result[i], static_cast<char>(i % 256));
}
// Ensure that a read of length 0 succeeds and doesn’t touch the result.
memset(result.get(), '\0', region_size);
ASSERT_TRUE(memory.Read(address, 0, result.get()));
for (size_t i = 0; i < region_size; ++i) {
EXPECT_EQ(result[i], 0);
}
// Ensure that a read starting at an unaligned address works.
ASSERT_TRUE(memory.Read(address + 1, region_size - 1, result.get()));
for (size_t i = 0; i < region_size - 1; ++i) {
EXPECT_EQ(result[i], static_cast<char>((i + 1) % 256));
}
// Ensure that a read ending at an unaligned address works.
ASSERT_TRUE(memory.Read(address, region_size - 1, result.get()));
for (size_t i = 0; i < region_size - 1; ++i) {
EXPECT_EQ(result[i], static_cast<char>(i % 256));
}
// Ensure that a read starting and ending at unaligned addresses works.
ASSERT_TRUE(memory.Read(address + 1, region_size - 2, result.get()));
for (size_t i = 0; i < region_size - 2; ++i) {
EXPECT_EQ(result[i], static_cast<char>((i + 1) % 256));
}
// Ensure that a read of exactly one page works.
size_t page_size = base::GetPageSize();
ASSERT_GE(region_size, page_size + page_size);
ASSERT_TRUE(memory.Read(address + page_size, page_size, result.get()));
for (size_t i = 0; i < page_size; ++i) {
EXPECT_EQ(result[i], static_cast<char>((i + page_size) % 256));
}
// Ensure that reading exactly a single byte works.
result[1] = 'J';
ASSERT_TRUE(memory.Read(address + 2, 1, result.get()));
EXPECT_EQ(result[0], 2);
EXPECT_EQ(result[1], 'J');
}
DISALLOW_COPY_AND_ASSIGN(ReadTest);
};
TEST(ProcessMemory, ReadSelf) {
ReadTest test;
test.RunAgainstSelf();
}
TEST(ProcessMemory, ReadChild) {
ReadTest test;
test.RunAgainstChild();
}
constexpr char kConstCharEmpty[] = "";
constexpr char kConstCharShort[] = "A short const char[]";
#define SHORT_LOCAL_STRING "A short local variable char[]"
std::string MakeLongString() {
std::string long_string;
const size_t kStringLongSize = 4 * base::GetPageSize();
for (size_t index = 0; index < kStringLongSize; ++index) {
long_string.push_back((index % 255) + 1);
}
EXPECT_EQ(long_string.size(), kStringLongSize);
return long_string;
}
void DoChildCStringReadTestSetup(const char** const_empty,
const char** const_short,
const char** local_empty,
const char** local_short,
std::string* long_string) {
*const_empty = kConstCharEmpty;
*const_short = kConstCharShort;
*local_empty = "";
*local_short = SHORT_LOCAL_STRING;
*long_string = MakeLongString();
}
CRASHPAD_CHILD_TEST_MAIN(ReadCStringTestChild) {
const char* const_empty;
const char* const_short;
const char* local_empty;
const char* local_short;
std::string long_string;
DoChildCStringReadTestSetup(
&const_empty, &const_short, &local_empty, &local_short, &long_string);
const auto write_address = [](const char* p) {
VMAddress address = FromPointerCast<VMAddress>(p);
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(MultiprocessAdaptor::InputHandle());
return 0;
}
class ReadCStringTest : public MultiprocessAdaptor {
public:
ReadCStringTest(bool limit_size)
: MultiprocessAdaptor(), limit_size_(limit_size) {
SetChildTestMainFunction("ReadCStringTestChild");
}
void RunAgainstSelf() {
const char* const_empty;
const char* const_short;
const char* local_empty;
const char* local_short;
std::string long_string;
DoChildCStringReadTestSetup(
&const_empty, &const_short, &local_empty, &local_short, &long_string);
DoTest(GetSelfProcess(),
FromPointerCast<VMAddress>(const_empty),
FromPointerCast<VMAddress>(const_short),
FromPointerCast<VMAddress>(local_empty),
FromPointerCast<VMAddress>(local_short),
FromPointerCast<VMAddress>(long_string.c_str()));
}
void RunAgainstChild() { Run(); }
private:
void Parent() override {
#define DECLARE_AND_READ_ADDRESS(name) \
VMAddress name; \
ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(), &name, sizeof(name)));
DECLARE_AND_READ_ADDRESS(const_empty_address);
DECLARE_AND_READ_ADDRESS(const_short_address);
DECLARE_AND_READ_ADDRESS(local_empty_address);
DECLARE_AND_READ_ADDRESS(local_short_address);
DECLARE_AND_READ_ADDRESS(long_string_address);
#undef DECLARE_AND_READ_ADDRESS
DoTest(ChildProcess(),
const_empty_address,
const_short_address,
local_empty_address,
local_short_address,
long_string_address);
}
void Compare(ProcessMemory& memory, VMAddress address, const char* str) {
std::string result;
if (limit_size_) {
ASSERT_TRUE(
memory.ReadCStringSizeLimited(address, strlen(str) + 1, &result));
EXPECT_EQ(result, str);
ASSERT_TRUE(
memory.ReadCStringSizeLimited(address, strlen(str) + 2, &result));
EXPECT_EQ(result, str);
EXPECT_FALSE(
memory.ReadCStringSizeLimited(address, strlen(str), &result));
} else {
ASSERT_TRUE(memory.ReadCString(address, &result));
EXPECT_EQ(result, str);
}
}
void DoTest(ProcessType process,
VMAddress const_empty_address,
VMAddress const_short_address,
VMAddress local_empty_address,
VMAddress local_short_address,
VMAddress long_string_address) {
ProcessMemoryNative memory;
ASSERT_TRUE(memory.Initialize(process));
Compare(memory, const_empty_address, kConstCharEmpty);
Compare(memory, const_short_address, kConstCharShort);
Compare(memory, local_empty_address, "");
Compare(memory, local_short_address, SHORT_LOCAL_STRING);
std::string long_string_for_comparison = MakeLongString();
Compare(memory, long_string_address, long_string_for_comparison.c_str());
}
const bool limit_size_;
DISALLOW_COPY_AND_ASSIGN(ReadCStringTest);
};
TEST(ProcessMemory, ReadCStringSelf) {
ReadCStringTest test(/* limit_size= */ false);
test.RunAgainstSelf();
}
TEST(ProcessMemory, ReadCStringChild) {
ReadCStringTest test(/* limit_size= */ false);
test.RunAgainstChild();
}
TEST(ProcessMemory, ReadCStringSizeLimitedSelf) {
ReadCStringTest test(/* limit_size= */ true);
test.RunAgainstSelf();
}
TEST(ProcessMemory, ReadCStringSizeLimitedChild) {
ReadCStringTest test(/* limit_size= */ true);
test.RunAgainstChild();
}
void DoReadUnmappedChildMainSetup(void* page) {
char* region = reinterpret_cast<char*>(page);
for (size_t index = 0; index < base::GetPageSize(); ++index) {
region[index] = static_cast<char>(index % 256);
}
}
CRASHPAD_CHILD_TEST_MAIN(ReadUnmappedChildMain) {
ScopedGuardedPage pages;
VMAddress address = reinterpret_cast<VMAddress>(pages.Pointer());
DoReadUnmappedChildMainSetup(pages.Pointer());
FileHandle out = MultiprocessAdaptor::OutputHandle();
CheckedWriteFile(out, &address, sizeof(address));
CheckedReadFileAtEOF(MultiprocessAdaptor::InputHandle());
return 0;
}
// This test only supports running against a child process because
// ScopedGuardedPage is not thread-safe.
class ReadUnmappedTest : public MultiprocessAdaptor {
public:
ReadUnmappedTest() : MultiprocessAdaptor() {
SetChildTestMainFunction("ReadUnmappedChildMain");
}
void RunAgainstChild() { Run(); }
private:
void Parent() override {
VMAddress address = 0;
ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(), &address, sizeof(address)));
DoTest(ChildProcess(), address);
}
void DoTest(ProcessType process, VMAddress address) {
ProcessMemoryNative memory;
ASSERT_TRUE(memory.Initialize(process));
VMAddress page_addr1 = address;
VMAddress page_addr2 = page_addr1 + base::GetPageSize();
std::unique_ptr<char[]> result(new char[base::GetPageSize() * 2]);
EXPECT_TRUE(memory.Read(page_addr1, base::GetPageSize(), result.get()));
EXPECT_TRUE(memory.Read(page_addr2 - 1, 1, result.get()));
EXPECT_FALSE(
memory.Read(page_addr1, base::GetPageSize() * 2, result.get()));
EXPECT_FALSE(memory.Read(page_addr2, base::GetPageSize(), result.get()));
EXPECT_FALSE(memory.Read(page_addr2 - 1, 2, result.get()));
}
DISALLOW_COPY_AND_ASSIGN(ReadUnmappedTest);
};
TEST(ProcessMemory, ReadUnmappedChild) {
ReadUnmappedTest test;
ASSERT_FALSE(testing::Test::HasFailure());
test.RunAgainstChild();
}
constexpr size_t kChildProcessStringLength = 10;
class StringDataInChildProcess {
public:
// This constructor only makes sense in the child process.
explicit StringDataInChildProcess(const char* cstring, bool valid)
: address_(FromPointerCast<VMAddress>(cstring)) {
if (valid) {
memcpy(expected_value_, cstring, kChildProcessStringLength + 1);
} else {
memset(expected_value_, 0xff, kChildProcessStringLength + 1);
}
}
void Write(FileHandle out) {
CheckedWriteFile(out, &address_, sizeof(address_));
CheckedWriteFile(out, &expected_value_, sizeof(expected_value_));
}
static StringDataInChildProcess Read(FileHandle in) {
StringDataInChildProcess str;
EXPECT_TRUE(ReadFileExactly(in, &str.address_, sizeof(str.address_)));
EXPECT_TRUE(
ReadFileExactly(in, &str.expected_value_, sizeof(str.expected_value_)));
return str;
}
VMAddress address() const { return address_; }
std::string expected_value() const { return expected_value_; }
private:
StringDataInChildProcess() : address_(0), expected_value_() {}
VMAddress address_;
char expected_value_[kChildProcessStringLength + 1];
};
void DoCStringUnmappedTestSetup(
void* page,
std::vector<StringDataInChildProcess>* strings) {
char* region = reinterpret_cast<char*>(page);
for (size_t index = 0; index < base::GetPageSize(); ++index) {
region[index] = 1 + index % 255;
}
// A string at the start of the mapped region
char* string1 = region;
string1[kChildProcessStringLength] = '\0';
// A string near the end of the mapped region
char* string2 = region + base::GetPageSize() - kChildProcessStringLength * 2;
string2[kChildProcessStringLength] = '\0';
// A string that crosses from the mapped into the unmapped region
char* string3 = region + base::GetPageSize() - kChildProcessStringLength + 1;
// A string entirely in the unmapped region
char* string4 = region + base::GetPageSize() + 10;
strings->push_back(StringDataInChildProcess(string1, true));
strings->push_back(StringDataInChildProcess(string2, true));
strings->push_back(StringDataInChildProcess(string3, false));
strings->push_back(StringDataInChildProcess(string4, false));
}
CRASHPAD_CHILD_TEST_MAIN(ReadCStringUnmappedChildMain) {
ScopedGuardedPage pages;
std::vector<StringDataInChildProcess> strings;
DoCStringUnmappedTestSetup(pages.Pointer(), &strings);
FileHandle out = MultiprocessAdaptor::OutputHandle();
strings[0].Write(out);
strings[1].Write(out);
strings[2].Write(out);
strings[3].Write(out);
CheckedReadFileAtEOF(MultiprocessAdaptor::InputHandle());
return 0;
}
// This test only supports running against a child process because
// ScopedGuardedPage is not thread-safe.
class ReadCStringUnmappedTest : public MultiprocessAdaptor {
public:
ReadCStringUnmappedTest(bool limit_size)
: MultiprocessAdaptor(), limit_size_(limit_size) {
SetChildTestMainFunction("ReadCStringUnmappedChildMain");
}
void RunAgainstChild() { Run(); }
private:
void Parent() override {
std::vector<StringDataInChildProcess> strings;
strings.push_back(StringDataInChildProcess::Read(ReadPipeHandle()));
strings.push_back(StringDataInChildProcess::Read(ReadPipeHandle()));
strings.push_back(StringDataInChildProcess::Read(ReadPipeHandle()));
strings.push_back(StringDataInChildProcess::Read(ReadPipeHandle()));
ASSERT_NO_FATAL_FAILURE(DoTest(ChildProcess(), strings));
}
void DoTest(ProcessType process,
const std::vector<StringDataInChildProcess>& strings) {
ProcessMemoryNative memory;
ASSERT_TRUE(memory.Initialize(process));
std::string result;
result.reserve(kChildProcessStringLength + 1);
if (limit_size_) {
ASSERT_TRUE(memory.ReadCStringSizeLimited(
strings[0].address(), kChildProcessStringLength + 1, &result));
EXPECT_EQ(result, strings[0].expected_value());
ASSERT_TRUE(memory.ReadCStringSizeLimited(
strings[1].address(), kChildProcessStringLength + 1, &result));
EXPECT_EQ(result, strings[1].expected_value());
EXPECT_FALSE(memory.ReadCStringSizeLimited(
strings[2].address(), kChildProcessStringLength + 1, &result));
EXPECT_FALSE(memory.ReadCStringSizeLimited(
strings[3].address(), kChildProcessStringLength + 1, &result));
} else {
ASSERT_TRUE(memory.ReadCString(strings[0].address(), &result));
EXPECT_EQ(result, strings[0].expected_value());
ASSERT_TRUE(memory.ReadCString(strings[1].address(), &result));
EXPECT_EQ(result, strings[1].expected_value());
EXPECT_FALSE(memory.ReadCString(strings[2].address(), &result));
EXPECT_FALSE(memory.ReadCString(strings[3].address(), &result));
}
}
const bool limit_size_;
DISALLOW_COPY_AND_ASSIGN(ReadCStringUnmappedTest);
};
TEST(ProcessMemory, ReadCStringUnmappedChild) {
ReadCStringUnmappedTest test(/* limit_size= */ false);
ASSERT_FALSE(testing::Test::HasFailure());
test.RunAgainstChild();
}
TEST(ProcessMemory, ReadCStringSizeLimitedUnmappedChild) {
ReadCStringUnmappedTest test(/* limit_size= */ true);
ASSERT_FALSE(testing::Test::HasFailure());
test.RunAgainstChild();
}
} // namespace
} // namespace test
} // namespace crashpad