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