aboutsummaryrefslogtreecommitdiff
path: root/sandboxed_api/sandbox2/stack_trace.cc
blob: 11f9b6f9c31dff9501afa6d15620d6e7b90477b3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Implementation of the sandbox2::StackTrace class.

#include "sandboxed_api/sandbox2/stack_trace.h"

#include <fcntl.h>
#include <sys/stat.h>
#include <syscall.h>
#include <unistd.h>

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "absl/cleanup/cleanup.h"
#include "absl/flags/flag.h"
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/memory/memory.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/strings/strip.h"
#include "absl/time/time.h"
#include "sandboxed_api/config.h"
#include "sandboxed_api/sandbox2/comms.h"
#include "sandboxed_api/sandbox2/executor.h"
#include "sandboxed_api/sandbox2/limits.h"
#include "sandboxed_api/sandbox2/mounts.h"
#include "sandboxed_api/sandbox2/namespace.h"
#include "sandboxed_api/sandbox2/policy.h"
#include "sandboxed_api/sandbox2/policybuilder.h"
#include "sandboxed_api/sandbox2/regs.h"
#include "sandboxed_api/sandbox2/result.h"
#include "sandboxed_api/sandbox2/unwind/unwind.h"
#include "sandboxed_api/sandbox2/unwind/unwind.pb.h"
#include "sandboxed_api/util/fileops.h"
#include "sandboxed_api/util/path.h"
#include "sandboxed_api/util/raw_logging.h"
#include "sandboxed_api/util/status_macros.h"

ABSL_FLAG(bool, sandbox_disable_all_stack_traces, false,
          "Completely disable stack trace collection for sandboxees");

ABSL_FLAG(bool, sandbox_libunwind_crash_handler, true,
          "Sandbox libunwind when handling violations (preferred)");

namespace sandbox2 {
namespace {

namespace file = ::sapi::file;
namespace file_util = ::sapi::file_util;

// Similar to GetStackTrace() but without using the sandbox to isolate
// libunwind.
absl::StatusOr<std::vector<std::string>> UnsafeGetStackTrace(pid_t pid) {
  LOG(WARNING) << "Using non-sandboxed libunwind";
  return RunLibUnwindAndSymbolizer(pid, kDefaultMaxFrames);
}

bool IsSameFile(const std::string& path, const std::string& other) {
  struct stat buf, other_buf;
  if (stat(path.c_str(), &buf) != 0 || stat(other.c_str(), &other_buf) != 0) {
    return false;
  }
  return buf.st_dev == other_buf.st_dev && buf.st_ino == other_buf.st_ino &&
         buf.st_mode == other_buf.st_mode &&
         buf.st_nlink == other_buf.st_nlink && buf.st_uid == other_buf.st_uid &&
         buf.st_gid == other_buf.st_gid && buf.st_rdev == other_buf.st_rdev &&
         buf.st_size == other_buf.st_size &&
         buf.st_blksize == other_buf.st_blksize &&
         buf.st_blocks == other_buf.st_blocks;
}

}  // namespace

class StackTracePeer {
 public:
  static absl::StatusOr<std::unique_ptr<Policy>> GetPolicy(
      pid_t target_pid, const std::string& maps_file,
      const std::string& app_path, const std::string& exe_path,
      const Namespace* ns, bool uses_custom_forkserver);

