-
Notifications
You must be signed in to change notification settings - Fork 190
/
Copy pathstack_trace.cc
346 lines (297 loc) · 11.6 KB
/
stack_trace.cc
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
// 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 <sys/resource.h>
#include <syscall.h>
#include <memory>
#include <utility>
#include <vector>
#include <glog/logging.h>
#include "absl/cleanup/cleanup.h"
#include "sandboxed_api/util/flag.h"
#include "absl/memory/memory.h"
#include "absl/status/status.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/strip.h"
#include "libcap/include/sys/capability.h"
#include "sandboxed_api/config.h"
#include "sandboxed_api/sandbox2/comms.h"
#include "sandboxed_api/sandbox2/executor.h"
#include "sandboxed_api/sandbox2/ipc.h"
#include "sandboxed_api/sandbox2/limits.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/sandbox2.h"
#include "sandboxed_api/sandbox2/unwind/unwind.h"
#include "sandboxed_api/sandbox2/unwind/unwind.pb.h"
#include "sandboxed_api/sandbox2/util/bpf_helper.h"
#include "sandboxed_api/util/fileops.h"
#include "sandboxed_api/util/path.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);
}
} // 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 Mounts& mounts);
static absl::StatusOr<UnwindResult> LaunchLibunwindSandbox(
const Regs* regs, const Mounts& mounts);
};
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 Mounts& mounts) {
PolicyBuilder builder;
builder
// Use the mounttree of the original executable as starting point.
.SetMounts(mounts)
.AllowOpen()
.AllowRead()
.AllowWrite()
.AllowSyscall(__NR_close)
.AllowMmap()
.AllowExit()
.AllowHandleSignals()
// libunwind
.AllowStat()
.AllowSyscall(__NR_lseek)
#ifdef __NR__llseek
.AllowSyscall(__NR__llseek) // Newer glibc on PPC
#endif
.AllowSyscall(__NR_mincore)
.AllowSyscall(__NR_mprotect)
.AllowSyscall(__NR_munmap)
.AllowSyscall(__NR_pipe2)
// Symbolizer
.AllowSyscall(__NR_brk)
.AllowSyscall(__NR_clock_gettime)
// Other
.AllowSyscall(__NR_dup)
.AllowSyscall(__NR_fcntl)
.AllowSyscall(__NR_getpid)
.AllowSyscall(__NR_gettid)
.AllowSyscall(__NR_madvise)
// Required for our ptrace replacement.
.AddPolicyOnSyscall(
__NR_process_vm_readv,
{
// The pid technically is a 64bit int, however Linux usually uses
// max 16 bit, so we are fine with comparing only 32 bits here.
ARG_32(0),
JEQ32(static_cast<unsigned int>(target_pid), ALLOW),
JEQ32(static_cast<unsigned int>(1), ALLOW),
})
// 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);
// Add all possible libraries without the need of parsing the binary
// or /proc/pid/maps.
for (const auto& library_path : {
"/usr/lib",
"/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";
}
}
SAPI_ASSIGN_OR_RETURN(std::unique_ptr<Policy> policy, builder.TryBuild());
policy->AllowUnsafeKeepCapabilities({CAP_SYS_PTRACE});
// Use no special namespace flags when cloning. We will join an existing
// user namespace and will unshare() afterwards (See forkserver.cc).
policy->GetNamespace()->clone_flags_ = 0;
return std::move(policy);
}
absl::StatusOr<UnwindResult> StackTracePeer::LaunchLibunwindSandbox(
const Regs* regs, const Mounts& mounts) {
const pid_t pid = regs->pid();
// Tell executor to use this special internal mode.
std::vector<std::string> argv;
std::vector<std::string> envp;
// We're not using absl::make_unique here as we're a friend of this specific
// constructor and using make_unique won't work.
auto executor = absl::WrapUnique(new Executor(pid));
executor->limits()
->set_rlimit_as(RLIM64_INFINITY)
.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");
}
// The exe_path will have a mountable path of the application, even if it was
// removed.
// Resolve app_path backing file.
std::string exe_path = 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, mounts));
Sandbox2 sandbox(std::move(executor), std::move(policy));
VLOG(1) << "Running libunwind sandbox";
sandbox.RunAsync();
Comms* comms = sandbox.comms();
UnwindSetup msg;
msg.set_pid(pid);
msg.set_regs(reinterpret_cast<const char*>(®s->user_regs_),
sizeof(regs->user_regs_));
msg.set_default_max_frames(kDefaultMaxFrames);
absl::Cleanup kill_sandbox = [&sandbox]() {
sandbox.Kill();
sandbox.AwaitResult().IgnoreResult();
};
if (!comms->SendProtoBuf(msg)) {
return absl::InternalError("Sending libunwind setup message failed");
}
absl::Status status;
if (!comms->RecvStatus(&status)) {
return absl::InternalError(
"Receiving status from libunwind sandbox failed");
}
if (!status.ok()) {
return 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 result;
}
absl::StatusOr<std::vector<std::string>> GetStackTrace(const Regs* regs,
const Mounts& mounts) {
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");
}
// Show a warning if sandboxed libunwind is requested but we're running in
// an ASAN/coverage build (= we can't use sandboxed libunwind).
if (const bool coverage_enabled =
getenv("COVERAGE") != nullptr;
absl::GetFlag(FLAGS_sandbox_libunwind_crash_handler) &&
(sapi::sanitizers::IsAny() || coverage_enabled)) {
LOG_IF(WARNING, sapi::sanitizers::IsAny())
<< "Sanitizer build, using non-sandboxed libunwind";
LOG_IF(WARNING, coverage_enabled)
<< "Coverage build, using non-sandboxed libunwind";
return UnsafeGetStackTrace(regs->pid());
}
if (!absl::GetFlag(FLAGS_sandbox_libunwind_crash_handler)) {
return UnsafeGetStackTrace(regs->pid());
}
SAPI_ASSIGN_OR_RETURN(UnwindResult res,
StackTracePeer::LaunchLibunwindSandbox(regs, mounts));
return std::vector<std::string>(res.stacktrace().begin(),
res.stacktrace().end());
}
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