• 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/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 <algorithm>
24 #include <cerrno>
25 #include <cstddef>
26 #include <cstdint>
27 #include <string>
28 #include <tuple>
29 #include <utility>
30 #include <vector>
31 
32 #include "absl/container/flat_hash_set.h"
33 #include "absl/status/status.h"
34 #include "absl/status/statusor.h"
35 #include "absl/strings/match.h"
36 #include "absl/strings/str_cat.h"
37 #include "absl/strings/str_join.h"
38 #include "absl/strings/str_split.h"
39 #include "absl/strings/string_view.h"
40 #include "absl/strings/strip.h"
41 #include "sandboxed_api/config.h"
42 #include "sandboxed_api/sandbox2/mount_tree.pb.h"
43 #include "sandboxed_api/sandbox2/util/minielf.h"
44 #include "sandboxed_api/util/fileops.h"
45 #include "sandboxed_api/util/path.h"
46 #include "sandboxed_api/util/raw_logging.h"
47 #include "sandboxed_api/util/status_macros.h"
48 
49 namespace sandbox2 {
50 namespace {
51 
52 namespace cpu = ::sapi::cpu;
53 namespace file_util = ::sapi::file_util;
54 namespace host_cpu = ::sapi::host_cpu;
55 
PathContainsNullByte(absl::string_view path)56 bool PathContainsNullByte(absl::string_view path) {
57   return absl::StrContains(path, '\0');
58 }
59 
GetOutsidePath(const MountTree::Node & node)60 absl::string_view GetOutsidePath(const MountTree::Node& node) {
61   switch (node.node_case()) {
62     case MountTree::Node::kFileNode:
63       return node.file_node().outside();
64     case MountTree::Node::kDirNode:
65       return node.dir_node().outside();
66     default:
67       SAPI_RAW_LOG(FATAL, "Invalid node type");
68   }
69 }
70 
ExistingPathInsideDir(absl::string_view dir_path,absl::string_view relative_path)71 absl::StatusOr<std::string> ExistingPathInsideDir(
72     absl::string_view dir_path, absl::string_view relative_path) {
73   auto path =
74       sapi::file::CleanPath(sapi::file::JoinPath(dir_path, relative_path));
75   if (file_util::fileops::StripBasename(path) != dir_path) {
76     return absl::InvalidArgumentError("Relative path goes above the base dir");
77   }
78   if (!file_util::fileops::Exists(path, false)) {
79     return absl::NotFoundError(absl::StrCat("Does not exist: ", path));
80   }
81   return path;
82 }
83 
ValidateInterpreter(absl::string_view interpreter)84 absl::Status ValidateInterpreter(absl::string_view interpreter) {
85   const absl::flat_hash_set<std::string> allowed_interpreters = {
86       "/lib64/ld-linux-x86-64.so.2",
87       "/lib64/ld64.so.2",            // PPC64
88       "/lib/ld-linux-aarch64.so.1",  // AArch64
89       "/lib/ld-linux-armhf.so.3",    // Arm
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 
284   MountTree* curtree = &mount_tree_;
285   for (int i = 0; true; ++i) {
286     // ANDROID: std::string_view to std::string
287     auto [it, did_insert] =
288         curtree->mutable_entries()->emplace(std::string(parts[i]), MountTree());
289     if (did_insert) {
290       it->second.set_index(++mount_index_);
291     }
292     curtree = &it->second;
293     if (i == parts.size() - 1) {  // Final part
294       break;
295     }
296     if (curtree->has_node() && curtree->node().has_file_node()) {
297       return absl::FailedPreconditionError(
298           absl::StrCat("Cannot insert ", path,
299                        " since a file is mounted as a parent directory"));
300     }
301   }
302 
303   if (curtree->has_node()) {
304     if (internal::IsEquivalentNode(curtree->node(), new_node)) {
305       SAPI_RAW_LOG(INFO, "Inserting %s with the same value twice",
306                    std::string(path).c_str());
307       return absl::OkStatus();
308     }
309     if (internal::HasSameTarget(curtree->node(), new_node)) {
310       if (!internal::IsWritable(curtree->node()) &&
311           internal::IsWritable(new_node)) {
312         SAPI_RAW_LOG(INFO,
313                      "Changing %s to writable, was inserted read-only before",
314                      std::string(path).c_str());
315         *curtree->mutable_node() = new_node;
316         return absl::OkStatus();
317       }
318       if (internal::IsWritable(curtree->node()) &&
319           !internal::IsWritable(new_node)) {
320         SAPI_RAW_LOG(INFO,
321                      "Inserting %s read-only is a nop, as it was inserted "
322                      "writable before",
323                      std::string(path).c_str());
324         return absl::OkStatus();
325       }
326     }
327     return absl::FailedPreconditionError(absl::StrCat(
328         "Inserting ", path, " twice with conflicting values ",
329         curtree->node().DebugString(), " vs. ", new_node.DebugString()));
330   }
331 
332   if (new_node.has_file_node() && !curtree->entries().empty()) {
333     return absl::FailedPreconditionError(
334         absl::StrCat("Trying to mount file over existing directory at ", path));
335   }
336 
337   *curtree->mutable_node() = new_node;
338   return absl::OkStatus();
339 }
340 
AddFileAt(absl::string_view outside,absl::string_view inside,bool is_ro)341 absl::Status Mounts::AddFileAt(absl::string_view outside,
342                                absl::string_view inside, bool is_ro) {
343   MountTree::Node node;
344   auto* file_node = node.mutable_file_node();
345   file_node->set_outside(std::string(outside));
346   file_node->set_writable(!is_ro);
347   return Insert(inside, node);
348 }
349 
AddDirectoryAt(absl::string_view outside,absl::string_view inside,bool is_ro)350 absl::Status Mounts::AddDirectoryAt(absl::string_view outside,
351                                     absl::string_view inside, bool is_ro) {
352   MountTree::Node node;
353   auto* dir_node = node.mutable_dir_node();
354   dir_node->set_outside(std::string(outside));
355   dir_node->set_writable(!is_ro);
356   return Insert(inside, node);
357 }
358 
ResolvePath(absl::string_view path) const359 absl::StatusOr<std::string> Mounts::ResolvePath(absl::string_view path) const {
360   if (!sapi::file::IsAbsolutePath(path)) {
361     return absl::InvalidArgumentError("Path has to be absolute");
362   }
363   std::string fixed_path = sapi::file::CleanPath(path);
364   absl::string_view tail = absl::StripPrefix(fixed_path, "/");
365 
366   const MountTree* curtree = &mount_tree_;
367   while (!tail.empty()) {
368     std::pair<absl::string_view, absl::string_view> parts =
369         absl::StrSplit(tail, absl::MaxSplits('/', 1));
370     const std::string cur(parts.first);
371     const auto it = curtree->entries().find(cur);
372     if (it == curtree->entries().end()) {
373       if (curtree->node().has_dir_node()) {
374         return sapi::file::JoinPath(curtree->node().dir_node().outside(), tail);
375       }
376       return absl::NotFoundError("Path could not be resolved in the mounts");
377     }
378     curtree = &it->second;
379     tail = parts.second;
380   }
381   switch (curtree->node().node_case()) {
382     case MountTree::Node::kFileNode:
383     case MountTree::Node::kDirNode:
384       return std::string(GetOutsidePath(curtree->node()));
385     case MountTree::Node::kRootNode:
386     case MountTree::Node::kTmpfsNode:
387     case MountTree::Node::NODE_NOT_SET:
388       break;
389   }
390   return absl::NotFoundError("Path could not be resolved in the mounts");
391 }
392 
393 namespace {
394 
LogContainer(const std::vector<std::string> & container)395 void LogContainer(const std::vector<std::string>& container) {
396   for (size_t i = 0; i < container.size(); ++i) {
397     SAPI_RAW_LOG(INFO, "[%4zd]=%s", i, container[i].c_str());
398   }
399 }
400 
401 }  // namespace
402 
AddMappingsForBinary(const std::string & path,absl::string_view ld_library_path)403 absl::Status Mounts::AddMappingsForBinary(const std::string& path,
404                                           absl::string_view ld_library_path) {
405   SAPI_ASSIGN_OR_RETURN(
406       auto elf,
407       ElfFile::ParseFromFile(
408           path, ElfFile::kGetInterpreter | ElfFile::kLoadImportedLibraries));
409   const std::string& interpreter = elf.interpreter();
410 
411   if (interpreter.empty()) {
412     SAPI_RAW_VLOG(1, "The file %s is not a dynamic executable", path.c_str());
413     return absl::OkStatus();
414   }
415 
416   SAPI_RAW_VLOG(1, "The file %s is using interpreter %s", path.c_str(),
417                 interpreter.c_str());
418   SAPI_RETURN_IF_ERROR(ValidateInterpreter(interpreter));
419 
420   std::vector<std::string> search_paths;
421   // 1. LD_LIBRARY_PATH
422   if (!ld_library_path.empty()) {
423     std::vector<std::string> ld_library_paths =
424         absl::StrSplit(ld_library_path, absl::ByAnyChar(":;"));
425     search_paths.insert(search_paths.end(), ld_library_paths.begin(),
426                         ld_library_paths.end());
427   }
428   // 2. Standard paths
429   search_paths.insert(search_paths.end(), {
430                                               "/lib",
431                                               "/lib64",
432                                               "/usr/lib",
433                                               "/usr/lib64",
434                                           });
435   std::vector<std::string> hw_cap_paths = {
436       GetPlatform(interpreter),
437       "tls",
438   };
439   std::vector<std::string> full_search_paths;
440   for (const auto& search_path : search_paths) {
441     for (int hw_caps_set = (1 << hw_cap_paths.size()) - 1; hw_caps_set >= 0;
442          --hw_caps_set) {
443       std::string path = search_path;
444       for (int hw_cap = 0; hw_cap < hw_cap_paths.size(); ++hw_cap) {
445         if ((hw_caps_set & (1 << hw_cap)) != 0) {
446           path = sapi::file::JoinPath(path, hw_cap_paths[hw_cap]);
447         }
448       }
449       if (file_util::fileops::Exists(path, /*fully_resolve=*/false)) {
450         full_search_paths.push_back(path);
451       }
452     }
453   }
454 
455   // Arbitrary cut-off values, so we can safely resolve the libs.
456   constexpr int kMaxWorkQueueSize = 1000;
457   constexpr int kMaxResolvingDepth = 10;
458   constexpr int kMaxResolvedEntries = 1000;
459   constexpr int kMaxLoadedEntries = 100;
460   constexpr int kMaxImportedLibraries = 100;
461 
462   absl::flat_hash_set<std::string> imported_libraries;
463   std::vector<std::pair<std::string, int>> to_resolve;
464   {
465     auto imported_libs = elf.imported_libraries();
466     if (imported_libs.size() > kMaxWorkQueueSize) {
467       return absl::FailedPreconditionError(
468           "Exceeded max entries pending resolving limit");
469     }
470     for (const auto& imported_lib : imported_libs) {
471       to_resolve.emplace_back(imported_lib, 1);
472     }
473 
474     if (SAPI_RAW_VLOG_IS_ON(1)) {
475       SAPI_RAW_VLOG(
476           1, "Resolving dynamic library dependencies of %s using these dirs:",
477           path.c_str());
478       LogContainer(full_search_paths);
479     }
480     if (SAPI_RAW_VLOG_IS_ON(2)) {
481       SAPI_RAW_VLOG(2, "Direct dependencies of %s to resolve:", path.c_str());
482       LogContainer(imported_libs);
483     }
484   }
485 
486   // This is DFS with an auxiliary stack
487   int resolved = 0;
488   int loaded = 0;
489   while (!to_resolve.empty()) {
490     int depth;
491     std::string lib;
492     std::tie(lib, depth) = to_resolve.back();
493     to_resolve.pop_back();
494     ++resolved;
495     if (resolved > kMaxResolvedEntries) {
496       return absl::FailedPreconditionError(
497           "Exceeded max resolved entries limit");
498     }
499     if (depth > kMaxResolvingDepth) {
500       return absl::FailedPreconditionError(
501           "Exceeded max resolving depth limit");
502     }
503     std::string resolved_lib = ResolveLibraryPath(lib, full_search_paths);
504     if (resolved_lib.empty()) {
505       SAPI_RAW_LOG(ERROR, "Failed to resolve library: %s", lib.c_str());
506       continue;
507     }
508     if (imported_libraries.contains(resolved_lib)) {
509       continue;
510     }
511 
512     SAPI_RAW_VLOG(1, "Resolved library: %s => %s", lib.c_str(),
513                   resolved_lib.c_str());
514 
515     imported_libraries.insert(resolved_lib);
516     if (imported_libraries.size() > kMaxImportedLibraries) {
517       return absl::FailedPreconditionError(
518           "Exceeded max imported libraries limit");
519     }
520     ++loaded;
521     if (loaded > kMaxLoadedEntries) {
522       return absl::FailedPreconditionError("Exceeded max loaded entries limit");
523     }
524     SAPI_ASSIGN_OR_RETURN(
525         auto lib_elf,
526         ElfFile::ParseFromFile(resolved_lib, ElfFile::kLoadImportedLibraries));
527     auto imported_libs = lib_elf.imported_libraries();
528     if (imported_libs.size() > kMaxWorkQueueSize - to_resolve.size()) {
529       return absl::FailedPreconditionError(
530           "Exceeded max entries pending resolving limit");
531     }
532 
533     if (SAPI_RAW_VLOG_IS_ON(2)) {
534       SAPI_RAW_VLOG(2,
535                     "Transitive dependencies of %s to resolve (depth = %d): ",
536                     resolved_lib.c_str(), depth + 1);
537       LogContainer(imported_libs);
538     }
539 
540     for (const auto& imported_lib : imported_libs) {
541       to_resolve.emplace_back(imported_lib, depth + 1);
542     }
543   }
544 
545   imported_libraries.insert(interpreter);
546   for (const auto& lib : imported_libraries) {
547     SAPI_RETURN_IF_ERROR(AddFile(lib));
548   }
549 
550   return absl::OkStatus();
551 }
552 
AddTmpfs(absl::string_view inside,size_t sz)553 absl::Status Mounts::AddTmpfs(absl::string_view inside, size_t sz) {
554   MountTree::Node node;
555   auto tmpfs_node = node.mutable_tmpfs_node();
556   tmpfs_node->set_tmpfs_options(absl::StrCat("size=", sz));
557   return Insert(inside, node);
558 }
559 
560 namespace {
561 
GetMountFlagsFor(const std::string & path)562 uint64_t GetMountFlagsFor(const std::string& path) {
563   struct statvfs vfs;
564   if (TEMP_FAILURE_RETRY(statvfs(path.c_str(), &vfs)) == -1) {
565     SAPI_RAW_PLOG(ERROR, "statvfs");
566     return 0;
567   }
568 
569   uint64_t flags = 0;
570   using MountPair = std::pair<uint64_t, uint64_t>;
571   for (const auto& [mount_flag, vfs_flag] : {
572            MountPair(MS_RDONLY, ST_RDONLY),
573            MountPair(MS_NOSUID, ST_NOSUID),
574            MountPair(MS_NODEV, ST_NODEV),
575            MountPair(MS_NOEXEC, ST_NOEXEC),
576            MountPair(MS_SYNCHRONOUS, ST_SYNCHRONOUS),
577            MountPair(MS_MANDLOCK, ST_MANDLOCK),
578            MountPair(MS_NOATIME, ST_NOATIME),
579            MountPair(MS_NODIRATIME, ST_NODIRATIME),
580            MountPair(MS_RELATIME, ST_RELATIME),
581        }) {
582     if (vfs.f_flag & vfs_flag) {
583       flags |= mount_flag;
584     }
585   }
586   return flags;
587 }
588 
MountFlagsToString(uint64_t flags)589 std::string MountFlagsToString(uint64_t flags) {
590 #define SAPI_MAP(x) {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 may be the case when trying to
642       // gather stack-traces on SAPI crashes. The sandboxee application is a
643       // memfd file that is not existing anymore.
644       // Check which file/dir of the call is actually missing.
645       bool have_source =
646           file_util::fileops::Exists(source, /*fully_resolve=*/true);
647       bool have_target =
648           file_util::fileops::Exists(target, /*fully_resolve=*/true);
649       const char* detail = "unknown error, source and target exist";
650       if (!have_source && !have_target) {
651         detail = "neither source nor target exist";
652       } else if (!have_source) {
653         detail = "source does not exist";
654       } else if (!have_target) {
655         detail = "target does not exist";
656       }
657       SAPI_RAW_LOG(WARNING, "Could not mount %s (source) to %s (target): %s",
658                    source.c_str(), target.c_str(), detail);
659       return;
660     }
661     SAPI_RAW_PLOG(FATAL, "mounting %s to %s failed (flags=%s)", source, target,
662                   MountFlagsToString(flags));
663   }
664 
665   // Flags are ignored for a bind mount, a remount is needed to set the flags.
666   if (extra_flags & MS_BIND) {
667     // Get actual mount flags.
668     uint64_t target_flags = GetMountFlagsFor(target);
669     if ((target_flags & MS_RDONLY) != 0 && (flags & MS_RDONLY) == 0) {
670       SAPI_RAW_LOG(FATAL,
671                    "cannot remount %s as read-write as it's on read-only dev",
672                    target.c_str());
673     }
674     res = mount("", target.c_str(), "", flags | target_flags | MS_REMOUNT,
675                 nullptr);
676     SAPI_RAW_PCHECK(res != -1, "remounting %s with flags=%s failed", target,
677                     MountFlagsToString(flags));
678   }
679 
680   // Mount propagation has to be set separately
681   const uint64_t propagation =
682       extra_flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE);
683   if (propagation != 0) {
684     res = mount("", target.c_str(), "", propagation, nullptr);
685     SAPI_RAW_PCHECK(res != -1, "changing %s mount propagation to %s failed",
686                     target, MountFlagsToString(propagation).c_str());
687   }
688 }
689 
690 using MapEntry = std::pair<absl::string_view, const MountTree*>;
691 
GetSortedEntries(const MountTree & tree)692 std::vector<MapEntry> GetSortedEntries(const MountTree& tree) {
693   std::vector<MapEntry> ordered;
694   ordered.reserve(tree.entries_size());
695   for (auto& entry : tree.entries()) {
696     ordered.emplace_back(entry.first, &entry.second);
697   }
698   std::sort(ordered.begin(), ordered.end(),
699             [](const MapEntry& a, const MapEntry& b) {
700               return a.second->index() < b.second->index();
701             });
702   return ordered;
703 }
704 
IsSymlink(const std::string & path)705 bool IsSymlink(const std::string& path) {
706   struct stat sb;
707   if (stat(path.c_str(), &sb) == -1) {
708     return false;
709   }
710   return S_ISLNK(sb.st_mode);
711 }
712 
713 // Traverses the MountTree to create all required files and perform the mounts.
CreateMounts(const MountTree & tree,const std::string & root_path,const std::string & path,bool create_backing_files)714 void CreateMounts(const MountTree& tree, const std::string& root_path,
715                   const std::string& path, bool create_backing_files) {
716   // First, create the backing files if needed.
717   if (create_backing_files) {
718     switch (tree.node().node_case()) {
719       case MountTree::Node::kFileNode: {
720         SAPI_RAW_VLOG(2, "Creating backing file at %s", path.c_str());
721         int fd = open(path.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0600);
722         SAPI_RAW_PCHECK(fd != -1, "");
723         SAPI_RAW_PCHECK(close(fd) == 0, "");
724         break;
725       }
726       case MountTree::Node::kDirNode:
727       case MountTree::Node::kTmpfsNode:
728       case MountTree::Node::kRootNode:
729       case MountTree::Node::NODE_NOT_SET:
730         SAPI_RAW_VLOG(2, "Creating directory at %s", path.c_str());
731         SAPI_RAW_PCHECK(mkdir(path.c_str(), 0700) == 0 || errno == EEXIST, "");
732         break;
733         // Intentionally no default to make sure we handle all the cases.
734     }
735   }
736 
737   if (IsSymlink(path)) {
738     std::string abs_path;
739     if (!file_util::fileops::ReadLinkAbsolute(path, &abs_path)) {
740       SAPI_RAW_LOG(WARNING, "could not resolve mount target path %s",
741                    path.c_str());
742     } else if (!absl::StartsWith(abs_path, absl::StrCat(root_path, "/"))) {
743       SAPI_RAW_LOG(ERROR, "Mount target not within chroot: %s resolved to %s",
744                    path.c_str(), abs_path.c_str());
745     }
746   }
747 
748   // Perform the actual mounts based on the node type.
749   switch (tree.node().node_case()) {
750     case MountTree::Node::kDirNode: {
751       // Since this directory is bind mounted, it's the users
752       // responsibility to make sure that all backing files are in place.
753       create_backing_files = false;
754 
755       auto node = tree.node().dir_node();
756       MountWithDefaults(node.outside(), path, "", MS_BIND, nullptr,
757                         !node.writable());
758       break;
759     }
760     case MountTree::Node::kTmpfsNode: {
761       // We can always create backing files under a tmpfs.
762       create_backing_files = true;
763 
764       auto node = tree.node().tmpfs_node();
765       MountWithDefaults("", path, "tmpfs", 0, node.tmpfs_options().c_str(),
766                         /* is_ro */ false);
767       break;
768     }
769     case MountTree::Node::kFileNode: {
770       auto node = tree.node().file_node();
771       MountWithDefaults(node.outside(), path, "", MS_BIND, nullptr,
772                         !node.writable());
773 
774       // A file node has to be a leaf so we can skip traversing here.
775       return;
776     }
777     case MountTree::Node::kRootNode:
778     case MountTree::Node::NODE_NOT_SET:
779       // Nothing to do, we already created the directory above.
780       break;
781       // Intentionally no default to make sure we handle all the cases.
782   }
783 
784   // Traverse the subtrees.
785   for (const auto& [key, value] : GetSortedEntries(tree)) {
786     std::string new_path = sapi::file::JoinPath(path, key);
787     CreateMounts(*value, root_path, new_path, create_backing_files);
788   }
789 }
790 
791 }  // namespace
792 
CreateMounts(const std::string & root_path) const793 void Mounts::CreateMounts(const std::string& root_path) const {
794   sandbox2::CreateMounts(mount_tree_, root_path, root_path, true);
795 }
796 
797 namespace {
798 
RecursivelyListMountsImpl(const MountTree & tree,const std::string & tree_path,std::vector<std::string> * outside_entries,std::vector<std::string> * inside_entries)799 void RecursivelyListMountsImpl(const MountTree& tree,
800                                const std::string& tree_path,
801                                std::vector<std::string>* outside_entries,
802                                std::vector<std::string>* inside_entries) {
803   const MountTree::Node& node = tree.node();
804   if (node.has_dir_node()) {
805     const char* rw_str = node.dir_node().writable() ? "W " : "R ";
806     inside_entries->emplace_back(absl::StrCat(rw_str, tree_path, "/"));
807     outside_entries->emplace_back(absl::StrCat(node.dir_node().outside(), "/"));
808   } else if (node.has_file_node()) {
809     const char* rw_str = node.file_node().writable() ? "W " : "R ";
810     inside_entries->emplace_back(absl::StrCat(rw_str, tree_path));
811     outside_entries->emplace_back(absl::StrCat(node.file_node().outside()));
812   } else if (node.has_tmpfs_node()) {
813     inside_entries->emplace_back(tree_path);
814     outside_entries->emplace_back(
815         absl::StrCat("tmpfs: ", node.tmpfs_node().tmpfs_options()));
816   }
817 
818   for (const auto& [key, value] : GetSortedEntries(tree)) {
819     std::string new_path = sapi::file::JoinPath(tree_path, key);
820     RecursivelyListMountsImpl(*value, absl::StrCat(tree_path, "/", key),
821                               outside_entries, inside_entries);
822   }
823 }
824 
825 }  // namespace
826 
RecursivelyListMounts(std::vector<std::string> * outside_entries,std::vector<std::string> * inside_entries) const827 void Mounts::RecursivelyListMounts(
828     std::vector<std::string>* outside_entries,
829     std::vector<std::string>* inside_entries) const {
830   RecursivelyListMountsImpl(GetMountTree(), "", outside_entries,
831                             inside_entries);
832 }
833 
834 }  // namespace sandbox2
835