sandboxed-api/sandboxed_api/sandbox2/examples/tool/sandbox2tool.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

235 lines
8.2 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.
// 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"
#include "sandboxed_api/util/fileops.h"
using std::string;
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);
sapi::file_util::fileops::FDCloser recv_fd1;
if (absl::GetFlag(FLAGS_sandbox2tool_redirect_fd1)) {
// Make the sandboxed process' fd be available as fd in the current process.
recv_fd1 = sapi::file_util::fileops::FDCloser(
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();
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.get());
// 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;
}