  static absl::StatusOr<std::vector<std::string>> LaunchLibunwindSandbox(
      const Regs* regs, const Namespace* ns, bool uses_custom_forkserver,
      int recursion_depth);
};

absl::StatusOr<std::unique_ptr<Policy>> StackTracePeer::GetPolicy(
    pid_t target_pid, const std::string& maps_file, const std::string& app_path,
    const std::string& exe_path, const Namespace* ns,
    bool uses_custom_forkserver) {
  PolicyBuilder builder;
  if (uses_custom_forkserver) {
    // Custom forkserver just forks, the binary is loaded outside of the
    // sandboxee's mount namespace.
    // Add all possible libraries without the need of parsing the binary
    // or /proc/pid/maps.
    for (const auto& library_path : {
             "/usr/lib64",
             "/usr/lib",
             "/lib64",
             "/lib",
         }) {
      if (access(library_path, F_OK) != -1) {
        VLOG(1) << "Adding library folder '" << library_path << "'";
        builder.AddDirectory(library_path);
      } else {
        VLOG(1) << "Could not add library folder '" << library_path
                << "' as it does not exist";
      }
    }
  } else {
    // Use the mounttree of the original executable.
    CHECK(ns != nullptr);
    Mounts mounts = ns->mounts();
    mounts.Remove("/proc").IgnoreError();
    mounts.Remove(app_path).IgnoreError();
    builder.SetMounts(std::move(mounts));
  }
  builder.AllowOpen()
      .AllowRead()
      .AllowWrite()
      .AllowSyscall(__NR_close)
      .AllowExit()
      .AllowHandleSignals()
      .AllowTcMalloc()
      .AllowSystemMalloc()
      // for Comms:RecvFD
      .AllowSyscall(__NR_recvmsg)

      // libunwind
      .AllowMmapWithoutExec()
      .AllowStat()
      .AllowSyscall(__NR_lseek)
#ifdef __NR__llseek
      .AllowSyscall(__NR__llseek)  // Newer glibc on PPC
#endif
      .AllowSyscall(__NR_mincore)
      .AllowSyscall(__NR_munmap)
      .AllowPipe()

      // Symbolizer
      .AllowSyscall(__NR_brk)
      .AllowTime()

      // Other
      .AllowDup()
      .AllowSafeFcntl()
      .AllowGetPIDs()

      // Required for our ptrace replacement.
      .TrapPtrace()

      // Add proc maps.
      .AddFileAt(maps_file,
                 file::JoinPath("/proc", absl::StrCat(target_pid), "maps"))
      .AddFileAt(maps_file,
                 file::JoinPath("/proc", absl::StrCat(target_pid), "task",
                                absl::StrCat(target_pid), "maps"))

      // Add the binary itself.
      .AddFileAt(exe_path, app_path)
      .AllowLlvmCoverage();

  return builder.TryBuild();
}

namespace internal {
SandboxPeer::SpawnFn SandboxPeer::spawn_fn_ = nullptr;
}  // namespace internal

absl::StatusOr<std::vector<std::string>> StackTracePeer::LaunchLibunwindSandbox(
    const Regs* regs, const Namespace* ns, bool uses_custom_forkserver,
    int recursion_depth) {
  const pid_t pid = regs->pid();

  sapi::file_util::fileops::FDCloser memory_fd(
      open(absl::StrCat("/proc/", pid, "/mem").c_str(), O_RDONLY));
  if (memory_fd.get() == -1) {
    return absl::InternalError("Opening sandboxee process memory failed");
  }
  // Tell executor to use this special internal mode. Using `new` to access a
  // non-public constructor.
  auto executor = absl::WrapUnique(new Executor(pid, recursion_depth));

  executor->limits()->set_rlimit_cpu(10).set_walltime_limit(absl::Seconds(5));

  // Temporary directory used to provide files from /proc to the unwind sandbox.
  char unwind_temp_directory_template[] = "/tmp/.sandbox2_unwind_XXXXXX";
  char* unwind_temp_directory = mkdtemp(unwind_temp_directory_template);
  if (!unwind_temp_directory) {
    return absl::InternalError(
        "Could not create temporary directory for unwinding");
  }
  struct UnwindTempDirectoryCleanup {
    ~UnwindTempDirectoryCleanup() {
      file_util::fileops::DeleteRecursively(capture);
    }
    char* capture;
  } cleanup{unwind_temp_directory};

  // Copy over important files from the /proc directory as we can't mount them.
  const std::string unwind_temp_maps_path =
      file::JoinPath(unwind_temp_directory, "maps");

  if (!file_util::fileops::CopyFile(
          file::JoinPath("/proc", absl::StrCat(pid), "maps"),
          unwind_temp_maps_path, 0400)) {
    return absl::InternalError("Could not copy maps file");
  }

  // Get path to the binary.
  // app_path contains the path like it is also in /proc/pid/maps. It is
  // relative to the sandboxee's mount namespace. If it is not existing
  // (anymore) it will have a ' (deleted)' suffix.
  std::string app_path;
  std::string proc_pid_exe = file::JoinPath("/proc", absl::StrCat(pid), "exe");
  if (!file_util::fileops::ReadLinkAbsolute(proc_pid_exe, &app_path)) {
    return absl::InternalError("Could not obtain absolute path to the binary");
  }

  std::string exe_path;
  if (IsSameFile(app_path, proc_pid_exe)) {
    exe_path = app_path;
  } else {
    // The exe_path will have a mountable path of the application, even if it
    // was removed. Resolve app_path backing file.
    exe_path = ns ? ns->mounts().ResolvePath(app_path).value_or("") : "";
  }

  if (exe_path.empty()) {
    // File was probably removed.
    LOG(WARNING) << "File was removed, using /proc/pid/exe.";
    app_path = std::string(absl::StripSuffix(app_path, " (deleted)"));
    // Create a copy of /proc/pid/exe, mount that one.
    exe_path = file::JoinPath(unwind_temp_directory, "exe");
    if (!file_util::fileops::CopyFile(proc_pid_exe, exe_path, 0700)) {
      return absl::InternalError("Could not copy /proc/pid/exe");
    }
  }

  VLOG(1) << "Resolved binary: " << app_path << " / " << exe_path;

  // Add mappings for the binary (as they might not have been added due to the
  // forkserver).
  SAPI_ASSIGN_OR_RETURN(
      std::unique_ptr<Policy> policy,
      StackTracePeer::GetPolicy(pid, unwind_temp_maps_path, app_path, exe_path,
                                ns, uses_custom_forkserver));

  VLOG(1) << "Running libunwind sandbox";
  auto sandbox =
      internal::SandboxPeer::Spawn(std::move(executor), std::move(policy));
  Comms* comms = sandbox->comms();

  UnwindSetup msg;
  msg.set_pid(pid);
  msg.set_regs(reinterpret_cast<const char*>(&regs->user_regs_),
               sizeof(regs->user_regs_));
  msg.set_default_max_frames(kDefaultMaxFrames);

  absl::Cleanup kill_sandbox = [&sandbox]() {
    sandbox->Kill();
    sandbox2::Result result = sandbox->AwaitResult();
    LOG(INFO) << "Libunwind execution status: " << result.ToString();
  };

  if (!comms->SendProtoBuf(msg)) {
    return absl::InternalError("Sending libunwind setup message failed");
  }
  if (!comms->SendFD(memory_fd.get())) {
    return absl::InternalError("Sending sandboxee's memory fd failed");
  }
  absl::Status status;
  if (!comms->RecvStatus(&status)) {
    return absl::InternalError(
        "Receiving status from libunwind sandbox failed");
  }
  SAPI_RETURN_IF_ERROR(status);

  UnwindResult result;
  if (!comms->RecvProtoBuf(&result)) {
    return absl::InternalError("Receiving libunwind result failed");
  }

  std::move(kill_sandbox).Cancel();

  auto sandbox_result = sandbox->AwaitResult();

  LOG(INFO) << "Libunwind execution status: " << sandbox_result.ToString();

  if (sandbox_result.final_status() != Result::OK) {
    return absl::InternalError(
        absl::StrCat("libunwind sandbox did not finish properly: ",
                     sandbox_result.ToString()));
  }

  return std::vector<std::string>(result.stacktrace().begin(),
                                  result.stacktrace().end());
}

absl::StatusOr<std::vector<std::string>> GetStackTrace(
    const Regs* regs, const Namespace* ns, bool uses_custom_forkserver,
    int recursion_depth) {
  if (absl::GetFlag(FLAGS_sandbox_disable_all_stack_traces)) {
    return absl::UnavailableError("Stacktraces disabled");
  }
  if (!regs) {
    return absl::InvalidArgumentError(
        "Could not obtain stacktrace, regs == nullptr");
  }

  if (!absl::GetFlag(FLAGS_sandbox_libunwind_crash_handler)) {
    return UnsafeGetStackTrace(regs->pid());
  }

  // Show a warning if sandboxed libunwind is requested but we're running in
  // a sanitizer build (= we can't use sandboxed libunwind).
  if (sapi::sanitizers::IsAny()) {
    LOG(WARNING) << "Sanitizer build, using non-sandboxed libunwind";
    return UnsafeGetStackTrace(regs->pid());
  }

  return StackTracePeer::LaunchLibunwindSandbox(
      regs, ns, uses_custom_forkserver, recursion_depth);
}

std::vector<std::string> CompactStackTrace(
    const std::vector<std::string>& stack_trace) {
  std::vector<std::string> compact_trace;
  compact_trace.reserve(stack_trace.size() / 2);
  const std::string* prev = nullptr;
  int seen = 0;
  auto add_repeats = [&compact_trace](int seen) {
    if (seen != 0) {
      compact_trace.push_back(
          absl::StrCat("(previous frame repeated ", seen, " times)"));
    }
  };
  for (const auto& frame : stack_trace) {
    if (prev && frame == *prev) {
      ++seen;
    } else {
      prev = &frame;
      add_repeats(seen);
      seen = 0;
      compact_trace.push_back(frame);
    }
  }
  add_repeats(seen);
  return compact_trace;
}

}  // namespace sandbox2