// 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 #include #include #include #include #include "base/files/file_path.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #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 "third_party/lss/lss.h" #include "util/file/file_io.h" #include "util/file/scoped_remove_file.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" namespace crashpad { namespace test { namespace { TEST(MemoryMap, SelfLargeFiles) { // This test is meant to test the handler's ability to understand files // mapped from large offsets, even if the handler wasn't built with // _FILE_OFFSET_BITS=64. ScopedTempDir needs to stat files to determine // whether to recurse into directories, which may will fail without large file // support. ScopedRemoveFile doesn't have that restriction. ScopedTempDir dir; ScopedRemoveFile large_file_path(dir.path().Append("crashpad_test_file")); ScopedFileHandle handle( LoggingOpenFileForReadAndWrite(large_file_path.get(), FileWriteMode::kCreateOrFail, FilePermissions::kWorldReadable)); ASSERT_TRUE(handle.is_valid()); // sys_fallocate supports large files as long as the kernel supports them, // regardless of _FILE_OFFSET_BITS. off64_t off = 1llu + UINT32_MAX; ASSERT_EQ(sys_fallocate(handle.get(), 0, off, getpagesize()), 0) << ErrnoMessage("fallocate"); ScopedMmap mapping; void* addr = sys_mmap( nullptr, getpagesize(), PROT_READ, MAP_SHARED, handle.get(), off); ASSERT_TRUE(addr); ASSERT_TRUE(mapping.ResetAddrLen(addr, getpagesize())); FakePtraceConnection connection; ASSERT_TRUE(connection.Initialize(getpid())); MemoryMap map; ASSERT_TRUE(map.Initialize(&connection)); } TEST(MemoryMap, SelfBasic) { ScopedMmap mmapping; ASSERT_TRUE(mmapping.ResetMmap(nullptr, getpagesize(), PROT_EXEC | PROT_READ, MAP_SHARED | MAP_ANON, -1, 0)); FakePtraceConnection connection; ASSERT_TRUE(connection.Initialize(getpid())); MemoryMap map; ASSERT_TRUE(map.Initialize(&connection)); auto stack_address = FromPointerCast(&map); const MemoryMap::Mapping* mapping = map.FindMapping(stack_address); ASSERT_TRUE(mapping); EXPECT_GE(stack_address, mapping->range.Base()); EXPECT_LT(stack_address, mapping->range.End()); EXPECT_TRUE(mapping->readable); EXPECT_TRUE(mapping->writable); auto code_address = FromPointerCast(getpid); mapping = map.FindMapping(code_address); ASSERT_TRUE(mapping); EXPECT_GE(code_address, mapping->range.Base()); EXPECT_LT(code_address, mapping->range.End()); #if !defined(OS_ANDROID) // Android Q+ supports execute only memory. EXPECT_TRUE(mapping->readable); #endif EXPECT_FALSE(mapping->writable); EXPECT_TRUE(mapping->executable); auto mapping_address = mmapping.addr_as(); mapping = map.FindMapping(mapping_address); ASSERT_TRUE(mapping); mapping = map.FindMapping(mapping_address + mmapping.len() - 1); ASSERT_TRUE(mapping); EXPECT_EQ(mapping_address, mapping->range.Base()); EXPECT_EQ(mapping_address + mmapping.len(), mapping->range.End()); EXPECT_TRUE(mapping->readable); EXPECT_FALSE(mapping->writable); EXPECT_TRUE(mapping->executable); EXPECT_TRUE(mapping->shareable); } void InitializeFile(const base::FilePath& path, size_t size, ScopedFileHandle* handle) { ASSERT_FALSE(FileExists(path)); handle->reset(LoggingOpenFileForReadAndWrite( path, FileWriteMode::kReuseOrCreate, FilePermissions::kOwnerOnly)); ASSERT_TRUE(handle->is_valid()); std::string file_contents(size, std::string::value_type()); CheckedWriteFile(handle->get(), file_contents.c_str(), file_contents.size()); } class MapChildTest : public Multiprocess { public: MapChildTest() : Multiprocess(), page_size_(getpagesize()) {} ~MapChildTest() {} private: void MultiprocessParent() override { LinuxVMAddress code_address; CheckedReadFileExactly( ReadPipeHandle(), &code_address, sizeof(code_address)); LinuxVMAddress stack_address; CheckedReadFileExactly( ReadPipeHandle(), &stack_address, sizeof(stack_address)); LinuxVMAddress mapped_address; CheckedReadFileExactly( ReadPipeHandle(), &mapped_address, sizeof(mapped_address)); LinuxVMAddress mapped_file_address; CheckedReadFileExactly( ReadPipeHandle(), &mapped_file_address, sizeof(mapped_file_address)); LinuxVMSize path_length; CheckedReadFileExactly(ReadPipeHandle(), &path_length, sizeof(path_length)); std::string mapped_file_name(path_length, std::string::value_type()); CheckedReadFileExactly(ReadPipeHandle(), &mapped_file_name[0], path_length); DirectPtraceConnection connection; ASSERT_TRUE(connection.Initialize(ChildPID())); MemoryMap map; ASSERT_TRUE(map.Initialize(&connection)); const MemoryMap::Mapping* mapping = map.FindMapping(code_address); ASSERT_TRUE(mapping); EXPECT_GE(code_address, mapping->range.Base()); EXPECT_LT(code_address, mapping->range.End()); #if !defined(OS_ANDROID) // Android Q+ supports execute only memory. EXPECT_TRUE(mapping->readable); #endif EXPECT_TRUE(mapping->executable); EXPECT_FALSE(mapping->writable); mapping = map.FindMapping(stack_address); ASSERT_TRUE(mapping); EXPECT_GE(stack_address, mapping->range.Base()); EXPECT_LT(stack_address, mapping->range.End()); EXPECT_TRUE(mapping->readable); EXPECT_TRUE(mapping->writable); mapping = map.FindMapping(mapped_address); ASSERT_TRUE(mapping); EXPECT_EQ(mapped_address, mapping->range.Base()); EXPECT_EQ(mapped_address + page_size_, mapping->range.End()); EXPECT_FALSE(mapping->readable); EXPECT_FALSE(mapping->writable); EXPECT_FALSE(mapping->executable); EXPECT_TRUE(mapping->shareable); mapping = map.FindMapping(mapped_file_address); ASSERT_TRUE(mapping); EXPECT_EQ(mapped_file_address, mapping->range.Base()); EXPECT_EQ(mapping->offset, static_cast(page_size_)); EXPECT_TRUE(mapping->readable); EXPECT_TRUE(mapping->writable); EXPECT_FALSE(mapping->executable); EXPECT_FALSE(mapping->shareable); EXPECT_EQ(mapping->name, mapped_file_name); struct stat file_stat; ASSERT_EQ(stat(mapped_file_name.c_str(), &file_stat), 0) << ErrnoMessage("stat"); EXPECT_EQ(mapping->device, file_stat.st_dev); EXPECT_EQ(mapping->inode, file_stat.st_ino); EXPECT_EQ(map.FindMappingWithName(mapping->name), mapping); } void MultiprocessChild() override { auto code_address = FromPointerCast(getpid); CheckedWriteFile(WritePipeHandle(), &code_address, sizeof(code_address)); auto stack_address = FromPointerCast(&code_address); CheckedWriteFile(WritePipeHandle(), &stack_address, sizeof(stack_address)); ScopedMmap mapping; ASSERT_TRUE(mapping.ResetMmap( nullptr, page_size_, PROT_NONE, MAP_SHARED | MAP_ANON, -1, 0)); auto mapped_address = mapping.addr_as(); CheckedWriteFile( WritePipeHandle(), &mapped_address, sizeof(mapped_address)); ScopedTempDir temp_dir; base::FilePath path = temp_dir.path().Append(FILE_PATH_LITERAL("test_file")); ScopedFileHandle handle; ASSERT_NO_FATAL_FAILURE(InitializeFile(path, page_size_ * 2, &handle)); ScopedMmap file_mapping; ASSERT_TRUE(file_mapping.ResetMmap(nullptr, page_size_, PROT_READ | PROT_WRITE, MAP_PRIVATE, handle.get(), page_size_)); auto mapped_file_address = file_mapping.addr_as(); CheckedWriteFile( WritePipeHandle(), &mapped_file_address, sizeof(mapped_file_address)); LinuxVMSize path_length = path.value().size(); CheckedWriteFile(WritePipeHandle(), &path_length, sizeof(path_length)); CheckedWriteFile(WritePipeHandle(), path.value().c_str(), path_length); CheckedReadFileAtEOF(ReadPipeHandle()); } const size_t page_size_; DISALLOW_COPY_AND_ASSIGN(MapChildTest); }; TEST(MemoryMap, MapChild) { MapChildTest test; test.Run(); } // Some systems optimize mappings by allocating new mappings inside existing // mappings with matching permissions. Work around this by allocating one large // mapping and then switching up the permissions of individual pages to force // populating more entries in the maps file. void InitializeMappings(ScopedMmap* mappings, size_t num_mappings, size_t mapping_size) { ASSERT_TRUE(mappings->ResetMmap(nullptr, mapping_size * num_mappings, PROT_READ, MAP_PRIVATE | MAP_ANON, -1, 0)); auto region = mappings->addr_as(); for (size_t index = 0; index < num_mappings; index += 2) { ASSERT_EQ(mprotect(reinterpret_cast(region + index * mapping_size), mapping_size, PROT_READ | PROT_WRITE), 0) << ErrnoMessage("mprotect"); } } void ExpectMappings(const MemoryMap& map, LinuxVMAddress region_addr, size_t num_mappings, size_t mapping_size) { for (size_t index = 0; index < num_mappings; ++index) { SCOPED_TRACE(base::StringPrintf("index %zu", index)); auto mapping_address = region_addr + index * mapping_size; const MemoryMap::Mapping* mapping = map.FindMapping(mapping_address); ASSERT_TRUE(mapping); EXPECT_EQ(mapping_address, mapping->range.Base()); EXPECT_EQ(mapping_address + mapping_size, mapping->range.End()); EXPECT_TRUE(mapping->readable); EXPECT_FALSE(mapping->shareable); EXPECT_FALSE(mapping->executable); if (index % 2 == 0) { EXPECT_TRUE(mapping->writable); } else { EXPECT_FALSE(mapping->writable); } } } TEST(MemoryMap, SelfLargeMapFile) { constexpr size_t kNumMappings = 1024; const size_t page_size = getpagesize(); ScopedMmap mappings; ASSERT_NO_FATAL_FAILURE( InitializeMappings(&mappings, kNumMappings, page_size)); FakePtraceConnection connection; ASSERT_TRUE(connection.Initialize(getpid())); MemoryMap map; ASSERT_TRUE(map.Initialize(&connection)); ExpectMappings( map, mappings.addr_as(), kNumMappings, page_size); } class MapRunningChildTest : public Multiprocess { public: MapRunningChildTest() : Multiprocess(), page_size_(getpagesize()) {} ~MapRunningChildTest() {} private: void MultiprocessParent() override { // Let the child get started LinuxVMAddress region_addr; CheckedReadFileExactly(ReadPipeHandle(), ®ion_addr, sizeof(region_addr)); for (int iter = 0; iter < 8; ++iter) { SCOPED_TRACE(base::StringPrintf("iter %d", iter)); // Let the child get back to its work SleepNanoseconds(1000); DirectPtraceConnection connection; ASSERT_TRUE(connection.Initialize(ChildPID())); MemoryMap map; ASSERT_TRUE(map.Initialize(&connection)); // We should at least find the original mappings. The extra mappings may // or not be found depending on scheduling. ExpectMappings(map, region_addr, kNumMappings, page_size_); } } void MultiprocessChild() override { ASSERT_EQ(fcntl(ReadPipeHandle(), F_SETFL, O_NONBLOCK), 0) << ErrnoMessage("fcntl"); ScopedMmap mappings; ASSERT_NO_FATAL_FAILURE( InitializeMappings(&mappings, kNumMappings, page_size_)); // Let the parent start mapping us auto region_addr = mappings.addr_as(); CheckedWriteFile(WritePipeHandle(), ®ion_addr, sizeof(region_addr)); // But don't stop there! constexpr size_t kNumExtraMappings = 256; ScopedMmap extra_mappings; while (true) { ASSERT_NO_FATAL_FAILURE( InitializeMappings(&extra_mappings, kNumExtraMappings, page_size_)); // Quit when the parent is done char c; FileOperationResult res = ReadFile(ReadPipeHandle(), &c, sizeof(c)); if (res == 0) { break; } ASSERT_EQ(errno, EAGAIN); } } static constexpr size_t kNumMappings = 1024; const size_t page_size_; DISALLOW_COPY_AND_ASSIGN(MapRunningChildTest); }; TEST(MemoryMap, MapRunningChild) { MapRunningChildTest test; test.Run(); } // Expects first and third pages from mapping_start to refer to the same mapped // file. The second page should not. void ExpectFindFilePossibleMmapStarts(LinuxVMAddress mapping_start, LinuxVMSize page_size) { FakePtraceConnection connection; ASSERT_TRUE(connection.Initialize(getpid())); MemoryMap map; ASSERT_TRUE(map.Initialize(&connection)); auto mapping1 = map.FindMapping(mapping_start); ASSERT_TRUE(mapping1); auto mapping2 = map.FindMapping(mapping_start + page_size); ASSERT_TRUE(mapping2); auto mapping3 = map.FindMapping(mapping_start + page_size * 2); ASSERT_TRUE(mapping3); ASSERT_NE(mapping1, mapping2); ASSERT_NE(mapping2, mapping3); auto mappings = map.FindFilePossibleMmapStarts(*mapping1); ASSERT_EQ(mappings->Count(), 1u); EXPECT_EQ(mappings->Next(), mapping1); mappings = map.FindFilePossibleMmapStarts(*mapping2); ASSERT_EQ(mappings->Count(), 1u); EXPECT_EQ(mappings->Next(), mapping2); mappings = map.FindFilePossibleMmapStarts(*mapping3); #if defined(OS_ANDROID) EXPECT_EQ(mappings->Count(), 2u); #else ASSERT_EQ(mappings->Count(), 1u); EXPECT_EQ(mappings->Next(), mapping1); #endif } TEST(MemoryMap, FindFilePossibleMmapStarts) { const size_t page_size = getpagesize(); ScopedTempDir temp_dir; base::FilePath path = temp_dir.path().Append( FILE_PATH_LITERAL("FindFilePossibleMmapStartsTestFile")); ScopedFileHandle handle; size_t file_length = page_size * 3; ASSERT_NO_FATAL_FAILURE(InitializeFile(path, file_length, &handle)); ScopedMmap file_mapping; ASSERT_TRUE(file_mapping.ResetMmap( nullptr, file_length, PROT_READ, MAP_PRIVATE, handle.get(), 0)); auto mapping_start = file_mapping.addr_as(); // Change the permissions on the second page to split the mapping into three // parts. ASSERT_EQ(mprotect(file_mapping.addr_as() + page_size, page_size, PROT_READ | PROT_WRITE), 0); // Basic { FakePtraceConnection connection; ASSERT_TRUE(connection.Initialize(getpid())); MemoryMap map; ASSERT_TRUE(map.Initialize(&connection)); auto mapping1 = map.FindMapping(mapping_start); ASSERT_TRUE(mapping1); auto mapping2 = map.FindMapping(mapping_start + page_size); ASSERT_TRUE(mapping2); auto mapping3 = map.FindMapping(mapping_start + page_size * 2); ASSERT_TRUE(mapping3); ASSERT_NE(mapping1, mapping2); ASSERT_NE(mapping2, mapping3); #if defined(OS_ANDROID) auto mappings = map.FindFilePossibleMmapStarts(*mapping1); EXPECT_EQ(mappings->Count(), 1u); EXPECT_EQ(mappings->Next(), mapping1); EXPECT_EQ(mappings->Next(), nullptr); mappings = map.FindFilePossibleMmapStarts(*mapping2); EXPECT_EQ(mappings->Count(), 2u); mappings = map.FindFilePossibleMmapStarts(*mapping3); EXPECT_EQ(mappings->Count(), 3u); #else auto mappings = map.FindFilePossibleMmapStarts(*mapping1); ASSERT_EQ(mappings->Count(), 1u); EXPECT_EQ(mappings->Next(), mapping1); EXPECT_EQ(mappings->Next(), nullptr); mappings = map.FindFilePossibleMmapStarts(*mapping2); ASSERT_EQ(mappings->Count(), 1u); EXPECT_EQ(mappings->Next(), mapping1); mappings = map.FindFilePossibleMmapStarts(*mapping3); ASSERT_EQ(mappings->Count(), 1u); EXPECT_EQ(mappings->Next(), mapping1); #endif #if defined(ARCH_CPU_64_BITS) constexpr bool is_64_bit = true; #else constexpr bool is_64_bit = false; #endif MemoryMap::Mapping bad_mapping; bad_mapping.range.SetRange(is_64_bit, 0, 1); mappings = map.FindFilePossibleMmapStarts(bad_mapping); EXPECT_EQ(mappings->Count(), 0u); EXPECT_EQ(mappings->Next(), nullptr); } // Make the second page an anonymous mapping file_mapping.ResetAddrLen(file_mapping.addr_as(), page_size); ScopedMmap page2_mapping; ASSERT_TRUE(page2_mapping.ResetMmap(file_mapping.addr_as() + page_size, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0)); ScopedMmap page3_mapping; ASSERT_TRUE( page3_mapping.ResetMmap(file_mapping.addr_as() + page_size * 2, page_size, PROT_READ, MAP_PRIVATE | MAP_FIXED, handle.get(), page_size * 2)); ExpectFindFilePossibleMmapStarts(mapping_start, page_size); // Map the second page to another file. ScopedFileHandle handle2; base::FilePath path2 = temp_dir.path().Append( FILE_PATH_LITERAL("FindFilePossibleMmapStartsTestFile2")); ASSERT_NO_FATAL_FAILURE(InitializeFile(path2, page_size, &handle2)); page2_mapping.ResetMmap(file_mapping.addr_as() + page_size, page_size, PROT_READ, MAP_PRIVATE | MAP_FIXED, handle2.get(), 0); ExpectFindFilePossibleMmapStarts(mapping_start, page_size); } TEST(MemoryMap, FindFilePossibleMmapStarts_MultipleStarts) { ScopedTempDir temp_dir; base::FilePath path = temp_dir.path().Append(FILE_PATH_LITERAL("MultipleStartsTestFile")); const size_t page_size = getpagesize(); ScopedFileHandle handle; ASSERT_NO_FATAL_FAILURE(InitializeFile(path, page_size * 2, &handle)); // Locate a sequence of pages to setup a test in. char* seq_addr; { ScopedMmap whole_mapping; ASSERT_TRUE(whole_mapping.ResetMmap( nullptr, page_size * 8, PROT_READ, MAP_PRIVATE | MAP_ANON, -1, 0)); seq_addr = whole_mapping.addr_as(); } // Arrange file and anonymous mappings in the sequence. ScopedMmap file_mapping0; ASSERT_TRUE(file_mapping0.ResetMmap(seq_addr, page_size, PROT_READ, MAP_PRIVATE | MAP_FIXED, handle.get(), page_size)); ScopedMmap file_mapping1; ASSERT_TRUE(file_mapping1.ResetMmap(seq_addr + page_size, page_size * 2, PROT_READ, MAP_PRIVATE | MAP_FIXED, handle.get(), 0)); ScopedMmap file_mapping2; ASSERT_TRUE(file_mapping2.ResetMmap(seq_addr + page_size * 3, page_size, PROT_READ, MAP_PRIVATE | MAP_FIXED, handle.get(), 0)); // Skip a page ScopedMmap file_mapping3; ASSERT_TRUE(file_mapping3.ResetMmap(seq_addr + page_size * 5, page_size, PROT_READ, MAP_PRIVATE | MAP_FIXED, handle.get(), 0)); ScopedMmap anon_mapping; ASSERT_TRUE(anon_mapping.ResetMmap(seq_addr + page_size * 6, page_size, PROT_READ, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0)); ScopedMmap file_mapping4; ASSERT_TRUE(file_mapping4.ResetMmap(seq_addr + page_size * 7, page_size, PROT_READ, MAP_PRIVATE | MAP_FIXED, handle.get(), 0)); FakePtraceConnection connection; ASSERT_TRUE(connection.Initialize(getpid())); MemoryMap map; ASSERT_TRUE(map.Initialize(&connection)); auto mapping = map.FindMapping(file_mapping0.addr_as()); ASSERT_TRUE(mapping); auto possible_starts = map.FindFilePossibleMmapStarts(*mapping); #if defined(OS_ANDROID) EXPECT_EQ(possible_starts->Count(), 1u); #else EXPECT_EQ(possible_starts->Count(), 0u); #endif mapping = map.FindMapping(file_mapping1.addr_as()); ASSERT_TRUE(mapping); possible_starts = map.FindFilePossibleMmapStarts(*mapping); #if defined(OS_ANDROID) EXPECT_EQ(possible_starts->Count(), 2u); #else EXPECT_EQ(possible_starts->Count(), 1u); #endif mapping = map.FindMapping(file_mapping2.addr_as()); ASSERT_TRUE(mapping); possible_starts = map.FindFilePossibleMmapStarts(*mapping); #if defined(OS_ANDROID) EXPECT_EQ(possible_starts->Count(), 3u); #else EXPECT_EQ(possible_starts->Count(), 2u); #endif mapping = map.FindMapping(file_mapping3.addr_as()); ASSERT_TRUE(mapping); possible_starts = map.FindFilePossibleMmapStarts(*mapping); #if defined(OS_ANDROID) EXPECT_EQ(possible_starts->Count(), 4u); #else EXPECT_EQ(possible_starts->Count(), 3u); #endif mapping = map.FindMapping(file_mapping4.addr_as()); ASSERT_TRUE(mapping); possible_starts = map.FindFilePossibleMmapStarts(*mapping); #if defined(OS_ANDROID) EXPECT_EQ(possible_starts->Count(), 5u); #else EXPECT_EQ(possible_starts->Count(), 4u); #endif } } // namespace } // namespace test } // namespace crashpad