494 lines
14 KiB
C++
494 lines
14 KiB
C++
// 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 "handler/linux/exception_handler_server.h"
|
|
|
|
#include <errno.h>
|
|
#include <linux/capability.h>
|
|
#include <sys/epoll.h>
|
|
#include <sys/eventfd.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#include <utility>
|
|
|
|
#include "base/compiler_specific.h"
|
|
#include "base/logging.h"
|
|
#include "base/posix/eintr_wrapper.h"
|
|
#include "base/strings/string_number_conversions.h"
|
|
#include "build/build_config.h"
|
|
#include "util/file/file_io.h"
|
|
#include "util/file/filesystem.h"
|
|
#include "util/linux/proc_task_reader.h"
|
|
#include "util/linux/socket.h"
|
|
#include "util/misc/as_underlying_type.h"
|
|
|
|
namespace crashpad {
|
|
|
|
namespace {
|
|
|
|
// Log an error for a socket after an EPOLLERR.
|
|
void LogSocketError(int sock) {
|
|
int err;
|
|
socklen_t err_len = sizeof(err);
|
|
if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &err_len) != 0) {
|
|
PLOG(ERROR) << "getsockopt";
|
|
} else {
|
|
errno = err;
|
|
PLOG(ERROR) << "EPOLLERR";
|
|
}
|
|
}
|
|
|
|
enum class PtraceScope {
|
|
kClassic = 0,
|
|
kRestricted,
|
|
kAdminOnly,
|
|
kNoAttach,
|
|
kUnknown
|
|
};
|
|
|
|
PtraceScope GetPtraceScope() {
|
|
const base::FilePath settings_file("/proc/sys/kernel/yama/ptrace_scope");
|
|
if (!IsRegularFile(base::FilePath(settings_file))) {
|
|
return PtraceScope::kClassic;
|
|
}
|
|
|
|
std::string contents;
|
|
if (!LoggingReadEntireFile(settings_file, &contents)) {
|
|
return PtraceScope::kUnknown;
|
|
}
|
|
|
|
if (contents.back() != '\n') {
|
|
LOG(ERROR) << "format error";
|
|
return PtraceScope::kUnknown;
|
|
}
|
|
contents.pop_back();
|
|
|
|
int ptrace_scope;
|
|
if (!base::StringToInt(contents, &ptrace_scope)) {
|
|
LOG(ERROR) << "format error";
|
|
return PtraceScope::kUnknown;
|
|
}
|
|
|
|
if (ptrace_scope < static_cast<int>(PtraceScope::kClassic) ||
|
|
ptrace_scope >= static_cast<int>(PtraceScope::kUnknown)) {
|
|
LOG(ERROR) << "invalid ptrace scope";
|
|
return PtraceScope::kUnknown;
|
|
}
|
|
|
|
return static_cast<PtraceScope>(ptrace_scope);
|
|
}
|
|
|
|
bool HaveCapSysPtrace() {
|
|
__user_cap_header_struct cap_header;
|
|
cap_header.pid = getpid();
|
|
cap_header.version = _LINUX_CAPABILITY_VERSION_3;
|
|
|
|
__user_cap_data_struct cap_data[_LINUX_CAPABILITY_U32S_3];
|
|
if (syscall(SYS_capget, &cap_header, &cap_data) != 0) {
|
|
PLOG(ERROR) << "capget";
|
|
LOG_IF(ERROR, errno == EINVAL) << "cap_header.version " << std::hex
|
|
<< cap_header.version;
|
|
return false;
|
|
}
|
|
|
|
return (cap_data[CAP_TO_INDEX(CAP_SYS_PTRACE)].effective &
|
|
CAP_TO_MASK(CAP_SYS_PTRACE)) != 0;
|
|
}
|
|
|
|
bool SendMessageToClient(
|
|
int client_sock,
|
|
ExceptionHandlerProtocol::ServerToClientMessage::Type type) {
|
|
ExceptionHandlerProtocol::ServerToClientMessage message = {};
|
|
message.type = type;
|
|
if (type ==
|
|
ExceptionHandlerProtocol::ServerToClientMessage::kTypeSetPtracer) {
|
|
message.pid = getpid();
|
|
}
|
|
return LoggingWriteFile(client_sock, &message, sizeof(message));
|
|
}
|
|
|
|
int tgkill(pid_t pid, pid_t tid, int signo) {
|
|
return syscall(SYS_tgkill, pid, tid, signo);
|
|
}
|
|
|
|
void SendSIGCONT(pid_t pid, pid_t tid) {
|
|
if (tid > 0) {
|
|
if (tgkill(pid, tid, ExceptionHandlerProtocol::kDumpDoneSignal) != 0) {
|
|
PLOG(ERROR) << "tgkill";
|
|
}
|
|
return;
|
|
}
|
|
|
|
std::vector<pid_t> threads;
|
|
if (!ReadThreadIDs(pid, &threads)) {
|
|
return;
|
|
}
|
|
for (const auto& thread : threads) {
|
|
if (tgkill(pid, thread, ExceptionHandlerProtocol::kDumpDoneSignal) != 0) {
|
|
PLOG(ERROR) << "tgkill";
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SendCredentials(int client_sock) {
|
|
ExceptionHandlerProtocol::ServerToClientMessage message = {};
|
|
message.type =
|
|
ExceptionHandlerProtocol::ServerToClientMessage::kTypeCredentials;
|
|
return UnixCredentialSocket::SendMsg(
|
|
client_sock, &message, sizeof(message)) == 0;
|
|
}
|
|
|
|
class PtraceStrategyDeciderImpl : public PtraceStrategyDecider {
|
|
public:
|
|
PtraceStrategyDeciderImpl() : PtraceStrategyDecider() {}
|
|
~PtraceStrategyDeciderImpl() = default;
|
|
|
|
Strategy ChooseStrategy(int sock,
|
|
bool multiple_clients,
|
|
const ucred& client_credentials) override {
|
|
if (client_credentials.pid <= 0) {
|
|
LOG(ERROR) << "invalid credentials";
|
|
return Strategy::kNoPtrace;
|
|
}
|
|
|
|
switch (GetPtraceScope()) {
|
|
case PtraceScope::kClassic:
|
|
if (getuid() == client_credentials.uid || HaveCapSysPtrace()) {
|
|
return Strategy::kDirectPtrace;
|
|
}
|
|
return multiple_clients ? Strategy::kNoPtrace : TryForkingBroker(sock);
|
|
|
|
case PtraceScope::kRestricted:
|
|
if (multiple_clients) {
|
|
return Strategy::kDirectPtrace;
|
|
}
|
|
if (!SendMessageToClient(sock,
|
|
ExceptionHandlerProtocol::
|
|
ServerToClientMessage::kTypeSetPtracer)) {
|
|
return Strategy::kError;
|
|
}
|
|
|
|
ExceptionHandlerProtocol::Errno status;
|
|
if (!LoggingReadFileExactly(sock, &status, sizeof(status))) {
|
|
return Strategy::kError;
|
|
}
|
|
|
|
if (status != 0) {
|
|
errno = status;
|
|
PLOG(ERROR) << "Handler Client SetPtracer";
|
|
return TryForkingBroker(sock);
|
|
}
|
|
return Strategy::kDirectPtrace;
|
|
|
|
case PtraceScope::kAdminOnly:
|
|
if (HaveCapSysPtrace()) {
|
|
return Strategy::kDirectPtrace;
|
|
}
|
|
[[fallthrough]];
|
|
case PtraceScope::kNoAttach:
|
|
LOG(WARNING) << "no ptrace";
|
|
return Strategy::kNoPtrace;
|
|
|
|
case PtraceScope::kUnknown:
|
|
LOG(WARNING) << "Unknown ptrace scope";
|
|
return Strategy::kError;
|
|
}
|
|
|
|
DCHECK(false);
|
|
return Strategy::kError;
|
|
}
|
|
|
|
private:
|
|
static Strategy TryForkingBroker(int client_sock) {
|
|
if (!SendMessageToClient(
|
|
client_sock,
|
|
ExceptionHandlerProtocol::ServerToClientMessage::kTypeForkBroker)) {
|
|
return Strategy::kError;
|
|
}
|
|
|
|
ExceptionHandlerProtocol::Errno status;
|
|
if (!LoggingReadFileExactly(client_sock, &status, sizeof(status))) {
|
|
return Strategy::kError;
|
|
}
|
|
|
|
if (status != 0) {
|
|
errno = status;
|
|
PLOG(ERROR) << "Handler Client ForkBroker";
|
|
return Strategy::kNoPtrace;
|
|
}
|
|
return Strategy::kUseBroker;
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
ExceptionHandlerServer::ExceptionHandlerServer()
|
|
: clients_(),
|
|
shutdown_event_(),
|
|
strategy_decider_(new PtraceStrategyDeciderImpl()),
|
|
delegate_(nullptr),
|
|
pollfd_(),
|
|
keep_running_(true) {}
|
|
|
|
ExceptionHandlerServer::~ExceptionHandlerServer() = default;
|
|
|
|
void ExceptionHandlerServer::SetPtraceStrategyDecider(
|
|
std::unique_ptr<PtraceStrategyDecider> decider) {
|
|
strategy_decider_ = std::move(decider);
|
|
}
|
|
|
|
bool ExceptionHandlerServer::InitializeWithClient(ScopedFileHandle sock,
|
|
bool multiple_clients) {
|
|
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
|
|
|
|
pollfd_.reset(epoll_create1(EPOLL_CLOEXEC));
|
|
if (!pollfd_.is_valid()) {
|
|
PLOG(ERROR) << "epoll_create1";
|
|
return false;
|
|
}
|
|
|
|
shutdown_event_ = std::make_unique<Event>();
|
|
shutdown_event_->type = Event::Type::kShutdown;
|
|
shutdown_event_->fd.reset(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK));
|
|
if (!shutdown_event_->fd.is_valid()) {
|
|
PLOG(ERROR) << "eventfd";
|
|
return false;
|
|
}
|
|
|
|
epoll_event poll_event;
|
|
poll_event.events = EPOLLIN;
|
|
poll_event.data.ptr = shutdown_event_.get();
|
|
if (epoll_ctl(pollfd_.get(),
|
|
EPOLL_CTL_ADD,
|
|
shutdown_event_->fd.get(),
|
|
&poll_event) != 0) {
|
|
PLOG(ERROR) << "epoll_ctl";
|
|
return false;
|
|
}
|
|
|
|
if (!InstallClientSocket(std::move(sock),
|
|
multiple_clients ? Event::Type::kSharedSocketMessage
|
|
: Event::Type::kClientMessage)) {
|
|
return false;
|
|
}
|
|
|
|
INITIALIZATION_STATE_SET_VALID(initialized_);
|
|
return true;
|
|
}
|
|
|
|
void ExceptionHandlerServer::Run(Delegate* delegate) {
|
|
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
|
delegate_ = delegate;
|
|
|
|
while (keep_running_ && clients_.size() > 0) {
|
|
epoll_event poll_event;
|
|
int res = HANDLE_EINTR(epoll_wait(pollfd_.get(), &poll_event, 1, -1));
|
|
if (res < 0) {
|
|
PLOG(ERROR) << "epoll_wait";
|
|
return;
|
|
}
|
|
DCHECK_EQ(res, 1);
|
|
|
|
Event* eventp = reinterpret_cast<Event*>(poll_event.data.ptr);
|
|
if (eventp->type == Event::Type::kShutdown) {
|
|
if (poll_event.events & EPOLLERR) {
|
|
LogSocketError(eventp->fd.get());
|
|
}
|
|
keep_running_ = false;
|
|
} else {
|
|
HandleEvent(eventp, poll_event.events);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ExceptionHandlerServer::Stop() {
|
|
keep_running_ = false;
|
|
if (shutdown_event_ && shutdown_event_->fd.is_valid()) {
|
|
uint64_t value = 1;
|
|
LoggingWriteFile(shutdown_event_->fd.get(), &value, sizeof(value));
|
|
}
|
|
}
|
|
|
|
void ExceptionHandlerServer::HandleEvent(Event* event, uint32_t event_type) {
|
|
DCHECK_NE(AsUnderlyingType(event->type),
|
|
AsUnderlyingType(Event::Type::kShutdown));
|
|
|
|
if (event_type & EPOLLERR) {
|
|
LogSocketError(event->fd.get());
|
|
UninstallClientSocket(event);
|
|
return;
|
|
}
|
|
|
|
if (event_type & EPOLLIN) {
|
|
if (!ReceiveClientMessage(event)) {
|
|
UninstallClientSocket(event);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (event_type & EPOLLHUP || event_type & EPOLLRDHUP) {
|
|
UninstallClientSocket(event);
|
|
return;
|
|
}
|
|
|
|
LOG(ERROR) << "Unexpected event 0x" << std::hex << event_type;
|
|
return;
|
|
}
|
|
|
|
bool ExceptionHandlerServer::InstallClientSocket(ScopedFileHandle socket,
|
|
Event::Type type) {
|
|
// The handler may not have permission to set SO_PASSCRED on the socket, but
|
|
// it doesn't need to if the client has already set it.
|
|
// https://bugs.chromium.org/p/crashpad/issues/detail?id=252
|
|
int optval;
|
|
socklen_t optlen = sizeof(optval);
|
|
if (getsockopt(socket.get(), SOL_SOCKET, SO_PASSCRED, &optval, &optlen) !=
|
|
0) {
|
|
PLOG(ERROR) << "getsockopt";
|
|
return false;
|
|
}
|
|
if (!optval) {
|
|
optval = 1;
|
|
optlen = sizeof(optval);
|
|
if (setsockopt(socket.get(), SOL_SOCKET, SO_PASSCRED, &optval, optlen) !=
|
|
0) {
|
|
PLOG(ERROR) << "setsockopt";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
auto event = std::make_unique<Event>();
|
|
event->type = type;
|
|
event->fd.reset(socket.release());
|
|
|
|
Event* eventp = event.get();
|
|
|
|
if (!clients_.insert(std::make_pair(event->fd.get(), std::move(event)))
|
|
.second) {
|
|
LOG(ERROR) << "duplicate descriptor";
|
|
return false;
|
|
}
|
|
|
|
epoll_event poll_event;
|
|
poll_event.events = EPOLLIN | EPOLLRDHUP;
|
|
poll_event.data.ptr = eventp;
|
|
|
|
if (epoll_ctl(pollfd_.get(), EPOLL_CTL_ADD, eventp->fd.get(), &poll_event) !=
|
|
0) {
|
|
PLOG(ERROR) << "epoll_ctl";
|
|
clients_.erase(eventp->fd.get());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ExceptionHandlerServer::UninstallClientSocket(Event* event) {
|
|
if (epoll_ctl(pollfd_.get(), EPOLL_CTL_DEL, event->fd.get(), nullptr) != 0) {
|
|
PLOG(ERROR) << "epoll_ctl";
|
|
return false;
|
|
}
|
|
|
|
if (clients_.erase(event->fd.get()) != 1) {
|
|
LOG(ERROR) << "event not found";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ExceptionHandlerServer::ReceiveClientMessage(Event* event) {
|
|
ExceptionHandlerProtocol::ClientToServerMessage message;
|
|
ucred creds;
|
|
if (!UnixCredentialSocket::RecvMsg(
|
|
event->fd.get(), &message, sizeof(message), &creds)) {
|
|
return false;
|
|
}
|
|
|
|
switch (message.type) {
|
|
case ExceptionHandlerProtocol::ClientToServerMessage::kTypeCheckCredentials:
|
|
return SendCredentials(event->fd.get());
|
|
|
|
case ExceptionHandlerProtocol::ClientToServerMessage::kTypeCrashDumpRequest:
|
|
return HandleCrashDumpRequest(
|
|
creds,
|
|
message.client_info,
|
|
message.requesting_thread_stack_address,
|
|
event->fd.get(),
|
|
event->type == Event::Type::kSharedSocketMessage);
|
|
}
|
|
|
|
DCHECK(false);
|
|
LOG(ERROR) << "Unknown message type";
|
|
return false;
|
|
}
|
|
|
|
bool ExceptionHandlerServer::HandleCrashDumpRequest(
|
|
const ucred& creds,
|
|
const ExceptionHandlerProtocol::ClientInformation& client_info,
|
|
VMAddress requesting_thread_stack_address,
|
|
int client_sock,
|
|
bool multiple_clients) {
|
|
pid_t client_process_id = creds.pid;
|
|
pid_t requesting_thread_id = -1;
|
|
uid_t client_uid = creds.uid;
|
|
|
|
switch (
|
|
strategy_decider_->ChooseStrategy(client_sock, multiple_clients, creds)) {
|
|
case PtraceStrategyDecider::Strategy::kError:
|
|
if (multiple_clients) {
|
|
SendSIGCONT(client_process_id, requesting_thread_id);
|
|
}
|
|
return false;
|
|
|
|
case PtraceStrategyDecider::Strategy::kNoPtrace:
|
|
if (multiple_clients) {
|
|
SendSIGCONT(client_process_id, requesting_thread_id);
|
|
return true;
|
|
}
|
|
return SendMessageToClient(
|
|
client_sock,
|
|
ExceptionHandlerProtocol::ServerToClientMessage::
|
|
kTypeCrashDumpFailed);
|
|
|
|
case PtraceStrategyDecider::Strategy::kDirectPtrace: {
|
|
delegate_->HandleException(client_process_id,
|
|
client_uid,
|
|
client_info,
|
|
requesting_thread_stack_address,
|
|
&requesting_thread_id);
|
|
if (multiple_clients) {
|
|
SendSIGCONT(client_process_id, requesting_thread_id);
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case PtraceStrategyDecider::Strategy::kUseBroker:
|
|
DCHECK(!multiple_clients);
|
|
delegate_->HandleExceptionWithBroker(
|
|
client_process_id, client_uid, client_info, client_sock);
|
|
break;
|
|
}
|
|
|
|
return SendMessageToClient(
|
|
client_sock,
|
|
ExceptionHandlerProtocol::ServerToClientMessage::kTypeCrashDumpComplete);
|
|
}
|
|
|
|
} // namespace crashpad
|