sandboxed-api/sandboxed_api/sandbox2/examples/tool/sandbox2tool.cc

233 lines
8.1 KiB
C++
Raw Normal View History

// Copyright 2019 Google LLC. All Rights Reserved.
//
// 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.
// A simple sandbox2 testing tool.
//
// Usage:
// sandbox2tool -v=1 -sandbox2_danger_danger_permit_all -logtostderr -- /bin/ls
#include <sys/resource.h>
#include <sys/stat.h>
#include <syscall.h>
#include <unistd.h>
#include <csignal>
#include <cstdlib>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <glog/logging.h>
#include "sandboxed_api/util/flag.h"
#include "absl/memory/memory.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_split.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/result.h"
#include "sandboxed_api/sandbox2/sandbox2.h"
#include "sandboxed_api/sandbox2/util.h"
#include "sandboxed_api/sandbox2/util/bpf_helper.h"
ABSL_FLAG(bool, sandbox2tool_keep_env, false,
"Keep current environment variables");
ABSL_FLAG(bool, sandbox2tool_redirect_fd1, false,
"Receive sandboxee's STDOUT_FILENO (1) and output it locally");
ABSL_FLAG(bool, sandbox2tool_need_networking, false,
"If user namespaces are enabled, this option will enable "
"networking (by disabling the network namespace)");
ABSL_FLAG(bool, sandbox2tool_mount_tmp, false,
"If user namespaces are enabled, this option will create a tmpfs "
"mount at /tmp");
ABSL_FLAG(bool, sandbox2tool_resolve_and_add_libraries, false,
"resolve and mount the required libraries for the sandboxee");
ABSL_FLAG(bool, sandbox2tool_pause_resume, false,
"Pause the process after 3 seconds, resume after the subsequent "
"3 seconds, kill it after the final 3 seconds");
ABSL_FLAG(bool, sandbox2tool_pause_kill, false,
"Pause the process after 3 seconds, then SIGKILL it.");
ABSL_FLAG(bool, sandbox2tool_dump_stack, false,
"Dump the stack trace one second after the process is running.");
ABSL_FLAG(uint64_t, sandbox2tool_cpu_timeout, 60U,
"CPU timeout in seconds (if > 0)");
ABSL_FLAG(uint64_t, sandbox2tool_walltime_timeout, 60U,
"Wall-time timeout in seconds (if >0)");
ABSL_FLAG(uint64_t, sandbox2tool_file_size_creation_limit, 1024U,
"Maximum size of created files");
ABSL_FLAG(string, sandbox2tool_cwd, "/",
"If not empty, chdir to the directory before sandboxed");
ABSL_FLAG(string, sandbox2tool_additional_bind_mounts, "",
"If user namespaces are enabled, this option will add additional "
"bind mounts. Mounts are separated by comma and can optionally "
"specify a target using \"=>\" "
"(e.g. \"/usr,/bin,/lib,/tmp/foo=>/etc/passwd\")");
namespace {
void OutputFD(int fd) {
for (;;) {
char buf[4096];
ssize_t rlen = read(fd, buf, sizeof(buf));
if (rlen < 1) {
break;
}
LOG(INFO) << "Received from the sandboxee (FD STDOUT_FILENO (1)):"
<< "\n========================================\n"
<< std::string(buf, rlen)
<< "\n========================================\n";
}
}
} // namespace
int main(int argc, char** argv) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
google::InitGoogleLogging(argv[0]);
if (argc < 2) {
absl::FPrintF(stderr, "Usage: %s [flags] -- cmd args...", argv[0]);
return EXIT_FAILURE;
}
// Pass everything after '--' to the sandbox.
std::vector<std::string> args;
sandbox2::util::CharPtrArrToVecString(&argv[1], &args);
// Pass the current environ pointer, depending on the flag.
std::vector<std::string> envp;
if (absl::GetFlag(FLAGS_sandbox2tool_keep_env)) {
sandbox2::util::CharPtrArrToVecString(environ, &envp);
}
auto executor = absl::make_unique<sandbox2::Executor>(argv[1], args, envp);
int recv_fd1 = -1;
if (absl::GetFlag(FLAGS_sandbox2tool_redirect_fd1)) {
// Make the sandboxed process' fd be available as fd in the current process.
recv_fd1 = executor->ipc()->ReceiveFd(STDOUT_FILENO);
}
executor
->limits()
// Remove restrictions on the size of address-space of sandboxed
// processes.
->set_rlimit_as(RLIM64_INFINITY)
// Kill sandboxed processes with a signal (SIGXFSZ) if it writes more than
// this to the file-system.
.set_rlimit_fsize(
absl::GetFlag(FLAGS_sandbox2tool_file_size_creation_limit))
// An arbitrary, but empirically safe value.
.set_rlimit_nofile(1024U)
.set_walltime_limit(
absl::Seconds(absl::GetFlag(FLAGS_sandbox2tool_walltime_timeout)));
if (absl::GetFlag(FLAGS_sandbox2tool_cpu_timeout) > 0) {
executor->limits()->set_rlimit_cpu(
absl::GetFlag(FLAGS_sandbox2tool_cpu_timeout));
}
sandbox2::PolicyBuilder builder;
builder.AddPolicyOnSyscall(__NR_tee, {KILL});
builder.DangerDefaultAllowAll();
builder.EnableNamespaces();
if (absl::GetFlag(FLAGS_sandbox2tool_need_networking)) {
builder.AllowUnrestrictedNetworking();
}
if (absl::GetFlag(FLAGS_sandbox2tool_mount_tmp)) {
builder.AddTmpfs("/tmp");
}
auto mounts_string = absl::GetFlag(FLAGS_sandbox2tool_additional_bind_mounts);
if (!mounts_string.empty()) {
for (auto mount : absl::StrSplit(mounts_string, ',')) {
std::vector<std::string> source_target = absl::StrSplit(mount, "=>");
auto source = source_target[0];
auto target = source_target[0];
if (source_target.size() == 2) {
target = source_target[1];
}
struct stat64 st;
PCHECK(stat64(source.c_str(), &st) != -1)
<< "could not stat additional mount " << source;
if ((st.st_mode & S_IFMT) == S_IFDIR) {
builder.AddDirectoryAt(source, target, true);
} else {
builder.AddFileAt(source, target, true);
}
}
}
if (absl::GetFlag(FLAGS_sandbox2tool_resolve_and_add_libraries)) {
builder.AddLibrariesForBinary(argv[1]);
}
auto policy = builder.BuildOrDie();
// Current working directory.
if (!absl::GetFlag(FLAGS_sandbox2tool_cwd).empty()) {
executor->set_cwd(absl::GetFlag(FLAGS_sandbox2tool_cwd));
}
// Instantiate the Sandbox2 object with policies and executors.
sandbox2::Sandbox2 s2(std::move(executor), std::move(policy));
// This sandbox runs asynchronously. If there was no OutputFD() loop receiving
// the data from the recv_fd1, one could just use Sandbox2::Run().
if (s2.RunAsync()) {
if (absl::GetFlag(FLAGS_sandbox2tool_pause_resume)) {
sleep(3);
kill(s2.GetPid(), SIGSTOP);
sleep(3);
s2.SetWallTimeLimit(3);
kill(s2.GetPid(), SIGCONT);
} else if (absl::GetFlag(FLAGS_sandbox2tool_pause_kill)) {
sleep(3);
kill(s2.GetPid(), SIGSTOP);
sleep(1);
kill(s2.GetPid(), SIGKILL);
sleep(1);
} else if (absl::GetFlag(FLAGS_sandbox2tool_dump_stack)) {
sleep(1);
s2.DumpStackTrace();
} else if (absl::GetFlag(FLAGS_sandbox2tool_redirect_fd1)) {
OutputFD(recv_fd1);
// We couldn't receive more data from the sandboxee's STDOUT_FILENO, but
// the process could still be running. Kill it unconditionally. A correct
// final status code will be reported instead of Result::EXTERNAL_KILL.
s2.Kill();
}
} else {
LOG(ERROR) << "Sandbox failed";
}
auto result = s2.AwaitResult();
if (result.final_status() != sandbox2::Result::OK) {
LOG(ERROR) << "Sandbox error: " << result.ToString();
return 2; // sandbox violation
}
auto code = result.reason_code();
if (code) {
LOG(ERROR) << "Child exited with non-zero " << code;
return 1; // normal child error
}
return EXIT_SUCCESS;
}