sandboxed-api/sandboxed_api/sandbox2/stack_trace.cc
Christian Blichmann dbaf95c724 Move utility code into sandboxed_api/util
This change should make it less confusing where utility code comes from.
Having it in two places made sense when we were debating whether to publish
Sandbox2 separately, but not any longer.

Follow-up changes will move `sandbox2/util.h` and rename the remaining
`sandbox2/util` folder.

PiperOrigin-RevId: 351601640
Change-Id: I6256845261f610e590c25e2c59851cc51da2d778
2021-01-13 09:25:52 -08:00

359 lines
12 KiB
C++

// 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 <sys/resource.h>
#include <syscall.h>
#include <memory>
#include <utility>
#include <vector>
#include <glog/logging.h>
#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/config.h"
#include "sandboxed_api/sandbox2/comms.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/util/fileops.h"
#include "sandboxed_api/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 {
namespace file = ::sapi::file;
namespace file_util = ::sapi::file_util;
class StackTracePeer {
public:
static std::unique_ptr<Policy> 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<Policy> 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)
#ifdef __NR_fstat64
.AllowSyscall(__NR_fstat64)
#endif
.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<unsigned int>(target_pid), ALLOW),
JEQ32(static_cast<unsigned int>(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<std::vector<int>>();
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<std::string> argv;
std::vector<std::string> 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<const char*>(&regs->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<std::string> GetStackTrace(const Regs* regs, const Mounts& mounts) {
if constexpr (sapi::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<std::string> UnsafeGetStackTrace(pid_t pid) {
LOG(WARNING) << "Using non-sandboxed libunwind";
std::vector<uintptr_t> ips;
return RunLibUnwindAndSymbolizer(pid, &ips, kDefaultMaxFrames);
}
std::vector<std::string> CompactStackTrace(
const std::vector<std::string>& stack_trace) {
std::vector<std::string> 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