blob: 5a49f065259cff6194de2a2ce300c96f799ec3ab [file] [log] [blame]
Elly Fong-Jonesd9a16cd2012-11-12 16:09:49 -05001// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Alex Vakulenko262be3f2014-07-30 15:25:50 -07005#include "debugd/src/sandboxed_process.h"
Elly Fong-Jonesd9a16cd2012-11-12 16:09:49 -05006
Edward Hillad7de4e2017-07-05 14:45:17 -06007#include <inttypes.h>
Jorge Lucangeli Obes389a9ee2015-05-14 17:37:01 -07008#include <signal.h>
Eric Carusoa6f1adb2017-05-24 14:19:46 -07009#include <stdlib.h>
Jorge Lucangeli Obes389a9ee2015-05-14 17:37:01 -070010#include <sys/types.h>
11#include <sys/wait.h>
Jorge Lucangeli Obes389a9ee2015-05-14 17:37:01 -070012
Mike Frysinger56379d72019-02-19 16:03:03 -050013#include <string>
14#include <vector>
15
Ben Chan297c3c22013-07-17 17:34:12 -070016#include <base/strings/stringprintf.h>
17
Elly Fong-Jonesd9a16cd2012-11-12 16:09:49 -050018namespace debugd {
19
Ben Chanaf125862017-02-08 23:11:18 -080020namespace {
21
22const size_t kMaxWaitAttempts = 3;
23const unsigned int kDelayUSec = 1000;
24
25const char kMiniJail[] = "/sbin/minijail0";
26
Lann Martin419fe712017-06-07 10:45:47 -060027// waitpid(2) with a timeout of kMaxWaitAttempts * kDelayUSec.
28bool waitpid_awhile(pid_t pid) {
29 DCHECK_GT(pid, 0);
30 for (size_t attempt = 0; attempt < kMaxWaitAttempts; ++attempt) {
31 pid_t res = waitpid(pid, nullptr, WNOHANG);
32 if (res > 0) {
33 return true;
34 }
35 if (res < 0) {
36 PLOG(ERROR) << "waitpid(" << pid << ") failed";
37 return false;
38 }
39 usleep(kDelayUSec);
40 }
41 return false;
42}
43
Ben Chanaf125862017-02-08 23:11:18 -080044} // namespace
45
46const char SandboxedProcess::kDefaultUser[] = "debugd";
47const char SandboxedProcess::kDefaultGroup[] = "debugd";
Elly Fong-Jones215b5622013-03-20 14:32:18 -040048
Elly Fong-Jonesd9a16cd2012-11-12 16:09:49 -050049SandboxedProcess::SandboxedProcess()
Jorge Lucangeli Obesc99a12a2014-09-17 16:43:40 -070050 : sandboxing_(true),
51 access_root_mount_ns_(false),
Edward Hillad7de4e2017-07-05 14:45:17 -060052 set_capabilities_(false),
David Valleau09f46642017-12-19 17:55:14 -080053 inherit_usergroups_(false),
Jorge Lucangeli Obesc99a12a2014-09-17 16:43:40 -070054 user_(kDefaultUser),
55 group_(kDefaultGroup) {
56}
Elly Fong-Jonesd9a16cd2012-11-12 16:09:49 -050057
Ben Chan297c3c22013-07-17 17:34:12 -070058// static
59bool SandboxedProcess::GetHelperPath(const std::string& relative_path,
60 std::string* full_path) {
61 // This environment variable controls the root directory for debugd helpers,
62 // which lets people develop helpers even when verified boot is on.
63 const char* helpers_dir = getenv("DEBUGD_HELPERS");
64 std::string path = base::StringPrintf(
65 "%s/%s",
66 helpers_dir ? helpers_dir : "/usr/libexec/debugd/helpers",
67 relative_path.c_str());
68
69 if (path.length() > PATH_MAX)
70 return false;
71
72 *full_path = path;
73 return true;
74}
75
Mike Frysinger56379d72019-02-19 16:03:03 -050076bool SandboxedProcess::Init(
77 const std::vector<std::string>& minijail_extra_args) {
Jorge Lucangeli Obes75cb7ba2017-07-06 10:52:48 -040078 if (sandboxing_ && (user_.empty() || group_.empty())) {
79 // Cannot sandbox without user/group.
80 return false;
81 }
82
83 if (set_capabilities_ && (!sandboxing_ || user_ == "root")) {
84 // Restricting capabilities requires dropping root.
85 return false;
86 }
87
Jorge Lucangeli Obes623f8ca2014-09-18 10:50:06 -070088 AddArg(kMiniJail);
89 // Enter a new mount namespace. This is done for every process to avoid
90 // affecting the original mount namespace.
91 AddArg("-v");
92
Elly Fong-Jonesd9a16cd2012-11-12 16:09:49 -050093 if (sandboxing_) {
Elly Fong-Jonese56a8f62013-01-23 15:50:21 -050094 if (user_ != "root") {
95 AddArg("-u");
96 AddArg(user_);
97 }
98 if (group_ != "root") {
99 AddArg("-g");
100 AddArg(group_);
101 }
David Valleau09f46642017-12-19 17:55:14 -0800102 if (inherit_usergroups_) {
103 AddArg("-G");
104 }
Jorge Lucangeli Obes75cb7ba2017-07-06 10:52:48 -0400105 if (set_capabilities_) {
Edward Hillad7de4e2017-07-05 14:45:17 -0600106 AddStringOption("-c",
Jorge Lucangeli Obes75cb7ba2017-07-06 10:52:48 -0400107 base::StringPrintf("0x%" PRIx64, capabilities_mask_));
Edward Hillad7de4e2017-07-05 14:45:17 -0600108 }
109 }
110
Jorge Lucangeli Obes623f8ca2014-09-18 10:50:06 -0700111 if (access_root_mount_ns_) {
112 // Enter root mount namespace.
113 AddStringOption("-V", "/proc/1/ns/mnt");
114 }
115
Justin Carlson73310fb2016-10-11 16:13:26 -0700116 if (!seccomp_filter_policy_file_.empty()) {
117 AddStringOption("-S", seccomp_filter_policy_file_);
Justin Carlson187c7252016-10-28 11:03:12 -0700118
119 // Whenever we use a seccomp filter, we want no-new-privs so we can apply
120 // the policy after dropping other privs.
121 AddArg("-n");
Justin Carlson73310fb2016-10-11 16:13:26 -0700122 }
123
Mike Frysinger56379d72019-02-19 16:03:03 -0500124 for (const auto& arg : minijail_extra_args)
125 AddArg(arg);
126
Jorge Lucangeli Obes623f8ca2014-09-18 10:50:06 -0700127 AddArg("--");
128
Elly Fong-Jonesd9a16cd2012-11-12 16:09:49 -0500129 return true;
130}
131
Mike Frysinger56379d72019-02-19 16:03:03 -0500132bool SandboxedProcess::Init() {
133 return Init({});
134}
135
Elly Fong-Jonesd9a16cd2012-11-12 16:09:49 -0500136void SandboxedProcess::DisableSandbox() {
137 sandboxing_ = false;
138}
139
140void SandboxedProcess::SandboxAs(const std::string& user,
141 const std::string& group) {
142 sandboxing_ = true;
143 user_ = user;
144 group_ = group;
145}
146
David Valleau09f46642017-12-19 17:55:14 -0800147void SandboxedProcess::InheritUsergroups() {
148 inherit_usergroups_ = true;
149}
150
Edward Hillad7de4e2017-07-05 14:45:17 -0600151void SandboxedProcess::SetCapabilities(uint64_t capabilities_mask) {
152 set_capabilities_ = true;
153 capabilities_mask_ = capabilities_mask;
154}
155
Justin Carlson73310fb2016-10-11 16:13:26 -0700156void SandboxedProcess::SetSeccompFilterPolicyFile(const std::string& path) {
157 seccomp_filter_policy_file_ = path;
158}
159
Jorge Lucangeli Obesc99a12a2014-09-17 16:43:40 -0700160void SandboxedProcess::AllowAccessRootMountNamespace() {
161 access_root_mount_ns_ = true;
162}
163
Jorge Lucangeli Obes389a9ee2015-05-14 17:37:01 -0700164bool SandboxedProcess::KillProcessGroup() {
165 pid_t minijail_pid = pid();
166 if (minijail_pid == 0) {
167 LOG(ERROR) << "Process is not running";
168 return false;
169 }
170
171 // Minijail sets its process group ID equal to its PID,
172 // so we can use pid() as PGID. Check that's still the case.
173 pid_t pgid = getpgid(minijail_pid);
174 if (pgid < 0) {
175 PLOG(ERROR) << "getpgid(minijail_pid) failed";
176 return false;
177 }
178 if (pgid != minijail_pid) {
179 LOG(ERROR) << "Minijail PGID " << pgid << " is different from PID "
180 << minijail_pid;
181 return false;
182 }
183
Lann Martin419fe712017-06-07 10:45:47 -0600184 // Attempt to kill minijail gracefully with SIGINT and then SIGTERM.
185 // Note: we fall through to SIGKILLing the process group below even if this
186 // succeeds to ensure all descendents have been killed.
187 bool minijail_reaped = false;
188 for (auto sig : {SIGINT, SIGTERM}) {
189 if (kill(minijail_pid, sig) != 0) {
190 // ESRCH means the process already exited.
191 if (errno != ESRCH) {
192 PLOG(WARNING) << "failed to kill " << minijail_pid
193 << " with signal " << sig;
194 }
195 break;
196 }
197 if (waitpid_awhile(minijail_pid)) {
198 minijail_reaped = true;
199 break;
200 }
201 }
202
Jorge Lucangeli Obes389a9ee2015-05-14 17:37:01 -0700203 // kill(-pgid) kills every process with process group ID |pgid|.
Lann Martin419fe712017-06-07 10:45:47 -0600204 if (kill(-pgid, SIGKILL) != 0) {
205 // ESRCH means the graceful exit above caught everything.
206 if (errno != ESRCH) {
207 PLOG(ERROR) << "kill(-pgid, SIGKILL) failed";
208 return false;
209 }
Jorge Lucangeli Obes389a9ee2015-05-14 17:37:01 -0700210 }
211
212 // If kill(2) succeeded, we release the PID.
213 UpdatePid(0);
214
215 // We only expect to reap one process, the Minijail process.
216 // If the jailed process dies first, Minijail or init will reap it.
217 // If the Minijail process dies first, we will reap it. The jailed process
218 // will then be reaped by init.
Lann Martin419fe712017-06-07 10:45:47 -0600219 if (!minijail_reaped && !waitpid_awhile(minijail_pid)) {
220 LOG(ERROR) << "Process " << minijail_pid << " did not terminate";
221 return false;
Jorge Lucangeli Obes389a9ee2015-05-14 17:37:01 -0700222 }
223
Lann Martin419fe712017-06-07 10:45:47 -0600224 return true;
Jorge Lucangeli Obes389a9ee2015-05-14 17:37:01 -0700225}
226
Ben Chana0011d82014-05-13 00:19:29 -0700227} // namespace debugd