// 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 "util/win/exception_handler_server.h" #include #include #include #include #include #include "base/check.h" #include "base/logging.h" #include "base/numerics/safe_conversions.h" #include "base/rand_util.h" #include "base/strings/utf_string_conversions.h" #include "util/file/file_writer.h" #include "util/misc/tri_state.h" #include "util/misc/uuid.h" #include "util/win/get_function.h" #include "util/win/handle.h" #include "util/win/registration_protocol_win.h" #include "util/win/safe_terminate_process.h" #include "util/win/xp_compat.h" namespace crashpad { namespace { decltype(GetNamedPipeClientProcessId)* GetNamedPipeClientProcessIdFunction() { static const auto get_named_pipe_client_process_id = GET_FUNCTION(L"kernel32.dll", ::GetNamedPipeClientProcessId); return get_named_pipe_client_process_id; } HANDLE DuplicateEvent(HANDLE process, HANDLE event) { HANDLE handle; if (DuplicateHandle(GetCurrentProcess(), event, process, &handle, SYNCHRONIZE | EVENT_MODIFY_STATE, false, 0)) { return handle; } return nullptr; } } // namespace namespace internal { //! \brief Context information for the named pipe handler threads. class PipeServiceContext { public: PipeServiceContext(HANDLE port, HANDLE pipe, ExceptionHandlerServer::Delegate* delegate, base::Lock* clients_lock, std::set* clients, uint64_t shutdown_token) : port_(port), pipe_(pipe), delegate_(delegate), clients_lock_(clients_lock), clients_(clients), shutdown_token_(shutdown_token) {} PipeServiceContext(const PipeServiceContext&) = delete; PipeServiceContext& operator=(const PipeServiceContext&) = delete; HANDLE port() const { return port_; } HANDLE pipe() const { return pipe_.get(); } ExceptionHandlerServer::Delegate* delegate() const { return delegate_; } base::Lock* clients_lock() const { return clients_lock_; } std::set* clients() const { return clients_; } uint64_t shutdown_token() const { return shutdown_token_; } private: HANDLE port_; // weak ScopedKernelHANDLE pipe_; ExceptionHandlerServer::Delegate* delegate_; // weak base::Lock* clients_lock_; // weak std::set* clients_; // weak uint64_t shutdown_token_; }; //! \brief The context data for registered threadpool waits. //! //! This object must be created and destroyed on the main thread. Access must be //! guarded by use of the lock() with the exception of the threadpool wait //! variables which are accessed only by the main thread. class ClientData { public: ClientData(HANDLE port, ExceptionHandlerServer::Delegate* delegate, ScopedKernelHANDLE process, ScopedKernelHANDLE crash_dump_requested_event, ScopedKernelHANDLE non_crash_dump_requested_event, ScopedKernelHANDLE non_crash_dump_completed_event, WinVMAddress crash_exception_information_address, WinVMAddress non_crash_exception_information_address, WinVMAddress debug_critical_section_address, WAITORTIMERCALLBACK crash_dump_request_callback, WAITORTIMERCALLBACK non_crash_dump_request_callback, WAITORTIMERCALLBACK process_end_callback) : crash_dump_request_thread_pool_wait_(INVALID_HANDLE_VALUE), non_crash_dump_request_thread_pool_wait_(INVALID_HANDLE_VALUE), process_end_thread_pool_wait_(INVALID_HANDLE_VALUE), lock_(), port_(port), delegate_(delegate), crash_dump_requested_event_(std::move(crash_dump_requested_event)), non_crash_dump_requested_event_( std::move(non_crash_dump_requested_event)), non_crash_dump_completed_event_( std::move(non_crash_dump_completed_event)), process_(std::move(process)), crash_exception_information_address_( crash_exception_information_address), non_crash_exception_information_address_( non_crash_exception_information_address), debug_critical_section_address_(debug_critical_section_address) { RegisterThreadPoolWaits(crash_dump_request_callback, non_crash_dump_request_callback, process_end_callback); } ClientData(const ClientData&) = delete; ClientData& operator=(const ClientData&) = delete; ~ClientData() { // It is important that this only access the threadpool waits (it's called // from the main thread) until the waits are unregistered, to ensure that // any outstanding callbacks are complete. UnregisterThreadPoolWaits(); } base::Lock* lock() { return &lock_; } HANDLE port() const { return port_; } ExceptionHandlerServer::Delegate* delegate() const { return delegate_; } HANDLE crash_dump_requested_event() const { return crash_dump_requested_event_.get(); } HANDLE non_crash_dump_requested_event() const { return non_crash_dump_requested_event_.get(); } HANDLE non_crash_dump_completed_event() const { return non_crash_dump_completed_event_.get(); } WinVMAddress crash_exception_information_address() const { return crash_exception_information_address_; } WinVMAddress non_crash_exception_information_address() const { return non_crash_exception_information_address_; } WinVMAddress debug_critical_section_address() const { return debug_critical_section_address_; } HANDLE process() const { return process_.get(); } private: void RegisterThreadPoolWaits( WAITORTIMERCALLBACK crash_dump_request_callback, WAITORTIMERCALLBACK non_crash_dump_request_callback, WAITORTIMERCALLBACK process_end_callback) { if (!RegisterWaitForSingleObject(&crash_dump_request_thread_pool_wait_, crash_dump_requested_event_.get(), crash_dump_request_callback, this, INFINITE, WT_EXECUTEDEFAULT)) { LOG(ERROR) << "RegisterWaitForSingleObject crash dump requested"; } if (!RegisterWaitForSingleObject(&non_crash_dump_request_thread_pool_wait_, non_crash_dump_requested_event_.get(), non_crash_dump_request_callback, this, INFINITE, WT_EXECUTEDEFAULT)) { LOG(ERROR) << "RegisterWaitForSingleObject non-crash dump requested"; } if (!RegisterWaitForSingleObject(&process_end_thread_pool_wait_, process_.get(), process_end_callback, this, INFINITE, WT_EXECUTEONLYONCE)) { LOG(ERROR) << "RegisterWaitForSingleObject process end"; } } // This blocks until outstanding calls complete so that we know it's safe to // delete this object. Because of this, it must be executed on the main // thread, not a threadpool thread. void UnregisterThreadPoolWaits() { UnregisterWaitEx(crash_dump_request_thread_pool_wait_, INVALID_HANDLE_VALUE); crash_dump_request_thread_pool_wait_ = INVALID_HANDLE_VALUE; UnregisterWaitEx(non_crash_dump_request_thread_pool_wait_, INVALID_HANDLE_VALUE); non_crash_dump_request_thread_pool_wait_ = INVALID_HANDLE_VALUE; UnregisterWaitEx(process_end_thread_pool_wait_, INVALID_HANDLE_VALUE); process_end_thread_pool_wait_ = INVALID_HANDLE_VALUE; } // These are only accessed on the main thread. HANDLE crash_dump_request_thread_pool_wait_; HANDLE non_crash_dump_request_thread_pool_wait_; HANDLE process_end_thread_pool_wait_; base::Lock lock_; // Access to these fields must be guarded by lock_. HANDLE port_; // weak ExceptionHandlerServer::Delegate* delegate_; // weak ScopedKernelHANDLE crash_dump_requested_event_; ScopedKernelHANDLE non_crash_dump_requested_event_; ScopedKernelHANDLE non_crash_dump_completed_event_; ScopedKernelHANDLE process_; WinVMAddress crash_exception_information_address_; WinVMAddress non_crash_exception_information_address_; WinVMAddress debug_critical_section_address_; }; } // namespace internal ExceptionHandlerServer::Delegate::~Delegate() { } ExceptionHandlerServer::ExceptionHandlerServer(bool persistent) : pipe_name_(), port_(CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1)), first_pipe_instance_(), clients_lock_(), clients_(), persistent_(persistent) { } ExceptionHandlerServer::~ExceptionHandlerServer() { } void ExceptionHandlerServer::SetPipeName(const std::wstring& pipe_name) { DCHECK(pipe_name_.empty()); DCHECK(!pipe_name.empty()); pipe_name_ = pipe_name; } void ExceptionHandlerServer::InitializeWithInheritedDataForInitialClient( const InitialClientData& initial_client_data, Delegate* delegate) { DCHECK(pipe_name_.empty()); DCHECK(!first_pipe_instance_.is_valid()); first_pipe_instance_.reset(initial_client_data.first_pipe_instance()); // TODO(scottmg): Vista+. Might need to pass through or possibly find an Nt*. size_t bytes = sizeof(wchar_t) * _MAX_PATH + sizeof(FILE_NAME_INFO); std::unique_ptr data(new uint8_t[bytes]); if (!GetFileInformationByHandleEx(first_pipe_instance_.get(), FileNameInfo, data.get(), static_cast(bytes))) { PLOG(FATAL) << "GetFileInformationByHandleEx"; } FILE_NAME_INFO* file_name_info = reinterpret_cast(data.get()); pipe_name_ = L"\\\\.\\pipe" + std::wstring(file_name_info->FileName, file_name_info->FileNameLength / sizeof(file_name_info->FileName[0])); { base::AutoLock lock(clients_lock_); internal::ClientData* client = new internal::ClientData( port_.get(), delegate, ScopedKernelHANDLE(initial_client_data.client_process()), ScopedKernelHANDLE(initial_client_data.request_crash_dump()), ScopedKernelHANDLE(initial_client_data.request_non_crash_dump()), ScopedKernelHANDLE(initial_client_data.non_crash_dump_completed()), initial_client_data.crash_exception_information(), initial_client_data.non_crash_exception_information(), initial_client_data.debug_critical_section_address(), &OnCrashDumpEvent, &OnNonCrashDumpEvent, &OnProcessEnd); clients_.insert(client); } } void ExceptionHandlerServer::Run(Delegate* delegate) { uint64_t shutdown_token = base::RandUint64(); ScopedKernelHANDLE thread_handles[kPipeInstances]; for (size_t i = 0; i < std::size(thread_handles); ++i) { HANDLE pipe; if (first_pipe_instance_.is_valid()) { pipe = first_pipe_instance_.release(); } else { pipe = CreateNamedPipeInstance(pipe_name_, i == 0); PCHECK(pipe != INVALID_HANDLE_VALUE) << "CreateNamedPipe"; } // Ownership of this object (and the pipe instance) is given to the new // thread. We close the thread handles at the end of the scope. They clean // up the context object and the pipe instance on termination. internal::PipeServiceContext* context = new internal::PipeServiceContext(port_.get(), pipe, delegate, &clients_lock_, &clients_, shutdown_token); thread_handles[i].reset( CreateThread(nullptr, 0, &PipeServiceProc, context, 0, nullptr)); PCHECK(thread_handles[i].is_valid()) << "CreateThread"; } delegate->ExceptionHandlerServerStarted(); // This is the main loop of the server. Most work is done on the threadpool, // other than process end handling which is posted back to this main thread, // as we must unregister the threadpool waits here. for (;;) { OVERLAPPED* ov = nullptr; ULONG_PTR key = 0; DWORD bytes = 0; GetQueuedCompletionStatus(port_.get(), &bytes, &key, &ov, INFINITE); if (!key) { // Shutting down. break; } // Otherwise, this is a request to unregister and destroy the given client. // delete'ing the ClientData blocks in UnregisterWaitEx to ensure all // outstanding threadpool waits are complete. This is important because the // process handle can be signalled *before* the dump request is signalled. internal::ClientData* client = reinterpret_cast(key); base::AutoLock lock(clients_lock_); clients_.erase(client); delete client; if (!persistent_ && clients_.empty()) break; } // Signal to the named pipe instances that they should terminate. for (size_t i = 0; i < std::size(thread_handles); ++i) { ClientToServerMessage message; memset(&message, 0, sizeof(message)); message.type = ClientToServerMessage::kShutdown; message.shutdown.token = shutdown_token; ServerToClientMessage response; SendToCrashHandlerServer(pipe_name_, reinterpret_cast(message), &response); } for (auto& handle : thread_handles) WaitForSingleObject(handle.get(), INFINITE); // Deleting ClientData does a blocking wait until the threadpool executions // have terminated when unregistering them. { base::AutoLock lock(clients_lock_); for (auto* client : clients_) delete client; clients_.clear(); } } void ExceptionHandlerServer::Stop() { // Post a null key (third argument) to trigger shutdown. PostQueuedCompletionStatus(port_.get(), 0, 0, nullptr); } // This function must be called with service_context.pipe() already connected to // a client pipe. It exchanges data with the client and adds a ClientData record // to service_context->clients(). // // static bool ExceptionHandlerServer::ServiceClientConnection( const internal::PipeServiceContext& service_context) { ClientToServerMessage message; if (!LoggingReadFileExactly( service_context.pipe(), &message, sizeof(message))) return false; switch (message.type) { case ClientToServerMessage::kShutdown: { if (message.shutdown.token != service_context.shutdown_token()) { LOG(ERROR) << "forged shutdown request, got: " << message.shutdown.token; return false; } ServerToClientMessage shutdown_response = {}; LoggingWriteFile(service_context.pipe(), &shutdown_response, sizeof(shutdown_response)); return true; } case ClientToServerMessage::kPing: { // No action required, the fact that the message was processed is // sufficient. ServerToClientMessage shutdown_response = {}; LoggingWriteFile(service_context.pipe(), &shutdown_response, sizeof(shutdown_response)); return false; } case ClientToServerMessage::kRegister: // Handled below. break; default: LOG(ERROR) << "unhandled message type: " << message.type; return false; } if (message.registration.version != RegistrationRequest::kMessageVersion) { LOG(ERROR) << "unexpected version. got: " << message.registration.version << " expecting: " << RegistrationRequest::kMessageVersion; return false; } decltype(GetNamedPipeClientProcessId)* get_named_pipe_client_process_id = GetNamedPipeClientProcessIdFunction(); if (get_named_pipe_client_process_id) { // GetNamedPipeClientProcessId is only available on Vista+. DWORD real_pid = 0; if (get_named_pipe_client_process_id(service_context.pipe(), &real_pid) && message.registration.client_process_id != real_pid) { LOG(ERROR) << "forged client pid, real pid: " << real_pid << ", got: " << message.registration.client_process_id; return false; } } // We attempt to open the process as us. This is the main case that should // almost always succeed as the server will generally be more privileged. If // we're running as a different user, it may be that we will fail to open // the process, but the client will be able to, so we make a second attempt // having impersonated the client. HANDLE client_process = OpenProcess( kXPProcessAllAccess, false, message.registration.client_process_id); if (!client_process) { if (!ImpersonateNamedPipeClient(service_context.pipe())) { PLOG(ERROR) << "ImpersonateNamedPipeClient"; return false; } client_process = OpenProcess( kXPProcessAllAccess, false, message.registration.client_process_id); PCHECK(RevertToSelf()); if (!client_process) { LOG(ERROR) << "failed to open " << message.registration.client_process_id; return false; } } internal::ClientData* client; { base::AutoLock lock(*service_context.clients_lock()); client = new internal::ClientData( service_context.port(), service_context.delegate(), ScopedKernelHANDLE(client_process), ScopedKernelHANDLE( CreateEvent(nullptr, false /* auto reset */, false, nullptr)), ScopedKernelHANDLE( CreateEvent(nullptr, false /* auto reset */, false, nullptr)), ScopedKernelHANDLE( CreateEvent(nullptr, false /* auto reset */, false, nullptr)), message.registration.crash_exception_information, message.registration.non_crash_exception_information, message.registration.critical_section_address, &OnCrashDumpEvent, &OnNonCrashDumpEvent, &OnProcessEnd); service_context.clients()->insert(client); } // Duplicate the events back to the client so they can request a dump. ServerToClientMessage response; response.registration.request_crash_dump_event = HandleToInt(DuplicateEvent( client->process(), client->crash_dump_requested_event())); response.registration.request_non_crash_dump_event = HandleToInt(DuplicateEvent( client->process(), client->non_crash_dump_requested_event())); response.registration.non_crash_dump_completed_event = HandleToInt(DuplicateEvent( client->process(), client->non_crash_dump_completed_event())); if (!LoggingWriteFile(service_context.pipe(), &response, sizeof(response))) return false; return false; } // static DWORD __stdcall ExceptionHandlerServer::PipeServiceProc(void* ctx) { internal::PipeServiceContext* service_context = reinterpret_cast(ctx); DCHECK(service_context); for (;;) { bool ret = !!ConnectNamedPipe(service_context->pipe(), nullptr); if (!ret && GetLastError() != ERROR_PIPE_CONNECTED) { PLOG(ERROR) << "ConnectNamedPipe"; } else if (ServiceClientConnection(*service_context)) { break; } DisconnectNamedPipe(service_context->pipe()); } delete service_context; return 0; } // static void __stdcall ExceptionHandlerServer::OnCrashDumpEvent(void* ctx, BOOLEAN) { // This function is executed on the thread pool. internal::ClientData* client = reinterpret_cast(ctx); base::AutoLock lock(*client->lock()); // Capture the exception. unsigned int exit_code = client->delegate()->ExceptionHandlerServerException( client->process(), client->crash_exception_information_address(), client->debug_critical_section_address()); SafeTerminateProcess(client->process(), exit_code); } // static void __stdcall ExceptionHandlerServer::OnNonCrashDumpEvent(void* ctx, BOOLEAN) { // This function is executed on the thread pool. internal::ClientData* client = reinterpret_cast(ctx); base::AutoLock lock(*client->lock()); // Capture the exception. client->delegate()->ExceptionHandlerServerException( client->process(), client->non_crash_exception_information_address(), client->debug_critical_section_address()); bool result = !!SetEvent(client->non_crash_dump_completed_event()); PLOG_IF(ERROR, !result) << "SetEvent"; } // static void __stdcall ExceptionHandlerServer::OnProcessEnd(void* ctx, BOOLEAN) { // This function is executed on the thread pool. internal::ClientData* client = reinterpret_cast(ctx); base::AutoLock lock(*client->lock()); // Post back to the main thread to have it delete this client record. PostQueuedCompletionStatus(client->port(), 0, ULONG_PTR(client), nullptr); } } // namespace crashpad