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 // Implementation of the sandbox2::StackTrace class.
16
17 #include "sandboxed_api/sandbox2/stack_trace.h"
18
19 #include <fcntl.h>
20 #include <sys/stat.h>
21 #include <syscall.h>
22 #include <unistd.h>
23
24 #include <memory>
25 #include <string>
26 #include <utility>
27 #include <vector>
28
29 #include "absl/cleanup/cleanup.h"
30 #include "absl/flags/flag.h"
31 #include "absl/log/check.h"
32 #include "absl/log/log.h"
33 #include "absl/memory/memory.h"
34 #include "absl/status/status.h"
35 #include "absl/status/statusor.h"
36 #include "absl/strings/str_cat.h"
37 #include "absl/strings/string_view.h"
38 #include "absl/strings/strip.h"
39 #include "absl/time/time.h"
40 #include "sandboxed_api/sandbox2/comms.h"
41 #include "sandboxed_api/sandbox2/executor.h"
42 #include "sandboxed_api/sandbox2/limits.h"
43 #include "sandboxed_api/sandbox2/mounts.h"
44 #include "sandboxed_api/sandbox2/namespace.h"
45 #include "sandboxed_api/sandbox2/policy.h"
46 #include "sandboxed_api/sandbox2/policybuilder.h"
47 #include "sandboxed_api/sandbox2/regs.h"
48 #include "sandboxed_api/sandbox2/result.h"
49 #include "sandboxed_api/sandbox2/unwind/unwind.pb.h"
50 #include "sandboxed_api/util/fileops.h"
51 #include "sandboxed_api/util/path.h"
52 #include "sandboxed_api/util/status_macros.h"
53
54 ABSL_FLAG(bool, sandbox_disable_all_stack_traces, false,
55 "Completely disable stack trace collection for sandboxees");
56
57 ABSL_RETIRED_FLAG(bool, sandbox_libunwind_crash_handler, true,
58 "Sandbox libunwind when handling violations (preferred)");
59
60 namespace sandbox2 {
61 namespace {
62
63 namespace file = ::sapi::file;
64 namespace file_util = ::sapi::file_util;
65
66 // Use a fake pid so that /proc/{pid}/maps etc. also exist in the new pid
67 // namespace
68 constexpr int kFakePid = 1;
69
IsSameFile(const std::string & path,const std::string & other)70 bool IsSameFile(const std::string& path, const std::string& other) {
71 struct stat buf, other_buf;
72 if (stat(path.c_str(), &buf) != 0 || stat(other.c_str(), &other_buf) != 0) {
73 return false;
74 }
75 return buf.st_dev == other_buf.st_dev && buf.st_ino == other_buf.st_ino &&
76 buf.st_mode == other_buf.st_mode &&
77 buf.st_nlink == other_buf.st_nlink && buf.st_uid == other_buf.st_uid &&
78 buf.st_gid == other_buf.st_gid && buf.st_rdev == other_buf.st_rdev &&
79 buf.st_size == other_buf.st_size &&
80 buf.st_blksize == other_buf.st_blksize &&
81 buf.st_blocks == other_buf.st_blocks;
82 }
83
84 } // namespace
85
86 class StackTracePeer {
87 public:
88 static absl::StatusOr<std::unique_ptr<Policy>> GetPolicy(
89 const std::string& maps_file, const std::string& app_path,
90 const std::string& exe_path, const Namespace* ns,
91 bool uses_custom_forkserver);
92
93 static absl::StatusOr<std::vector<std::string>> LaunchLibunwindSandbox(
94 const Regs* regs, const Namespace* ns, bool uses_custom_forkserver,
95 int recursion_depth);
96 };
97
GetPolicy(const std::string & maps_file,const std::string & app_path,const std::string & exe_path,const Namespace * ns,bool uses_custom_forkserver)98 absl::StatusOr<std::unique_ptr<Policy>> StackTracePeer::GetPolicy(
99 const std::string& maps_file, const std::string& app_path,
100 const std::string& exe_path, const Namespace* ns,
101 bool uses_custom_forkserver) {
102 PolicyBuilder builder;
103 if (uses_custom_forkserver || ns == nullptr) {
104 // Custom forkserver just forks, the binary is loaded outside of the
105 // sandboxee's mount namespace.
106 // Add all possible libraries without the need of parsing the binary
107 // or /proc/pid/maps.
108 for (const auto& library_path : {
109 "/usr/lib64",
110 "/usr/lib",
111 "/lib64",
112 "/lib",
113 }) {
114 if (access(library_path, F_OK) != -1) {
115 VLOG(1) << "Adding library folder '" << library_path << "'";
116 builder.AddDirectory(library_path);
117 } else {
118 VLOG(1) << "Could not add library folder '" << library_path
119 << "' as it does not exist";
120 }
121 }
122 } else {
123 // Use the mounttree of the original executable.
124 CHECK(ns != nullptr);
125 Mounts mounts = ns->mounts();
126 mounts.Remove("/proc").IgnoreError();
127 mounts.Remove(app_path).IgnoreError();
128 builder.SetMounts(std::move(mounts));
129 }
130 builder.AllowOpen()
131 .AllowRead()
132 .AllowWrite()
133 .AllowSyscall(__NR_close)
134 .AllowExit()
135 .AllowHandleSignals()
136 .AllowTcMalloc()
137 .AllowSystemMalloc()
138 // for Comms:RecvFD
139 .AllowSyscall(__NR_recvmsg)
140
141 // libunwind
142 .AllowMmapWithoutExec()
143 .AllowStat()
144 .AllowSyscall(__NR_lseek)
145 #ifdef __NR__llseek
146 .AllowSyscall(__NR__llseek) // Newer glibc on PPC
147 #endif
148 .AllowSyscall(__NR_mincore)
149 .AllowSyscall(__NR_munmap)
150 .AllowPipe()
151
152 // Symbolizer
153 .AllowSyscall(__NR_brk)
154 .AllowTime()
155
156 // Other
157 .AllowDup()
158 .AllowSafeFcntl()
159 .AllowGetPIDs()
160
161 // Required for our ptrace replacement.
162 .TrapPtrace()
163
164 // Add proc maps.
165 .AddFileAt(maps_file,
166 file::JoinPath("/proc", absl::StrCat(kFakePid), "maps"))
167 .AddFileAt(maps_file,
168 file::JoinPath("/proc", absl::StrCat(kFakePid), "task",
169 absl::StrCat(kFakePid), "maps"))
170
171 // Add the binary itself.
172 .AddFileAt(exe_path, app_path)
173 .AllowLlvmCoverage()
174 .AllowLlvmSanitizers();
175
176 return builder.TryBuild();
177 }
178
179 namespace internal {
180 SandboxPeer::SpawnFn SandboxPeer::spawn_fn_ = nullptr;
181 } // namespace internal
182
LaunchLibunwindSandbox(const Regs * regs,const Namespace * ns,bool uses_custom_forkserver,int recursion_depth)183 absl::StatusOr<std::vector<std::string>> StackTracePeer::LaunchLibunwindSandbox(
184 const Regs* regs, const Namespace* ns, bool uses_custom_forkserver,
185 int recursion_depth) {
186 const pid_t pid = regs->pid();
187
188 sapi::file_util::fileops::FDCloser memory_fd(
189 open(absl::StrCat("/proc/", pid, "/mem").c_str(), O_RDONLY));
190 if (memory_fd.get() == -1) {
191 return absl::InternalError("Opening sandboxee process memory failed");
192 }
193 // Tell executor to use this special internal mode. Using `new` to access a
194 // non-public constructor.
195 auto executor = absl::WrapUnique(new Executor(pid, recursion_depth));
196
197 executor->limits()->set_rlimit_cpu(10).set_walltime_limit(absl::Seconds(5));
198
199 // Temporary directory used to provide files from /proc to the unwind sandbox.
200 char unwind_temp_directory_template[] = "/tmp/.sandbox2_unwind_XXXXXX";
201 char* unwind_temp_directory = mkdtemp(unwind_temp_directory_template);
202 if (!unwind_temp_directory) {
203 return absl::InternalError(
204 "Could not create temporary directory for unwinding");
205 }
206 struct UnwindTempDirectoryCleanup {
207 ~UnwindTempDirectoryCleanup() {
208 file_util::fileops::DeleteRecursively(capture);
209 }
210 char* capture;
211 } cleanup{unwind_temp_directory};
212
213 // Copy over important files from the /proc directory as we can't mount them.
214 const std::string unwind_temp_maps_path =
215 file::JoinPath(unwind_temp_directory, "maps");
216
217 if (!file_util::fileops::CopyFile(
218 file::JoinPath("/proc", absl::StrCat(pid), "maps"),
219 unwind_temp_maps_path, 0400)) {
220 return absl::InternalError("Could not copy maps file");
221 }
222
223 // Get path to the binary.
224 // app_path contains the path like it is also in /proc/pid/maps. It is
225 // relative to the sandboxee's mount namespace. If it is not existing
226 // (anymore) it will have a ' (deleted)' suffix.
227 std::string app_path;
228 std::string proc_pid_exe = file::JoinPath("/proc", absl::StrCat(pid), "exe");
229 if (!file_util::fileops::ReadLinkAbsolute(proc_pid_exe, &app_path)) {
230 return absl::InternalError("Could not obtain absolute path to the binary");
231 }
232
233 std::string exe_path;
234 if (IsSameFile(app_path, proc_pid_exe)) {
235 exe_path = app_path;
236 } else {
237 // The exe_path will have a mountable path of the application, even if it
238 // was removed. Resolve app_path backing file.
239 exe_path = ns ? ns->mounts().ResolvePath(app_path).value_or("") : "";
240 }
241
242 if (exe_path.empty()) {
243 // File was probably removed.
244 LOG(WARNING) << "File was removed, using /proc/pid/exe.";
245 app_path = std::string(absl::StripSuffix(app_path, " (deleted)"));
246 // Create a copy of /proc/pid/exe, mount that one.
247 exe_path = file::JoinPath(unwind_temp_directory, "exe");
248 if (!file_util::fileops::CopyFile(proc_pid_exe, exe_path, 0700)) {
249 return absl::InternalError("Could not copy /proc/pid/exe");
250 }
251 }
252
253 VLOG(1) << "Resolved binary: " << app_path << " / " << exe_path;
254
255 // Add mappings for the binary (as they might not have been added due to the
256 // forkserver).
257 SAPI_ASSIGN_OR_RETURN(
258 std::unique_ptr<Policy> policy,
259 StackTracePeer::GetPolicy(unwind_temp_maps_path, app_path, exe_path, ns,
260 uses_custom_forkserver));
261
262 VLOG(1) << "Running libunwind sandbox";
263 auto sandbox =
264 internal::SandboxPeer::Spawn(std::move(executor), std::move(policy));
265 Comms* comms = sandbox->comms();
266
267 UnwindSetup msg;
268 msg.set_pid(kFakePid);
269 msg.set_regs(reinterpret_cast<const char*>(®s->user_regs_),
270 sizeof(regs->user_regs_));
271 msg.set_default_max_frames(kDefaultMaxFrames);
272
273 absl::Cleanup kill_sandbox = [&sandbox]() {
274 sandbox->Kill();
275 sandbox2::Result result = sandbox->AwaitResult();
276 LOG(INFO) << "Libunwind execution status: " << result.ToString();
277 };
278
279 if (!comms->SendProtoBuf(msg)) {
280 return absl::InternalError("Sending libunwind setup message failed");
281 }
282 if (!comms->SendFD(memory_fd.get())) {
283 return absl::InternalError("Sending sandboxee's memory fd failed");
284 }
285 absl::Status status;
286 if (!comms->RecvStatus(&status)) {
287 return absl::InternalError(
288 "Receiving status from libunwind sandbox failed");
289 }
290 SAPI_RETURN_IF_ERROR(status);
291
292 UnwindResult result;
293 if (!comms->RecvProtoBuf(&result)) {
294 return absl::InternalError("Receiving libunwind result failed");
295 }
296
297 std::move(kill_sandbox).Cancel();
298
299 auto sandbox_result = sandbox->AwaitResult();
300
301 LOG(INFO) << "Libunwind execution status: " << sandbox_result.ToString();
302
303 if (sandbox_result.final_status() != Result::OK) {
304 return absl::InternalError(
305 absl::StrCat("libunwind sandbox did not finish properly: ",
306 sandbox_result.ToString()));
307 }
308
309 return std::vector<std::string>(result.stacktrace().begin(),
310 result.stacktrace().end());
311 }
312
GetStackTrace(const Regs * regs,const Namespace * ns,bool uses_custom_forkserver,int recursion_depth)313 absl::StatusOr<std::vector<std::string>> GetStackTrace(
314 const Regs* regs, const Namespace* ns, bool uses_custom_forkserver,
315 int recursion_depth) {
316 if (absl::GetFlag(FLAGS_sandbox_disable_all_stack_traces)) {
317 return absl::UnavailableError("Stacktraces disabled");
318 }
319 if (!regs) {
320 return absl::InvalidArgumentError(
321 "Could not obtain stacktrace, regs == nullptr");
322 }
323
324 return StackTracePeer::LaunchLibunwindSandbox(
325 regs, ns, uses_custom_forkserver, recursion_depth);
326 }
327
CompactStackTrace(const std::vector<std::string> & stack_trace)328 std::vector<std::string> CompactStackTrace(
329 const std::vector<std::string>& stack_trace) {
330 std::vector<std::string> compact_trace;
331 compact_trace.reserve(stack_trace.size() / 2);
332 const std::string* prev = nullptr;
333 int seen = 0;
334 auto add_repeats = [&compact_trace](int seen) {
335 if (seen != 0) {
336 compact_trace.push_back(
337 absl::StrCat("(previous frame repeated ", seen, " times)"));
338 }
339 };
340 for (const auto& frame : stack_trace) {
341 if (prev && frame == *prev) {
342 ++seen;
343 } else {
344 prev = &frame;
345 add_repeats(seen);
346 seen = 0;
347 compact_trace.push_back(frame);
348 }
349 }
350 add_repeats(seen);
351 return compact_trace;
352 }
353
354 } // namespace sandbox2
355