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/mounts.h"
16
17 #include <fcntl.h>
18 #include <sys/mount.h>
19 #include <sys/stat.h>
20 #include <sys/statvfs.h>
21 #include <unistd.h>
22
23 #include <cerrno>
24 #include <cstddef>
25 #include <cstdint>
26 #include <string>
27 #include <tuple>
28 #include <utility>
29 #include <vector>
30
31 #include "absl/container/flat_hash_set.h"
32 #include "absl/status/status.h"
33 #include "absl/status/statusor.h"
34 #include "absl/strings/match.h"
35 #include "absl/strings/str_cat.h"
36 #include "absl/strings/str_join.h"
37 #include "absl/strings/str_split.h"
38 #include "absl/strings/string_view.h"
39 #include "absl/strings/strip.h"
40 #include "sandboxed_api/config.h"
41 #include "sandboxed_api/sandbox2/mount_tree.pb.h"
42 #include "sandboxed_api/sandbox2/util/minielf.h"
43 #include "sandboxed_api/util/fileops.h"
44 #include "sandboxed_api/util/path.h"
45 #include "sandboxed_api/util/raw_logging.h"
46 #include "sandboxed_api/util/status_macros.h"
47
48 namespace sandbox2 {
49 namespace {
50
51 namespace cpu = ::sapi::cpu;
52 namespace file_util = ::sapi::file_util;
53 namespace host_cpu = ::sapi::host_cpu;
54
PathContainsNullByte(absl::string_view path)55 bool PathContainsNullByte(absl::string_view path) {
56 return absl::StrContains(path, '\0');
57 }
58
GetOutsidePath(const MountTree::Node & node)59 absl::string_view GetOutsidePath(const MountTree::Node& node) {
60 switch (node.node_case()) {
61 case MountTree::Node::kFileNode:
62 return node.file_node().outside();
63 case MountTree::Node::kDirNode:
64 return node.dir_node().outside();
65 default:
66 SAPI_RAW_LOG(FATAL, "Invalid node type");
67 }
68 }
69
ExistingPathInsideDir(absl::string_view dir_path,absl::string_view relative_path)70 absl::StatusOr<std::string> ExistingPathInsideDir(
71 absl::string_view dir_path, absl::string_view relative_path) {
72 auto path =
73 sapi::file::CleanPath(sapi::file::JoinPath(dir_path, relative_path));
74 if (file_util::fileops::StripBasename(path) != dir_path) {
75 return absl::InvalidArgumentError("Relative path goes above the base dir");
76 }
77 if (!file_util::fileops::Exists(path, false)) {
78 return absl::NotFoundError(absl::StrCat("Does not exist: ", path));
79 }
80 return path;
81 }
82
ValidateInterpreter(absl::string_view interpreter)83 absl::Status ValidateInterpreter(absl::string_view interpreter) {
84 const absl::flat_hash_set<std::string> allowed_interpreters = {
85 "/lib64/ld-linux-x86-64.so.2",
86 "/lib64/ld64.so.2", // PPC64
87 "/lib/ld-linux-aarch64.so.1", // AArch64
88 "/lib/ld-linux-armhf.so.3", // Arm
89 "/system/bin/linker64", // android_arm64
90 };
91
92 if (!allowed_interpreters.contains(interpreter)) {
93 return absl::InvalidArgumentError(
94 absl::StrCat("Interpreter not on the whitelist: ", interpreter));
95 }
96 return absl::OkStatus();
97 }
98
ResolveLibraryPath(absl::string_view lib_name,const std::vector<std::string> & search_paths)99 std::string ResolveLibraryPath(absl::string_view lib_name,
100 const std::vector<std::string>& search_paths) {
101 for (const auto& search_path : search_paths) {
102 if (auto path_or = ExistingPathInsideDir(search_path, lib_name);
103 path_or.ok()) {
104 return path_or.value();
105 }
106 }
107 return "";
108 }
109
GetPlatformCPUName()110 constexpr absl::string_view GetPlatformCPUName() {
111 switch (host_cpu::Architecture()) {
112 case cpu::kX8664:
113 return "x86_64";
114 case cpu::kPPC64LE:
115 return "ppc64";
116 case cpu::kArm64:
117 return "aarch64";
118 default:
119 return "unknown";
120 }
121 }
122
GetPlatform(absl::string_view interpreter)123 std::string GetPlatform(absl::string_view interpreter) {
124 return absl::StrCat(GetPlatformCPUName(), "-linux-gnu");
125 }
126
127 } // namespace
128
129 namespace internal {
130
IsSameFile(const std::string & path1,const std::string & path2)131 bool IsSameFile(const std::string& path1, const std::string& path2) {
132 if (path1 == path2) {
133 return true;
134 }
135
136 struct stat stat1, stat2;
137 if (stat(path1.c_str(), &stat1) == -1) {
138 return false;
139 }
140
141 if (stat(path2.c_str(), &stat2) == -1) {
142 return false;
143 }
144
145 return stat1.st_dev == stat2.st_dev && stat1.st_ino == stat2.st_ino;
146 }
147
IsWritable(const MountTree::Node & node)148 bool IsWritable(const MountTree::Node& node) {
149 switch (node.node_case()) {
150 case MountTree::Node::kFileNode:
151 return node.file_node().writable();
152 case MountTree::Node::kDirNode:
153 return node.dir_node().writable();
154 case MountTree::Node::kRootNode:
155 return node.root_node().writable();
156 default:
157 return false;
158 }
159 }
160
HasSameTarget(const MountTree::Node & n1,const MountTree::Node & n2)161 bool HasSameTarget(const MountTree::Node& n1, const MountTree::Node& n2) {
162 // Return early when node types are different
163 if (n1.node_case() != n2.node_case()) {
164 return false;
165 }
166 // Compare proto fields
167 switch (n1.node_case()) {
168 case MountTree::Node::kFileNode:
169 // Check whether files are the same (e.g. symlinks / hardlinks)
170 return IsSameFile(n1.file_node().outside(), n2.file_node().outside());
171 case MountTree::Node::kDirNode:
172 // Check whether dirs are the same (e.g. symlinks / hardlinks)
173 return IsSameFile(n1.dir_node().outside(), n2.dir_node().outside());
174 case MountTree::Node::kTmpfsNode:
175 return n1.tmpfs_node().tmpfs_options() == n2.tmpfs_node().tmpfs_options();
176 case MountTree::Node::kRootNode:
177 return true;
178 default:
179 return false;
180 }
181 }
182
IsEquivalentNode(const MountTree::Node & n1,const MountTree::Node & n2)183 bool IsEquivalentNode(const MountTree::Node& n1, const MountTree::Node& n2) {
184 if (!HasSameTarget(n1, n2)) {
185 return false;
186 }
187
188 // Compare proto fields
189 switch (n1.node_case()) {
190 case MountTree::Node::kFileNode:
191 return n1.file_node().writable() == n2.file_node().writable();
192 case MountTree::Node::kDirNode:
193 return n1.dir_node().writable() == n2.dir_node().writable();
194 case MountTree::Node::kTmpfsNode:
195 return true;
196 case MountTree::Node::kRootNode:
197 return n1.root_node().writable() == n2.root_node().writable();
198 default:
199 return false;
200 }
201 }
202
203 } // namespace internal
204
Remove(absl::string_view path)205 absl::Status Mounts::Remove(absl::string_view path) {
206 if (PathContainsNullByte(path)) {
207 return absl::InvalidArgumentError(
208 absl::StrCat("Path contains a null byte: ", path));
209 }
210
211 std::string fixed_path = sapi::file::CleanPath(path);
212 if (!sapi::file::IsAbsolutePath(fixed_path)) {
213 return absl::InvalidArgumentError("Only absolute paths are supported");
214 }
215
216 if (fixed_path == "/") {
217 return absl::InvalidArgumentError("Cannot remove root");
218 }
219 std::vector<absl::string_view> parts =
220 absl::StrSplit(absl::StripPrefix(fixed_path, "/"), '/');
221
222 MountTree* curtree = &mount_tree_;
223 for (absl::string_view part : parts) {
224 if (curtree->has_node() && curtree->node().has_file_node()) {
225 return absl::NotFoundError(
226 absl::StrCat("File node is mounted at parent of: ", path));
227 }
228 auto it = curtree->mutable_entries()->find(std::string(part));
229 if (it == curtree->mutable_entries()->end()) {
230 return absl::NotFoundError(
231 absl::StrCat("Path does not exist in mounts: ", path));
232 }
233 curtree = &it->second;
234 }
235 curtree->clear_node();
236 curtree->clear_entries();
237 return absl::OkStatus();
238 }
239
Insert(absl::string_view path,const MountTree::Node & new_node)240 absl::Status Mounts::Insert(absl::string_view path,
241 const MountTree::Node& new_node) {
242 // Some sandboxes allow the inside/outside paths to be partially
243 // user-controlled with some sanitization.
244 // Since we're handling C++ strings and later convert them to C style
245 // strings, a null byte in a path component might silently truncate the path
246 // and mount something not expected by the caller. Check for null bytes in the
247 // strings to protect against this.
248 if (PathContainsNullByte(path)) {
249 return absl::InvalidArgumentError(
250 absl::StrCat("Inside path contains a null byte: ", path));
251 }
252 switch (new_node.node_case()) {
253 case MountTree::Node::kFileNode:
254 case MountTree::Node::kDirNode: {
255 auto outside_path = GetOutsidePath(new_node);
256 if (outside_path.empty()) {
257 return absl::InvalidArgumentError("Outside path cannot be empty");
258 }
259 if (PathContainsNullByte(outside_path)) {
260 return absl::InvalidArgumentError(
261 absl::StrCat("Outside path contains a null byte: ", outside_path));
262 }
263 break;
264 }
265 case MountTree::Node::kRootNode:
266 return absl::InvalidArgumentError("Cannot insert a RootNode");
267 case MountTree::Node::kTmpfsNode:
268 case MountTree::Node::NODE_NOT_SET:
269 break;
270 }
271
272 std::string fixed_path = sapi::file::CleanPath(path);
273 if (!sapi::file::IsAbsolutePath(fixed_path)) {
274 return absl::InvalidArgumentError("Only absolute paths are supported");
275 }
276
277 if (fixed_path == "/") {
278 return absl::InvalidArgumentError("The root already exists");
279 }
280
281 std::vector<absl::string_view> parts =
282 absl::StrSplit(absl::StripPrefix(fixed_path, "/"), '/');
283 std::string final_part(parts.back());
284 parts.pop_back();
285
286 MountTree* curtree = &mount_tree_;
287 for (absl::string_view part : parts) {
288 curtree = &(curtree->mutable_entries()
289 ->insert({std::string(part), MountTree()})
290 .first->second);
291 if (curtree->has_node() && curtree->node().has_file_node()) {
292 return absl::FailedPreconditionError(
293 absl::StrCat("Cannot insert ", path,
294 " since a file is mounted as a parent directory"));
295 }
296 }
297
298 curtree = &(curtree->mutable_entries()
299 ->insert({final_part, MountTree()})
300 .first->second);
301
302 if (curtree->has_node()) {
303 if (internal::IsEquivalentNode(curtree->node(), new_node)) {
304 SAPI_RAW_LOG(INFO, "Inserting %s with the same value twice",
305 std::string(path).c_str());
306 return absl::OkStatus();
307 }
308 if (internal::HasSameTarget(curtree->node(), new_node)) {
309 if (!internal::IsWritable(curtree->node()) &&
310 internal::IsWritable(new_node)) {
311 SAPI_RAW_LOG(INFO,
312 "Changing %s to writable, was inserted read-only before",
313 std::string(path).c_str());
314 *curtree->mutable_node() = new_node;
315 return absl::OkStatus();
316 }
317 if (internal::IsWritable(curtree->node()) &&
318 !internal::IsWritable(new_node)) {
319 SAPI_RAW_LOG(INFO,
320 "Inserting %s read-only is a nop, as it was inserted "
321 "writable before",
322 std::string(path).c_str());
323 return absl::OkStatus();
324 }
325 }
326 return absl::FailedPreconditionError(absl::StrCat(
327 "Inserting ", path, " twice with conflicting values ",
328 curtree->node().DebugString(), " vs. ", new_node.DebugString()));
329 }
330
331 if (new_node.has_file_node() && !curtree->entries().empty()) {
332 return absl::FailedPreconditionError(
333 absl::StrCat("Trying to mount file over existing directory at ", path));
334 }
335
336 *curtree->mutable_node() = new_node;
337 return absl::OkStatus();
338 }
339
AddFileAt(absl::string_view outside,absl::string_view inside,bool is_ro)340 absl::Status Mounts::AddFileAt(absl::string_view outside,
341 absl::string_view inside, bool is_ro) {
342 MountTree::Node node;
343 auto* file_node = node.mutable_file_node();
344 file_node->set_outside(std::string(outside));
345 file_node->set_writable(!is_ro);
346 return Insert(inside, node);
347 }
348
AddDirectoryAt(absl::string_view outside,absl::string_view inside,bool is_ro)349 absl::Status Mounts::AddDirectoryAt(absl::string_view outside,
350 absl::string_view inside, bool is_ro) {
351 MountTree::Node node;
352 auto* dir_node = node.mutable_dir_node();
353 dir_node->set_outside(std::string(outside));
354 dir_node->set_writable(!is_ro);
355 return Insert(inside, node);
356 }
357
ResolvePath(absl::string_view path) const358 absl::StatusOr<std::string> Mounts::ResolvePath(absl::string_view path) const {
359 if (!sapi::file::IsAbsolutePath(path)) {
360 return absl::InvalidArgumentError("Path has to be absolute");
361 }
362 std::string fixed_path = sapi::file::CleanPath(path);
363 absl::string_view tail = absl::StripPrefix(fixed_path, "/");
364
365 const MountTree* curtree = &mount_tree_;
366 while (!tail.empty()) {
367 std::pair<absl::string_view, absl::string_view> parts =
368 absl::StrSplit(tail, absl::MaxSplits('/', 1));
369 const std::string cur(parts.first);
370 const auto it = curtree->entries().find(cur);
371 if (it == curtree->entries().end()) {
372 if (curtree->node().has_dir_node()) {
373 return sapi::file::JoinPath(curtree->node().dir_node().outside(), tail);
374 }
375 return absl::NotFoundError("Path could not be resolved in the mounts");
376 }
377 curtree = &it->second;
378 tail = parts.second;
379 }
380 switch (curtree->node().node_case()) {
381 case MountTree::Node::kFileNode:
382 case MountTree::Node::kDirNode:
383 return std::string(GetOutsidePath(curtree->node()));
384 case MountTree::Node::kRootNode:
385 case MountTree::Node::kTmpfsNode:
386 case MountTree::Node::NODE_NOT_SET:
387 break;
388 }
389 return absl::NotFoundError("Path could not be resolved in the mounts");
390 }
391
392 namespace {
393
LogContainer(const std::vector<std::string> & container)394 void LogContainer(const std::vector<std::string>& container) {
395 for (size_t i = 0; i < container.size(); ++i) {
396 SAPI_RAW_LOG(INFO, "[%4zd]=%s", i, container[i].c_str());
397 }
398 }
399
400 } // namespace
401
AddMappingsForBinary(const std::string & path,absl::string_view ld_library_path)402 absl::Status Mounts::AddMappingsForBinary(const std::string& path,
403 absl::string_view ld_library_path) {
404 SAPI_ASSIGN_OR_RETURN(
405 auto elf,
406 ElfFile::ParseFromFile(
407 path, ElfFile::kGetInterpreter | ElfFile::kLoadImportedLibraries));
408 const std::string& interpreter = elf.interpreter();
409
410 if (interpreter.empty()) {
411 SAPI_RAW_VLOG(1, "The file %s is not a dynamic executable", path.c_str());
412 return absl::OkStatus();
413 }
414
415 SAPI_RAW_VLOG(1, "The file %s is using interpreter %s", path.c_str(),
416 interpreter.c_str());
417 SAPI_RETURN_IF_ERROR(ValidateInterpreter(interpreter));
418
419 std::vector<std::string> search_paths;
420 // 1. LD_LIBRARY_PATH
421 if (!ld_library_path.empty()) {
422 std::vector<std::string> ld_library_paths =
423 absl::StrSplit(ld_library_path, absl::ByAnyChar(":;"));
424 search_paths.insert(search_paths.end(), ld_library_paths.begin(),
425 ld_library_paths.end());
426 }
427 // 2. Standard paths
428 search_paths.insert(search_paths.end(), {
429 "/lib",
430 "/lib64",
431 "/usr/lib",
432 "/usr/lib64",
433 });
434 std::vector<std::string> hw_cap_paths = {
435 GetPlatform(interpreter),
436 "tls",
437 };
438 std::vector<std::string> full_search_paths;
439 for (const auto& search_path : search_paths) {
440 for (int hw_caps_set = (1 << hw_cap_paths.size()) - 1; hw_caps_set >= 0;
441 --hw_caps_set) {
442 std::string path = search_path;
443 for (int hw_cap = 0; hw_cap < hw_cap_paths.size(); ++hw_cap) {
444 if ((hw_caps_set & (1 << hw_cap)) != 0) {
445 path = sapi::file::JoinPath(path, hw_cap_paths[hw_cap]);
446 }
447 }
448 if (file_util::fileops::Exists(path, /*fully_resolve=*/false)) {
449 full_search_paths.push_back(path);
450 }
451 }
452 }
453
454 // Arbitrary cut-off values, so we can safely resolve the libs.
455 constexpr int kMaxWorkQueueSize = 1000;
456 constexpr int kMaxResolvingDepth = 10;
457 constexpr int kMaxResolvedEntries = 1000;
458 constexpr int kMaxLoadedEntries = 100;
459 constexpr int kMaxImportedLibraries = 100;
460
461 absl::flat_hash_set<std::string> imported_libraries;
462 std::vector<std::pair<std::string, int>> to_resolve;
463 {
464 auto imported_libs = elf.imported_libraries();
465 if (imported_libs.size() > kMaxWorkQueueSize) {
466 return absl::FailedPreconditionError(
467 "Exceeded max entries pending resolving limit");
468 }
469 for (const auto& imported_lib : imported_libs) {
470 to_resolve.emplace_back(imported_lib, 1);
471 }
472
473 if (SAPI_RAW_VLOG_IS_ON(1)) {
474 SAPI_RAW_VLOG(
475 1, "Resolving dynamic library dependencies of %s using these dirs:",
476 path.c_str());
477 LogContainer(full_search_paths);
478 }
479 if (SAPI_RAW_VLOG_IS_ON(2)) {
480 SAPI_RAW_VLOG(2, "Direct dependencies of %s to resolve:", path.c_str());
481 LogContainer(imported_libs);
482 }
483 }
484
485 // This is DFS with an auxiliary stack
486 int resolved = 0;
487 int loaded = 0;
488 while (!to_resolve.empty()) {
489 int depth;
490 std::string lib;
491 std::tie(lib, depth) = to_resolve.back();
492 to_resolve.pop_back();
493 ++resolved;
494 if (resolved > kMaxResolvedEntries) {
495 return absl::FailedPreconditionError(
496 "Exceeded max resolved entries limit");
497 }
498 if (depth > kMaxResolvingDepth) {
499 return absl::FailedPreconditionError(
500 "Exceeded max resolving depth limit");
501 }
502 std::string resolved_lib = ResolveLibraryPath(lib, full_search_paths);
503 if (resolved_lib.empty()) {
504 SAPI_RAW_LOG(ERROR, "Failed to resolve library: %s", lib.c_str());
505 continue;
506 }
507 if (imported_libraries.contains(resolved_lib)) {
508 continue;
509 }
510
511 SAPI_RAW_VLOG(1, "Resolved library: %s => %s", lib.c_str(),
512 resolved_lib.c_str());
513
514 imported_libraries.insert(resolved_lib);
515 if (imported_libraries.size() > kMaxImportedLibraries) {
516 return absl::FailedPreconditionError(
517 "Exceeded max imported libraries limit");
518 }
519 ++loaded;
520 if (loaded > kMaxLoadedEntries) {
521 return absl::FailedPreconditionError("Exceeded max loaded entries limit");
522 }
523 SAPI_ASSIGN_OR_RETURN(
524 auto lib_elf,
525 ElfFile::ParseFromFile(resolved_lib, ElfFile::kLoadImportedLibraries));
526 auto imported_libs = lib_elf.imported_libraries();
527 if (imported_libs.size() > kMaxWorkQueueSize - to_resolve.size()) {
528 return absl::FailedPreconditionError(
529 "Exceeded max entries pending resolving limit");
530 }
531
532 if (SAPI_RAW_VLOG_IS_ON(2)) {
533 SAPI_RAW_VLOG(2,
534 "Transitive dependencies of %s to resolve (depth = %d): ",
535 resolved_lib.c_str(), depth + 1);
536 LogContainer(imported_libs);
537 }
538
539 for (const auto& imported_lib : imported_libs) {
540 to_resolve.emplace_back(imported_lib, depth + 1);
541 }
542 }
543
544 imported_libraries.insert(interpreter);
545 for (const auto& lib : imported_libraries) {
546 SAPI_RETURN_IF_ERROR(AddFile(lib));
547 }
548
549 return absl::OkStatus();
550 }
551
AddTmpfs(absl::string_view inside,size_t sz)552 absl::Status Mounts::AddTmpfs(absl::string_view inside, size_t sz) {
553 MountTree::Node node;
554 auto tmpfs_node = node.mutable_tmpfs_node();
555 tmpfs_node->set_tmpfs_options(absl::StrCat("size=", sz));
556 return Insert(inside, node);
557 }
558
559 namespace {
560
GetMountFlagsFor(const std::string & path)561 uint64_t GetMountFlagsFor(const std::string& path) {
562 struct statvfs vfs;
563 if (TEMP_FAILURE_RETRY(statvfs(path.c_str(), &vfs)) == -1) {
564 SAPI_RAW_PLOG(ERROR, "statvfs");
565 return 0;
566 }
567
568 uint64_t flags = 0;
569 using MountPair = std::pair<uint64_t, uint64_t>;
570 for (const auto& [mount_flag, vfs_flag] : {
571 MountPair(MS_RDONLY, ST_RDONLY),
572 MountPair(MS_NOSUID, ST_NOSUID),
573 MountPair(MS_NODEV, ST_NODEV),
574 MountPair(MS_NOEXEC, ST_NOEXEC),
575 MountPair(MS_SYNCHRONOUS, ST_SYNCHRONOUS),
576 MountPair(MS_MANDLOCK, ST_MANDLOCK),
577 MountPair(MS_NOATIME, ST_NOATIME),
578 MountPair(MS_NODIRATIME, ST_NODIRATIME),
579 MountPair(MS_RELATIME, ST_RELATIME),
580 }) {
581 if (vfs.f_flag & vfs_flag) {
582 flags |= mount_flag;
583 }
584 }
585 return flags;
586 }
587
MountFlagsToString(uint64_t flags)588 std::string MountFlagsToString(uint64_t flags) {
589 #define SAPI_MAP(x) \
590 { x, #x }
591 static constexpr std::pair<uint64_t, absl::string_view> kMap[] = {
592 SAPI_MAP(MS_RDONLY), SAPI_MAP(MS_NOSUID),
593 SAPI_MAP(MS_NODEV), SAPI_MAP(MS_NOEXEC),
594 SAPI_MAP(MS_SYNCHRONOUS), SAPI_MAP(MS_REMOUNT),
595 SAPI_MAP(MS_MANDLOCK), SAPI_MAP(MS_DIRSYNC),
596 SAPI_MAP(MS_NOATIME), SAPI_MAP(MS_NODIRATIME),
597 SAPI_MAP(MS_BIND), SAPI_MAP(MS_MOVE),
598 SAPI_MAP(MS_REC),
599 #ifdef MS_VERBOSE
600 SAPI_MAP(MS_VERBOSE), // Deprecated
601 #endif
602 SAPI_MAP(MS_SILENT), SAPI_MAP(MS_POSIXACL),
603 SAPI_MAP(MS_UNBINDABLE), SAPI_MAP(MS_PRIVATE),
604 SAPI_MAP(MS_SLAVE), // Inclusive language: system constant
605 SAPI_MAP(MS_SHARED), SAPI_MAP(MS_RELATIME),
606 SAPI_MAP(MS_KERNMOUNT), SAPI_MAP(MS_I_VERSION),
607 SAPI_MAP(MS_STRICTATIME),
608 #ifdef MS_LAZYTIME
609 SAPI_MAP(MS_LAZYTIME), // Added in Linux 4.0
610 #endif
611 };
612 #undef SAPI_MAP
613 std::vector<absl::string_view> flags_list;
614 for (const auto& [val, str] : kMap) {
615 if ((flags & val) == val) {
616 flags &= ~val;
617 flags_list.push_back(str);
618 }
619 }
620 std::string flags_str = absl::StrCat(flags);
621 if (flags_list.empty() || flags != 0) {
622 flags_list.push_back(flags_str);
623 }
624 return absl::StrJoin(flags_list, "|");
625 }
626
MountWithDefaults(const std::string & source,const std::string & target,const char * fs_type,uint64_t extra_flags,const char * option_str,bool is_ro)627 void MountWithDefaults(const std::string& source, const std::string& target,
628 const char* fs_type, uint64_t extra_flags,
629 const char* option_str, bool is_ro) {
630 uint64_t flags = MS_REC | MS_NOSUID | extra_flags;
631 if (is_ro) {
632 flags |= MS_RDONLY;
633 }
634 SAPI_RAW_VLOG(1, R"(mount("%s", "%s", "%s", %s, "%s"))", source.c_str(),
635 target.c_str(), fs_type, MountFlagsToString(flags).c_str(),
636 option_str);
637
638 int res = mount(source.c_str(), target.c_str(), fs_type, flags, option_str);
639 if (res == -1) {
640 if (errno == ENOENT) {
641 // File does not exist (anymore). This is e.g. the case when we're trying
642 // to gather stack-traces on SAPI crashes. The sandboxee application is a
643 // memfd file that is not existing anymore.
644 SAPI_RAW_LOG(WARNING, "Could not mount %s: file does not exist",
645 source.c_str());
646 return;
647 }
648 SAPI_RAW_PLOG(FATAL, "mounting %s to %s failed (flags=%s)", source, target,
649 MountFlagsToString(flags));
650 }
651
652 // Flags are ignored for a bind mount, a remount is needed to set the flags.
653 if (extra_flags & MS_BIND) {
654 // Get actual mount flags.
655 uint64_t target_flags = GetMountFlagsFor(target);
656 if ((target_flags & MS_RDONLY) != 0 && (flags & MS_RDONLY) == 0) {
657 SAPI_RAW_LOG(FATAL,
658 "cannot remount %s as read-write as it's on read-only dev",
659 target.c_str());
660 }
661 res = mount("", target.c_str(), "", flags | target_flags | MS_REMOUNT,
662 nullptr);
663 SAPI_RAW_PCHECK(res != -1, "remounting %s with flags=%s failed", target,
664 MountFlagsToString(flags));
665 }
666
667 // Mount propagation has to be set separately
668 const uint64_t propagation =
669 extra_flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE);
670 if (propagation != 0) {
671 res = mount("", target.c_str(), "", propagation, nullptr);
672 SAPI_RAW_PCHECK(res != -1, "changing %s mount propagation to %s failed",
673 target, MountFlagsToString(propagation).c_str());
674 }
675 }
676
677 // Traverses the MountTree to create all required files and perform the mounts.
CreateMounts(const MountTree & tree,const std::string & path,bool create_backing_files)678 void CreateMounts(const MountTree& tree, const std::string& path,
679 bool create_backing_files) {
680 // First, create the backing files if needed.
681 if (create_backing_files) {
682 switch (tree.node().node_case()) {
683 case MountTree::Node::kFileNode: {
684 SAPI_RAW_VLOG(2, "Creating backing file at %s", path.c_str());
685 int fd = open(path.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0600);
686 SAPI_RAW_PCHECK(fd != -1, "");
687 SAPI_RAW_PCHECK(close(fd) == 0, "");
688 break;
689 }
690 case MountTree::Node::kDirNode:
691 case MountTree::Node::kTmpfsNode:
692 case MountTree::Node::kRootNode:
693 case MountTree::Node::NODE_NOT_SET:
694 SAPI_RAW_VLOG(2, "Creating directory at %s", path.c_str());
695 SAPI_RAW_PCHECK(mkdir(path.c_str(), 0700) == 0 || errno == EEXIST, "");
696 break;
697 // Intentionally no default to make sure we handle all the cases.
698 }
699 }
700
701 // Perform the actual mounts based on the node type.
702 switch (tree.node().node_case()) {
703 case MountTree::Node::kDirNode: {
704 // Since this directory is bind mounted, it's the users
705 // responsibility to make sure that all backing files are in place.
706 create_backing_files = false;
707
708 auto node = tree.node().dir_node();
709 MountWithDefaults(node.outside(), path, "", MS_BIND, nullptr,
710 !node.writable());
711 break;
712 }
713 case MountTree::Node::kTmpfsNode: {
714 // We can always create backing files under a tmpfs.
715 create_backing_files = true;
716
717 auto node = tree.node().tmpfs_node();
718 MountWithDefaults("", path, "tmpfs", 0, node.tmpfs_options().c_str(),
719 /* is_ro */ false);
720 break;
721 }
722 case MountTree::Node::kFileNode: {
723 auto node = tree.node().file_node();
724 MountWithDefaults(node.outside(), path, "", MS_BIND, nullptr,
725 !node.writable());
726
727 // A file node has to be a leaf so we can skip traversing here.
728 return;
729 }
730 case MountTree::Node::kRootNode:
731 case MountTree::Node::NODE_NOT_SET:
732 // Nothing to do, we already created the directory above.
733 break;
734 // Intentionally no default to make sure we handle all the cases.
735 }
736
737 // Traverse the subtrees.
738 for (const auto& kv : tree.entries()) {
739 std::string new_path = sapi::file::JoinPath(path, kv.first);
740 CreateMounts(kv.second, new_path, create_backing_files);
741 }
742 }
743
744 } // namespace
745
CreateMounts(const std::string & root_path) const746 void Mounts::CreateMounts(const std::string& root_path) const {
747 sandbox2::CreateMounts(mount_tree_, root_path, true);
748 }
749
750 namespace {
751
RecursivelyListMountsImpl(const MountTree & tree,const std::string & tree_path,std::vector<std::string> * outside_entries,std::vector<std::string> * inside_entries)752 void RecursivelyListMountsImpl(const MountTree& tree,
753 const std::string& tree_path,
754 std::vector<std::string>* outside_entries,
755 std::vector<std::string>* inside_entries) {
756 const MountTree::Node& node = tree.node();
757 if (node.has_dir_node()) {
758 const char* rw_str = node.dir_node().writable() ? "W " : "R ";
759 inside_entries->emplace_back(absl::StrCat(rw_str, tree_path, "/"));
760 outside_entries->emplace_back(absl::StrCat(node.dir_node().outside(), "/"));
761 } else if (node.has_file_node()) {
762 const char* rw_str = node.file_node().writable() ? "W " : "R ";
763 inside_entries->emplace_back(absl::StrCat(rw_str, tree_path));
764 outside_entries->emplace_back(absl::StrCat(node.file_node().outside()));
765 } else if (node.has_tmpfs_node()) {
766 inside_entries->emplace_back(tree_path);
767 outside_entries->emplace_back(
768 absl::StrCat("tmpfs: ", node.tmpfs_node().tmpfs_options()));
769 }
770
771 for (const auto& subentry : tree.entries()) {
772 RecursivelyListMountsImpl(subentry.second,
773 absl::StrCat(tree_path, "/", subentry.first),
774 outside_entries, inside_entries);
775 }
776 }
777
778 } // namespace
779
RecursivelyListMounts(std::vector<std::string> * outside_entries,std::vector<std::string> * inside_entries) const780 void Mounts::RecursivelyListMounts(
781 std::vector<std::string>* outside_entries,
782 std::vector<std::string>* inside_entries) const {
783 RecursivelyListMountsImpl(GetMountTree(), "", outside_entries,
784 inside_entries);
785 }
786
787 } // namespace sandbox2
788