blob: 2ad16d705c51b92b915435553dc675c597f5d6a1 [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 <cstdio>
#include <cstdlib>
#include <base/command_line.h>
#include <brillo/syslog_logging.h>
#include <libminijail.h>
#include <scoped_minijail.h>
#include <sys/mount.h>
#include "usb_bouncer/entry_manager.h"
#include "usb_bouncer/util.h"
using usb_bouncer::EntryManager;
namespace {
void PrintUsage() {
printf(R"(Usage:
help - prints this help message.
cleanup - removes stale allow-list entries.
genrules - writes the generated rules configuration and to stdout.
udev (add|remove) <devpath> - handles a udev device event.
userlogin - add current entries to user allow-list.
)");
}
void DropPrivileges() {
ScopedMinijail j(minijail_new());
minijail_change_user(j.get(), usb_bouncer::kUsbBouncerUser);
minijail_change_group(j.get(), usb_bouncer::kUsbBouncerGroup);
minijail_inherit_usergroups(j.get());
minijail_no_new_privs(j.get());
minijail_use_seccomp_filter(j.get());
minijail_parse_seccomp_filters(
j.get(), "/usr/share/policy/usb_bouncer-seccomp.policy");
minijail_namespace_ipc(j.get());
minijail_namespace_net(j.get());
minijail_namespace_pids(j.get());
minijail_namespace_uts(j.get());
minijail_namespace_vfs(j.get());
if (minijail_enter_pivot_root(j.get(), "/mnt/empty") != 0) {
PLOG(FATAL) << "minijail_enter_pivot_root() failed.";
}
if (minijail_bind(j.get(), "/", "/", 0 /*writable*/)) {
PLOG(FATAL) << "minijail_bind(\"/\") failed.";
}
if (minijail_bind(j.get(), "/proc", "/proc", 0 /*writable*/)) {
PLOG(FATAL) << "minijail_bind(\"/\") failed.";
}
if (minijail_bind(j.get(), "/dev/log", "/dev/log", 0 /*writable*/)) {
PLOG(FATAL) << "minijail_bind(\"/dev/log\") failed.";
}
// "usb_bouncer genrules" writes to stdout.
minijail_preserve_fd(j.get(), STDOUT_FILENO, STDOUT_FILENO);
minijail_mount_dev(j.get());
minijail_mount_tmp(j.get());
if (minijail_bind(j.get(), "/sys", "/sys", 0 /*writable*/) != 0) {
PLOG(FATAL) << "minijail_bind(\"/sys\") failed.";
}
if (minijail_mount_with_data(j.get(), "tmpfs", "/run", "tmpfs",
MS_NOSUID | MS_NOEXEC | MS_NODEV,
"mode=0755,size=10M") != 0) {
PLOG(FATAL) << "minijail_mount_with_data(\"/run\") failed.";
}
std::string global_db_path("/");
global_db_path.append(usb_bouncer::kDefaultGlobalDir);
if (minijail_bind(j.get(), global_db_path.c_str(), global_db_path.c_str(),
1 /*writable*/) != 0) {
PLOG(FATAL) << "minijail_bind(\"" << global_db_path << "\") failed.";
}
constexpr char dbus_path[] = "/run/dbus";
if (minijail_bind(j.get(), dbus_path, dbus_path, 0 /*writable*/) != 0) {
PLOG(FATAL) << "minijail_bind(\"" << dbus_path << "\") failed.";
}
minijail_remount_mode(j.get(), MS_SLAVE);
// minijail_bind was not used because the MS_REC flag is needed.
if (minijail_mount(j.get(), usb_bouncer::kUserDbParentDir,
usb_bouncer::kUserDbParentDir, "none",
MS_BIND | MS_REC) != 0) {
PLOG(FATAL) << "minijail_mount(\"/" << usb_bouncer::kUserDbParentDir
<< "\") failed";
}
minijail_forward_signals(j.get());
pid_t pid = minijail_fork(j.get());
if (pid != 0) {
exit(minijail_wait(j.get()));
}
umask(0077);
}
EntryManager* GetEntryManagerOrDie() {
if (!EntryManager::CreateDefaultGlobalDB()) {
LOG(FATAL) << "Unable to create default global DB";
}
DropPrivileges();
EntryManager* entry_manager = EntryManager::GetInstance();
if (!entry_manager) {
LOG(FATAL) << "EntryManager::GetInstance() failed!";
}
return entry_manager;
}
int HandleCleanup(const std::vector<std::string>& argv) {
if (!argv.empty()) {
LOG(ERROR) << "Invalid options!";
PrintUsage();
return EXIT_FAILURE;
}
EntryManager* entry_manager = GetEntryManagerOrDie();
if (!entry_manager->GarbageCollect()) {
LOG(ERROR) << "cleanup failed!";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
int HandleGenRules(const std::vector<std::string>& argv) {
if (!argv.empty()) {
LOG(ERROR) << "Invalid options!";
PrintUsage();
return EXIT_FAILURE;
}
EntryManager* entry_manager = GetEntryManagerOrDie();
std::string rules = entry_manager->GenerateRules();
if (rules.empty()) {
LOG(ERROR) << "genrules failed!";
return EXIT_FAILURE;
}
printf("%s", rules.c_str());
return EXIT_SUCCESS;
}
int HandleUdev(const std::vector<std::string>& argv) {
if (argv.size() != 2) {
LOG(ERROR) << "Invalid options!";
PrintUsage();
return EXIT_FAILURE;
}
EntryManager::UdevAction action;
if (argv[0] == "add") {
action = EntryManager::UdevAction::kAdd;
} else if (argv[0] == "remove") {
action = EntryManager::UdevAction::kRemove;
} else {
LOG(ERROR) << "Invalid options!";
PrintUsage();
return EXIT_FAILURE;
}
EntryManager* entry_manager = GetEntryManagerOrDie();
if (!entry_manager->HandleUdev(action, argv[1])) {
LOG(ERROR) << "udev failed!";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
int HandleUserLogin(const std::vector<std::string>& argv) {
if (!argv.empty()) {
LOG(ERROR) << "Invalid options!";
PrintUsage();
return EXIT_FAILURE;
}
EntryManager* entry_manager = GetEntryManagerOrDie();
if (!entry_manager->HandleUserLogin()) {
LOG(ERROR) << "userlogin failed!";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
} // namespace
int main(int argc, char** argv) {
base::CommandLine::Init(argc, argv);
brillo::InitLog(brillo::kLogToSyslog | brillo::kLogToStderr);
base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
const auto& args = cl->argv();
if (args.size() < 2) {
LOG(ERROR) << "Invalid options!";
PrintUsage();
return EXIT_FAILURE;
}
const auto& command = args[1];
auto command_args_start = args.begin() + 2;
const struct {
const std::string command;
int (*handler)(const std::vector<std::string>& argv);
} command_handlers[] = {
{"cleanup", HandleCleanup},
{"genrules", HandleGenRules},
{"udev", HandleUdev},
{"userlogin", HandleUserLogin},
};
for (const auto& command_handler : command_handlers) {
if (command_handler.command == command) {
return command_handler.handler(
std::vector<std::string>(command_args_start, args.end()));
}
}
if (command != "help") {
LOG(ERROR) << "Invalid options!";
}
PrintUsage();
return EXIT_FAILURE;
}