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/policybuilder.h"
16
17 #include <syscall.h>
18 #include <unistd.h>
19
20 #include <cerrno>
21 #include <initializer_list>
22 #include <memory>
23 #include <string>
24 #include <tuple>
25 #include <utility>
26 #include <vector>
27
28 #include "gmock/gmock.h"
29 #include "gtest/gtest.h"
30 #include "absl/status/status.h"
31 #include "absl/strings/string_view.h"
32 #include "sandboxed_api/sandbox2/allowlists/unrestricted_networking.h"
33 #include "sandboxed_api/sandbox2/policy.h"
34 #include "sandboxed_api/sandbox2/util/bpf_helper.h"
35 #include "sandboxed_api/util/fileops.h"
36 #include "sandboxed_api/util/path.h"
37 #include "sandboxed_api/util/status_matchers.h"
38
39 namespace sandbox2 {
40
41 class PolicyBuilderPeer {
42 public:
PolicyBuilderPeer(PolicyBuilder * builder)43 explicit PolicyBuilderPeer(PolicyBuilder* builder) : builder_{builder} {}
44
policy_size() const45 int policy_size() const { return builder_->user_policy_.size(); }
46
47 private:
48 PolicyBuilder* builder_;
49 };
50
51 namespace {
52
53 namespace fileops = ::sapi::file_util::fileops;
54
55 using ::sapi::IsOk;
56 using ::sapi::StatusIs;
57 using ::testing::Eq;
58 using ::testing::Lt;
59 using ::testing::StartsWith;
60 using ::testing::StrEq;
61
TEST(PolicyBuilderTest,Testpolicy_size)62 TEST(PolicyBuilderTest, Testpolicy_size) {
63 ssize_t last_size = 0;
64 PolicyBuilder builder;
65 PolicyBuilderPeer builder_peer{&builder};
66
67 auto assert_increased = [&last_size, &builder_peer]() {
68 ASSERT_THAT(last_size, Lt(builder_peer.policy_size()));
69 last_size = builder_peer.policy_size();
70 };
71
72 auto assert_same = [&last_size, &builder_peer]() {
73 ASSERT_THAT(last_size, Eq(builder_peer.policy_size()));
74 };
75
76 // clang-format off
77 assert_same();
78
79 builder.AllowSyscall(__NR_chroot); assert_increased();
80 builder.AllowSyscall(__NR_chroot); assert_same();
81 builder.AllowSyscall(__NR_umask); assert_increased();
82 builder.AllowSyscall(__NR_umask); assert_same();
83 builder.AllowSyscall(__NR_chroot); assert_same();
84 builder.AllowSyscall(__NR_chroot); assert_same();
85
86 builder.AllowSystemMalloc(); assert_increased();
87 builder.AllowSyscall(__NR_munmap); assert_same();
88 builder.BlockSyscallWithErrno(__NR_munmap, 1); assert_same();
89 builder.BlockSyscallWithErrno(__NR_openat, 1);
90 assert_increased();
91
92 builder.AllowTCGETS(); assert_increased();
93 builder.AllowTCGETS(); assert_same();
94 builder.AllowTCGETS(); assert_same();
95
96 builder.AddPolicyOnSyscall(__NR_fchmod, { ALLOW }); assert_increased();
97 builder.AddPolicyOnSyscall(__NR_fchmod, { ALLOW }); assert_increased();
98
99 builder.AddPolicyOnSyscalls({ __NR_fchmod, __NR_chdir }, { ALLOW });
100 assert_increased();
101 builder.AddPolicyOnSyscalls({ __NR_fchmod, __NR_chdir }, { ALLOW });
102 assert_increased();
103
104 // This might change in the future if we implement an optimization.
105 builder.AddPolicyOnSyscall(__NR_umask, { ALLOW }); assert_increased();
106 builder.AddPolicyOnSyscall(__NR_umask, { ALLOW }); assert_increased();
107
108 // None of the namespace functions should alter the seccomp policy.
109 builder.AddFile("/usr/bin/find"); assert_same();
110 builder.AddDirectory("/bin"); assert_same();
111 builder.AddTmpfs("/tmp", /*size=*/4ULL << 20 /* 4 MiB */); assert_same();
112 builder.UseForkServerSharedNetNs(); assert_same();
113 builder.Allow(UnrestrictedNetworking()); assert_same();
114 // clang-format on
115 }
116
TEST(PolicyBuilderTest,ApisWithPathValidation)117 TEST(PolicyBuilderTest, ApisWithPathValidation) {
118 const std::initializer_list<std::pair<absl::string_view, absl::StatusCode>>
119 kTestCases = {
120 {"/a", absl::StatusCode::kOk},
121 {"/a/b/c/d", absl::StatusCode::kOk},
122 {"/a/b/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", absl::StatusCode::kOk},
123 {"", absl::StatusCode::kInvalidArgument},
124 // Fails because we reject paths starting with '..'
125 {"..", absl::StatusCode::kInvalidArgument},
126 {"..a", absl::StatusCode::kInvalidArgument},
127 {"../a", absl::StatusCode::kInvalidArgument},
128 // Fails because is not absolute
129 {"a", absl::StatusCode::kInvalidArgument},
130 {"a/b", absl::StatusCode::kInvalidArgument},
131 {"a/b/c", absl::StatusCode::kInvalidArgument},
132 // Fails because '..' in path
133 {"/a/b/c/../d", absl::StatusCode::kInvalidArgument},
134 // Fails because '.' in path
135 {"/a/b/c/./d", absl::StatusCode::kInvalidArgument},
136 // Fails because '//' in path
137 {"/a/b/c//d", absl::StatusCode::kInvalidArgument},
138 // Fails because path ends with '/'
139 {"/a/b/c/d/", absl::StatusCode::kInvalidArgument},
140 };
141 for (auto const& [path, status] : kTestCases) {
142 EXPECT_THAT(PolicyBuilder().AddFile(path).TryBuild(), StatusIs(status));
143 EXPECT_THAT(PolicyBuilder().AddFileAt(path, "/input").TryBuild(),
144 StatusIs(status));
145 EXPECT_THAT(PolicyBuilder().AddDirectory(path).TryBuild(),
146 StatusIs(status));
147 EXPECT_THAT(PolicyBuilder().AddDirectoryAt(path, "/input").TryBuild(),
148 StatusIs(status));
149 }
150
151 // Fails because it attempts to mount to '/' inside
152 EXPECT_THAT(PolicyBuilder().AddFile("/").TryBuild(),
153 StatusIs(absl::StatusCode::kInternal));
154 EXPECT_THAT(PolicyBuilder().AddDirectory("/").TryBuild(),
155 StatusIs(absl::StatusCode::kInternal));
156
157 // Succeeds because it attempts to mount to '/' inside
158 EXPECT_THAT(PolicyBuilder().AddFileAt("/a", "/input").TryBuild(), IsOk());
159 EXPECT_THAT(PolicyBuilder().AddDirectoryAt("/a", "/input").TryBuild(),
160 IsOk());
161 }
162
TEST(PolicyBuilderTest,TestAnchorPathAbsolute)163 TEST(PolicyBuilderTest, TestAnchorPathAbsolute) {
164 const std::initializer_list<
165 std::tuple<absl::string_view, absl::string_view, std::string>>
166 kTestCases = {
167 // relative_path is empty:
168 {"", "/base", ""}, // Error: relative path is empty
169 {"", "", ""}, // Error: relative path is empty
170
171 // relative_path is absolute:
172 {"/a/b/c/d", "/base", "/a/b/c/d"},
173 {"/a/../../../../../etc/passwd", "/base",
174 "/a/../../../../../etc/passwd"},
175 {"/a/b/c/d", "base", "/a/b/c/d"},
176 {"/a/b/c/d", "", "/a/b/c/d"},
177
178 // base is absolute:
179 {"a/b/c/d", "/base", "/base/a/b/c/d"},
180 {"a/b/c/d/", "/base", "/base/a/b/c/d"},
181 {"a/b/c//d", "/base", "/base/a/b/c/d"},
182 {"a/b/../d/", "/base", "/base/a/d"},
183 {"a/./b/c/", "/base", "/base/a/b/c"},
184 {"./a/b/c/", "/base", "/base/a/b/c"},
185 {"..foobar", "/base", "/base/..foobar"},
186 {"a/b/c/d", "/base/../foo/bar",
187 "/foo/bar/a/b/c/d"}, // Not an error because base is trusted.
188 {"a/../../d/", "/base", ""}, // Error: can't guarantee anchor
189 {"../a/b/c/", "/base", ""}, // Error: can't guarantee anchor
190 {"..", "/base", ""}, // Error: can't guarantee anchor
191
192 // base path is empty:
193 {"a/b/c", "", fileops::GetCWD() + "/a/b/c"},
194 {"a/../../../../c", "", ""}, // Error: can't guarantee anchor
195
196 // base is relative:
197 {"a/b/c/d", "base", fileops::GetCWD() + "/base/a/b/c/d"},
198 {"a/b/c/d/", "base", fileops::GetCWD() + "/base/a/b/c/d"},
199 {"a/b/c//d", "base", fileops::GetCWD() + "/base/a/b/c/d"},
200 {"a/b/../d/", "base", fileops::GetCWD() + "/base/a/d"},
201 {"a/./b/c/", "base", fileops::GetCWD() + "/base/a/b/c"},
202 {"./a/b/c/", "base", fileops::GetCWD() + "/base/a/b/c"},
203 {"..foobar", "base", fileops::GetCWD() + "/base/..foobar"},
204 {"a/../../d/", "base", ""}, // Error: can't guarantee anchor
205 {"../a/b/c/", "base", ""}, // Error: can't guarantee anchor
206 {"..", "base", ""}, // Error: can't guarantee anchor
207 {"a/b/c", ".base/foo/", fileops::GetCWD() + "/.base/foo/a/b/c"},
208 {"a/b/c", "./base/foo", fileops::GetCWD() + "/base/foo/a/b/c"},
209 {"a/b/c", "base/foo/../bar", fileops::GetCWD() + "/base/bar/a/b/c"},
210 {"a/b/c", "base/foo//bar/",
211 fileops::GetCWD() + "/base/foo/bar/a/b/c"},
212 {"a/b/c", "..base/foo", fileops::GetCWD() + "/..base/foo/a/b/c"},
213 {"a/b/c", "../base/foo",
214 sapi::file::CleanPath(fileops::GetCWD() + "/../base/foo/a/b/c")},
215 {"a/b/c", "..",
216 sapi::file::CleanPath(fileops::GetCWD() + "/../a/b/c")},
217 };
218 for (auto const& [path, base, result] : kTestCases) {
219 EXPECT_THAT(PolicyBuilder::AnchorPathAbsolute(path, base), StrEq(result));
220 }
221 }
222
TEST(PolicyBuilderTest,TestCanOnlyBuildOnce)223 TEST(PolicyBuilderTest, TestCanOnlyBuildOnce) {
224 PolicyBuilder b;
225 ASSERT_THAT(b.TryBuild(), IsOk());
226 EXPECT_THAT(b.TryBuild(), StatusIs(absl::StatusCode::kFailedPrecondition,
227 "Can only build policy once."));
228 }
229
TEST(PolicyBuilderTest,TestIsCopyable)230 TEST(PolicyBuilderTest, TestIsCopyable) {
231 PolicyBuilder builder;
232 builder.AllowSyscall(__NR_getpid);
233
234 PolicyBuilder copy = builder;
235 ASSERT_EQ(PolicyBuilderPeer(©).policy_size(),
236 PolicyBuilderPeer(&builder).policy_size());
237
238 // Both can be built.
239 EXPECT_THAT(builder.TryBuild(), IsOk());
240 EXPECT_THAT(copy.TryBuild(), IsOk());
241 }
242
TEST(PolicyBuilderTest,CannotBypassBpf)243 TEST(PolicyBuilderTest, CannotBypassBpf) {
244 PolicyBuilder builder;
245 builder.AddPolicyOnSyscall(__NR_bpf, {ALLOW})
246 .BlockSyscallWithErrno(__NR_bpf, ENOENT);
247 EXPECT_THAT(builder.TryBuild(), Not(IsOk()));
248 }
249
TEST(PolicyBuilderTest,CannotBypassAfterAllowSafeBpf)250 TEST(PolicyBuilderTest, CannotBypassAfterAllowSafeBpf) {
251 PolicyBuilder builder;
252 builder.AllowSafeBpf().AddPolicyOnSyscall(__NR_bpf, {ALLOW});
253 EXPECT_THAT(builder.TryBuild(), Not(IsOk()));
254 }
255
TEST(PolicyBuilderTest,CannotBypassPtrace)256 TEST(PolicyBuilderTest, CannotBypassPtrace) {
257 PolicyBuilder builder;
258 builder.AddPolicyOnSyscall(__NR_ptrace, {ALLOW})
259 .BlockSyscallWithErrno(__NR_ptrace, ENOENT);
260 EXPECT_THAT(builder.TryBuild(), Not(IsOk()));
261 }
262
TEST(PolicyBuilderTest,AddPolicyOnSyscallsNoEmptyList)263 TEST(PolicyBuilderTest, AddPolicyOnSyscallsNoEmptyList) {
264 PolicyBuilder builder;
265 builder.AddPolicyOnSyscalls({}, {ALLOW});
266 EXPECT_THAT(builder.TryBuild(), StatusIs(absl::StatusCode::kInvalidArgument));
267 }
268
TEST(PolicyBuilderTest,AddPolicyOnSyscallJumpOutOfBounds)269 TEST(PolicyBuilderTest, AddPolicyOnSyscallJumpOutOfBounds) {
270 PolicyBuilder builder;
271 builder.AddPolicyOnSyscall(__NR_write,
272 {BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 1, 2, 0)});
273 EXPECT_THAT(builder.TryBuild(), StatusIs(absl::StatusCode::kInvalidArgument));
274 }
275
TEST(PolicyBuilderTest,TestAllowLlvmCoverage)276 TEST(PolicyBuilderTest, TestAllowLlvmCoverage) {
277 ASSERT_THAT(setenv("COVERAGE", "1", 0), Eq(0));
278 ASSERT_THAT(setenv("COVERAGE_DIR", "/tmp", 0), Eq(0));
279 PolicyBuilder builder;
280 builder.AllowLlvmCoverage();
281 EXPECT_THAT(builder.TryBuild(), IsOk());
282 ASSERT_THAT(unsetenv("COVERAGE"), Eq(0));
283 ASSERT_THAT(unsetenv("COVERAGE_DIR"), Eq(0));
284 }
285
TEST(PolicyBuilderTest,TestAllowLlvmCoverageWithoutCoverageDir)286 TEST(PolicyBuilderTest, TestAllowLlvmCoverageWithoutCoverageDir) {
287 ASSERT_THAT(setenv("COVERAGE", "1", 0), Eq(0));
288 PolicyBuilder builder;
289 builder.AllowLlvmCoverage();
290 EXPECT_THAT(builder.TryBuild(), IsOk());
291 ASSERT_THAT(unsetenv("COVERAGE"), Eq(0));
292 }
293 } // namespace
294 } // namespace sandbox2
295