// Copyright 2014 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 "snapshot/mac/mach_o_image_annotations_reader.h" #include #include #include #include #include #include #include #include #include #include #include "base/files/file_path.h" #include "base/macros.h" #include "client/annotation.h" #include "client/annotation_list.h" #include "client/crashpad_info.h" #include "client/simple_string_dictionary.h" #include "gtest/gtest.h" #include "snapshot/mac/process_reader_mac.h" #include "test/errors.h" #include "test/mac/mach_errors.h" #include "test/mac/mach_multiprocess.h" #include "test/test_paths.h" #include "util/file/file_io.h" #include "util/mac/mac_util.h" #include "util/mach/exc_server_variants.h" #include "util/mach/exception_ports.h" #include "util/mach/mach_extensions.h" #include "util/mach/mach_message.h" #include "util/mach/mach_message_server.h" namespace crashpad { namespace test { namespace { // \return The path to crashpad_snapshot_test_module_crashy_initializer.so base::FilePath ModuleWithCrashyInitializer() { return TestPaths::BuildArtifact("snapshot", "module_crashy_initializer", TestPaths::FileType::kLoadableModule); } //! \return The path to the crashpad_snapshot_test_no_op executable. base::FilePath NoOpExecutable() { return TestPaths::BuildArtifact( "snapshot", "no_op", TestPaths::FileType::kExecutable); } class TestMachOImageAnnotationsReader final : public MachMultiprocess, public UniversalMachExcServer::Interface { public: enum TestType { // Don’t crash, just test the CrashpadInfo interface. kDontCrash = 0, // The child process should crash by calling abort(). The parent verifies // that the system libraries set the expected annotations. // // This test verifies that the message field in crashreporter_annotations_t // can be recovered. Either 10.10.2 Libc-1044.1.2/stdlib/FreeBSD/abort.c // abort() or 10.10.2 Libc-1044.10.1/sys/_libc_fork_child.c // _libc_fork_child() calls CRSetCrashLogMessage() to set the message field. kCrashAbort, // The child process should crash at module initialization time, when dyld // will have set an annotation matching the path of the module being // initialized. // // This test exists to verify that the message2 field in // crashreporter_annotations_t can be recovered. 10.10.2 // dyld-353.2.1/src/ImageLoaderMachO.cpp // ImageLoaderMachO::doInitialization() calls CRSetCrashLogMessage2() to set // the message2 field. kCrashModuleInitialization, // The child process should crash by setting DYLD_INSERT_LIBRARIES to // contain a nonexistent library. The parent verifies that dyld sets the // expected annotations. kCrashDyld, }; explicit TestMachOImageAnnotationsReader(TestType test_type) : MachMultiprocess(), UniversalMachExcServer::Interface(), test_type_(test_type) { switch (test_type_) { case kDontCrash: // SetExpectedChildTermination(kTerminationNormal, EXIT_SUCCESS) is the // default. break; case kCrashAbort: SetExpectedChildTermination(kTerminationSignal, SIGABRT); break; case kCrashModuleInitialization: SetExpectedChildTerminationBuiltinTrap(); break; case kCrashDyld: // Prior to 10.12, dyld fatal errors result in the execution of an // int3 instruction on x86 and a trap instruction on ARM, both of // which raise SIGTRAP. 10.9.5 dyld-239.4/src/dyldStartup.s // _dyld_fatal_error. This changed in 10.12 to use // abort_with_payload(), which appears as SIGABRT to a waiting parent. SetExpectedChildTermination( kTerminationSignal, MacOSVersionNumber() < 10'12'00 ? SIGTRAP : SIGABRT); break; } } ~TestMachOImageAnnotationsReader() {} // UniversalMachExcServer::Interface: kern_return_t CatchMachException(exception_behavior_t behavior, exception_handler_t exception_port, thread_t thread, task_t task, exception_type_t exception, const mach_exception_data_type_t* code, mach_msg_type_number_t code_count, thread_state_flavor_t* flavor, ConstThreadState old_state, mach_msg_type_number_t old_state_count, thread_state_t new_state, mach_msg_type_number_t* new_state_count, const mach_msg_trailer_t* trailer, bool* destroy_complex_request) override { *destroy_complex_request = true; if (test_type_ != kCrashDyld) { // In 10.12.1 and later, the task port will not match ChildTask() in the // kCrashDyld case, because kCrashDyld uses execl(), which results in a // new task port being assigned. EXPECT_EQ(task, ChildTask()); } // The process ID should always compare favorably. pid_t task_pid; kern_return_t kr = pid_for_task(task, &task_pid); EXPECT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "pid_for_task"); EXPECT_EQ(task_pid, ChildPID()); ProcessReaderMac process_reader; bool rv = process_reader.Initialize(task); if (!rv) { ADD_FAILURE(); } else { const std::vector& modules = process_reader.Modules(); std::vector all_annotations_vector; for (const ProcessReaderMac::Module& module : modules) { if (module.reader) { MachOImageAnnotationsReader module_annotations_reader( &process_reader, module.reader, module.name); std::vector module_annotations_vector = module_annotations_reader.Vector(); all_annotations_vector.insert(all_annotations_vector.end(), module_annotations_vector.begin(), module_annotations_vector.end()); } else { EXPECT_TRUE(module.reader); } } // Mac OS X 10.6 doesn’t have support for CrashReporter annotations // (CrashReporterClient.h), so don’t look for any special annotations in // that version. const int macos_version_number = MacOSVersionNumber(); if (macos_version_number > 10'07'00) { EXPECT_GE(all_annotations_vector.size(), 1u); std::string expected_annotation; switch (test_type_) { case kCrashAbort: // The child process calls abort(), so the expected annotation // reflects this, with a string set by 10.7.5 // Libc-763.13/stdlib/abort-fbsd.c abort(). This string is still // present in 10.9.5 Libc-997.90.3/stdlib/FreeBSD/abort.c abort(), // but because abort() tests to see if a message is already set and // something else in Libc will have set a message, this string is // not the expectation on 10.9 or higher. Instead, after fork(), the // child process has a message indicating that a fork() without // exec() occurred. See 10.9.5 Libc-997.90.3/sys/_libc_fork_child.c // _libc_fork_child(). expected_annotation = macos_version_number <= 10'08'00 ? "abort() called" : "crashed on child side of fork pre-exec"; break; case kCrashModuleInitialization: // This message is set by dyld-353.2.1/src/ImageLoaderMachO.cpp // ImageLoaderMachO::doInitialization(). expected_annotation = ModuleWithCrashyInitializer().value(); break; case kCrashDyld: // This is independent of dyld’s error_string, which is tested // below. expected_annotation = "dyld: launch, loading dependent libraries"; break; default: ADD_FAILURE(); break; } bool found = false; for (const std::string& annotation : all_annotations_vector) { // Look for the expectation as a leading susbtring, because the actual // string that dyld uses will have the contents of the // DYLD_INSERT_LIBRARIES environment variable appended to it on OS X // 10.10. if (annotation.substr(0, expected_annotation.length()) == expected_annotation) { found = true; break; } } EXPECT_TRUE(found) << expected_annotation; } // dyld exposes its error_string at least as far back as Mac OS X 10.4. if (test_type_ == kCrashDyld) { static constexpr char kExpectedAnnotation[] = "could not load inserted library"; size_t expected_annotation_length = strlen(kExpectedAnnotation); bool found = false; for (const std::string& annotation : all_annotations_vector) { // Look for the expectation as a leading substring, because the actual // string will contain the library’s pathname and, on OS X 10.9 and // later, a reason. if (annotation.substr(0, expected_annotation_length) == kExpectedAnnotation) { found = true; break; } } EXPECT_TRUE(found) << kExpectedAnnotation; } } ExcServerCopyState( behavior, old_state, old_state_count, new_state, new_state_count); return ExcServerSuccessfulReturnValue(exception, behavior, false); } private: // MachMultiprocess: void MachMultiprocessParent() override { ProcessReaderMac process_reader; ASSERT_TRUE(process_reader.Initialize(ChildTask())); // Wait for the child process to indicate that it’s done setting up its // annotations via the CrashpadInfo interface. char c; CheckedReadFileExactly(ReadPipeHandle(), &c, sizeof(c)); // Verify the “simple map” and object-based annotations set via the // CrashpadInfo interface. const std::vector& modules = process_reader.Modules(); std::map all_annotations_simple_map; std::vector all_annotations; for (const ProcessReaderMac::Module& module : modules) { MachOImageAnnotationsReader module_annotations_reader( &process_reader, module.reader, module.name); std::map module_annotations_simple_map = module_annotations_reader.SimpleMap(); all_annotations_simple_map.insert(module_annotations_simple_map.begin(), module_annotations_simple_map.end()); std::vector annotations = module_annotations_reader.AnnotationsList(); all_annotations.insert( all_annotations.end(), annotations.begin(), annotations.end()); } EXPECT_GE(all_annotations_simple_map.size(), 5u); EXPECT_EQ(all_annotations_simple_map["#TEST# pad"], "crash"); EXPECT_EQ(all_annotations_simple_map["#TEST# key"], "value"); EXPECT_EQ(all_annotations_simple_map["#TEST# x"], "y"); EXPECT_EQ(all_annotations_simple_map["#TEST# longer"], "shorter"); EXPECT_EQ(all_annotations_simple_map["#TEST# empty_value"], ""); EXPECT_EQ(all_annotations.size(), 3u); bool saw_same_name_3 = false, saw_same_name_4 = false; for (const auto& annotation : all_annotations) { EXPECT_EQ(annotation.type, static_cast(Annotation::Type::kString)); std::string value(reinterpret_cast(annotation.value.data()), annotation.value.size()); if (annotation.name == "#TEST# one") { EXPECT_EQ(value, "moocow"); } else if (annotation.name == "#TEST# same-name") { if (value == "same-name 3") { EXPECT_FALSE(saw_same_name_3); saw_same_name_3 = true; } else if (value == "same-name 4") { EXPECT_FALSE(saw_same_name_4); saw_same_name_4 = true; } else { ADD_FAILURE() << "unexpected annotation value " << value; } } else { ADD_FAILURE() << "unexpected annotation " << annotation.name; } } // Tell the child process that it’s permitted to crash. CheckedWriteFile(WritePipeHandle(), &c, sizeof(c)); if (test_type_ != kDontCrash) { // Handle the child’s crash. Further validation will be done in // CatchMachException(). UniversalMachExcServer universal_mach_exc_server(this); mach_msg_return_t mr = MachMessageServer::Run(&universal_mach_exc_server, LocalPort(), MACH_MSG_OPTION_NONE, MachMessageServer::kOneShot, MachMessageServer::kReceiveLargeError, kMachMessageTimeoutWaitIndefinitely); EXPECT_EQ(mr, MACH_MSG_SUCCESS) << MachErrorMessage(mr, "MachMessageServer::Run"); } } void MachMultiprocessChild() override { CrashpadInfo* crashpad_info = CrashpadInfo::GetCrashpadInfo(); // This is “leaked” to crashpad_info. SimpleStringDictionary* simple_annotations = new SimpleStringDictionary(); simple_annotations->SetKeyValue("#TEST# pad", "break"); simple_annotations->SetKeyValue("#TEST# key", "value"); simple_annotations->SetKeyValue("#TEST# pad", "crash"); simple_annotations->SetKeyValue("#TEST# x", "y"); simple_annotations->SetKeyValue("#TEST# longer", "shorter"); simple_annotations->SetKeyValue("#TEST# empty_value", ""); crashpad_info->set_simple_annotations(simple_annotations); AnnotationList::Register(); // This is “leaked” to crashpad_info. static StringAnnotation<32> test_annotation_one{"#TEST# one"}; static StringAnnotation<32> test_annotation_two{"#TEST# two"}; static StringAnnotation<32> test_annotation_three{"#TEST# same-name"}; static StringAnnotation<32> test_annotation_four{"#TEST# same-name"}; test_annotation_one.Set("moocow"); test_annotation_two.Set("this will be cleared"); test_annotation_three.Set("same-name 3"); test_annotation_four.Set("same-name 4"); test_annotation_two.Clear(); // Tell the parent that the environment has been set up. char c = '\0'; CheckedWriteFile(WritePipeHandle(), &c, sizeof(c)); // Wait for the parent to indicate that it’s safe to crash. CheckedReadFileExactly(ReadPipeHandle(), &c, sizeof(c)); // Direct an exception message to the exception server running in the // parent. ExceptionPorts exception_ports(ExceptionPorts::kTargetTypeTask, mach_task_self()); ASSERT_TRUE(exception_ports.SetExceptionPort( EXC_MASK_CRASH, RemotePort(), EXCEPTION_DEFAULT, THREAD_STATE_NONE)); switch (test_type_) { case kDontCrash: { break; } case kCrashAbort: { abort(); } case kCrashModuleInitialization: { // Load a module that crashes while executing a module initializer. void* dl_handle = dlopen(ModuleWithCrashyInitializer().value().c_str(), RTLD_LAZY | RTLD_LOCAL); // This should have crashed in the dlopen(). If dlopen() failed, the // ASSERT_NE() will show the message. If it succeeded without crashing, // the FAIL() will fail the test. ASSERT_NE(dl_handle, nullptr) << dlerror(); FAIL(); } case kCrashDyld: { // Set DYLD_INSERT_LIBRARIES to contain a library that does not exist. // Unable to load it, dyld will abort with a fatal error. ASSERT_EQ( setenv( "DYLD_INSERT_LIBRARIES", "/var/empty/NoDirectory/NoLibrary", 1), 0) << ErrnoMessage("setenv"); // The actual executable doesn’t matter very much, because dyld won’t // ever launch it. It just needs to be an executable that uses dyld as // its LC_LOAD_DYLINKER (all normal executables do). A custom no-op // executable is provided because DYLD_INSERT_LIBRARIES does not work // with system executables on OS X 10.11 due to System Integrity // Protection. base::FilePath no_op_executable = NoOpExecutable(); ASSERT_EQ(execl(no_op_executable.value().c_str(), no_op_executable.BaseName().value().c_str(), nullptr), 0) << ErrnoMessage("execl"); break; } default: break; } } TestType test_type_; DISALLOW_COPY_AND_ASSIGN(TestMachOImageAnnotationsReader); }; TEST(MachOImageAnnotationsReader, DontCrash) { TestMachOImageAnnotationsReader test_mach_o_image_annotations_reader( TestMachOImageAnnotationsReader::kDontCrash); test_mach_o_image_annotations_reader.Run(); } TEST(MachOImageAnnotationsReader, CrashAbort) { TestMachOImageAnnotationsReader test_mach_o_image_annotations_reader( TestMachOImageAnnotationsReader::kCrashAbort); test_mach_o_image_annotations_reader.Run(); } #if defined(ADDRESS_SANITIZER) // https://crbug.com/844396 #define MAYBE_CrashModuleInitialization DISABLED_CrashModuleInitialization #else #define MAYBE_CrashModuleInitialization CrashModuleInitialization #endif TEST(MachOImageAnnotationsReader, MAYBE_CrashModuleInitialization) { TestMachOImageAnnotationsReader test_mach_o_image_annotations_reader( TestMachOImageAnnotationsReader::kCrashModuleInitialization); test_mach_o_image_annotations_reader.Run(); } TEST(MachOImageAnnotationsReader, CrashDyld) { TestMachOImageAnnotationsReader test_mach_o_image_annotations_reader( TestMachOImageAnnotationsReader::kCrashDyld); test_mach_o_image_annotations_reader.Run(); } } // namespace } // namespace test } // namespace crashpad