diff --git a/compat/android/sys/epoll.cc b/compat/android/sys/epoll.cc new file mode 100644 index 00000000..64de763f --- /dev/null +++ b/compat/android/sys/epoll.cc @@ -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 + +#include +#include +#include + +#if __ANDROID_API__ < 21 + +extern "C" { + +int epoll_create1(int flags) { + static const auto epoll_create1_p = + reinterpret_cast(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 diff --git a/compat/android/sys/epoll.h b/compat/android/sys/epoll.h new file mode 100644 index 00000000..387813e6 --- /dev/null +++ b/compat/android/sys/epoll.h @@ -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 + +#include +#include + +// 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_ diff --git a/compat/android/sys/syscall.h b/compat/android/sys/syscall.h index 81fce78a..facce20f 100644 --- a/compat/android/sys/syscall.h +++ b/compat/android/sys/syscall.h @@ -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 diff --git a/compat/compat.gyp b/compat/compat.gyp index 6bc54848..d3b785b9 100644 --- a/compat/compat.gyp +++ b/compat/compat.gyp @@ -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', diff --git a/handler/handler.gyp b/handler/handler.gyp index ff455dee..6c45e1a3 100644 --- a/handler/handler.gyp +++ b/handler/handler.gyp @@ -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', diff --git a/handler/handler_test.gyp b/handler/handler_test.gyp index 4712c055..c047e7c9 100644 --- a/handler/handler_test.gyp +++ b/handler/handler_test.gyp @@ -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', diff --git a/handler/linux/exception_handler_server.cc b/handler/linux/exception_handler_server.cc new file mode 100644 index 00000000..3369c4f8 --- /dev/null +++ b/handler/linux/exception_handler_server.cc @@ -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 +#include +#include +#include +#include +#include +#include + +#include + +#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(PtraceScope::kClassic) || + ptrace_scope >= static_cast(PtraceScope::kUnknown)) { + LOG(ERROR) << "invalid ptrace scope"; + return PtraceScope::kUnknown; + } + + return static_cast(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 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(); + 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(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->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(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(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 diff --git a/handler/linux/exception_handler_server.h b/handler/linux/exception_handler_server.h new file mode 100644 index 00000000..fcafb887 --- /dev/null +++ b/handler/linux/exception_handler_server.h @@ -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 +#include + +#include +#include + +#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 Crashpad’s 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 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> clients_; + std::unique_ptr shutdown_event_; + std::unique_ptr 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_ diff --git a/handler/linux/exception_handler_server_test.cc b/handler/linux/exception_handler_server_test.cc new file mode 100644 index 00000000..fb6c21a9 --- /dev/null +++ b/handler/linux/exception_handler_server_test.cc @@ -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 +#include + +#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(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 diff --git a/handler/mac/exception_handler_server.h b/handler/mac/exception_handler_server.h index 7d893002..272cf762 100644 --- a/handler/mac/exception_handler_server.h +++ b/handler/mac/exception_handler_server.h @@ -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. diff --git a/handler/win/crash_report_exception_handler.h b/handler/win/crash_report_exception_handler.h index 54dfa971..e1fb725d 100644 --- a/handler/win/crash_report_exception_handler.h +++ b/handler/win/crash_report_exception_handler.h @@ -58,7 +58,7 @@ class CrashReportExceptionHandler : public ExceptionHandlerServer::Delegate { const std::map* process_annotations, const UserStreamDataSources* user_stream_data_sources); - ~CrashReportExceptionHandler() override; + ~CrashReportExceptionHandler(); // ExceptionHandlerServer::Delegate: diff --git a/snapshot/linux/cpu_context_linux.h b/snapshot/linux/cpu_context_linux.h index 8a2e812c..092762c2 100644 --- a/snapshot/linux/cpu_context_linux.h +++ b/snapshot/linux/cpu_context_linux.h @@ -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. //! diff --git a/snapshot/win/exception_snapshot_win_test.cc b/snapshot/win/exception_snapshot_win_test.cc index 843ad263..a1ab8c65 100644 --- a/snapshot/win/exception_snapshot_win_test.cc +++ b/snapshot/win/exception_snapshot_win_test.cc @@ -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; } diff --git a/test/linux/scoped_pr_set_ptracer.cc b/test/linux/scoped_pr_set_ptracer.cc new file mode 100644 index 00000000..bc9695ae --- /dev/null +++ b/test/linux/scoped_pr_set_ptracer.cc @@ -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 +#include + +#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 diff --git a/test/linux/scoped_pr_set_ptracer.h b/test/linux/scoped_pr_set_ptracer.h new file mode 100644 index 00000000..df9cff78 --- /dev/null +++ b/test/linux/scoped_pr_set_ptracer.h @@ -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 + +#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_ diff --git a/test/test.gyp b/test/test.gyp index ce2ba7d5..ad88e036 100644 --- a/test/test.gyp +++ b/test/test.gyp @@ -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', diff --git a/util/linux/exception_handler_client.cc b/util/linux/exception_handler_client.cc new file mode 100644 index 00000000..d6d22338 --- /dev/null +++ b/util/linux/exception_handler_client.cc @@ -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 +#include +#include +#include +#include + +#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(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 diff --git a/util/linux/exception_handler_client.h b/util/linux/exception_handler_client.h new file mode 100644 index 00000000..a60b0656 --- /dev/null +++ b/util/linux/exception_handler_client.h @@ -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 + +#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_ diff --git a/util/linux/exception_handler_protocol.h b/util/linux/exception_handler_protocol.h new file mode 100644 index 00000000..3f71abf7 --- /dev/null +++ b/util/linux/exception_handler_protocol.h @@ -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 +#include +#include + +#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_ diff --git a/util/linux/ptrace_broker.h b/util/linux/ptrace_broker.h index d725cb7b..96cbee58 100644 --- a/util/linux/ptrace_broker.h +++ b/util/linux/ptrace_broker.h @@ -20,6 +20,7 @@ #include #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 diff --git a/util/linux/ptrace_client.cc b/util/linux/ptrace_client.cc index dabda12e..3cdfcbe2 100644 --- a/util/linux/ptrace_client.cc +++ b/util/linux/ptrace_client.cc @@ -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; } diff --git a/util/linux/scoped_ptrace_attach_test.cc b/util/linux/scoped_ptrace_attach_test.cc index 99072e4c..78552e77 100644 --- a/util/linux/scoped_ptrace_attach_test.cc +++ b/util/linux/scoped_ptrace_attach_test.cc @@ -15,12 +15,12 @@ #include "util/linux/scoped_ptrace_attach.h" #include -#include #include #include #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: diff --git a/util/posix/signals.cc b/util/posix/signals.cc index d79d53ab..63764ab8 100644 --- a/util/posix/signals.cc +++ b/util/posix/signals.cc @@ -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, diff --git a/util/posix/signals.h b/util/posix/signals.h index 36d33cd6..ade093bf 100644 --- a/util/posix/signals.h +++ b/util/posix/signals.h @@ -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. //! diff --git a/util/util.gyp b/util/util.gyp index a7bed771..43b6e32b 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -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', diff --git a/util/win/exception_handler_server.h b/util/win/exception_handler_server.h index 69f760d2..994cdba4 100644 --- a/util/win/exception_handler_server.h +++ b/util/win/exception_handler_server.h @@ -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. diff --git a/util/win/exception_handler_server_test.cc b/util/win/exception_handler_server_test.cc index 6f880516..ce316774 100644 --- a/util/win/exception_handler_server_test.cc +++ b/util/win/exception_handler_server_test.cc @@ -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_);