• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 // A simple sandbox2 testing tool.
16 //
17 // Example usage:
18 //   sandbox2tool
19 //     --v=1
20 //     --sandbox2tool_resolve_and_add_libraries
21 //     --sandbox2_danger_danger_permit_all
22 //     --logtostderr
23 //     /bin/ls
24 
25 #include <sys/stat.h>
26 #include <syscall.h>
27 #include <unistd.h>
28 
29 #include <csignal>
30 #include <cstdint>
31 #include <cstdio>
32 #include <cstdlib>
33 #include <memory>
34 #include <string>
35 #include <utility>
36 #include <vector>
37 
38 #include "absl/base/log_severity.h"
39 #include "absl/flags/flag.h"
40 #include "absl/flags/parse.h"
41 #include "absl/flags/usage.h"
42 #include "absl/log/check.h"
43 #include "absl/log/globals.h"
44 #include "absl/log/initialize.h"
45 #include "absl/log/log.h"
46 #include "absl/strings/str_format.h"
47 #include "absl/strings/str_split.h"
48 #include "absl/strings/string_view.h"
49 #include "absl/time/time.h"
50 #include "sandboxed_api/sandbox2/allowlists/all_syscalls.h"
51 #include "sandboxed_api/sandbox2/allowlists/unrestricted_networking.h"
52 #include "sandboxed_api/sandbox2/executor.h"
53 #include "sandboxed_api/sandbox2/ipc.h"
54 #include "sandboxed_api/sandbox2/limits.h"
55 #include "sandboxed_api/sandbox2/policy.h"
56 #include "sandboxed_api/sandbox2/policybuilder.h"
57 #include "sandboxed_api/sandbox2/result.h"
58 #include "sandboxed_api/sandbox2/sandbox2.h"
59 #include "sandboxed_api/sandbox2/util.h"
60 #include "sandboxed_api/sandbox2/util/bpf_helper.h"
61 #include "sandboxed_api/util/fileops.h"
62 
63 ABSL_FLAG(bool, sandbox2tool_keep_env, false,
64           "Keep current environment variables");
65 ABSL_FLAG(bool, sandbox2tool_redirect_fd1, false,
66           "Receive sandboxee's STDOUT_FILENO (1) and output it locally");
67 ABSL_FLAG(bool, sandbox2tool_need_networking, false,
68           "If user namespaces are enabled, this option will enable "
69           "networking (by disabling the network namespace)");
70 ABSL_FLAG(bool, sandbox2tool_mount_tmp, false,
71           "If user namespaces are enabled, this option will create a tmpfs "
72           "mount at /tmp");
73 ABSL_FLAG(bool, sandbox2tool_resolve_and_add_libraries, false,
74           "resolve and mount the required libraries for the sandboxee");
75 ABSL_FLAG(bool, sandbox2tool_pause_resume, false,
76           "Pause the process after 3 seconds, resume after the subsequent "
77           "3 seconds, kill it after the final 3 seconds");
78 ABSL_FLAG(bool, sandbox2tool_pause_kill, false,
79           "Pause the process after 3 seconds, then SIGKILL it.");
80 ABSL_FLAG(bool, sandbox2tool_dump_stack, false,
81           "Dump the stack trace one second after the process is running.");
82 ABSL_FLAG(uint64_t, sandbox2tool_cpu_timeout, 60U,
83           "CPU timeout in seconds (if > 0)");
84 ABSL_FLAG(uint64_t, sandbox2tool_walltime_timeout, 60U,
85           "Wall-time timeout in seconds (if >0)");
86 ABSL_FLAG(uint64_t, sandbox2tool_file_size_creation_limit, 1024U,
87           "Maximum size of created files");
88 ABSL_FLAG(std::string, sandbox2tool_cwd, "/",
89           "If not empty, chdir to the directory before sandboxed");
90 ABSL_FLAG(std::string, sandbox2tool_additional_bind_mounts, "",
91           "If user namespaces are enabled, this option will add additional "
92           "bind mounts. Mounts are separated by comma and can optionally "
93           "specify a target using \"=>\" "
94           "(e.g. \"/usr,/bin,/lib,/tmp/foo=>/etc/passwd\")");
95 
96 namespace {
97 
OutputFD(int fd)98 void OutputFD(int fd) {
99   for (;;) {
100     char buf[4096];
101     ssize_t rlen = read(fd, buf, sizeof(buf));
102     if (rlen < 1) {
103       break;
104     }
105     LOG(INFO) << "Received from the sandboxee (FD STDOUT_FILENO (1)):"
106               << "\n========================================\n"
107               << std::string(buf, rlen)
108               << "\n========================================\n";
109   }
110 }
111 
112 }  // namespace
113 
main(int argc,char * argv[])114 int main(int argc, char* argv[]) {
115   const std::string program_name = sapi::file_util::fileops::Basename(argv[0]);
116   absl::SetProgramUsageMessage(
117       absl::StrFormat("A sandbox testing tool.\n"
118                       "Usage: %1$s [OPTION] -- CMD [ARGS]...",
119                       program_name));
120 
121   std::vector<std::string> args;
122   {
123     const std::vector<char*> parsed_argv = absl::ParseCommandLine(argc, argv);
124     args.assign(parsed_argv.begin() + 1, parsed_argv.end());
125   }
126   absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo);
127   absl::InitializeLog();
128 
129   if (args.empty()) {
130     absl::FPrintF(stderr, "Missing command to execute\n");
131     return EXIT_FAILURE;
132   }
133 
134   const std::string& sandboxee = args[0];
135 
136   // Pass the current environ pointer, depending on the flag.
137   std::vector<std::string> envp;
138   if (absl::GetFlag(FLAGS_sandbox2tool_keep_env)) {
139     envp = sandbox2::util::CharPtrArray(environ).ToStringVector();
140   }
141   auto executor = std::make_unique<sandbox2::Executor>(sandboxee, args, envp);
142 
143   sapi::file_util::fileops::FDCloser recv_fd1;
144   if (absl::GetFlag(FLAGS_sandbox2tool_redirect_fd1)) {
145     // Make the sandboxed process' fd be available as fd in the current process.
146     recv_fd1 = sapi::file_util::fileops::FDCloser(
147         executor->ipc()->ReceiveFd(STDOUT_FILENO));
148   }
149 
150   executor
151       ->limits()
152       // Kill sandboxed processes with a signal (SIGXFSZ) if it writes more than
153       // this to the file-system.
154       ->set_rlimit_fsize(
155           absl::GetFlag(FLAGS_sandbox2tool_file_size_creation_limit))
156       // An arbitrary, but empirically safe value.
157       .set_rlimit_nofile(1024U)
158       .set_walltime_limit(
159           absl::Seconds(absl::GetFlag(FLAGS_sandbox2tool_walltime_timeout)));
160 
161   if (absl::GetFlag(FLAGS_sandbox2tool_cpu_timeout) > 0) {
162     executor->limits()->set_rlimit_cpu(
163         absl::GetFlag(FLAGS_sandbox2tool_cpu_timeout));
164   }
165 
166   sandbox2::PolicyBuilder builder;
167   builder.AddPolicyOnSyscall(__NR_tee, {KILL});
168   builder.DefaultAction(sandbox2::AllowAllSyscalls());
169 
170   if (absl::GetFlag(FLAGS_sandbox2tool_need_networking)) {
171     builder.Allow(sandbox2::UnrestrictedNetworking());
172   }
173   if (absl::GetFlag(FLAGS_sandbox2tool_mount_tmp)) {
174     builder.AddTmpfs("/tmp", /*size=*/4ULL << 20 /* 4 MiB */);
175   }
176 
177   std::string mounts_string =
178       absl::GetFlag(FLAGS_sandbox2tool_additional_bind_mounts);
179   if (!mounts_string.empty()) {
180     for (absl::string_view mount : absl::StrSplit(mounts_string, ',')) {
181       std::vector<std::string> source_target = absl::StrSplit(mount, "=>");
182       std::string source = source_target[0];
183       std::string target = source_target[0];
184       if (source_target.size() == 2) {
185         target = source_target[1];
186       }
187       struct stat64 st;
188       PCHECK(stat64(source.c_str(), &st) != -1)
189           << "could not stat additional mount " << source;
190       if ((st.st_mode & S_IFMT) == S_IFDIR) {
191         builder.AddDirectoryAt(source, target, true);
192       } else {
193         builder.AddFileAt(source, target, true);
194       }
195     }
196   }
197 
198   if (absl::GetFlag(FLAGS_sandbox2tool_resolve_and_add_libraries)) {
199     builder.AddLibrariesForBinary(sandboxee);
200   }
201 
202   auto policy = builder.BuildOrDie();
203 
204   // Current working directory.
205   if (!absl::GetFlag(FLAGS_sandbox2tool_cwd).empty()) {
206     executor->set_cwd(absl::GetFlag(FLAGS_sandbox2tool_cwd));
207   }
208 
209   // Instantiate the Sandbox2 object with policies and executors.
210   sandbox2::Sandbox2 s2(std::move(executor), std::move(policy));
211 
212   // This sandbox runs asynchronously. If there was no OutputFD() loop receiving
213   // the data from the recv_fd1, one could just use Sandbox2::Run().
214   if (s2.RunAsync()) {
215     if (absl::GetFlag(FLAGS_sandbox2tool_pause_resume)) {
216       sleep(3);
217       kill(s2.pid(), SIGSTOP);
218       sleep(3);
219       s2.set_walltime_limit(absl::Seconds(3));
220       kill(s2.pid(), SIGCONT);
221     } else if (absl::GetFlag(FLAGS_sandbox2tool_pause_kill)) {
222       sleep(3);
223       kill(s2.pid(), SIGSTOP);
224       sleep(1);
225       kill(s2.pid(), SIGKILL);
226       sleep(1);
227     } else if (absl::GetFlag(FLAGS_sandbox2tool_dump_stack)) {
228       sleep(1);
229       s2.DumpStackTrace();
230     } else if (absl::GetFlag(FLAGS_sandbox2tool_redirect_fd1)) {
231       OutputFD(recv_fd1.get());
232       // We couldn't receive more data from the sandboxee's STDOUT_FILENO, but
233       // the process could still be running. Kill it unconditionally. A correct
234       // final status code will be reported instead of Result::EXTERNAL_KILL.
235       s2.Kill();
236     }
237   } else {
238     LOG(ERROR) << "Sandbox failed";
239   }
240 
241   sandbox2::Result result = s2.AwaitResult();
242 
243   if (result.final_status() != sandbox2::Result::OK) {
244     LOG(ERROR) << "Sandbox error: " << result.ToString();
245     return 2;  // sandbox violation
246   }
247   auto code = result.reason_code();
248   if (code) {
249     LOG(ERROR) << "Child exited with non-zero " << code;
250     return 1;  // normal child error
251   }
252 
253   return EXIT_SUCCESS;
254 }
255