Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 1 | // 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 | |
| 5 | #include "cros-disks/sandboxed_init.h" |
| 6 | |
| 7 | #include <utility> |
| 8 | #include <stdlib.h> |
François Degros | 1ef6994 | 2019-10-01 15:31:17 +1000 | [diff] [blame] | 9 | #include <unistd.h> |
| 10 | |
Sergei Datsenko | 1c8f215 | 2019-06-19 15:21:21 +1000 | [diff] [blame] | 11 | #include <sys/prctl.h> |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 12 | #include <sys/wait.h> |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 13 | |
Qijiang Fan | 713061e | 2021-03-08 15:45:12 +0900 | [diff] [blame] | 14 | #include <base/check.h> |
| 15 | #include <base/check_op.h> |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 16 | #include <base/files/file_util.h> |
| 17 | #include <base/logging.h> |
Qijiang Fan | 886c469 | 2021-02-19 11:54:10 +0900 | [diff] [blame] | 18 | #include <base/notreached.h> |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 19 | #include <brillo/syslog_logging.h> |
| 20 | #include <chromeos/libminijail.h> |
| 21 | |
| 22 | namespace cros_disks { |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 23 | namespace { |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 24 | |
François Degros | 21bc9bb | 2020-04-18 00:14:43 +1000 | [diff] [blame] | 25 | // Signal handler that forwards the received signal to all children processes. |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 26 | void SigTerm(int sig) { |
François Degros | 21bc9bb | 2020-04-18 00:14:43 +1000 | [diff] [blame] | 27 | if (kill(-1, sig) < 0) { |
| 28 | const int err = errno; |
| 29 | RAW_LOG(ERROR, "Cannot broadcast signal"); |
| 30 | _exit(err + 64); |
Sergei Datsenko | 1c8f215 | 2019-06-19 15:21:21 +1000 | [diff] [blame] | 31 | } |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 32 | } |
| 33 | |
| 34 | } // namespace |
| 35 | |
François Degros | c2bfe0e | 2019-10-14 13:08:49 +1100 | [diff] [blame] | 36 | SubprocessPipe::SubprocessPipe(const Direction direction) { |
François Degros | 4a76d70 | 2019-09-26 14:54:35 +1000 | [diff] [blame] | 37 | int fds[2]; |
François Degros | c2bfe0e | 2019-10-14 13:08:49 +1100 | [diff] [blame] | 38 | PCHECK(pipe(fds) >= 0); |
| 39 | child_fd.reset(fds[1 - direction]); |
| 40 | parent_fd.reset(fds[direction]); |
| 41 | PCHECK(base::SetCloseOnExec(parent_fd.get())); |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 42 | } |
| 43 | |
François Degros | 1ef6994 | 2019-10-01 15:31:17 +1000 | [diff] [blame] | 44 | base::ScopedFD SubprocessPipe::Open(const Direction direction, |
| 45 | base::ScopedFD* const parent_fd) { |
| 46 | DCHECK(parent_fd); |
| 47 | |
| 48 | SubprocessPipe p(direction); |
| 49 | *parent_fd = std::move(p.parent_fd); |
| 50 | return std::move(p.child_fd); |
| 51 | } |
| 52 | |
| 53 | SandboxedInit::SandboxedInit(base::ScopedFD in_fd, |
| 54 | base::ScopedFD out_fd, |
| 55 | base::ScopedFD err_fd, |
| 56 | base::ScopedFD ctrl_fd) |
| 57 | : in_fd_(std::move(in_fd)), |
| 58 | out_fd_(std::move(out_fd)), |
| 59 | err_fd_(std::move(err_fd)), |
| 60 | ctrl_fd_(std::move(ctrl_fd)) {} |
François Degros | 4a76d70 | 2019-09-26 14:54:35 +1000 | [diff] [blame] | 61 | |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 62 | SandboxedInit::~SandboxedInit() = default; |
| 63 | |
François Degros | 7316856 | 2019-10-03 13:34:26 +1000 | [diff] [blame] | 64 | [[noreturn]] void SandboxedInit::RunInsideSandboxNoReturn( |
Anand K Mistry | 08a71ca | 2019-09-13 10:38:20 +1000 | [diff] [blame] | 65 | base::OnceCallback<int()> launcher) { |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 66 | // To run our custom init that handles daemonized processes inside the |
| 67 | // sandbox we have to set up fork/exec ourselves. We do error-handling |
| 68 | // the "minijail-style" - abort if something not right. |
| 69 | |
| 70 | // This performs as the init process in the jail PID namespace (PID 1). |
| 71 | // Redirect in/out so logging can communicate assertions and children |
| 72 | // to inherit right FDs. |
| 73 | brillo::InitLog(brillo::kLogToSyslog | brillo::kLogToStderr); |
François Degros | 1ef6994 | 2019-10-01 15:31:17 +1000 | [diff] [blame] | 74 | |
| 75 | if (dup2(in_fd_.get(), STDIN_FILENO) < 0) { |
| 76 | PLOG(FATAL) << "Cannot dup2 stdin"; |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 77 | } |
François Degros | 1ef6994 | 2019-10-01 15:31:17 +1000 | [diff] [blame] | 78 | |
| 79 | if (dup2(out_fd_.get(), STDOUT_FILENO) < 0) { |
| 80 | PLOG(FATAL) << "Cannot dup2 stdout"; |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 81 | } |
François Degros | 1ef6994 | 2019-10-01 15:31:17 +1000 | [diff] [blame] | 82 | |
| 83 | if (dup2(err_fd_.get(), STDERR_FILENO) < 0) { |
| 84 | PLOG(FATAL) << "Cannot dup2 stderr"; |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 85 | } |
| 86 | |
Anand K Mistry | 3e2b58b | 2019-10-02 15:59:05 +1000 | [diff] [blame] | 87 | // Set an identifiable process name. |
François Degros | 4a76d70 | 2019-09-26 14:54:35 +1000 | [diff] [blame] | 88 | if (prctl(PR_SET_NAME, "cros-disks-INIT") < 0) { |
Sergei Datsenko | 1c8f215 | 2019-06-19 15:21:21 +1000 | [diff] [blame] | 89 | PLOG(WARNING) << "Can't set init's process name"; |
| 90 | } |
| 91 | |
François Degros | 1ef6994 | 2019-10-01 15:31:17 +1000 | [diff] [blame] | 92 | // Close unused file descriptors. |
| 93 | in_fd_.reset(); |
| 94 | out_fd_.reset(); |
| 95 | err_fd_.reset(); |
François Degros | c2bfe0e | 2019-10-14 13:08:49 +1100 | [diff] [blame] | 96 | |
| 97 | // Avoid leaking file descriptor into launcher process. |
François Degros | 1ef6994 | 2019-10-01 15:31:17 +1000 | [diff] [blame] | 98 | PCHECK(base::SetCloseOnExec(ctrl_fd_.get())); |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 99 | |
François Degros | 21bc9bb | 2020-04-18 00:14:43 +1000 | [diff] [blame] | 100 | // Setup the SIGTERM signal handler. |
| 101 | if (signal(SIGTERM, SigTerm) == SIG_ERR) { |
| 102 | PLOG(FATAL) << "Cannot install signal handler"; |
| 103 | } |
| 104 | |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 105 | // PID of the launcher process inside the jail PID namespace (e.g. PID 2). |
| 106 | pid_t root_pid = StartLauncher(std::move(launcher)); |
| 107 | CHECK_LT(0, root_pid); |
| 108 | |
François Degros | 1ef6994 | 2019-10-01 15:31:17 +1000 | [diff] [blame] | 109 | _exit(RunInitLoop(root_pid, std::move(ctrl_fd_))); |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 110 | NOTREACHED(); |
| 111 | } |
| 112 | |
| 113 | int SandboxedInit::RunInitLoop(pid_t root_pid, base::ScopedFD ctrl_fd) { |
| 114 | CHECK(base::SetNonBlocking(ctrl_fd.get())); |
| 115 | |
| 116 | // Most of this is mirroring minijail's embedded "init" (exit status handling) |
| 117 | // with addition of piping the "root" status code to the calling process. |
| 118 | |
François Degros | 02c5c71 | 2019-10-03 13:00:11 +1000 | [diff] [blame] | 119 | // By now it's unlikely something to go wrong here, so disconnect |
| 120 | // from in/out. |
| 121 | HANDLE_EINTR(close(STDIN_FILENO)); |
| 122 | HANDLE_EINTR(close(STDOUT_FILENO)); |
| 123 | HANDLE_EINTR(close(STDERR_FILENO)); |
| 124 | |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 125 | // This loop will only end when either there are no processes left inside |
| 126 | // our PID namespace or we get a signal. |
Sergei Datsenko | 1c8f215 | 2019-06-19 15:21:21 +1000 | [diff] [blame] | 127 | int last_failure_code = 0; |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 128 | |
François Degros | 43208ad | 2019-09-27 14:01:00 +1000 | [diff] [blame] | 129 | while (true) { |
| 130 | // Wait for any child to terminate. |
| 131 | int wstatus; |
| 132 | const pid_t pid = HANDLE_EINTR(wait(&wstatus)); |
| 133 | |
| 134 | if (pid < 0) { |
| 135 | if (errno == ECHILD) { |
| 136 | // No more child |
François Degros | 02c5c71 | 2019-10-03 13:00:11 +1000 | [diff] [blame] | 137 | CHECK(!ctrl_fd.is_valid()); |
| 138 | return last_failure_code; |
François Degros | 43208ad | 2019-09-27 14:01:00 +1000 | [diff] [blame] | 139 | } |
| 140 | |
| 141 | PLOG(FATAL) << "Cannot wait for child processes"; |
| 142 | } |
| 143 | |
François Degros | 02c5c71 | 2019-10-03 13:00:11 +1000 | [diff] [blame] | 144 | // Convert wait status to exit code. |
| 145 | const int exit_code = WStatusToStatus(wstatus); |
| 146 | if (exit_code >= 0) { |
| 147 | // A child process finished. |
Sergei Datsenko | 1c8f215 | 2019-06-19 15:21:21 +1000 | [diff] [blame] | 148 | if (exit_code) { |
| 149 | last_failure_code = exit_code; |
| 150 | } |
| 151 | |
François Degros | 02c5c71 | 2019-10-03 13:00:11 +1000 | [diff] [blame] | 152 | // Was it the launcher? |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 153 | if (pid == root_pid) { |
François Degros | 02c5c71 | 2019-10-03 13:00:11 +1000 | [diff] [blame] | 154 | // Write the launcher's exit code to the control pipe. |
| 155 | const ssize_t written = |
| 156 | HANDLE_EINTR(write(ctrl_fd.get(), &exit_code, sizeof(exit_code))); |
| 157 | if (written != sizeof(exit_code)) { |
| 158 | PLOG(ERROR) << "Cannot write exit code"; |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 159 | return MINIJAIL_ERR_INIT; |
| 160 | } |
Sergei Datsenko | 1c8f215 | 2019-06-19 15:21:21 +1000 | [diff] [blame] | 161 | |
François Degros | 02c5c71 | 2019-10-03 13:00:11 +1000 | [diff] [blame] | 162 | ctrl_fd.reset(); |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 163 | } |
| 164 | } |
| 165 | } |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 166 | } |
| 167 | |
Anand K Mistry | 08a71ca | 2019-09-13 10:38:20 +1000 | [diff] [blame] | 168 | pid_t SandboxedInit::StartLauncher(base::OnceCallback<int()> launcher) { |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 169 | pid_t exec_child = fork(); |
Sergei Datsenko | 1c8f215 | 2019-06-19 15:21:21 +1000 | [diff] [blame] | 170 | |
François Degros | 4a76d70 | 2019-09-26 14:54:35 +1000 | [diff] [blame] | 171 | if (exec_child < 0) { |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 172 | PLOG(FATAL) << "Can't fork"; |
| 173 | } |
| 174 | |
| 175 | if (exec_child == 0) { |
François Degros | 4a76d70 | 2019-09-26 14:54:35 +1000 | [diff] [blame] | 176 | // In child process |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 177 | // Launch the invoked program. |
François Degros | 7316856 | 2019-10-03 13:34:26 +1000 | [diff] [blame] | 178 | _exit(std::move(launcher).Run()); |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 179 | NOTREACHED(); |
| 180 | } |
| 181 | |
François Degros | 4a76d70 | 2019-09-26 14:54:35 +1000 | [diff] [blame] | 182 | // In parent process |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 183 | return exec_child; |
| 184 | } |
| 185 | |
François Degros | 02c5c71 | 2019-10-03 13:00:11 +1000 | [diff] [blame] | 186 | bool SandboxedInit::PollLauncherStatus(base::ScopedFD* ctrl_fd, |
| 187 | int* exit_code) { |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 188 | CHECK(ctrl_fd->is_valid()); |
| 189 | ssize_t read_bytes = |
François Degros | 02c5c71 | 2019-10-03 13:00:11 +1000 | [diff] [blame] | 190 | HANDLE_EINTR(read(ctrl_fd->get(), exit_code, sizeof(*exit_code))); |
| 191 | if (read_bytes != sizeof(*exit_code)) { |
François Degros | 4a76d70 | 2019-09-26 14:54:35 +1000 | [diff] [blame] | 192 | return false; |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 193 | } |
François Degros | 4a76d70 | 2019-09-26 14:54:35 +1000 | [diff] [blame] | 194 | |
| 195 | ctrl_fd->reset(); |
| 196 | return true; |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 197 | } |
| 198 | |
Sergei Datsenko | 1c8f215 | 2019-06-19 15:21:21 +1000 | [diff] [blame] | 199 | int SandboxedInit::WStatusToStatus(int wstatus) { |
| 200 | if (WIFEXITED(wstatus)) { |
| 201 | return WEXITSTATUS(wstatus); |
Sergei Datsenko | 1c8f215 | 2019-06-19 15:21:21 +1000 | [diff] [blame] | 202 | } |
François Degros | d5c741f | 2019-09-27 12:20:31 +1000 | [diff] [blame] | 203 | |
| 204 | if (WIFSIGNALED(wstatus)) { |
| 205 | // Mirrors behavior of minijail_wait(). |
| 206 | const int signum = WTERMSIG(wstatus); |
François Degros | a97ad49 | 2019-10-04 12:12:12 +1000 | [diff] [blame] | 207 | return signum == SIGSYS ? MINIJAIL_ERR_JAIL |
| 208 | : MINIJAIL_ERR_SIG_BASE + signum; |
François Degros | d5c741f | 2019-09-27 12:20:31 +1000 | [diff] [blame] | 209 | } |
| 210 | |
Sergei Datsenko | 1c8f215 | 2019-06-19 15:21:21 +1000 | [diff] [blame] | 211 | return -1; |
| 212 | } |
| 213 | |
Sergei Datsenko | 495f5da | 2019-06-06 17:44:23 +1000 | [diff] [blame] | 214 | } // namespace cros_disks |