linux: Add ExceptionHandlerServer and ExceptionHandlerClient

Bug: crashpad:30
Change-Id: I60874a26ccb281144f870df2b4d16c6970a39f6b
Reviewed-on: https://chromium-review.googlesource.com/772824
Commit-Queue: Joshua Peraza <jperaza@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
Joshua Peraza 2017-12-19 12:03:54 -08:00 committed by Commit Bot
parent 99b0030616
commit 9b2ba587f6
27 changed files with 1483 additions and 58 deletions

View File

@ -0,0 +1,37 @@
// Copyright 2017 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 <sys/epoll.h>
#include <dlfcn.h>
#include <sys/syscall.h>
#include <unistd.h>
#if __ANDROID_API__ < 21
extern "C" {
int epoll_create1(int flags) {
static const auto epoll_create1_p =
reinterpret_cast<int (*)(int)>(dlsym(RTLD_DEFAULT, "epoll_create1"));
if (epoll_create1_p) {
return epoll_create1_p(flags);
}
return syscall(SYS_epoll_create1, flags);
}
} // extern "C"
#endif // __ANDROID_API__ < 21

View File

@ -0,0 +1,50 @@
// Copyright 2017 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.
#ifndef CRASHPAD_COMPAT_ANDROID_SYS_EPOLL_H_
#define CRASHPAD_COMPAT_ANDROID_SYS_EPOLL_H_
#include_next <sys/epoll.h>
#include <android/api-level.h>
#include <fcntl.h>
// This is missing from traditional headers before API 21.
#if !defined(EPOLLRDHUP)
#define EPOLLRDHUP 0x00002000
#endif
// EPOLL_CLOEXEC is undefined in traditional headers before API 21 and removed
// from unified headers at API levels < 21 as a means to indicate that
// epoll_create1 is missing from the C library, but the raw system call should
// still be available.
#if !defined(EPOLL_CLOEXEC)
#define EPOLL_CLOEXEC O_CLOEXEC
#endif
#if __ANDROID_API__ < 21
#ifdef __cplusplus
extern "C" {
#endif
int epoll_create1(int flags);
#ifdef __cplusplus
} // extern "C"
#endif
#endif // __ANDROID_API__ < 21
#endif // CRASHPAD_COMPAT_ANDROID_SYS_EPOLL_H_

View File

@ -19,6 +19,10 @@
// Android 5.0.0 (API 21) NDK
#if !defined(SYS_epoll_create1)
#define SYS_epoll_create1 __NR_epoll_create1
#endif
#if !defined(SYS_gettid)
#define SYS_gettid __NR_gettid
#endif

View File

@ -26,6 +26,8 @@
'android/linux/prctl.h',
'android/linux/ptrace.h',
'android/sched.h',
'android/sys/epoll.cc',
'android/sys/epoll.h',
'android/sys/mman.cc',
'android/sys/mman.h',
'android/sys/syscall.h',

View File

