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