diff --git a/sandboxed_api/sandbox2/BUILD.bazel b/sandboxed_api/sandbox2/BUILD.bazel index 2936beb..e7126c0 100644 --- a/sandboxed_api/sandbox2/BUILD.bazel +++ b/sandboxed_api/sandbox2/BUILD.bazel @@ -495,6 +495,7 @@ cc_test( data = ["//sandboxed_api/sandbox2/testcases:minimal_dynamic"], deps = [ ":mounts", + ":mounttree_cc_proto", "//sandboxed_api:testing", "//sandboxed_api/util:file_base", "//sandboxed_api/util:file_helpers", diff --git a/sandboxed_api/sandbox2/CMakeLists.txt b/sandboxed_api/sandbox2/CMakeLists.txt index 0eb1f74..6c76860 100644 --- a/sandboxed_api/sandbox2/CMakeLists.txt +++ b/sandboxed_api/sandbox2/CMakeLists.txt @@ -667,6 +667,7 @@ if(SAPI_ENABLE_TESTS) absl::strings sapi::file_base sandbox2::mounts + sandbox2::mounttree_proto sapi::temp_file sapi::testing sapi::status_matchers diff --git a/sandboxed_api/sandbox2/mounts.cc b/sandboxed_api/sandbox2/mounts.cc index e55c382..dd943b7 100644 --- a/sandboxed_api/sandbox2/mounts.cc +++ b/sandboxed_api/sandbox2/mounts.cc @@ -55,43 +55,6 @@ bool PathContainsNullByte(absl::string_view path) { return absl::StrContains(path, '\0'); } -bool IsSameFile(const std::string& path1, const std::string& path2) { - struct stat stat1, stat2; - if (stat(path1.c_str(), &stat1) == -1) { - return false; - } - - if (stat(path2.c_str(), &stat2) == -1) { - return false; - } - - return stat1.st_dev == stat2.st_dev && stat1.st_ino == stat2.st_ino; -} - -bool IsEquivalentNode(const sandbox2::MountTree::Node& n1, - const sandbox2::MountTree::Node& n2) { - // Node equals 1:1 - if (google::protobuf::util::MessageDifferencer::Equals(n1, n2)) { - return true; - } - - if (n1.node_case() != n2.node_case()) { - return false; - } - - // Check whether files/dirs are the same (e.g symlinks / hardlinks) - switch (n1.node_case()) { - case sandbox2::MountTree::Node::kFileNode: - return n1.file_node().is_ro() == n2.file_node().is_ro() && - IsSameFile(n1.file_node().outside(), n2.file_node().outside()); - case sandbox2::MountTree::Node::kDirNode: - return n1.dir_node().is_ro() == n2.dir_node().is_ro() && - IsSameFile(n1.dir_node().outside(), n2.dir_node().outside()); - default: - return false; - } -} - absl::string_view GetOutsidePath(const MountTree::Node& node) { switch (node.node_case()) { case MountTree::Node::kFileNode: @@ -162,6 +125,47 @@ std::string GetPlatform(absl::string_view interpreter) { } // namespace +namespace internal { + +bool IsSameFile(const std::string& path1, const std::string& path2) { + struct stat stat1, stat2; + if (stat(path1.c_str(), &stat1) == -1) { + return false; + } + + if (stat(path2.c_str(), &stat2) == -1) { + return false; + } + + return stat1.st_dev == stat2.st_dev && stat1.st_ino == stat2.st_ino; +} + +bool IsEquivalentNode(const sandbox2::MountTree::Node& n1, + const sandbox2::MountTree::Node& n2) { + // Node equals 1:1 + if (google::protobuf::util::MessageDifferencer::Equals(n1, n2)) { + return true; + } + + if (n1.node_case() != n2.node_case()) { + return false; + } + + // Check whether files/dirs are the same (e.g symlinks / hardlinks) + switch (n1.node_case()) { + case sandbox2::MountTree::Node::kFileNode: + return n1.file_node().is_ro() == n2.file_node().is_ro() && + IsSameFile(n1.file_node().outside(), n2.file_node().outside()); + case sandbox2::MountTree::Node::kDirNode: + return n1.dir_node().is_ro() == n2.dir_node().is_ro() && + IsSameFile(n1.dir_node().outside(), n2.dir_node().outside()); + default: + return false; + } +} + +} // namespace internal + absl::Status Mounts::Insert(absl::string_view path, const MountTree::Node& new_node) { // Some sandboxes allow the inside/outside paths to be partially @@ -225,7 +229,7 @@ absl::Status Mounts::Insert(absl::string_view path, .first->second); if (curtree->has_node()) { - if (IsEquivalentNode(curtree->node(), new_node)) { + if (internal::IsEquivalentNode(curtree->node(), new_node)) { SAPI_RAW_LOG(INFO, "Inserting %s with the same value twice", std::string(path).c_str()); return absl::OkStatus(); diff --git a/sandboxed_api/sandbox2/mounts.h b/sandboxed_api/sandbox2/mounts.h index b7bb8b4..079745e 100644 --- a/sandboxed_api/sandbox2/mounts.h +++ b/sandboxed_api/sandbox2/mounts.h @@ -26,6 +26,12 @@ namespace sandbox2 { +namespace internal { + +bool IsSameFile(const std::string& path1, const std::string& path2); +bool IsEquivalentNode(const MountTree::Node& n1, const MountTree::Node& n2); +} // namespace internal + class Mounts { public: Mounts() { diff --git a/sandboxed_api/sandbox2/mounts_test.cc b/sandboxed_api/sandbox2/mounts_test.cc index 5df5a3d..4587996 100644 --- a/sandboxed_api/sandbox2/mounts_test.cc +++ b/sandboxed_api/sandbox2/mounts_test.cc @@ -178,7 +178,7 @@ TEST(MountTreeTest, TestMinimalDynamicBinary) { TEST(MountTreeTest, TestList) { struct TestCase { - const char *path; + const char* path; const bool is_ro; }; // clang-format off @@ -196,7 +196,7 @@ TEST(MountTreeTest, TestList) { Mounts mounts; // Create actual directories and files on disk and selectively add - for (const auto &test_case : kTestCases) { + for (const auto& test_case : kTestCases) { const auto inside_path = test_case.path; const std::string outside_path = absl::StrCat("/some/dir/", inside_path); if (absl::EndsWith(outside_path, "/")) { @@ -244,6 +244,61 @@ TEST(MountTreeTest, TestList) { // clang-format on } +TEST(MountTreeTest, TestNodeEquivalence) { + MountTree::Node nodes[8]; + MountTree::FileNode* fn0 = nodes[0].mutable_file_node(); + fn0->set_is_ro(true); + fn0->set_outside("foo"); + MountTree::FileNode* fn1 = nodes[1].mutable_file_node(); + fn1->set_is_ro(true); + fn1->set_outside("bar"); + MountTree::DirNode* dn0 = nodes[2].mutable_dir_node(); + dn0->set_is_ro(true); + dn0->set_outside("foo"); + MountTree::DirNode* dn1 = nodes[3].mutable_dir_node(); + dn1->set_is_ro(true); + dn1->set_outside("bar"); + MountTree::TmpfsNode* tn0 = nodes[4].mutable_tmpfs_node(); + tn0->set_tmpfs_options("option1"); + MountTree::TmpfsNode* tn1 = nodes[5].mutable_tmpfs_node(); + tn1->set_tmpfs_options("option2"); + MountTree::RootNode* rn0 = nodes[6].mutable_root_node(); + rn0->set_is_ro(true); + MountTree::RootNode* rn1 = nodes[7].mutable_root_node(); + rn1->set_is_ro(false); + + for (const MountTree::Node n : nodes) { + ASSERT_TRUE(n.IsInitialized()); + } + // Compare same file nodes + EXPECT_TRUE(internal::IsEquivalentNode(nodes[0], nodes[0])); + // Compare with different file node + EXPECT_FALSE(internal::IsEquivalentNode(nodes[0], nodes[1])); + // compare file node with dir node + EXPECT_FALSE(internal::IsEquivalentNode(nodes[0], nodes[2])); + + // Compare same dir nodes + EXPECT_TRUE(internal::IsEquivalentNode(nodes[2], nodes[2])); + // Compare with different dir node + EXPECT_FALSE(internal::IsEquivalentNode(nodes[2], nodes[3])); + // Compare dir node with tmpfs node + EXPECT_FALSE(internal::IsEquivalentNode(nodes[2], nodes[4])); + + // Compare same tmpfs nodes + EXPECT_TRUE(internal::IsEquivalentNode(nodes[4], nodes[4])); + // Compare with different tmpfs nodes + EXPECT_FALSE(internal::IsEquivalentNode(nodes[4], nodes[5])); + // Compare tmpfs node with root node + EXPECT_FALSE(internal::IsEquivalentNode(nodes[4], nodes[6])); + + // Compare same root nodes + EXPECT_TRUE(internal::IsEquivalentNode(nodes[6], nodes[6])); + // Compare different root node + EXPECT_FALSE(internal::IsEquivalentNode(nodes[6], nodes[7])); + // Compare root node with file node + EXPECT_FALSE(internal::IsEquivalentNode(nodes[6], nodes[0])); +} + TEST(MountsResolvePathTest, Files) { Mounts mounts; ASSERT_THAT(mounts.AddFileAt("/A", "/a"), IsOk());