// 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 #include #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) #if defined(OS_ANDROID) || defined(OS_LINUX) || defined(OS_CHROMEOS) #include "test/linux/fake_ptrace_connection.h" #include "util/linux/direct_ptrace_connection.h" #endif // OS_ANDROID || OS_LINUX || OS_CHROMEOS 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* 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(index % 256); } } CRASHPAD_CHILD_TEST_MAIN(ReadTestChild) { size_t region_size; std::unique_ptr region; DoChildReadTestSetup(®ion_size, ®ion); FileHandle out = MultiprocessAdaptor::OutputHandle(); CheckedWriteFile(out, ®ion_size, sizeof(region_size)); VMAddress address = FromPointerCast(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 region; DoChildReadTestSetup(®ion_size, ®ion); DoTest(GetSelfProcess(), region_size, FromPointerCast(region.get())); } void RunAgainstChild() { Run(); } private: void Parent() override { size_t region_size; VMAddress region; ASSERT_TRUE( ReadFileExactly(ReadPipeHandle(), ®ion_size, sizeof(region_size))); ASSERT_TRUE(ReadFileExactly(ReadPipeHandle(), ®ion, sizeof(region))); DoTest(ChildProcess(), region_size, region); } void DoTest(ProcessType process, size_t region_size, VMAddress address) { #if defined(OS_ANDROID) || defined(OS_LINUX) || defined(OS_CHROMEOS) FakePtraceConnection connection; ASSERT_TRUE(connection.Initialize(process)); ProcessMemoryLinux memory(&connection); #else ProcessMemoryNative memory; ASSERT_TRUE(memory.Initialize(process)); #endif // OS_ANDROID || OS_LINUX || OS_CHROMEOS std::unique_ptr 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(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((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(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((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((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(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(const_empty), FromPointerCast(const_short), FromPointerCast(local_empty), FromPointerCast(local_short), FromPointerCast(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) { #if defined(OS_ANDROID) || defined(OS_LINUX) || defined(OS_CHROMEOS) FakePtraceConnection connection; ASSERT_TRUE(connection.Initialize(process)); ProcessMemoryLinux memory(&connection); #else ProcessMemoryNative memory; ASSERT_TRUE(memory.Initialize(process)); #endif // OS_ANDROID || OS_LINUX || OS_CHROMEOS 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(page); for (size_t index = 0; index < base::GetPageSize(); ++index) { region[index] = static_cast(index % 256); } } CRASHPAD_CHILD_TEST_MAIN(ReadUnmappedChildMain) { ScopedGuardedPage pages; VMAddress address = reinterpret_cast(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) { #if defined(OS_ANDROID) || defined(OS_LINUX) || defined(OS_CHROMEOS) DirectPtraceConnection connection; ASSERT_TRUE(connection.Initialize(process)); ProcessMemoryLinux memory(&connection); #else ProcessMemoryNative memory; ASSERT_TRUE(memory.Initialize(process)); #endif // OS_ANDROID || OS_LINUX || OS_CHROMEOS VMAddress page_addr1 = address; VMAddress page_addr2 = page_addr1 + base::GetPageSize(); std::unique_ptr 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(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* strings) { char* region = reinterpret_cast(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 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 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& strings) { #if defined(OS_ANDROID) || defined(OS_LINUX) || defined(OS_CHROMEOS) DirectPtraceConnection connection; ASSERT_TRUE(connection.Initialize(process)); ProcessMemoryLinux memory(&connection); #else ProcessMemoryNative memory; ASSERT_TRUE(memory.Initialize(process)); #endif // OS_ANDROID || OS_LINUX || OS_CHROMEOS 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