blob: 7553e2842e0724e96be2f0655300087f4cfb3c43 [file] [log] [blame]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdlib.h>
#include <sys/capability.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <base/files/file.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <brillo/syslog_logging.h>
#include <libminijail.h>
#include <scoped_minijail.h>
namespace {
// The base directory where we keep various state flags.
#define RUN_STATE_DIR FILE_PATH_LITERAL("/run/crash_reporter")
// File whose existence mocks crash sending. If empty we pretend the
// crash sending was successful, otherwise unsuccessful.
constexpr base::FilePath::CharType kMockCrashSending[] =
RUN_STATE_DIR "/mock-crash-sending";
// Crash sender lock in case the sender is already running.
constexpr base::FilePath::CharType kLockFile[] = "/run/lock/crash_sender";
// Returns true if mock is enabled.
bool IsMock() {
return base::PathExists(base::FilePath(kMockCrashSending));
}
// Records that the crash sending is done.
void RecordCrashDone() {
if (IsMock()) {
// For testing purposes, emit a message to log so that we
// know when the test has received all the messages from this run.
// The string is referenced in
// third_party/autotest/files/client/cros/crash/crash_test.py
LOG(INFO) << "crash_sender done. (mock)";
}
}
// Sets up the minijail sandbox.
//
// crash_sender currently needs to run as root:
// - System crash reports in /var/spool/crash are owned by root.
// - User crash reports in /home/chronos/ are owned by chronos.
//
// crash_sender needs network access in order to upload things.
//
void SetUpSandbox(struct minijail* jail) {
// Keep CAP_DAC_OVERRIDE in order to access non-root paths.
minijail_use_caps(jail, CAP_TO_MASK(CAP_DAC_OVERRIDE));
// Set ambient capabilities because crash_sender runs other programs.
// TODO(satorux): Remove this once the code is entirely C++.
minijail_set_ambient_caps(jail);
minijail_no_new_privs(jail);
minijail_namespace_ipc(jail);
minijail_namespace_pids(jail);
minijail_remount_proc_readonly(jail);
minijail_namespace_vfs(jail);
minijail_mount_tmp(jail);
minijail_namespace_uts(jail);
minijail_forward_signals(jail);
}
// Locks on the file, or exits if locking fails.
void LockOrExit(const base::File& lock_file) {
if (flock(lock_file.GetPlatformFile(), LOCK_EX | LOCK_NB) != 0) {
if (errno == EWOULDBLOCK) {
LOG(ERROR) << "Already running; quitting.";
} else {
PLOG(ERROR) << "Failed to acquire a lock.";
}
RecordCrashDone();
exit(EXIT_FAILURE);
}
}
// Runs the main function for the child process.
int RunChildMain(int argc, char* argv[]) {
// Ensure only one instance of crash_sender runs at the same time.
base::File lock_file(base::FilePath(kLockFile), base::File::FLAG_OPEN_ALWAYS);
LockOrExit(lock_file);
char shell_script_path[] = "/sbin/crash_sender.sh";
argv[0] = shell_script_path;
execve(argv[0], argv, environ);
return EXIT_FAILURE; // execve() failed.
}
} // namespace
int main(int argc, char* argv[]) {
// Log to syslog (/var/log/messages), and stderr if stdin is a tty.
brillo::InitLog(brillo::kLogToSyslog | brillo::kLogToStderrIfTty);
// Set up a sandbox, and jail the child process.
ScopedMinijail jail(minijail_new());
SetUpSandbox(jail.get());
const pid_t pid = minijail_fork(jail.get());
if (pid == 0)
return RunChildMain(argc, argv);
// We rely on the child handling its own exit status, and a non-zero status
// isn't necessarily a bug (e.g. if mocked out that way). Only warn for an
// internal error.
const int status = minijail_wait(jail.get());
LOG_IF(ERROR, status < 0)
<< "Child process " << pid << " did not finish cleanly: " << status;
return status;
}