@ -39,6 +39,8 @@
'crash_report_upload_thread.h',
'handler_main.cc',
'handler_main.h',
'linux/exception_handler_server.cc',
'linux/exception_handler_server.h',
'mac/crash_report_exception_handler.cc',
'mac/crash_report_exception_handler.h',
'mac/exception_handler_server.cc',
@ -54,6 +56,27 @@
'win/crash_report_exception_handler.cc',
'win/crash_report_exception_handler.h',
],
'conditions': [
['OS=="linux" or OS=="android"', {
'sources!': [
'handler_main.cc',
],
}],
['OS=="linux"', {
'link_settings': {
'libraries': [
'-lcap',
],
},
}],
],
'target_conditions': [
['OS=="android"', {
'sources/': [
['include', '^linux/'],
],
}],
],
},
{
'target_name': 'crashpad_handler',

View File

@ -38,6 +38,7 @@
],
'sources': [
'crashpad_handler_test.cc',
'linux/exception_handler_server_test.cc',
'minidump_to_upload_parameters_test.cc',
],
'conditions': [
@ -50,6 +51,13 @@
],
}],
],
'target_conditions': [
['OS=="android"', {
'sources/': [
['include', '^linux/'],
],
}],
],
},
{
'target_name': 'crashpad_handler_test_extended_handler',

View File

@ -0,0 +1,446 @@
// Copyright 2017 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 "handler/linux/exception_handler_server.h"
#include <errno.h>
#include <sys/capability.h>
#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <utility>
#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/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() {
struct __user_cap_header_struct cap_header = {};
struct __user_cap_data_struct cap_data = {};
cap_header.pid = getpid();
if (capget(&cap_header, &cap_data) != 0) {
PLOG(ERROR) << "capget";
return false;
}
if (cap_header.version != _LINUX_CAPABILITY_VERSION_3) {
LOG(ERROR) << "Unexpected capability version " << std::hex
<< cap_header.version;
return false;
}
return (cap_data.effective & (1 << CAP_SYS_PTRACE)) != 0;
}
bool SendMessageToClient(int client_sock, ServerToClientMessage::Type type) {
ServerToClientMessage message = {};
message.type = type;
if (type == ServerToClientMessage::kTypeSetPtracer) {
message.pid = getpid();
}
return LoggingWriteFile(client_sock, &message, sizeof(message));
}
class PtraceStrategyDeciderImpl : public PtraceStrategyDecider {
public:
PtraceStrategyDeciderImpl() : PtraceStrategyDecider() {}
~PtraceStrategyDeciderImpl() = default;
Strategy ChooseStrategy(int sock, const ucred& client_credentials) override {
switch (GetPtraceScope()) {
case PtraceScope::kClassic:
return getuid() == client_credentials.uid ? Strategy::kDirectPtrace
: Strategy::kForkBroker;
case PtraceScope::kRestricted:
if (!SendMessageToClient(sock,
ServerToClientMessage::kTypeSetPtracer)) {
return Strategy::kError;
}
Errno status;
if (!LoggingReadFileExactly(sock, &status, sizeof(status))) {
return Strategy::kError;
}
if (status != 0) {
errno = status;
PLOG(ERROR) << "Handler Client SetPtracer";
return Strategy::kForkBroker;
}
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);
}
};
} // namespace
struct ExceptionHandlerServer::Event {
enum class Type { kShutdown, kClientMessage } type;
ScopedFileHandle fd;
};
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) {
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))) {
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_EQ(AsUnderlyingType(event->type),
AsUnderlyingType(Event::Type::kClientMessage));
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) {
int optval = 1;
socklen_t 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 = Event::Type::kClientMessage;
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) {
ClientToServerMessage message;
iovec iov;
iov.iov_base = &message;
iov.iov_len = sizeof(message);
msghdr msg;
msg.msg_name = nullptr;
msg.msg_namelen = 0;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
char cmsg_buf[CMSG_SPACE(sizeof(ucred))];
msg.msg_control = cmsg_buf;
msg.msg_controllen = sizeof(cmsg_buf);
msg.msg_flags = 0;
int res = recvmsg(event->fd.get(), &msg, 0);
if (res < 0) {
PLOG(ERROR) << "recvmsg";
return false;
}
if (res == 0) {
// The client had an orderly shutdown.
return false;
}
if (msg.msg_name != nullptr || msg.msg_namelen != 0) {
LOG(ERROR) << "unexpected msg name";
return false;
}
if (msg.msg_iovlen != 1) {
LOG(ERROR) << "unexpected iovlen";
return false;
}
if (msg.msg_iov[0].iov_len != sizeof(ClientToServerMessage)) {
LOG(ERROR) << "unexpected message size " << msg.msg_iov[0].iov_len;
return false;
}
auto client_msg =
reinterpret_cast<ClientToServerMessage*>(msg.msg_iov[0].iov_base);
switch (client_msg->type) {
case ClientToServerMessage::kCrashDumpRequest:
return HandleCrashDumpRequest(
msg, client_msg->client_info, event->fd.get());
}
DCHECK(false);
LOG(ERROR) << "Unknown message type";
return false;
}
bool ExceptionHandlerServer::HandleCrashDumpRequest(
const msghdr& msg,
const ClientInformation& client_info,
int client_sock) {
cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
if (cmsg == nullptr) {
LOG(ERROR) << "missing credentials";
return false;
}
if (cmsg->cmsg_level != SOL_SOCKET) {
LOG(ERROR) << "unexpected cmsg_level " << cmsg->cmsg_level;
return false;
}
if (cmsg->cmsg_type != SCM_CREDENTIALS) {
LOG(ERROR) << "unexpected cmsg_type " << cmsg->cmsg_type;
return false;
}
if (cmsg->cmsg_len != CMSG_LEN(sizeof(ucred))) {
LOG(ERROR) << "unexpected cmsg_len " << cmsg->cmsg_len;
return false;
}
ucred* client_credentials = reinterpret_cast<ucred*>(CMSG_DATA(cmsg));
pid_t client_process_id = client_credentials->pid;
switch (strategy_decider_->ChooseStrategy(client_sock, *client_credentials)) {
case PtraceStrategyDecider::Strategy::kError:
return false;
case PtraceStrategyDecider::Strategy::kNoPtrace:
return SendMessageToClient(client_sock,
ServerToClientMessage::kTypeCrashDumpFailed);
case PtraceStrategyDecider::Strategy::kDirectPtrace:
delegate_->HandleException(client_process_id,
client_info.exception_information_address);
break;
case PtraceStrategyDecider::Strategy::kForkBroker:
if (!SendMessageToClient(client_sock,
ServerToClientMessage::kTypeForkBroker)) {
return false;
}
delegate_->HandleExceptionWithBroker(
client_process_id,
client_info.exception_information_address,
client_sock);
break;
}
return SendMessageToClient(client_sock,
ServerToClientMessage::kTypeCrashDumpComplete);
}
} // namespace crashpad

View File

