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 #include "sandboxed_api/sandbox2/stack_trace.h"
16
17 #include <sys/types.h>
18
19 #include <cstdio>
20 #include <functional>
21 #include <memory>
22 #include <string>
23 #include <utility>
24 #include <vector>
25
26 #include "gmock/gmock.h"
27 #include "gtest/gtest.h"
28 #include "absl/base/log_severity.h"
29 #include "absl/flags/declare.h"
30 #include "absl/flags/flag.h"
31 #include "absl/flags/reflection.h"
32 #include "absl/log/check.h"
33 #include "absl/log/scoped_mock_log.h"
34 #include "absl/strings/str_cat.h"
35 #include "absl/time/time.h"
36 #include "sandboxed_api/sandbox2/executor.h"
37 #include "sandboxed_api/sandbox2/global_forkclient.h"
38 #include "sandboxed_api/sandbox2/policy.h"
39 #include "sandboxed_api/sandbox2/policybuilder.h"
40 #include "sandboxed_api/sandbox2/result.h"
41 #include "sandboxed_api/sandbox2/sandbox2.h"
42 #include "sandboxed_api/testing.h"
43 #include "sandboxed_api/util/fileops.h"
44 #include "sandboxed_api/util/status_matchers.h"
45
46 ABSL_DECLARE_FLAG(bool, sandbox_libunwind_crash_handler);
47
48 namespace sandbox2 {
49
50 class StackTraceTestPeer {
51 public:
GetInstance()52 static StackTraceTestPeer& GetInstance() {
53 static auto* peer = new StackTraceTestPeer();
54 return *peer;
55 }
SpawnFn(std::unique_ptr<Executor> executor,std::unique_ptr<Policy> policy)56 std::unique_ptr<internal::SandboxPeer> SpawnFn(
57 std::unique_ptr<Executor> executor, std::unique_ptr<Policy> policy) {
58 if (crash_unwind_) {
59 policy = PolicyBuilder().BuildOrDie();
60 crash_unwind_ = false;
61 }
62 return old_spawn_fn_(std::move(executor), std::move(policy));
63 }
ReplaceSpawnFn()64 void ReplaceSpawnFn() {
65 old_spawn_fn_ = internal::SandboxPeer::spawn_fn_;
66 internal::SandboxPeer::spawn_fn_ = +[](std::unique_ptr<Executor> executor,
67 std::unique_ptr<Policy> policy) {
68 return GetInstance().SpawnFn(std::move(executor), std::move(policy));
69 };
70 }
RestoreSpawnFn()71 void RestoreSpawnFn() { internal::SandboxPeer::spawn_fn_ = old_spawn_fn_; }
CrashNextUnwind()72 void CrashNextUnwind() { crash_unwind_ = true; }
73
74 private:
75 internal::SandboxPeer::SpawnFn old_spawn_fn_;
76 bool crash_unwind_ = false;
77 };
78
79 struct ScopedSpawnOverride {
ScopedSpawnOverridesandbox2::ScopedSpawnOverride80 ScopedSpawnOverride() { StackTraceTestPeer::GetInstance().ReplaceSpawnFn(); }
~ScopedSpawnOverridesandbox2::ScopedSpawnOverride81 ~ScopedSpawnOverride() { StackTraceTestPeer::GetInstance().RestoreSpawnFn(); }
82 ScopedSpawnOverride(ScopedSpawnOverride&&) = delete;
83 ScopedSpawnOverride& operator=(ScopedSpawnOverride&&) = delete;
84 ScopedSpawnOverride(const ScopedSpawnOverride&) = delete;
85 ScopedSpawnOverride& operator=(const ScopedSpawnOverride&) = delete;
86
CrashNextUnwindsandbox2::ScopedSpawnOverride87 void CrashNextUnwind() {
88 StackTraceTestPeer::GetInstance().CrashNextUnwind();
89 }
90 };
91
92 namespace {
93
94 namespace file_util = ::sapi::file_util;
95 using ::sapi::CreateDefaultPermissiveTestPolicy;
96 using ::sapi::GetTestSourcePath;
97 using ::testing::_;
98 using ::testing::Contains;
99 using ::testing::ContainsRegex;
100 using ::testing::ElementsAre;
101 using ::testing::Eq;
102 using ::testing::IsEmpty;
103 using ::testing::StartsWith;
104
105 struct TestCase {
106 std::string testname = "CrashMe";
107 int testno = 1;
108 int testmode = 1;
109 int final_status = Result::SIGNALED;
110 std::string function_name = testname;
111 std::string full_function_description = "CrashMe(char)";
112 std::function<void(PolicyBuilder*)> modify_policy;
113 absl::Duration wall_time_limit = absl::ZeroDuration();
114 };
115
116 class StackTraceTest : public ::testing::TestWithParam<TestCase> {};
117
118 // Test that symbolization of stack traces works.
SymbolizationWorksCommon(TestCase param)119 void SymbolizationWorksCommon(TestCase param) {
120 const std::string path = GetTestSourcePath("sandbox2/testcases/symbolize");
121 std::vector<std::string> args = {path, absl::StrCat(param.testno),
122 absl::StrCat(param.testmode)};
123
124 PolicyBuilder builder = CreateDefaultPermissiveTestPolicy(path);
125 if (param.modify_policy) {
126 param.modify_policy(&builder);
127 }
128 SAPI_ASSERT_OK_AND_ASSIGN(auto policy, builder.TryBuild());
129
130 Sandbox2 s2(std::make_unique<Executor>(path, args), std::move(policy));
131 ASSERT_TRUE(s2.RunAsync());
132 s2.set_walltime_limit(param.wall_time_limit);
133 auto result = s2.AwaitResult();
134
135 EXPECT_THAT(result.final_status(), Eq(param.final_status));
136 EXPECT_THAT(result.stack_trace(), Contains(StartsWith(param.function_name)));
137 // Check that demangling works as well.
138 EXPECT_THAT(result.stack_trace(),
139 Contains(StartsWith(param.full_function_description)));
140 EXPECT_THAT(result.stack_trace(), Contains(StartsWith("RunTest")));
141 EXPECT_THAT(result.stack_trace(), Contains(StartsWith("main")));
142 if (param.testmode == 2) {
143 EXPECT_THAT(result.stack_trace(),
144 Contains(StartsWith("RecurseA")).Times(5));
145 EXPECT_THAT(result.stack_trace(),
146 Contains(StartsWith("RecurseB")).Times(5));
147 } else if (param.testmode == 3) {
148 EXPECT_THAT(result.stack_trace(), Contains(StartsWith("LibCallCallback")));
149 EXPECT_THAT(result.stack_trace(), Contains(StartsWith("LibRecurse")));
150 EXPECT_THAT(result.stack_trace(),
151 Contains(StartsWith("LibRecurseA")).Times(5));
152 EXPECT_THAT(result.stack_trace(),
153 Contains(StartsWith("LibRecurseB")).Times(5));
154 }
155 }
156
SymbolizationWorksWithModifiedPolicy(std::function<void (PolicyBuilder *)> modify_policy)157 void SymbolizationWorksWithModifiedPolicy(
158 std::function<void(PolicyBuilder*)> modify_policy) {
159 TestCase test_case;
160 test_case.modify_policy = std::move(modify_policy);
161 SymbolizationWorksCommon(test_case);
162 }
163
TEST_P(StackTraceTest,SymbolizationWorksNonSandboxedLibunwind)164 TEST_P(StackTraceTest, SymbolizationWorksNonSandboxedLibunwind) {
165 absl::FlagSaver fs;
166 absl::SetFlag(&FLAGS_sandbox_libunwind_crash_handler, false);
167
168 SymbolizationWorksCommon(GetParam());
169 }
170
TEST_P(StackTraceTest,SymbolizationWorksSandboxedLibunwind)171 TEST_P(StackTraceTest, SymbolizationWorksSandboxedLibunwind) {
172 absl::FlagSaver fs;
173 absl::SetFlag(&FLAGS_sandbox_libunwind_crash_handler, true);
174
175 SymbolizationWorksCommon(GetParam());
176 }
177
TEST(StackTraceTest,SymbolizationWorksSandboxedLibunwindProcDirMounted)178 TEST(StackTraceTest, SymbolizationWorksSandboxedLibunwindProcDirMounted) {
179 absl::FlagSaver fs;
180 absl::SetFlag(&FLAGS_sandbox_libunwind_crash_handler, true);
181
182 SymbolizationWorksWithModifiedPolicy(
183 [](PolicyBuilder* builder) { builder->AddDirectory("/proc"); });
184 }
185
TEST(StackTraceTest,SymbolizationWorksSandboxedLibunwindProcFileMounted)186 TEST(StackTraceTest, SymbolizationWorksSandboxedLibunwindProcFileMounted) {
187 absl::FlagSaver fs;
188 absl::SetFlag(&FLAGS_sandbox_libunwind_crash_handler, true);
189
190 SymbolizationWorksWithModifiedPolicy([](PolicyBuilder* builder) {
191 builder->AddFile("/proc/sys/vm/overcommit_memory");
192 });
193 }
194
TEST(StackTraceTest,SymbolizationWorksSandboxedLibunwindSysDirMounted)195 TEST(StackTraceTest, SymbolizationWorksSandboxedLibunwindSysDirMounted) {
196 absl::FlagSaver fs;
197 absl::SetFlag(&FLAGS_sandbox_libunwind_crash_handler, true);
198
199 SymbolizationWorksWithModifiedPolicy(
200 [](PolicyBuilder* builder) { builder->AddDirectory("/sys"); });
201 }
202
TEST(StackTraceTest,SymbolizationWorksSandboxedLibunwindSysFileMounted)203 TEST(StackTraceTest, SymbolizationWorksSandboxedLibunwindSysFileMounted) {
204 absl::FlagSaver fs;
205 absl::SetFlag(&FLAGS_sandbox_libunwind_crash_handler, true);
206
207 SymbolizationWorksWithModifiedPolicy([](PolicyBuilder* builder) {
208 builder->AddFile("/sys/devices/system/cpu/online");
209 });
210 }
211
FileCountInDirectory(const std::string & path)212 size_t FileCountInDirectory(const std::string& path) {
213 std::vector<std::string> fds;
214 std::string error;
215 CHECK(file_util::fileops::ListDirectoryEntries(path, &fds, &error));
216 return fds.size();
217 }
218
TEST(StackTraceTest,ForkEnterNsLibunwindDoesNotLeakFDs)219 TEST(StackTraceTest, ForkEnterNsLibunwindDoesNotLeakFDs) {
220 absl::FlagSaver fs;
221 absl::SetFlag(&FLAGS_sandbox_libunwind_crash_handler, true);
222
223 // Very first sanitization might create some fds (e.g. for initial
224 // namespaces).
225 SymbolizationWorksCommon({});
226
227 // Get list of open FDs in the global forkserver.
228 pid_t forkserver_pid = GlobalForkClient::GetPid();
229 std::string forkserver_fd_path =
230 absl::StrCat("/proc/", forkserver_pid, "/fd");
231 size_t filecount_before = FileCountInDirectory(forkserver_fd_path);
232
233 SymbolizationWorksCommon({});
234
235 EXPECT_THAT(filecount_before, Eq(FileCountInDirectory(forkserver_fd_path)));
236 }
237
TEST(StackTraceTest,CompactStackTrace)238 TEST(StackTraceTest, CompactStackTrace) {
239 EXPECT_THAT(CompactStackTrace({}), IsEmpty());
240 EXPECT_THAT(CompactStackTrace({"_start"}), ElementsAre("_start"));
241 EXPECT_THAT(CompactStackTrace({
242 "_start",
243 "main",
244 "recursive_call",
245 "recursive_call",
246 "recursive_call",
247 "tail_call",
248 }),
249 ElementsAre("_start", "main", "recursive_call",
250 "(previous frame repeated 2 times)", "tail_call"));
251 EXPECT_THAT(CompactStackTrace({
252 "_start",
253 "main",
254 "recursive_call",
255 "recursive_call",
256 "recursive_call",
257 "recursive_call",
258 }),
259 ElementsAre("_start", "main", "recursive_call",
260 "(previous frame repeated 3 times)"));
261 }
262
TEST(StackTraceTest,RecursiveStackTrace)263 TEST(StackTraceTest, RecursiveStackTrace) {
264 // Very first sandbox run will initialize spawn_fn_
265 SKIP_SANITIZERS;
266 ScopedSpawnOverride spawn_override;
267 SymbolizationWorksCommon({});
268 absl::ScopedMockLog log;
269 EXPECT_CALL(
270 log,
271 Log(absl::LogSeverity::kInfo, _,
272 ContainsRegex(
273 "Libunwind execution status: SYSCALL VIOLATION.*Stack: \\w+")));
274 spawn_override.CrashNextUnwind();
275 const std::string path = GetTestSourcePath("sandbox2/testcases/symbolize");
276 std::vector<std::string> args = {path, absl::StrCat(1), absl::StrCat(1)};
277 PolicyBuilder builder = CreateDefaultPermissiveTestPolicy(path);
278 SAPI_ASSERT_OK_AND_ASSIGN(auto policy, builder.TryBuild());
279
280 Sandbox2 s2(std::make_unique<Executor>(path, args), std::move(policy));
281 log.StartCapturingLogs();
282 ASSERT_TRUE(s2.RunAsync());
283 auto result = s2.AwaitResult();
284 EXPECT_THAT(result.final_status(), Eq(Result::SIGNALED));
285 }
286
287 INSTANTIATE_TEST_SUITE_P(
288 Instantiation, StackTraceTest,
289 ::testing::Values(
290 TestCase{
291 .testname = "CrashMe",
292 .testno = 1,
293 .final_status = Result::SIGNALED,
294 .full_function_description = "CrashMe(char)",
295 },
296 TestCase{
297 .testname = "ViolatePolicy",
298 .testno = 2,
299 .final_status = Result::VIOLATION,
300 .full_function_description = "ViolatePolicy(int)",
301 },
302 TestCase{
303 .testname = "ExitNormally",
304 .testno = 3,
305 .final_status = Result::OK,
306 .full_function_description = "ExitNormally(int)",
307 .modify_policy =
__anon6e1b7efc0702(PolicyBuilder* builder) 308 [](PolicyBuilder* builder) {
309 builder->CollectStacktracesOnExit(true);
310 },
311 },
312 TestCase{
313 .testname = "SleepForXSeconds",
314 .testno = 4,
315 .final_status = Result::TIMEOUT,
316 .full_function_description = "SleepForXSeconds(int)",
317 .wall_time_limit = absl::Seconds(1),
318 },
319 TestCase{
320 .testname = "ViolatePolicyRecursive",
321 .testno = 2,
322 .testmode = 2,
323 .final_status = Result::VIOLATION,
324 .function_name = "ViolatePolicy",
325 .full_function_description = "ViolatePolicy(int)",
326 },
327 TestCase{
328 .testname = "ViolatePolicyRecursiveLib",
329 .testno = 2,
330 .testmode = 3,
331 .final_status = Result::VIOLATION,
332 .function_name = "ViolatePolicy",
333 .full_function_description = "ViolatePolicy(int)",
334 }),
__anon6e1b7efc0802(const ::testing::TestParamInfo<TestCase>& info) 335 [](const ::testing::TestParamInfo<TestCase>& info) {
336 return info.param.testname;
337 });
338
339 } // namespace
340 } // namespace sandbox2
341