// Copyright (c) 2010 Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include #include #include #include #include #include #include #include #include #include #include "client/linux/crash_generation/crash_generation_server.h" #include "client/linux/crash_generation/client_info.h" #include "client/linux/handler/exception_handler.h" #include "client/linux/minidump_writer/minidump_writer.h" #include "common/linux/eintr_wrapper.h" #include "common/linux/guid_creator.h" #include "common/linux/safe_readlink.h" static const char kCommandQuit = 'x'; namespace google_breakpad { CrashGenerationServer::CrashGenerationServer( const int listen_fd, OnClientDumpRequestCallback dump_callback, void* dump_context, OnClientExitingCallback exit_callback, void* exit_context, bool generate_dumps, const string* dump_path) : server_fd_(listen_fd), dump_callback_(dump_callback), dump_context_(dump_context), exit_callback_(exit_callback), exit_context_(exit_context), generate_dumps_(generate_dumps), started_(false) { if (dump_path) dump_dir_ = *dump_path; else dump_dir_ = "/tmp"; } CrashGenerationServer::~CrashGenerationServer() { if (started_) Stop(); } bool CrashGenerationServer::Start() { if (started_ || 0 > server_fd_) return false; int control_pipe[2]; if (pipe(control_pipe)) return false; if (fcntl(control_pipe[0], F_SETFD, FD_CLOEXEC)) return false; if (fcntl(control_pipe[1], F_SETFD, FD_CLOEXEC)) return false; if (fcntl(control_pipe[0], F_SETFL, O_NONBLOCK)) return false; control_pipe_in_ = control_pipe[0]; control_pipe_out_ = control_pipe[1]; if (pthread_create(&thread_, NULL, ThreadMain, reinterpret_cast(this))) return false; started_ = true; return true; } void CrashGenerationServer::Stop() { assert(pthread_self() != thread_); if (!started_) return; HANDLE_EINTR(write(control_pipe_out_, &kCommandQuit, 1)); void* dummy; pthread_join(thread_, &dummy); close(control_pipe_in_); close(control_pipe_out_); started_ = false; } //static bool CrashGenerationServer::CreateReportChannel(int* server_fd, int* client_fd) { int fds[2]; if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds)) return false; static const int on = 1; // Enable passcred on the server end of the socket if (setsockopt(fds[1], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on))) return false; if (fcntl(fds[1], F_SETFL, O_NONBLOCK)) return false; if (fcntl(fds[1], F_SETFD, FD_CLOEXEC)) return false; *client_fd = fds[0]; *server_fd = fds[1]; return true; } // The following methods/functions execute on the server thread void CrashGenerationServer::Run() { struct pollfd pollfds[2]; memset(&pollfds, 0, sizeof(pollfds)); pollfds[0].fd = server_fd_; pollfds[0].events = POLLIN; pollfds[1].fd = control_pipe_in_; pollfds[1].events = POLLIN; while (true) { // infinite timeout int nevents = poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), -1); if (-1 == nevents) { if (EINTR == errno) { continue; } else { return; } } if (pollfds[0].revents && !ClientEvent(pollfds[0].revents)) return; if (pollfds[1].revents && !ControlEvent(pollfds[1].revents)) return; } } bool CrashGenerationServer::ClientEvent(short revents) { if (POLLHUP & revents) return false; assert(POLLIN & revents); // A process has crashed and has signaled us by writing a datagram // to the death signal socket. The datagram contains the crash context needed // for writing the minidump as well as a file descriptor and a credentials // block so that they can't lie about their pid. // The length of the control message: static const unsigned kControlMsgSize = CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred)); // The length of the regular payload: static const unsigned kCrashContextSize = sizeof(google_breakpad::ExceptionHandler::CrashContext); struct msghdr msg = {0}; struct iovec iov[1]; char crash_context[kCrashContextSize]; char control[kControlMsgSize]; const ssize_t expected_msg_size = sizeof(crash_context); iov[0].iov_base = crash_context; iov[0].iov_len = sizeof(crash_context); msg.msg_iov = iov; msg.msg_iovlen = sizeof(iov)/sizeof(iov[0]); msg.msg_control = control; msg.msg_controllen = kControlMsgSize; const ssize_t msg_size = HANDLE_EINTR(recvmsg(server_fd_, &msg, 0)); if (msg_size != expected_msg_size) return true; if (msg.msg_controllen != kControlMsgSize || msg.msg_flags & ~MSG_TRUNC) return true; // Walk the control payload and extract the file descriptor and validated pid. pid_t crashing_pid = -1; int signal_fd = -1; for (struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg); hdr; hdr = CMSG_NXTHDR(&msg, hdr)) { if (hdr->cmsg_level != SOL_SOCKET) continue; if (hdr->cmsg_type == SCM_RIGHTS) { const unsigned len = hdr->cmsg_len - (((uint8_t*)CMSG_DATA(hdr)) - (uint8_t*)hdr); assert(len % sizeof(int) == 0u); const unsigned num_fds = len / sizeof(int); if (num_fds > 1 || num_fds == 0) { // A nasty process could try and send us too many descriptors and // force a leak. for (unsigned i = 0; i < num_fds; ++i) close(reinterpret_cast(CMSG_DATA(hdr))[i]); return true; } else { signal_fd = reinterpret_cast(CMSG_DATA(hdr))[0]; } } else if (hdr->cmsg_type == SCM_CREDENTIALS) { const struct ucred *cred = reinterpret_cast(CMSG_DATA(hdr)); crashing_pid = cred->pid; } } if (crashing_pid == -1 || signal_fd == -1) { if (signal_fd != -1) close(signal_fd); return true; } string minidump_filename; if (!MakeMinidumpFilename(minidump_filename)) return true; if (!google_breakpad::WriteMinidump(minidump_filename.c_str(), crashing_pid, crash_context, kCrashContextSize)) { close(signal_fd); return true; } if (dump_callback_) { ClientInfo info(crashing_pid, this); dump_callback_(dump_context_, &info, &minidump_filename); } // Send the done signal to the process: it can exit now. // (Closing this will make the child's sys_read unblock and return 0.) close(signal_fd); return true; } bool CrashGenerationServer::ControlEvent(short revents) { if (POLLHUP & revents) return false; assert(POLLIN & revents); char command; if (read(control_pipe_in_, &command, 1)) return false; switch (command) { case kCommandQuit: return false; default: assert(0); } return true; } bool CrashGenerationServer::MakeMinidumpFilename(string& outFilename) { GUID guid; char guidString[kGUIDStringLength+1]; if (!(CreateGUID(&guid) && GUIDToString(&guid, guidString, sizeof(guidString)))) return false; char path[PATH_MAX]; snprintf(path, sizeof(path), "%s/%s.dmp", dump_dir_.c_str(), guidString); outFilename = path; return true; } // static void* CrashGenerationServer::ThreadMain(void *arg) { reinterpret_cast(arg)->Run(); return NULL; } } // namespace google_breakpad