@ -0,0 +1,155 @@
// Copyright 2017 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.
#ifndef CRASHPAD_HANDLER_LINUX_EXCEPTION_HANDLER_SERVER_H_
#define CRASHPAD_HANDLER_LINUX_EXCEPTION_HANDLER_SERVER_H_
#include <stdint.h>
#include <sys/socket.h>
#include <memory>
#include <unordered_map>
#include "base/macros.h"
#include "util/file/file_io.h"
#include "util/linux/exception_handler_protocol.h"
#include "util/misc/address_types.h"
#include "util/misc/initialization_state_dcheck.h"
namespace crashpad {
//! \brief Abstract base class for deciding how the handler should `ptrace` a
//! client.
class PtraceStrategyDecider {
public:
virtual ~PtraceStrategyDecider() = default;
//! \brief The possible return values for ChooseStrategy().
enum class Strategy {
//! \brief An error occurred, with a message logged.
kError,
//! \brief Ptrace cannot be used.
kNoPtrace,
//! \brief The handler should `ptrace`-attach the client directly.
kDirectPtrace,
//! \brief The client should `fork` a PtraceBroker for the handler.
kForkBroker,
};
//! \brief Chooses an appropriate `ptrace` strategy.
//!
//! \param[in] sock A socket conncted to a ExceptionHandlerClient.
//! \param[in] client_credentials The credentials for the connected client.
//! \return the chosen #Strategy.
virtual Strategy ChooseStrategy(int sock,
const ucred& client_credentials) = 0;
protected:
PtraceStrategyDecider() = default;
};
//! \brief Runs the main exception-handling server in Crashpads handler
//! process.
class ExceptionHandlerServer {
public:
class Delegate {
public:
//! \brief Called on receipt of a crash dump request from a client.
//!
//! \param[in] client_process_id The process ID of the crashing client.
//! \param[in] exception_information_address The address in the client's
//! address space of an ExceptionInformation struct.
//! \return `true` on success. `false` on failure with a message logged.
virtual bool HandleException(pid_t client_process_id,
VMAddress exception_information_address) = 0;
//! \brief Called on the receipt of a crash dump request from a client for a
//! crash that should be mediated by a PtraceBroker.
//!
//! \param[in] client_process_id The process ID of the crashing client.
//! \param[in] exception_information_address The address in the client's
//! address space of an ExceptionInformation struct.
//! \param[in] broker_sock A socket connected to the PtraceBroker.
//! \return `true` on success. `false` on failure with a message logged.
virtual bool HandleExceptionWithBroker(
pid_t client_process_id,
VMAddress exception_information_address,
int broker_sock) = 0;
protected:
~Delegate() {}
};
ExceptionHandlerServer();
~ExceptionHandlerServer();
//! \brief Sets the handler's PtraceStrategyDecider.
//!
//! If this method is not called, a default PtraceStrategyDecider will be
//! used.
void SetPtraceStrategyDecider(std::unique_ptr<PtraceStrategyDecider> decider);
//! \brief Initializes this object.
//!
//! This method must be successfully called before Run().
//!
//! \param[in] sock A socket on which to receive client requests.
//! \return `true` on success. `false` on failure with a message logged.
bool InitializeWithClient(ScopedFileHandle sock);
//! \brief Runs the exception-handling server.
//!
//! This method must only be called once on an ExceptionHandlerServer object.
//! This method returns when there are no more client connections or Stop()
//! has been called.
//!
//! \param[in] delegate An object to send exceptions to.
void Run(Delegate* delegate);
//! \brief Stops a running exception-handling server.
//!
//! Stop() may be called at any time, and may be called from a signal handler.
//! If Stop() is called before Run() it will cause Run() to return as soon as
//! it is called. It is harmless to call Stop() after Run() has already
//! returned, or to call Stop() after it has already been called.
void Stop();
private:
struct Event;
void HandleEvent(Event* event, uint32_t event_type);
bool InstallClientSocket(ScopedFileHandle socket);
bool UninstallClientSocket(Event* event);
bool ReceiveClientMessage(Event* event);
bool HandleCrashDumpRequest(const msghdr& msg,
const ClientInformation& client_info,
int client_sock);
std::unordered_map<int, std::unique_ptr<Event>> clients_;
std::unique_ptr<Event> shutdown_event_;
std::unique_ptr<PtraceStrategyDecider> strategy_decider_;
Delegate* delegate_;
ScopedFileHandle pollfd_;
bool keep_running_;
InitializationStateDcheck initialized_;
DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServer);
};
} // namespace crashpad
#endif // CRASHPAD_HANDLER_LINUX_EXCEPTION_HANDLER_SERVER_H_

View File

