xref: /aosp_15_r20/external/sandboxed-api/sandboxed_api/sandbox2/examples/tool/sandbox2tool.cc (revision ec63e07ab9515d95e79c211197c445ef84cefa6a)
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/allow_all_syscalls.h"
51 #include "sandboxed_api/sandbox2/executor.h"
52 #include "sandboxed_api/sandbox2/ipc.h"
53 #include "sandboxed_api/sandbox2/limits.h"
54 #include "sandboxed_api/sandbox2/policy.h"
55 #include "sandboxed_api/sandbox2/policybuilder.h"
56 #include "sandboxed_api/sandbox2/result.h"
57 #include "sandboxed_api/sandbox2/sandbox2.h"
58 #include "sandboxed_api/sandbox2/util.h"
59 #include "sandboxed_api/sandbox2/util/bpf_helper.h"
60 #include "sandboxed_api/util/fileops.h"
61 
62 ABSL_FLAG(bool, sandbox2tool_keep_env, false,
63           "Keep current environment variables");
64 ABSL_FLAG(bool, sandbox2tool_redirect_fd1, false,
65           "Receive sandboxee's STDOUT_FILENO (1) and output it locally");
66 ABSL_FLAG(bool, sandbox2tool_need_networking, false,
67           "If user namespaces are enabled, this option will enable "
68           "networking (by disabling the network namespace)");
69 ABSL_FLAG(bool, sandbox2tool_mount_tmp, false,
70           "If user namespaces are enabled, this option will create a tmpfs "
71           "mount at /tmp");
72 ABSL_FLAG(bool, sandbox2tool_resolve_and_add_libraries, false,
73           "resolve and mount the required libraries for the sandboxee");
74 ABSL_FLAG(bool, sandbox2tool_pause_resume, false,
75           "Pause the process after 3 seconds, resume after the subsequent "
76           "3 seconds, kill it after the final 3 seconds");
77 ABSL_FLAG(bool, sandbox2tool_pause_kill, false,
78           "Pause the process after 3 seconds, then SIGKILL it.");
79 ABSL_FLAG(bool, sandbox2tool_dump_stack, false,
80           "Dump the stack trace one second after the process is running.");
81 ABSL_FLAG(uint64_t, sandbox2tool_cpu_timeout, 60U,
82           "CPU timeout in seconds (if > 0)");
83 ABSL_FLAG(uint64_t, sandbox2tool_walltime_timeout, 60U,
84           "Wall-time timeout in seconds (if >0)");
85 ABSL_FLAG(uint64_t, sandbox2tool_file_size_creation_limit, 1024U,
86           "Maximum size of created files");
87 ABSL_FLAG(std::string, sandbox2tool_cwd, "/",
88           "If not empty, chdir to the directory before sandboxed");
89 ABSL_FLAG(std::string, sandbox2tool_additional_bind_mounts, "",
90           "If user namespaces are enabled, this option will add additional "
91           "bind mounts. Mounts are separated by comma and can optionally "
92           "specify a target using \"=>\" "
93           "(e.g. \"/usr,/bin,/lib,/tmp/foo=>/etc/passwd\")");
94 
95 namespace {
96 
OutputFD(int fd)97 void OutputFD(int fd) {
98   for (;;) {
99     char buf[4096];
100     ssize_t rlen = read(fd, buf, sizeof(buf));
101     if (rlen < 1) {
102       break;
103     }
104     LOG(INFO) << "Received from the sandboxee (FD STDOUT_FILENO (1)):"
105               << "\n========================================\n"
106               << std::string(buf, rlen)
107               << "\n========================================\n";
108   }
109 }
110 
111 }  // namespace
112 
main(int argc,char * argv[])113 int main(int argc, char* argv[]) {
114   const std::string program_name = sapi::file_util::fileops::Basename(argv[0]);
115   absl::SetProgramUsageMessage(
116       absl::StrFormat("A sandbox testing tool.\n"
117                       "Usage: %1$s [OPTION] -- CMD [ARGS]...",
118                       program_name));
119 
120   std::vector<std::string> args;
121   {
122     const std::vector<char*> parsed_argv = absl::ParseCommandLine(argc, argv);
123     args.assign(parsed_argv.begin() + 1, parsed_argv.end());
124   }
125   absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo);
126   absl::InitializeLog();
127 
128   if (args.empty()) {
129     absl::FPrintF(stderr, "Missing command to execute\n");
130     return EXIT_FAILURE;
131   }
132 
133   const std::string& sandboxee = args[0];
134 
135   // Pass the current environ pointer, depending on the flag.
136   std::vector<std::string> envp;
137   if (absl::GetFlag(FLAGS_sandbox2tool_keep_env)) {
138     envp = sandbox2::util::CharPtrArray(environ).ToStringVector();
139   }
140   auto executor = std::make_unique<sandbox2::Executor>(sandboxee, args, envp);
141 
142   sapi::file_util::fileops::FDCloser recv_fd1;
143   if (absl::GetFlag(FLAGS_sandbox2tool_redirect_fd1)) {
144     // Make the sandboxed process' fd be available as fd in the current process.
145     recv_fd1 = sapi::file_util::fileops::FDCloser(
146         executor->ipc()->ReceiveFd(STDOUT_FILENO));
147   }
148 
149   executor
150       ->limits()
151       // Kill sandboxed processes with a signal (SIGXFSZ) if it writes more than
152       // this to the file-system.
153       ->set_rlimit_fsize(
154           absl::GetFlag(FLAGS_sandbox2tool_file_size_creation_limit))
155       // An arbitrary, but empirically safe value.
156       .set_rlimit_nofile(1024U)
157       .set_walltime_limit(
158           absl::Seconds(absl::GetFlag(FLAGS_sandbox2tool_walltime_timeout)));
159 
160   if (absl::GetFlag(FLAGS_sandbox2tool_cpu_timeout) > 0) {
161     executor->limits()->set_rlimit_cpu(
162         absl::GetFlag(FLAGS_sandbox2tool_cpu_timeout));
163   }
164 
165   sandbox2::PolicyBuilder builder;
166   builder.AddPolicyOnSyscall(__NR_tee, {KILL});
167   builder.DefaultAction(sandbox2::AllowAllSyscalls());
168 
169   if (absl::GetFlag(FLAGS_sandbox2tool_need_networking)) {
170     builder.AllowUnrestrictedNetworking();
171   }
172   if (absl::GetFlag(FLAGS_sandbox2tool_mount_tmp)) {
173     builder.AddTmpfs("/tmp", /*size=*/4ULL << 20 /* 4 MiB */);
174   }
175 
176   std::string mounts_string =
177       absl::GetFlag(FLAGS_sandbox2tool_additional_bind_mounts);
178   if (!mounts_string.empty()) {
179     for (absl::string_view mount : absl::StrSplit(mounts_string, ',')) {
180       std::vector<std::string> source_target = absl::StrSplit(mount, "=>");
181       std::string source = source_target[0];
182       std::string target = source_target[0];
183       if (source_target.size() == 2) {
184         target = source_target[1];
185       }
186       struct stat64 st;
187       PCHECK(stat64(source.c_str(), &st) != -1)
188           << "could not stat additional mount " << source;
189       if ((st.st_mode & S_IFMT) == S_IFDIR) {
190         builder.AddDirectoryAt(source, target, true);
191       } else {
192         builder.AddFileAt(source, target, true);
193       }
194     }
195   }
196 
197   if (absl::GetFlag(FLAGS_sandbox2tool_resolve_and_add_libraries)) {
198     builder.AddLibrariesForBinary(sandboxee);
199   }
200 
201   auto policy = builder.BuildOrDie();
202 
203   // Current working directory.
204   if (!absl::GetFlag(FLAGS_sandbox2tool_cwd).empty()) {
205     executor->set_cwd(absl::GetFlag(FLAGS_sandbox2tool_cwd));
206   }
207 
208   // Instantiate the Sandbox2 object with policies and executors.
209   sandbox2::Sandbox2 s2(std::move(executor), std::move(policy));
210 
211   // This sandbox runs asynchronously. If there was no OutputFD() loop receiving
212   // the data from the recv_fd1, one could just use Sandbox2::Run().
213   if (s2.RunAsync()) {
214     if (absl::GetFlag(FLAGS_sandbox2tool_pause_resume)) {
215       sleep(3);
216       kill(s2.pid(), SIGSTOP);
217       sleep(3);
218       s2.set_walltime_limit(absl::Seconds(3));
219       kill(s2.pid(), SIGCONT);
220     } else if (absl::GetFlag(FLAGS_sandbox2tool_pause_kill)) {
221       sleep(3);
222       kill(s2.pid(), SIGSTOP);
223       sleep(1);
224       kill(s2.pid(), SIGKILL);
225       sleep(1);
226     } else if (absl::GetFlag(FLAGS_sandbox2tool_dump_stack)) {
227       sleep(1);
228       s2.DumpStackTrace();
229     } else if (absl::GetFlag(FLAGS_sandbox2tool_redirect_fd1)) {
230       OutputFD(recv_fd1.get());
231       // We couldn't receive more data from the sandboxee's STDOUT_FILENO, but
232       // the process could still be running. Kill it unconditionally. A correct
233       // final status code will be reported instead of Result::EXTERNAL_KILL.
234       s2.Kill();
235     }
236   } else {
237     LOG(ERROR) << "Sandbox failed";
238   }
239 
240   sandbox2::Result result = s2.AwaitResult();
241 
242   if (result.final_status() != sandbox2::Result::OK) {
243     LOG(ERROR) << "Sandbox error: " << result.ToString();
244     return 2;  // sandbox violation
245   }
246   auto code = result.reason_code();
247   if (code) {
248     LOG(ERROR) << "Child exited with non-zero " << code;
249     return 1;  // normal child error
250   }
251 
252   return EXIT_SUCCESS;
253 }
254