// Copyright 2019 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Implementation of the sandbox2::StackTrace class. #include "sandboxed_api/sandbox2/stack_trace.h" #include #include #include #include #include #include #include "sandboxed_api/util/flag.h" #include "absl/memory/memory.h" #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" #include "absl/strings/strip.h" #include "libcap/include/sys/capability.h" #include "sandboxed_api/sandbox2/comms.h" #include "sandboxed_api/sandbox2/config.h" #include "sandboxed_api/sandbox2/executor.h" #include "sandboxed_api/sandbox2/ipc.h" #include "sandboxed_api/sandbox2/limits.h" #include "sandboxed_api/sandbox2/policy.h" #include "sandboxed_api/sandbox2/policybuilder.h" #include "sandboxed_api/sandbox2/regs.h" #include "sandboxed_api/sandbox2/result.h" #include "sandboxed_api/sandbox2/sandbox2.h" #include "sandboxed_api/sandbox2/unwind/unwind.h" #include "sandboxed_api/sandbox2/unwind/unwind.pb.h" #include "sandboxed_api/sandbox2/util/bpf_helper.h" #include "sandboxed_api/sandbox2/util/fileops.h" #include "sandboxed_api/sandbox2/util/path.h" ABSL_FLAG(bool, sandbox_disable_all_stack_traces, false, "Completely disable stack trace collection for sandboxees"); ABSL_FLAG(bool, sandbox_libunwind_crash_handler, true, "Sandbox libunwind when handling violations (preferred)"); namespace sandbox2 { class StackTracePeer { public: static std::unique_ptr GetPolicy(pid_t target_pid, const std::string& maps_file, const std::string& app_path, const std::string& exe_path, const Mounts& mounts); static bool LaunchLibunwindSandbox(const Regs* regs, const Mounts& mounts, UnwindResult* result); }; std::unique_ptr StackTracePeer::GetPolicy(pid_t target_pid, const std::string& maps_file, const std::string& app_path, const std::string& exe_path, const Mounts& mounts) { PolicyBuilder builder; builder // Use the mounttree of the original executable as starting point. .SetMounts(mounts) .AllowOpen() .AllowRead() .AllowWrite() .AllowSyscall(__NR_close) .AllowMmap() .AllowExit() .AllowHandleSignals() // libunwind .AllowSyscall(__NR_fstat) .AllowSyscall(__NR_lseek) #ifdef __NR__llseek .AllowSyscall(__NR__llseek) // Newer glibc on PPC #endif .AllowSyscall(__NR_mincore) .AllowSyscall(__NR_mprotect) .AllowSyscall(__NR_munmap) .AllowSyscall(__NR_pipe2) // Symbolizer .AllowSyscall(__NR_brk) .AllowSyscall(__NR_clock_gettime) // Other .AllowSyscall(__NR_dup) .AllowSyscall(__NR_fcntl) .AllowSyscall(__NR_getpid) .AllowSyscall(__NR_gettid) .AllowSyscall(__NR_madvise) // Required for our ptrace replacement. .AddPolicyOnSyscall( __NR_process_vm_readv, { // The pid technically is a 64bit int, however Linux usually uses // max 16 bit, so we are fine with comparing only 32 bits here. ARG_32(0), JEQ32(static_cast(target_pid), ALLOW), JEQ32(static_cast(1), ALLOW), }) // Add proc maps. .AddFileAt(maps_file, file::JoinPath("/proc", absl::StrCat(target_pid), "maps")) .AddFileAt(maps_file, file::JoinPath("/proc", absl::StrCat(target_pid), "task", absl::StrCat(target_pid), "maps")) // Add the binary itself. .AddFileAt(exe_path, app_path); // Add all possible libraries without the need of parsing the binary // or /proc/pid/maps. for (const auto& library_path : { "/usr/lib", "/lib", }) { if (access(library_path, F_OK) != -1) { VLOG(1) << "Adding library folder '" << library_path << "'"; builder.AddDirectory(library_path); } else { VLOG(1) << "Could not add library folder '" << library_path << "' as it does not exist"; } } auto policy = builder.TryBuild(); if (!policy.ok()) { LOG(ERROR) << "Creating stack unwinder sandbox policy failed"; return nullptr; } auto keep_capabilities = absl::make_unique>(); keep_capabilities->push_back(CAP_SYS_PTRACE); (*policy)->AllowUnsafeKeepCapabilities(std::move(keep_capabilities)); // Use no special namespace flags when cloning. We will join an existing // user namespace and will unshare() afterwards (See forkserver.cc). (*policy)->GetNamespace()->clone_flags_ = 0; return std::move(*policy); } bool StackTracePeer::LaunchLibunwindSandbox(const Regs* regs, const Mounts& mounts, sandbox2::UnwindResult* result) { const pid_t pid = regs->pid(); // Tell executor to use this special internal mode. std::vector argv; std::vector envp; // We're not using absl::make_unique here as we're a friend of this specific // constructor and using make_unique won't work. auto executor = absl::WrapUnique(new Executor(pid)); executor->limits() ->set_rlimit_as(RLIM64_INFINITY) .set_rlimit_cpu(10) .set_walltime_limit(absl::Seconds(5)); // Temporary directory used to provide files from /proc to the unwind sandbox. char unwind_temp_directory_template[] = "/tmp/.sandbox2_unwind_XXXXXX"; char* unwind_temp_directory = mkdtemp(unwind_temp_directory_template); if (!unwind_temp_directory) { LOG(WARNING) << "Could not create temporary directory for unwinding"; return false; } struct UnwindTempDirectoryCleanup { ~UnwindTempDirectoryCleanup() { file_util::fileops::DeleteRecursively(capture); } char* capture; } cleanup{unwind_temp_directory}; // Copy over important files from the /proc directory as we can't mount them. const std::string unwind_temp_maps_path = file::JoinPath(unwind_temp_directory, "maps"); if (!file_util::fileops::CopyFile( file::JoinPath("/proc", absl::StrCat(pid), "maps"), unwind_temp_maps_path, 0400)) { LOG(WARNING) << "Could not copy maps file"; return false; } // Get path to the binary. // app_path contains the path like it is also in /proc/pid/maps. It is // relative to the sandboxee's mount namespace. If it is not existing // (anymore) it will have a ' (deleted)' suffix. std::string app_path; std::string proc_pid_exe = file::JoinPath("/proc", absl::StrCat(pid), "exe"); if (!file_util::fileops::ReadLinkAbsolute(proc_pid_exe, &app_path)) { LOG(WARNING) << "Could not obtain absolute path to the binary"; return false; } // The exe_path will have a mountable path of the application, even if it was // removed. std::string exe_path; // Resolve app_path backing file. const auto* app_node = mounts.GetNode(app_path); if (app_node) { exe_path = app_node->file_node().outside(); } if (exe_path.empty()) { // File was probably removed. LOG(WARNING) << "File was removed, using /proc/pid/exe."; app_path = std::string(absl::StripSuffix(app_path, " (deleted)")); // Create a copy of /proc/pid/exe, mount that one. exe_path = file::JoinPath(unwind_temp_directory, "exe"); if (!file_util::fileops::CopyFile(proc_pid_exe, exe_path, 0700)) { LOG(WARNING) << "Could not copy /proc/pid/exe"; return false; } } VLOG(1) << "Resolved binary: " << app_path << " / " << exe_path; // Add mappings for the binary (as they might not have been added due to the // forkserver). auto policy = StackTracePeer::GetPolicy(pid, unwind_temp_maps_path, app_path, exe_path, mounts); if (!policy) { return false; } auto* comms = executor->ipc()->comms(); Sandbox2 sandbox(std::move(executor), std::move(policy)); VLOG(1) << "Running libunwind sandbox"; sandbox.RunAsync(); UnwindSetup msg; msg.set_pid(pid); msg.set_regs(reinterpret_cast(®s->user_regs_), sizeof(regs->user_regs_)); msg.set_default_max_frames(kDefaultMaxFrames); bool success = true; if (!comms->SendProtoBuf(msg)) { LOG(ERROR) << "Sending libunwind setup message failed"; success = false; } if (success && !comms->RecvProtoBuf(result)) { LOG(ERROR) << "Receiving libunwind result failed"; success = false; } if (!success) { sandbox.Kill(); } auto sandbox_result = sandbox.AwaitResult(); LOG(INFO) << "Libunwind execution status: " << sandbox_result.ToString(); return success && sandbox_result.final_status() == Result::OK; } std::vector GetStackTrace(const Regs* regs, const Mounts& mounts) { if constexpr (host_cpu::IsArm64()) { return {"[Stack traces unavailable]"}; } if (absl::GetFlag(FLAGS_sandbox_disable_all_stack_traces)) { return {"[Stacktraces disabled]"}; } if (!regs) { LOG(WARNING) << "Could not obtain stacktrace, regs == nullptr"; return {"[ERROR (noregs)]"}; } #if defined(THREAD_SANITIZER) || defined(ADDRESS_SANITIZER) || \ defined(MEMORY_SANITIZER) constexpr bool kSanitizerEnabled = true; #else constexpr bool kSanitizerEnabled = false; #endif const bool coverage_enabled = getenv("COVERAGE"); // Show a warning if sandboxed libunwind is requested but we're running in // an ASAN / coverage build (= we can't use sandboxed libunwind). if (absl::GetFlag(FLAGS_sandbox_libunwind_crash_handler) && (kSanitizerEnabled || coverage_enabled)) { LOG_IF(WARNING, kSanitizerEnabled) << "Sanitizer build, using non-sandboxed libunwind"; LOG_IF(WARNING, coverage_enabled) << "Coverage build, using non-sandboxed libunwind"; return UnsafeGetStackTrace(regs->pid()); } if (!absl::GetFlag(FLAGS_sandbox_libunwind_crash_handler)) { return UnsafeGetStackTrace(regs->pid()); } UnwindResult res; if (!StackTracePeer::LaunchLibunwindSandbox(regs, mounts, &res)) { return {}; } return {res.stacktrace().begin(), res.stacktrace().end()}; } std::vector UnsafeGetStackTrace(pid_t pid) { LOG(WARNING) << "Using non-sandboxed libunwind"; std::vector ips; return RunLibUnwindAndSymbolizer(pid, &ips, kDefaultMaxFrames); } std::vector CompactStackTrace( const std::vector& stack_trace) { std::vector compact_trace; compact_trace.reserve(stack_trace.size() / 2); const std::string* prev = nullptr; int seen = 0; auto add_repeats = [&compact_trace](int seen) { if (seen != 0) { compact_trace.push_back( absl::StrCat("(previous frame repeated ", seen, " times)")); } }; for (const auto& frame : stack_trace) { if (prev && frame == *prev) { ++seen; } else { prev = &frame; add_repeats(seen); seen = 0; compact_trace.push_back(frame); } } add_repeats(seen); return compact_trace; } } // namespace sandbox2