@ -0,0 +1,307 @@
// Copyright 2017 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 "handler/linux/exception_handler_server.h"
#include <sys/types.h>
#include <unistd.h>
#include "base/logging.h"
#include "gtest/gtest.h"
#include "test/errors.h"
#include "test/linux/scoped_pr_set_ptracer.h"
#include "test/multiprocess.h"
#include "util/linux/direct_ptrace_connection.h"
#include "util/linux/exception_handler_client.h"
#include "util/linux/ptrace_client.h"
#include "util/synchronization/semaphore.h"
#include "util/thread/thread.h"
namespace crashpad {
namespace test {
namespace {
// Runs the ExceptionHandlerServer on a background thread.
class RunServerThread : public Thread {
public:
RunServerThread(ExceptionHandlerServer* server,
ExceptionHandlerServer::Delegate* delegate)
: server_(server), delegate_(delegate), join_sem_(0) {}
~RunServerThread() override {}
bool JoinWithTimeout(double timeout) {
if (!join_sem_.TimedWait(timeout)) {
return false;
}
Join();
return true;
}
private:
// Thread:
void ThreadMain() override {
server_->Run(delegate_);
join_sem_.Signal();
}
ExceptionHandlerServer* server_;
ExceptionHandlerServer::Delegate* delegate_;
Semaphore join_sem_;
DISALLOW_COPY_AND_ASSIGN(RunServerThread);
};
class ScopedStopServerAndJoinThread {
public:
ScopedStopServerAndJoinThread(ExceptionHandlerServer* server,
RunServerThread* thread)
: server_(server), thread_(thread) {}
~ScopedStopServerAndJoinThread() {
server_->Stop();
EXPECT_TRUE(thread_->JoinWithTimeout(5.0));
}
private:
ExceptionHandlerServer* server_;
RunServerThread* thread_;
DISALLOW_COPY_AND_ASSIGN(ScopedStopServerAndJoinThread);
};
class TestDelegate : public ExceptionHandlerServer::Delegate {
public:
TestDelegate()
: Delegate(), last_exception_address_(0), last_client_(-1), sem_(0) {}
~TestDelegate() {}
bool WaitForException(double timeout_seconds,
pid_t* last_client,
VMAddress* last_address) {
if (sem_.TimedWait(timeout_seconds)) {
*last_client = last_client_;
*last_address = last_exception_address_;
return true;
}
return false;
}
bool HandleException(pid_t client_process_id,
VMAddress exception_information_address) override {
DirectPtraceConnection connection;
bool connected = connection.Initialize(client_process_id);
EXPECT_TRUE(connected);
last_exception_address_ = exception_information_address;
last_client_ = client_process_id;
sem_.Signal();
return connected;
}
bool HandleExceptionWithBroker(pid_t client_process_id,
VMAddress exception_information_address,
int broker_sock) override {
PtraceClient client;
bool connected = client.Initialize(broker_sock, client_process_id);
EXPECT_TRUE(connected);
last_exception_address_ = exception_information_address,
last_client_ = client_process_id;
sem_.Signal();
return connected;
}
private:
VMAddress last_exception_address_;
pid_t last_client_;
Semaphore sem_;
DISALLOW_COPY_AND_ASSIGN(TestDelegate);
};
class MockPtraceStrategyDecider : public PtraceStrategyDecider {
public:
MockPtraceStrategyDecider(PtraceStrategyDecider::Strategy strategy)
: PtraceStrategyDecider(), strategy_(strategy) {}
~MockPtraceStrategyDecider() {}
Strategy ChooseStrategy(int sock, const ucred& client_credentials) override {
return strategy_;
}
private:
Strategy strategy_;
DISALLOW_COPY_AND_ASSIGN(MockPtraceStrategyDecider);
};
class ExceptionHandlerServerTest : public testing::Test {
public:
ExceptionHandlerServerTest()
: server_(),
delegate_(),
server_thread_(&server_, &delegate_),
sock_to_handler_() {}
~ExceptionHandlerServerTest() = default;
int SockToHandler() { return sock_to_handler_.get(); }
TestDelegate* Delegate() { return &delegate_; }
void Hangup() { sock_to_handler_.reset(); }
RunServerThread* ServerThread() { return &server_thread_; }
ExceptionHandlerServer* Server() { return &server_; }
class CrashDumpTest : public Multiprocess {
public:
CrashDumpTest(ExceptionHandlerServerTest* server_test, bool succeeds)
: Multiprocess(), server_test_(server_test), succeeds_(succeeds) {}
~CrashDumpTest() = default;
void MultiprocessParent() override {
ClientInformation info;
ASSERT_TRUE(
LoggingReadFileExactly(ReadPipeHandle(), &info, sizeof(info)));
if (succeeds_) {
VMAddress last_address;
pid_t last_client;
ASSERT_TRUE(server_test_->Delegate()->WaitForException(
5.0, &last_client, &last_address));
EXPECT_EQ(last_address, info.exception_information_address);
EXPECT_EQ(last_client, ChildPID());
} else {
CheckedReadFileAtEOF(ReadPipeHandle());
}
}
void MultiprocessChild() override {
ASSERT_EQ(close(server_test_->sock_to_client_), 0);
ClientInformation info;
info.exception_information_address = 42;
ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), &info, sizeof(info)));
// If the current ptrace_scope is restricted, the broker needs to be set
// as the ptracer for this process. Setting this process as its own
// ptracer allows the broker to inherit this condition.
ScopedPrSetPtracer set_ptracer(getpid());
ExceptionHandlerClient client(server_test_->SockToHandler());
ASSERT_EQ(client.RequestCrashDump(info), 0);
}
private:
ExceptionHandlerServerTest* server_test_;
bool succeeds_;
DISALLOW_COPY_AND_ASSIGN(CrashDumpTest);
};
void ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy strategy,
bool succeeds) {
ScopedStopServerAndJoinThread stop_server(Server(), ServerThread());
ServerThread()->Start();
Server()->SetPtraceStrategyDecider(
std::make_unique<MockPtraceStrategyDecider>(strategy));
CrashDumpTest test(this, succeeds);
test.Run();
}
protected:
void SetUp() override {
int socks[2];
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, socks), 0);
sock_to_handler_.reset(socks[0]);
sock_to_client_ = socks[1];
ASSERT_TRUE(server_.InitializeWithClient(ScopedFileHandle(socks[1])));
}
private:
ExceptionHandlerServer server_;
TestDelegate delegate_;
RunServerThread server_thread_;
ScopedFileHandle sock_to_handler_;
int sock_to_client_;
DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServerTest);
};
TEST_F(ExceptionHandlerServerTest, ShutdownWithNoClients) {
ServerThread()->Start();
Hangup();
ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0));
}
TEST_F(ExceptionHandlerServerTest, StopWithClients) {
ServerThread()->Start();
Server()->Stop();
ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0));
}
TEST_F(ExceptionHandlerServerTest, StopBeforeRun) {
Server()->Stop();
ServerThread()->Start();
ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0));
}
TEST_F(ExceptionHandlerServerTest, MultipleStops) {
ServerThread()->Start();
Server()->Stop();
Server()->Stop();
ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0));
}
TEST_F(ExceptionHandlerServerTest, RequestCrashDumpDefault) {
ScopedStopServerAndJoinThread stop_server(Server(), ServerThread());
ServerThread()->Start();
CrashDumpTest test(this, true);
test.Run();
}
TEST_F(ExceptionHandlerServerTest, RequestCrashDumpNoPtrace) {
ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kNoPtrace,
false);
}
TEST_F(ExceptionHandlerServerTest, RequestCrashDumpForkBroker) {
ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kForkBroker,
true);
}
TEST_F(ExceptionHandlerServerTest, RequestCrashDumpDirectPtrace) {
ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kDirectPtrace,
true);
}
TEST_F(ExceptionHandlerServerTest, RequestCrashDumpError) {
ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kError, false);
}
} // namespace
} // namespace test
} // namespace crashpad

