summaryrefslogtreecommitdiffhomepage
path: root/test/syscalls/linux/mount.cc
diff options
context:
space:
mode:
Diffstat (limited to 'test/syscalls/linux/mount.cc')
-rw-r--r--test/syscalls/linux/mount.cc419
1 files changed, 0 insertions, 419 deletions
diff --git a/test/syscalls/linux/mount.cc b/test/syscalls/linux/mount.cc
deleted file mode 100644
index e2a41d172..000000000
--- a/test/syscalls/linux/mount.cc
+++ /dev/null
@@ -1,419 +0,0 @@
-// Copyright 2018 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 <errno.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <sys/mount.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#include <functional>
-#include <memory>
-#include <string>
-#include <vector>
-
-#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"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/mount_util.h"
-#include "test/util/multiprocess_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/save_util.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-using ::testing::AnyOf;
-using ::testing::Contains;
-using ::testing::Pair;
-
-TEST(MountTest, MountBadFilesystem) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- // Linux expects a valid target before it checks the file system name.
- auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- EXPECT_THAT(mount("", dir.path().c_str(), "foobar", 0, ""),
- SyscallFailsWithErrno(ENODEV));
-}
-
-TEST(MountTest, MountInvalidTarget) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- auto const dir = NewTempAbsPath();
- EXPECT_THAT(mount("", dir.c_str(), "tmpfs", 0, ""),
- SyscallFailsWithErrno(ENOENT));
-}
-
-TEST(MountTest, MountPermDenied) {
- // Clear CAP_SYS_ADMIN.
- AutoCapability cap(CAP_SYS_ADMIN, false);
-
- // Linux expects a valid target before checking capability.
- auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- EXPECT_THAT(mount("", dir.path().c_str(), "", 0, ""),
- SyscallFailsWithErrno(EPERM));
-}
-
-TEST(MountTest, UmountPermDenied) {
- 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", 0, "", 0));
-
- // Drop privileges in another thread, so we can still unmount the mounted
- // directory.
- ScopedThread([&]() {
- EXPECT_NO_ERRNO(SetCapability(CAP_SYS_ADMIN, false));
- EXPECT_THAT(umount(dir.path().c_str()), SyscallFailsWithErrno(EPERM));
- });
-}
-
-TEST(MountTest, MountOverBusy) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto const fd = ASSERT_NO_ERRNO_AND_VALUE(
- Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777));
-
- // Should be able to mount over a busy directory.
- ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "tmpfs", 0, "", 0));
-}
-
-TEST(MountTest, OpenFileBusy) {
- 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", 0, "mode=0700", 0));
- auto const fd = ASSERT_NO_ERRNO_AND_VALUE(
- Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777));
-
- // An open file should prevent unmounting.
- EXPECT_THAT(umount(dir.path().c_str()), SyscallFailsWithErrno(EBUSY));
-}
-
-TEST(MountTest, UmountNoFollow) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- auto const mountPoint = NewTempAbsPathInDir(dir.path());
- ASSERT_THAT(mkdir(mountPoint.c_str(), 0777), SyscallSucceeds());
-
- // Create a symlink in dir which will point to the actual mountpoint.
- const std::string symlinkInDir = NewTempAbsPathInDir(dir.path());
- EXPECT_THAT(symlink(mountPoint.c_str(), symlinkInDir.c_str()),
- SyscallSucceeds());
-
- // Create a symlink to the dir.
- const std::string symlinkToDir = NewTempAbsPath();
- EXPECT_THAT(symlink(dir.path().c_str(), symlinkToDir.c_str()),
- SyscallSucceeds());
-
- // Should fail with ELOOP when UMOUNT_NOFOLLOW is specified and the last
- // component is a symlink.
- auto mount = ASSERT_NO_ERRNO_AND_VALUE(
- Mount("", mountPoint, "tmpfs", 0, "mode=0700", 0));
- EXPECT_THAT(umount2(symlinkInDir.c_str(), UMOUNT_NOFOLLOW),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(unlink(symlinkInDir.c_str()), SyscallSucceeds());
-
- // UMOUNT_NOFOLLOW should only apply to the last path component. A symlink in
- // non-last path component should be just fine.
- EXPECT_THAT(umount2(JoinPath(symlinkToDir, Basename(mountPoint)).c_str(),
- UMOUNT_NOFOLLOW),
- SyscallSucceeds());
- mount.Release();
-}
-
-TEST(MountTest, UmountDetach) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- // structure:
- //
- // dir (mount point)
- // subdir
- // file
- //
- // We show that we can walk around in the mount after detach-unmount dir.
- //
- // We show that even though dir is unreachable from outside the mount, we can
- // still reach dir's (former) parent!
- auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- const struct stat before = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
- auto mount =
- ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "tmpfs", 0, "mode=0700",
- /* umountflags= */ MNT_DETACH));
- const struct stat after = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
- EXPECT_FALSE(before.st_dev == after.st_dev && before.st_ino == after.st_ino)
- << "mount point has device number " << before.st_dev
- << " and inode number " << before.st_ino << " before and after mount";
-
- // Create files in the new mount.
- constexpr char kContents[] = "no no no";
- auto const subdir =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path()));
- auto const file = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateFileWith(dir.path(), kContents, 0777));
-
- auto const dir_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(subdir.path(), O_RDONLY | O_DIRECTORY));
- auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY));
-
- // Unmount the tmpfs.
- mount.Release()();
-
- // Inode numbers for gofer-accessed files may change across save/restore.
- //
- // For overlayfs, if xino option is not enabled and if all overlayfs layers do
- // not belong to the same filesystem then "the value of st_ino for directory
- // objects may not be persistent and could change even while the overlay
- // filesystem is mounted." -- Documentation/filesystems/overlayfs.txt
- if (!IsRunningWithSaveRestore() &&
- !ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(dir.path()))) {
- const struct stat after2 = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
- EXPECT_EQ(before.st_ino, after2.st_ino);
- }
-
- // Can still read file after unmounting.
- std::vector<char> buf(sizeof(kContents));
- EXPECT_THAT(ReadFd(fd.get(), buf.data(), buf.size()), SyscallSucceeds());
-
- // Walk to dir.
- auto const mounted_dir = ASSERT_NO_ERRNO_AND_VALUE(
- OpenAt(dir_fd.get(), "..", O_DIRECTORY | O_RDONLY));
- // Walk to dir/file.
- auto const fd_again = ASSERT_NO_ERRNO_AND_VALUE(
- OpenAt(mounted_dir.get(), std::string(Basename(file.path())), O_RDONLY));
-
- std::vector<char> buf2(sizeof(kContents));
- EXPECT_THAT(ReadFd(fd_again.get(), buf2.data(), buf2.size()),
- SyscallSucceeds());
- EXPECT_EQ(buf, buf2);
-
- // Walking outside the unmounted realm should still work, too!
- auto const dir_parent = ASSERT_NO_ERRNO_AND_VALUE(
- OpenAt(mounted_dir.get(), "..", O_DIRECTORY | O_RDONLY));
-}
-
-TEST(MountTest, ActiveSubmountBusy) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto const mount1 = ASSERT_NO_ERRNO_AND_VALUE(
- Mount("", dir.path(), "tmpfs", 0, "mode=0700", 0));
-
- auto const dir2 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path()));
- auto const mount2 =
- ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir2.path(), "tmpfs", 0, "", 0));
-
- // Since dir now has an active submount, should not be able to unmount.
- EXPECT_THAT(umount(dir.path().c_str()), SyscallFailsWithErrno(EBUSY));
-}
-
-TEST(MountTest, MountTmpfs) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- // NOTE(b/129868551): Inode IDs are only stable across S/R if we have an open
- // FD for that inode. Since we are going to compare inode IDs below, get a
- // FileDescriptor for this directory here, which will be closed automatically
- // at the end of the test.
- auto const fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY, O_RDONLY));
-
- const struct stat before = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
-
- {
- auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
- Mount("", dir.path(), "tmpfs", 0, "mode=0700", 0));
-
- const struct stat s = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
- EXPECT_EQ(s.st_mode, S_IFDIR | 0700);
- EXPECT_FALSE(before.st_dev == s.st_dev && before.st_ino == s.st_ino)
- << "mount point has device number " << before.st_dev
- << " and inode number " << before.st_ino << " before and after mount";
-
- EXPECT_NO_ERRNO(Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777));
- }
-
- // Now that dir is unmounted again, we should have the old inode back.
- //
- // Inode numbers for gofer-accessed files may change across save/restore.
- //
- // For overlayfs, if xino option is not enabled and if all overlayfs layers do
- // not belong to the same filesystem then "the value of st_ino for directory
- // objects may not be persistent and could change even while the overlay
- // filesystem is mounted." -- Documentation/filesystems/overlayfs.txt
- if (!IsRunningWithSaveRestore() &&
- !ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(dir.path()))) {
- const struct stat after = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
- EXPECT_EQ(before.st_ino, after.st_ino);
- }
-}
-
-TEST(MountTest, MountTmpfsMagicValIgnored) {
- 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_MGC_VAL, "mode=0700", 0));
-}
-
-// Passing nullptr to data is equivalent to "".
-TEST(MountTest, NullData) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- EXPECT_THAT(mount("", dir.path().c_str(), "tmpfs", 0, nullptr),
- SyscallSucceeds());
- EXPECT_THAT(umount2(dir.path().c_str(), 0), SyscallSucceeds());
-}
-
-TEST(MountTest, MountReadonly) {
- 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_RDONLY, "mode=0777", 0));
-
- const struct stat s = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
- EXPECT_EQ(s.st_mode, S_IFDIR | 0777);
-
- std::string const filename = JoinPath(dir.path(), "foo");
- EXPECT_THAT(open(filename.c_str(), O_RDWR | O_CREAT, 0777),
- SyscallFailsWithErrno(EROFS));
-}
-
-PosixErrorOr<absl::Time> ATime(absl::string_view file) {
- struct stat s = {};
- if (stat(std::string(file).c_str(), &s) == -1) {
- return PosixError(errno, "stat failed");
- }
- return absl::TimeFromTimespec(s.st_atim);
-}
-
-TEST(MountTest, MountNoAtime) {
- 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_NOATIME, "mode=0777", 0));
-
- std::string const contents = "No no no, don't follow the instructions!";
- auto const file = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateFileWith(dir.path(), contents, 0777));
-
- absl::Time const before = ASSERT_NO_ERRNO_AND_VALUE(ATime(file.path()));
-
- // Reading from the file should change the atime, but the MS_NOATIME flag
- // should prevent that.
- auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
- char buf[100];
- int read_n;
- ASSERT_THAT(read_n = read(fd.get(), buf, sizeof(buf)), SyscallSucceeds());
- EXPECT_EQ(std::string(buf, read_n), contents);
-
- absl::Time const after = ASSERT_NO_ERRNO_AND_VALUE(ATime(file.path()));
-
- // Expect that atime hasn't changed.
- EXPECT_EQ(before, after);
-}
-
-TEST(MountTest, MountNoExec) {
- 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=0777", 0));
-
- std::string const contents = "No no no, don't follow the instructions!";
- auto const file = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateFileWith(dir.path(), contents, 0777));
-
- int execve_errno;
- ASSERT_NO_ERRNO_AND_VALUE(
- ForkAndExec(file.path(), {}, {}, nullptr, &execve_errno));
- EXPECT_EQ(execve_errno, EACCES);
-}
-
-TEST(MountTest, RenameRemoveMountPoint) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
-
- auto const dir_parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- auto const dir =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir_parent.path()));
- auto const new_dir = NewTempAbsPath();
-
- auto const mount =
- ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "tmpfs", 0, "", 0));
-
- ASSERT_THAT(rename(dir.path().c_str(), new_dir.c_str()),
- SyscallFailsWithErrno(EBUSY));
-
- 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, AnyOf(Contains(Pair("mode", "0123")),
- Contains(Pair("mode", "123"))));
- }
- }
-
- 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, AnyOf(Contains(Pair("mode", "0123")),
- Contains(Pair("mode", "123"))));
- }
- }
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor