1 // Copyright 2019 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "sandboxed_api/sandbox2/util.h"
16
17 #include <sched.h>
18 #include <spawn.h>
19 #include <sys/ptrace.h>
20 #include <sys/resource.h>
21 #include <sys/uio.h>
22 #include <sys/wait.h>
23 #include <syscall.h>
24 #include <unistd.h>
25
26 #include <cerrno>
27 #include <csetjmp>
28 #include <cstddef>
29 #include <cstdint>
30 #include <cstdlib>
31 #include <cstring>
32 #include <string>
33 #include <utility>
34 #include <vector>
35
36 #include "absl/base/attributes.h"
37 #include "absl/base/macros.h"
38 #include "absl/base/optimization.h"
39 #include "absl/status/status.h"
40 #include "absl/status/statusor.h"
41 #include "absl/strings/ascii.h"
42 #include "absl/strings/escaping.h"
43 #include "absl/strings/str_cat.h"
44 #include "absl/strings/str_format.h"
45 #include "absl/strings/str_join.h"
46 #include "absl/strings/str_replace.h"
47 #include "absl/strings/str_split.h"
48 #include "absl/strings/string_view.h"
49 #include "sandboxed_api/config.h"
50 #include "sandboxed_api/util/file_helpers.h"
51 #include "sandboxed_api/util/fileops.h"
52 #include "sandboxed_api/util/path.h"
53 #include "sandboxed_api/util/raw_logging.h"
54
55 namespace sandbox2::util {
56
57 namespace file = ::sapi::file;
58 namespace file_util = ::sapi::file_util;
59
60 namespace {
61
ConcatenateAll(char * const * arr)62 std::string ConcatenateAll(char* const* arr) {
63 std::string result;
64 for (; *arr != nullptr; ++arr) {
65 size_t len = strlen(*arr);
66 result.append(*arr, len + 1);
67 }
68 return result;
69 }
70
71 #ifdef __ELF__
72 extern "C" void __gcov_dump() ABSL_ATTRIBUTE_WEAK;
73 extern "C" void __gcov_flush() ABSL_ATTRIBUTE_WEAK;
74 extern "C" void __gcov_reset() ABSL_ATTRIBUTE_WEAK;
75 #endif
76
ResetCoverageData()77 void ResetCoverageData() {
78 #ifdef __ELF__
79 if (&__gcov_reset != nullptr) {
80 __gcov_reset();
81 }
82 #endif
83 }
84
85 } // namespace
86
DumpCoverageData()87 void DumpCoverageData() {
88 #ifdef __ELF__
89 if (&__gcov_dump != nullptr) {
90 SAPI_RAW_LOG(WARNING, "Flushing coverage data (dump)");
91 __gcov_dump();
92 } else if (&__gcov_flush != nullptr) {
93 SAPI_RAW_LOG(WARNING, "Flushing coverage data (flush)");
94 __gcov_flush();
95 }
96 #endif
97 }
98
CharPtrArray(char * const * arr)99 CharPtrArray::CharPtrArray(char* const* arr) : content_(ConcatenateAll(arr)) {
100 for (auto it = content_.begin(); it != content_.end();
101 it += strlen(&*it) + 1) {
102 array_.push_back(&*it);
103 }
104 array_.push_back(nullptr);
105 }
106
CharPtrArray(const std::vector<std::string> & vec)107 CharPtrArray::CharPtrArray(const std::vector<std::string>& vec)
108 : content_(absl::StrJoin(vec, absl::string_view("\0", 1))) {
109 size_t len = 0;
110 array_.reserve(vec.size() + 1);
111 for (const std::string& str : vec) {
112 array_.push_back(&content_[len]);
113 len += str.size() + 1;
114 }
115 array_.push_back(nullptr);
116 }
117
FromStringVector(const std::vector<std::string> & vec)118 CharPtrArray CharPtrArray::FromStringVector(
119 const std::vector<std::string>& vec) {
120 return CharPtrArray(vec);
121 }
122
ToStringVector() const123 std::vector<std::string> CharPtrArray::ToStringVector() const {
124 std::vector<std::string> result;
125 result.reserve(array_.size() - 1);
126 for (size_t i = 0; i < array_.size() - 1; ++i) {
127 result.push_back(array_[i]);
128 }
129 return result;
130 }
131
GetProgName(pid_t pid)132 std::string GetProgName(pid_t pid) {
133 std::string fname = file::JoinPath("/proc", absl::StrCat(pid), "exe");
134 // Use ReadLink instead of RealPath, as for fd-based executables (e.g. created
135 // via memfd_create()) the RealPath will not work, as the destination file
136 // doesn't exist on the local file-system.
137 return file_util::fileops::Basename(file_util::fileops::ReadLink(fname));
138 }
139
GetCmdLine(pid_t pid)140 std::string GetCmdLine(pid_t pid) {
141 std::string fname = file::JoinPath("/proc", absl::StrCat(pid), "cmdline");
142 std::string cmdline;
143 auto status =
144 sapi::file::GetContents(fname, &cmdline, sapi::file::Defaults());
145 if (!status.ok()) {
146 SAPI_RAW_LOG(WARNING, "%s", std::string(status.message()).c_str());
147 return "";
148 }
149 return absl::StrReplaceAll(cmdline, {{absl::string_view("\0", 1), " "}});
150 }
151
GetProcStatusLine(int pid,const std::string & value)152 std::string GetProcStatusLine(int pid, const std::string& value) {
153 const std::string fname = absl::StrCat("/proc/", pid, "/status");
154 std::string procpidstatus;
155 auto status =
156 sapi::file::GetContents(fname, &procpidstatus, sapi::file::Defaults());
157 if (!status.ok()) {
158 SAPI_RAW_LOG(WARNING, "%s", std::string(status.message()).c_str());
159 return "";
160 }
161
162 for (const auto& line : absl::StrSplit(procpidstatus, '\n')) {
163 std::pair<std::string, std::string> kv =
164 absl::StrSplit(line, absl::MaxSplits(':', 1));
165 SAPI_RAW_VLOG(3, "Key: '%s' Value: '%s'", kv.first.c_str(),
166 kv.second.c_str());
167 if (kv.first == value) {
168 absl::StripLeadingAsciiWhitespace(&kv.second);
169 return std::move(kv.second);
170 }
171 }
172 SAPI_RAW_LOG(ERROR, "No '%s' field found in '%s'", value.c_str(),
173 fname.c_str());
174 return "";
175 }
176
Syscall(long sys_no,uintptr_t a1,uintptr_t a2,uintptr_t a3,uintptr_t a4,uintptr_t a5,uintptr_t a6)177 long Syscall(long sys_no, // NOLINT
178 uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4,
179 uintptr_t a5, uintptr_t a6) {
180 return syscall(sys_no, a1, a2, a3, a4, a5, a6);
181 }
182
183 namespace {
184
ChildFunc(void * arg)185 int ChildFunc(void* arg) {
186 auto* env_ptr = reinterpret_cast<jmp_buf*>(arg);
187 // Restore the old stack.
188 longjmp(*env_ptr, 1);
189 }
190
191 // This code is inspired by base/process/launch_posix.cc in the Chromium source.
192 // There are a few things to be careful of here:
193 // - Make sure the stack_buf is below the env_ptr to please FORTIFY_SOURCE.
194 // - Make sure the stack_buf is not too far away from the real stack to please
195 // ASAN. If they are too far away, a warning is printed. This means not only
196 // that the temporary stack buffer needs to also be on the stack, but also that
197 // we need to disable ASAN for this function, to prevent it from being placed on
198 // the fake ASAN stack.
199 // - Make sure that the buffer is aligned to whatever is required by the CPU.
200 ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS
201 ABSL_ATTRIBUTE_NOINLINE
CloneAndJump(int flags,jmp_buf * env_ptr)202 pid_t CloneAndJump(int flags, jmp_buf* env_ptr) {
203 uint8_t stack_buf[PTHREAD_STACK_MIN] ABSL_CACHELINE_ALIGNED;
204 static_assert(sapi::host_cpu::IsX8664() || sapi::host_cpu::IsPPC64LE() ||
205 sapi::host_cpu::IsArm64() || sapi::host_cpu::IsArm(),
206 "Host CPU architecture not supported, see config.h");
207 // Stack grows down.
208 void* stack = stack_buf + sizeof(stack_buf);
209 int r = clone(&ChildFunc, stack, flags, env_ptr, nullptr, nullptr, nullptr);
210 if (r == -1) {
211 SAPI_RAW_PLOG(ERROR, "clone()");
212 }
213 return r;
214 }
215
216 } // namespace
217
ForkWithFlags(int flags)218 pid_t ForkWithFlags(int flags) {
219 const int unsupported_flags = CLONE_CHILD_CLEARTID | CLONE_CHILD_SETTID |
220 CLONE_PARENT_SETTID | CLONE_SETTLS | CLONE_VM;
221 if (flags & unsupported_flags) {
222 SAPI_RAW_LOG(ERROR, "ForkWithFlags used with unsupported flag");
223 return -1;
224 }
225
226 jmp_buf env;
227 if (setjmp(env) == 0) {
228 return CloneAndJump(flags, &env);
229 }
230
231 // Child.
232 return 0;
233 }
234
CreateMemFd(int * fd,const char * name)235 bool CreateMemFd(int* fd, const char* name) {
236 // Usually defined in linux/memfd.h. Define it here to avoid dependency on
237 // UAPI headers.
238 constexpr uintptr_t kMfdCloseOnExec = 0x0001;
239 constexpr uintptr_t kMfdAllowSealing = 0x0002;
240 int tmp_fd = Syscall(__NR_memfd_create, reinterpret_cast<uintptr_t>(name),
241 kMfdCloseOnExec | kMfdAllowSealing);
242 if (tmp_fd < 0) {
243 if (errno == ENOSYS) {
244 SAPI_RAW_LOG(ERROR,
245 "This system does not seem to support the memfd_create()"
246 " syscall. Try running on a newer kernel.");
247 } else {
248 SAPI_RAW_PLOG(ERROR, "Could not create tmp file '%s'", name);
249 }
250 return false;
251 }
252 *fd = tmp_fd;
253 return true;
254 }
255
Communicate(const std::vector<std::string> & argv,const std::vector<std::string> & envv,std::string * output)256 absl::StatusOr<int> Communicate(const std::vector<std::string>& argv,
257 const std::vector<std::string>& envv,
258 std::string* output) {
259 int cout_pipe[2];
260 posix_spawn_file_actions_t action;
261
262 if (pipe(cout_pipe) == -1) {
263 return absl::ErrnoToStatus(errno, "creating pipe");
264 }
265 file_util::fileops::FDCloser cout_closer{cout_pipe[1]};
266
267 posix_spawn_file_actions_init(&action);
268 struct ActionCleanup {
269 ~ActionCleanup() { posix_spawn_file_actions_destroy(action_); }
270 posix_spawn_file_actions_t* action_;
271 } action_cleanup{&action};
272
273 // Redirect both stdout and stderr to stdout to our pipe.
274 posix_spawn_file_actions_addclose(&action, cout_pipe[0]);
275 posix_spawn_file_actions_adddup2(&action, cout_pipe[1], 1);
276 posix_spawn_file_actions_adddup2(&action, cout_pipe[1], 2);
277 posix_spawn_file_actions_addclose(&action, cout_pipe[1]);
278
279 CharPtrArray args = CharPtrArray::FromStringVector(argv);
280 CharPtrArray envp = CharPtrArray::FromStringVector(envv);
281
282 pid_t pid;
283 if (posix_spawnp(&pid, args.array()[0], &action, nullptr,
284 const_cast<char**>(args.data()),
285 const_cast<char**>(envp.data())) != 0) {
286 return absl::ErrnoToStatus(errno, "posix_spawnp()");
287 }
288
289 // Close child end of the pipe.
290 cout_closer.Close();
291
292 std::string buffer(1024, '\0');
293 for (;;) {
294 int bytes_read =
295 TEMP_FAILURE_RETRY(read(cout_pipe[0], &buffer[0], buffer.length()));
296 if (bytes_read < 0) {
297 return absl::ErrnoToStatus(errno, "reading from cout pipe");
298 }
299 if (bytes_read == 0) {
300 break; // Nothing left to read
301 }
302 absl::StrAppend(output, absl::string_view(buffer.data(), bytes_read));
303 }
304
305 int status;
306 SAPI_RAW_PCHECK(TEMP_FAILURE_RETRY(waitpid(pid, &status, 0)) == pid,
307 "Waiting for subprocess");
308 return WEXITSTATUS(status);
309 }
310
GetSignalName(int signo)311 std::string GetSignalName(int signo) {
312 constexpr absl::string_view kSignalNames[] = {
313 "SIG_0", "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP",
314 "SIGABRT", "SIGBUS", "SIGFPE", "SIGKILL", "SIGUSR1", "SIGSEGV",
315 "SIGUSR2", "SIGPIPE", "SIGALRM", "SIGTERM", "SIGSTKFLT", "SIGCHLD",
316 "SIGCONT", "SIGSTOP", "SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGURG",
317 "SIGXCPU", "SIGXFSZ", "SIGVTALARM", "SIGPROF", "SIGWINCH", "SIGIO",
318 "SIGPWR", "SIGSYS"};
319
320 if (signo >= SIGRTMIN && signo <= SIGRTMAX) {
321 return absl::StrFormat("SIGRT-%d [%d]", signo - SIGRTMIN, signo);
322 }
323 if (signo < 0 || signo >= static_cast<int>(ABSL_ARRAYSIZE(kSignalNames))) {
324 return absl::StrFormat("UNKNOWN_SIGNAL [%d]", signo);
325 }
326 return absl::StrFormat("%s [%d]", kSignalNames[signo], signo);
327 }
328
GetRlimitName(int resource)329 std::string GetRlimitName(int resource) {
330 switch (resource) {
331 case RLIMIT_AS:
332 return "RLIMIT_AS";
333 case RLIMIT_FSIZE:
334 return "RLIMIT_FSIZE";
335 case RLIMIT_NOFILE:
336 return "RLIMIT_NOFILE";
337 case RLIMIT_CPU:
338 return "RLIMIT_CPU";
339 case RLIMIT_CORE:
340 return "RLIMIT_CORE";
341 default:
342 return absl::StrCat("UNKNOWN: ", resource);
343 }
344 }
345
GetPtraceEventName(int event)346 std::string GetPtraceEventName(int event) {
347 #if !defined(PTRACE_EVENT_STOP)
348 #define PTRACE_EVENT_STOP 128
349 #endif
350
351 switch (event) {
352 case PTRACE_EVENT_FORK:
353 return "PTRACE_EVENT_FORK";
354 case PTRACE_EVENT_VFORK:
355 return "PTRACE_EVENT_VFORK";
356 case PTRACE_EVENT_CLONE:
357 return "PTRACE_EVENT_CLONE";
358 case PTRACE_EVENT_EXEC:
359 return "PTRACE_EVENT_EXEC";
360 case PTRACE_EVENT_VFORK_DONE:
361 return "PTRACE_EVENT_VFORK_DONE";
362 case PTRACE_EVENT_EXIT:
363 return "PTRACE_EVENT_EXIT";
364 case PTRACE_EVENT_SECCOMP:
365 return "PTRACE_EVENT_SECCOMP";
366 case PTRACE_EVENT_STOP:
367 return "PTRACE_EVENT_STOP";
368 default:
369 return absl::StrCat("UNKNOWN: ", event);
370 }
371 }
372
ReadCPathFromPid(pid_t pid,uintptr_t ptr)373 absl::StatusOr<std::string> ReadCPathFromPid(pid_t pid, uintptr_t ptr) {
374 std::string path(PATH_MAX, '\0');
375 iovec local_iov[] = {{&path[0], path.size()}};
376
377 static const uintptr_t page_size = getpagesize();
378 static const uintptr_t page_mask = ~(page_size - 1);
379 // See 'man process_vm_readv' for details on how to read NUL-terminated
380 // strings with this syscall.
381 size_t len1 = ((ptr + page_size) & page_mask) - ptr;
382 len1 = (len1 > path.size()) ? path.size() : len1;
383 size_t len2 = (path.size() <= len1) ? 0UL : path.size() - len1;
384 // Second iov is wrapping around to NULL ptr.
385 if ((ptr + len1) < ptr) {
386 len2 = 0UL;
387 }
388
389 iovec remote_iov[] = {
390 {reinterpret_cast<void*>(ptr), len1},
391 {reinterpret_cast<void*>(ptr + len1), len2},
392 };
393
394 SAPI_RAW_VLOG(4, "ReadCPathFromPid (iovec): len1: %zu, len2: %zu", len1,
395 len2);
396 if (process_vm_readv(pid, local_iov, ABSL_ARRAYSIZE(local_iov), remote_iov,
397 ABSL_ARRAYSIZE(remote_iov), 0) < 0) {
398 return absl::ErrnoToStatus(
399 errno,
400 absl::StrFormat("process_vm_readv() failed for PID: %d at address: %#x",
401 pid, reinterpret_cast<uintptr_t>(ptr)));
402 }
403
404 // Check for whether there's a NUL byte in the buffer. If not, it's an
405 // incorrect path (or >PATH_MAX).
406 auto pos = path.find('\0');
407 if (pos == std::string::npos) {
408 return absl::FailedPreconditionError(absl::StrCat(
409 "No NUL-byte inside the C string '", absl::CHexEscape(path), "'"));
410 }
411 path.resize(pos);
412 return path;
413 }
414
Execveat(int dirfd,const char * pathname,const char * const argv[],const char * const envp[],int flags,uintptr_t extra_arg)415 int Execveat(int dirfd, const char* pathname, const char* const argv[],
416 const char* const envp[], int flags, uintptr_t extra_arg) {
417 // Flush coverage data prior to exec.
418 if (extra_arg == 0) {
419 DumpCoverageData();
420 }
421 int res = syscall(__NR_execveat, static_cast<uintptr_t>(dirfd),
422 reinterpret_cast<uintptr_t>(pathname),
423 reinterpret_cast<uintptr_t>(argv),
424 reinterpret_cast<uintptr_t>(envp),
425 static_cast<uintptr_t>(flags), extra_arg);
426 // Reset coverage data if exec fails as the counters have been already dumped.
427 if (extra_arg == 0) {
428 ResetCoverageData();
429 }
430 return res;
431 }
432
433 } // namespace sandbox2::util
434