334 lines
8.8 KiB
C++
334 lines
8.8 KiB
C++
// 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 <assert.h>
|
|
#include <dirent.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <poll.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#include <vector>
|
|
|
|
#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<void*>(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<int*>(CMSG_DATA(hdr))[i]);
|
|
return true;
|
|
} else {
|
|
signal_fd = reinterpret_cast<int*>(CMSG_DATA(hdr))[0];
|
|
}
|
|
} else if (hdr->cmsg_type == SCM_CREDENTIALS) {
|
|
const struct ucred *cred =
|
|
reinterpret_cast<struct ucred*>(CMSG_DATA(hdr));
|
|
crashing_pid = cred->pid;
|
|
}
|
|
}
|
|
|
|
if (crashing_pid == -1 || signal_fd == -1) {
|
|
if (signal_fd)
|
|
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<CrashGenerationServer*>(arg)->Run();
|
|
return NULL;
|
|
}
|
|
|
|
} // namespace google_breakpad
|