blob: d0cc785550cacc3e07c3953c11bf8a7f0ff6d022 [file] [log] [blame]
Garrick Evans64a2df32018-12-12 16:53:46 +09001// Copyright 2019 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
Garrick Evans3388a032020-03-24 11:25:55 +09005#include "patchpanel/minijailed_process_runner.h"
Garrick Evans64a2df32018-12-12 16:53:46 +09006
7#include <linux/capability.h>
Jie Jiangcf5ce9c2020-07-14 17:22:03 +09008#include <sys/wait.h>
9#include <utility>
Garrick Evans64a2df32018-12-12 16:53:46 +090010
Jie Jiang0137f5d2020-12-16 16:23:59 +090011#include <base/files/scoped_file.h>
Garrick Evans64a2df32018-12-12 16:53:46 +090012#include <base/logging.h>
Jie Jiangcf5ce9c2020-07-14 17:22:03 +090013#include <base/posix/eintr_wrapper.h>
Garrick Evans2470caa2020-03-04 14:15:41 +090014#include <base/strings/string_number_conversions.h>
Garrick Evans64a2df32018-12-12 16:53:46 +090015#include <base/strings/string_util.h>
Simon Glass2b1da092020-05-21 12:24:16 -060016#include <brillo/process/process.h>
Garrick Evans64a2df32018-12-12 16:53:46 +090017
Garrick Evans3388a032020-03-24 11:25:55 +090018#include "patchpanel/net_util.h"
Garrick Evans7a1a9ee2020-01-28 11:03:57 +090019
Garrick Evans3388a032020-03-24 11:25:55 +090020namespace patchpanel {
Garrick Evans64a2df32018-12-12 16:53:46 +090021
22namespace {
23
Jason Jeremy Imand89b5f52019-10-24 10:39:17 +090024constexpr char kUnprivilegedUser[] = "nobody";
Garrick Evans6776b502020-05-01 10:41:56 +090025constexpr char kNetworkUnprivilegedUser[] = "patchpaneld";
Garrick Evans6d227b92019-12-03 16:11:29 +090026constexpr char kChownCapMask = CAP_TO_MASK(CAP_CHOWN);
Jason Jeremy Imand89b5f52019-10-24 10:39:17 +090027constexpr uint64_t kModprobeCapMask = CAP_TO_MASK(CAP_SYS_MODULE);
Garrick Evans8e8e3472020-01-23 14:03:50 +090028constexpr uint64_t kNetRawCapMask = CAP_TO_MASK(CAP_NET_RAW);
29constexpr uint64_t kNetRawAdminCapMask =
30 CAP_TO_MASK(CAP_NET_ADMIN) | CAP_TO_MASK(CAP_NET_RAW);
31
32// These match what is used in iptables.cc in firewalld.
33constexpr char kBrctlPath[] = "/sbin/brctl";
Garrick Evans6d227b92019-12-03 16:11:29 +090034constexpr char kChownPath[] = "/bin/chown";
Garrick Evans8e8e3472020-01-23 14:03:50 +090035constexpr char kIpPath[] = "/bin/ip";
36constexpr char kIptablesPath[] = "/sbin/iptables";
37constexpr char kIp6tablesPath[] = "/sbin/ip6tables";
Jason Jeremy Imand89b5f52019-10-24 10:39:17 +090038constexpr char kModprobePath[] = "/sbin/modprobe";
Garrick Evans6d227b92019-12-03 16:11:29 +090039constexpr char kSysctlPath[] = "/usr/sbin/sysctl";
Garrick Evans64a2df32018-12-12 16:53:46 +090040
Jie Jiangcf5ce9c2020-07-14 17:22:03 +090041// An empty string will be returned if read fails.
Jie Jiang0137f5d2020-12-16 16:23:59 +090042std::string ReadBlockingFDToStringAndClose(base::ScopedFD fd) {
43 if (!fd.is_valid()) {
44 LOG(ERROR) << "Invalid fd";
45 return "";
46 }
47
Jie Jiangcf5ce9c2020-07-14 17:22:03 +090048 static constexpr int kBufSize = 2048;
49 char buf[kBufSize] = {0};
50 std::string output;
51 while (true) {
Jie Jiang0137f5d2020-12-16 16:23:59 +090052 ssize_t cnt = HANDLE_EINTR(read(fd.get(), buf, kBufSize));
Jie Jiangcf5ce9c2020-07-14 17:22:03 +090053 if (cnt == -1) {
54 PLOG(ERROR) << __func__ << " failed";
Jie Jiang0137f5d2020-12-16 16:23:59 +090055 return "";
Jie Jiangcf5ce9c2020-07-14 17:22:03 +090056 }
57
Jie Jiang0137f5d2020-12-16 16:23:59 +090058 if (cnt == 0) {
Jie Jiangcf5ce9c2020-07-14 17:22:03 +090059 return output;
Jie Jiang0137f5d2020-12-16 16:23:59 +090060 }
Jie Jiangcf5ce9c2020-07-14 17:22:03 +090061
62 output.append({buf, static_cast<size_t>(cnt)});
63 }
64}
65
66} // namespace
67
68pid_t MinijailedProcessRunner::SyscallImpl::WaitPID(pid_t pid,
69 int* wstatus,
70 int options) {
71 return waitpid(pid, wstatus, options);
72}
73
74int MinijailedProcessRunner::RunSyncDestroy(
75 const std::vector<std::string>& argv,
76 brillo::Minijail* mj,
77 minijail* jail,
78 bool log_failures,
Jie Jiang0137f5d2020-12-16 16:23:59 +090079 std::string* output) {
Garrick Evans64a2df32018-12-12 16:53:46 +090080 std::vector<char*> args;
81 for (const auto& arg : argv) {
82 args.push_back(const_cast<char*>(arg.c_str()));
83 }
84 args.push_back(nullptr);
85
Jie Jiangcf5ce9c2020-07-14 17:22:03 +090086 pid_t pid;
Jie Jiang0137f5d2020-12-16 16:23:59 +090087 int fd_stdout = -1;
88 int* stdout_p = output ? &fd_stdout : nullptr;
Jie Jiangcf5ce9c2020-07-14 17:22:03 +090089 bool ran = mj->RunPipesAndDestroy(jail, args, &pid, nullptr /*stdin*/,
Jie Jiang0137f5d2020-12-16 16:23:59 +090090 stdout_p, nullptr /*stderr*/);
91 if (output) {
92 *output = ReadBlockingFDToStringAndClose(base::ScopedFD(fd_stdout));
93 }
94
95 int status = 0;
Jie Jiangcf5ce9c2020-07-14 17:22:03 +090096 if (ran) {
97 ran = syscall_->WaitPID(pid, &status) == pid;
98 }
99
Garrick Evans64a2df32018-12-12 16:53:46 +0900100 if (!ran) {
101 LOG(ERROR) << "Could not execute '" << base::JoinString(argv, " ") << "'";
102 } else if (log_failures && (!WIFEXITED(status) || WEXITSTATUS(status) != 0)) {
103 if (WIFEXITED(status)) {
Hugo Benichid4b19562019-06-21 13:00:12 +0900104 LOG(WARNING) << "Subprocess '" << base::JoinString(argv, " ")
105 << "' exited with code " << WEXITSTATUS(status);
Garrick Evans64a2df32018-12-12 16:53:46 +0900106 } else if (WIFSIGNALED(status)) {
Hugo Benichid4b19562019-06-21 13:00:12 +0900107 LOG(WARNING) << "Subprocess '" << base::JoinString(argv, " ")
108 << "' exited with signal " << WTERMSIG(status);
Garrick Evans64a2df32018-12-12 16:53:46 +0900109 } else {
Hugo Benichid4b19562019-06-21 13:00:12 +0900110 LOG(WARNING) << "Subprocess '" << base::JoinString(argv, " ")
111 << "' exited with unknown status " << status;
Garrick Evans64a2df32018-12-12 16:53:46 +0900112 }
113 }
114 return ran && WIFEXITED(status) ? WEXITSTATUS(status) : -1;
115}
116
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900117int MinijailedProcessRunner::RunSync(const std::vector<std::string>& argv,
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900118 bool log_failures,
Jie Jiangc9a5cd52020-12-14 17:38:52 +0900119 std::string* output) {
Jie Jiang0137f5d2020-12-16 16:23:59 +0900120 return RunSyncDestroy(argv, mj_, mj_->New(), log_failures, output);
Garrick Evans721eee52019-01-28 16:26:23 +0900121}
122
Jason Jeremy Imand89b5f52019-10-24 10:39:17 +0900123void EnterChildProcessJail() {
124 brillo::Minijail* m = brillo::Minijail::GetInstance();
125 struct minijail* jail = m->New();
126
127 // Most of these return void, but DropRoot() can fail if the user/group
128 // does not exist.
129 CHECK(m->DropRoot(jail, kNetworkUnprivilegedUser, kNetworkUnprivilegedUser))
130 << "Could not drop root privileges";
Garrick Evans8e8e3472020-01-23 14:03:50 +0900131 m->UseCapabilities(jail, kNetRawCapMask);
Jason Jeremy Imand89b5f52019-10-24 10:39:17 +0900132 m->Enter(jail);
133 m->Destroy(jail);
134}
135
Garrick Evans64a2df32018-12-12 16:53:46 +0900136MinijailedProcessRunner::MinijailedProcessRunner(brillo::Minijail* mj) {
137 mj_ = mj ? mj : brillo::Minijail::GetInstance();
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900138 syscall_ = std::make_unique<SyscallImpl>();
Garrick Evans64a2df32018-12-12 16:53:46 +0900139}
140
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900141MinijailedProcessRunner::MinijailedProcessRunner(
142 brillo::Minijail* mj, std::unique_ptr<SyscallImpl> syscall)
143 : mj_(mj), syscall_(std::move(syscall)) {}
144
Garrick Evans64a2df32018-12-12 16:53:46 +0900145int MinijailedProcessRunner::Run(const std::vector<std::string>& argv,
146 bool log_failures) {
147 minijail* jail = mj_->New();
148 CHECK(mj_->DropRoot(jail, kUnprivilegedUser, kUnprivilegedUser));
Garrick Evans8e8e3472020-01-23 14:03:50 +0900149 mj_->UseCapabilities(jail, kNetRawAdminCapMask);
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900150 return RunSyncDestroy(argv, mj_, jail, log_failures, nullptr);
Garrick Evans64a2df32018-12-12 16:53:46 +0900151}
152
Garrick Evans8e8e3472020-01-23 14:03:50 +0900153int MinijailedProcessRunner::brctl(const std::string& cmd,
154 const std::vector<std::string>& argv,
155 bool log_failures) {
156 std::vector<std::string> args = {kBrctlPath, cmd};
157 args.insert(args.end(), argv.begin(), argv.end());
158 return Run(args, log_failures);
159}
160
161int MinijailedProcessRunner::chown(const std::string& uid,
162 const std::string& gid,
163 const std::string& file,
164 bool log_failures) {
165 minijail* jail = mj_->New();
166 CHECK(mj_->DropRoot(jail, kUnprivilegedUser, kUnprivilegedUser));
167 mj_->UseCapabilities(jail, kChownCapMask);
168 std::vector<std::string> args = {kChownPath, uid + ":" + gid, file};
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900169 return RunSyncDestroy(args, mj_, jail, log_failures, nullptr);
Garrick Evans8e8e3472020-01-23 14:03:50 +0900170}
171
Garrick Evans8e8e3472020-01-23 14:03:50 +0900172int MinijailedProcessRunner::ip(const std::string& obj,
173 const std::string& cmd,
174 const std::vector<std::string>& argv,
175 bool log_failures) {
176 std::vector<std::string> args = {kIpPath, obj, cmd};
177 args.insert(args.end(), argv.begin(), argv.end());
178 return Run(args, log_failures);
179}
180
181int MinijailedProcessRunner::ip6(const std::string& obj,
182 const std::string& cmd,
183 const std::vector<std::string>& argv,
184 bool log_failures) {
185 std::vector<std::string> args = {kIpPath, "-6", obj, cmd};
186 args.insert(args.end(), argv.begin(), argv.end());
187 return Run(args, log_failures);
188}
189
190int MinijailedProcessRunner::iptables(const std::string& table,
191 const std::vector<std::string>& argv,
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900192 bool log_failures,
193 std::string* output) {
Garrick Evans8e8e3472020-01-23 14:03:50 +0900194 std::vector<std::string> args = {kIptablesPath, "-t", table};
195 args.insert(args.end(), argv.begin(), argv.end());
Jie Jiangc9a5cd52020-12-14 17:38:52 +0900196 return RunSync(args, log_failures, output);
Garrick Evans8e8e3472020-01-23 14:03:50 +0900197}
198
199int MinijailedProcessRunner::ip6tables(const std::string& table,
200 const std::vector<std::string>& argv,
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900201 bool log_failures,
202 std::string* output) {
Garrick Evans8e8e3472020-01-23 14:03:50 +0900203 std::vector<std::string> args = {kIp6tablesPath, "-t", table};
204 args.insert(args.end(), argv.begin(), argv.end());
Jie Jiangc9a5cd52020-12-14 17:38:52 +0900205 return RunSync(args, log_failures, output);
Garrick Evans8e8e3472020-01-23 14:03:50 +0900206}
207
208int MinijailedProcessRunner::modprobe_all(
209 const std::vector<std::string>& modules, bool log_failures) {
Garrick Evans78b414e2019-03-14 15:58:56 +0900210 minijail* jail = mj_->New();
211 CHECK(mj_->DropRoot(jail, kUnprivilegedUser, kUnprivilegedUser));
212 mj_->UseCapabilities(jail, kModprobeCapMask);
213 std::vector<std::string> args = {kModprobePath, "-a"};
214 args.insert(args.end(), modules.begin(), modules.end());
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900215 return RunSyncDestroy(args, mj_, jail, log_failures, nullptr);
Garrick Evans78b414e2019-03-14 15:58:56 +0900216}
217
Garrick Evans8e8e3472020-01-23 14:03:50 +0900218int MinijailedProcessRunner::sysctl_w(const std::string& key,
219 const std::string& value,
220 bool log_failures) {
Garrick Evans6d227b92019-12-03 16:11:29 +0900221 std::vector<std::string> args = {kSysctlPath, "-w", key + "=" + value};
Jie Jiangc9a5cd52020-12-14 17:38:52 +0900222 return RunSync(args, log_failures, nullptr);
Garrick Evans6d227b92019-12-03 16:11:29 +0900223}
224
Hugo Benichi33860d72020-07-09 16:34:01 +0900225int MinijailedProcessRunner::ip_netns_attach(const std::string& netns_name,
226 pid_t netns_pid,
227 bool log_failures) {
228 std::vector<std::string> args = {kIpPath, "netns", "attach", netns_name,
229 std::to_string(netns_pid)};
Jie Jiangc9a5cd52020-12-14 17:38:52 +0900230 return RunSync(args, log_failures, nullptr);
Hugo Benichi33860d72020-07-09 16:34:01 +0900231}
232
233int MinijailedProcessRunner::ip_netns_delete(const std::string& netns_name,
234 bool log_failures) {
235 std::vector<std::string> args = {kIpPath, "netns", "delete", netns_name};
Jie Jiangc9a5cd52020-12-14 17:38:52 +0900236 return RunSync(args, log_failures, nullptr);
Hugo Benichi33860d72020-07-09 16:34:01 +0900237}
238
Garrick Evans3388a032020-03-24 11:25:55 +0900239} // namespace patchpanel