From f29a5a81ed7a8a3c8c605399fa8d37401aabd9b8 Mon Sep 17 00:00:00 2001 From: Sandboxed API Team Date: Tue, 7 May 2019 18:29:51 -0700 Subject: [PATCH] Print final FS mounts in sandboxee's chroot After all requested filesystem mounts are fully mounted under a sandboxee's virtual chroot, print a list of the outside paths and a list of the inside chroot paths that the outside paths are mapped to. This provides a valuable insight while debugging sandboxed binaries. PiperOrigin-RevId: 247130923 Change-Id: I42b4b3db68d826587c0fe8127aabbead38bc6f20 --- sandboxed_api/sandbox2/BUILD.bazel | 3 +- sandboxed_api/sandbox2/monitor.cc | 26 ++++++++++- sandboxed_api/sandbox2/mounts.cc | 37 ++++++++++++++- sandboxed_api/sandbox2/mounts.h | 10 ++++ sandboxed_api/sandbox2/mounts_test.cc | 67 +++++++++++++++++++++++++++ 5 files changed, 139 insertions(+), 4 deletions(-) diff --git a/sandboxed_api/sandbox2/BUILD.bazel b/sandboxed_api/sandbox2/BUILD.bazel index 96242ce..d164e5a 100644 --- a/sandboxed_api/sandbox2/BUILD.bazel +++ b/sandboxed_api/sandbox2/BUILD.bazel @@ -299,6 +299,7 @@ cc_library( "//sandboxed_api/sandbox2/util:bpf_helper", "//sandboxed_api/sandbox2/util:file_base", "//sandboxed_api/sandbox2/util:fileops", + "//sandboxed_api/util:raw_logging", "//sandboxed_api/util:status", "//sandboxed_api/util:statusor", @@ -377,7 +378,6 @@ cc_library( "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/strings", - "@com_google_absl//absl/strings:str_format", "@com_google_protobuf//:protobuf", ], ) @@ -390,6 +390,7 @@ cc_test( ":mounts", ":testing", "//sandboxed_api/sandbox2/util:file_base", + "//sandboxed_api/sandbox2/util:file_helpers", "//sandboxed_api/sandbox2/util:temp_file", "//sandboxed_api/util:status_matchers", "@com_google_absl//absl/strings", diff --git a/sandboxed_api/sandbox2/monitor.cc b/sandboxed_api/sandbox2/monitor.cc index f48e91f..b69fa74 100644 --- a/sandboxed_api/sandbox2/monitor.cc +++ b/sandboxed_api/sandbox2/monitor.cc @@ -16,9 +16,10 @@ #include "sandboxed_api/sandbox2/monitor.h" +// clang-format off #include // NOLINT: Needs to come before linux/ipc.h - #include +// clang-format on #include #include #include @@ -59,6 +60,7 @@ #include "sandboxed_api/sandbox2/stack-trace.h" #include "sandboxed_api/sandbox2/syscall.h" #include "sandboxed_api/sandbox2/util.h" +#include "sandboxed_api/util/raw_logging.h" ABSL_FLAG(bool, sandbox2_report_on_sandboxee_signal, true, "Report sandbox2 sandboxee deaths caused by signals"); @@ -108,6 +110,16 @@ Monitor::~Monitor() { } } +namespace { + +void LogContainer(const std::vector& container) { + for (size_t i = 0; i < container.size(); ++i) { + SAPI_RAW_LOG(INFO, "[%4d]=%s", i, container[i]); + } +} + +} // namespace + void Monitor::Run() { using DecrementCounter = decltype(setup_counter_); std::unique_ptr> @@ -139,6 +151,18 @@ void Monitor::Run() { return; } + if (SAPI_VLOG_IS_ON(1) && policy_->GetNamespace() != nullptr) { + std::vector outside_entries; + std::vector inside_entries; + policy_->GetNamespace()->mounts().RecursivelyListMounts( + /*outside_entries=*/&outside_entries, + /*inside_entries=*/&inside_entries); + SAPI_RAW_VLOG(1, "Outside entries mapped to chroot:"); + LogContainer(outside_entries); + SAPI_RAW_VLOG(1, "Inside entries as they appear in chroot:"); + LogContainer(inside_entries); + } + // Don't trace the child: it will allow to use 'strace -f' with the whole // sandbox master/monitor, which ptrace_attach'es to the child. int clone_flags = CLONE_UNTRACED; diff --git a/sandboxed_api/sandbox2/mounts.cc b/sandboxed_api/sandbox2/mounts.cc index e8b98b9..9828023 100644 --- a/sandboxed_api/sandbox2/mounts.cc +++ b/sandboxed_api/sandbox2/mounts.cc @@ -30,8 +30,6 @@ #include "absl/strings/ascii.h" #include "absl/strings/match.h" #include "absl/strings/str_cat.h" -#include "absl/strings/str_format.h" -#include "absl/strings/str_join.h" #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" #include "sandboxed_api/sandbox2/util/fileops.h" @@ -521,4 +519,39 @@ void Mounts::CreateMounts(const std::string& root_path) const { sandbox2::CreateMounts(mount_tree_, root_path, true); } +namespace { + +void RecursivelyListMountsImpl(const MountTree& tree, + const std::string& tree_path, + std::vector* outside_entries, + std::vector* inside_entries) { + const MountTree::Node& node = tree.node(); + if (node.has_dir_node()) { + const char* rw_str = node.dir_node().is_ro() ? "R " : "W "; + inside_entries->emplace_back(absl::StrCat(rw_str, tree_path, "/")); + outside_entries->emplace_back(absl::StrCat(node.dir_node().outside(), "/")); + } else if (node.has_file_node()) { + const char* rw_str = node.file_node().is_ro() ? "R " : "W "; + inside_entries->emplace_back(absl::StrCat(rw_str, tree_path)); + outside_entries->emplace_back(absl::StrCat(node.file_node().outside())); + } else if (node.has_tmpfs_node()) { + outside_entries->emplace_back( + absl::StrCat("tmpfs: ", node.tmpfs_node().tmpfs_options())); + } + + for (const auto& subentry : tree.entries()) { + RecursivelyListMountsImpl(subentry.second, + absl::StrCat(tree_path, "/", subentry.first), + outside_entries, inside_entries); + } +} + +} // namespace + +void Mounts::RecursivelyListMounts(std::vector* outside_entries, + std::vector* inside_entries) { + RecursivelyListMountsImpl(GetMountTree(), "", outside_entries, + inside_entries); +} + } // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/mounts.h b/sandboxed_api/sandbox2/mounts.h index 8ba346a..52003ee 100644 --- a/sandboxed_api/sandbox2/mounts.h +++ b/sandboxed_api/sandbox2/mounts.h @@ -47,6 +47,16 @@ class Mounts { MountTree GetMountTree() const { return mount_tree_; } + // Lists the outside and inside entries of the input tree in the output + // parameters, in an ls-like manner. Each entry is traversed in the + // depth-first order. However, the entries on the same level of hierarchy are + // traversed in their natural order in the tree. The elements in the output + // containers match each other pairwise: outside_entries[i] is mounted as + // inside_entries[i]. The elements of inside_entries are prefixed with either + // 'R' (read-only) or 'W' (writable). + void RecursivelyListMounts(std::vector* outside_entries, + std::vector* inside_entries); + private: friend class MountTreeTest; ::sapi::Status Insert(absl::string_view path, const MountTree::Node& node); diff --git a/sandboxed_api/sandbox2/mounts_test.cc b/sandboxed_api/sandbox2/mounts_test.cc index cefb670..e026648 100644 --- a/sandboxed_api/sandbox2/mounts_test.cc +++ b/sandboxed_api/sandbox2/mounts_test.cc @@ -20,8 +20,10 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/strings/match.h" #include "absl/strings/str_cat.h" #include "sandboxed_api/sandbox2/testing.h" +#include "sandboxed_api/sandbox2/util/file_helpers.h" #include "sandboxed_api/sandbox2/util/path.h" #include "sandboxed_api/sandbox2/util/temp_file.h" #include "sandboxed_api/util/status_matchers.h" @@ -29,6 +31,7 @@ using sapi::IsOk; using sapi::StatusIs; using ::testing::Eq; +using ::testing::UnorderedElementsAreArray; namespace sandbox2 { namespace { @@ -166,5 +169,69 @@ TEST(MountTreeTest, TestMinimalDynamicBinary) { EXPECT_THAT(mounts.AddFile("/lib/x86_64-linux-gnu/libc.so.6"), IsOk()); } +TEST(MountTreeTest, TestList) { + struct TestCase { + const char *path; + const bool is_ro; + }; + // clang-format off + const TestCase test_cases[] = { + // NOTE: Directories have a trailing '/'; files don't. + {"/a/b", true}, + {"/a/c/", true}, + {"/a/c/d/e/f/g", true}, + {"/h", true}, + {"/i/j/k", false}, + {"/i/l/", false}, + }; + // clang-format on + + Mounts mounts; + + // Create actual directories and files on disk and selectively add + for (const auto &test_case : test_cases) { + const auto inside_path = test_case.path; + const std::string outside_path = absl::StrCat("/some/dir/", inside_path); + if (absl::EndsWith(outside_path, "/")) { + ASSERT_THAT( + mounts.AddDirectoryAt(file::CleanPath(outside_path), + file::CleanPath(inside_path), test_case.is_ro), + IsOk()); + } else { + ASSERT_THAT( + mounts.AddFileAt(file::CleanPath(outside_path), + file::CleanPath(inside_path), test_case.is_ro), + IsOk()); + } + } + + std::vector outside_entries; + std::vector inside_entries; + mounts.RecursivelyListMounts(&outside_entries, &inside_entries); + + // clang-format off + EXPECT_THAT( + inside_entries, + UnorderedElementsAreArray({ + "R /a/b", + "R /a/c/", + "R /a/c/d/e/f/g", + "R /h", + "W /i/j/k", + "W /i/l/", + })); + EXPECT_THAT( + outside_entries, + UnorderedElementsAreArray({ + absl::StrCat("/some/dir/", "a/b"), + absl::StrCat("/some/dir/", "a/c/"), + absl::StrCat("/some/dir/", "a/c/d/e/f/g"), + absl::StrCat("/some/dir/", "h"), + absl::StrCat("/some/dir/", "i/j/k"), + absl::StrCat("/some/dir/", "i/l/"), + })); + // clang-format on +} + } // namespace } // namespace sandbox2