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