// Copyright 2017 The Crashpad Authors // // 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 "snapshot/linux/process_reader_linux.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "base/format_macros.h" #include "base/memory/free_deleter.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "gtest/gtest.h" #include "snapshot/linux/test_modules.h" #include "test/errors.h" #include "test/linux/fake_ptrace_connection.h" #include "test/linux/get_tls.h" #include "test/multiprocess.h" #include "test/scoped_module_handle.h" #include "test/scoped_set_thread_name.h" #include "test/test_paths.h" #include "util/file/file_io.h" #include "util/file/file_writer.h" #include "util/file/filesystem.h" #include "util/linux/direct_ptrace_connection.h" #include "util/misc/address_sanitizer.h" #include "util/misc/from_pointer_cast.h" #include "util/misc/memory_sanitizer.h" #include "util/synchronization/semaphore.h" #if BUILDFLAG(IS_ANDROID) #include #include #include "dlfcn_internal.h" // Normally this comes from set_abort_message.h, but only at API level 21. extern "C" void android_set_abort_message(const char* msg) __attribute__((weak)); #endif namespace crashpad { namespace test { namespace { pid_t gettid() { return syscall(SYS_gettid); } TEST(ProcessReaderLinux, SelfBasic) { FakePtraceConnection connection; connection.Initialize(getpid()); ProcessReaderLinux process_reader; ASSERT_TRUE(process_reader.Initialize(&connection)); #if defined(ARCH_CPU_64_BITS) EXPECT_TRUE(process_reader.Is64Bit()); #else EXPECT_FALSE(process_reader.Is64Bit()); #endif EXPECT_EQ(process_reader.ProcessID(), getpid()); EXPECT_EQ(process_reader.ParentProcessID(), getppid()); static constexpr char kTestMemory[] = "Some test memory"; char buffer[std::size(kTestMemory)]; ASSERT_TRUE(process_reader.Memory()->Read( reinterpret_cast(kTestMemory), sizeof(kTestMemory), &buffer)); EXPECT_STREQ(kTestMemory, buffer); EXPECT_EQ("", process_reader.AbortMessage()); } constexpr char kTestMemory[] = "Read me from another process"; class BasicChildTest : public Multiprocess { public: BasicChildTest() : Multiprocess() {} BasicChildTest(const BasicChildTest&) = delete; BasicChildTest& operator=(const BasicChildTest&) = delete; ~BasicChildTest() {} private: void MultiprocessParent() override { DirectPtraceConnection connection; ASSERT_TRUE(connection.Initialize(ChildPID())); ProcessReaderLinux process_reader; ASSERT_TRUE(process_reader.Initialize(&connection)); #if !defined(ARCH_CPU_64_BITS) EXPECT_FALSE(process_reader.Is64Bit()); #else EXPECT_TRUE(process_reader.Is64Bit()); #endif EXPECT_EQ(process_reader.ParentProcessID(), getpid()); EXPECT_EQ(process_reader.ProcessID(), ChildPID()); std::string read_string; ASSERT_TRUE(process_reader.Memory()->ReadCString( reinterpret_cast(kTestMemory), &read_string)); EXPECT_EQ(read_string, kTestMemory); } void MultiprocessChild() override { CheckedReadFileAtEOF(ReadPipeHandle()); } }; TEST(ProcessReaderLinux, ChildBasic) { BasicChildTest test; test.Run(); } class TestThreadPool { public: struct ThreadExpectation { LinuxVMAddress tls = 0; LinuxVMAddress stack_address = 0; LinuxVMSize max_stack_size = 0; int sched_policy = 0; int static_priority = 0; int nice_value = 0; }; TestThreadPool() : threads_() {} TestThreadPool(const TestThreadPool&) = delete; TestThreadPool& operator=(const TestThreadPool&) = delete; ~TestThreadPool() { for (const auto& thread : threads_) { thread->exit_semaphore.Signal(); } for (const auto& thread : threads_) { EXPECT_EQ(pthread_join(thread->pthread, nullptr), 0) << ErrnoMessage("pthread_join"); } } void StartThreads(size_t thread_count, size_t stack_size = 0) { for (size_t thread_index = 0; thread_index < thread_count; ++thread_index) { const std::string thread_name = base::StringPrintf("ThreadPool-%zu", thread_index); threads_.push_back(std::make_unique(thread_name)); Thread* thread = threads_.back().get(); pthread_attr_t attr; ASSERT_EQ(pthread_attr_init(&attr), 0) << ErrnoMessage("pthread_attr_init"); if (stack_size > 0) { void* stack_ptr; errno = posix_memalign(&stack_ptr, getpagesize(), stack_size); ASSERT_EQ(errno, 0) << ErrnoMessage("posix_memalign"); thread->stack.reset(reinterpret_cast(stack_ptr)); ASSERT_EQ(pthread_attr_setstack(&attr, thread->stack.get(), stack_size), 0) << ErrnoMessage("pthread_attr_setstack"); thread->expectation.max_stack_size = stack_size; } ASSERT_EQ(pthread_attr_setschedpolicy(&attr, SCHED_OTHER), 0) << ErrnoMessage("pthread_attr_setschedpolicy"); thread->expectation.sched_policy = SCHED_OTHER; sched_param param; param.sched_priority = 0; ASSERT_EQ(pthread_attr_setschedparam(&attr, ¶m), 0) << ErrnoMessage("pthread_attr_setschedparam"); thread->expectation.static_priority = 0; thread->expectation.nice_value = thread_index % 20; ASSERT_EQ(pthread_create(&thread->pthread, &attr, ThreadMain, thread), 0) << ErrnoMessage("pthread_create"); } for (const auto& thread : threads_) { thread->ready_semaphore.Wait(); } } pid_t GetThreadExpectation(size_t thread_index, ThreadExpectation* expectation, std::string* thread_name_expectation) { CHECK_LT(thread_index, threads_.size()); const Thread* thread = threads_[thread_index].get(); *expectation = thread->expectation; *thread_name_expectation = thread->name; return thread->tid; } private: struct Thread { explicit Thread(const std::string& name) : pthread(), expectation(), ready_semaphore(0), exit_semaphore(0), tid(-1), name(name) { } ~Thread() {} pthread_t pthread; ThreadExpectation expectation; std::unique_ptr stack; Semaphore ready_semaphore; Semaphore exit_semaphore; pid_t tid; const std::string name; }; static void* ThreadMain(void* argument) { Thread* thread = static_cast(argument); const ScopedSetThreadName scoped_set_thread_name(thread->name); CHECK_EQ(setpriority(PRIO_PROCESS, 0, thread->expectation.nice_value), 0) << ErrnoMessage("setpriority"); thread->expectation.tls = GetTLS(); thread->expectation.stack_address = reinterpret_cast(&thread); thread->tid = gettid(); thread->ready_semaphore.Signal(); thread->exit_semaphore.Wait(); CHECK_EQ(pthread_self(), thread->pthread); return nullptr; } std::vector> threads_; }; using ThreadMap = std::map; using ThreadNameMap = std::map; void ExpectThreads(const ThreadMap& thread_map, const ThreadNameMap& thread_name_map, const std::vector& threads, PtraceConnection* connection) { ASSERT_EQ(threads.size(), thread_map.size()); ASSERT_EQ(threads.size(), thread_name_map.size()); MemoryMap memory_map; ASSERT_TRUE(memory_map.Initialize(connection)); for (const auto& thread : threads) { SCOPED_TRACE( base::StringPrintf("Thread id %d, name %s, tls 0x%" PRIx64 ", stack addr 0x%" PRIx64 ", stack size 0x%" PRIx64, thread.tid, thread.name.c_str(), thread.thread_info.thread_specific_data_address, thread.stack_region_address, thread.stack_region_size)); const auto& iterator = thread_map.find(thread.tid); ASSERT_NE(iterator, thread_map.end()); EXPECT_EQ(thread.thread_info.thread_specific_data_address, iterator->second.tls); ASSERT_TRUE(memory_map.FindMapping(thread.stack_region_address)); ASSERT_TRUE(memory_map.FindMapping(thread.stack_region_address + thread.stack_region_size - 1)); #if !defined(ADDRESS_SANITIZER) // AddressSanitizer causes stack variables to be stored separately from the // call stack. EXPECT_LE( thread.stack_region_address, connection->Memory()->PointerToAddress(iterator->second.stack_address)); EXPECT_GE( thread.stack_region_address + thread.stack_region_size, connection->Memory()->PointerToAddress(iterator->second.stack_address)); #endif // !defined(ADDRESS_SANITIZER) if (iterator->second.max_stack_size) { EXPECT_LT(thread.stack_region_size, iterator->second.max_stack_size); } EXPECT_EQ(thread.sched_policy, iterator->second.sched_policy); EXPECT_EQ(thread.static_priority, iterator->second.static_priority); EXPECT_EQ(thread.nice_value, iterator->second.nice_value); const auto& thread_name_iterator = thread_name_map.find(thread.tid); ASSERT_NE(thread_name_iterator, thread_name_map.end()); EXPECT_EQ(thread.name, thread_name_iterator->second); } } class ChildThreadTest : public Multiprocess { public: ChildThreadTest(size_t stack_size = 0) : Multiprocess(), stack_size_(stack_size) {} ChildThreadTest(const ChildThreadTest&) = delete; ChildThreadTest& operator=(const ChildThreadTest&) = delete; ~ChildThreadTest() {} private: void MultiprocessParent() override { ThreadMap thread_map; ThreadNameMap thread_name_map; for (size_t thread_index = 0; thread_index < kThreadCount + 1; ++thread_index) { pid_t tid; TestThreadPool::ThreadExpectation expectation; CheckedReadFileExactly(ReadPipeHandle(), &tid, sizeof(tid)); CheckedReadFileExactly( ReadPipeHandle(), &expectation, sizeof(expectation)); thread_map[tid] = expectation; std::string::size_type thread_name_length; CheckedReadFileExactly( ReadPipeHandle(), &thread_name_length, sizeof(thread_name_length)); std::string thread_name(thread_name_length, '\0'); CheckedReadFileExactly( ReadPipeHandle(), thread_name.data(), thread_name_length); thread_name_map[tid] = thread_name; } DirectPtraceConnection connection; ASSERT_TRUE(connection.Initialize(ChildPID())); ProcessReaderLinux process_reader; ASSERT_TRUE(process_reader.Initialize(&connection)); const std::vector& threads = process_reader.Threads(); ExpectThreads(thread_map, thread_name_map, threads, &connection); } void MultiprocessChild() override { TestThreadPool thread_pool; thread_pool.StartThreads(kThreadCount, stack_size_); const std::string current_thread_name = "MultiprocChild"; const ScopedSetThreadName scoped_set_thread_name(current_thread_name); TestThreadPool::ThreadExpectation expectation; #if defined(MEMORY_SANITIZER) // memset() + re-initialization is required to zero padding bytes for MSan. memset(&expectation, 0, sizeof(expectation)); #endif // defined(MEMORY_SANITIZER) expectation = {0}; expectation.tls = GetTLS(); expectation.stack_address = reinterpret_cast(&thread_pool); int res = sched_getscheduler(0); ASSERT_GE(res, 0) << ErrnoMessage("sched_getscheduler"); expectation.sched_policy = res; sched_param param; ASSERT_EQ(sched_getparam(0, ¶m), 0) << ErrnoMessage("sched_getparam"); expectation.static_priority = param.sched_priority; errno = 0; res = getpriority(PRIO_PROCESS, 0); ASSERT_FALSE(res == -1 && errno) << ErrnoMessage("getpriority"); expectation.nice_value = res; pid_t tid = gettid(); CheckedWriteFile(WritePipeHandle(), &tid, sizeof(tid)); CheckedWriteFile(WritePipeHandle(), &expectation, sizeof(expectation)); const std::string::size_type current_thread_name_length = current_thread_name.length(); CheckedWriteFile(WritePipeHandle(), ¤t_thread_name_length, sizeof(current_thread_name_length)); CheckedWriteFile(WritePipeHandle(), current_thread_name.data(), current_thread_name_length); for (size_t thread_index = 0; thread_index < kThreadCount; ++thread_index) { std::string thread_name_expectation; tid = thread_pool.GetThreadExpectation( thread_index, &expectation, &thread_name_expectation); CheckedWriteFile(WritePipeHandle(), &tid, sizeof(tid)); CheckedWriteFile(WritePipeHandle(), &expectation, sizeof(expectation)); const std::string::size_type thread_name_length = thread_name_expectation.length(); CheckedWriteFile( WritePipeHandle(), &thread_name_length, sizeof(thread_name_length)); CheckedWriteFile(WritePipeHandle(), thread_name_expectation.data(), thread_name_length); } CheckedReadFileAtEOF(ReadPipeHandle()); } static constexpr size_t kThreadCount = 3; const size_t stack_size_; }; TEST(ProcessReaderLinux, ChildWithThreads) { ChildThreadTest test; test.Run(); } TEST(ProcessReaderLinux, ChildThreadsWithSmallUserStacks) { ChildThreadTest test(PTHREAD_STACK_MIN); test.Run(); } // Tests a thread with a stack that spans multiple mappings. class ChildWithSplitStackTest : public Multiprocess { public: ChildWithSplitStackTest() : Multiprocess(), page_size_(getpagesize()) {} ChildWithSplitStackTest(const ChildWithSplitStackTest&) = delete; ChildWithSplitStackTest& operator=(const ChildWithSplitStackTest&) = delete; ~ChildWithSplitStackTest() {} private: void MultiprocessParent() override { LinuxVMAddress stack_addr1; LinuxVMAddress stack_addr2; LinuxVMAddress stack_addr3; CheckedReadFileExactly(ReadPipeHandle(), &stack_addr1, sizeof(stack_addr1)); CheckedReadFileExactly(ReadPipeHandle(), &stack_addr2, sizeof(stack_addr2)); CheckedReadFileExactly(ReadPipeHandle(), &stack_addr3, sizeof(stack_addr3)); DirectPtraceConnection connection; ASSERT_TRUE(connection.Initialize(ChildPID())); ProcessReaderLinux process_reader; ASSERT_TRUE(process_reader.Initialize(&connection)); const std::vector& threads = process_reader.Threads(); ASSERT_EQ(threads.size(), 1u); LinuxVMAddress thread_stack_start = threads[0].stack_region_address; EXPECT_LE(thread_stack_start, stack_addr1); EXPECT_LE(thread_stack_start, stack_addr2); EXPECT_LE(thread_stack_start, stack_addr3); LinuxVMAddress thread_stack_end = thread_stack_start + threads[0].stack_region_size; EXPECT_GE(thread_stack_end, stack_addr1); EXPECT_GE(thread_stack_end, stack_addr2); EXPECT_GE(thread_stack_end, stack_addr3); } void MultiprocessChild() override { const LinuxVMSize stack_size = page_size_ * 4; GrowStack(stack_size, reinterpret_cast(&stack_size)); } void GrowStack(LinuxVMSize stack_size, LinuxVMAddress bottom_of_stack) { char stack_contents[4096]; auto stack_address = reinterpret_cast(&stack_contents); if (bottom_of_stack - stack_address < stack_size) { GrowStack(stack_size, bottom_of_stack); } else { // Write-protect a page on our stack to split up the mapping LinuxVMAddress page_addr = stack_address - (stack_address % page_size_) + 2 * page_size_; ASSERT_EQ( mprotect(reinterpret_cast(page_addr), page_size_, PROT_READ), 0) << ErrnoMessage("mprotect"); CheckedWriteFile( WritePipeHandle(), &bottom_of_stack, sizeof(bottom_of_stack)); CheckedWriteFile(WritePipeHandle(), &page_addr, sizeof(page_addr)); CheckedWriteFile( WritePipeHandle(), &stack_address, sizeof(stack_address)); // Wait for parent to read us CheckedReadFileAtEOF(ReadPipeHandle()); ASSERT_EQ(mprotect(reinterpret_cast(page_addr), page_size_, PROT_READ | PROT_WRITE), 0) << ErrnoMessage("mprotect"); } } const size_t page_size_; }; // AddressSanitizer with use-after-return detection causes stack variables to // be allocated on the heap. #if defined(ADDRESS_SANITIZER) #define MAYBE_ChildWithSplitStack DISABLED_ChildWithSplitStack #else #define MAYBE_ChildWithSplitStack ChildWithSplitStack #endif TEST(ProcessReaderLinux, MAYBE_ChildWithSplitStack) { ChildWithSplitStackTest test; test.Run(); } // Android doesn't provide dl_iterate_phdr on ARM until API 21. #if !BUILDFLAG(IS_ANDROID) || !defined(ARCH_CPU_ARMEL) || __ANDROID_API__ >= 21 int ExpectFindModule(dl_phdr_info* info, size_t size, void* data) { SCOPED_TRACE( base::StringPrintf("module %s at 0x%" PRIx64 " phdrs 0x%" PRIx64, info->dlpi_name, LinuxVMAddress{info->dlpi_addr}, FromPointerCast(info->dlpi_phdr))); auto modules = reinterpret_cast*>(data); #if BUILDFLAG(IS_ANDROID) // Prior to API 27, Bionic includes a null entry for /system/bin/linker. if (!info->dlpi_name) { EXPECT_EQ(info->dlpi_addr, 0u); EXPECT_EQ(info->dlpi_phnum, 0u); EXPECT_EQ(info->dlpi_phdr, nullptr); return 0; } #endif // Bionic doesn't always set both of these addresses for the vdso and // /system/bin/linker, but it does always set one of them. VMAddress module_addr = info->dlpi_phdr ? FromPointerCast(info->dlpi_phdr) : info->dlpi_addr; // TODO(jperaza): This can use a range map when one is available. bool found = false; for (const auto& module : *modules) { if (module.elf_reader && module_addr >= module.elf_reader->Address() && module_addr < module.elf_reader->Address() + module.elf_reader->Size()) { found = true; break; } } EXPECT_TRUE(found); return 0; } #endif // !BUILDFLAG(IS_ANDROID) || !ARCH_CPU_ARMEL || __ANDROID_API__ >= 21 void ExpectModulesFromSelf( const std::vector& modules) { for (const auto& module : modules) { EXPECT_FALSE(module.name.empty()); EXPECT_NE(module.type, ModuleSnapshot::kModuleTypeUnknown); } // Android doesn't provide dl_iterate_phdr on ARM until API 21. #if !BUILDFLAG(IS_ANDROID) || !defined(ARCH_CPU_ARMEL) || __ANDROID_API__ >= 21 EXPECT_EQ( dl_iterate_phdr( ExpectFindModule, reinterpret_cast( const_cast*>(&modules))), 0); #endif // !BUILDFLAG(IS_ANDROID) || !ARCH_CPU_ARMEL || __ANDROID_API__ >= 21 } #if !defined(ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) void ExpectTestModule(ProcessReaderLinux* reader, const std::string& module_name) { for (const auto& module : reader->Modules()) { if (module.name.find(module_name) != std::string::npos) { ASSERT_TRUE(module.elf_reader); VMAddress dynamic_addr; ASSERT_TRUE(module.elf_reader->GetDynamicArrayAddress(&dynamic_addr)); auto dynamic_mapping = reader->GetMemoryMap()->FindMapping(dynamic_addr); auto mappings = reader->GetMemoryMap()->FindFilePossibleMmapStarts(*dynamic_mapping); EXPECT_EQ(mappings->Count(), 2u); return; } } ADD_FAILURE() << "Test module not found"; } #endif // !ADDRESS_SANITIZER && !MEMORY_SANITIZER TEST(ProcessReaderLinux, SelfModules) { #if !defined(ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) const std::string module_name = "test_module.so"; const std::string module_soname = "test_module_soname"; ScopedModuleHandle empty_test_module( LoadTestModule(module_name, module_soname)); ASSERT_TRUE(empty_test_module.valid()); #endif // !ADDRESS_SANITIZER && !MEMORY_SANITIZER FakePtraceConnection connection; connection.Initialize(getpid()); ProcessReaderLinux process_reader; ASSERT_TRUE(process_reader.Initialize(&connection)); ExpectModulesFromSelf(process_reader.Modules()); #if !defined(ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) ExpectTestModule(&process_reader, module_soname); #endif // !ADDRESS_SANITIZER && !MEMORY_SANITIZER } class ChildModuleTest : public Multiprocess { public: ChildModuleTest() : Multiprocess(), module_soname_("test_module_soname") {} ChildModuleTest(const ChildModuleTest&) = delete; ChildModuleTest& operator=(const ChildModuleTest&) = delete; ~ChildModuleTest() = default; private: void MultiprocessParent() override { char c; ASSERT_TRUE(LoggingReadFileExactly(ReadPipeHandle(), &c, sizeof(c))); DirectPtraceConnection connection; ASSERT_TRUE(connection.Initialize(ChildPID())); ProcessReaderLinux process_reader; ASSERT_TRUE(process_reader.Initialize(&connection)); ExpectModulesFromSelf(process_reader.Modules()); #if !defined(ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) ExpectTestModule(&process_reader, module_soname_); #endif // !ADDRESS_SANITIZER && !MEMORY_SANITIZER } void MultiprocessChild() override { #if !defined(ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) ScopedModuleHandle empty_test_module( LoadTestModule("test_module.so", module_soname_)); ASSERT_TRUE(empty_test_module.valid()); #endif // !ADDRESS_SANITIZER && !MEMORY_SANITIZER char c = 0; ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), &c, sizeof(c))); CheckedReadFileAtEOF(ReadPipeHandle()); } const std::string module_soname_; }; TEST(ProcessReaderLinux, ChildModules) { ChildModuleTest test; test.Run(); } #if BUILDFLAG(IS_ANDROID) const char kTestAbortMessage[] = "test abort message"; TEST(ProcessReaderLinux, AbortMessage) { // This test requires Q. The API level on Q devices will be 28 until the API // is finalized, so we can't check API level yet. For now, test for the // presence of a libc symbol which was introduced in Q. if (!crashpad::internal::Dlsym(RTLD_DEFAULT, "android_fdsan_close_with_tag")) { GTEST_SKIP(); } android_set_abort_message(kTestAbortMessage); FakePtraceConnection connection; connection.Initialize(getpid()); ProcessReaderLinux process_reader; ASSERT_TRUE(process_reader.Initialize(&connection)); EXPECT_EQ(kTestAbortMessage, process_reader.AbortMessage()); } #endif } // namespace } // namespace test } // namespace crashpad