2019-03-19 00:21:48 +08:00
|
|
|
// 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>
|
2019-06-05 15:25:50 +08:00
|
|
|
#include "sandboxed_api/util/flag.h"
|
2019-03-19 00:21:48 +08:00
|
|
|
#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");
|
2019-06-05 15:25:50 +08:00
|
|
|
ABSL_FLAG(string, sandbox2tool_cwd, "/",
|
2019-03-19 00:21:48 +08:00
|
|
|
"If not empty, chdir to the directory before sandboxed");
|
2019-06-05 15:25:50 +08:00
|
|
|
ABSL_FLAG(string, sandbox2tool_additional_bind_mounts, "",
|
2019-03-19 00:21:48 +08:00
|
|
|
"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) {
|
2019-06-05 22:39:11 +08:00
|
|
|
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
2019-03-19 00:21:48 +08:00
|
|
|
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;
|
|
|
|
}
|