| // 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 <string.h> |
| |
| #include <iterator> |
| |
| #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 <sys/syscall.h> |
| |
| #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<VMAddress>(siginfo); |
| info.context_address = FromPointerCast<VMAddress>(context); |
| info.thread_id = syscall(SYS_gettid); |
| |
| auto info_addr = FromPointerCast<VMAddress>(&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 kAllowedAnnotationNamePattern[] = "name_of_another_*"; |
| constexpr char kAllowedAnnotationNamePatternActual[] = "name_of_another_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> allowed_matched_annotation( |
| kAllowedAnnotationNamePatternActual); |
| allowed_matched_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<VMAddress>(string_data); |
| addrs.module_address = FromPointerCast<VMAddress>(ChildTestFunction); |
| addrs.non_module_address = FromPointerCast<VMAddress>(&addrs); |
| addrs.code_pointer_address = FromPointerCast<VMAddress>(&code_pointer); |
| addrs.code_pointer_value = FromPointerCast<VMAddress>(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_IN_MIGRATION(); |
| return EXIT_SUCCESS; |
| } |
| |
| void ExpectAnnotations(ProcessSnapshot* snapshot, bool sanitized) { |
| bool found_allowed = false; |
| bool found_matched_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; |
| } |
| if (anno.name == kAllowedAnnotationNamePatternActual) { |
| found_matched_allowed = true; |
| } else if (anno.name == kNonAllowedAnnotationName) { |
| found_non_allowed = true; |
| } |
| } |
| } |
| |
| EXPECT_TRUE(found_allowed); |
| EXPECT_TRUE(found_matched_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<char*>(data); |
| VMAddress pointer_value; |
| if (is_64_bit_) { |
| pointer_value = *reinterpret_cast<uint64_t*>(data_c + pointer_offset); |
| } else { |
| pointer_value = *reinterpret_cast<uint32_t*>(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<std::vector<std::string>>(); |
| allowed_annotations->push_back(kAllowedAnnotationName); |
| allowed_annotations->push_back(kAllowedAnnotationNamePattern); |
| |
| auto allowed_memory_ranges = |
| std::make_unique<std::vector<std::pair<VMAddress, VMAddress>>>(); |
| 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 |