blob: aa25219e0a2f4cc18000ebaa441a1f93bbf58584 [file] [log] [blame]
// Copyright 2020 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 "vm_tools/syslog/guest_collector.h"
#include <fcntl.h>
#include <signal.h>
#include <stdint.h>
#include <string.h>
#include <sys/signalfd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/sysinfo.h>
#include <sys/un.h>
#include <linux/vm_sockets.h> // Needs to come after sys/socket.h
#include <memory>
#include <string>
#include <utility>
#include <base/bind.h>
#include <base/callback.h>
#include <base/callback_helpers.h>
#include <base/check.h>
#include <base/check_op.h>
#include <base/location.h>
#include <base/logging.h>
#include <base/memory/ptr_util.h>
#include <base/posix/eintr_wrapper.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_piece.h>
#include <base/strings/stringprintf.h>
#include <base/threading/thread_task_runner_handle.h>
#include <base/time/time.h>
#include <chromeos/scoped_minijail.h>
#include <grpcpp/grpcpp.h>
#include "vm_tools/syslog/parser.h"
namespace pb = google::protobuf;
namespace vm_tools {
namespace syslog {
namespace {
// Path to the standard syslog listening path.
constexpr char kDevLog[] = "/dev/log";
// Known host port for the LogCollector service.
constexpr unsigned int kLogCollectorPort = 9999;
// Path to the standard empty directory where we will jail the daemon.
constexpr char kPivotRoot[] = "/mnt/empty";
// Name for the "syslog" user and group.
constexpr char kSyslog[] = "syslog";
} // namespace
std::unique_ptr<GuestCollector> GuestCollector::Create(
base::OnceClosure shutdown_closure) {
auto collector = base::WrapUnique<GuestCollector>(
new GuestCollector(std::move(shutdown_closure)));
if (!collector->Init()) {
return collector;
GuestCollector::GuestCollector(base::OnceClosure shutdown_closure)
: shutdown_closure_(std::move(shutdown_closure)), weak_factory_(this) {}
GuestCollector::~GuestCollector() {
bool GuestCollector::Init() {
if (!BindLogSocket(base::FilePath(kDevLog))) {
return false;
if (!StartWatcher(kFlushPeriod)) {
return false;
// Start listening for SIGTERM.
sigset_t mask;
sigaddset(&mask, SIGTERM);
signal_fd_.reset(signalfd(-1, &mask, SFD_CLOEXEC | SFD_NONBLOCK));
if (!signal_fd_.is_valid()) {
PLOG(ERROR) << "Unable to create signalfd";
return false;
signal_controller_ = base::FileDescriptorWatcher::WatchReadable(
signal_fd_.get(), base::BindRepeating(&GuestCollector::OnSignalReadable,
if (!signal_controller_) {
LOG(ERROR) << "Failed to watch signal file descriptor";
return false;
// Block the standard SIGTERM handler since we will be getting it via the
// signalfd.
sigprocmask(SIG_BLOCK, &mask, nullptr);
// Create the stub to the LogCollector service on the host.
stub_ = vm_tools::LogCollector::NewStub(grpc::CreateChannel(
base::StringPrintf("vsock:%u:%u", VMADDR_CID_HOST, kLogCollectorPort),
if (!stub_) {
LOG(ERROR) << "Failed to create stub for LogCollector service";
return false;
return EnterJail();
void GuestCollector::OnSignalReadable() {
signalfd_siginfo info;
if (read(signal_fd_.get(), &info, sizeof(info)) != sizeof(info)) {
PLOG(ERROR) << "Failed to read from signalfd";
DCHECK_EQ(info.ssi_signo, SIGTERM);
bool GuestCollector::SendUserLogs() {
vm_tools::EmptyMessage response;
grpc::Status status;
grpc::ClientContext ctx;
status = stub_->CollectUserLogs(&ctx, syslog_request(), &response);
if (!status.ok()) {
LOG(ERROR) << "Failed to send user logs to LogCollector service. Error "
<< "code " << status.error_code() << ": "
<< status.error_message();
return false;
return true;
bool GuestCollector::EnterJail() {
// Drop all unnecessary privileges.
ScopedMinijail jail(minijail_new());
if (!jail) {
PLOG(ERROR) << "Failed to create minijail";
return false;
minijail_change_user(jail.get(), kSyslog);
minijail_change_group(jail.get(), kSyslog);
// Pivot into an empty directory where we have no permissions.
minijail_enter_pivot_root(jail.get(), kPivotRoot);
// Everything succeeded.
return true;
std::unique_ptr<GuestCollector> GuestCollector::CreateForTesting(
base::ScopedFD syslog_fd,
std::unique_ptr<vm_tools::LogCollector::Stub> stub) {
auto collector =
base::WrapUnique<GuestCollector>(new GuestCollector(base::OnceClosure()));
if (!collector->InitForTesting(std::move(syslog_fd), std::move(stub))) {
return collector;
bool GuestCollector::InitForTesting(
base::ScopedFD syslog_fd,
std::unique_ptr<vm_tools::LogCollector::Stub> stub) {
// Store the stub for the LogCollector.
stub_ = std::move(stub);
// Start listening on the syslog socket.
return StartWatcher(kFlushPeriodForTesting);
} // namespace syslog
} // namespace vm_tools