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 <fcntl.h>
16
17 #include <memory>
18 #include <string>
19 #include <thread> // NOLINT(build/c++11)
20
21 #include "benchmark/benchmark.h"
22 #include "gmock/gmock.h"
23 #include "gtest/gtest.h"
24 #include "absl/status/status.h"
25 #include "absl/status/statusor.h"
26 #include "absl/time/clock.h"
27 #include "absl/time/time.h"
28 #include "sandboxed_api/examples/stringop/stringop-sapi.sapi.h"
29 #include "sandboxed_api/examples/stringop/stringop_params.pb.h"
30 #include "sandboxed_api/examples/sum/sum-sapi.sapi.h"
31 #include "sandboxed_api/sandbox.h"
32 #include "sandboxed_api/testing.h"
33 #include "sandboxed_api/transaction.h"
34 #include "sandboxed_api/util/status_matchers.h"
35
36 namespace sapi {
37 namespace {
38
39 using ::sapi::IsOk;
40 using ::sapi::StatusIs;
41 using ::testing::Eq;
42 using ::testing::Gt;
43 using ::testing::HasSubstr;
44
45 // Functions that will be used during the benchmarks:
46
47 // Function causing no load in the sandboxee.
InvokeNop(Sandbox * sandbox)48 absl::Status InvokeNop(Sandbox* sandbox) {
49 StringopApi api(sandbox);
50 return api.nop();
51 }
52
53 // Function that makes use of our special protobuf (de)-serialization code
54 // inside SAPI (including the back-synchronization of the structure).
InvokeStringReversal(Sandbox * sandbox)55 absl::Status InvokeStringReversal(Sandbox* sandbox) {
56 StringopApi api(sandbox);
57 stringop::StringReverse proto;
58 proto.set_input("Hello");
59 absl::StatusOr<v::Proto<stringop::StringReverse>> pp(
60 v::Proto<stringop::StringReverse>::FromMessage(proto));
61 SAPI_RETURN_IF_ERROR(pp.status());
62 SAPI_ASSIGN_OR_RETURN(int return_code, api.pb_reverse_string(pp->PtrBoth()));
63 TRANSACTION_FAIL_IF_NOT(return_code != 0, "pb_reverse_string failed");
64 SAPI_ASSIGN_OR_RETURN(auto pb_result, pp->GetMessage());
65 TRANSACTION_FAIL_IF_NOT(pb_result.output() == "olleH", "Incorrect output");
66 return absl::OkStatus();
67 }
68
69 // Benchmark functions:
70
71 // Restart SAPI sandbox by letting the sandbox object go out of scope.
72 // Minimal case for measuring the minimum overhead of restarting the sandbox.
BenchmarkSandboxRestartOverhead(benchmark::State & state)73 void BenchmarkSandboxRestartOverhead(benchmark::State& state) {
74 for (auto _ : state) {
75 BasicTransaction st(std::make_unique<StringopSandbox>());
76 // Invoke nop() to make sure that our sandbox is running.
77 EXPECT_THAT(st.Run(InvokeNop), IsOk());
78 }
79 }
80 BENCHMARK(BenchmarkSandboxRestartOverhead);
81
BenchmarkSandboxRestartForkserverOverhead(benchmark::State & state)82 void BenchmarkSandboxRestartForkserverOverhead(benchmark::State& state) {
83 sapi::BasicTransaction st(std::make_unique<StringopSandbox>());
84 for (auto _ : state) {
85 EXPECT_THAT(st.Run(InvokeNop), IsOk());
86 EXPECT_THAT(st.sandbox()->Restart(true), IsOk());
87 }
88 }
89 BENCHMARK(BenchmarkSandboxRestartForkserverOverhead);
90
BenchmarkSandboxRestartForkserverOverheadForced(benchmark::State & state)91 void BenchmarkSandboxRestartForkserverOverheadForced(benchmark::State& state) {
92 sapi::BasicTransaction st{std::make_unique<StringopSandbox>()};
93 for (auto _ : state) {
94 EXPECT_THAT(st.Run(InvokeNop), IsOk());
95 EXPECT_THAT(st.sandbox()->Restart(false), IsOk());
96 }
97 }
98 BENCHMARK(BenchmarkSandboxRestartForkserverOverheadForced);
99
100 // Reuse the sandbox. Used to measure the overhead of the call invocation.
BenchmarkCallOverhead(benchmark::State & state)101 void BenchmarkCallOverhead(benchmark::State& state) {
102 BasicTransaction st(std::make_unique<StringopSandbox>());
103 for (auto _ : state) {
104 EXPECT_THAT(st.Run(InvokeNop), IsOk());
105 }
106 }
107 BENCHMARK(BenchmarkCallOverhead);
108
109 // Make use of protobufs.
BenchmarkProtobufHandling(benchmark::State & state)110 void BenchmarkProtobufHandling(benchmark::State& state) {
111 BasicTransaction st(std::make_unique<StringopSandbox>());
112 for (auto _ : state) {
113 EXPECT_THAT(st.Run(InvokeStringReversal), IsOk());
114 }
115 }
116 BENCHMARK(BenchmarkProtobufHandling);
117
118 // Measure overhead of synchronizing data.
BenchmarkIntDataSynchronization(benchmark::State & state)119 void BenchmarkIntDataSynchronization(benchmark::State& state) {
120 auto sandbox = std::make_unique<StringopSandbox>();
121 ASSERT_THAT(sandbox->Init(), IsOk());
122
123 long current_val = 0; // NOLINT
124 v::Long long_var;
125 // Allocate remote memory.
126 ASSERT_THAT(sandbox->Allocate(&long_var, false), IsOk());
127
128 for (auto _ : state) {
129 // Write current_val to the process.
130 long_var.SetValue(current_val);
131 EXPECT_THAT(sandbox->TransferToSandboxee(&long_var), IsOk());
132 // Invalidate value to make sure that the next call
133 // is not simply a noop.
134 long_var.SetValue(-1);
135 // Read value back.
136 EXPECT_THAT(sandbox->TransferFromSandboxee(&long_var), IsOk());
137 EXPECT_THAT(long_var.GetValue(), Eq(current_val));
138
139 current_val++;
140 }
141 }
142 BENCHMARK(BenchmarkIntDataSynchronization);
143
144 // Test whether stack trace generation works.
TEST(SapiTest,HasStackTraces)145 TEST(SapiTest, HasStackTraces) {
146 SKIP_SANITIZERS_AND_COVERAGE;
147
148 auto sandbox = std::make_unique<StringopSandbox>();
149 ASSERT_THAT(sandbox->Init(), IsOk());
150 StringopApi api(sandbox.get());
151 EXPECT_THAT(api.violate(), StatusIs(absl::StatusCode::kUnavailable));
152 const auto& result = sandbox->AwaitResult();
153 EXPECT_THAT(
154 result.GetStackTrace(),
155 // Check that at least one expected function is present in the stack
156 // trace.
157 // Note: Typically, in optimized builds, on x86-64, only
158 // "ViolateIndirect()" will be present in the stack trace. On POWER, all
159 // stack frames are generated, but libunwind will be unable to track
160 // "ViolateIndirect()" on the stack and instead show its IP as zero.
161 AnyOf(HasSubstr("ViolateIndirect"), HasSubstr("violate")));
162 EXPECT_THAT(result.final_status(), Eq(sandbox2::Result::VIOLATION));
163 }
164
165 // Various tests:
166
167 // Leaks a file descriptor inside the sandboxee.
LeakFileDescriptor(sapi::Sandbox * sandbox,const char * path)168 int LeakFileDescriptor(sapi::Sandbox* sandbox, const char* path) {
169 int raw_fd = open(path, O_RDONLY);
170 sapi::v::Fd fd(raw_fd); // Takes ownership of the raw fd.
171 EXPECT_THAT(sandbox->TransferToSandboxee(&fd), IsOk());
172 // We want to leak the remote FD. The local FD will still be closed.
173 fd.OwnRemoteFd(false);
174 return fd.GetRemoteFd();
175 }
176
177 // Make sure that restarting the sandboxee works (= fresh set of FDs).
TEST(SandboxTest,RestartSandboxFD)178 TEST(SandboxTest, RestartSandboxFD) {
179 sapi::BasicTransaction st{std::make_unique<SumSandbox>()};
180
181 auto test_body = [](sapi::Sandbox* sandbox) -> absl::Status {
182 // Open some FDs and check their value.
183 int first_remote_fd = LeakFileDescriptor(sandbox, "/proc/self/exe");
184 EXPECT_THAT(LeakFileDescriptor(sandbox, "/proc/self/exe"),
185 Eq(first_remote_fd + 1));
186 SAPI_RETURN_IF_ERROR(sandbox->Restart(false));
187 // We should have a fresh sandbox now = FDs open previously should be
188 // closed now.
189 EXPECT_THAT(LeakFileDescriptor(sandbox, "/proc/self/exe"),
190 Eq(first_remote_fd));
191 return absl::OkStatus();
192 };
193
194 EXPECT_THAT(st.Run(test_body), IsOk());
195 }
196
TEST(SandboxTest,RestartTransactionSandboxFD)197 TEST(SandboxTest, RestartTransactionSandboxFD) {
198 sapi::BasicTransaction st{std::make_unique<SumSandbox>()};
199
200 int fd_no = -1;
201 ASSERT_THAT(st.Run([&fd_no](sapi::Sandbox* sandbox) -> absl::Status {
202 fd_no = LeakFileDescriptor(sandbox, "/proc/self/exe");
203 return absl::OkStatus();
204 }),
205 IsOk());
206
207 EXPECT_THAT(st.Run([fd_no](sapi::Sandbox* sandbox) -> absl::Status {
208 EXPECT_THAT(LeakFileDescriptor(sandbox, "/proc/self/exe"), Gt(fd_no));
209 return absl::OkStatus();
210 }),
211 IsOk());
212
213 EXPECT_THAT(st.Restart(), IsOk());
214
215 EXPECT_THAT(st.Run([fd_no](sapi::Sandbox* sandbox) -> absl::Status {
216 EXPECT_THAT(LeakFileDescriptor(sandbox, "/proc/self/exe"), Eq(fd_no));
217 return absl::OkStatus();
218 }),
219 IsOk());
220 }
221
222 // Make sure we can recover from a dying sandbox.
TEST(SandboxTest,RestartSandboxAfterCrash)223 TEST(SandboxTest, RestartSandboxAfterCrash) {
224 SumSandbox sandbox;
225 ASSERT_THAT(sandbox.Init(), IsOk());
226 SumApi api(&sandbox);
227
228 // Crash the sandbox.
229 EXPECT_THAT(api.crash(), StatusIs(absl::StatusCode::kUnavailable));
230 EXPECT_THAT(api.sum(1, 2).status(), StatusIs(absl::StatusCode::kUnavailable));
231 EXPECT_THAT(sandbox.AwaitResult().final_status(),
232 Eq(sandbox2::Result::SIGNALED));
233
234 // Restart the sandbox.
235 ASSERT_THAT(sandbox.Restart(false), IsOk());
236
237 // The sandbox should now be responsive again.
238 SAPI_ASSERT_OK_AND_ASSIGN(int result, api.sum(1, 2));
239 EXPECT_THAT(result, Eq(3));
240 }
241
TEST(SandboxTest,RestartSandboxAfterViolation)242 TEST(SandboxTest, RestartSandboxAfterViolation) {
243 SumSandbox sandbox;
244 ASSERT_THAT(sandbox.Init(), IsOk());
245 SumApi api(&sandbox);
246
247 // Violate the sandbox policy.
248 EXPECT_THAT(api.violate(), StatusIs(absl::StatusCode::kUnavailable));
249 EXPECT_THAT(api.sum(1, 2).status(), StatusIs(absl::StatusCode::kUnavailable));
250 EXPECT_THAT(sandbox.AwaitResult().final_status(),
251 Eq(sandbox2::Result::VIOLATION));
252
253 // Restart the sandbox.
254 ASSERT_THAT(sandbox.Restart(false), IsOk());
255
256 // The sandbox should now be responsive again.
257 SAPI_ASSERT_OK_AND_ASSIGN(int result, api.sum(1, 2));
258 EXPECT_THAT(result, Eq(3));
259 }
260
TEST(SandboxTest,NoRaceInAwaitResult)261 TEST(SandboxTest, NoRaceInAwaitResult) {
262 StringopSandbox sandbox;
263 ASSERT_THAT(sandbox.Init(), IsOk());
264 StringopApi api(&sandbox);
265
266 EXPECT_THAT(api.violate(), StatusIs(absl::StatusCode::kUnavailable));
267 absl::SleepFor(absl::Milliseconds(200)); // Make sure we lose the race
268 const auto& result = sandbox.AwaitResult();
269 EXPECT_THAT(result.final_status(), Eq(sandbox2::Result::VIOLATION));
270 }
271
TEST(SandboxTest,NoRaceInConcurrentTerminate)272 TEST(SandboxTest, NoRaceInConcurrentTerminate) {
273 SumSandbox sandbox;
274 ASSERT_THAT(sandbox.Init(), IsOk());
275 SumApi api(&sandbox);
276 std::thread th([&sandbox] {
277 // Sleep so that the call already starts
278 absl::SleepFor(absl::Seconds(1));
279 sandbox.Terminate(/*attempt_graceful_exit=*/false);
280 });
281 EXPECT_THAT(api.sleep_for_sec(10), StatusIs(absl::StatusCode::kUnavailable));
282 th.join();
283 const auto& result = sandbox.AwaitResult();
284 EXPECT_THAT(result.final_status(), Eq(sandbox2::Result::EXTERNAL_KILL));
285 }
286
TEST(SandboxTest,UseUnotifyMonitor)287 TEST(SandboxTest, UseUnotifyMonitor) {
288 SumSandbox sandbox;
289 ASSERT_THAT(sandbox.Init(/*use_unotify_monitor=*/true), IsOk());
290 SumApi api(&sandbox);
291
292 // Violate the sandbox policy.
293 EXPECT_THAT(api.violate(), StatusIs(absl::StatusCode::kUnavailable));
294 EXPECT_THAT(api.sum(1, 2).status(), StatusIs(absl::StatusCode::kUnavailable));
295 EXPECT_THAT(sandbox.AwaitResult().final_status(),
296 Eq(sandbox2::Result::VIOLATION));
297
298 // Restart the sandbox.
299 ASSERT_THAT(sandbox.Restart(false), IsOk());
300
301 // The sandbox should now be responsive again.
302 SAPI_ASSERT_OK_AND_ASSIGN(int result, api.sum(1, 2));
303 EXPECT_THAT(result, Eq(3));
304 }
305
306 } // namespace
307 } // namespace sapi
308