github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/test/syscalls/linux/mount.cc (about)

     1  // Copyright 2018 The gVisor Authors.
     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  //     http://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 <errno.h>
    16  #include <fcntl.h>
    17  #include <stdio.h>
    18  #include <sys/mount.h>
    19  #include <sys/stat.h>
    20  #include <unistd.h>
    21  
    22  #include <functional>
    23  #include <memory>
    24  #include <string>
    25  #include <vector>
    26  
    27  #include "gmock/gmock.h"
    28  #include "gtest/gtest.h"
    29  #include "absl/strings/str_split.h"
    30  #include "absl/strings/string_view.h"
    31  #include "absl/time/time.h"
    32  #include "test/util/capability_util.h"
    33  #include "test/util/file_descriptor.h"
    34  #include "test/util/fs_util.h"
    35  #include "test/util/mount_util.h"
    36  #include "test/util/multiprocess_util.h"
    37  #include "test/util/posix_error.h"
    38  #include "test/util/save_util.h"
    39  #include "test/util/temp_path.h"
    40  #include "test/util/test_util.h"
    41  #include "test/util/thread_util.h"
    42  
    43  namespace gvisor {
    44  namespace testing {
    45  
    46  namespace {
    47  
    48  using ::testing::AnyOf;
    49  using ::testing::Contains;
    50  using ::testing::Pair;
    51  
    52  TEST(MountTest, MountBadFilesystem) {
    53    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
    54  
    55    // Linux expects a valid target before it checks the file system name.
    56    auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
    57    EXPECT_THAT(mount("", dir.path().c_str(), "foobar", 0, ""),
    58                SyscallFailsWithErrno(ENODEV));
    59  }
    60  
    61  TEST(MountTest, MountInvalidTarget) {
    62    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
    63  
    64    auto const dir = NewTempAbsPath();
    65    EXPECT_THAT(mount("", dir.c_str(), "tmpfs", 0, ""),
    66                SyscallFailsWithErrno(ENOENT));
    67  }
    68  
    69  TEST(MountTest, MountPermDenied) {
    70    // Clear CAP_SYS_ADMIN.
    71    AutoCapability cap(CAP_SYS_ADMIN, false);
    72  
    73    // Linux expects a valid target before checking capability.
    74    auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
    75    EXPECT_THAT(mount("", dir.path().c_str(), "", 0, ""),
    76                SyscallFailsWithErrno(EPERM));
    77  }
    78  
    79  TEST(MountTest, UmountPermDenied) {
    80    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
    81  
    82    auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
    83    auto const mount =
    84        ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "tmpfs", 0, "", 0));
    85  
    86    // Drop privileges in another thread, so we can still unmount the mounted
    87    // directory.
    88    ScopedThread([&]() {
    89      EXPECT_NO_ERRNO(SetCapability(CAP_SYS_ADMIN, false));
    90      EXPECT_THAT(umount(dir.path().c_str()), SyscallFailsWithErrno(EPERM));
    91    });
    92  }
    93  
    94  TEST(MountTest, MountOverBusy) {
    95    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
    96  
    97    auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
    98    auto const fd = ASSERT_NO_ERRNO_AND_VALUE(
    99        Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777));
   100  
   101    // Should be able to mount over a busy directory.
   102    ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "tmpfs", 0, "", 0));
   103  }
   104  
   105  TEST(MountTest, OpenFileBusy) {
   106    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
   107  
   108    auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   109    auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
   110        Mount("", dir.path(), "tmpfs", 0, "mode=0700", 0));
   111    auto const fd = ASSERT_NO_ERRNO_AND_VALUE(
   112        Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777));
   113  
   114    // An open file should prevent unmounting.
   115    EXPECT_THAT(umount(dir.path().c_str()), SyscallFailsWithErrno(EBUSY));
   116  }
   117  
   118  TEST(MountTest, UmountDetach) {
   119    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
   120  
   121    // structure:
   122    //
   123    // dir (mount point)
   124    //   subdir
   125    //   file
   126    //
   127    // We show that we can walk around in the mount after detach-unmount dir.
   128    //
   129    // We show that even though dir is unreachable from outside the mount, we can
   130    // still reach dir's (former) parent!
   131    auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   132  
   133    const struct stat before = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
   134    auto mount =
   135        ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "tmpfs", 0, "mode=0700",
   136                                        /* umountflags= */ MNT_DETACH));
   137    const struct stat after = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
   138    EXPECT_FALSE(before.st_dev == after.st_dev && before.st_ino == after.st_ino)
   139        << "mount point has device number " << before.st_dev
   140        << " and inode number " << before.st_ino << " before and after mount";
   141  
   142    // Create files in the new mount.
   143    constexpr char kContents[] = "no no no";
   144    auto const subdir =
   145        ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path()));
   146    auto const file = ASSERT_NO_ERRNO_AND_VALUE(
   147        TempPath::CreateFileWith(dir.path(), kContents, 0777));
   148  
   149    auto const dir_fd =
   150        ASSERT_NO_ERRNO_AND_VALUE(Open(subdir.path(), O_RDONLY | O_DIRECTORY));
   151    auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY));
   152  
   153    // Unmount the tmpfs.
   154    mount.Release()();
   155  
   156    // Inode numbers for gofer-accessed files may change across save/restore.
   157    //
   158    // For overlayfs, if xino option is not enabled and if all overlayfs layers do
   159    // not belong to the same filesystem then "the value of st_ino for directory
   160    // objects may not be persistent and could change even while the overlay
   161    // filesystem is mounted."  -- Documentation/filesystems/overlayfs.txt
   162    if (!IsRunningWithSaveRestore() &&
   163        !ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(dir.path()))) {
   164      const struct stat after2 = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
   165      EXPECT_EQ(before.st_ino, after2.st_ino);
   166    }
   167  
   168    // Can still read file after unmounting.
   169    std::vector<char> buf(sizeof(kContents));
   170    EXPECT_THAT(ReadFd(fd.get(), buf.data(), buf.size()), SyscallSucceeds());
   171  
   172    // Walk to dir.
   173    auto const mounted_dir = ASSERT_NO_ERRNO_AND_VALUE(
   174        OpenAt(dir_fd.get(), "..", O_DIRECTORY | O_RDONLY));
   175    // Walk to dir/file.
   176    auto const fd_again = ASSERT_NO_ERRNO_AND_VALUE(
   177        OpenAt(mounted_dir.get(), std::string(Basename(file.path())), O_RDONLY));
   178  
   179    std::vector<char> buf2(sizeof(kContents));
   180    EXPECT_THAT(ReadFd(fd_again.get(), buf2.data(), buf2.size()),
   181                SyscallSucceeds());
   182    EXPECT_EQ(buf, buf2);
   183  
   184    // Walking outside the unmounted realm should still work, too!
   185    auto const dir_parent = ASSERT_NO_ERRNO_AND_VALUE(
   186        OpenAt(mounted_dir.get(), "..", O_DIRECTORY | O_RDONLY));
   187  }
   188  
   189  TEST(MountTest, ActiveSubmountBusy) {
   190    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
   191  
   192    auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   193    auto const mount1 = ASSERT_NO_ERRNO_AND_VALUE(
   194        Mount("", dir.path(), "tmpfs", 0, "mode=0700", 0));
   195  
   196    auto const dir2 =
   197        ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path()));
   198    auto const mount2 =
   199        ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir2.path(), "tmpfs", 0, "", 0));
   200  
   201    // Since dir now has an active submount, should not be able to unmount.
   202    EXPECT_THAT(umount(dir.path().c_str()), SyscallFailsWithErrno(EBUSY));
   203  }
   204  
   205  TEST(MountTest, MountTmpfs) {
   206    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
   207  
   208    auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   209  
   210    // NOTE(b/129868551): Inode IDs are only stable across S/R if we have an open
   211    // FD for that inode. Since we are going to compare inode IDs below, get a
   212    // FileDescriptor for this directory here, which will be closed automatically
   213    // at the end of the test.
   214    auto const fd =
   215        ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY, O_RDONLY));
   216  
   217    const struct stat before = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
   218  
   219    {
   220      auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
   221          Mount("", dir.path(), "tmpfs", 0, "mode=0700", 0));
   222  
   223      const struct stat s = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
   224      EXPECT_EQ(s.st_mode, S_IFDIR | 0700);
   225      EXPECT_FALSE(before.st_dev == s.st_dev && before.st_ino == s.st_ino)
   226          << "mount point has device number " << before.st_dev
   227          << " and inode number " << before.st_ino << " before and after mount";
   228  
   229      EXPECT_NO_ERRNO(Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777));
   230    }
   231  
   232    // Now that dir is unmounted again, we should have the old inode back.
   233    //
   234    // Inode numbers for gofer-accessed files may change across save/restore.
   235    //
   236    // For overlayfs, if xino option is not enabled and if all overlayfs layers do
   237    // not belong to the same filesystem then "the value of st_ino for directory
   238    // objects may not be persistent and could change even while the overlay
   239    // filesystem is mounted."  -- Documentation/filesystems/overlayfs.txt
   240    if (!IsRunningWithSaveRestore() &&
   241        !ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(dir.path()))) {
   242      const struct stat after = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
   243      EXPECT_EQ(before.st_ino, after.st_ino);
   244    }
   245  }
   246  
   247  TEST(MountTest, MountTmpfsMagicValIgnored) {
   248    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
   249  
   250    auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   251  
   252    auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
   253        Mount("", dir.path(), "tmpfs", MS_MGC_VAL, "mode=0700", 0));
   254  }
   255  
   256  // Passing nullptr to data is equivalent to "".
   257  TEST(MountTest, NullData) {
   258    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
   259  
   260    auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   261  
   262    EXPECT_THAT(mount("", dir.path().c_str(), "tmpfs", 0, nullptr),
   263                SyscallSucceeds());
   264    EXPECT_THAT(umount2(dir.path().c_str(), 0), SyscallSucceeds());
   265  }
   266  
   267  TEST(MountTest, MountReadonly) {
   268    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
   269  
   270    auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   271    auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
   272        Mount("", dir.path(), "tmpfs", MS_RDONLY, "mode=0777", 0));
   273  
   274    const struct stat s = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
   275    EXPECT_EQ(s.st_mode, S_IFDIR | 0777);
   276  
   277    std::string const filename = JoinPath(dir.path(), "foo");
   278    EXPECT_THAT(open(filename.c_str(), O_RDWR | O_CREAT, 0777),
   279                SyscallFailsWithErrno(EROFS));
   280  }
   281  
   282  PosixErrorOr<absl::Time> ATime(absl::string_view file) {
   283    struct stat s = {};
   284    if (stat(std::string(file).c_str(), &s) == -1) {
   285      return PosixError(errno, "stat failed");
   286    }
   287    return absl::TimeFromTimespec(s.st_atim);
   288  }
   289  
   290  TEST(MountTest, MountNoAtime) {
   291    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
   292  
   293    auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   294    auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
   295        Mount("", dir.path(), "tmpfs", MS_NOATIME, "mode=0777", 0));
   296  
   297    std::string const contents = "No no no, don't follow the instructions!";
   298    auto const file = ASSERT_NO_ERRNO_AND_VALUE(
   299        TempPath::CreateFileWith(dir.path(), contents, 0777));
   300  
   301    absl::Time const before = ASSERT_NO_ERRNO_AND_VALUE(ATime(file.path()));
   302  
   303    // Reading from the file should change the atime, but the MS_NOATIME flag
   304    // should prevent that.
   305    auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
   306    char buf[100];
   307    int read_n;
   308    ASSERT_THAT(read_n = read(fd.get(), buf, sizeof(buf)), SyscallSucceeds());
   309    EXPECT_EQ(std::string(buf, read_n), contents);
   310  
   311    absl::Time const after = ASSERT_NO_ERRNO_AND_VALUE(ATime(file.path()));
   312  
   313    // Expect that atime hasn't changed.
   314    EXPECT_EQ(before, after);
   315  }
   316  
   317  TEST(MountTest, MountNoExec) {
   318    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
   319  
   320    auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   321    auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
   322        Mount("", dir.path(), "tmpfs", MS_NOEXEC, "mode=0777", 0));
   323  
   324    std::string const contents = "No no no, don't follow the instructions!";
   325    auto const file = ASSERT_NO_ERRNO_AND_VALUE(
   326        TempPath::CreateFileWith(dir.path(), contents, 0777));
   327  
   328    int execve_errno;
   329    ASSERT_NO_ERRNO_AND_VALUE(
   330        ForkAndExec(file.path(), {}, {}, nullptr, &execve_errno));
   331    EXPECT_EQ(execve_errno, EACCES);
   332  }
   333  
   334  TEST(MountTest, RenameRemoveMountPoint) {
   335    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
   336  
   337    auto const dir_parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   338    auto const dir =
   339        ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir_parent.path()));
   340    auto const new_dir = NewTempAbsPath();
   341  
   342    auto const mount =
   343        ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "tmpfs", 0, "", 0));
   344  
   345    ASSERT_THAT(rename(dir.path().c_str(), new_dir.c_str()),
   346                SyscallFailsWithErrno(EBUSY));
   347  
   348    ASSERT_THAT(rmdir(dir.path().c_str()), SyscallFailsWithErrno(EBUSY));
   349  }
   350  
   351  TEST(MountTest, MountInfo) {
   352    SKIP_IF(IsRunningWithVFS1());
   353    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
   354  
   355    auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   356    auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
   357        Mount("", dir.path(), "tmpfs", MS_NOEXEC, "mode=0123", 0));
   358    const std::vector<ProcMountsEntry> mounts =
   359        ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountsEntries());
   360    for (const auto& e : mounts) {
   361      if (e.mount_point == dir.path()) {
   362        EXPECT_EQ(e.fstype, "tmpfs");
   363        auto mopts = ParseMountOptions(e.mount_opts);
   364        EXPECT_THAT(mopts, AnyOf(Contains(Pair("mode", "0123")),
   365                                 Contains(Pair("mode", "123"))));
   366      }
   367    }
   368  
   369    const std::vector<ProcMountInfoEntry> mountinfo =
   370        ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries());
   371  
   372    for (auto const& e : mountinfo) {
   373      if (e.mount_point == dir.path()) {
   374        EXPECT_EQ(e.fstype, "tmpfs");
   375        auto mopts = ParseMountOptions(e.super_opts);
   376        EXPECT_THAT(mopts, AnyOf(Contains(Pair("mode", "0123")),
   377                                 Contains(Pair("mode", "123"))));
   378      }
   379    }
   380  }
   381  
   382  }  // namespace
   383  
   384  }  // namespace testing
   385  }  // namespace gvisor