| // 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 <dlfcn.h> |
| #include <mach/mach.h> |
| #include <signal.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <map> |
| #include <string> |
| #include <vector> |
| |
| #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: |
| // This crash is triggered by __builtin_trap(), which shows up as |
| // SIGILL. |
| SetExpectedChildTermination(kTerminationSignal, SIGILL); |
| 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, MacOSXMinorVersion() < 12 ? 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<ProcessReaderMac::Module>& modules = |
| process_reader.Modules(); |
| std::vector<std::string> all_annotations_vector; |
| for (const ProcessReaderMac::Module& module : modules) { |
| if (module.reader) { |
| MachOImageAnnotationsReader module_annotations_reader( |
| &process_reader, module.reader, module.name); |
| std::vector<std::string> 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. |
| int mac_os_x_minor_version = MacOSXMinorVersion(); |
| if (mac_os_x_minor_version > 7) { |
| 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 = |
| mac_os_x_minor_version <= 8 |
| ? "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<ProcessReaderMac::Module>& modules = |
| process_reader.Modules(); |
| std::map<std::string, std::string> all_annotations_simple_map; |
| std::vector<AnnotationSnapshot> all_annotations; |
| for (const ProcessReaderMac::Module& module : modules) { |
| MachOImageAnnotationsReader module_annotations_reader( |
| &process_reader, module.reader, module.name); |
| std::map<std::string, std::string> 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<AnnotationSnapshot> 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<uint16_t>(Annotation::Type::kString)); |
| std::string value(reinterpret_cast<const char*>(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(); |
| break; |
| } |
| |
| 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(); |
| break; |
| } |
| |
| 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 |