diff options
-rw-r--r-- | pkg/sentry/vfs/mount.go | 3 | ||||
-rw-r--r-- | test/syscalls/linux/BUILD | 2 | ||||
-rw-r--r-- | test/syscalls/linux/cgroup.cc | 52 | ||||
-rw-r--r-- | test/syscalls/linux/mount.cc | 33 | ||||
-rw-r--r-- | test/syscalls/linux/proc.cc | 27 | ||||
-rw-r--r-- | test/util/BUILD | 3 | ||||
-rw-r--r-- | test/util/cgroup_util.cc | 2 | ||||
-rw-r--r-- | test/util/cgroup_util.h | 4 | ||||
-rw-r--r-- | test/util/mount_util.cc | 166 | ||||
-rw-r--r-- | test/util/mount_util.h | 38 |
10 files changed, 328 insertions, 2 deletions
diff --git a/pkg/sentry/vfs/mount.go b/pkg/sentry/vfs/mount.go index 7cdab6945..82fd382c2 100644 --- a/pkg/sentry/vfs/mount.go +++ b/pkg/sentry/vfs/mount.go @@ -826,6 +826,9 @@ func (vfs *VirtualFilesystem) GenerateProcMounts(ctx context.Context, taskRootDi if mnt.Flags.NoExec { opts += ",noexec" } + if mopts := mnt.fs.Impl().MountOptions(); mopts != "" { + opts += "," + mopts + } // Format: // <special device or remote filesystem> <mount point> <filesystem type> <mount options> <needs dump> <fsck order> diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 55f3fc4ae..94a582256 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -1726,6 +1726,7 @@ cc_binary( "//test/util:cleanup", "//test/util:file_descriptor", "//test/util:fs_util", + "//test/util:mount_util", "@com_google_absl//absl/container:node_hash_set", "@com_google_absl//absl/strings", "@com_google_absl//absl/synchronization", @@ -4243,6 +4244,7 @@ cc_binary( "//test/util:cgroup_util", "//test/util:file_descriptor", "//test/util:fs_util", + "//test/util:mount_util", "@com_google_absl//absl/strings", gtest, "//test/util:posix_error", diff --git a/test/syscalls/linux/cgroup.cc b/test/syscalls/linux/cgroup.cc index 862328f5b..70ad5868f 100644 --- a/test/syscalls/linux/cgroup.cc +++ b/test/syscalls/linux/cgroup.cc @@ -25,6 +25,7 @@ #include "absl/strings/str_split.h" #include "test/util/capability_util.h" #include "test/util/cgroup_util.h" +#include "test/util/mount_util.h" #include "test/util/temp_path.h" #include "test/util/test_util.h" @@ -33,8 +34,11 @@ namespace testing { namespace { using ::testing::_; +using ::testing::Contains; using ::testing::Ge; using ::testing::Gt; +using ::testing::Key; +using ::testing::Not; std::vector<std::string> known_controllers = { "cpu", "cpuset", "cpuacct", "job", "memory", @@ -447,6 +451,54 @@ TEST(ProcCgroup, MultiControllerHierarchy) { EXPECT_EQ(pid_e.hierarchy, mem_e.hierarchy); } +TEST(ProcCgroup, ProcfsReportsCgroupfsMountOptions) { + SKIP_IF(!CgroupsAvailable()); + + Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); + // Hierarchy with multiple controllers. + Cgroup c1 = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("memory,cpu")); + // Hierarchy with a single controller. + Cgroup c2 = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("cpuacct")); + + const std::vector<ProcMountsEntry> mounts = + ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountsEntries()); + + for (auto const& e : mounts) { + if (e.mount_point == c1.Path()) { + auto mopts = ParseMountOptions(e.mount_opts); + EXPECT_THAT(mopts, Contains(Key("memory"))); + EXPECT_THAT(mopts, Contains(Key("cpu"))); + EXPECT_THAT(mopts, Not(Contains(Key("cpuacct")))); + } + + if (e.mount_point == c2.Path()) { + auto mopts = ParseMountOptions(e.mount_opts); + EXPECT_THAT(mopts, Contains(Key("cpuacct"))); + EXPECT_THAT(mopts, Not(Contains(Key("cpu")))); + EXPECT_THAT(mopts, Not(Contains(Key("memory")))); + } + } + + const std::vector<ProcMountInfoEntry> mountinfo = + ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); + + for (auto const& e : mountinfo) { + if (e.mount_point == c1.Path()) { + auto mopts = ParseMountOptions(e.super_opts); + EXPECT_THAT(mopts, Contains(Key("memory"))); + EXPECT_THAT(mopts, Contains(Key("cpu"))); + EXPECT_THAT(mopts, Not(Contains(Key("cpuacct")))); + } + + if (e.mount_point == c2.Path()) { + auto mopts = ParseMountOptions(e.super_opts); + EXPECT_THAT(mopts, Contains(Key("cpuacct"))); + EXPECT_THAT(mopts, Not(Contains(Key("cpu")))); + EXPECT_THAT(mopts, Not(Contains(Key("memory")))); + } + } +} + } // namespace } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/mount.cc b/test/syscalls/linux/mount.cc index 15b645fb7..cdc223d07 100644 --- a/test/syscalls/linux/mount.cc +++ b/test/syscalls/linux/mount.cc @@ -26,6 +26,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/strings/str_split.h" #include "absl/strings/string_view.h" #include "absl/time/time.h" #include "test/util/capability_util.h" @@ -44,6 +45,9 @@ namespace testing { namespace { +using ::testing::Contains; +using ::testing::Pair; + TEST(MountTest, MountBadFilesystem) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); @@ -345,6 +349,35 @@ TEST(MountTest, RenameRemoveMountPoint) { ASSERT_THAT(rmdir(dir.path().c_str()), SyscallFailsWithErrno(EBUSY)); } +TEST(MountTest, MountInfo) { + SKIP_IF(IsRunningWithVFS1()); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); + + auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const mount = ASSERT_NO_ERRNO_AND_VALUE( + Mount("", dir.path(), "tmpfs", MS_NOEXEC, "mode=0123", 0)); + const std::vector<ProcMountsEntry> mounts = + ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountsEntries()); + for (const auto& e : mounts) { + if (e.mount_point == dir.path()) { + EXPECT_EQ(e.fstype, "tmpfs"); + auto mopts = ParseMountOptions(e.mount_opts); + EXPECT_THAT(mopts, Contains(Pair("mode", "0123"))); + } + } + + const std::vector<ProcMountInfoEntry> mountinfo = + ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); + + for (auto const& e : mountinfo) { + if (e.mount_point == dir.path()) { + EXPECT_EQ(e.fstype, "tmpfs"); + auto mopts = ParseMountOptions(e.super_opts); + EXPECT_THAT(mopts, Contains(Pair("mode", "0123"))); + } + } +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/proc.cc b/test/syscalls/linux/proc.cc index 6b055ea89..9e48fbca5 100644 --- a/test/syscalls/linux/proc.cc +++ b/test/syscalls/linux/proc.cc @@ -65,6 +65,7 @@ #include "test/util/file_descriptor.h" #include "test/util/fs_util.h" #include "test/util/memory_util.h" +#include "test/util/mount_util.h" #include "test/util/multiprocess_util.h" #include "test/util/posix_error.h" #include "test/util/proc_util.h" @@ -2468,6 +2469,19 @@ TEST(ProcSelfMountinfo, RequiredFieldsArePresent) { R"([0-9]+ [0-9]+ [0-9]+:[0-9]+ / /proc rw.*- \S+ \S+ rw\S*)"))); } +TEST(ProcSelfMountinfo, ContainsProcfsEntry) { + const std::vector<ProcMountInfoEntry> entries = + ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); + bool found = false; + for (const auto& e : entries) { + if (e.fstype == "proc") { + found = true; + break; + } + } + EXPECT_TRUE(found); +} + // Check that /proc/self/mounts looks something like a real mounts file. TEST(ProcSelfMounts, RequiredFieldsArePresent) { auto mounts = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/mounts")); @@ -2479,6 +2493,19 @@ TEST(ProcSelfMounts, RequiredFieldsArePresent) { ContainsRegex(R"(\S+ /proc \S+ rw\S* [0-9]+ [0-9]+\s)"))); } +TEST(ProcSelfMounts, ContainsProcfsEntry) { + const std::vector<ProcMountsEntry> entries = + ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountsEntries()); + bool found = false; + for (const auto& e : entries) { + if (e.fstype == "proc") { + found = true; + break; + } + } + EXPECT_TRUE(found); +} + void CheckDuplicatesRecursively(std::string path) { std::vector<std::string> child_dirs; diff --git a/test/util/BUILD b/test/util/BUILD index 383de00ed..6feda0e26 100644 --- a/test/util/BUILD +++ b/test/util/BUILD @@ -137,11 +137,14 @@ cc_library( cc_library( name = "mount_util", testonly = 1, + srcs = ["mount_util.cc"], hdrs = ["mount_util.h"], deps = [ ":cleanup", ":posix_error", ":test_util", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/strings", gtest, ], ) diff --git a/test/util/cgroup_util.cc b/test/util/cgroup_util.cc index d8d3fe471..04d4f8de0 100644 --- a/test/util/cgroup_util.cc +++ b/test/util/cgroup_util.cc @@ -25,7 +25,7 @@ namespace gvisor { namespace testing { -Cgroup::Cgroup(std::string path) : cgroup_path_(path) { +Cgroup::Cgroup(std::string_view path) : cgroup_path_(path) { id_ = ++Cgroup::next_id_; std::cerr << absl::StreamFormat("[cg#%d] <= %s", id_, cgroup_path_) << std::endl; diff --git a/test/util/cgroup_util.h b/test/util/cgroup_util.h index c6e4303e1..b797a8b24 100644 --- a/test/util/cgroup_util.h +++ b/test/util/cgroup_util.h @@ -30,10 +30,12 @@ namespace testing { // Cgroup represents a cgroup directory on a mounted cgroupfs. class Cgroup { public: - Cgroup(std::string path); + Cgroup(std::string_view path); uint64_t id() const { return id_; } + const std::string& Path() const { return cgroup_path_; } + std::string Relpath(absl::string_view leaf) const { return JoinPath(cgroup_path_, leaf); } diff --git a/test/util/mount_util.cc b/test/util/mount_util.cc new file mode 100644 index 000000000..a79ce6420 --- /dev/null +++ b/test/util/mount_util.cc @@ -0,0 +1,166 @@ +// Copyright 2021 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "test/util/mount_util.h" + +#include <sys/syscall.h> +#include <unistd.h> + +#include "absl/strings/numbers.h" +#include "absl/strings/str_split.h" + +namespace gvisor { +namespace testing { + +PosixErrorOr<std::vector<ProcMountsEntry>> ProcSelfMountsEntries() { + std::string content; + RETURN_IF_ERRNO(GetContents("/proc/self/mounts", &content)); + + std::vector<ProcMountsEntry> entries; + std::vector<std::string> lines = absl::StrSplit(content, '\n'); + std::cerr << "<contents of /proc/self/mounts>" << std::endl; + for (const std::string& line : lines) { + std::cerr << line << std::endl; + if (line.empty()) { + continue; + } + + // Parse a single entry from /proc/self/mounts. + // + // Example entries: + // + // sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0 + // proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0 + // ^ ^ ^ ^ ^ ^ + // 0 1 2 3 4 5 + + ProcMountsEntry entry; + std::vector<std::string> fields = + absl::StrSplit(line, absl::ByChar(' '), absl::SkipEmpty()); + if (fields.size() != 6) { + return PosixError(EINVAL, + absl::StrFormat("Not enough tokens, got %d, line: %s", + fields.size(), line)); + } + + entry.spec = fields[0]; + entry.mount_point = fields[1]; + entry.fstype = fields[2]; + entry.mount_opts = fields[3]; + ASSIGN_OR_RETURN_ERRNO(entry.dump, Atoi<uint32_t>(fields[4])); + ASSIGN_OR_RETURN_ERRNO(entry.fsck, Atoi<uint32_t>(fields[5])); + + entries.push_back(entry); + } + std::cerr << "<end of /proc/self/mounts>" << std::endl; + + return entries; +} + +PosixErrorOr<std::vector<ProcMountInfoEntry>> ProcSelfMountInfoEntries() { + std::string content; + RETURN_IF_ERRNO(GetContents("/proc/self/mountinfo", &content)); + + std::vector<ProcMountInfoEntry> entries; + std::vector<std::string> lines = absl::StrSplit(content, '\n'); + std::cerr << "<contents of /proc/self/mountinfo>" << std::endl; + for (const std::string& line : lines) { + std::cerr << line << std::endl; + if (line.empty()) { + continue; + } + + // Parse a single entry from /proc/self/mountinfo. + // + // Example entries: + // + // 22 28 0:20 / /sys rw,relatime shared:7 - sysfs sysfs rw + // 23 28 0:21 / /proc rw,relatime shared:14 - proc proc rw + // ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ + // 0 1 2 3 4 5 6 7 8 9 10 + + ProcMountInfoEntry entry; + std::vector<std::string> fields = + absl::StrSplit(line, absl::ByChar(' '), absl::SkipEmpty()); + if (fields.size() < 10 || fields.size() > 11) { + return PosixError( + EINVAL, + absl::StrFormat("Unexpected number of tokens, got %d, line: %s", + fields.size(), line)); + } + + ASSIGN_OR_RETURN_ERRNO(entry.id, Atoi<uint64_t>(fields[0])); + ASSIGN_OR_RETURN_ERRNO(entry.parent_id, Atoi<uint64_t>(fields[1])); + + std::vector<std::string> devs = + absl::StrSplit(fields[2], absl::ByChar(':')); + if (devs.size() != 2) { + return PosixError( + EINVAL, + absl::StrFormat( + "Failed to parse dev number field %s: too many tokens, got %d", + fields[2], devs.size())); + } + ASSIGN_OR_RETURN_ERRNO(entry.major, Atoi<dev_t>(devs[0])); + ASSIGN_OR_RETURN_ERRNO(entry.minor, Atoi<dev_t>(devs[1])); + + entry.root = fields[3]; + entry.mount_point = fields[4]; + entry.mount_opts = fields[5]; + + // The optional field (fields[6]) may or may not be present. We know based + // on the total number of tokens. + int off = -1; + if (fields.size() == 11) { + entry.optional = fields[6]; + off = 0; + } + // Field 7 is the optional field terminator char '-'. + entry.fstype = fields[8 + off]; + entry.mount_source = fields[9 + off]; + entry.super_opts = fields[10 + off]; + + entries.push_back(entry); + } + std::cerr << "<end of /proc/self/mountinfo>" << std::endl; + + return entries; +} + +absl::flat_hash_map<std::string, std::string> ParseMountOptions( + std::string mopts) { + absl::flat_hash_map<std::string, std::string> entries; + const std::vector<std::string> tokens = + absl::StrSplit(mopts, absl::ByChar(','), absl::SkipEmpty()); + for (const auto& token : tokens) { + std::vector<std::string> kv = + absl::StrSplit(token, absl::MaxSplits('=', 1)); + if (kv.size() == 2) { + entries[kv[0]] = kv[1]; + } else if (kv.size() == 1) { + entries[kv[0]] = ""; + } else { + TEST_CHECK_MSG( + false, + absl::StrFormat( + "Invalid mount option token '%s', was split into %d subtokens", + token, kv.size()) + .c_str()); + } + } + return entries; +} + +} // namespace testing +} // namespace gvisor diff --git a/test/util/mount_util.h b/test/util/mount_util.h index 09e2281eb..b75a490fb 100644 --- a/test/util/mount_util.h +++ b/test/util/mount_util.h @@ -22,6 +22,7 @@ #include <string> #include "gmock/gmock.h" +#include "absl/container/flat_hash_map.h" #include "test/util/cleanup.h" #include "test/util/posix_error.h" #include "test/util/test_util.h" @@ -45,6 +46,43 @@ inline PosixErrorOr<Cleanup> Mount(const std::string& source, }); } +struct ProcMountsEntry { + std::string spec; + std::string mount_point; + std::string fstype; + std::string mount_opts; + uint32_t dump; + uint32_t fsck; +}; + +// ProcSelfMountsEntries returns a parsed representation of /proc/self/mounts. +PosixErrorOr<std::vector<ProcMountsEntry>> ProcSelfMountsEntries(); + +struct ProcMountInfoEntry { + uint64_t id; + uint64_t parent_id; + dev_t major; + dev_t minor; + std::string root; + std::string mount_point; + std::string mount_opts; + std::string optional; + std::string fstype; + std::string mount_source; + std::string super_opts; +}; + +// ProcSelfMountInfoEntries returns a parsed representation of +// /proc/self/mountinfo. +PosixErrorOr<std::vector<ProcMountInfoEntry>> ProcSelfMountInfoEntries(); + +// Interprets the input string mopts as a comma separated list of mount +// options. A mount option can either be just a value, or a key=value pair. For +// example, the string "rw,relatime,fd=7" will be parsed into a map like { "rw": +// "", "relatime": "", "fd": "7" }. +absl::flat_hash_map<std::string, std::string> ParseMountOptions( + std::string mopts); + } // namespace testing } // namespace gvisor |