View File

@ -63,9 +63,7 @@ class ExceptionHandlerServer {
//! \brief Stops a running exception-handling server.
//!
//! The normal mode of operation is to call Stop() while Run() is running. It
//! is expected that Stop() would be called from a signal handler.
//!
//! Stop() may be called at any time, and may be called from a signal handler.
//! If Stop() is called before Run() it will cause Run() to return as soon as
//! it is called. It is harmless to call Stop() after Run() has already
//! returned, or to call Stop() after it has already been called.

View File

@ -58,7 +58,7 @@ class CrashReportExceptionHandler : public ExceptionHandlerServer::Delegate {
const std::map<std::string, std::string>* process_annotations,
const UserStreamDataSources* user_stream_data_sources);
~CrashReportExceptionHandler() override;
~CrashReportExceptionHandler();
// ExceptionHandlerServer::Delegate:

View File

@ -53,7 +53,7 @@ void InitializeCPUContextX86_NoFloatingPoint(
const SignalThreadContext32& thread_context,
CPUContextX86* context);
// \{
//! \{
//! \brief Initializes a CPUContextX86_64 structure from native context
//! structures on Linux.
//!

View File

@ -79,7 +79,7 @@ class CrashingDelegate : public ExceptionHandlerServer::Delegate {
: server_ready_(server_ready),
completed_test_event_(completed_test_event),
break_near_(0) {}
~CrashingDelegate() override {}
~CrashingDelegate() {}
void set_break_near(WinVMAddress break_near) { break_near_ = break_near; }
@ -183,7 +183,7 @@ class SimulateDelegate : public ExceptionHandlerServer::Delegate {
: server_ready_(server_ready),
completed_test_event_(completed_test_event),
dump_near_(0) {}
~SimulateDelegate() override {}
~SimulateDelegate() {}
void set_dump_near(WinVMAddress dump_near) { dump_near_ = dump_near; }

View File

@ -0,0 +1,40 @@
// Copyright 2017 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 "test/linux/scoped_pr_set_ptracer.h"
#include <errno.h>
#include <sys/prctl.h>
#include "gtest/gtest.h"
#include "test/errors.h"
namespace crashpad {
namespace test {
ScopedPrSetPtracer::ScopedPrSetPtracer(pid_t pid) {
success_ = prctl(PR_SET_PTRACER, pid, 0, 0, 0) == 0;
if (!success_) {
EXPECT_EQ(errno, EINVAL) << ErrnoMessage("prctl");
}
}
ScopedPrSetPtracer::~ScopedPrSetPtracer() {
if (success_) {
EXPECT_EQ(prctl(PR_SET_PTRACER, 0, 0, 0, 0), 0) << ErrnoMessage("prctl");
}
}
} // namespace test
} // namespace crashpad

View File

@ -0,0 +1,47 @@
// Copyright 2017 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.
#ifndef CRASHPAD_TEST_LINUX_SCOPED_PR_SET_PTRACER_H_
#define CRASHPAD_TEST_LINUX_SCOPED_PR_SET_PTRACER_H_
#include <sys/types.h>
#include "base/macros.h"
namespace crashpad {
namespace test {
class ScopedPrSetPtracer {
public:
//! \brief Uses `PR_SET_PTRACER` to set \a pid as the caller's ptracer or
//! expects `EINVAL`.
//!
//! `PR_SET_PTRACER` is only supported if the Yama Linux security module (LSM)
//! is enabled. Otherwise, `prctl(PR_SET_PTRACER, ...)` fails with `EINVAL`.
//! See linux-4.9.20/security/yama/yama_lsm.c yama_task_prctl() and
//! linux-4.9.20/kernel/sys.c [sys_]prctl().
explicit ScopedPrSetPtracer(pid_t pid);
~ScopedPrSetPtracer();
private:
bool success_;
DISALLOW_COPY_AND_ASSIGN(ScopedPrSetPtracer);
};
} // namespace test
} // namespace crashpad
#endif // CRASHPAD_TEST_LINUX_SCOPED_PR_SET_PTRACER_H_

View File

@ -45,6 +45,8 @@
'linux/fake_ptrace_connection.h',
'linux/get_tls.cc',
'linux/get_tls.h',
'linux/scoped_pr_set_ptracer.cc',
'linux/scoped_pr_set_ptracer.h',
'mac/dyld.cc',
'mac/dyld.h',
'mac/exception_swallower.cc',

View File

@ -0,0 +1,162 @@
// Copyright 2017 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 "util/linux/exception_handler_client.h"
#include <errno.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "util/file/file_io.h"
#include "util/linux/ptrace_broker.h"
#include "util/posix/signals.h"
namespace crashpad {
ExceptionHandlerClient::ExceptionHandlerClient(int sock)
: server_sock_(sock), ptracer_(-1), can_set_ptracer_(true) {}
ExceptionHandlerClient::~ExceptionHandlerClient() = default;
int ExceptionHandlerClient::RequestCrashDump(const ClientInformation& info) {
int status = SendCrashDumpRequest(info);
if (status != 0) {
return status;
}
return WaitForCrashDumpComplete();
}
int ExceptionHandlerClient::SetPtracer(pid_t pid) {
if (ptracer_ == pid) {
return 0;
}
if (!can_set_ptracer_) {
return EPERM;
}
if (prctl(PR_SET_PTRACER, pid, 0, 0, 0) == 0) {
return 0;
}
return errno;
}
void ExceptionHandlerClient::SetCanSetPtracer(bool can_set_ptracer) {
can_set_ptracer_ = can_set_ptracer;
}
int ExceptionHandlerClient::SendCrashDumpRequest(
const ClientInformation& info) {
ClientToServerMessage message;
message.type = ClientToServerMessage::kCrashDumpRequest;
message.client_info = info;
iovec iov;
iov.iov_base = &message;
iov.iov_len = sizeof(message);
msghdr msg;
msg.msg_name = nullptr;
msg.msg_namelen = 0;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
ucred creds;
creds.pid = getpid();
creds.uid = geteuid();
creds.gid = getegid();
char cmsg_buf[CMSG_SPACE(sizeof(creds))];
msg.msg_control = cmsg_buf;
msg.msg_controllen = sizeof(cmsg_buf);
cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_CREDENTIALS;
cmsg->cmsg_len = CMSG_LEN(sizeof(creds));
*reinterpret_cast<ucred*>(CMSG_DATA(cmsg)) = creds;
if (sendmsg(server_sock_, &msg, MSG_NOSIGNAL) < 0) {
PLOG(ERROR) << "sendmsg";
return errno;
}
return 0;
}
int ExceptionHandlerClient::WaitForCrashDumpComplete() {
ServerToClientMessage message;
// If the server hangs up, ReadFileExactly will return false without setting
// errno.
errno = 0;
while (ReadFileExactly(server_sock_, &message, sizeof(message))) {
switch (message.type) {
case ServerToClientMessage::kTypeForkBroker: {
Signals::InstallDefaultHandler(SIGCHLD);
pid_t pid = fork();
if (pid < 0) {
Errno error = errno;
if (!WriteFile(server_sock_, &error, sizeof(error))) {
return errno;
}
continue;
}
if (pid == 0) {
#if defined(ARCH_CPU_64_BITS)
constexpr bool am_64_bit = true;
#else
constexpr bool am_64_bit = false;
#endif // ARCH_CPU_64_BITS
PtraceBroker broker(server_sock_, am_64_bit);
_exit(broker.Run());
}
int status = 0;
pid_t child = HANDLE_EINTR(waitpid(pid, &status, 0));
DCHECK_EQ(child, pid);
if (child == pid && status != 0) {
return status;
}
continue;
}
case ServerToClientMessage::kTypeSetPtracer: {
Errno result = SetPtracer(message.pid);
if (!WriteFile(server_sock_, &result, sizeof(result))) {
return errno;
}
continue;
}
case ServerToClientMessage::kTypeCrashDumpComplete:
case ServerToClientMessage::kTypeCrashDumpFailed:
return 0;
}
DCHECK(false);
}
return errno;
}
} // namespace crashpad

View File

@ -0,0 +1,68 @@
// Copyright 2017 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.
#ifndef CRASHPAD_UTIL_LINUX_EXCEPTION_HANDLER_CLIENT_H_
#define CRASHPAD_UTIL_LINUX_EXCEPTION_HANDLER_CLIENT_H_
#include <sys/types.h>
#include "base/macros.h"
#include "util/linux/exception_handler_protocol.h"
namespace crashpad {
//! A client for an ExceptionHandlerServer
class ExceptionHandlerClient {
public:
//! \brief Constructs this object.
//!
//! \param[in] sock A socket connected to an ExceptionHandlerServer.
explicit ExceptionHandlerClient(int sock);
~ExceptionHandlerClient();
//! \brief Request a crash dump from the ExceptionHandlerServer.
//!
//! This method blocks until the crash dump is complete.
//!
//! \param[in] info Information about this client.
//! \return 0 on success or an error code on failure.
int RequestCrashDump(const ClientInformation& info);
//! \brief Uses `prctl(PR_SET_PTRACER, ...)` to set the process with
//! process ID \a pid as the ptracer for this process.
//!
//! \param[in] pid The process ID of the process to be set as this process'
//! ptracer.
//! \return 0 on success or an error code on failure.
int SetPtracer(pid_t pid);
//! \brief Enables or disables SetPtracer().
//! \param[in] can_set_ptracer Whether SetPtracer should be enabled.
void SetCanSetPtracer(bool can_set_ptracer);
private:
int SendCrashDumpRequest(const ClientInformation& info);
int WaitForCrashDumpComplete();
int server_sock_;
pid_t ptracer_;
bool can_set_ptracer_;
DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerClient);
};
} // namespace crashpad
#endif // CRASHPAD_UTIL_LINUX_EXCEPTION_HANDLER_CLIENT_H_

View File

@ -0,0 +1,86 @@
// Copyright 2017 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.
#ifndef CRASHPAD_UTIL_LINUX_EXCEPTION_HANDLER_PROTOCOL_H_
#define CRASHPAD_UTIL_LINUX_EXCEPTION_HANDLER_PROTOCOL_H_
#include <errno.h>
#include <stdint.h>
#include <sys/types.h>
#include "util/file/file_io.h"
#include "util/misc/address_types.h"
namespace crashpad {
#pragma pack(push, 1)
//! \brief The type used for error reporting.
using Errno = int32_t;
static_assert(sizeof(Errno) >= sizeof(errno), "Errno type is too small");
//! \brief A boolean status suitable for communication between processes.
enum Bool : char { kBoolFalse, kBoolTrue };
//! \brief Information about a client registered with an ExceptionHandlerServer.
struct ClientInformation {
//! \brief The address in the client's address space of an
//! ExceptionInformation struct.
VMAddress exception_information_address;
};
//! \brief The message passed from client to server.
struct ClientToServerMessage {
static constexpr int32_t kVersion = 1;
//! \brief Indicates what message version is being used.
int32_t version = kVersion;
enum Type : uint32_t {
//! \brief Used to request a crash dump for the sending client.
kCrashDumpRequest
} type;
union {
//! \brief Valid for type == kCrashDumpRequest
ClientInformation client_info;
};
};
//! \brief The message passed from server to client.
struct ServerToClientMessage {
enum Type : uint32_t {
//! \brief Indicates that the client should fork a PtraceBroker process.
kTypeForkBroker,
//! \brief Inidicates that the client should set allow the handler to trace
//! it using PR_SET_PTRACER.
kTypeSetPtracer,
//! \brief Indicates that the handler has completed a requested crash dump.
kTypeCrashDumpComplete,
//! \brief Indicicates that the handler was unable to produce a crash dump.
kTypeCrashDumpFailed
} type;
//! \brief The handler's process ID. Valid for kTypeSetPtracer.
pid_t pid;
};
#pragma pack(pop)
} // namespace crashpad
#endif // CRASHPAD_UTIL_LINUX_EXCEPTION_HANDLER_PROTOCOL_H_

View File

@ -20,6 +20,7 @@
#include <sys/types.h>
#include "base/macros.h"
#include "util/linux/exception_handler_protocol.h"
#include "util/linux/ptrace_connection.h"
#include "util/linux/ptracer.h"
#include "util/linux/scoped_ptrace_attach.h"
@ -87,13 +88,6 @@ class PtraceBroker {
} iov;
};
//! \brief The type used for error reporting.
using Errno = int32_t;
static_assert(sizeof(Errno) >= sizeof(errno), "Errno type is too small");
//! \brief A boolean status suitable for communication between processes.
enum Bool : char { kBoolFalse, kBoolTrue };
//! \brief The response sent for a Request with type kTypeGetThreadInfo.
struct GetThreadInfoResponse {
//! \brief Information about the specified thread. Only valid if #success

View File

@ -27,7 +27,7 @@ namespace crashpad {
namespace {
bool ReceiveAndLogError(int sock, const std::string& operation) {
PtraceBroker::Errno error;
Errno error;
if (!LoggingReadFileExactly(sock, &error, sizeof(error))) {
return false;
}
@ -44,13 +44,14 @@ bool AttachImpl(int sock, pid_t tid) {
return false;
}
PtraceBroker::Bool success;
Bool success;
if (!LoggingReadFileExactly(sock, &success, sizeof(success))) {
return false;
}
if (success != PtraceBroker::kBoolTrue) {
if (success != kBoolTrue) {
ReceiveAndLogError(sock, "PtraceBroker Attach");
return false;
}
return true;
@ -90,11 +91,11 @@ bool PtraceClient::Initialize(int sock, pid_t pid) {
return false;
}
PtraceBroker::Bool is_64_bit;
Bool is_64_bit;
if (!LoggingReadFileExactly(sock_, &is_64_bit, sizeof(is_64_bit))) {
return false;
}
is_64_bit_ = is_64_bit == PtraceBroker::kBoolTrue;
is_64_bit_ = is_64_bit == kBoolTrue;
INITIALIZATION_STATE_SET_VALID(initialized_);
return true;
@ -130,7 +131,7 @@ bool PtraceClient::GetThreadInfo(pid_t tid, ThreadInfo* info) {
return false;
}
if (response.success == PtraceBroker::kBoolTrue) {
if (response.success == kBoolTrue) {
*info = response.info;
return true;
}

View File

@ -15,12 +15,12 @@
#include "util/linux/scoped_ptrace_attach.h"
#include <errno.h>
#include <sys/prctl.h>
#include <sys/ptrace.h>
#include <unistd.h>
#include "gtest/gtest.h"
#include "test/errors.h"
#include "test/linux/scoped_pr_set_ptracer.h"
#include "test/multiprocess.h"
#include "util/file/file_io.h"
@ -28,39 +28,13 @@ namespace crashpad {
namespace test {
namespace {
class ScopedPrSetPtracer {
public:
explicit ScopedPrSetPtracer(pid_t pid) {
// PR_SET_PTRACER is only supported if the Yama Linux security module (LSM)
// is enabled. Otherwise, this prctl() call fails with EINVAL. See
// linux-4.9.20/security/yama/yama_lsm.c yama_task_prctl() and
// linux-4.9.20/kernel/sys.c [sys_]prctl().
//
// If Yama is not enabled, the default ptrace restrictions should be
// sufficient for these tests.
//
// If Yama is enabled, then /proc/sys/kernel/yama/ptrace_scope must be 0
// (YAMA_SCOPE_DISABLED, in which case this prctl() is not necessary) or 1
// (YAMA_SCOPE_RELATIONAL) for these tests to succeed. If it is 2
// (YAMA_SCOPE_CAPABILITY) then the test requires CAP_SYS_PTRACE, and if it
// is 3 (YAMA_SCOPE_NO_ATTACH), these tests will fail.
success_ = prctl(PR_SET_PTRACER, pid, 0, 0, 0) == 0;
if (!success_) {
EXPECT_EQ(errno, EINVAL) << ErrnoMessage("prctl");
}
}
~ScopedPrSetPtracer() {
if (success_) {
EXPECT_EQ(prctl(PR_SET_PTRACER, 0, 0, 0, 0), 0) << ErrnoMessage("prctl");
}
}
private:
bool success_;
DISALLOW_COPY_AND_ASSIGN(ScopedPrSetPtracer);
};
// If Yama is not enabled, the default ptrace restrictions should be
// sufficient for these tests.
//
// If Yama is enabled, then /proc/sys/kernel/yama/ptrace_scope must be 0
// (YAMA_SCOPE_DISABLED) or 1 (YAMA_SCOPE_RELATIONAL) for these tests to
// succeed. If it is 2 (YAMA_SCOPE_CAPABILITY) then the test requires
// CAP_SYS_PTRACE, and if it is 3 (YAMA_SCOPE_NO_ATTACH), these tests will fail.
class AttachTest : public Multiprocess {
public:

View File

@ -138,6 +138,15 @@ bool Signals::InstallHandler(int sig,
return true;
}
// static
bool Signals::InstallDefaultHandler(int sig) {
struct sigaction action;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
action.sa_handler = SIG_DFL;
return sigaction(sig, &action, nullptr) == 0;
}
// static
bool Signals::InstallCrashHandlers(Handler handler,
int flags,

View File

@ -85,6 +85,14 @@ class Signals {
int flags,
struct sigaction* old_action);
//! \brief Installs `SIG_DFL` for the signal \a sig.
//!
//! \param[in] sig The signal to set the default action for.
//!
//! \return `true` on success, `false` on failure with errno set. No message
//! is logged.
static bool InstallDefaultHandler(int sig);
//! \brief Installs a new signal handler for all signals associated with
//! crashes.
//!

View File

@ -58,6 +58,10 @@
'linux/checked_address_range.h',
'linux/direct_ptrace_connection.cc',
'linux/direct_ptrace_connection.h',
'linux/exception_handler_client.cc',
'linux/exception_handler_client.h',
'linux/exception_handler_protocol.h',
'linux/exception_information.h',
'linux/memory_map.cc',
'linux/memory_map.h',
'linux/proc_stat_reader.cc',
@ -69,7 +73,6 @@
'linux/ptrace_connection.h',
'linux/ptracer.cc',
'linux/ptracer.h',
'linux/exception_information.h',
'linux/scoped_ptrace_attach.cc',
'linux/scoped_ptrace_attach.h',
'linux/thread_info.cc',

View File

@ -38,8 +38,6 @@ class ExceptionHandlerServer {
public:
class Delegate {
public:
virtual ~Delegate();
//! \brief Called when the server has created the named pipe connection
//! points and is ready to service requests.
virtual void ExceptionHandlerServerStarted() = 0;
@ -60,6 +58,9 @@ class ExceptionHandlerServer {
HANDLE process,
WinVMAddress exception_information_address,
WinVMAddress debug_critical_section_address) = 0;
protected:
~Delegate();
};
//! \brief Constructs the exception handling server.

View File

@ -56,7 +56,7 @@ class RunServerThread : public Thread {
class TestDelegate : public ExceptionHandlerServer::Delegate {
public:
explicit TestDelegate(HANDLE server_ready) : server_ready_(server_ready) {}
~TestDelegate() override {}
~TestDelegate() {}
void ExceptionHandlerServerStarted() override {
SetEvent(server_ready_);