blob: 094abf9360ba2e200717d5261def9d858eb3cec6 [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/bind_helpers.h>
#include <base/callback.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::Closure shutdown_closure) {
auto collector = base::WrapUnique<GuestCollector>(
new GuestCollector(std::move(shutdown_closure)));
if (!collector->Init()) {
collector.reset();
}
return collector;
}
GuestCollector::GuestCollector(base::Closure shutdown_closure)
: shutdown_closure_(std::move(shutdown_closure)), weak_factory_(this) {}
GuestCollector::~GuestCollector() {
FlushLogs();
}
bool GuestCollector::Init() {
if (!BindLogSocket(base::FilePath(kDevLog))) {
return false;
}
if (!StartWatcher(kFlushPeriod)) {
return false;
}
// Start listening for SIGTERM.
sigset_t mask;
sigemptyset(&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,
base::Unretained(this)));
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),
grpc::InsecureChannelCredentials()));
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);
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, shutdown_closure_);
}
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);
minijail_no_new_privs(jail.get());
// Pivot into an empty directory where we have no permissions.
minijail_namespace_vfs(jail.get());
minijail_enter_pivot_root(jail.get(), kPivotRoot);
minijail_enter(jail.get());
// Everything succeeded.
return true;
}
std::unique_ptr<GuestCollector> GuestCollector::CreateForTesting(
base::ScopedFD syslog_fd,
std::unique_ptr<vm_tools::LogCollector::Stub> stub) {
CHECK(stub);
auto collector =
base::WrapUnique<GuestCollector>(new GuestCollector(base::Closure()));
if (!collector->InitForTesting(std::move(syslog_fd), std::move(stub))) {
collector.reset();
}
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);
SetSyslogFDForTesting(std::move(syslog_fd));
// Start listening on the syslog socket.
return StartWatcher(kFlushPeriodForTesting);
}
} // namespace syslog
} // namespace vm_tools