blob: 4bc617c65fe0c3b6ba5aa50e5df7ff3b05d2e709 [file] [log] [blame]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <init/process_killer/process_killer.h>
#include <sys/signal.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <base/json/json_reader.h>
#include <base/logging.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/threading/platform_thread.h>
#include <base/time/time.h>
#include <init/process_killer/process.h>
#include <init/process_killer/process_manager.h>
namespace init {
namespace {
constexpr char const* kSessionMountRegexes[] = {
"/data",
"/home/.shadow/[a-fA-F0-9]{40}/mount",
"/home/chronos/u-[a-fA-F0-9]{40}",
"/home/chronos/user",
"/home/root/[a-fA-F0-9]{40}",
"/home/user/[a-fA-F0-9]{40}",
"/run/daemon-store",
};
constexpr char const* kSystemMountRegexes[] = {"/var", "/home", "/usr/local",
"/mnt/stateful_partition"};
constexpr char const* kSessionDeviceRegexes[] = {
"/dev/mapper/dmcrypt-",
};
constexpr char const* kSystemDeviceRegexes[] = {
"/dev/mapper/encstateful",
"/dev/sd[a-z]1",
"/dev/mmcblk[0-9]p1",
"/dev/nvme[0-9]n[0-9]p1",
};
constexpr char kMatchNothingRegex[] = "$^";
constexpr int kKillerIterations = 10;
constexpr base::TimeDelta kSleepInterval = base::Milliseconds(100);
re2::RE2 ConstructMountRegex(bool session,
bool shutdown,
const std::string& mount_filter) {
std::vector<std::string> mounts;
if (session) {
for (auto mount : kSessionMountRegexes) {
mounts.push_back(mount);
}
}
if (shutdown) {
for (auto mount : kSystemMountRegexes) {
mounts.push_back(mount);
}
}
if (mount_filter.size()) {
auto filter = base::JSONReader::ReadAndReturnValueWithError(mount_filter);
if (!filter.has_value()) {
LOG(ERROR) << "Could not parse the mount filter JSON list. Error: "
<< filter.error().message;
} else if (!filter->is_list()) {
LOG(ERROR) << "Invalid mount filter JSON, expecting path regex list.";
} else {
for (const auto& mount : filter->GetList()) {
if (auto* mount_str = mount.GetIfString();
mount_str && mount_str->size()) {
mounts.push_back(*mount_str);
}
}
}
}
if (mounts.empty()) {
// Avoid empty regex which matches to any string, return
// `kMatchNothingRegex` instead.
return kMatchNothingRegex;
}
return re2::RE2("^(" + base::JoinString(mounts, "|") + ")");
}
re2::RE2 ConstructDeviceRegex(bool session, bool shutdown) {
std::vector<std::string> devices;
if (session) {
for (auto device : kSessionDeviceRegexes) {
devices.push_back(device);
}
}
if (shutdown) {
for (auto device : kSystemDeviceRegexes) {
devices.push_back(device);
}
}
if (devices.empty()) {
return kMatchNothingRegex;
}
return re2::RE2("(" + base::JoinString(devices, "|") + ")");
}
} // namespace
ProcessKiller::ProcessKiller(bool session,
bool shutdown,
const std::string& mount_filter)
: mount_regex_(ConstructMountRegex(session, shutdown, mount_filter)),
device_regex_(ConstructDeviceRegex(session, shutdown)),
pm_(std::make_unique<ProcessManager>(base::FilePath("/proc"))) {}
void ProcessKiller::LogProcesses() {
for (ActiveProcess& p : process_list_) {
p.LogProcess(mount_regex_, device_regex_);
}
}
void ProcessKiller::KillProcesses(bool files, bool devices) {
// First try sending SIGTERM.
for (int i = 0; i < kKillerIterations; i++) {
UpdateProcessList(files, devices);
if (process_list_.empty())
return;
LOG(INFO) << "Sending SIGTERM";
LogProcesses();
for (ActiveProcess& p : process_list_) {
pm_->SendSignalToProcess(p, SIGTERM);
}
base::PlatformThread::Sleep(kSleepInterval);
}
// If processes are still running, send SIGKILL.
for (int i = 0; i < kKillerIterations; i++) {
UpdateProcessList(files, devices);
if (process_list_.empty())
return;
LOG(INFO) << "Sending SIGKILL";
LogProcesses();
for (ActiveProcess& p : process_list_) {
pm_->SendSignalToProcess(p, SIGKILL);
}
base::PlatformThread::Sleep(kSleepInterval);
}
// Check processes still active and log.
UpdateProcessList(files, devices);
if (!process_list_.empty()) {
LOG(INFO) << "Processes still active:";
LogProcesses();
}
}
void ProcessKiller::UpdateProcessList(bool files, bool devices) {
process_list_ = pm_->GetProcessList(files, devices);
process_list_.erase(
std::remove_if(process_list_.begin(), process_list_.end(),
[this, files, devices](const ActiveProcess& p) {
bool dont_kill_this_process = true;
// Kill processes with a file open that matches the mount
// regex.
if (files && p.HasFileOpenOnMount(mount_regex_))
dont_kill_this_process = false;
// Kill processes with a non-init mount namespace and a
// mount open that matches the device regex.
if (devices && !p.InInitMountNamespace() &&
p.HasMountOpenFromDevice(device_regex_))
dont_kill_this_process = false;
if (!dont_kill_this_process && p.GetPid() == 1) {
LOG(ERROR) << "Cowardly refusing to kill init";
return true;
}
return dont_kill_this_process;
}),
process_list_.end());
}
} // namespace init