// Copyright 2015 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 "client/crashpad_client.h" #include #include #include #include #include #include "base/atomicops.h" #include "base/logging.h" #include "base/macros.h" #include "base/scoped_generic.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/synchronization/lock.h" #include "util/file/file_io.h" #include "util/misc/capture_context.h" #include "util/misc/from_pointer_cast.h" #include "util/misc/random_string.h" #include "util/win/address_types.h" #include "util/win/command_line.h" #include "util/win/context_wrappers.h" #include "util/win/critical_section_with_debug_info.h" #include "util/win/get_function.h" #include "util/win/handle.h" #include "util/win/initial_client_data.h" #include "util/win/loader_lock.h" #include "util/win/nt_internals.h" #include "util/win/ntstatus_logging.h" #include "util/win/process_info.h" #include "util/win/registration_protocol_win.h" #include "util/win/safe_terminate_process.h" #include "util/win/scoped_process_suspend.h" #include "util/win/termination_codes.h" #include "util/win/xp_compat.h" namespace crashpad { namespace { // This handle is never closed. This is used to signal to the server that a dump // should be taken in the event of a crash. HANDLE g_signal_exception = INVALID_HANDLE_VALUE; // Where we store the exception information that the crash handler reads. ExceptionInformation g_crash_exception_information; // These handles are never closed. g_signal_non_crash_dump is used to signal to // the server to take a dump (not due to an exception), and the server will // signal g_non_crash_dump_done when the dump is completed. HANDLE g_signal_non_crash_dump = INVALID_HANDLE_VALUE; HANDLE g_non_crash_dump_done = INVALID_HANDLE_VALUE; // Guards multiple simultaneous calls to DumpWithoutCrash(). This is leaked. base::Lock* g_non_crash_dump_lock; // Where we store a pointer to the context information when taking a non-crash // dump. ExceptionInformation g_non_crash_exception_information; enum class StartupState : int { kNotReady = 0, // This must be value 0 because it is the initial value of a // global AtomicWord. kSucceeded = 1, // The CreateProcess() for the handler succeeded. kFailed = 2, // The handler failed to start. }; // This is a tri-state of type StartupState. It starts at 0 == kNotReady, and // when the handler is known to have started successfully, or failed to start // the value will be updated. The unhandled exception filter will not proceed // until one of those two cases happens. base::subtle::AtomicWord g_handler_startup_state; // A CRITICAL_SECTION initialized with // RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO to force it to be allocated with a // valid .DebugInfo field. The address of this critical section is given to the // handler. All critical sections with debug info are linked in a doubly-linked // list, so this allows the handler to capture all of them. CRITICAL_SECTION g_critical_section_with_debug_info; void SetHandlerStartupState(StartupState state) { DCHECK(state == StartupState::kSucceeded || state == StartupState::kFailed); base::subtle::Release_Store(&g_handler_startup_state, static_cast(state)); } StartupState BlockUntilHandlerStartedOrFailed() { // Wait until we know the handler has either succeeded or failed to start. base::subtle::AtomicWord startup_state; while ( (startup_state = base::subtle::Acquire_Load(&g_handler_startup_state)) == static_cast(StartupState::kNotReady)) { Sleep(1); } return static_cast(startup_state); } #if defined(ADDRESS_SANITIZER) extern "C" LONG __asan_unhandled_exception_filter(EXCEPTION_POINTERS* info); #endif LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) { #if defined(ADDRESS_SANITIZER) // In ASan builds, delegate to the ASan exception filter. LONG status = __asan_unhandled_exception_filter(exception_pointers); if (status != EXCEPTION_CONTINUE_SEARCH) return status; #endif if (BlockUntilHandlerStartedOrFailed() == StartupState::kFailed) { // If we know for certain that the handler has failed to start, then abort // here, rather than trying to signal to a handler that will never arrive, // and then sleeping unnecessarily. LOG(ERROR) << "crash server failed to launch, self-terminating"; SafeTerminateProcess(GetCurrentProcess(), kTerminationCodeCrashNoDump); return EXCEPTION_CONTINUE_SEARCH; } // Otherwise, we know the handler startup has succeeded, and we can continue. // Tracks whether a thread has already entered UnhandledExceptionHandler. static base::subtle::AtomicWord have_crashed; // This is a per-process handler. While this handler is being invoked, other // threads are still executing as usual, so multiple threads could enter at // the same time. Because we're in a crashing state, we shouldn't be doing // anything that might cause allocations, call into kernel mode, etc. So, we // don't want to take a critical section here to avoid simultaneous access to // the global exception pointers in ExceptionInformation. Because the crash // handler will record all threads, it's fine to simply have the second and // subsequent entrants block here. They will soon be suspended by the crash // handler, and then the entire process will be terminated below. This means // that we won't save the exception pointers from the second and further // crashes, but contention here is very unlikely, and we'll still have a stack // that's blocked at this location. if (base::subtle::Barrier_AtomicIncrement(&have_crashed, 1) > 1) { SleepEx(INFINITE, false); } // Otherwise, we're the first thread, so record the exception pointer and // signal the crash handler. g_crash_exception_information.thread_id = GetCurrentThreadId(); g_crash_exception_information.exception_pointers = FromPointerCast(exception_pointers); // Now signal the crash server, which will take a dump and then terminate us // when it's complete. SetEvent(g_signal_exception); // Time to wait for the handler to create a dump. constexpr DWORD kMillisecondsUntilTerminate = 60 * 1000; // Sleep for a while to allow it to process us. Eventually, we terminate // ourselves in case the crash server is gone, so that we don't leave zombies // around. This would ideally never happen. Sleep(kMillisecondsUntilTerminate); LOG(ERROR) << "crash server did not respond, self-terminating"; SafeTerminateProcess(GetCurrentProcess(), kTerminationCodeCrashNoDump); return EXCEPTION_CONTINUE_SEARCH; } void HandleAbortSignal(int signum) { DCHECK_EQ(signum, SIGABRT); CONTEXT context; CaptureContext(&context); EXCEPTION_RECORD record = {}; record.ExceptionCode = STATUS_FATAL_APP_EXIT; record.ExceptionFlags = EXCEPTION_NONCONTINUABLE; record.ExceptionAddress = ProgramCounterFromCONTEXT(&context); EXCEPTION_POINTERS exception_pointers; exception_pointers.ContextRecord = &context; exception_pointers.ExceptionRecord = &record; UnhandledExceptionHandler(&exception_pointers); } std::wstring FormatArgumentString(const std::string& name, const std::wstring& value) { return std::wstring(L"--") + base::UTF8ToWide(name) + L"=" + value; } struct ScopedProcThreadAttributeListTraits { static PPROC_THREAD_ATTRIBUTE_LIST InvalidValue() { return nullptr; } static void Free(PPROC_THREAD_ATTRIBUTE_LIST proc_thread_attribute_list) { // This is able to use GET_FUNCTION_REQUIRED() instead of GET_FUNCTION() // because it will only be called if InitializeProcThreadAttributeList() and // UpdateProcThreadAttribute() are present. static const auto delete_proc_thread_attribute_list = GET_FUNCTION_REQUIRED(L"kernel32.dll", ::DeleteProcThreadAttributeList); delete_proc_thread_attribute_list(proc_thread_attribute_list); } }; using ScopedProcThreadAttributeList = base::ScopedGeneric; bool IsInheritableHandle(HANDLE handle) { if (!handle || handle == INVALID_HANDLE_VALUE) return false; // File handles (FILE_TYPE_DISK) and pipe handles (FILE_TYPE_PIPE) are known // to be inheritable. Console handles (FILE_TYPE_CHAR) are not inheritable via // PROC_THREAD_ATTRIBUTE_HANDLE_LIST. See // https://crashpad.chromium.org/bug/77. DWORD handle_type = GetFileType(handle); return handle_type == FILE_TYPE_DISK || handle_type == FILE_TYPE_PIPE; } // Adds |handle| to |handle_list| if it appears valid, and is not already in // |handle_list|. // // Invalid handles (including INVALID_HANDLE_VALUE and null handles) cannot be // added to a PPROC_THREAD_ATTRIBUTE_LIST’s PROC_THREAD_ATTRIBUTE_HANDLE_LIST. // If INVALID_HANDLE_VALUE appears, CreateProcess() will fail with // ERROR_INVALID_PARAMETER. If a null handle appears, the child process will // silently not inherit any handles. // // Use this function to add handles with uncertain validities. void AddHandleToListIfValidAndInheritable(std::vector* handle_list, HANDLE handle) { // There doesn't seem to be any documentation of this, but if there's a handle // duplicated in this list, CreateProcess() fails with // ERROR_INVALID_PARAMETER. if (IsInheritableHandle(handle) && std::find(handle_list->begin(), handle_list->end(), handle) == handle_list->end()) { handle_list->push_back(handle); } } void AddUint32(std::vector* data_vector, uint32_t data) { data_vector->push_back(static_cast(data & 0xff)); data_vector->push_back(static_cast((data & 0xff00) >> 8)); data_vector->push_back(static_cast((data & 0xff0000) >> 16)); data_vector->push_back(static_cast((data & 0xff000000) >> 24)); } void AddUint64(std::vector* data_vector, uint64_t data) { AddUint32(data_vector, static_cast(data & 0xffffffffULL)); AddUint32(data_vector, static_cast((data & 0xffffffff00000000ULL) >> 32)); } //! \brief Creates a randomized pipe name to listen for client registrations //! on and returns its name. //! //! \param[out] pipe_name The pipe name that will be listened on. //! \param[out] pipe_handle The first pipe instance corresponding for the pipe. void CreatePipe(std::wstring* pipe_name, ScopedFileHANDLE* pipe_instance) { int tries = 5; std::string pipe_name_base = base::StringPrintf( #if defined(WINDOWS_UWP) "\\\\.\\pipe\\LOCAL\\crashpad_%lu_", #else "\\\\.\\pipe\\crashpad_%lu_", #endif GetCurrentProcessId()); do { *pipe_name = base::UTF8ToWide(pipe_name_base + RandomString()); pipe_instance->reset(CreateNamedPipeInstance(*pipe_name, true)); // CreateNamedPipe() is documented as setting the error to // ERROR_ACCESS_DENIED if FILE_FLAG_FIRST_PIPE_INSTANCE is specified and the // pipe name is already in use. However it may set the error to other codes // such as ERROR_PIPE_BUSY (if the pipe already exists and has reached its // maximum instance count) or ERROR_INVALID_PARAMETER (if the pipe already // exists and its attributes differ from those specified to // CreateNamedPipe()). Some of these errors may be ambiguous: for example, // ERROR_INVALID_PARAMETER may also occur if CreateNamedPipe() is called // incorrectly even in the absence of an existing pipe by the same name. // Rather than chasing down all of the possible errors that might indicate // that a pipe name is already in use, retry up to a few times on any error. } while (!pipe_instance->is_valid() && --tries); PCHECK(pipe_instance->is_valid()) << "CreateNamedPipe"; } struct BackgroundHandlerStartThreadData { BackgroundHandlerStartThreadData( const base::FilePath& handler, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, const std::vector& attachments, const std::wstring& ipc_pipe, ScopedFileHANDLE ipc_pipe_handle) : handler(handler), database(database), metrics_dir(metrics_dir), url(url), annotations(annotations), arguments(arguments), attachments(attachments), ipc_pipe(ipc_pipe), ipc_pipe_handle(std::move(ipc_pipe_handle)) {} base::FilePath handler; base::FilePath database; base::FilePath metrics_dir; std::string url; std::map annotations; std::vector arguments; std::vector attachments; std::wstring ipc_pipe; ScopedFileHANDLE ipc_pipe_handle; }; // Ensures that SetHandlerStartupState() is called on scope exit. Assumes // failure, and on success, SetSuccessful() should be called. class ScopedCallSetHandlerStartupState { public: ScopedCallSetHandlerStartupState() : successful_(false) {} ~ScopedCallSetHandlerStartupState() { SetHandlerStartupState(successful_ ? StartupState::kSucceeded : StartupState::kFailed); } void SetSuccessful() { successful_ = true; } private: bool successful_; DISALLOW_COPY_AND_ASSIGN(ScopedCallSetHandlerStartupState); }; bool StartHandlerProcess( std::unique_ptr data) { CHECK(!IsThreadInLoaderLock()); ScopedCallSetHandlerStartupState scoped_startup_state_caller; std::wstring command_line; AppendCommandLineArgument(data->handler.value(), &command_line); for (const std::string& argument : data->arguments) { AppendCommandLineArgument(base::UTF8ToWide(argument), &command_line); } if (!data->database.value().empty()) { AppendCommandLineArgument( FormatArgumentString("database", data->database.value()), &command_line); } if (!data->metrics_dir.value().empty()) { AppendCommandLineArgument( FormatArgumentString("metrics-dir", data->metrics_dir.value()), &command_line); } if (!data->url.empty()) { AppendCommandLineArgument( FormatArgumentString("url", base::UTF8ToWide(data->url)), &command_line); } for (const auto& kv : data->annotations) { AppendCommandLineArgument( FormatArgumentString("annotation", base::UTF8ToWide(kv.first + '=' + kv.second)), &command_line); } for (const base::FilePath& attachment : data->attachments) { AppendCommandLineArgument( FormatArgumentString("attachment", attachment.value()), &command_line); } ScopedKernelHANDLE this_process( OpenProcess(kXPProcessAllAccess, true, GetCurrentProcessId())); if (!this_process.is_valid()) { PLOG(ERROR) << "OpenProcess"; return false; } InitialClientData initial_client_data( g_signal_exception, g_signal_non_crash_dump, g_non_crash_dump_done, data->ipc_pipe_handle.get(), this_process.get(), FromPointerCast(&g_crash_exception_information), FromPointerCast(&g_non_crash_exception_information), FromPointerCast(&g_critical_section_with_debug_info)); AppendCommandLineArgument( base::UTF8ToWide(std::string("--initial-client-data=") + initial_client_data.StringRepresentation()), &command_line); BOOL rv; DWORD creation_flags; STARTUPINFOEX startup_info = {}; startup_info.StartupInfo.dwFlags = STARTF_USESTDHANDLES; startup_info.StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); startup_info.StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); startup_info.StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); std::vector handle_list; std::unique_ptr proc_thread_attribute_list_storage; ScopedProcThreadAttributeList proc_thread_attribute_list_owner; static const auto initialize_proc_thread_attribute_list = GET_FUNCTION(L"kernel32.dll", ::InitializeProcThreadAttributeList); static const auto update_proc_thread_attribute = initialize_proc_thread_attribute_list ? GET_FUNCTION(L"kernel32.dll", ::UpdateProcThreadAttribute) : nullptr; if (!initialize_proc_thread_attribute_list || !update_proc_thread_attribute) { // The OS doesn’t allow handle inheritance to be restricted, so the handler // will inherit every inheritable handle. creation_flags = 0; startup_info.StartupInfo.cb = sizeof(startup_info.StartupInfo); } else { // Restrict handle inheritance to just those needed in the handler. creation_flags = EXTENDED_STARTUPINFO_PRESENT; startup_info.StartupInfo.cb = sizeof(startup_info); SIZE_T size; rv = initialize_proc_thread_attribute_list(nullptr, 1, 0, &size); if (rv) { LOG(ERROR) << "InitializeProcThreadAttributeList (size) succeeded, " "expected failure"; return false; } else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { PLOG(ERROR) << "InitializeProcThreadAttributeList (size)"; return false; } proc_thread_attribute_list_storage.reset(new uint8_t[size]); startup_info.lpAttributeList = reinterpret_cast( proc_thread_attribute_list_storage.get()); rv = initialize_proc_thread_attribute_list( startup_info.lpAttributeList, 1, 0, &size); if (!rv) { PLOG(ERROR) << "InitializeProcThreadAttributeList"; return false; } proc_thread_attribute_list_owner.reset(startup_info.lpAttributeList); handle_list.reserve(8); handle_list.push_back(g_signal_exception); handle_list.push_back(g_signal_non_crash_dump); handle_list.push_back(g_non_crash_dump_done); handle_list.push_back(data->ipc_pipe_handle.get()); handle_list.push_back(this_process.get()); AddHandleToListIfValidAndInheritable(&handle_list, startup_info.StartupInfo.hStdInput); AddHandleToListIfValidAndInheritable(&handle_list, startup_info.StartupInfo.hStdOutput); AddHandleToListIfValidAndInheritable(&handle_list, startup_info.StartupInfo.hStdError); rv = update_proc_thread_attribute( startup_info.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &handle_list[0], handle_list.size() * sizeof(handle_list[0]), nullptr, nullptr); if (!rv) { PLOG(ERROR) << "UpdateProcThreadAttribute"; return false; } } // If the embedded crashpad handler is being started via an entry point in a // DLL (the handler executable is rundll32.exe), then don't pass // the application name to CreateProcess as this appears to generate an // invalid command line where the first argument needed by rundll32 is not in // the correct format as required in: // https://support.microsoft.com/en-ca/help/164787/info-windows-rundll-and-rundll32-interface const base::WStringPiece kRunDll32Exe(L"rundll32.exe"); bool is_embedded_in_dll = false; if (data->handler.value().size() >= kRunDll32Exe.size() && _wcsicmp(data->handler.value() .substr(data->handler.value().size() - kRunDll32Exe.size()) .c_str(), kRunDll32Exe.data()) == 0) { is_embedded_in_dll = true; } PROCESS_INFORMATION process_info; rv = CreateProcess( is_embedded_in_dll ? nullptr : data->handler.value().c_str(), &command_line[0], nullptr, nullptr, true, creation_flags, nullptr, nullptr, &startup_info.StartupInfo, &process_info); if (!rv) { PLOG(ERROR) << "CreateProcess"; return false; } rv = CloseHandle(process_info.hThread); PLOG_IF(WARNING, !rv) << "CloseHandle thread"; rv = CloseHandle(process_info.hProcess); PLOG_IF(WARNING, !rv) << "CloseHandle process"; // It is important to close our side of the pipe here before confirming that // we can communicate with the server. By doing so, the only remaining copy of // the server side of the pipe belongs to the exception handler process we // just spawned. Otherwise, the pipe will continue to exist indefinitely, so // the connection loop will not detect that it will never be serviced. data->ipc_pipe_handle.reset(); // Confirm that the server is waiting for connections before continuing. ClientToServerMessage message = {}; message.type = ClientToServerMessage::kPing; ServerToClientMessage response = {}; if (!SendToCrashHandlerServer(data->ipc_pipe, message, &response)) { return false; } scoped_startup_state_caller.SetSuccessful(); return true; } DWORD WINAPI BackgroundHandlerStartThreadProc(void* data) { std::unique_ptr data_as_ptr( reinterpret_cast(data)); return StartHandlerProcess(std::move(data_as_ptr)) ? 0 : 1; } void CommonInProcessInitialization() { // We create this dummy CRITICAL_SECTION with the // RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO flag set to have an entry point // into the doubly-linked list of RTL_CRITICAL_SECTION_DEBUG objects. This // allows us to walk the list at crash time to gather data for !locks. A // debugger would instead inspect ntdll!RtlCriticalSectionList to get the head // of the list. But that is not an exported symbol, so on an arbitrary client // machine, we don't have a way of getting that pointer. InitializeCriticalSectionWithDebugInfoIfPossible( &g_critical_section_with_debug_info); g_non_crash_dump_lock = new base::Lock(); } void RegisterHandlers() { SetUnhandledExceptionFilter(&UnhandledExceptionHandler); // The Windows CRT's signal.h lists: // - SIGINT // - SIGILL // - SIGFPE // - SIGSEGV // - SIGTERM // - SIGBREAK // - SIGABRT // SIGILL and SIGTERM are documented as not being generated. SIGBREAK and // SIGINT are for Ctrl-Break and Ctrl-C, and aren't something for which // capturing a dump is warranted. SIGFPE and SIGSEGV are captured as regular // exceptions through the unhandled exception filter. This leaves SIGABRT. In // the standard CRT, abort() is implemented as a synchronous call to the // SIGABRT signal handler if installed, but after doing so, the unhandled // exception filter is not triggered (it instead __fastfail()s). So, register // to handle SIGABRT to catch abort() calls, as client code might use this and // expect it to cause a crash dump. This will only work when the abort() // that's called in client code is the same (or has the same behavior) as the // one in use here. void (*rv)(int) = signal(SIGABRT, HandleAbortSignal); DCHECK_NE(rv, SIG_ERR); } } // namespace CrashpadClient::CrashpadClient() : ipc_pipe_(), handler_start_thread_() {} CrashpadClient::~CrashpadClient() {} bool CrashpadClient::StartHandler( const base::FilePath& handler, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, bool restartable, bool asynchronous_start, const std::vector& attachments) { DCHECK(ipc_pipe_.empty()); // Both the pipe and the signalling events have to be created on the main // thread (not the spawning thread) so that they're valid after we return from // this function. ScopedFileHANDLE ipc_pipe_handle; CreatePipe(&ipc_pipe_, &ipc_pipe_handle); SECURITY_ATTRIBUTES security_attributes = {0}; security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); security_attributes.bInheritHandle = true; g_signal_exception = CreateEvent(&security_attributes, false /* auto reset */, false, nullptr); g_signal_non_crash_dump = CreateEvent(&security_attributes, false /* auto reset */, false, nullptr); g_non_crash_dump_done = CreateEvent(&security_attributes, false /* auto reset */, false, nullptr); CommonInProcessInitialization(); RegisterHandlers(); auto data = new BackgroundHandlerStartThreadData(handler, database, metrics_dir, url, annotations, arguments, attachments, ipc_pipe_, std::move(ipc_pipe_handle)); if (asynchronous_start) { // It is important that the current thread not be synchronized with the // thread that is created here. StartHandler() needs to be callable inside a // DllMain(). In that case, the background thread will not start until the // current DllMain() completes, which would cause deadlock if it was waited // upon. handler_start_thread_.reset(CreateThread(nullptr, 0, &BackgroundHandlerStartThreadProc, reinterpret_cast(data), 0, nullptr)); if (!handler_start_thread_.is_valid()) { PLOG(ERROR) << "CreateThread"; SetHandlerStartupState(StartupState::kFailed); return false; } // In asynchronous mode, we can't report on the overall success or failure // of initialization at this point. return true; } else { return StartHandlerProcess( std::unique_ptr(data)); } } bool CrashpadClient::SetHandlerIPCPipe(const std::wstring& ipc_pipe) { DCHECK(ipc_pipe_.empty()); DCHECK(!ipc_pipe.empty()); ipc_pipe_ = ipc_pipe; DCHECK(!ipc_pipe_.empty()); DCHECK_EQ(g_signal_exception, INVALID_HANDLE_VALUE); DCHECK_EQ(g_signal_non_crash_dump, INVALID_HANDLE_VALUE); DCHECK_EQ(g_non_crash_dump_done, INVALID_HANDLE_VALUE); DCHECK(!g_critical_section_with_debug_info.DebugInfo); DCHECK(!g_non_crash_dump_lock); ClientToServerMessage message; memset(&message, 0, sizeof(message)); message.type = ClientToServerMessage::kRegister; message.registration.version = RegistrationRequest::kMessageVersion; message.registration.client_process_id = GetCurrentProcessId(); message.registration.crash_exception_information = FromPointerCast(&g_crash_exception_information); message.registration.non_crash_exception_information = FromPointerCast(&g_non_crash_exception_information); CommonInProcessInitialization(); message.registration.critical_section_address = FromPointerCast(&g_critical_section_with_debug_info); ServerToClientMessage response = {}; if (!SendToCrashHandlerServer(ipc_pipe_, message, &response)) { return false; } SetHandlerStartupState(StartupState::kSucceeded); RegisterHandlers(); // The server returns these already duplicated to be valid in this process. g_signal_exception = IntToHandle(response.registration.request_crash_dump_event); g_signal_non_crash_dump = IntToHandle(response.registration.request_non_crash_dump_event); g_non_crash_dump_done = IntToHandle(response.registration.non_crash_dump_completed_event); return true; } std::wstring CrashpadClient::GetHandlerIPCPipe() const { DCHECK(!ipc_pipe_.empty()); return ipc_pipe_; } bool CrashpadClient::WaitForHandlerStart(unsigned int timeout_ms) { DCHECK(handler_start_thread_.is_valid()); DWORD result = WaitForSingleObject(handler_start_thread_.get(), timeout_ms); if (result == WAIT_TIMEOUT) { LOG(ERROR) << "WaitForSingleObject timed out"; return false; } else if (result == WAIT_ABANDONED) { LOG(ERROR) << "WaitForSingleObject abandoned"; return false; } else if (result != WAIT_OBJECT_0) { PLOG(ERROR) << "WaitForSingleObject"; return false; } DWORD exit_code; if (!GetExitCodeThread(handler_start_thread_.get(), &exit_code)) { PLOG(ERROR) << "GetExitCodeThread"; return false; } handler_start_thread_.reset(); return exit_code == 0; } // static void CrashpadClient::DumpWithoutCrash(const CONTEXT& context) { if (g_signal_non_crash_dump == INVALID_HANDLE_VALUE || g_non_crash_dump_done == INVALID_HANDLE_VALUE) { LOG(ERROR) << "not connected"; return; } if (BlockUntilHandlerStartedOrFailed() == StartupState::kFailed) { // If we know for certain that the handler has failed to start, then abort // here, as we would otherwise wait indefinitely for the // g_non_crash_dump_done event that would never be signalled. LOG(ERROR) << "crash server failed to launch, no dump captured"; return; } // In the non-crashing case, we aren't concerned about avoiding calls into // Win32 APIs, so just use regular locking here in case of multiple threads // calling this function. If a crash occurs while we're in here, the worst // that can happen is that the server captures a partial dump for this path // because another thread’s crash processing finished and the process was // terminated before this thread’s non-crash processing could be completed. base::AutoLock lock(*g_non_crash_dump_lock); // Create a fake EXCEPTION_POINTERS to give the handler something to work // with. EXCEPTION_POINTERS exception_pointers = {}; // This is logically const, but EXCEPTION_POINTERS does not declare it as // const, so we have to cast that away from the argument. exception_pointers.ContextRecord = const_cast(&context); // We include a fake exception and use a code of '0x517a7ed' (something like // "simulated") so that it's relatively obvious in windbg that it's not // actually an exception. Most values in // https://msdn.microsoft.com/library/aa363082.aspx have some of the top // nibble set, so we make sure to pick a value that doesn't, so as to be // unlikely to conflict. constexpr uint32_t kSimulatedExceptionCode = 0x517a7ed; EXCEPTION_RECORD record = {}; record.ExceptionCode = kSimulatedExceptionCode; record.ExceptionAddress = ProgramCounterFromCONTEXT(&context); exception_pointers.ExceptionRecord = &record; g_non_crash_exception_information.thread_id = GetCurrentThreadId(); g_non_crash_exception_information.exception_pointers = FromPointerCast(&exception_pointers); bool set_event_result = !!SetEvent(g_signal_non_crash_dump); PLOG_IF(ERROR, !set_event_result) << "SetEvent"; DWORD wfso_result = WaitForSingleObject(g_non_crash_dump_done, INFINITE); PLOG_IF(ERROR, wfso_result != WAIT_OBJECT_0) << "WaitForSingleObject"; } // static void CrashpadClient::DumpAndCrash(EXCEPTION_POINTERS* exception_pointers) { if (g_signal_exception == INVALID_HANDLE_VALUE) { LOG(ERROR) << "not connected"; SafeTerminateProcess(GetCurrentProcess(), kTerminationCodeNotConnectedToHandler); return; } // We don't need to check for handler startup here, as // UnhandledExceptionHandler() necessarily does that. UnhandledExceptionHandler(exception_pointers); } // static bool CrashpadClient::DumpAndCrashTargetProcess(HANDLE process, HANDLE blame_thread, DWORD exception_code) { // Confirm we're on Vista or later. const DWORD version = GetVersion(); const DWORD major_version = LOBYTE(LOWORD(version)); if (major_version < 6) { LOG(ERROR) << "unavailable before Vista"; return false; } // Confirm that our bitness is the same as the process we're crashing. ProcessInfo process_info; if (!process_info.Initialize(process)) { LOG(ERROR) << "ProcessInfo::Initialize"; return false; } #if defined(ARCH_CPU_64_BITS) if (!process_info.Is64Bit()) { LOG(ERROR) << "DumpAndCrashTargetProcess currently not supported x64->x86"; return false; } #endif // ARCH_CPU_64_BITS ScopedProcessSuspend suspend(process); // If no thread handle was provided, or the thread has already exited, we pass // 0 to the handler, which indicates no fake exception record to be created. DWORD thread_id = 0; if (blame_thread) { // Now that we've suspended the process, if our thread hasn't exited, we // know we're relatively safe to pass the thread id through. if (WaitForSingleObject(blame_thread, 0) == WAIT_TIMEOUT) { static const auto get_thread_id = GET_FUNCTION_REQUIRED(L"kernel32.dll", ::GetThreadId); thread_id = get_thread_id(blame_thread); } } constexpr size_t kInjectBufferSize = 4 * 1024; WinVMAddress inject_memory = FromPointerCast(VirtualAllocEx(process, nullptr, kInjectBufferSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); if (!inject_memory) { PLOG(ERROR) << "VirtualAllocEx"; return false; } // Because we're the same bitness as our target, we can rely kernel32 being // loaded at the same address in our process as the target, and just look up // its address here. WinVMAddress raise_exception_address = FromPointerCast(&RaiseException); WinVMAddress code_entry_point = 0; std::vector data_to_write; if (process_info.Is64Bit()) { // Data written is first, the data for the 4th argument (lpArguments) to // RaiseException(). A two element array: // // DWORD64: thread_id // DWORD64: exception_code // // Following that, code which sets the arguments to RaiseException() and // then calls it: // // mov r9, // mov r8d, 2 ; nNumberOfArguments // mov edx, 1 ; dwExceptionFlags = EXCEPTION_NONCONTINUABLE // mov ecx, 0xcca11ed ; dwExceptionCode, interpreted specially by the // ; handler. // jmp // // Note that the first three arguments to RaiseException() are DWORDs even // on x64, so only the 4th argument (a pointer) is a full-width register. // // We also don't need to set up a stack or use call, since the only // registers modified are volatile ones, and we can just jmp straight to // RaiseException(). // The data array. AddUint64(&data_to_write, thread_id); AddUint64(&data_to_write, exception_code); // The thread entry point. code_entry_point = inject_memory + data_to_write.size(); // r9 = pointer to data. data_to_write.push_back(0x49); data_to_write.push_back(0xb9); AddUint64(&data_to_write, inject_memory); // r8d = 2 for nNumberOfArguments. data_to_write.push_back(0x41); data_to_write.push_back(0xb8); AddUint32(&data_to_write, 2); // edx = 1 for dwExceptionFlags. data_to_write.push_back(0xba); AddUint32(&data_to_write, 1); // ecx = kTriggeredExceptionCode for dwExceptionCode. data_to_write.push_back(0xb9); AddUint32(&data_to_write, kTriggeredExceptionCode); // jmp to RaiseException() via rax. data_to_write.push_back(0x48); // mov rax, imm. data_to_write.push_back(0xb8); AddUint64(&data_to_write, raise_exception_address); data_to_write.push_back(0xff); // jmp rax. data_to_write.push_back(0xe0); } else { // Data written is first, the data for the 4th argument (lpArguments) to // RaiseException(). A two element array: // // DWORD: thread_id // DWORD: exception_code // // Following that, code which pushes our arguments to RaiseException() and // then calls it: // // push // push 2 ; nNumberOfArguments // push 1 ; dwExceptionFlags = EXCEPTION_NONCONTINUABLE // push 0xcca11ed ; dwExceptionCode, interpreted specially by the handler. // call // ud2 ; Generate invalid opcode to make sure we still crash if we return // ; for some reason. // // No need to clean up the stack, as RaiseException() is __stdcall. // The data array. AddUint32(&data_to_write, thread_id); AddUint32(&data_to_write, exception_code); // The thread entry point. code_entry_point = inject_memory + data_to_write.size(); // Push data address. data_to_write.push_back(0x68); AddUint32(&data_to_write, static_cast(inject_memory)); // Push 2 for nNumberOfArguments. data_to_write.push_back(0x6a); data_to_write.push_back(2); // Push 1 for dwExceptionCode. data_to_write.push_back(0x6a); data_to_write.push_back(1); // Push dwExceptionFlags. data_to_write.push_back(0x68); AddUint32(&data_to_write, kTriggeredExceptionCode); // Relative call to RaiseException(). int64_t relative_address_to_raise_exception = raise_exception_address - (inject_memory + data_to_write.size() + 5); data_to_write.push_back(0xe8); AddUint32(&data_to_write, static_cast(relative_address_to_raise_exception)); // ud2. data_to_write.push_back(0x0f); data_to_write.push_back(0x0b); } DCHECK_LT(data_to_write.size(), kInjectBufferSize); SIZE_T bytes_written; if (!WriteProcessMemory(process, reinterpret_cast(inject_memory), data_to_write.data(), data_to_write.size(), &bytes_written)) { PLOG(ERROR) << "WriteProcessMemory"; return false; } if (bytes_written != data_to_write.size()) { LOG(ERROR) << "WriteProcessMemory unexpected number of bytes"; return false; } if (!FlushInstructionCache( process, reinterpret_cast(inject_memory), bytes_written)) { PLOG(ERROR) << "FlushInstructionCache"; return false; } DWORD old_protect; if (!VirtualProtectEx(process, reinterpret_cast(inject_memory), kInjectBufferSize, PAGE_EXECUTE_READ, &old_protect)) { PLOG(ERROR) << "VirtualProtectEx"; return false; } // Cause an exception in the target process by creating a thread which calls // RaiseException with our arguments above. Note that we cannot get away with // using DebugBreakProcess() (nothing happens unless a debugger is attached) // and we cannot get away with CreateRemoteThread() because it doesn't work if // the target is hung waiting for the loader lock. We use NtCreateThreadEx() // with the SKIP_THREAD_ATTACH flag, which skips various notifications, // letting this cause an exception, even when the target is stuck in the // loader lock. HANDLE injected_thread; // This is what DebugBreakProcess() uses. constexpr size_t kStackSize = 0x4000; NTSTATUS status = NtCreateThreadEx(&injected_thread, STANDARD_RIGHTS_ALL | SPECIFIC_RIGHTS_ALL, nullptr, process, reinterpret_cast(code_entry_point), nullptr, THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH, 0, kStackSize, 0, nullptr); if (!NT_SUCCESS(status)) { NTSTATUS_LOG(ERROR, status) << "NtCreateThreadEx"; return false; } // The injected thread raises an exception and ultimately results in process // termination. The suspension must be made aware that the process may be // terminating, otherwise it’ll log an extraneous error. suspend.TolerateTermination(); bool result = true; if (WaitForSingleObject(injected_thread, 60 * 1000) != WAIT_OBJECT_0) { PLOG(ERROR) << "WaitForSingleObject"; result = false; } status = NtClose(injected_thread); if (!NT_SUCCESS(status)) { NTSTATUS_LOG(ERROR, status) << "NtClose"; result = false; } return result; } } // namespace crashpad