// Copyright 2015 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/crashpad_info_client_options.h" #include "base/auto_reset.h" #include "base/files/file_path.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "client/crashpad_info.h" #include "gtest/gtest.h" #include "test/errors.h" #include "test/scoped_module_handle.h" #include "test/test_paths.h" #if BUILDFLAG(IS_APPLE) #include #include "snapshot/mac/process_snapshot_mac.h" #elif BUILDFLAG(IS_WIN) #include #include "snapshot/win/process_snapshot_win.h" #elif BUILDFLAG(IS_FUCHSIA) #include #include "snapshot/fuchsia/process_snapshot_fuchsia.h" #endif namespace crashpad { namespace test { namespace { TEST(CrashpadInfoClientOptions, TriStateFromCrashpadInfo) { EXPECT_EQ(CrashpadInfoClientOptions::TriStateFromCrashpadInfo(0), TriState::kUnset); EXPECT_EQ(CrashpadInfoClientOptions::TriStateFromCrashpadInfo(1), TriState::kEnabled); EXPECT_EQ(CrashpadInfoClientOptions::TriStateFromCrashpadInfo(2), TriState::kDisabled); // These will produce log messages but should result in kUnset being returned. EXPECT_EQ(CrashpadInfoClientOptions::TriStateFromCrashpadInfo(3), TriState::kUnset); EXPECT_EQ(CrashpadInfoClientOptions::TriStateFromCrashpadInfo(4), TriState::kUnset); EXPECT_EQ(CrashpadInfoClientOptions::TriStateFromCrashpadInfo(0xff), TriState::kUnset); } class ScopedUnsetCrashpadInfoOptions { public: explicit ScopedUnsetCrashpadInfoOptions(CrashpadInfo* crashpad_info) : crashpad_info_(crashpad_info) { } ScopedUnsetCrashpadInfoOptions(const ScopedUnsetCrashpadInfoOptions&) = delete; ScopedUnsetCrashpadInfoOptions& operator=( const ScopedUnsetCrashpadInfoOptions&) = delete; ~ScopedUnsetCrashpadInfoOptions() { crashpad_info_->set_crashpad_handler_behavior(TriState::kUnset); crashpad_info_->set_system_crash_reporter_forwarding(TriState::kUnset); crashpad_info_->set_gather_indirectly_referenced_memory(TriState::kUnset, 0); } private: CrashpadInfo* crashpad_info_; }; CrashpadInfoClientOptions SelfProcessSnapshotAndGetCrashpadOptions() { #if BUILDFLAG(IS_APPLE) ProcessSnapshotMac process_snapshot; EXPECT_TRUE(process_snapshot.Initialize(mach_task_self())); #elif BUILDFLAG(IS_WIN) ProcessSnapshotWin process_snapshot; EXPECT_TRUE(process_snapshot.Initialize( GetCurrentProcess(), ProcessSuspensionState::kRunning, 0, 0)); #elif BUILDFLAG(IS_FUCHSIA) ProcessSnapshotFuchsia process_snapshot; EXPECT_TRUE(process_snapshot.Initialize(*zx::process::self())); #else #error Port. #endif // BUILDFLAG(IS_APPLE) CrashpadInfoClientOptions options; process_snapshot.GetCrashpadOptions(&options); return options; } TEST(CrashpadInfoClientOptions, OneModule) { // Make sure that the initial state has all values unset. auto options = SelfProcessSnapshotAndGetCrashpadOptions(); EXPECT_EQ(options.crashpad_handler_behavior, TriState::kUnset); EXPECT_EQ(options.system_crash_reporter_forwarding, TriState::kUnset); EXPECT_EQ(options.gather_indirectly_referenced_memory, TriState::kUnset); EXPECT_EQ(options.indirectly_referenced_memory_cap, 0u); CrashpadInfo* crashpad_info = CrashpadInfo::GetCrashpadInfo(); ASSERT_TRUE(crashpad_info); { ScopedUnsetCrashpadInfoOptions unset(crashpad_info); crashpad_info->set_crashpad_handler_behavior(TriState::kEnabled); options = SelfProcessSnapshotAndGetCrashpadOptions(); EXPECT_EQ(options.crashpad_handler_behavior, TriState::kEnabled); EXPECT_EQ(options.system_crash_reporter_forwarding, TriState::kUnset); EXPECT_EQ(options.gather_indirectly_referenced_memory, TriState::kUnset); EXPECT_EQ(options.indirectly_referenced_memory_cap, 0u); } { ScopedUnsetCrashpadInfoOptions unset(crashpad_info); crashpad_info->set_system_crash_reporter_forwarding(TriState::kDisabled); options = SelfProcessSnapshotAndGetCrashpadOptions(); EXPECT_EQ(options.crashpad_handler_behavior, TriState::kUnset); EXPECT_EQ(options.system_crash_reporter_forwarding, TriState::kDisabled); EXPECT_EQ(options.gather_indirectly_referenced_memory, TriState::kUnset); EXPECT_EQ(options.indirectly_referenced_memory_cap, 0u); } { ScopedUnsetCrashpadInfoOptions unset(crashpad_info); crashpad_info->set_gather_indirectly_referenced_memory(TriState::kEnabled, 1234); options = SelfProcessSnapshotAndGetCrashpadOptions(); EXPECT_EQ(options.crashpad_handler_behavior, TriState::kUnset); EXPECT_EQ(options.system_crash_reporter_forwarding, TriState::kUnset); EXPECT_EQ(options.gather_indirectly_referenced_memory, TriState::kEnabled); EXPECT_LE(options.indirectly_referenced_memory_cap, 1234u); } } TEST(CrashpadInfoClientOptions, TwoModules) { // Open the module, which has its own CrashpadInfo structure. base::FilePath module_path = TestPaths::BuildArtifact(FILE_PATH_LITERAL("snapshot"), FILE_PATH_LITERAL("module"), TestPaths::FileType::kLoadableModule); #if BUILDFLAG(IS_POSIX) ScopedModuleHandle module( dlopen(module_path.value().c_str(), RTLD_LAZY | RTLD_LOCAL)); ASSERT_TRUE(module.valid()) << "dlopen " << module_path.value() << ": " << dlerror(); #elif BUILDFLAG(IS_WIN) ScopedModuleHandle module(LoadLibrary(module_path.value().c_str())); ASSERT_TRUE(module.valid()) << "LoadLibrary " << base::WideToUTF8(module_path.value()) << ": " << ErrorMessage(); #else #error Port. #endif // BUILDFLAG(IS_POSIX) // Get the function pointer from the module. This wraps GetCrashpadInfo(), but // because it runs in the module, it returns the remote module’s CrashpadInfo // structure. CrashpadInfo* (*TestModule_GetCrashpadInfo)() = module.LookUpSymbol("TestModule_GetCrashpadInfo"); ASSERT_TRUE(TestModule_GetCrashpadInfo); auto options = SelfProcessSnapshotAndGetCrashpadOptions(); // Make sure that the initial state has all values unset. EXPECT_EQ(options.crashpad_handler_behavior, TriState::kUnset); EXPECT_EQ(options.system_crash_reporter_forwarding, TriState::kUnset); EXPECT_EQ(options.gather_indirectly_referenced_memory, TriState::kUnset); // Get both CrashpadInfo structures. CrashpadInfo* local_crashpad_info = CrashpadInfo::GetCrashpadInfo(); ASSERT_TRUE(local_crashpad_info); CrashpadInfo* remote_crashpad_info = TestModule_GetCrashpadInfo(); ASSERT_TRUE(remote_crashpad_info); { ScopedUnsetCrashpadInfoOptions unset_local(local_crashpad_info); ScopedUnsetCrashpadInfoOptions unset_remote(remote_crashpad_info); // When only one module sets a value, it applies to the entire process. remote_crashpad_info->set_crashpad_handler_behavior(TriState::kEnabled); options = SelfProcessSnapshotAndGetCrashpadOptions(); EXPECT_EQ(options.crashpad_handler_behavior, TriState::kEnabled); EXPECT_EQ(options.system_crash_reporter_forwarding, TriState::kUnset); EXPECT_EQ(options.gather_indirectly_referenced_memory, TriState::kUnset); // When more than one module sets a value, the first one in the module list // applies to the process. The local module should appear before the remote // module, because the local module loaded the remote module. local_crashpad_info->set_crashpad_handler_behavior(TriState::kDisabled); options = SelfProcessSnapshotAndGetCrashpadOptions(); EXPECT_EQ(options.crashpad_handler_behavior, TriState::kDisabled); EXPECT_EQ(options.system_crash_reporter_forwarding, TriState::kUnset); EXPECT_EQ(options.gather_indirectly_referenced_memory, TriState::kUnset); } { ScopedUnsetCrashpadInfoOptions unset_local(local_crashpad_info); ScopedUnsetCrashpadInfoOptions unset_remote(remote_crashpad_info); // When only one module sets a value, it applies to the entire process. remote_crashpad_info->set_system_crash_reporter_forwarding( TriState::kDisabled); options = SelfProcessSnapshotAndGetCrashpadOptions(); EXPECT_EQ(options.crashpad_handler_behavior, TriState::kUnset); EXPECT_EQ(options.system_crash_reporter_forwarding, TriState::kDisabled); EXPECT_EQ(options.gather_indirectly_referenced_memory, TriState::kUnset); // When more than one module sets a value, the first one in the module list // applies to the process. The local module should appear before the remote // module, because the local module loaded the remote module. local_crashpad_info->set_system_crash_reporter_forwarding( TriState::kEnabled); options = SelfProcessSnapshotAndGetCrashpadOptions(); EXPECT_EQ(options.crashpad_handler_behavior, TriState::kUnset); EXPECT_EQ(options.system_crash_reporter_forwarding, TriState::kEnabled); EXPECT_EQ(options.gather_indirectly_referenced_memory, TriState::kUnset); } } class CrashpadInfoSizes_ClientOptions : public testing::TestWithParam {}; // UBSan detects a function type mismatch when calling // TestModule_GetCrashpadInfo since the expected function signature should // return a CrashpadInfo* but the actual TestModule_GetCrashpadInfo defined for // the test returns a TestCrashpadInfo*. CrashpadInfo is a struct with its // members set as private and TestCrashpadInfo is a POD meant to replicate the // layout of CrashpadInfo byte-for-byte. Note this is intentional since the // whole point of the test is to exercise the snapshot reader’s ability to // handle CrashpadInfo. #if defined(__clang__) [[clang::no_sanitize("function")]] #endif inline CrashpadInfo* CallGetCrashpadInfo(CrashpadInfo* (*func)()) { return func(); } TEST_P(CrashpadInfoSizes_ClientOptions, DifferentlySizedStruct) { base::FilePath::StringType artifact(FILE_PATH_LITERAL("module_")); artifact += GetParam(); // Open the module, which has a CrashpadInfo-like structure that’s smaller or // larger than the current version’s CrashpadInfo structure defined in the // client library. base::FilePath module_path = TestPaths::BuildArtifact(FILE_PATH_LITERAL("snapshot"), artifact, TestPaths::FileType::kLoadableModule); #if BUILDFLAG(IS_POSIX) ScopedModuleHandle module( dlopen(module_path.value().c_str(), RTLD_LAZY | RTLD_LOCAL)); ASSERT_TRUE(module.valid()) << "dlopen " << module_path.value() << ": " << dlerror(); #elif BUILDFLAG(IS_WIN) ScopedModuleHandle module(LoadLibrary(module_path.value().c_str())); ASSERT_TRUE(module.valid()) << "LoadLibrary " << base::WideToUTF8(module_path.value()) << ": " << ErrorMessage(); #else #error Port. #endif // BUILDFLAG(IS_POSIX) // Get the function pointer from the module. CrashpadInfo* (*TestModule_GetCrashpadInfo)() = module.LookUpSymbol("TestModule_GetCrashpadInfo"); ASSERT_TRUE(TestModule_GetCrashpadInfo); auto options = SelfProcessSnapshotAndGetCrashpadOptions(); // Make sure that the initial state has all values unset. EXPECT_EQ(options.crashpad_handler_behavior, TriState::kUnset); EXPECT_EQ(options.system_crash_reporter_forwarding, TriState::kUnset); EXPECT_EQ(options.gather_indirectly_referenced_memory, TriState::kUnset); // Get the remote CrashpadInfo structure. CrashpadInfo* remote_crashpad_info = CallGetCrashpadInfo(TestModule_GetCrashpadInfo); ASSERT_TRUE(remote_crashpad_info); { ScopedUnsetCrashpadInfoOptions unset_remote(remote_crashpad_info); // Make sure that a change in the remote structure can be read back out, // even though it’s a different size. remote_crashpad_info->set_crashpad_handler_behavior(TriState::kEnabled); remote_crashpad_info->set_system_crash_reporter_forwarding( TriState::kDisabled); options = SelfProcessSnapshotAndGetCrashpadOptions(); EXPECT_EQ(options.crashpad_handler_behavior, TriState::kEnabled); EXPECT_EQ(options.system_crash_reporter_forwarding, TriState::kDisabled); EXPECT_EQ(options.gather_indirectly_referenced_memory, TriState::kUnset); } { ScopedUnsetCrashpadInfoOptions unset_remote(remote_crashpad_info); // Make sure that the portion of the remote structure lying beyond its // declared size reads as zero. // 4 = offsetof(CrashpadInfo, size_), but it’s private. uint32_t* size = reinterpret_cast( reinterpret_cast(remote_crashpad_info) + 4); // 21 = offsetof(CrashpadInfo, system_crash_reporter_forwarding_, but it’s // private. base::AutoReset reset_size(size, 21); // system_crash_reporter_forwarding_ is now beyond the struct’s declared // size. Storage has actually been allocated for it, so it’s safe to set // here. remote_crashpad_info->set_crashpad_handler_behavior(TriState::kEnabled); remote_crashpad_info->set_system_crash_reporter_forwarding( TriState::kDisabled); // Since system_crash_reporter_forwarding_ is beyond the struct’s declared // size, it should read as 0 (TriState::kUnset), even though it was set to // a different value above. options = SelfProcessSnapshotAndGetCrashpadOptions(); EXPECT_EQ(options.crashpad_handler_behavior, TriState::kEnabled); EXPECT_EQ(options.system_crash_reporter_forwarding, TriState::kUnset); EXPECT_EQ(options.gather_indirectly_referenced_memory, TriState::kUnset); } } INSTANTIATE_TEST_SUITE_P(CrashpadInfoSizes_ClientOptions, CrashpadInfoSizes_ClientOptions, testing::Values(FILE_PATH_LITERAL("small"), FILE_PATH_LITERAL("large"))); } // namespace } // namespace test } // namespace crashpad