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/namespace.h"
16
17 #include <unistd.h>
18
19 #include <cstdint>
20 #include <cstdlib>
21 #include <initializer_list>
22 #include <memory>
23 #include <string>
24 #include <utility>
25 #include <vector>
26
27 #include "gmock/gmock.h"
28 #include "gtest/gtest.h"
29 #include "absl/log/check.h"
30 #include "absl/status/statusor.h"
31 #include "absl/strings/str_cat.h"
32 #include "absl/strings/string_view.h"
33 #include "sandboxed_api/sandbox2/allow_all_syscalls.h"
34 #include "sandboxed_api/sandbox2/executor.h"
35 #include "sandboxed_api/sandbox2/policy.h"
36 #include "sandboxed_api/sandbox2/policybuilder.h"
37 #include "sandboxed_api/sandbox2/result.h"
38 #include "sandboxed_api/sandbox2/sandbox2.h"
39 #include "sandboxed_api/testing.h"
40 #include "sandboxed_api/util/fileops.h"
41 #include "sandboxed_api/util/status_matchers.h"
42 #include "sandboxed_api/util/temp_file.h"
43
44 namespace sandbox2 {
45 namespace {
46
47 namespace file_util = ::sapi::file_util;
48 using ::sapi::CreateDefaultPermissiveTestPolicy;
49 using ::sapi::CreateNamedTempFile;
50 using ::sapi::GetTestSourcePath;
51 using ::sapi::GetTestTempPath;
52 using ::testing::AllOf;
53 using ::testing::AnyOfArray;
54 using ::testing::Contains;
55 using ::testing::ElementsAre;
56 using ::testing::Eq;
57 using ::testing::Gt;
58 using ::testing::HasSubstr;
59 using ::testing::IsEmpty;
60 using ::testing::Matcher;
61 using ::testing::Ne;
62 using ::testing::SizeIs;
63 using ::testing::StartsWith;
64 using ::testing::StrEq; // sapi::google3-only(broken matchers)
65
GetTestcaseBinPath(absl::string_view bin_name)66 std::string GetTestcaseBinPath(absl::string_view bin_name) {
67 return GetTestSourcePath(absl::StrCat("sandbox2/testcases/", bin_name));
68 }
69
RunSandboxeeWithArgsAndPolicy(const std::string & bin_path,std::initializer_list<std::string> args,std::unique_ptr<Policy> policy=nullptr)70 std::vector<std::string> RunSandboxeeWithArgsAndPolicy(
71 const std::string& bin_path, std::initializer_list<std::string> args,
72 std::unique_ptr<Policy> policy = nullptr) {
73 if (!policy) {
74 policy = CreateDefaultPermissiveTestPolicy(bin_path).BuildOrDie();
75 }
76 Sandbox2 sandbox(std::make_unique<Executor>(bin_path, args),
77 std::move(policy));
78
79 CHECK(sandbox.RunAsync());
80 Comms* comms = sandbox.comms();
81 uint64_t num;
82
83 std::vector<std::string> entries;
84 if (comms->RecvUint64(&num)) {
85 entries.reserve(num);
86 for (int i = 0; i < num; ++i) {
87 std::string entry;
88 CHECK(comms->RecvString(&entry));
89 entries.push_back(std::move(entry));
90 }
91 }
92 Result result = sandbox.AwaitResult();
93 EXPECT_THAT(result.final_status(), Eq(Result::OK));
94 EXPECT_THAT(result.reason_code(), Eq(0));
95 return entries;
96 }
97
TEST(NamespaceTest,FileNamespaceWorks)98 TEST(NamespaceTest, FileNamespaceWorks) {
99 // Mount /binary_path RO and check that it exists and is readable.
100 // /etc/passwd should not exist.
101
102 const std::string path = GetTestcaseBinPath("namespace");
103 SAPI_ASSERT_OK_AND_ASSIGN(auto policy, CreateDefaultPermissiveTestPolicy(path)
104 .AddFileAt(path, "/binary_path")
105 .TryBuild());
106 std::vector<std::string> result = RunSandboxeeWithArgsAndPolicy(
107 path, {path, "0", "/binary_path", "/etc/passwd"}, std::move(policy));
108 EXPECT_THAT(result, ElementsAre("/binary_path"));
109 }
110
TEST(NamespaceTest,ReadOnlyIsRespected)111 TEST(NamespaceTest, ReadOnlyIsRespected) {
112 // Mount temporary file as RO and check that it actually is RO.
113 auto [name, fd] = CreateNamedTempFile(GetTestTempPath("temp_file")).value();
114 file_util::fileops::FDCloser temp_closer(fd);
115
116 const std::string path = GetTestcaseBinPath("namespace");
117 {
118 SAPI_ASSERT_OK_AND_ASSIGN(auto policy,
119 CreateDefaultPermissiveTestPolicy(path)
120 .AddFileAt(name, "/temp_file")
121 .TryBuild());
122 // Check that it is readable
123 std::vector<std::string> result = RunSandboxeeWithArgsAndPolicy(
124 path, {path, "0", "/temp_file"}, std::move(policy));
125 EXPECT_THAT(result, ElementsAre("/temp_file"));
126 }
127 {
128 SAPI_ASSERT_OK_AND_ASSIGN(auto policy,
129 CreateDefaultPermissiveTestPolicy(path)
130 .AddFileAt(name, "/temp_file")
131 .TryBuild());
132 // Now check that it is not writeable
133 std::vector<std::string> result = RunSandboxeeWithArgsAndPolicy(
134 path, {path, "1", "/temp_file"}, std::move(policy));
135 EXPECT_THAT(result, IsEmpty());
136 }
137 }
138
TEST(NamespaceTest,UserNamespaceWorks)139 TEST(NamespaceTest, UserNamespaceWorks) {
140 const std::string path = GetTestcaseBinPath("namespace");
141
142 // Check that getpid() returns 2 (which is the case inside pid NS).
143 {
144 std::vector<std::string> result =
145 RunSandboxeeWithArgsAndPolicy(path, {path, "2"});
146 EXPECT_THAT(result, ElementsAre("2"));
147 }
148
149 // Validate that getpid() does not return 2 when outside of a pid NS.
150 {
151 std::vector<std::string> result = RunSandboxeeWithArgsAndPolicy(
152 path, {path, "2"},
153 PolicyBuilder()
154 .DisableNamespaces()
155 .DefaultAction(AllowAllSyscalls()) // Do not restrict syscalls
156 .BuildOrDie());
157 EXPECT_THAT(result, ElementsAre(Ne("2")));
158 }
159 }
160
TEST(NamespaceTest,UserNamespaceIDMapWritten)161 TEST(NamespaceTest, UserNamespaceIDMapWritten) {
162 // Check that the idmap is initialized before the sandbox application is
163 // started.
164 const std::string path = GetTestcaseBinPath("namespace");
165 {
166 std::vector<std::string> result =
167 RunSandboxeeWithArgsAndPolicy(path, {path, "3", "1000", "1000"});
168 EXPECT_THAT(result, ElementsAre("1000", "1000"));
169 }
170
171 // Check that the uid/gid is the same when not using namespaces.
172 {
173 std::vector<std::string> result = RunSandboxeeWithArgsAndPolicy(
174 path, {path, "3"},
175 PolicyBuilder()
176 .DisableNamespaces()
177 .DefaultAction(AllowAllSyscalls()) // Do not restrict syscalls
178 .BuildOrDie());
179 EXPECT_THAT(result,
180 ElementsAre(absl::StrCat(getuid()), absl::StrCat(getgid())));
181 }
182 }
183
TEST(NamespaceTest,RootReadOnly)184 TEST(NamespaceTest, RootReadOnly) {
185 // Mount rw tmpfs at /tmp and check it is RW.
186 // Check also that / is RO.
187 const std::string path = GetTestcaseBinPath("namespace");
188 SAPI_ASSERT_OK_AND_ASSIGN(
189 auto policy, CreateDefaultPermissiveTestPolicy(path)
190 .AddTmpfs("/tmp", /*size=*/4ULL << 20 /* 4 MiB */)
191 .TryBuild());
192 std::vector<std::string> result = RunSandboxeeWithArgsAndPolicy(
193 path, {path, "4", "/tmp/testfile", "/testfile"}, std::move(policy));
194 EXPECT_THAT(result, ElementsAre("/tmp/testfile"));
195 }
196
TEST(NamespaceTest,RootWritable)197 TEST(NamespaceTest, RootWritable) {
198 // Mount root rw and check it
199 const std::string path = GetTestcaseBinPath("namespace");
200 SAPI_ASSERT_OK_AND_ASSIGN(
201 auto policy,
202 CreateDefaultPermissiveTestPolicy(path).SetRootWritable().TryBuild());
203 std::vector<std::string> result = RunSandboxeeWithArgsAndPolicy(
204 path, {path, "4", "/testfile"}, std::move(policy));
205 EXPECT_THAT(result, ElementsAre("/testfile"));
206 }
207
TEST(NamespaceTest,HostnameNone)208 TEST(NamespaceTest, HostnameNone) {
209 const std::string path = GetTestcaseBinPath("namespace");
210 std::vector<std::string> result = RunSandboxeeWithArgsAndPolicy(
211 path, {path, "7"},
212 PolicyBuilder()
213 .DisableNamespaces()
214 .DefaultAction(AllowAllSyscalls()) // Do not restrict syscalls
215 .BuildOrDie());
216 EXPECT_THAT(result, ElementsAre(Ne("sandbox2")));
217 }
218
TEST(NamespaceTest,HostnameDefault)219 TEST(NamespaceTest, HostnameDefault) {
220 const std::string path = GetTestcaseBinPath("namespace");
221 std::vector<std::string> result =
222 RunSandboxeeWithArgsAndPolicy(path, {path, "7"});
223 EXPECT_THAT(result, ElementsAre("sandbox2"));
224 }
225
TEST(NamespaceTest,HostnameConfigured)226 TEST(NamespaceTest, HostnameConfigured) {
227 const std::string path = GetTestcaseBinPath("namespace");
228 SAPI_ASSERT_OK_AND_ASSIGN(auto policy, CreateDefaultPermissiveTestPolicy(path)
229 .SetHostname("configured")
230 .TryBuild());
231 std::vector<std::string> result =
232 RunSandboxeeWithArgsAndPolicy(path, {path, "7"}, std::move(policy));
233 EXPECT_THAT(result, ElementsAre("configured"));
234 }
235
TEST(NamespaceTest,TestInterfacesNoNetwork)236 TEST(NamespaceTest, TestInterfacesNoNetwork) {
237 const std::string path = GetTestcaseBinPath("namespace");
238 std::vector<std::string> result =
239 RunSandboxeeWithArgsAndPolicy(path, {path, "5"});
240 // Only loopback network interface 'lo'.
241 EXPECT_THAT(result, ElementsAre("lo"));
242 }
243
TEST(NamespaceTest,TestInterfacesWithNetwork)244 TEST(NamespaceTest, TestInterfacesWithNetwork) {
245 const std::string path = GetTestcaseBinPath("namespace");
246 SAPI_ASSERT_OK_AND_ASSIGN(auto policy, CreateDefaultPermissiveTestPolicy(path)
247 .AllowUnrestrictedNetworking()
248 .TryBuild());
249
250 std::vector<std::string> result =
251 RunSandboxeeWithArgsAndPolicy(path, {path, "5"}, std::move(policy));
252 // Loopback network interface 'lo' and more.
253 EXPECT_THAT(result, Contains("lo"));
254 EXPECT_THAT(result, SizeIs(Gt(1)));
255 }
256
TEST(NamespaceTest,TestFiles)257 TEST(NamespaceTest, TestFiles) {
258 SKIP_ANDROID;
259 const std::string path = GetTestcaseBinPath("namespace");
260 std::vector<std::string> result =
261 RunSandboxeeWithArgsAndPolicy(path, {path, "6", "/"});
262
263 std::vector<Matcher<std::string>> lib_paths = {
264 StartsWith("/lib/"), // Often a symlink -> /usr/lib
265 StartsWith("/usr/lib/"),
266 StartsWith("/lib64/"), // Often a symlink -> /usr/lib64
267 StartsWith("/usr/lib64/")};
268 auto correct_lib_path_matcher =
269 AllOf(HasSubstr(".so"), AnyOfArray(lib_paths));
270 std::vector<Matcher<std::string>> matchers = {
271 correct_lib_path_matcher,
272 // Conditionally mapped if Tomoyo is active
273 StrEq(absl::StrCat("/dev/fd/", Comms::kSandbox2TargetExecFD)),
274 // System ldconfig cache
275 StrEq("/etc/ld.so.cache"),
276 // GRTE ldconfig cache
277 StrEq("/usr/grte/v4/etc/ld.so.cache"),
278 StrEq("/usr/grte/v5/etc/ld.so.cache"),
279 // procfs and sysfs
280 StartsWith("/proc"), StartsWith("/sys")};
281 // Coverage DIR
282 char* coverage_dir = getenv("COVERAGE_DIR");
283 if (coverage_dir != nullptr) {
284 matchers.push_back(StartsWith(coverage_dir));
285 }
286 for (const auto& file : result) {
287 EXPECT_THAT(file, AnyOfArray(matchers));
288 }
289 }
290
291 } // namespace
292 } // namespace sandbox2
293