// 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 <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <iterator>
#include <utility>
#include "base/check.h"
#include "base/containers/heap_array.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<internal::ClientData*>* 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<internal::ClientData*>* 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<internal::ClientData*>* 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*.
auto data = base::HeapArray<uint8_t>::Uninit(sizeof(wchar_t) * _MAX_PATH +
sizeof(FILE_NAME_INFO));
if (!GetFileInformationByHandleEx(first_pipe_instance_.get(),
FileNameInfo,
data.data(),
static_cast<DWORD>(data.size()))) {
PLOG(FATAL) << "GetFileInformationByHandleEx";
}
FILE_NAME_INFO* file_name_info =
reinterpret_cast<FILE_NAME_INFO*>(data.data());
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<internal::ClientData*>(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<ClientToServerMessage&>(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<internal::PipeServiceContext*>(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<internal::ClientData*>(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<internal::ClientData*>(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<internal::ClientData*>(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