// Copyright 2017 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 "test/mac/exception_swallower.h" #include #include #include #include #include "base/apple/scoped_mach_port.h" #include "base/check_op.h" #include "base/strings/stringprintf.h" #include "handler/mac/exception_handler_server.h" #include "util/mach/bootstrap.h" #include "util/mach/exc_server_variants.h" #include "util/mach/exception_ports.h" #include "util/mach/mach_extensions.h" #include "util/misc/random_string.h" #include "util/thread/thread.h" namespace crashpad { namespace test { namespace { constexpr char kServiceEnvironmentVariable[] = "CRASHPAD_EXCEPTION_SWALLOWER_SERVICE"; ExceptionSwallower* g_exception_swallower; // Like getenv(), but fails a CHECK() if the underlying function fails. It’s not // considered a failure for |name| to be unset in the environment. In that case, // nullptr is returned. const char* CheckedGetenv(const char* name) { errno = 0; const char* value; PCHECK((value = getenv(name)) || errno == 0) << "getenv"; return value; } } // namespace class ExceptionSwallower::ExceptionSwallowerThread : public Thread, public UniversalMachExcServer::Interface { public: explicit ExceptionSwallowerThread( base::apple::ScopedMachReceiveRight receive_right) : Thread(), UniversalMachExcServer::Interface(), exception_handler_server_(std::move(receive_right), true), pid_(getpid()) { Start(); } ExceptionSwallowerThread(const ExceptionSwallowerThread&) = delete; ExceptionSwallowerThread& operator=(const ExceptionSwallowerThread&) = delete; ~ExceptionSwallowerThread() override {} void Stop() { exception_handler_server_.Stop(); } // Returns the process ID that the thread is running in. This is used to // detect misuses that place the exception swallower server thread and code // that wants its exceptions swallowed in the same process. pid_t ProcessID() const { return pid_; } private: // Thread: void ThreadMain() override { exception_handler_server_.Run(this); } // 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; // Swallow. ExcServerCopyState( behavior, old_state, old_state_count, new_state, new_state_count); return ExcServerSuccessfulReturnValue(exception, behavior, false); } ExceptionHandlerServer exception_handler_server_; pid_t pid_; }; ExceptionSwallower::ExceptionSwallower() : exception_swallower_thread_() { CHECK(!g_exception_swallower); g_exception_swallower = this; if (CheckedGetenv(kServiceEnvironmentVariable)) { // The environment variable is already set, so just proceed with the // existing service. This normally happens when the Google Test “threadsafe” // death test style is chosen, because the test child process will // re-execute code already run in the test parent process. See // https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md#death-test-styles. return; } std::string service_name = base::StringPrintf("org.chromium.crashpad.test.exception_swallower.%d.%s", getpid(), RandomString().c_str()); base::apple::ScopedMachReceiveRight receive_right( BootstrapCheckIn(service_name)); CHECK(receive_right.is_valid()); exception_swallower_thread_.reset( new ExceptionSwallowerThread(std::move(receive_right))); PCHECK(setenv(kServiceEnvironmentVariable, service_name.c_str(), 1) == 0) << "setenv"; } ExceptionSwallower::~ExceptionSwallower() { PCHECK(unsetenv(kServiceEnvironmentVariable) == 0) << "unsetenv"; exception_swallower_thread_->Stop(); exception_swallower_thread_->Join(); CHECK_EQ(g_exception_swallower, this); g_exception_swallower = nullptr; } // static void ExceptionSwallower::SwallowExceptions() { // The exception swallower thread can’t be in this process, because the // EXC_CRASH or EXC_CORPSE_NOTIFY exceptions that it needs to swallow will be // delivered after a crash has occurred and none of its threads will be // scheduled to run. CHECK(!g_exception_swallower || !g_exception_swallower->exception_swallower_thread_ || g_exception_swallower->exception_swallower_thread_->ProcessID() != getpid()); const char* service_name = CheckedGetenv(kServiceEnvironmentVariable); CHECK(service_name); base::apple::ScopedMachSendRight exception_swallower_port( BootstrapLookUp(service_name)); CHECK(exception_swallower_port.is_valid()); ExceptionPorts task_exception_ports(ExceptionPorts::kTargetTypeTask, TASK_NULL); // The mask is similar to the one used by CrashpadClient::UseHandler(), but // EXC_CORPSE_NOTIFY is added. This is done for the benefit of tests that // crash intentionally with their own custom exception port set for EXC_CRASH. // In that case, depending on the actions taken by the EXC_CRASH handler, the // exception may be transformed by the kernel into an EXC_CORPSE_NOTIFY, which // would be sent to an EXC_CORPSE_NOTIFY handler, normally the system’s crash // reporter at the task or host level. See 10.13.0 // xnu-4570.1.46/bsd/kern/kern_exit.c proc_prepareexit(). Swallowing // EXC_CORPSE_NOTIFY at the task level prevents such exceptions from reaching // the system’s crash reporter. CHECK(task_exception_ports.SetExceptionPort( (EXC_MASK_CRASH | EXC_MASK_RESOURCE | EXC_MASK_GUARD | EXC_MASK_CORPSE_NOTIFY) & ExcMaskValid(), exception_swallower_port.get(), EXCEPTION_DEFAULT, THREAD_STATE_NONE)); } } // namespace test } // namespace crashpad