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