• 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 // 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*>(&regs->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