// Copyright 2018 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/sanitized/process_snapshot_sanitized.h" #include #include #include "base/notreached.h" #include "build/build_config.h" #include "gtest/gtest.h" #include "test/multiprocess_exec.h" #include "util/file/file_io.h" #include "util/misc/address_sanitizer.h" #include "util/numeric/safe_assignment.h" #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) #include #include "snapshot/linux/process_snapshot_linux.h" #include "util/linux/direct_ptrace_connection.h" #include "util/linux/exception_information.h" #include "util/posix/signals.h" #endif namespace crashpad { namespace test { namespace { class ExceptionGenerator { public: ExceptionGenerator(const ExceptionGenerator&) = delete; ExceptionGenerator& operator=(const ExceptionGenerator&) = delete; static ExceptionGenerator* Get() { static ExceptionGenerator* instance = new ExceptionGenerator(); return instance; } bool Initialize(FileHandle in, FileHandle out) { in_ = in; out_ = out; return Signals::InstallCrashHandlers(HandleCrash, 0, nullptr); } private: ExceptionGenerator() = default; ~ExceptionGenerator() = delete; static void HandleCrash(int signo, siginfo_t* siginfo, void* context) { auto state = Get(); ExceptionInformation info = {}; info.siginfo_address = FromPointerCast(siginfo); info.context_address = FromPointerCast(context); info.thread_id = syscall(SYS_gettid); auto info_addr = FromPointerCast(&info); ASSERT_TRUE(LoggingWriteFile(state->out_, &info_addr, sizeof(info_addr))); CheckedReadFileAtEOF(state->in_); Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr); } FileHandle in_; FileHandle out_; }; constexpr char kAllowedAnnotationName[] = "name_of_allowed_anno"; constexpr char kAllowedAnnotationValue[] = "some_value"; constexpr char kNonAllowedAnnotationName[] = "non_allowed_anno"; constexpr char kNonAllowedAnnotationValue[] = "private_annotation"; constexpr char kSensitiveStackData[] = "sensitive_stack_data"; struct ChildTestAddresses { VMAddress string_address; VMAddress module_address; VMAddress non_module_address; VMAddress code_pointer_address; VMAddress code_pointer_value; }; void ChildTestFunction() { FileHandle in = StdioFileHandle(StdioStream::kStandardInput); FileHandle out = StdioFileHandle(StdioStream::kStandardOutput); static StringAnnotation<32> allowed_annotation(kAllowedAnnotationName); allowed_annotation.Set(kAllowedAnnotationValue); static StringAnnotation<32> non_allowed_annotation(kNonAllowedAnnotationName); non_allowed_annotation.Set(kNonAllowedAnnotationValue); char string_data[std::size(kSensitiveStackData)]; strcpy(string_data, kSensitiveStackData); void (*code_pointer)(void) = ChildTestFunction; ChildTestAddresses addrs = {}; addrs.string_address = FromPointerCast(string_data); addrs.module_address = FromPointerCast(ChildTestFunction); addrs.non_module_address = FromPointerCast(&addrs); addrs.code_pointer_address = FromPointerCast(&code_pointer); addrs.code_pointer_value = FromPointerCast(code_pointer); ASSERT_TRUE(LoggingWriteFile(out, &addrs, sizeof(addrs))); auto gen = ExceptionGenerator::Get(); ASSERT_TRUE(gen->Initialize(in, out)); __builtin_trap(); } CRASHPAD_CHILD_TEST_MAIN(ChildToBeSanitized) { ChildTestFunction(); NOTREACHED(); return EXIT_SUCCESS; } void ExpectAnnotations(ProcessSnapshot* snapshot, bool sanitized) { bool found_allowed = false; bool found_non_allowed = false; for (auto module : snapshot->Modules()) { for (const auto& anno : module->AnnotationObjects()) { if (anno.name == kAllowedAnnotationName) { found_allowed = true; } else if (anno.name == kNonAllowedAnnotationName) { found_non_allowed = true; } } } EXPECT_TRUE(found_allowed); if (sanitized) { EXPECT_FALSE(found_non_allowed); } else { EXPECT_TRUE(found_non_allowed); } } void ExpectProcessMemory(ProcessSnapshot* snapshot, VMAddress allowed_byte, bool sanitized) { auto memory = snapshot->Memory(); char out; EXPECT_TRUE(memory->Read(allowed_byte, 1, &out)); bool disallowed_read = memory->Read(allowed_byte + 1, 1, &out); if (sanitized) { EXPECT_FALSE(disallowed_read); } else { EXPECT_TRUE(disallowed_read); } } class StackSanitizationChecker : public MemorySnapshot::Delegate { public: StackSanitizationChecker() = default; ~StackSanitizationChecker() = default; void CheckStack(const MemorySnapshot* stack, const ChildTestAddresses& addrs, bool is_64_bit, bool sanitized) { stack_ = stack; addrs_ = addrs; is_64_bit_ = is_64_bit; sanitized_ = sanitized; EXPECT_TRUE(stack_->Read(this)); } // MemorySnapshot::Delegate bool MemorySnapshotDelegateRead(void* data, size_t size) override { // AddressSanitizer with use-after-return detection causes stack variables // to be allocated on the heap. #if !defined(ADDRESS_SANITIZER) size_t pointer_offset; if (!AssignIfInRange(&pointer_offset, addrs_.code_pointer_address - stack_->Address())) { ADD_FAILURE(); return false; } const auto data_c = static_cast(data); VMAddress pointer_value; if (is_64_bit_) { pointer_value = *reinterpret_cast(data_c + pointer_offset); } else { pointer_value = *reinterpret_cast(data_c + pointer_offset); } EXPECT_EQ(pointer_value, addrs_.code_pointer_value); size_t string_offset; if (!AssignIfInRange(&string_offset, addrs_.string_address - stack_->Address())) { ADD_FAILURE(); return false; } auto string = data_c + string_offset; if (sanitized_) { EXPECT_STRNE(string, kSensitiveStackData); } else { EXPECT_STREQ(string, kSensitiveStackData); } #endif // !ADDRESS_SANITIZER return true; } private: const MemorySnapshot* stack_; ChildTestAddresses addrs_; bool is_64_bit_; bool sanitized_; }; void ExpectStackData(ProcessSnapshot* snapshot, const ChildTestAddresses& addrs, bool sanitized) { const ThreadSnapshot* crasher = nullptr; for (const auto thread : snapshot->Threads()) { if (thread->ThreadID() == snapshot->Exception()->ThreadID()) { crasher = thread; break; } } ASSERT_TRUE(crasher); const MemorySnapshot* stack = crasher->Stack(); StackSanitizationChecker().CheckStack( stack, addrs, crasher->Context()->Is64Bit(), sanitized); } class SanitizeTest : public MultiprocessExec { public: SanitizeTest() : MultiprocessExec() { SetChildTestMainFunction("ChildToBeSanitized"); SetExpectedChildTerminationBuiltinTrap(); } SanitizeTest(const SanitizeTest&) = delete; SanitizeTest& operator=(const SanitizeTest&) = delete; ~SanitizeTest() = default; private: void MultiprocessParent() { ChildTestAddresses addrs = {}; ASSERT_TRUE( LoggingReadFileExactly(ReadPipeHandle(), &addrs, sizeof(addrs))); VMAddress exception_info_addr; ASSERT_TRUE(LoggingReadFileExactly( ReadPipeHandle(), &exception_info_addr, sizeof(exception_info_addr))); DirectPtraceConnection connection; ASSERT_TRUE(connection.Initialize(ChildProcess())); ProcessSnapshotLinux snapshot; ASSERT_TRUE(snapshot.Initialize(&connection)); ASSERT_TRUE(snapshot.InitializeException(exception_info_addr)); ExpectAnnotations(&snapshot, /* sanitized= */ false); ExpectStackData(&snapshot, addrs, /* sanitized= */ false); ExpectProcessMemory(&snapshot, addrs.string_address, /* sanitized= */ false); auto allowed_annotations = std::make_unique>(); allowed_annotations->push_back(kAllowedAnnotationName); auto allowed_memory_ranges = std::make_unique>>(); allowed_memory_ranges->push_back( std::make_pair(addrs.string_address, addrs.string_address + 1)); ProcessSnapshotSanitized sanitized; ASSERT_TRUE(sanitized.Initialize(&snapshot, std::move(allowed_annotations), std::move(allowed_memory_ranges), addrs.module_address, true)); ExpectAnnotations(&sanitized, /* sanitized= */ true); ExpectStackData(&sanitized, addrs, /* sanitized= */ true); ExpectProcessMemory(&sanitized, addrs.string_address, /* sanitized= */ true); ProcessSnapshotSanitized screened_snapshot; EXPECT_FALSE(screened_snapshot.Initialize( &snapshot, nullptr, nullptr, addrs.non_module_address, false)); } }; TEST(ProcessSnapshotSanitized, Sanitize) { SanitizeTest test; test.Run(); } } // namespace } // namespace test } // namespace crashpad