1 /*
2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #include "host/commands/process_sandboxer/sandbox_manager.h"
17
18 #include <fcntl.h>
19 #include <linux/sched.h>
20 #include <signal.h>
21 #include <sys/eventfd.h>
22 #include <sys/prctl.h>
23 #include <sys/signalfd.h>
24 #include <sys/socket.h>
25 #include <sys/syscall.h>
26 #include <sys/un.h>
27 #include <sys/wait.h>
28 #include <unistd.h>
29
30 #include <memory>
31 #include <sstream>
32 #include <thread>
33 #include <utility>
34
35 #include <absl/functional/bind_front.h>
36 #include <absl/log/log.h>
37 #include <absl/log/vlog_is_on.h>
38 #include <absl/memory/memory.h>
39 #include <absl/random/bit_gen_ref.h>
40 #include <absl/random/uniform_int_distribution.h>
41 #include <absl/status/status.h>
42 #include <absl/status/statusor.h>
43 #include <absl/strings/numbers.h>
44 #include <absl/strings/str_cat.h>
45 #include <absl/strings/str_format.h>
46 #include <absl/strings/str_join.h>
47 #include <absl/types/span.h>
48 #pragma clang diagnostic push
49 #pragma clang diagnostic ignored "-Wunused-parameter"
50 #include <sandboxed_api/sandbox2/executor.h>
51 #include <sandboxed_api/sandbox2/policy.h>
52 #include <sandboxed_api/sandbox2/sandbox2.h>
53 #include <sandboxed_api/sandbox2/util.h>
54 #pragma clang diagnostic pop
55
56 #include "host/commands/process_sandboxer/credentialed_unix_server.h"
57 #include "host/commands/process_sandboxer/filesystem.h"
58 #include "host/commands/process_sandboxer/pidfd.h"
59 #include "host/commands/process_sandboxer/policies.h"
60 #include "host/commands/process_sandboxer/poll_callback.h"
61 #include "host/commands/process_sandboxer/proxy_common.h"
62
63 namespace cuttlefish::process_sandboxer {
64
65 using sandbox2::Executor;
66 using sandbox2::Policy;
67 using sandbox2::Sandbox2;
68 using sandbox2::Syscall;
69 using sandbox2::util::GetProgName;
70
71 namespace {
72
ServerSocketOutsidePath(std::string_view runtime_dir)73 std::string ServerSocketOutsidePath(std::string_view runtime_dir) {
74 return JoinPath(runtime_dir, "/", "server.sock");
75 }
76
77 } // namespace
78
79 class SandboxManager::ProcessNoSandbox : public SandboxManager::ManagedProcess {
80 public:
ProcessNoSandbox(int client_fd,PidFd pid_fd)81 ProcessNoSandbox(int client_fd, PidFd pid_fd)
82 : client_fd_(client_fd), pid_fd_(std::move(pid_fd)) {}
~ProcessNoSandbox()83 ~ProcessNoSandbox() {
84 auto halt = pid_fd_.HaltHierarchy();
85 if (!halt.ok()) {
86 LOG(ERROR) << "Failed to halt children: " << halt.ToString();
87 }
88 }
89
ClientFd() const90 std::optional<int> ClientFd() const override { return client_fd_; }
PollFd() const91 int PollFd() const override { return pid_fd_.Get(); }
92
ExitCode()93 absl::StatusOr<uintptr_t> ExitCode() override {
94 siginfo_t infop;
95 idtype_t id_type = (idtype_t)3; // P_PIDFD
96 if (waitid(id_type, pid_fd_.Get(), &infop, WEXITED | WNOWAIT) < 0) {
97 return absl::ErrnoToStatus(errno, "`waitid` failed");
98 }
99 switch (infop.si_code) {
100 case CLD_EXITED:
101 return infop.si_status;
102 case CLD_DUMPED:
103 case CLD_KILLED:
104 LOG(ERROR) << "Child killed by signal " << infop.si_code;
105 return 255;
106 default:
107 LOG(ERROR) << "Unexpected si_code: " << infop.si_code;
108 return 255;
109 }
110 }
111
112 private:
113 int client_fd_;
114 PidFd pid_fd_;
115 };
116
117 class SandboxManager::SandboxedProcess : public SandboxManager::ManagedProcess {
118 public:
SandboxedProcess(std::optional<int> client_fd,UniqueFd event_fd,std::unique_ptr<Sandbox2> sandbox)119 SandboxedProcess(std::optional<int> client_fd, UniqueFd event_fd,
120 std::unique_ptr<Sandbox2> sandbox)
121 : client_fd_(client_fd),
122 event_fd_(std::move(event_fd)),
123 sandbox_(std::move(sandbox)) {
124 waiter_thread_ = std::thread([this]() { WaitForExit(); });
125 }
~SandboxedProcess()126 ~SandboxedProcess() override {
127 sandbox_->Kill();
128 waiter_thread_.join();
129 auto res = sandbox_->AwaitResult().ToStatus();
130 if (!res.ok()) {
131 LOG(ERROR) << "Issue in closing sandbox: '" << res.ToString() << "'";
132 }
133 }
134
ClientFd() const135 std::optional<int> ClientFd() const override { return client_fd_; }
PollFd() const136 int PollFd() const override { return event_fd_.Get(); }
137
ExitCode()138 absl::StatusOr<uintptr_t> ExitCode() override {
139 return sandbox_->AwaitResult().reason_code();
140 }
141
142 private:
WaitForExit()143 void WaitForExit() {
144 sandbox_->AwaitResult().IgnoreResult();
145 uint64_t buf = 1;
146 if (write(event_fd_.Get(), &buf, sizeof(buf)) < 0) {
147 PLOG(ERROR) << "Failed to write to eventfd";
148 }
149 }
150
151 std::optional<int> client_fd_;
152 UniqueFd event_fd_;
153 std::thread waiter_thread_;
154 std::unique_ptr<Sandbox2> sandbox_;
155 };
156
RandomString(absl::BitGenRef gen,std::size_t size)157 std::string RandomString(absl::BitGenRef gen, std::size_t size) {
158 std::stringstream output;
159 absl::uniform_int_distribution<char> distribution;
160 for (std::size_t i = 0; i < size; i++) {
161 output << distribution(gen);
162 }
163 return output.str();
164 }
165
166 class SandboxManager::SocketClient {
167 public:
SocketClient(SandboxManager & manager,UniqueFd client_fd)168 SocketClient(SandboxManager& manager, UniqueFd client_fd)
169 : manager_(manager), client_fd_(std::move(client_fd)) {}
170 SocketClient(SocketClient&) = delete;
171
ClientFd() const172 int ClientFd() const { return client_fd_.Get(); }
173
HandleMessage()174 absl::Status HandleMessage() {
175 auto message_status = Message::RecvFrom(client_fd_.Get());
176 if (!message_status.ok()) {
177 return message_status.status();
178 }
179 auto creds_status = UpdateCredentials(message_status->Credentials());
180 if (!creds_status.ok()) {
181 return creds_status;
182 }
183
184 /* This handshake process is to reliably build a `pidfd` based on the pid
185 * supplied in the process `ucreds`, through the following steps:
186 * 1. Proxy process opens a socket and sends an opening message.
187 * 2. Server receives opening message with a kernel-validated `ucreds`
188 * containing the outside-sandbox pid.
189 * 3. Server opens a pidfd matching this pid.
190 * 4. Server sends a message to the client with some unique data.
191 * 5. Client responds with the unique data.
192 * 6. Server validates the unique data and credentials match.
193 * 7. Server launches a possible sandboxed subprocess based on the pidfd and
194 * /proc/{pid}/
195 *
196 * Step 5 builds confidence that the pidfd opened in step 3 still
197 * corresponds to the client sending messages on the client socket. The
198 * pidfd and /proc/{pid} data provide everything necessary to launch the
199 * subprocess.
200 */
201 auto& message = message_status->Data();
202 switch (client_state_) {
203 case ClientState::kInitial: {
204 if (message != kHandshakeBegin) {
205 std::string err =
206 absl::StrFormat("'%v' != '%v'", kHandshakeBegin, message);
207 return absl::InternalError(err);
208 }
209 pingback_ = RandomString(manager_.bit_gen_, 32);
210 absl::StatusOr<std::size_t> stat =
211 SendStringMsg(client_fd_.Get(), pingback_);
212 if (stat.ok()) {
213 client_state_ = ClientState::kIgnoredFd;
214 }
215 return stat.status();
216 }
217 case ClientState::kIgnoredFd:
218 if (!absl::SimpleAtoi(message, &ignored_fd_)) {
219 std::string error =
220 absl::StrFormat("Expected integer, got '%v'", message);
221 return absl::InternalError(error);
222 }
223 client_state_ = ClientState::kPingback;
224 return absl::OkStatus();
225 case ClientState::kPingback: {
226 if (message != pingback_) {
227 std::string err =
228 absl::StrFormat("Incorrect '%v' != '%v'", message, pingback_);
229 return absl::InternalError(err);
230 }
231 client_state_ = ClientState::kWaitingForExit;
232 return LaunchProcess();
233 }
234 case ClientState::kWaitingForExit:
235 return absl::InternalError("No messages allowed");
236 }
237 }
238
SendExitCode(int code)239 absl::Status SendExitCode(int code) {
240 auto send_exit_status = SendStringMsg(client_fd_.Get(), "exit");
241 if (!send_exit_status.ok()) {
242 return send_exit_status.status();
243 }
244
245 return SendStringMsg(client_fd_.Get(), std::to_string(code)).status();
246 }
247
248 private:
249 enum class ClientState { kInitial, kIgnoredFd, kPingback, kWaitingForExit };
250
UpdateCredentials(const std::optional<ucred> & credentials)251 absl::Status UpdateCredentials(const std::optional<ucred>& credentials) {
252 if (!credentials) {
253 return absl::InvalidArgumentError("no creds");
254 } else if (!credentials_) {
255 credentials_ = credentials;
256 } else if (credentials_->pid != credentials->pid) {
257 std::string err = absl::StrFormat("pid went from '%d' to '%d'",
258 credentials_->pid, credentials->pid);
259 return absl::PermissionDeniedError(err);
260 } else if (credentials_->uid != credentials->uid) {
261 return absl::PermissionDeniedError("uid changed");
262 } else if (credentials_->gid != credentials->gid) {
263 return absl::PermissionDeniedError("gid changed");
264 }
265 if (!pid_fd_) {
266 absl::StatusOr<PidFd> pid_fd =
267 PidFd::FromRunningProcess(credentials_->pid);
268 if (!pid_fd.ok()) {
269 return pid_fd.status();
270 }
271 pid_fd_ = std::move(*pid_fd);
272 }
273 return absl::OkStatus();
274 }
275
LaunchProcess()276 absl::Status LaunchProcess() {
277 if (!pid_fd_) {
278 return absl::InternalError("missing pid_fd_");
279 }
280 absl::StatusOr<std::vector<std::string>> argv = pid_fd_->Argv();
281 if (!argv.ok()) {
282 return argv.status();
283 }
284 if ((*argv)[0] == "openssl") {
285 (*argv)[0] = "/usr/bin/openssl";
286 }
287 absl::StatusOr<std::vector<std::pair<UniqueFd, int>>> fds =
288 pid_fd_->AllFds();
289 if (!fds.ok()) {
290 return fds.status();
291 }
292 absl::StatusOr<std::vector<std::string>> env = pid_fd_->Env();
293 if (!env.ok()) {
294 return env.status();
295 }
296 fds->erase(std::remove_if(fds->begin(), fds->end(), [this](auto& arg) {
297 return arg.second == ignored_fd_;
298 }));
299 return manager_.RunProcess(client_fd_.Get(), std::move(*argv),
300 std::move(*fds), *env);
301 }
302
303 SandboxManager& manager_;
304 UniqueFd client_fd_;
305 std::optional<ucred> credentials_;
306 std::optional<PidFd> pid_fd_;
307
308 ClientState client_state_ = ClientState::kInitial;
309 std::string pingback_;
310 int ignored_fd_ = -1;
311 };
312
SandboxManager(HostInfo host_info,std::string runtime_dir,SignalFd signals,CredentialedUnixServer server)313 SandboxManager::SandboxManager(HostInfo host_info, std::string runtime_dir,
314 SignalFd signals, CredentialedUnixServer server)
315 : host_info_(std::move(host_info)),
316 runtime_dir_(std::move(runtime_dir)),
317 signals_(std::move(signals)),
318 server_(std::move(server)) {}
319
Create(HostInfo host_info)320 absl::StatusOr<std::unique_ptr<SandboxManager>> SandboxManager::Create(
321 HostInfo host_info) {
322 std::string runtime_dir =
323 absl::StrFormat("/tmp/sandbox_manager.%u.XXXXXX", getpid());
324 if (mkdtemp(runtime_dir.data()) == nullptr) {
325 return absl::ErrnoToStatus(errno, "mkdtemp failed");
326 }
327 VLOG(1) << "Created temporary directory '" << runtime_dir << "'";
328
329 absl::StatusOr<SignalFd> signals = SignalFd::AllExceptSigChld();
330 if (!signals.ok()) {
331 return signals.status();
332 }
333
334 absl::StatusOr<CredentialedUnixServer> server =
335 CredentialedUnixServer::Open(ServerSocketOutsidePath(runtime_dir));
336 if (!server.ok()) {
337 return server.status();
338 }
339
340 return absl::WrapUnique(
341 new SandboxManager(std::move(host_info), std::move(runtime_dir),
342 std::move(*signals), std::move(*server)));
343 }
344
~SandboxManager()345 SandboxManager::~SandboxManager() {
346 VLOG(1) << "Sandbox shutting down";
347 if (!runtime_dir_.empty()) {
348 if (unlink(ServerSocketOutsidePath(runtime_dir_).c_str()) < 0) {
349 PLOG(ERROR) << "`unlink` failed";
350 }
351 if (rmdir(runtime_dir_.c_str()) < 0) {
352 PLOG(ERROR) << "Failed to remove '" << runtime_dir_ << "'";
353 }
354 }
355 }
356
RunProcess(std::optional<int> client_fd,absl::Span<const std::string> argv,std::vector<std::pair<UniqueFd,int>> fds,absl::Span<const std::string> env)357 absl::Status SandboxManager::RunProcess(
358 std::optional<int> client_fd, absl::Span<const std::string> argv,
359 std::vector<std::pair<UniqueFd, int>> fds,
360 absl::Span<const std::string> env) {
361 if (argv.empty()) {
362 return absl::InvalidArgumentError("Not enough arguments");
363 }
364 bool stdio_mapped[3] = {false, false, false};
365 for (const auto& [input_fd, target_fd] : fds) {
366 if (0 <= target_fd && target_fd <= 2) {
367 stdio_mapped[target_fd] = true;
368 }
369 }
370 // If stdio is not filled in, file descriptors opened by the target process
371 // may occupy the standard stdio positions. This can cause unexpected
372 for (int i = 0; i <= 2; i++) {
373 if (stdio_mapped[i]) {
374 continue;
375 }
376 auto& [stdio_dup, stdio] = fds.emplace_back(dup(i), i);
377 if (stdio_dup.Get() < 0) {
378 return absl::ErrnoToStatus(errno, "Failed to `dup` stdio descriptor");
379 }
380 }
381 std::string exe = CleanPath(argv[0]);
382 std::unique_ptr<Policy> policy = PolicyForExecutable(
383 host_info_, ServerSocketOutsidePath(runtime_dir_), exe);
384 if (policy) {
385 return RunSandboxedProcess(client_fd, argv, std::move(fds), env,
386 std::move(policy));
387 } else {
388 return RunProcessNoSandbox(client_fd, argv, std::move(fds), env);
389 }
390 }
391
RunSandboxedProcess(std::optional<int> client_fd,absl::Span<const std::string> argv,std::vector<std::pair<UniqueFd,int>> fds,absl::Span<const std::string> env,std::unique_ptr<Policy> policy)392 absl::Status SandboxManager::RunSandboxedProcess(
393 std::optional<int> client_fd, absl::Span<const std::string> argv,
394 std::vector<std::pair<UniqueFd, int>> fds,
395 absl::Span<const std::string> env, std::unique_ptr<Policy> policy) {
396 if (VLOG_IS_ON(1)) {
397 std::stringstream process_stream;
398 process_stream << "Launching executable with argv: [\n";
399 for (const auto& arg : argv) {
400 process_stream << "\t\"" << arg << "\",\n";
401 }
402 process_stream << "] with FD mapping: [\n";
403 for (const auto& [fd_in, fd_out] : fds) {
404 process_stream << '\t' << fd_in.Get() << " -> " << fd_out << ",\n";
405 }
406 process_stream << "]\n";
407 VLOG(1) << process_stream.str();
408 }
409
410 std::string exe = CleanPath(argv[0]);
411 auto executor = std::make_unique<Executor>(exe, argv, env);
412 executor->set_cwd(host_info_.runtime_dir);
413
414 // https://cs.android.com/android/platform/superproject/main/+/main:external/sandboxed-api/sandboxed_api/sandbox2/limits.h;l=116;drc=d451478e26c0352ecd6912461e867a1ae64b17f5
415 // Default is 120 seconds
416 executor->limits()->set_walltime_limit(absl::InfiniteDuration());
417 // Default is 1024 seconds
418 executor->limits()->set_rlimit_cpu(RLIM64_INFINITY);
419
420 for (auto& [fd_outer, fd_inner] : fds) {
421 // Will close `fd_outer` in this process
422 executor->ipc()->MapFd(fd_outer.Release(), fd_inner);
423 }
424
425 UniqueFd event_fd(eventfd(0, EFD_CLOEXEC));
426 if (event_fd.Get() < 0) {
427 return absl::ErrnoToStatus(errno, "`eventfd` failed");
428 }
429
430 auto sbx = std::make_unique<Sandbox2>(std::move(executor), std::move(policy));
431 if (!sbx->RunAsync()) {
432 return sbx->AwaitResult().ToStatus();
433 }
434
435 // A pidfd over the sandbox is another option, but there are two problems:
436 //
437 // 1. There's a race between launching the sandbox and opening the pidfd. If
438 // the sandboxed process exits too quickly, the monitor thread in sandbox2
439 // could reap it and another process could reuse the pid before `pidfd_open`
440 // runs. Sandbox2 could produce a pidfd itself using `CLONE_PIDFD`, but it
441 // does not do this at the time of writing.
442 //
443 // 2. The sandbox could outlive its top-level process. It's not clear to me if
444 // sandbox2 allows this in practice, but `AwaitResult` could theoretically
445 // wait on subprocesses of the original sandboxed process as well.
446 //
447 // To deal with these concerns, we use another thread blocked on AwaitResult
448 // that signals the eventfd when sandbox2 says the sandboxed process has
449 // exited.
450
451 subprocesses_.emplace_back(
452 new SandboxedProcess(client_fd, std::move(event_fd), std::move(sbx)));
453
454 return absl::OkStatus();
455 }
456
RunProcessNoSandbox(std::optional<int> client_fd,absl::Span<const std::string> argv,std::vector<std::pair<UniqueFd,int>> fds,absl::Span<const std::string> env)457 absl::Status SandboxManager::RunProcessNoSandbox(
458 std::optional<int> client_fd, absl::Span<const std::string> argv,
459 std::vector<std::pair<UniqueFd, int>> fds,
460 absl::Span<const std::string> env) {
461 if (!client_fd) {
462 return absl::InvalidArgumentError("no client for unsandboxed process");
463 }
464
465 absl::StatusOr<PidFd> fd = PidFd::LaunchSubprocess(argv, std::move(fds), env);
466 if (!fd.ok()) {
467 return fd.status();
468 }
469 subprocesses_.emplace_back(new ProcessNoSandbox(*client_fd, std::move(*fd)));
470
471 return absl::OkStatus();
472 }
473
Running() const474 bool SandboxManager::Running() const { return running_; }
475
Iterate()476 absl::Status SandboxManager::Iterate() {
477 PollCallback poll_cb;
478
479 poll_cb.Add(signals_.Fd(), bind_front(&SandboxManager::Signalled, this));
480 poll_cb.Add(server_.Fd(), bind_front(&SandboxManager::NewClient, this));
481
482 for (auto it = subprocesses_.begin(); it != subprocesses_.end(); it++) {
483 int fd = (*it)->PollFd();
484 poll_cb.Add(fd, bind_front(&SandboxManager::ProcessExit, this, it));
485 }
486 for (auto it = clients_.begin(); it != clients_.end(); it++) {
487 int fd = (*it)->ClientFd();
488 poll_cb.Add(fd, bind_front(&SandboxManager::ClientMessage, this, it));
489 }
490
491 return poll_cb.Poll();
492 }
493
Signalled(short revents)494 absl::Status SandboxManager::Signalled(short revents) {
495 if (revents != POLLIN) {
496 running_ = false;
497 return absl::InternalError("signalfd exited");
498 }
499
500 absl::StatusOr<signalfd_siginfo> info = signals_.ReadSignal();
501 if (!info.ok()) {
502 return info.status();
503 }
504 VLOG(1) << "Received signal with signo '" << info->ssi_signo << "'";
505
506 switch (info->ssi_signo) {
507 case SIGHUP:
508 case SIGINT:
509 case SIGTERM:
510 LOG(INFO) << "Received signal '" << info->ssi_signo << "', exiting";
511 running_ = false;
512 return absl::OkStatus();
513 default:
514 std::string err = absl::StrCat("Unexpected signal ", info->ssi_signo);
515 return absl::InternalError(err);
516 }
517 }
518
NewClient(short revents)519 absl::Status SandboxManager::NewClient(short revents) {
520 if (revents != POLLIN) {
521 running_ = false;
522 return absl::InternalError("server socket exited");
523 }
524 absl::StatusOr<UniqueFd> client = server_.AcceptClient();
525 if (!client.ok()) {
526 return client.status();
527 }
528 clients_.emplace_back(new SocketClient(*this, std::move(*client)));
529 return absl::OkStatus();
530 }
531
ProcessExit(SandboxManager::SboxIter it,short revents)532 absl::Status SandboxManager::ProcessExit(SandboxManager::SboxIter it,
533 short revents) {
534 if ((*it)->ClientFd()) {
535 int client_fd = *(*it)->ClientFd();
536 for (auto& client : clients_) {
537 if (client->ClientFd() != client_fd) {
538 continue;
539 }
540 auto exit_code = (*it)->ExitCode();
541 if (!exit_code.ok()) {
542 LOG(ERROR) << exit_code.status();
543 }
544 // TODO(schuffelen): Forward more complete exit information
545 auto send_res = client->SendExitCode(exit_code.value_or(254));
546 if (!send_res.ok()) {
547 return send_res;
548 }
549 }
550 }
551 subprocesses_.erase(it);
552 if (subprocesses_.empty()) {
553 running_ = false;
554 }
555 static constexpr char kErr[] = "eventfd exited";
556 return revents == POLLIN ? absl::OkStatus() : absl::InternalError(kErr);
557 }
558
ClientMessage(SandboxManager::ClientIter it,short rev)559 absl::Status SandboxManager::ClientMessage(SandboxManager::ClientIter it,
560 short rev) {
561 if (rev == POLLIN) {
562 return (*it)->HandleMessage();
563 }
564 clients_.erase(it);
565 return absl::InternalError("client dropped file descriptor");
566 }
567
568 } // namespace cuttlefish::process_sandboxer
569