blob: 4391c767e6007e8fed2f7d47a401b9f3d3011fbb [file] [log] [blame]
// Copyright (c) 2012 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 "debugd/src/sandboxed_process.h"
#include <inttypes.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <base/strings/stringprintf.h>
namespace debugd {
namespace {
const size_t kMaxWaitAttempts = 3;
const unsigned int kDelayUSec = 1000;
const char kMiniJail[] = "/sbin/minijail0";
// waitpid(2) with a timeout of kMaxWaitAttempts * kDelayUSec.
bool waitpid_awhile(pid_t pid) {
DCHECK_GT(pid, 0);
for (size_t attempt = 0; attempt < kMaxWaitAttempts; ++attempt) {
pid_t res = waitpid(pid, nullptr, WNOHANG);
if (res > 0) {
return true;
}
if (res < 0) {
PLOG(ERROR) << "waitpid(" << pid << ") failed";
return false;
}
usleep(kDelayUSec);
}
return false;
}
} // namespace
const char SandboxedProcess::kDefaultUser[] = "debugd";
const char SandboxedProcess::kDefaultGroup[] = "debugd";
SandboxedProcess::SandboxedProcess()
: sandboxing_(true),
access_root_mount_ns_(false),
set_capabilities_(false),
user_(kDefaultUser),
group_(kDefaultGroup) {
}
// static
bool SandboxedProcess::GetHelperPath(const std::string& relative_path,
std::string* full_path) {
// This environment variable controls the root directory for debugd helpers,
// which lets people develop helpers even when verified boot is on.
const char* helpers_dir = getenv("DEBUGD_HELPERS");
std::string path = base::StringPrintf(
"%s/%s",
helpers_dir ? helpers_dir : "/usr/libexec/debugd/helpers",
relative_path.c_str());
if (path.length() > PATH_MAX)
return false;
*full_path = path;
return true;
}
bool SandboxedProcess::Init() {
if (sandboxing_ && (user_.empty() || group_.empty())) {
// Cannot sandbox without user/group.
return false;
}
if (set_capabilities_ && (!sandboxing_ || user_ == "root")) {
// Restricting capabilities requires dropping root.
return false;
}
AddArg(kMiniJail);
// Enter a new mount namespace. This is done for every process to avoid
// affecting the original mount namespace.
AddArg("-v");
if (sandboxing_) {
if (user_ != "root") {
AddArg("-u");
AddArg(user_);
}
if (group_ != "root") {
AddArg("-g");
AddArg(group_);
}
if (set_capabilities_) {
AddStringOption("-c",
base::StringPrintf("0x%" PRIx64, capabilities_mask_));
}
}
if (access_root_mount_ns_) {
// Enter root mount namespace.
AddStringOption("-V", "/proc/1/ns/mnt");
}
if (!seccomp_filter_policy_file_.empty()) {
AddStringOption("-S", seccomp_filter_policy_file_);
// Whenever we use a seccomp filter, we want no-new-privs so we can apply
// the policy after dropping other privs.
AddArg("-n");
}
AddArg("--");
return true;
}
void SandboxedProcess::DisableSandbox() {
sandboxing_ = false;
}
void SandboxedProcess::SandboxAs(const std::string& user,
const std::string& group) {
sandboxing_ = true;
user_ = user;
group_ = group;
}
void SandboxedProcess::SetCapabilities(uint64_t capabilities_mask) {
set_capabilities_ = true;
capabilities_mask_ = capabilities_mask;
}
void SandboxedProcess::SetSeccompFilterPolicyFile(const std::string& path) {
seccomp_filter_policy_file_ = path;
}
void SandboxedProcess::AllowAccessRootMountNamespace() {
access_root_mount_ns_ = true;
}
bool SandboxedProcess::KillProcessGroup() {
pid_t minijail_pid = pid();
if (minijail_pid == 0) {
LOG(ERROR) << "Process is not running";
return false;
}
// Minijail sets its process group ID equal to its PID,
// so we can use pid() as PGID. Check that's still the case.
pid_t pgid = getpgid(minijail_pid);
if (pgid < 0) {
PLOG(ERROR) << "getpgid(minijail_pid) failed";
return false;
}
if (pgid != minijail_pid) {
LOG(ERROR) << "Minijail PGID " << pgid << " is different from PID "
<< minijail_pid;
return false;
}
// Attempt to kill minijail gracefully with SIGINT and then SIGTERM.
// Note: we fall through to SIGKILLing the process group below even if this
// succeeds to ensure all descendents have been killed.
bool minijail_reaped = false;
for (auto sig : {SIGINT, SIGTERM}) {
if (kill(minijail_pid, sig) != 0) {
// ESRCH means the process already exited.
if (errno != ESRCH) {
PLOG(WARNING) << "failed to kill " << minijail_pid
<< " with signal " << sig;
}
break;
}
if (waitpid_awhile(minijail_pid)) {
minijail_reaped = true;
break;
}
}
// kill(-pgid) kills every process with process group ID |pgid|.
if (kill(-pgid, SIGKILL) != 0) {
// ESRCH means the graceful exit above caught everything.
if (errno != ESRCH) {
PLOG(ERROR) << "kill(-pgid, SIGKILL) failed";
return false;
}
}
// If kill(2) succeeded, we release the PID.
UpdatePid(0);
// We only expect to reap one process, the Minijail process.
// If the jailed process dies first, Minijail or init will reap it.
// If the Minijail process dies first, we will reap it. The jailed process
// will then be reaped by init.
if (!minijail_reaped && !waitpid_awhile(minijail_pid)) {
LOG(ERROR) << "Process " << minijail_pid << " did not terminate";
return false;
}
return true;
}
} // namespace debugd