• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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(&copy).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