| // 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 <string> |
| #include <vector> |
| |
| #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), |
| inherit_usergroups_(false), |
| user_(kDefaultUser), |
| group_(kDefaultGroup) {} |
| |
| bool SandboxedProcess::Init( |
| const std::vector<std::string>& minijail_extra_args) { |
| 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 (inherit_usergroups_) { |
| AddArg("-G"); |
| } |
| 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"); |
| } |
| |
| for (const auto& arg : minijail_extra_args) |
| AddArg(arg); |
| |
| AddArg("--"); |
| |
| return true; |
| } |
| |
| bool SandboxedProcess::Init() { |
| return Init({}); |
| } |
| |
| void SandboxedProcess::DisableSandbox() { |
| sandboxing_ = false; |
| } |
| |
| void SandboxedProcess::SandboxAs(const std::string& user, |
| const std::string& group) { |
| sandboxing_ = true; |
| user_ = user; |
| group_ = group; |
| } |
| |
| void SandboxedProcess::InheritUsergroups() { |
| inherit_usergroups_ = true; |
| } |
| |
| 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 |