// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/container/flat_hash_map.h" #include "absl/strings/match.h" #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" #include "absl/strings/substitute.h" #include "absl/time/clock.h" #include "absl/time/time.h" #include "test/util/capability_util.h" #include "test/util/cleanup.h" #include "test/util/eventfd_util.h" #include "test/util/file_descriptor.h" #include "test/util/fs_util.h" #include "test/util/linux_capability_util.h" #include "test/util/logging.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/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::IsSupersetOf; using ::testing::Pair; constexpr char kTmpfs[] = "tmpfs"; 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(), kTmpfs, 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(), kTmpfs, 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(), kTmpfs, 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(), kTmpfs, 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, kTmpfs, 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(), kTmpfs, 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 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 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, MMapWithExecProtFailsOnNoExecFile) { // Skips the test if test does not have needed capability to create the volume // mount. SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto ret = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir.path(), kTmpfs, MS_NOEXEC, "", 0)); auto file = ASSERT_NO_ERRNO_AND_VALUE( TempPath::CreateFileWith(dir.path(), "random1", 0777)); FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path().c_str(), O_RDWR)); ASSERT_THAT(reinterpret_cast( mmap(0, kPageSize, PROT_EXEC, MAP_PRIVATE, fd.get(), 0)), SyscallFailsWithErrno(EPERM)); } TEST(MountTest, MMapWithExecProtSucceedsOnExecutableVolumeFile) { // Capability is needed to create tmpfs. SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto ret = ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), kTmpfs, 0, "", 0)); auto file = ASSERT_NO_ERRNO_AND_VALUE( TempPath::CreateFileWith(dir.path(), "random1", 0777)); FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path().c_str(), O_RDWR)); void* address = mmap(0, kPageSize, PROT_EXEC, MAP_PRIVATE, fd.get(), 0); EXPECT_NE(address, MAP_FAILED); MunmapSafe(address, kPageSize); } TEST(MountTest, MMapWithoutNoExecProtSucceedsOnNoExecFile) { // Capability is needed to create tmpfs. SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto ret = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir.path(), kTmpfs, MS_NOEXEC, "", 0)); auto file = ASSERT_NO_ERRNO_AND_VALUE( TempPath::CreateFileWith(dir.path(), "random1", 0777)); FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path().c_str(), O_RDWR)); void* address = mmap(0, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd.get(), 0); EXPECT_NE(address, MAP_FAILED); MunmapSafe(address, kPageSize); } TEST(MountTest, MProtectWithNoExecProtFailsOnNoExecFile) { // Capability is needed to create tmpfs. SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto ret = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir.path(), kTmpfs, MS_NOEXEC, "", 0)); auto file = ASSERT_NO_ERRNO_AND_VALUE( TempPath::CreateFileWith(dir.path(), "random1", 0777)); FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path().c_str(), O_RDWR)); void* address = mmap(0, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd.get(), 0); EXPECT_NE(address, MAP_FAILED); ASSERT_THAT(mprotect(address, kPageSize, PROT_EXEC), SyscallFailsWithErrno(EACCES)); MunmapSafe(address, kPageSize); } TEST(MountTest, UmountMountsStackedOnDot) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); // Verify that unmounting at "." properly unmounts the mount at the top of // mount stack. auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); TEST_CHECK_SUCCESS(chdir(dir.path().c_str())); const struct stat before = ASSERT_NO_ERRNO_AND_VALUE(Stat(".")); TEST_CHECK_SUCCESS(mount("", dir.path().c_str(), kTmpfs, 0, "mode=0700")); TEST_CHECK_SUCCESS(mount("", dir.path().c_str(), kTmpfs, 0, "mode=0700")); // Unmount the second mount at "." TEST_CHECK_SUCCESS(umount2(".", MNT_DETACH)); // Unmount the first mount at "."; this will fail if umount does not resolve // "." to the topmost mount. TEST_CHECK_SUCCESS(umount2(".", MNT_DETACH)); const struct stat after2 = ASSERT_NO_ERRNO_AND_VALUE(Stat(".")); EXPECT_TRUE(before.st_dev == after2.st_dev && before.st_ino == after2.st_ino); } 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(), kTmpfs, 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(), kTmpfs, 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(), kTmpfs, 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(), kTmpfs, 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(), kTmpfs, 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(), kTmpfs, 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); EXPECT_THAT(access(dir.path().c_str(), W_OK), SyscallFailsWithErrno(EROFS)); std::string const filename = JoinPath(dir.path(), "foo"); EXPECT_THAT(open(filename.c_str(), O_RDWR | O_CREAT, 0777), SyscallFailsWithErrno(EROFS)); } TEST(MountTest, BindMountReadonly) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const bindDir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(dir.path(), bindDir.path(), "", MS_BIND, "", 0)); std::string const filename = JoinPath(bindDir.path(), "foo"); FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_RDWR | O_CREAT, 0644)); char msg[] = "hello world"; EXPECT_THAT(pwrite64(fd.get(), msg, strlen(msg), 0), SyscallSucceedsWithValue(strlen(msg))); fd.reset(); EXPECT_THAT(mount(dir.path().c_str(), bindDir.path().c_str(), NULL, MS_BIND | MS_RDONLY | MS_REMOUNT, NULL), SyscallSucceeds()); EXPECT_THAT(access(bindDir.path().c_str(), W_OK), SyscallFailsWithErrno(EROFS)); EXPECT_THAT(open(filename.c_str(), O_RDWR), SyscallFailsWithErrno(EROFS)); EXPECT_THAT(open(filename.c_str(), O_RDWR | O_TRUNC), SyscallFailsWithErrno(EROFS)); EXPECT_THAT(open(filename.c_str(), O_RDONLY | O_TRUNC), SyscallFailsWithErrno(EROFS)); const struct stat s = ASSERT_NO_ERRNO_AND_VALUE(Stat(filename)); EXPECT_EQ(s.st_size, strlen(msg)); } // Special files (such as FIFO) should be openable for writing even on a // read-only mount. Regular files should not. TEST(MountTest, FifoWritableOnReadonlyMount) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt = ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), kTmpfs, 0, "", 0)); // Populate the still-writable mount with a FIFO and a regular file. std::string const fifo_path = JoinPath(dir.path(), "fifo"); std::string const reg_path = JoinPath(dir.path(), "reg"); ASSERT_THAT(mkfifo(fifo_path.c_str(), 0666), SyscallSucceeds()); FileDescriptor reg_fd = ASSERT_NO_ERRNO_AND_VALUE(Open(reg_path, O_WRONLY | O_CREAT, 0644)); reg_fd.reset(); // Remount read-only. ASSERT_THAT( mount("", dir.path().c_str(), nullptr, MS_REMOUNT | MS_RDONLY, nullptr), SyscallSucceeds()); ASSERT_THAT(access(dir.path().c_str(), W_OK), SyscallFailsWithErrno(EROFS)); // The FIFO opens writably despite the RO mount. FileDescriptor fifo_fd = ASSERT_NO_ERRNO_AND_VALUE(Open(fifo_path, O_RDWR)); EXPECT_THAT(write(fifo_fd.get(), "x", 1), SyscallSucceedsWithValue(1)); // The regular file still fails a writable open with EROFS, but a read-only // open succeeds. EXPECT_THAT(open(reg_path.c_str(), O_WRONLY), SyscallFailsWithErrno(EROFS)); ASSERT_NO_ERRNO_AND_VALUE(Open(reg_path, O_RDONLY)); } // Same test as above, but for a character device. TEST(MountTest, DeviceFileWritableOnReadonlyMount) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt = ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), kTmpfs, 0, "", 0)); // Create a /dev/null-style character device while the mount is writable. std::string const dev_path = JoinPath(dir.path(), "null"); int ret = mknod(dev_path.c_str(), S_IFCHR | 0666, makedev(1, 3)); const bool was_eperm = Value(ret, SyscallFailsWithErrno(EPERM)); // In our test infrastructure, this may not always be possible to test on // native since CAP_MKNOD is required in the *initial* user ns, and native // tests are sometimes run in a user ns. SKIP_IF(was_eperm); // (But otherwise, mknod() should succeed.) ASSERT_THAT(ret, SyscallSucceeds()); // Remount read-only. ASSERT_THAT( mount("", dir.path().c_str(), nullptr, MS_REMOUNT | MS_RDONLY, nullptr), SyscallSucceeds()); // The device node opens writably and accepts writes despite the RO mount. FileDescriptor dev_fd = ASSERT_NO_ERRNO_AND_VALUE(Open(dev_path, O_WRONLY)); EXPECT_THAT(write(dev_fd.get(), "x", 1), SyscallSucceedsWithValue(1)); } TEST(MountTest, ReadOnlyBindMountAllowsWriteOpenOfCharDevice) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); std::string const nullPath = JoinPath(dir.path(), "null"); FileDescriptor create_fd = ASSERT_NO_ERRNO_AND_VALUE(Open(nullPath, O_CREAT | O_RDWR, 0666)); create_fd.reset(); auto const mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount("/dev/null", nullPath, "", MS_BIND, "", 0)); ASSERT_THAT(mount("/dev/null", nullPath.c_str(), nullptr, MS_BIND | MS_REMOUNT | MS_RDONLY, nullptr), SyscallSucceeds()); FileDescriptor wfd = ASSERT_NO_ERRNO_AND_VALUE(Open(nullPath, O_WRONLY)); char msg[] = "hello"; EXPECT_THAT(write(wfd.get(), msg, sizeof(msg)), SyscallSucceedsWithValue(sizeof(msg))); FileDescriptor rwfd = ASSERT_NO_ERRNO_AND_VALUE(Open(nullPath, O_RDWR)); EXPECT_THAT(write(rwfd.get(), msg, sizeof(msg)), SyscallSucceedsWithValue(sizeof(msg))); } TEST(MountTest, ReadOnlyBindMountAllowsWriteOpenOfFIFO) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); std::string const fifoPath = JoinPath(dir.path(), "fifo"); ASSERT_THAT(mkfifo(fifoPath.c_str(), 0666), SyscallSucceeds()); auto const bindDir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(dir.path(), bindDir.path(), "", MS_BIND, "", 0)); ASSERT_THAT(mount(dir.path().c_str(), bindDir.path().c_str(), nullptr, MS_BIND | MS_REMOUNT | MS_RDONLY, nullptr), SyscallSucceeds()); std::string const boundFifoPath = JoinPath(bindDir.path(), "fifo"); FileDescriptor rfd = ASSERT_NO_ERRNO_AND_VALUE(Open(boundFifoPath, O_RDONLY | O_NONBLOCK)); FileDescriptor wfd = ASSERT_NO_ERRNO_AND_VALUE(Open(boundFifoPath, O_WRONLY | O_NONBLOCK)); char msg[] = "hello"; EXPECT_THAT(write(wfd.get(), msg, sizeof(msg)), SyscallSucceedsWithValue(sizeof(msg))); } // Test that bind mounting a directory onto a regular file fails with ENOTDIR. TEST(MountTest, BindMountDirectoryOntoFileFails) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const source_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path())); auto const target_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path())); // Bind mounting a directory onto a file should fail with ENOTDIR. EXPECT_THAT(mount(source_dir.path().c_str(), target_file.path().c_str(), nullptr, MS_BIND, nullptr), SyscallFailsWithErrno(ENOTDIR)); } // Test that bind mounting a regular file onto a directory fails with ENOTDIR. TEST(MountTest, BindMountFileOntoDirectoryFails) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const source_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path())); auto const target_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path())); // Bind mounting a file onto a directory should fail with ENOTDIR. EXPECT_THAT(mount(source_file.path().c_str(), target_dir.path().c_str(), nullptr, MS_BIND, nullptr), SyscallFailsWithErrno(ENOTDIR)); } PosixErrorOr 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(), kTmpfs, 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, MountWithStrictAtime) { 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(), kTmpfs, MS_NOATIME | MS_STRICTATIME, "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())); absl::SleepFor(absl::Milliseconds(100)); // MS_STRICTATIME should override MS_NOATIME and update the file's atime. 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())); // The after atime is expected to be larger than the before atime. EXPECT_LT(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(), kTmpfs, 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(), kTmpfs, 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, MountMoveSuccess) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const privateParent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto privateMount = ASSERT_NO_ERRNO_AND_VALUE( Mount("source", privateParent.path(), kTmpfs, 0, "", 0)); EXPECT_THAT(mount("", privateParent.path().c_str(), "", MS_PRIVATE, ""), SyscallSucceeds()); auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(privateParent.path())); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(privateParent.path())); auto mount1 = ASSERT_NO_ERRNO_AND_VALUE(Mount("source", dir1.path(), kTmpfs, 0, "", 0)); auto fd = ASSERT_NO_ERRNO_AND_VALUE( Open(JoinPath(dir1.path(), "foo").c_str(), O_CREAT | O_RDWR, 0777)); EXPECT_THAT(close(fd.release()), SyscallSucceeds()); auto const mount2 = ASSERT_NO_ERRNO_AND_VALUE( Mount(dir1.path(), dir2.path(), "", MS_MOVE, "", 0)); mount1.Release(); EXPECT_THAT(Exists(JoinPath(dir1.path(), "foo")), IsPosixErrorOkAndHolds(false)); EXPECT_THAT(Exists(JoinPath(dir2.path(), "foo")), IsPosixErrorOkAndHolds(true)); } // TODO(b/305893463): When support for MS_UNBINDABLE is added, moving a mount // tree with unbindable children to an MS_SHARED destination should EINVAL. TEST(MountTest, MountMoveSharedParent) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const parentDir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto parentMount = ASSERT_NO_ERRNO_AND_VALUE( Mount("source", parentDir1.path(), kTmpfs, 0, "", 0)); EXPECT_THAT(mount("", parentDir1.path().c_str(), "", MS_SHARED, ""), SyscallSucceeds()); auto const subDir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parentDir1.path())); auto subMount = ASSERT_NO_ERRNO_AND_VALUE( Mount("source", subDir.path(), kTmpfs, 0, "", 0)); EXPECT_THAT( mount(subDir.path().c_str(), dir2.path().c_str(), "", MS_MOVE, nullptr), SyscallFailsWithErrno(EINVAL)); } TEST(MountTest, MountMoveSourceNotMounted) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto fd = ASSERT_NO_ERRNO_AND_VALUE( Open(JoinPath(dir1.path(), "foo").c_str(), O_CREAT | O_RDWR, 0777)); EXPECT_THAT(close(fd.release()), SyscallSucceeds()); EXPECT_THAT(mount(dir1.path().c_str(), dir2.path().c_str(), "", MS_MOVE, ""), SyscallFailsWithErrno(EINVAL)); } TEST(MountTest, MountInfo) { 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(), kTmpfs, MS_NOEXEC, "mode=0123", 0)); const std::vector mounts = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountsEntries()); for (const auto& e : mounts) { if (e.mount_point == dir.path()) { EXPECT_EQ(e.fstype, kTmpfs); auto mopts = ParseMountOptions(e.mount_opts); EXPECT_THAT(mopts, AnyOf(Contains(Pair("mode", "0123")), Contains(Pair("mode", "123")))); } } const std::vector mountinfo = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); for (auto const& e : mountinfo) { if (e.mount_point == dir.path()) { EXPECT_EQ(e.fstype, kTmpfs); auto mopts = ParseMountOptions(e.super_opts); EXPECT_THAT(mopts, AnyOf(Contains(Pair("mode", "0123")), Contains(Pair("mode", "123")))); } } } TEST(MountTest, MountFlagsShownInProcMounts) { 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(), kTmpfs, MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME, "", 0)); // The per-mount flags must be reflected in both /proc/self/mounts and // /proc/self/mountinfo: userspace (e.g. systemd) reads them back to // preserve locked flags across bind-remounts in user namespaces. bool found_mounts = false; const std::vector mounts = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountsEntries()); for (const auto& e : mounts) { if (e.mount_point != dir.path()) continue; found_mounts = true; auto mopts = ParseMountOptions(e.mount_opts); EXPECT_THAT(mopts, IsSupersetOf({Pair("nosuid", ""), Pair("nodev", ""), Pair("noexec", ""), Pair("noatime", "")})) << "/proc/self/mounts options: " << e.mount_opts; } EXPECT_TRUE(found_mounts); bool found_mountinfo = false; const std::vector mountinfo = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); for (auto const& e : mountinfo) { if (e.mount_point != dir.path()) continue; found_mountinfo = true; auto mopts = ParseMountOptions(e.mount_opts); EXPECT_THAT(mopts, IsSupersetOf({Pair("nosuid", ""), Pair("nodev", ""), Pair("noexec", ""), Pair("noatime", "")})) << "/proc/self/mountinfo options: " << e.mount_opts; } EXPECT_TRUE(found_mountinfo); } TEST(MountTest, TmpfsSizeRoundUpSinglePageSize) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto tmpfs_size_opt = absl::StrCat("size=", kPageSize / 2); auto const mount = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir.path(), kTmpfs, 0, tmpfs_size_opt, 0)); auto fd = ASSERT_NO_ERRNO_AND_VALUE( Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777)); // Check that it starts at size zero. struct stat buf; ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds()); EXPECT_EQ(buf.st_size, 0); // Grow to 1 Page Size. ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize), SyscallSucceeds()); ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds()); EXPECT_EQ(buf.st_size, kPageSize); // Grow to size beyond tmpfs allocated bytes. ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize + 1), SyscallFailsWithErrno(ENOSPC)); ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds()); EXPECT_EQ(buf.st_size, kPageSize); } TEST(MountTest, TmpfsNrBlocksAllocation) { 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(), kTmpfs, 0, "nr_blocks=1", 0)); auto fd = ASSERT_NO_ERRNO_AND_VALUE( Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777)); // Check that it starts at size zero. struct stat buf; ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds()); EXPECT_EQ(buf.st_size, 0); // Grow to 1 Page Size. ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize), SyscallSucceeds()); ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds()); EXPECT_EQ(buf.st_size, kPageSize); // Grow to size beyond the block limit. ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize + 1), SyscallFailsWithErrno(ENOSPC)); ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds()); EXPECT_EQ(buf.st_size, kPageSize); } TEST(MountTest, TmpfsSizeAllocationMultiplePages) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto page_multiple = 2; auto size = kPageSize * page_multiple; auto tmpfs_size_opt = absl::StrCat("size=", size); auto const mount = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir.path(), kTmpfs, 0, tmpfs_size_opt, 0)); auto fd = ASSERT_NO_ERRNO_AND_VALUE( Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777)); // Check that it starts at size zero. struct stat buf; ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds()); EXPECT_EQ(buf.st_size, 0); // Ensure fallocate does not allow partial allocations. ASSERT_THAT(fallocate(fd.get(), 0, 0, size + 1), SyscallFailsWithErrno(ENOSPC)); ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds()); EXPECT_EQ(buf.st_size, 0); // Grow to multiple of page size. ASSERT_THAT(fallocate(fd.get(), 0, 0, size), SyscallSucceeds()); ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds()); EXPECT_EQ(buf.st_size, size); // Grow to beyond tmpfs size bytes. ASSERT_THAT(fallocate(fd.get(), 0, 0, size + 1), SyscallFailsWithErrno(ENOSPC)); ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds()); EXPECT_EQ(buf.st_size, size); } TEST(MountTest, TmpfsSizeMoreThanSinglePgSZMultipleFiles) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const page_multiple = 10; auto const size = kPageSize * page_multiple; auto tmpfs_size_opt = absl::StrCat("size=", size); auto const mount = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir.path(), kTmpfs, 0, tmpfs_size_opt, 0)); for (int i = 0; i < page_multiple; i++) { auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open( JoinPath(dir.path(), absl::StrCat("foo_", i)), O_CREAT | O_RDWR, 0777)); // Create buffer & Grow to 100 bytes. struct stat buf; ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds()); ASSERT_THAT(fallocate(fd.get(), 0, 0, 100), SyscallSucceeds()); ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds()); EXPECT_EQ(buf.st_size, 100); } auto fd = ASSERT_NO_ERRNO_AND_VALUE( Open(JoinPath(dir.path(), absl::StrCat("foo_", page_multiple + 1)), O_CREAT | O_RDWR, 0777)); // Grow to beyond tmpfs size bytes after exhausting the size. ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize), SyscallFailsWithErrno(ENOSPC)); } TEST(MountTest, TmpfsSizeFtruncate) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto tmpfs_size_opt = absl::StrCat("size=", kPageSize); auto const mount = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir.path(), kTmpfs, 0, tmpfs_size_opt, 0)); auto fd = ASSERT_NO_ERRNO_AND_VALUE( Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777)); ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize), SyscallSucceeds()); struct stat status; ASSERT_THAT(fstat(fd.get(), &status), SyscallSucceeds()); EXPECT_EQ(status.st_size, kPageSize); ASSERT_THAT(ftruncate(fd.get(), kPageSize + 1), SyscallSucceeds()); ASSERT_THAT(fstat(fd.get(), &status), SyscallSucceeds()); EXPECT_EQ(status.st_size, kPageSize + 1); ASSERT_THAT(ftruncate(fd.get(), 0), SyscallSucceeds()); ASSERT_THAT(fstat(fd.get(), &status), SyscallSucceeds()); EXPECT_EQ(status.st_size, 0); ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize), SyscallSucceeds()); ASSERT_THAT(fstat(fd.get(), &status), SyscallSucceeds()); EXPECT_EQ(status.st_size, kPageSize); } // Test shows directory does not take up any pages. TEST(MountTest, TmpfsDirectoryAllocCheck) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir_parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto tmpfs_size_opt = absl::StrCat("size=", kPageSize); auto const mount = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir_parent.path(), kTmpfs, 0, tmpfs_size_opt, 0)); auto const dir_tmp = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir_parent.path())); // Creating only 1 regular file allocates 1 page size. auto fd = ASSERT_NO_ERRNO_AND_VALUE( Open(JoinPath(dir_parent.path(), "foo"), O_CREAT | O_RDWR, 0777)); // Check that it starts at size zero. struct stat buf; ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds()); EXPECT_EQ(buf.st_size, 0); // Grow to 1 Page Size. ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize), SyscallSucceeds()); ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds()); EXPECT_EQ(buf.st_size, kPageSize); // Grow to beyond 1 Page Size. ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize + 1), SyscallFailsWithErrno(ENOSPC)); } // Tests memory allocation for symlinks. TEST(MountTest, TmpfsSymlinkAllocCheck) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir_parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto tmpfs_size_opt = absl::StrCat("size=", kPageSize); auto const mount = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir_parent.path(), kTmpfs, 0, tmpfs_size_opt, 0)); const int target_size = 128; auto target = std::string(target_size - 1, 'a'); auto pathname = JoinPath(dir_parent.path(), "foo1"); EXPECT_THAT(symlink(target.c_str(), pathname.c_str()), SyscallSucceeds()); target = std::string(target_size, 'a'); pathname = absl::StrCat(dir_parent.path(), "/foo2"); EXPECT_THAT(symlink(target.c_str(), pathname.c_str()), SyscallSucceeds()); target = std::string(target_size, 'a'); pathname = absl::StrCat(dir_parent.path(), "/foo3"); EXPECT_THAT(symlink(target.c_str(), pathname.c_str()), SyscallFailsWithErrno(ENOSPC)); target = std::string(target_size - 1, 'a'); pathname = absl::StrCat(dir_parent.path(), "/foo4"); EXPECT_THAT(symlink(target.c_str(), pathname.c_str()), SyscallSucceeds()); EXPECT_THAT(unlink(pathname.c_str()), SyscallSucceeds()); } // Tests memory unallocation for symlinks. TEST(MountTest, TmpfsSymlinkUnallocCheck) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir_parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto tmpfs_size_opt = absl::StrCat("size=", kPageSize); auto const mount = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir_parent.path(), kTmpfs, 0, tmpfs_size_opt, 0)); const int target_size = 128; auto pathname = JoinPath(dir_parent.path(), "foo1"); auto target = std::string(target_size, 'a'); EXPECT_THAT(symlink(target.c_str(), pathname.c_str()), SyscallSucceeds()); auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(pathname, O_CREAT | O_RDWR, 0777)); ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize), SyscallFailsWithErrno(ENOSPC)); EXPECT_THAT(unlink(pathname.c_str()), SyscallSucceeds()); ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize), SyscallSucceeds()); } // Tests memory allocation for Hard Links is not double allocated. TEST(MountTest, TmpfsHardLinkAllocCheck) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto tmpfs_size_opt = absl::StrCat("size=", kPageSize); auto const mount = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir.path(), kTmpfs, 0, tmpfs_size_opt, 0)); const std::string fileOne = JoinPath(dir.path(), "foo1"); const std::string fileTwo = JoinPath(dir.path(), "foo2"); auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(fileOne, O_CREAT | O_RDWR, 0777)); EXPECT_THAT(link(fileOne.c_str(), fileTwo.c_str()), SyscallSucceeds()); // Check that it starts at size zero. struct stat buf; ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds()); EXPECT_EQ(buf.st_size, 0); // Grow to 1 Page Size. ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize), SyscallSucceeds()); ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds()); EXPECT_EQ(buf.st_size, kPageSize); // Grow to size beyond tmpfs allocated bytes. ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize + 1), SyscallFailsWithErrno(ENOSPC)); ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds()); EXPECT_EQ(buf.st_size, kPageSize); EXPECT_THAT(unlink(fileTwo.c_str()), SyscallSucceeds()); EXPECT_THAT(unlink(fileOne.c_str()), SyscallSucceeds()); } // Tests memory allocation for empty size. TEST(MountTest, TmpfsEmptySizeAllocCheck) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); ASSERT_THAT(mount("", dir.path().c_str(), kTmpfs, 0, "size"), SyscallFailsWithErrno(EINVAL)); } TEST(MountTest, TmpfsUnlinkRegularFileAllocCheck) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto tmpfs_size_opt = absl::StrCat("size=", kPageSize); const int kTruncateSize = 2 * kPageSize; auto const mount = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir.path(), kTmpfs, 0, tmpfs_size_opt, 0)); const std::string fileOne = JoinPath(dir.path(), "foo1"); auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(fileOne, O_CREAT | O_RDWR, 0777)); EXPECT_THAT(unlink(fileOne.c_str()), SyscallSucceeds()); EXPECT_THAT(ftruncate(fd.get(), kTruncateSize), SyscallSucceeds()); } TEST(MountTest, TmpfsSizePartialWriteSinglePage) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto tmpfs_size_opt = absl::StrCat("size=", kPageSize); auto const mount = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir.path(), kTmpfs, 0, tmpfs_size_opt, 0)); const std::string fileOne = JoinPath(dir.path(), "foo1"); auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(fileOne, O_CREAT | O_RDWR, 0777)); lseek(fd.get(), kPageSize - 2, SEEK_SET); char buf[4]; EXPECT_THAT(write(fd.get(), buf, 4), SyscallSucceedsWithValue(2)); EXPECT_THAT(write(fd.get(), buf, 4), SyscallFailsWithErrno(ENOSPC)); } TEST(MountTest, TmpfsSizePartialWriteMultiplePages) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto tmpfs_size_opt = absl::StrCat("size=", 3 * kPageSize); auto const mount = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir.path(), kTmpfs, 0, tmpfs_size_opt, 0)); const std::string fileOne = JoinPath(dir.path(), "foo1"); auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(fileOne, O_CREAT | O_RDWR, 0777)); lseek(fd.get(), kPageSize, SEEK_SET); std::vector buf(kPageSize + 2); EXPECT_THAT(write(fd.get(), buf.data(), 4), SyscallSucceedsWithValue(4)); struct stat status; ASSERT_THAT(fstat(fd.get(), &status), SyscallSucceeds()); EXPECT_EQ(status.st_size, kPageSize + 4); EXPECT_THAT(write(fd.get(), buf.data(), 1), SyscallSucceedsWithValue(1)); // Writing with size exactly until the end of page boundary. EXPECT_THAT(write(fd.get(), buf.data(), kPageSize - 5), SyscallSucceedsWithValue(kPageSize - 5)); EXPECT_THAT(write(fd.get(), buf.data(), 1), SyscallSucceedsWithValue(1)); // Writing with size more than page end & having extra page available as well. EXPECT_THAT(write(fd.get(), buf.data(), kPageSize + 1), SyscallSucceedsWithValue(kPageSize + 1)); // Writing with size more than page end & having no page available. EXPECT_THAT(write(fd.get(), buf.data(), kPageSize + 1), SyscallSucceedsWithValue(kPageSize - 2)); EXPECT_THAT(write(fd.get(), buf.data(), 1), SyscallFailsWithErrno(ENOSPC)); } TEST(MountTest, TmpfsSizeMmap) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto tmpfs_size_opt = absl::StrCat("size=", kPageSize); auto const mount = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir.path(), kTmpfs, 0, tmpfs_size_opt, 0)); const std::string fileOne = JoinPath(dir.path(), "foo"); auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(fileOne, O_CREAT | O_RDWR, 0777)); EXPECT_THAT(ftruncate(fd.get(), 2 * kPageSize), SyscallSucceeds()); void* addr = mmap(NULL, 2 * kPageSize, PROT_READ, MAP_PRIVATE, fd.get(), 0); EXPECT_NE(addr, MAP_FAILED); // Access memory so that the first page to page fault occurs and is allocated. char data = ((char*)addr)[kPageSize - 2]; EXPECT_EQ(data, 0); std::vector in(kPageSize + 2); // Access memory such that it causes the second page to page fault. The page // fault should fail due to hitting tmpfs size limit which should cause // SIGBUS signal. EXPECT_EXIT(memcpy(in.data(), reinterpret_cast(addr), kPageSize + 2), ::testing::KilledBySignal(SIGBUS), ""); EXPECT_THAT(munmap(addr, 2 * kPageSize), SyscallSucceeds()); } TEST(MountTest, SimpleBind) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mount = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir1.path(), kTmpfs, 0, "mode=0123", 0)); auto const child1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); auto const child2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); auto const bind_mount = Mount(dir1.path(), dir2.path(), "", MS_BIND, "", 0); // Write to child1 in dir1. const std::string filename = "foo.txt"; const std::string contents = "barbaz"; ASSERT_NO_ERRNO( CreateWithContents(JoinPath(child1.path(), filename), contents, 0666)); // Verify both directories have the same nodes. std::vector child_names = {std::string(Basename(child1.path())), std::string(Basename(child2.path()))}; ASSERT_NO_ERRNO(DirContains(dir1.path(), child_names, {})); ASSERT_NO_ERRNO(DirContains(dir2.path(), child_names, {})); const std::string dir1_filepath = JoinPath(dir1.path(), Basename(child1.path()), filename); const std::string dir2_filepath = JoinPath(dir2.path(), Basename(child1.path()), filename); std::string output; ASSERT_NO_ERRNO(GetContents(dir1_filepath, &output)); EXPECT_EQ(output, contents); ASSERT_NO_ERRNO(GetContents(dir2_filepath, &output)); EXPECT_EQ(output, contents); } TEST(MountTest, BindToSelf) { // Test that we can turn a normal directory into a mount with MS_BIND. SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); const std::vector mounts_before = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountsEntries()); for (const auto& e : mounts_before) { ASSERT_NE(e.mount_point, dir.path()); } auto const mount = ASSERT_NO_ERRNO_AND_VALUE( Mount(dir.path(), dir.path(), "", MS_BIND, "", 0)); const std::vector mounts_after = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountsEntries()); bool found = false; for (const auto& e : mounts_after) { if (e.mount_point == dir.path()) { found = true; } } ASSERT_TRUE(found); } TEST(MountTest, MaxMounts) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); ASSERT_THAT(mount("", parent.path().c_str(), kTmpfs, 0, ""), SyscallSucceeds()); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path())); ASSERT_THAT( mount(dir.path().c_str(), dir.path().c_str(), nullptr, MS_BIND, nullptr), SyscallSucceeds()); ASSERT_THAT(mount("", dir.path().c_str(), "", MS_SHARED, ""), SyscallSucceeds()); // Each bind mount doubles the number of mounts in the peer group. The number // of binds we can do before failing is log2(max_mounts-num_current_mounts). int mount_max = 10000; bool mount_max_exists = ASSERT_NO_ERRNO_AND_VALUE(Exists("/proc/sys/fs/mount-max")); if (mount_max_exists) { std::string mount_max_string; ASSERT_NO_ERRNO(GetContents("/proc/sys/fs/mount-max", &mount_max_string)); ASSERT_TRUE(absl::SimpleAtoi(mount_max_string, &mount_max)); } const std::vector mounts = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); int num_binds = static_cast(std::log2(mount_max - mounts.size())); for (int i = 0; i < num_binds; i++) { ASSERT_THAT(mount(dir.path().c_str(), dir.path().c_str(), nullptr, MS_BIND, nullptr), SyscallSucceeds()); } ASSERT_THAT( mount(dir.path().c_str(), dir.path().c_str(), nullptr, MS_BIND, nullptr), SyscallFailsWithErrno(ENOSPC)); umount2(parent.path().c_str(), MNT_DETACH); } TEST(MountTest, PropagateToSameMountpointStacksMounts) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt = ASSERT_NO_ERRNO_AND_VALUE(Mount( dir.path().c_str(), dir.path().c_str(), "", MS_BIND, "", MNT_DETACH)); auto const child = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path())); ASSERT_THAT(mount(child.path().c_str(), child.path().c_str(), "", MS_BIND, 0), SyscallSucceeds()); ASSERT_THAT(mount("", dir.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount( dir.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH)); std::string dir2_child_path = JoinPath(dir2.path(), Basename(child.path())); ASSERT_THAT( mount(dir2_child_path.c_str(), dir2_child_path.c_str(), "", MS_BIND, 0), SyscallSucceeds()); // Check that mounts at the child mount point have distinct parents. std::vector mounts = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); uint64_t parent_id = 0; for (auto& minfo : mounts) { if (minfo.mount_point == child.path()) { if (parent_id == 0) { parent_id = minfo.parent_id; } else { EXPECT_NE(parent_id, minfo.parent_id); } } } } TEST(MountTest, UmountReparentsCoveredMounts) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir.path().c_str(), kTmpfs, 0, "", MNT_DETACH)); ASSERT_THAT(mount("", dir.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const child = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path())); ASSERT_THAT(mount("", child.path().c_str(), kTmpfs, 0, 0), SyscallSucceeds()); auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount( dir.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH)); std::string dir2_child_path = JoinPath(dir2.path(), Basename(child.path())); ASSERT_THAT(mount("", dir2_child_path.c_str(), kTmpfs, 0, 0), SyscallSucceeds()); umount2(dir2_child_path.c_str(), MNT_DETACH); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); ASSERT_FALSE(optionals[child.path()].empty()); EXPECT_NE(optionals[child.path()][0].shared, 0); EXPECT_TRUE(optionals[dir2_child_path].empty()); } // Tests that it is possible to make a shared mount. TEST(MountTest, MakeShared) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir.path().c_str(), kTmpfs, 0, "", 0)); ASSERT_THAT(mount("", dir.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); ASSERT_FALSE(optionals[dir.path()].empty()); EXPECT_NE(optionals[dir.path()][0].shared, 0); } // Tests that shared mounts have different group IDs. TEST(MountTest, MakeMultipleShared) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mount1 = ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir1.path(), kTmpfs, 0, "", 0)); ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mount2 = ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir2.path(), kTmpfs, 0, "", 0)); ASSERT_THAT(mount("", dir2.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); ASSERT_FALSE(optionals[dir1.path()].empty()); ASSERT_FALSE(optionals[dir2.path()].empty()); EXPECT_NE(optionals[dir1.path()][0].shared, optionals[dir2.path()][0].shared); } // Tests that shared mounts reused group IDs from deleted groups. TEST(MountTest, ReuseGroupIDs) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mount1 = ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir1.path(), kTmpfs, 0, "", 0)); ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); int reused_group_id; { auto const mount2 = ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir2.path(), kTmpfs, 0, "", 0)); ASSERT_THAT(mount("", dir2.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); ASSERT_FALSE(optionals[dir2.path()].empty()); reused_group_id = optionals[dir2.path()][0].shared; } // Check that created a new shared mount reuses the ID 2. auto const mount2 = ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir2.path(), kTmpfs, 0, "", 0)); ASSERT_THAT(mount("", dir2.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); ASSERT_FALSE(optionals[dir2.path()].empty()); EXPECT_EQ(reused_group_id, optionals[dir2.path()][0].shared); } // Tests that a child mount inherits the propagation type of its parent. TEST(MountTest, InerheritPropagation) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mount1 = ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir1.path(), kTmpfs, 0, "", 0)); ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); auto const mount2 = ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir2.path(), kTmpfs, 0, "", 0)); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); ASSERT_FALSE(optionals[dir2.path()].empty()); EXPECT_NE(optionals[dir2.path()][0].shared, 0); } // Tests that it is possible to make a mount private again after it is shared. TEST(MountTest, MakePrivate) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt = ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), kTmpfs, 0, "", 0)); ASSERT_THAT(mount("", dir.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); ASSERT_THAT(mount("", dir.path().c_str(), "", MS_PRIVATE, 0), SyscallSucceeds()); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); ASSERT_FALSE(optionals[dir.path()].empty()); EXPECT_EQ(optionals[dir.path()][0].shared, 0); } TEST(MountTest, ArgumentsAreIgnored) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); // These mounts should not fail even though string arguments are passed as // NULL. auto const mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(dir.path(), dir.path(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount(NULL, dir.path().c_str(), NULL, MS_SHARED, NULL), SyscallSucceeds()); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); ASSERT_FALSE(optionals[dir.path()].empty()); EXPECT_NE(optionals[dir.path()][0].shared, 0); } TEST(MountTest, MultiplePropagationFlagsFails) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt = ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), kTmpfs, 0, "", 0)); EXPECT_THAT(mount("", dir.path().c_str(), "", MS_SHARED | MS_PRIVATE, 0), SyscallFailsWithErrno(EINVAL)); } TEST(MountTest, SetMountPropagationOfStackedMounts) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir.path().c_str(), kTmpfs, 0, "", 0)); // Only the topmost mount on the stack should be shared. auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir.path().c_str(), kTmpfs, 0, "", 0)); ASSERT_THAT(mount("", dir.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); ASSERT_FALSE(optionals[dir.path()].empty()); EXPECT_EQ(optionals[dir.path()][0].shared, 0); EXPECT_NE(optionals[dir.path()][1].shared, 0); } TEST(MountTest, MakePeer) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir1.path().c_str(), kTmpfs, 0, "", 0)); ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE( Mount(dir1.path(), dir2.path(), "", MS_BIND, "", MNT_DETACH)); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); ASSERT_FALSE(optionals[dir1.path()].empty()); ASSERT_FALSE(optionals[dir2.path()].empty()); EXPECT_EQ(optionals[dir1.path()][0].shared, optionals[dir2.path()][0].shared); EXPECT_NE(optionals[dir1.path()][0].shared, 0); } TEST(MountTest, PropagateMountEvent) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir1.path().c_str(), kTmpfs, 0, "", MNT_DETACH)); auto const child_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE( Mount(dir1.path(), dir2.path(), "", MS_BIND, "", MNT_DETACH)); // This mount should propagate to dir2. auto const child_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount("", child_dir.path().c_str(), kTmpfs, 0, "", MNT_DETACH)); const std::string child_path1 = JoinPath(dir1.path(), Basename(child_dir.path())); const std::string child_path2 = JoinPath(dir2.path(), Basename(child_dir.path())); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); ASSERT_FALSE(optionals[dir1.path()].empty()); ASSERT_FALSE(optionals[dir2.path()].empty()); ASSERT_FALSE(optionals[child_path1].empty()); ASSERT_FALSE(optionals[child_path2].empty()); EXPECT_EQ(optionals[dir1.path()][0].shared, optionals[dir2.path()][0].shared); EXPECT_NE(optionals[dir1.path()][0].shared, 0); EXPECT_EQ(optionals[child_path1][0].shared, optionals[child_path2][0].shared); EXPECT_NE(optionals[child_path1][0].shared, 0); } TEST(MountTest, PropagateUmountEvent) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir1.path().c_str(), kTmpfs, 0, "", MNT_DETACH)); auto const child_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE( Mount(dir1.path(), dir2.path(), "", MS_BIND, "", MNT_DETACH)); // This mount will propagate to dir2. Once the block ends it will be // unmounted, which should also propagate to dir2. { auto const child_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount("", child_dir.path().c_str(), kTmpfs, 0, "", MNT_DETACH)); } const std::string child_path1 = JoinPath(dir1.path(), Basename(child_dir.path())); const std::string child_path2 = JoinPath(dir2.path(), Basename(child_dir.path())); std::vector mounts = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); for (const auto& e : mounts) { EXPECT_NE(e.mount_point, child_path1); EXPECT_NE(e.mount_point, child_path2); } } TEST(MountTest, PropagateChildUmountEvent) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); TempPath const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); ASSERT_THAT(mount("", dir1.path().c_str(), kTmpfs, 0, ""), SyscallSucceeds()); TempPath const child = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); ASSERT_THAT(mount("", child.path().c_str(), kTmpfs, 0, ""), SyscallSucceeds()); ASSERT_THAT(mount("", child.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); TempPath const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); ASSERT_THAT(mount(child.path().c_str(), dir2.path().c_str(), "", MS_BIND, 0), SyscallSucceeds()); ASSERT_THAT(mount("", child.path().c_str(), kTmpfs, 0, ""), SyscallSucceeds()); ASSERT_THAT(umount2(dir1.path().c_str(), MNT_DETACH), SyscallSucceeds()); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); EXPECT_EQ(optionals[dir2.path()].size(), 1); ASSERT_EQ(umount2(dir2.path().c_str(), MNT_DETACH), 0); } TEST(MountTest, UmountIgnoresPeersWithChildren) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); ASSERT_THAT(mount("", dir1.path().c_str(), kTmpfs, 0, ""), SyscallSucceeds()); ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); ASSERT_THAT(mount(dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, 0), SyscallSucceeds()); auto const child_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); ASSERT_THAT(mount("", child_dir.path().c_str(), kTmpfs, 0, ""), SyscallSucceeds()); auto const grandchild_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(child_dir.path())); ASSERT_THAT(mount("", grandchild_dir.path().c_str(), kTmpfs, 0, ""), SyscallSucceeds()); const std::string child_path1 = JoinPath(dir1.path(), Basename(child_dir.path())); const std::string child_path2 = JoinPath(dir2.path(), Basename(child_dir.path())); ASSERT_THAT(mount("", child_path2.c_str(), "", MS_PRIVATE, 0), SyscallSucceeds()); const std::string grandchild_path2 = JoinPath(child_path2, Basename(grandchild_dir.path())); ASSERT_THAT(umount2(grandchild_path2.c_str(), MNT_DETACH), SyscallSucceeds()); // This umount event should not propagate to the peer at dir1 because its // child mount still has its own child mount. ASSERT_THAT(umount2(child_path2.c_str(), MNT_DETACH), SyscallSucceeds()); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); EXPECT_EQ(optionals[child_path2].size(), 0); EXPECT_EQ(optionals[child_path1].size(), 1); ASSERT_THAT(umount2(dir1.path().c_str(), MNT_DETACH), SyscallSucceeds()); ASSERT_THAT(umount2(dir2.path().c_str(), MNT_DETACH), SyscallSucceeds()); } TEST(MountTest, BindSharedOnShared) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const dir3 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const dir4 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); // Dir 1 and 2 are part of peer group 'A', dir 3 and 4 are part of peer group // 'B'. auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir1.path().c_str(), kTmpfs, 0, "", MNT_DETACH)); auto const dir5 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount( dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH)); auto const mnt3 = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir3.path().c_str(), kTmpfs, 0, "", MNT_DETACH)); ASSERT_THAT(mount("", dir3.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const mnt4 = ASSERT_NO_ERRNO_AND_VALUE(Mount( dir3.path().c_str(), dir4.path().c_str(), "", MS_BIND, "", MNT_DETACH)); const std::string dir5_path2 = JoinPath(dir2.path(), Basename(dir5.path())); // Bind peer group 'A' to peer group 'B'. auto const mnt5 = ASSERT_NO_ERRNO_AND_VALUE(Mount( dir4.path().c_str(), dir5.path().c_str(), "", MS_BIND, "", MNT_DETACH)); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); ASSERT_FALSE(optionals[dir1.path()].empty()); ASSERT_FALSE(optionals[dir2.path()].empty()); ASSERT_FALSE(optionals[dir3.path()].empty()); ASSERT_FALSE(optionals[dir4.path()].empty()); ASSERT_FALSE(optionals[dir5.path()].empty()); ASSERT_FALSE(optionals[dir5_path2].empty()); EXPECT_EQ(optionals[dir3.path()][0].shared, optionals[dir4.path()][0].shared); EXPECT_EQ(optionals[dir4.path()][0].shared, optionals[dir5.path()][0].shared); EXPECT_EQ(optionals[dir5.path()][0].shared, optionals[dir5_path2][0].shared); EXPECT_NE(optionals[dir3.path()][0].shared, 0); } TEST(MountTest, BindSharedOnPrivate) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir1.path().c_str(), kTmpfs, 0, "", MNT_DETACH)); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); auto const dir3 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const dir4 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt3 = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir3.path().c_str(), kTmpfs, 0, "", MNT_DETACH)); ASSERT_THAT(mount("", dir3.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const mnt4 = ASSERT_NO_ERRNO_AND_VALUE(Mount( dir3.path().c_str(), dir4.path().c_str(), "", MS_BIND, "", MNT_DETACH)); // bind to private mount. auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount( dir3.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH)); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); ASSERT_FALSE(optionals[dir1.path()].empty()); ASSERT_FALSE(optionals[dir2.path()].empty()); ASSERT_FALSE(optionals[dir3.path()].empty()); ASSERT_FALSE(optionals[dir4.path()].empty()); EXPECT_EQ(optionals[dir1.path()][0].shared, 0); EXPECT_EQ(optionals[dir2.path()][0].shared, optionals[dir3.path()][0].shared); EXPECT_EQ(optionals[dir3.path()][0].shared, optionals[dir4.path()][0].shared); } TEST(MountTest, BindPeerGroupsWithChildren) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir1.path().c_str(), kTmpfs, 0, "", MNT_DETACH)); ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir2.path().c_str(), kTmpfs, 0, "", MNT_DETACH)); ASSERT_THAT(mount("", dir2.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); // dir3 and dir4 are child mounts of dir1. auto const dir3 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); auto const mnt3 = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir3.path().c_str(), kTmpfs, 0, "", MNT_DETACH)); auto const dir4 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); auto const mnt4 = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir4.path().c_str(), kTmpfs, 0, "", MNT_DETACH)); auto const mnt5 = ASSERT_NO_ERRNO_AND_VALUE(Mount( dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH)); const std::string dir3_path2 = JoinPath(dir2.path(), Basename(dir3.path())); const std::string dir4_path2 = JoinPath(dir2.path(), Basename(dir4.path())); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); ASSERT_FALSE(optionals[dir1.path()].empty()); ASSERT_FALSE(optionals[dir2.path()].empty()); ASSERT_FALSE(optionals[dir3.path()].empty()); EXPECT_NE(optionals[dir1.path()][0].shared, optionals[dir3.path()][0].shared); EXPECT_NE(optionals[dir3.path()][0].shared, optionals[dir4.path()][0].shared); EXPECT_NE(optionals[dir4.path()][0].shared, optionals[dir1.path()][0].shared); EXPECT_EQ(optionals[dir3_path2].size(), 0); EXPECT_EQ(optionals[dir4_path2].size(), 0); } TEST(MountTest, BindParentToChild) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE(Mount( dir1.path().c_str(), dir1.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount( dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH)); auto const child_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); auto const mnt3 = ASSERT_NO_ERRNO_AND_VALUE( Mount(dir1.path().c_str(), child_dir.path().c_str(), "", MS_BIND, "", MNT_DETACH)); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); ASSERT_FALSE(optionals[dir1.path()].empty()); ASSERT_FALSE(optionals[dir2.path()].empty()); ASSERT_FALSE(optionals[child_dir.path()].empty()); EXPECT_EQ(optionals[dir1.path()][0].shared, optionals[dir2.path()][0].shared); EXPECT_EQ(optionals[dir2.path()][0].shared, optionals[child_dir.path()][0].shared); EXPECT_NE(optionals[dir1.path()][0].shared, 0); } TEST(MountTest, MountInfoHasRoot) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mount = ASSERT_NO_ERRNO_AND_VALUE( Mount("", parent.path(), kTmpfs, 0, "mode=0123", 0)); auto const child = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path())); auto const bind_mount = Mount(child.path(), child.path(), "", MS_BIND, "", 0); std::vector mounts = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); for (const auto& e : mounts) { if (e.mount_point == child.path()) { EXPECT_EQ(e.root, JoinPath("/", Basename(child.path()))) << "Offending mount ID is: " << e.id; } if (e.mount_point == parent.path()) { EXPECT_EQ(e.root, "/") << "Offending mount ID is: " << e.id; } } } TEST(MountTest, DeadMountsAreDecRefd) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); DisableSave ds; std::string home = NewTempAbsPath(); ASSERT_NO_ERRNO(Mkdir(home)); ASSERT_THAT(chdir(home.c_str()), SyscallSucceeds()); constexpr char dirpath[] = "./file"; for (int i = 0; i < 10; ++i) { const auto rest = [&] { mkdir(dirpath, 0); mount(dirpath, ".", 0, MS_BIND, 0); rmdir(dirpath); mkdir(dirpath, 0); mount(dirpath, ".", 0, MS_BIND, 0); }; EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); } } TEST(MountTest, UmountSharedBind) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); std::string home = NewTempAbsPath(); ASSERT_NO_ERRNO(Mkdir(home)); ASSERT_THAT(chdir(home.c_str()), SyscallSucceeds()); constexpr char dirpath[] = "./file"; ASSERT_THAT(mkdir(dirpath, 0), SyscallSucceeds()); ASSERT_THAT(mount(dirpath, dirpath, 0, MS_BIND, 0), SyscallSucceeds()); ASSERT_THAT(mount(0, dirpath, 0, MS_SHARED, 0), SyscallSucceeds()); ASSERT_THAT(mount(dirpath, dirpath, 0, MS_BIND, 0), SyscallSucceeds()); ASSERT_THAT(mount(0, dirpath, 0, MS_SHARED, 0), SyscallSucceeds()); ASSERT_THAT(umount2(dirpath, MNT_DETACH), SyscallSucceeds()); ASSERT_THAT(umount2(dirpath, MNT_DETACH), SyscallSucceeds()); } TEST(MountTest, MakeSlave) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE(Mount( dir1.path().c_str(), dir1.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount( dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount(0, dir2.path().c_str(), 0, MS_SLAVE, 0), SyscallSucceeds()); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); ASSERT_NE(optionals[dir1.path()][0].shared, 0); ASSERT_NE(optionals[dir2.path()][0].master, 0); } // Regression test: a bind mount that fails midway through attachment (e.g. // because the mount namespace is at its mount limit) must not leave its // half-constructed clone linked into the source's peer group. If it does, the // dead clone can later be selected as a slave's master, silently breaking // mount propagation to that slave. TEST(MountTest, FailedBindDoesNotBreakPropagation) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); // Mount A: a shared tmpfs. auto const dir_a = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt_a = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir_a.path(), kTmpfs, 0, "", MNT_DETACH)); ASSERT_THAT(mount("", dir_a.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); // Create the future mountpoint inside A so that it exists in all of A's // peers. std::string const sub_a = JoinPath(dir_a.path(), "sub"); ASSERT_THAT(mkdir(sub_a.c_str(), 0755), SyscallSucceeds()); // Mount B: a bind of A, and its peer. auto const dir_b = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt_b = ASSERT_NO_ERRNO_AND_VALUE( Mount(dir_a.path(), dir_b.path(), "", MS_BIND, "", MNT_DETACH)); // The default mount limits (100k on Linux, 10k on gVisor) can make this test // slow. Try and lower it if we're allowed to. constexpr char kMountMax[] = "/proc/sys/fs/mount-max"; bool changed_mount_max = false; int lower_mount_max = 100; Cleanup restore_mount_max; if (access(kMountMax, W_OK) == 0) { std::string const old_max = ASSERT_NO_ERRNO_AND_VALUE(GetContents(kMountMax)); ASSERT_NO_ERRNO(SetContents(kMountMax, absl::StrCat(lower_mount_max))); changed_mount_max = true; restore_mount_max = Cleanup([old_max, &kMountMax]() { EXPECT_NO_ERRNO(SetContents(kMountMax, old_max)); }); } // Fill the mount namespace up to its mount limit with private bind mounts, // each on its own directory in the outer tmpfs. All of them are children of // the outer mount, so detaching outer releases all of them with a single // umount2() call. auto const outer = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); ASSERT_THAT(mount("", outer.path().c_str(), kTmpfs, 0, 0), SyscallSucceeds()); auto outer_cleanup = Cleanup([&outer]() { EXPECT_THAT(umount2(outer.path().c_str(), MNT_DETACH), SyscallSucceeds()); }); if (!changed_mount_max) { // Repeatedly double the mounts in outer via recursive bind mounts of outer // onto subdirectories within outer. Each successful step doubles the total // number of mounts in outer. std::vector subdirs; while (true) { std::string const d = absl::StrCat(outer.path(), "/d", subdirs.size()); ASSERT_THAT(mkdir(d.c_str(), 0755), SyscallSucceeds()); if (mount(outer.path().c_str(), d.c_str(), nullptr, MS_BIND | MS_REC, nullptr) != 0) { break; } subdirs.push_back(d); } // Binary fill: try adding powers of 2 from largest to smallest to top off // the namespace without exceeding the limit. for (int i = static_cast(subdirs.size()) - 1; i >= 0; i--) { std::string const d = absl::StrCat(outer.path(), "/fill", i); ASSERT_THAT(mkdir(d.c_str(), 0755), SyscallSucceeds()); mount(subdirs[i].c_str(), d.c_str(), nullptr, MS_BIND | MS_REC, nullptr); } } bool full = false; for (int i = 0; i < lower_mount_max; i++) { std::string const d = absl::StrCat(outer.path(), "/single", i); ASSERT_THAT(mkdir(d.c_str(), 0755), SyscallSucceeds()); if (mount(d.c_str(), d.c_str(), nullptr, MS_BIND, nullptr) != 0) { const int err = errno; ASSERT_EQ(err, ENOSPC) << strerror(err); full = true; break; } } ASSERT_TRUE(full) << "couldn't fill up the mount namespace"; // Binding A while the namespace is at its mount limit must fail, and must // not leave any trace of the aborted mount in A's peer group. auto const dir_c = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); ASSERT_THAT(mount(dir_a.path().c_str(), dir_c.path().c_str(), nullptr, MS_BIND, nullptr), SyscallFailsWithErrno(ENOSPC)); // Release the mount budget again. outer_cleanup.Release()(); // Make A a slave. Its new master must be B, its only remaining peer. ASSERT_THAT(mount("", dir_a.path().c_str(), 0, MS_SLAVE, 0), SyscallSucceeds()); // A mount under the shared peer B must propagate to the slave A. std::string const sub_b = JoinPath(dir_b.path(), "sub"); ASSERT_THAT(mount("", sub_b.c_str(), kTmpfs, 0, 0), SyscallSucceeds()); auto sub_cleanup = Cleanup([&sub_b, &sub_a]() { EXPECT_THAT(umount2(sub_b.c_str(), MNT_DETACH), SyscallSucceeds()); // The umount of sub_b may or may not have propagated to sub_a depending // on whether the mount propagated there in the first place. umount2(sub_a.c_str(), MNT_DETACH); }); struct stat st_a, st_b; ASSERT_THAT(stat(sub_b.c_str(), &st_b), SyscallSucceeds()); ASSERT_THAT(stat(sub_a.c_str(), &st_a), SyscallSucceeds()); EXPECT_EQ(st_a.st_dev, st_b.st_dev) << "mount under shared peer B did not propagate to slave A"; } TEST(MountTest, MountMax) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); constexpr char kMountMax[] = "/proc/sys/fs/mount-max"; SKIP_IF(access(kMountMax, W_OK) != 0); std::string const old_max = ASSERT_NO_ERRNO_AND_VALUE(GetContents(kMountMax)); auto const restore = Cleanup([&]() { EXPECT_NO_ERRNO(SetContents(kMountMax, old_max)); }); // The limit must be at least 1 (Linux: proc_dointvec_minmax with // extra1 == SYSCTL_ONE). EXPECT_THAT(SetContents(kMountMax, "0"), PosixErrorIs(EINVAL)); // Lower the limit to the current number of mounts; any new mount must then // fail with ENOSPC. auto const mounts = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountsEntries()); ASSERT_NO_ERRNO(SetContents(kMountMax, absl::StrCat(mounts.size()))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); EXPECT_THAT(mount("", dir.path().c_str(), kTmpfs, 0, 0), SyscallFailsWithErrno(ENOSPC)); // Mounting works again once the limit is restored. ASSERT_NO_ERRNO(SetContents(kMountMax, old_max)); auto const mnt = ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), kTmpfs, 0, "", 0)); } TEST(MountTest, MakeSharedSlave) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE(Mount( dir1.path().c_str(), dir1.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount( dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount(0, dir2.path().c_str(), 0, MS_SLAVE, 0), SyscallSucceeds()); ASSERT_THAT(mount(0, dir2.path().c_str(), 0, MS_SHARED, 0), SyscallSucceeds()); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); ASSERT_NE(optionals[dir1.path()][0].shared, 0); ASSERT_NE(optionals[dir2.path()][0].shared, 0); ASSERT_NE(optionals[dir2.path()][0].master, 0); } TEST(MountTest, PrivateMasterUnslaves) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const base = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const base_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount("", base.path().c_str(), kTmpfs, 0, "", MNT_DETACH)); ASSERT_THAT(mount("", base.path().c_str(), "", MS_PRIVATE, 0), SyscallSucceeds()); auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(base.path())); auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE(Mount( dir1.path().c_str(), dir1.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(base.path())); auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount( dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount(0, dir2.path().c_str(), 0, MS_SLAVE, 0), SyscallSucceeds()); ASSERT_THAT(mount(0, dir2.path().c_str(), 0, MS_SHARED, 0), SyscallSucceeds()); ASSERT_THAT(mount(0, dir1.path().c_str(), 0, MS_PRIVATE, 0), SyscallSucceeds()); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); ASSERT_EQ(optionals[dir1.path()][0].shared, 0); ASSERT_EQ(optionals[dir2.path()][0].master, 0); ASSERT_NE(optionals[dir2.path()][0].shared, 0); } TEST(MountTest, SlaveMaster) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE(Mount( dir1.path().c_str(), dir1.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount( dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount(0, dir2.path().c_str(), 0, MS_SLAVE, 0), SyscallSucceeds()); ASSERT_THAT(mount(0, dir2.path().c_str(), 0, MS_SHARED, 0), SyscallSucceeds()); auto const dir3 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt3 = ASSERT_NO_ERRNO_AND_VALUE(Mount( dir2.path().c_str(), dir3.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount(0, dir3.path().c_str(), 0, MS_SLAVE, 0), SyscallSucceeds()); ASSERT_THAT(mount(0, dir2.path().c_str(), 0, MS_PRIVATE, 0), SyscallSucceeds()); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); ASSERT_NE(optionals[dir1.path()][0].shared, 0); ASSERT_EQ(optionals[dir2.path()][0].shared, 0); ASSERT_EQ(optionals[dir2.path()][0].master, 0); ASSERT_EQ(optionals[dir3.path()][0].master, optionals[dir1.path()][0].shared); } TEST(MountTest, BindSharedToSlave) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const src = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const src_mnt = ASSERT_NO_ERRNO_AND_VALUE(Mount( src.path().c_str(), src.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", src.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const dst_master = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const dst_master_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(dst_master.path().c_str(), dst_master.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", dst_master.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const dst = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const dst_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(dst_master.path().c_str(), dst.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", dst.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); auto const dst_mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount( src.path().c_str(), dst.path().c_str(), "", MS_BIND, "", MNT_DETACH)); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); ASSERT_EQ(optionals[src.path()][0].shared, optionals[dst.path()][1].shared); ASSERT_EQ(optionals[dst.path()][0].master, optionals[dst_master.path()][0].shared); } TEST(MountTest, BindSlaveToShared) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const src = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const src_mnt = ASSERT_NO_ERRNO_AND_VALUE(Mount( src.path().c_str(), src.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", src.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const src_master = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const src_master_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(src.path().c_str(), src_master.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", src.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); auto const dst = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const dst_mnt = ASSERT_NO_ERRNO_AND_VALUE(Mount( dst.path().c_str(), dst.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", dst.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const dst_peer = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const dst_peer_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(dst.path().c_str(), dst_peer.path().c_str(), "", MS_BIND, "", MNT_DETACH)); auto const dst_mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount( src.path().c_str(), dst.path().c_str(), "", MS_BIND, "", MNT_DETACH)); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); ASSERT_EQ(optionals[dst.path()][1].shared, optionals[dst_peer.path()][1].shared); ASSERT_EQ(optionals[dst.path()][1].master, optionals[dst_peer.path()][1].master); ASSERT_EQ(optionals[dst.path()][1].master, optionals[src_master.path()][0].shared); } TEST(MountTest, BindSlaveToSlave) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const src = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const src_mnt = ASSERT_NO_ERRNO_AND_VALUE(Mount( src.path().c_str(), src.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", src.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const src_master = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const src_master_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(src.path().c_str(), src_master.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", src.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); auto const dst = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const dst_mnt = ASSERT_NO_ERRNO_AND_VALUE(Mount( dst.path().c_str(), dst.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", dst.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const dst_master = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const dst_master_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(dst.path().c_str(), dst_master.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", dst.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); auto const dst_mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount( src.path().c_str(), dst.path().c_str(), "", MS_BIND, "", MNT_DETACH)); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); ASSERT_EQ(optionals[dst.path()][1].master, optionals[src.path()][0].master); } // Test that mounting on a slave mount does not propagate to the master. TEST(MountTest, SlavePropagationEvent) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dst_master = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const dst_master_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(dst_master.path().c_str(), dst_master.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", dst_master.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const dst = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const dst_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(dst_master.path().c_str(), dst.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", dst.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); auto const child = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dst_master.path())); const std::string master_child_path = JoinPath(dst_master.path(), Basename(child.path())); const std::string slave_child_path = JoinPath(dst.path(), Basename(child.path())); auto const slave_child_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount("", slave_child_path.c_str(), kTmpfs, 0, "", MNT_DETACH)); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); ASSERT_EQ(optionals[child.path()].size(), 0); } // We are building a propagation tree that looks like this: /* A <--> B <--> C <---> D /|\ /| |\ / F G J K H I / E<-->O /|\ M L N */ // Propagating mount events across this tree should cover most propagation // cases. TEST(MountTest, LargeTreePropagationEvent) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); // 15 total mounts that should all get propagated to if we mount on A. auto const a = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const b = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const c = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const d = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const e = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const g = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const h = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const i = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const j = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const k = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const l = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const m = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const n = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const o = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const a_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(a.path().c_str(), a.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", a.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); // Place E, F, and G in A's peer group, then make them slaves. auto const e_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(a.path().c_str(), e.path().c_str(), "", MS_BIND, "", MNT_DETACH)); auto const f_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(a.path().c_str(), f.path().c_str(), "", MS_BIND, "", MNT_DETACH)); auto const g_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(a.path().c_str(), g.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", e.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); ASSERT_THAT(mount("", f.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); ASSERT_THAT(mount("", g.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); // Add B to A's shared group. auto const b_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(a.path().c_str(), b.path().c_str(), "", MS_BIND, "", MNT_DETACH)); // Add C to A's shared group and place J and K in C's peer group, then make // them slaves. auto const c_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(a.path().c_str(), c.path().c_str(), "", MS_BIND, "", MNT_DETACH)); auto const j_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(c.path().c_str(), j.path().c_str(), "", MS_BIND, "", MNT_DETACH)); auto const k_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(c.path().c_str(), k.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", j.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); ASSERT_THAT(mount("", k.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); // Add D to A's shared group and place H and I in Ds peer group, then make // them slaves. auto const d_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(a.path().c_str(), d.path().c_str(), "", MS_BIND, "", MNT_DETACH)); auto const h_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(d.path().c_str(), h.path().c_str(), "", MS_BIND, "", MNT_DETACH)); auto const i_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(d.path().c_str(), i.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", h.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); ASSERT_THAT(mount("", i.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); // Make E shared and create a group with O. ASSERT_THAT(mount("", e.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const o_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(e.path().c_str(), o.path().c_str(), "", MS_BIND, "", MNT_DETACH)); // Add M, L and N to K's shared group and make them slaves of O. auto const m_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(o.path().c_str(), m.path().c_str(), "", MS_BIND, "", MNT_DETACH)); auto const l_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(o.path().c_str(), l.path().c_str(), "", MS_BIND, "", MNT_DETACH)); auto const n_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(o.path().c_str(), n.path().c_str(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", m.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); ASSERT_THAT(mount("", l.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); ASSERT_THAT(mount("", n.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); std::vector mounts = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); ASSERT_THAT(mount("", a.path().c_str(), kTmpfs, 0, ""), SyscallSucceeds()); std::vector mounts_after_mount = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); ASSERT_EQ(mounts_after_mount.size(), mounts.size() + 15); auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); // A, B, C, and D are all mounted over and in a peer group. ASSERT_NE(optionals[a.path()][1].shared, 0); ASSERT_EQ(optionals[a.path()][1].shared, optionals[b.path()][1].shared); ASSERT_EQ(optionals[a.path()][1].shared, optionals[c.path()][1].shared); ASSERT_EQ(optionals[a.path()][1].shared, optionals[d.path()][1].shared); // E, F, G, H, I, J, K are all mounted over and slave to A's peer group. ASSERT_EQ(optionals[a.path()][1].shared, optionals[e.path()][1].master); ASSERT_EQ(optionals[a.path()][1].shared, optionals[f.path()][1].master); ASSERT_EQ(optionals[a.path()][1].shared, optionals[g.path()][1].master); ASSERT_EQ(optionals[a.path()][1].shared, optionals[h.path()][1].master); ASSERT_EQ(optionals[a.path()][1].shared, optionals[i.path()][1].master); ASSERT_EQ(optionals[a.path()][1].shared, optionals[j.path()][1].master); ASSERT_EQ(optionals[a.path()][1].shared, optionals[k.path()][1].master); // E and O are all mounted over and in a peer group. ASSERT_EQ(optionals[e.path()][1].shared, optionals[o.path()][1].shared); // L, M, and N are all mounted over and slaves to E's peer group. ASSERT_EQ(optionals[e.path()][1].shared, optionals[l.path()][1].master); ASSERT_EQ(optionals[e.path()][1].shared, optionals[m.path()][1].master); ASSERT_EQ(optionals[e.path()][1].shared, optionals[n.path()][1].master); ASSERT_THAT(umount2(a.path().c_str(), MNT_DETACH), SyscallSucceeds()); std::vector mounts_after_umount = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); ASSERT_EQ(mounts_after_umount.size(), mounts.size()); } TEST(MountTest, MaxMountsWithSlave) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const parent_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount("test", parent.path(), kTmpfs, 0, "mode=0123", MNT_DETACH)); auto const a = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path())); auto const b = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path())); auto const c = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path())); auto const a_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount("test", a.path(), kTmpfs, 0, "mode=0123", MNT_DETACH)); ASSERT_THAT(mount("", a.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const b_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(a.path(), b.path(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", b.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); ASSERT_THAT(mount("", b.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const c_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(b.path(), c.path(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", c.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); auto const d = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(a.path())); auto const e = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(a.path())); int mount_max = 10000; bool mount_max_exists = ASSERT_NO_ERRNO_AND_VALUE(Exists("/proc/sys/fs/mount-max")); if (mount_max_exists) { std::string mount_max_string; ASSERT_NO_ERRNO(GetContents("/proc/sys/fs/mount-max", &mount_max_string)); ASSERT_TRUE(absl::SimpleAtoi(mount_max_string, &mount_max)); } // Each bind mount doubles the number of mounts in the propagation tree // starting with 3. The number of binds we can do before failing is // log2((max_mounts-num_current_mounts)/3). std::vector mounts = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); int num_binds = static_cast(std::log2((mount_max - mounts.size()) / 3)); for (int i = 0; i < num_binds; i++) { ASSERT_THAT( mount(d.path().c_str(), d.path().c_str(), nullptr, MS_BIND, nullptr), SyscallSucceeds()); ASSERT_THAT(mount("", d.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); } for (int i = 0; i < 2; i++) { EXPECT_THAT( mount(d.path().c_str(), d.path().c_str(), nullptr, MS_BIND, nullptr), SyscallFailsWithErrno(ENOSPC)); } } TEST(MountTest, SetPropagationRecursive) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); const TempPath a = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const a_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount("test", a.path(), kTmpfs, 0, "mode=0123", MNT_DETACH)); const auto b = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(a.path())); auto const b_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount("test", b.path(), kTmpfs, 0, "mode=0123", MNT_DETACH)); const auto c = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(b.path())); auto const c_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount("test", c.path(), kTmpfs, 0, "mode=0123", MNT_DETACH)); const auto d = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(c.path())); auto const d_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount("test", d.path(), kTmpfs, 0, "mode=0123", MNT_DETACH)); ASSERT_THAT(mount("", a.path().c_str(), "", MS_SHARED | MS_REC, 0), SyscallSucceeds()); absl::flat_hash_map> optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); EXPECT_NE(optionals[a.path()][0].shared, 0); EXPECT_NE(optionals[b.path()][0].shared, 0); EXPECT_NE(optionals[c.path()][0].shared, 0); EXPECT_NE(optionals[d.path()][0].shared, 0); } TEST(MountTest, SetSlaveRecursive) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); const auto a = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const a_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(a.path(), a.path(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", a.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); const auto a_master = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const a_master_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(a.path(), a_master.path(), "", MS_BIND, "", MNT_DETACH)); const auto b = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(a.path())); auto const b_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(a.path(), b.path(), "", MS_BIND, "", MNT_DETACH)); const auto b_master = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const b_master_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(b.path(), b_master.path(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount("", a.path().c_str(), "", MS_SLAVE | MS_REC, 0), SyscallSucceeds()); absl::flat_hash_map> optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); EXPECT_EQ(optionals[a.path()][0].master, optionals[a_master.path()][0].shared); EXPECT_EQ(optionals[b.path()][0].master, optionals[b_master.path()][0].shared); } TEST(MountTest, RecursiveBind) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); const auto a = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const a_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(a.path(), a.path(), "", MS_BIND, "", MNT_DETACH)); const auto b = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(a.path())); auto const b_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(b.path(), b.path(), "", MS_BIND, "", MNT_DETACH)); const auto c = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(b.path())); const auto d = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const d_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(a.path(), d.path(), "", MS_BIND | MS_REC, "", MNT_DETACH)); // Write to child1 in dir1. const std::string filename = "foo.txt"; const std::string contents = "barbaz"; ASSERT_NO_ERRNO( CreateWithContents(JoinPath(c.path(), filename), contents, 0666)); // Verify both directories have the same nodes. const std::string path = JoinPath(d.path(), Basename(b.path()), Basename(c.path()), filename); std::string output; ASSERT_NO_ERRNO(GetContents(path, &output)); EXPECT_EQ(output, contents); } TEST(MountTest, MaxRecursiveBind) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); const auto a = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const a_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(a.path(), a.path(), "", MS_BIND, "", MNT_DETACH)); const auto b = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(a.path())); int mount_max = 10000; bool mount_max_exists = ASSERT_NO_ERRNO_AND_VALUE(Exists("/proc/sys/fs/mount-max")); if (mount_max_exists) { std::string mount_max_string; ASSERT_NO_ERRNO(GetContents("/proc/sys/fs/mount-max", &mount_max_string)); ASSERT_TRUE(absl::SimpleAtoi(mount_max_string, &mount_max)); } const std::vector mounts = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); int num_binds = static_cast(std::log2(mount_max - mounts.size())); for (int i = 0; i < num_binds; i++) { ASSERT_THAT(mount(a.path().c_str(), b.path().c_str(), nullptr, MS_BIND | MS_REC, nullptr), SyscallSucceeds()); } ASSERT_THAT(mount(a.path().c_str(), b.path().c_str(), nullptr, MS_BIND | MS_REC, nullptr), SyscallFailsWithErrno(ENOSPC)); } TEST(MountTest, RecursiveBindPropagation) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); const auto parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const parent_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(parent.path(), parent.path(), "", MS_BIND, "", MNT_DETACH)); const auto a = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path())); const auto b = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path())); const auto c = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path())); auto const a_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount("test", a.path(), kTmpfs, 0, "", MNT_DETACH)); ASSERT_THAT(mount("", a.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); auto const b_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(a.path(), b.path(), "", MS_BIND, "", MNT_DETACH)); auto const c_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(a.path(), c.path(), "", MS_BIND, "", MNT_DETACH)); const auto d = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path())); const auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(a.path())); auto const d_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(d.path(), d.path(), "", MS_BIND, "", MNT_DETACH)); const auto e = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(d.path())); auto const e_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(e.path(), e.path(), "", MS_BIND, "", MNT_DETACH)); auto const f_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(d.path(), f.path(), "", MS_BIND | MS_REC, "", MNT_DETACH)); absl::flat_hash_map> optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); auto b_e_path = JoinPath(b.path(), Basename(f.path()), Basename(e.path())); ASSERT_FALSE(optionals[b_e_path].empty()); auto c_e_path = JoinPath(c.path(), Basename(f.path()), Basename(e.path())); ASSERT_FALSE(optionals[c_e_path].empty()); } TEST(MountTest, MountNamespace) { 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(), kTmpfs, 0, "mode=0700", 0)); auto const file_path = JoinPath(dir.path(), "foo"); EXPECT_NO_ERRNO(Open(file_path, O_CREAT | O_RDWR, 0777)); pid_t child = fork(); if (child == 0) { // Create a new mount namespace and umount the test mount from it. TEST_PCHECK(unshare(CLONE_NEWNS) == 0); TEST_PCHECK(access(file_path.c_str(), F_OK) == 0); TEST_PCHECK(umount2(dir.path().c_str(), MNT_DETACH) == 0); _exit(0); } ASSERT_THAT(child, SyscallSucceeds()); int status; ASSERT_THAT(waitpid(child, &status, 0), SyscallSucceedsWithValue(child)); ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); // Check that the test mount is still here. EXPECT_NO_ERRNO(Open(file_path, O_RDWR)); } TEST(MountTest, MountNamespaceSetns) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir.path(), kTmpfs, 0, "mode=0700", MNT_DETACH)); auto const file_path = JoinPath(dir.path(), "foo"); EXPECT_NO_ERRNO(Open(file_path, O_CREAT | O_RDWR, 0777)); const FileDescriptor nsfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/thread-self/ns/mnt", O_RDONLY)); pid_t child = fork(); if (child == 0) { TEST_PCHECK(unshare(CLONE_NEWNS) == 0); TEST_PCHECK(umount2(dir.path().c_str(), MNT_DETACH) == 0); TEST_PCHECK(setns(nsfd.get(), CLONE_NEWNS) == 0); TEST_PCHECK(access(file_path.c_str(), F_OK) == 0); _exit(0); } ASSERT_THAT(child, SyscallSucceeds()); int status; ASSERT_THAT(waitpid(child, &status, 0), SyscallSucceedsWithValue(child)); ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); } TEST(MountTest, MountNamespacePropagation) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir.path(), kTmpfs, 0, "mode=0700", MNT_DETACH)); auto child_dir = JoinPath(dir.path(), "test"); auto const file_path = JoinPath(child_dir, "foo"); auto const file2_path = JoinPath(child_dir, "boo"); ASSERT_THAT(mount(NULL, dir.path().c_str(), NULL, MS_SHARED, NULL), SyscallSucceeds()); ASSERT_THAT(mkdir(child_dir.c_str(), 0700), SyscallSucceeds()); ASSERT_THAT(mount("child", child_dir.c_str(), kTmpfs, 0, NULL), SyscallSucceeds()); EXPECT_NO_ERRNO(Open(file_path, O_CREAT | O_RDWR, 0777)); pid_t child = fork(); if (child == 0) { TEST_PCHECK(unshare(CLONE_NEWNS) == 0); TEST_PCHECK(access(file_path.c_str(), F_OK) == 0); // The test mount has to be umounted from the second mount namespace too. TEST_PCHECK(umount2(child_dir.c_str(), MNT_DETACH) == 0); // The new mount has to be propagated to the second mount namespace. TEST_PCHECK(mount("test2", child_dir.c_str(), kTmpfs, 0, NULL) == 0); TEST_PCHECK(mknod(file2_path.c_str(), 0777 | S_IFREG, 0) == 0); _exit(0); } ASSERT_THAT(child, SyscallSucceeds()); int status; ASSERT_THAT(waitpid(child, &status, 0), SyscallSucceedsWithValue(child)); ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); // Check that the test mount is still here. EXPECT_NO_ERRNO(Open(file2_path, O_RDWR)); EXPECT_THAT(umount2(child_dir.c_str(), MNT_DETACH), SyscallSucceeds()); } TEST(MountTest, MountNamespaceSlavesNewUserNamespace) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); const Cleanup dir_mount = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir.path(), kTmpfs, 0, "", MNT_DETACH)); ASSERT_THAT(mount(NULL, dir.path().c_str(), NULL, MS_SHARED, NULL), SyscallSucceeds()); const TempPath child_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path())); const std::string file_path = JoinPath(child_dir.path(), "foo"); const std::string file2_path = JoinPath(child_dir.path(), "boo"); const std::function parent = [&] { TEST_CHECK_SUCCESS( mount("child", child_dir.path().c_str(), kTmpfs, 0, NULL)); TEST_CHECK_SUCCESS(open(file_path.c_str(), O_CREAT | O_RDWR, 0777)); }; const std::function child = [&] { TEST_CHECK_SUCCESS(access(JoinPath(child_dir.path(), "foo").c_str(), F_OK)); // These mount operations will not propagate to the other namespace // because it is a slave mount. TEST_CHECK_SUCCESS(umount2(child_dir.path().c_str(), MNT_DETACH)); TEST_CHECK_SUCCESS( mount("test2", child_dir.path().c_str(), kTmpfs, 0, NULL)); TEST_CHECK_SUCCESS(mknod(file2_path.c_str(), 0777 | S_IFREG, 0)); TEST_CHECK_SUCCESS(umount2(child_dir.path().c_str(), MNT_DETACH)); // This should fail because the mount is locked. TEST_CHECK_ERRNO(umount2(child_dir.path().c_str(), MNT_DETACH), EINVAL); // Check that there is a master entry in mountinfo. int fd = open("/proc/self/mountinfo", O_RDONLY); TEST_CHECK(fd >= 0); char child_mountinfo[0x8000]; int size = 0; while (true) { int ret = read(fd, child_mountinfo + size, sizeof(child_mountinfo) - size); TEST_CHECK(ret != -1); size += ret; if (ret == 0) { break; } } TEST_CHECK(absl::StrContains(child_mountinfo, "master:")); }; EXPECT_THAT(InForkedUserMountNamespace(parent, child), IsPosixErrorOkAndHolds(0)); // Check that the test mount is still here. EXPECT_EQ(Open(file2_path, O_RDWR).error().errno_value(), ENOENT); EXPECT_THAT(umount2(child_dir.path().c_str(), MNT_DETACH), SyscallSucceeds()); } TEST(MountTest, LockedMountStopsNonRecBind) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); const Cleanup dir_mount = ASSERT_NO_ERRNO_AND_VALUE( Mount("", dir.path(), kTmpfs, 0, "", MNT_DETACH)); const TempPath child_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path())); const Cleanup child_mount = ASSERT_NO_ERRNO_AND_VALUE( Mount("", child_dir.path().c_str(), kTmpfs, 0, "", MNT_DETACH)); const std::string foo_dir = JoinPath(dir.path(), "foo"); const std::function child_fn = [&] { TEST_CHECK_SUCCESS(mkdir(foo_dir.c_str(), 0700)); TEST_CHECK_ERRNO( mount(dir.path().c_str(), foo_dir.c_str(), "", MS_BIND, ""), EINVAL); TEST_CHECK_SUCCESS( mount(dir.path().c_str(), foo_dir.c_str(), "", MS_BIND | MS_REC, "")); }; EXPECT_THAT(InForkedUserMountNamespace([] {}, child_fn), IsPosixErrorOkAndHolds(0)); } // This test checks that a mount tree that propagates from a more privileged // mount namespace cannot be partially unmounted. It must be unmounted as a // single unit as described in point 4 of the notes in // https://man7.org/linux/man-pages/man7/mount_namespaces.7.html. This test // also checks that unmounting a propagated mount tree does not reveal the // contents of overmounted filesystems from the more privileged mount namespace. TEST(MountTest, UmountPropagatedSubtreeFromPrivilegedNS) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); const Cleanup dir_mount = ASSERT_NO_ERRNO_AND_VALUE( Mount(dir.path(), dir.path(), "", MS_BIND, "", MNT_DETACH)); ASSERT_THAT(mount(NULL, dir.path().c_str(), NULL, MS_SHARED, NULL), SyscallSucceeds()); const TempPath child_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path())); const TempPath sibling_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path())); const Cleanup child_mount = ASSERT_NO_ERRNO_AND_VALUE( Mount("", child_dir.path(), kTmpfs, 0, "", MNT_DETACH)); ASSERT_THAT(mount(NULL, child_dir.path().c_str(), NULL, MS_PRIVATE, NULL), SyscallSucceeds()); const TempPath grandchild_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(child_dir.path())); ASSERT_THAT(open(JoinPath(grandchild_dir.path(), "foo").c_str(), O_CREAT | O_RDWR, 0777), SyscallSucceeds()); const Cleanup grandchild_mnt = ASSERT_NO_ERRNO_AND_VALUE( Mount("", grandchild_dir.path(), kTmpfs, 0, "", MNT_DETACH)); ASSERT_THAT( mount(NULL, grandchild_dir.path().c_str(), NULL, MS_PRIVATE, NULL), SyscallSucceeds()); const std::string grandsibling_dir = JoinPath(sibling_dir.path(), Basename(grandchild_dir.path())); const std::function parent = [&] { TEST_CHECK_SUCCESS(mount(child_dir.path().c_str(), sibling_dir.path().c_str(), "", MS_BIND | MS_REC, "")); TEST_CHECK_SUCCESS( mount("", sibling_dir.path().c_str(), "", MS_PRIVATE | MS_REC, "")); }; // You can umount an entire subtree that propagated from a more privileged // mount namespace, but can't umount only part of the subtree. const std::string file_path = JoinPath(Basename(grandchild_dir.path()), "foo"); const std::function child = [&] { TEST_CHECK_ERRNO(umount2(grandsibling_dir.c_str(), MNT_DETACH), EINVAL); int dirfd = open(sibling_dir.path().c_str(), O_RDONLY | O_DIRECTORY); TEST_PCHECK(dirfd >= 0); TEST_CHECK_SUCCESS(umount2(sibling_dir.path().c_str(), MNT_DETACH)); // Check to ensure you cannot access an overmounted file with openat after // the mount unit has been destroyed. TEST_CHECK_ERRNO(openat(dirfd, file_path.c_str(), O_RDONLY), ENOENT); }; EXPECT_THAT(InForkedUserMountNamespace(parent, child), IsPosixErrorOkAndHolds(0)); } TEST(MountTest, MountFailsOnPseudoFilesystemMountpoint) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const fd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, 0)); std::string path = absl::StrCat("/proc/self/fd/", fd.get()); // After 0d039eac6e595 ("fix a couple of races in MNT_TREE_BENEATH handling // by do_move_mount()"), this hits fs/namespace.c:do_lock_mount() => // !is_mounted() (eventfds use fs/anon_inodes.c:anon_inode_mnt, which has // real_mount(mnt)->mnt_ns == fs/mount.h:MNT_NS_INTERNAL == ERR_PTR(-EINVAL) // from kern_mount()) and returns ENOENT. EXPECT_THAT( mount("test", path.c_str(), kTmpfs, 0, 0), AnyOf(SyscallFailsWithErrno(EINVAL), SyscallFailsWithErrno(ENOENT))); } TEST(MountTest, ChangeMountFlags) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); const auto flag = MS_NODEV; const auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); const auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); const auto mount_cleanup = ASSERT_NO_ERRNO_AND_VALUE( Mount(dir1.path(), dir2.path(), kTmpfs, MS_BIND, "", 0)); struct statfs st; EXPECT_THAT(mount(dir1.path().c_str(), dir2.path().c_str(), kTmpfs, MS_REMOUNT | MS_BIND | flag, ""), SyscallSucceeds()); EXPECT_THAT(statfs(dir2.path().c_str(), &st), SyscallSucceeds()); EXPECT_EQ(st.f_flags & flag, flag); // Resets mount flags. EXPECT_THAT(mount(dir1.path().c_str(), dir2.path().c_str(), kTmpfs, MS_REMOUNT | MS_BIND, ""), SyscallSucceeds()); ASSERT_THAT(statfs(dir2.path().c_str(), &st), SyscallSucceeds()); ASSERT_EQ(st.f_flags & flag, 0); } constexpr int kLockableFlags = MS_RDONLY | MS_NOEXEC | MS_NODEV | MS_NOSUID; // Sets kLockableFlags on the mount at `path`. void SetLockableFlags(const std::string& path) { ASSERT_THAT(mount("", path.c_str(), nullptr, MS_REMOUNT | MS_BIND | kLockableFlags, ""), SyscallSucceeds()); } TEST(MountTest, ChangeMountFlagsOfLockedMount) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); const Cleanup mount_cleanup = ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), kTmpfs, 0, "", 0)); ASSERT_NO_FATAL_FAILURE(SetLockableFlags(dir.path())); const std::string path = dir.path(); const std::string file_path = JoinPath(path, "foo"); const std::function child = [&] { // The mount was copied into a new user namespace, so none of its flags may // be cleared. for (const int flag : {MS_RDONLY, MS_NOEXEC, MS_NODEV, MS_NOSUID}) { TEST_CHECK_ERRNO( mount("", path.c_str(), nullptr, MS_REMOUNT | MS_BIND | (kLockableFlags & ~flag), ""), EPERM); } TEST_CHECK_ERRNO(mount("", path.c_str(), nullptr, MS_REMOUNT | MS_BIND, ""), EPERM); // The mount is still read-only. TEST_CHECK_ERRNO(open(file_path.c_str(), O_CREAT | O_RDWR, 0666), EROFS); // A remount that keeps all of the locked flags set is allowed. TEST_CHECK_SUCCESS(mount("", path.c_str(), nullptr, MS_REMOUNT | MS_BIND | kLockableFlags, "")); }; EXPECT_THAT(InForkedUserMountNamespace([] {}, child), IsPosixErrorOkAndHolds(0)); // The flags are not locked in the namespace that owns the mount. EXPECT_THAT(mount("", path.c_str(), nullptr, MS_REMOUNT | MS_BIND, ""), SyscallSucceeds()); struct statfs st; ASSERT_THAT(statfs(path.c_str(), &st), SyscallSucceeds()); EXPECT_EQ(st.f_flags & kLockableFlags, 0); } TEST(MountTest, LockedMountFlagsAreInheritedByBind) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); const Cleanup mount_cleanup = ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), kTmpfs, 0, "", 0)); ASSERT_NO_FATAL_FAILURE(SetLockableFlags(dir.path())); const TempPath target = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); const std::string path = dir.path(); const std::string target_path = target.path(); const std::function child = [&] { // Binding the locked mount elsewhere must not result in a mount for which // flags can be cleared. TEST_CHECK_SUCCESS( mount(path.c_str(), target_path.c_str(), "", MS_BIND, "")); for (const int flag : {MS_RDONLY, MS_NOEXEC, MS_NODEV, MS_NOSUID}) { TEST_CHECK_ERRNO( mount("", target_path.c_str(), nullptr, MS_REMOUNT | MS_BIND | (kLockableFlags & ~flag), ""), EPERM); } TEST_CHECK_SUCCESS(mount("", target_path.c_str(), nullptr, MS_REMOUNT | MS_BIND | kLockableFlags, "")); // The bind mount itself was created in this namespace, so unlike the mount // it was cloned from, it can be unmounted. TEST_CHECK_SUCCESS(umount2(target_path.c_str(), MNT_DETACH)); }; EXPECT_THAT(InForkedUserMountNamespace([] {}, child), IsPosixErrorOkAndHolds(0)); } TEST(MountTest, RemountUnmounted) { 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(), kTmpfs, MS_REMOUNT, ""), SyscallFailsWithErrno(EINVAL)); } TEST(MountTest, DetachedMountBindFails) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); const TempPath path1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); Cleanup mount_cleanup = ASSERT_NO_ERRNO_AND_VALUE( Mount("", path1.path().c_str(), kTmpfs, 0, "", MNT_DETACH)); mount_cleanup.Release(); const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path1.path().c_str(), O_RDONLY)); std::string fd_path = absl::Substitute("/proc/self/fd/$0", fd.get()); ASSERT_THAT(umount2(path1.path().c_str(), MNT_DETACH), SyscallSucceeds()); const TempPath path2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); EXPECT_THAT(mount(fd_path.c_str(), path2.path().c_str(), "", MS_BIND, 0), SyscallFailsWithErrno(EINVAL)); } TEST(MountTest, MountProc) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); // Mount procfs with a NULL source to a temporary directory. const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); ASSERT_THAT(mount(NULL, dir.path().c_str(), "proc", 0, NULL), SyscallSucceeds()); auto cleanup = Cleanup([&dir] { EXPECT_THAT(umount2(dir.path().c_str(), 0), SyscallSucceeds()); }); // Verify that /proc/self/mountinfo describes the device as "none". const std::vector mountinfo = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); for (auto const& e : mountinfo) { if (e.mount_point == dir.path()) { EXPECT_EQ(e.fstype, "proc"); EXPECT_EQ(e.mount_source, "none"); } } } TEST(MountTest, OverlayfsSgidBitIsCopiedUp) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto base_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); // Overlayfs can not be used as upper layer for another overlayfs mount. If // running in overlayfs, create a tmpfs mount to use as the upper layer. bool in_overlayfs = ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(base_dir.path())); if (in_overlayfs) { ASSERT_THAT( mount("tmpfs", base_dir.path().c_str(), "tmpfs", 0, "mode=1777"), SyscallSucceeds()); } auto tmpfs_cleanup = Cleanup([&base_dir, &in_overlayfs] { if (in_overlayfs) { ASSERT_THAT(umount2(base_dir.path().c_str(), MNT_DETACH), SyscallSucceeds()); } }); // Create the files under test in `lower` and set the S_ISGID bit on them. auto lower = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(base_dir.path())); struct FileUT { absl::string_view name; void (*create)(const std::string& path); }; static constexpr std::array kFilesUnderTest = { FileUT{"setgid_dir", [](const std::string& path) { ASSERT_THAT(mkdir(path.c_str(), 0755), SyscallSucceeds()); }}, FileUT{"setgid_empty_file", [](const std::string& path) { ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_CREAT | O_RDWR, 0755)); }}, FileUT{"setgid_nonempty_file", [](const std::string& path) { ASSERT_NO_ERRNO(CreateWithContents(path, "junk", 0755)); }}, }; for (const auto& file_ut : kFilesUnderTest) { std::string lower_target = lower.path() + "/" + std::string(file_ut.name); file_ut.create(lower_target); struct stat st_lower; ASSERT_THAT(stat(lower_target.c_str(), &st_lower), SyscallSucceeds()); ASSERT_THAT(chmod(lower_target.c_str(), st_lower.st_mode | S_ISGID), SyscallSucceeds()); ASSERT_THAT(stat(lower_target.c_str(), &st_lower), SyscallSucceeds()); ASSERT_TRUE(st_lower.st_mode & S_ISGID); } // Mount the overlayfs. auto upper = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(base_dir.path())); auto work = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(base_dir.path())); auto merged = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(base_dir.path())); std::string opts = "lowerdir=" + lower.path() + ",upperdir=" + upper.path() + ",workdir=" + work.path(); ASSERT_THAT( mount("overlay", merged.path().c_str(), "overlay", 0, opts.c_str()), SyscallSucceeds()); auto overlayfs_cleanup = Cleanup([&merged] { umount2(merged.path().c_str(), MNT_DETACH); }); for (const auto& file_ut : kFilesUnderTest) { SCOPED_TRACE(file_ut.name); // The S_ISGID bit is visible before any copy-up took place. std::string merged_target = merged.path() + "/" + std::string(file_ut.name); struct stat st_merged; ASSERT_THAT(stat(merged_target.c_str(), &st_merged), SyscallSucceeds()); ASSERT_TRUE(st_merged.st_mode & S_ISGID); // Touch the target to force a copy-up to `upper`. ASSERT_THAT(utimensat(AT_FDCWD, merged_target.c_str(), nullptr, 0), SyscallSucceeds()); // The S_ISGID bit must also be copied up. std::string upper_target = upper.path() + "/" + std::string(file_ut.name); struct stat upper_st_after_touch; ASSERT_THAT(stat(upper_target.c_str(), &upper_st_after_touch), SyscallSucceeds()); EXPECT_TRUE(upper_st_after_touch.st_mode & S_ISGID); struct stat merged_st_after_touch; ASSERT_THAT(stat(merged_target.c_str(), &merged_st_after_touch), SyscallSucceeds()); EXPECT_TRUE(merged_st_after_touch.st_mode & S_ISGID); } } // Renaming a directory on an overlay inside a user namespace requires // user.overlay.* xattrs to mark the directory opaque. TEST(MountTest, OverlayfsDirectoryRenameInUserNamespace) { // Test fails in gVisor running on <6.0 due to fsgofer getting permission // errors on `mknod`. SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(GetHostKernelVersion()).major < 6); SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace())); auto base_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); bool in_overlayfs = ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(base_dir.path())); const std::function parent = [] {}; const std::function child = [&base_dir, &in_overlayfs] { // Overlayfs can not be used as upper layer for another overlayfs mount. If // running in overlayfs, create a tmpfs mount to use as the upper layer. if (in_overlayfs) { TEST_CHECK_SUCCESS(mount("tmpfs", base_dir.path().c_str(), "tmpfs", 0, "mode=1777,size=10m")); } auto tmpfs_cleanup = Cleanup([&base_dir, &in_overlayfs] { if (in_overlayfs) { TEST_CHECK_SUCCESS(umount2(base_dir.path().c_str(), 0)); } }); auto lower = JoinPath(base_dir.path(), "lower"); auto upper = JoinPath(base_dir.path(), "upper"); auto work = JoinPath(base_dir.path(), "work"); auto merged = JoinPath(base_dir.path(), "merged"); TEST_CHECK_SUCCESS(mkdir(lower.c_str(), 0755)); TEST_CHECK_SUCCESS(mkdir(upper.c_str(), 0755)); TEST_CHECK_SUCCESS(mkdir(work.c_str(), 0755)); TEST_CHECK_SUCCESS(mkdir(merged.c_str(), 0755)); // 1. Create the DESTINATION directory in lower. // (It must be empty so standard POSIX rename allows overwriting it). { auto lower_renamed = JoinPath(lower, "renamed"); TEST_CHECK_SUCCESS(mkdir(lower_renamed.c_str(), 0755)); } std::string opts = "lowerdir=" + lower + ",upperdir=" + upper + ",workdir=" + work + ",userxattr"; TEST_CHECK_SUCCESS( mount("overlay", merged.c_str(), "overlay", 0, opts.c_str())); auto overlayfs_cleanup = Cleanup([&merged] { TEST_CHECK_SUCCESS(umount2(merged.c_str(), 0)); }); // 2. Create the SOURCE directory directly in merged. // Because it doesn't exist in lower, it is a "pure upper" directory. auto mydir = JoinPath(merged, "mydir"); TEST_CHECK_SUCCESS(mkdir(mydir.c_str(), 0755)); int fd = open(JoinPath(mydir, "file.txt").c_str(), O_WRONLY | O_CREAT, 0644); TEST_CHECK(fd >= 0); TEST_CHECK_SUCCESS(close(fd)); // 3. Rename the pure upper directory over the existing lower directory. // - This avoids EXDEV on Linux because 'mydir' is pure upper. // - This forces the kernel to apply user.overlay.opaque="y" to 'renamed' // so that 'lower/renamed' is hidden. auto renamed = JoinPath(merged, "renamed"); TEST_CHECK_SUCCESS(rename(mydir.c_str(), renamed.c_str())); // 4. Verify the rename succeeded and contents are correct struct stat st; TEST_CHECK_SUCCESS(stat(renamed.c_str(), &st)); TEST_CHECK(S_ISDIR(st.st_mode)); // Original directory should be gone TEST_CHECK(stat(mydir.c_str(), &st) == -1 && errno == ENOENT); // File inside should have moved with the directory TEST_CHECK_SUCCESS(stat(JoinPath(renamed, "file.txt").c_str(), &st)); TEST_CHECK(S_ISREG(st.st_mode)); }; EXPECT_THAT(InForkedUserMountNamespace(parent, child), IsPosixErrorOkAndHolds(0)); } // Test that overlay can be mounted with a gofer upper layer. Runs some basic // overlayfs tests to confirm basic functionality. TEST(MountTest, OverlayfsOnGoferBehavior) { // Test fails in gVisor running on <6.0 due to fsgofer getting permission // errors on `mknod`. SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(GetHostKernelVersion()).major < 6); SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto base_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); // We only want to test overlayfs on gofer. SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsGoferfs(base_dir.path()))); // Create directories for overlayfs mount. auto lower = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(base_dir.path())); auto upper = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(base_dir.path())); auto work = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(base_dir.path())); auto merged = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(base_dir.path())); // Set up lower layer with some content. const std::string kTestFile = "testfile"; const std::string kTestFileContent = "lower_content"; std::string lower_file = JoinPath(lower.path(), kTestFile); ASSERT_NO_ERRNO(CreateWithContents(lower_file, kTestFileContent, 0644)); const std::string kTestDirName = "testdir"; std::string lower_dir = JoinPath(lower.path(), kTestDirName); ASSERT_THAT(mkdir(lower_dir.c_str(), 0755), SyscallSucceeds()); const std::string kCopyUpFile = "copyupfile"; std::string lower_copyup_file = JoinPath(lower.path(), kCopyUpFile); ASSERT_NO_ERRNO(CreateWithContents(lower_copyup_file, "initial", 0644)); bool xattr_supported = true; if (setxattr(lower_copyup_file.c_str(), "user.test", "value", 5, 0) < 0) { if (errno == EOPNOTSUPP) { xattr_supported = false; } else { ASSERT_NO_ERRNO(PosixError(errno, "setxattr failed")); } } // Mount the overlay. std::string opts = "lowerdir=" + lower.path() + ",upperdir=" + upper.path() + ",workdir=" + work.path() + ",userxattr"; ASSERT_THAT( mount("overlay", merged.path().c_str(), "overlay", 0, opts.c_str()), SyscallSucceeds()); auto cleanup = Cleanup([&merged] { umount2(merged.path().c_str(), 0); }); // Read the lower layer file through the overlay. std::string merged_file = JoinPath(merged.path(), kTestFile); std::string merged_content; ASSERT_NO_ERRNO(GetContents(merged_file, &merged_content)); EXPECT_EQ(merged_content, kTestFileContent); // Write a new file through the overlay and confirm it appears in the upper. const std::string kNewFile = "newfile"; const std::string kNewFileContent = "new"; std::string new_file = JoinPath(merged.path(), kNewFile); ASSERT_NO_ERRNO(CreateWithContents(new_file, kNewFileContent, 0644)); std::string upper_new_file = JoinPath(upper.path(), kNewFile); std::string upper_new_content; ASSERT_NO_ERRNO(GetContents(upper_new_file, &upper_new_content)); EXPECT_EQ(upper_new_content, kNewFileContent); // Delete the lower layer file through the overlay and confirm it creates a // whiteout in the upper. ASSERT_THAT(unlink(merged_file.c_str()), SyscallSucceeds()); std::string whiteout = JoinPath(upper.path(), kTestFile); struct stat st; ASSERT_THAT(stat(whiteout.c_str(), &st), SyscallSucceeds()); EXPECT_TRUE(S_ISCHR(st.st_mode)); EXPECT_EQ(major(st.st_rdev), 0); EXPECT_EQ(minor(st.st_rdev), 0); // Delete the lower layer directory through the overlay and recreate it. This // should mark it opaque in the upper layer. std::string merged_dir = JoinPath(merged.path(), kTestDirName); ASSERT_THAT(rmdir(merged_dir.c_str()), SyscallSucceeds()); ASSERT_THAT(mkdir(merged_dir.c_str(), 0755), SyscallSucceeds()); std::string upper_dir = JoinPath(upper.path(), kTestDirName); char opaque_buf[16] = {}; ASSERT_THAT(getxattr(upper_dir.c_str(), "user.overlay.opaque", opaque_buf, sizeof(opaque_buf)), SyscallSucceeds()); EXPECT_EQ(opaque_buf[0], 'y'); // Test copy up on regular file. std::string merged_copyup_file = JoinPath(merged.path(), kCopyUpFile); ASSERT_NO_ERRNO(SetContents(merged_copyup_file, "modified")); if (xattr_supported) { // Confirm that the xattr is copied up with the file. char xattr_buf[16] = {}; ASSERT_THAT(getxattr(merged_copyup_file.c_str(), "user.test", xattr_buf, sizeof(xattr_buf)), SyscallSucceeds()); EXPECT_STREQ(xattr_buf, "value"); } } TEST(MountTest, PollMountInfo) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); for (const char* path : {"/proc/self/mounts", "/proc/self/mountinfo"}) { FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_RDONLY)); int epfd_raw = epoll_create1(0); ASSERT_GE(epfd_raw, 0) << "epoll_create1 failed: " << strerror(errno); FileDescriptor epfd(epfd_raw); struct epoll_event ev = {}; ev.events = EPOLLPRI | EPOLLERR; ev.data.fd = fd.get(); ASSERT_THAT(epoll_ctl(epfd.get(), EPOLL_CTL_ADD, fd.get(), &ev), SyscallSucceeds()); // Drains one event and assert it has EPOLLPRI or EPOLLERR. auto expectEvent = [&](absl::string_view label) { struct epoll_event events[1] = {}; int nfds = epoll_wait(epfd.get(), events, 1, 0); ASSERT_GE(nfds, 0) << label << ": epoll_wait failed: " << strerror(errno); ASSERT_EQ(nfds, 1) << label << ": expected 1 event"; EXPECT_TRUE(events[0].events & (EPOLLPRI | EPOLLERR)) << label; }; // Asserts no events pending. auto expectNoEvent = [&](absl::string_view label) { struct epoll_event events[1] = {}; int nfds = epoll_wait(epfd.get(), events, 1, 0); ASSERT_GE(nfds, 0) << label << ": epoll_wait failed: " << strerror(errno); EXPECT_EQ(nfds, 0) << label; }; // Initially, there should be no events. expectNoEvent("initial"); // mount() triggers event. auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto mnt = ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), kTmpfs, 0, "", 0)); expectEvent("mount"); // Event is consumed by poll itself, no read needed. expectNoEvent("after mount consumed"); // remount triggers event. ASSERT_THAT( mount("", dir.path().c_str(), nullptr, MS_REMOUNT | MS_RDONLY, nullptr), SyscallSucceeds()); expectEvent("remount"); expectNoEvent("after remount consumed"); // Undo the read-only remount so umount works cleanly. ASSERT_THAT(mount("", dir.path().c_str(), nullptr, MS_REMOUNT, nullptr), SyscallSucceeds()); // Drain the remount-undo event. expectEvent("remount undo"); // Umount triggers event. mnt.Release(); ASSERT_THAT(umount2(dir.path().c_str(), 0), SyscallSucceeds()); expectEvent("umount"); expectNoEvent("after umount consumed"); // Move mount triggers event. // // MS_MOVE is not allowed under a MS_SHARED parent mount, so we create a // private parent mount containing both src and dst. auto const privateParent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto privateMount = ASSERT_NO_ERRNO_AND_VALUE( Mount("", privateParent.path(), kTmpfs, 0, "", 0)); expectEvent("move setup private parent mount"); ASSERT_THAT(mount("", privateParent.path().c_str(), "", MS_PRIVATE, ""), SyscallSucceeds()); auto const src = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(privateParent.path())); auto const dst = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(privateParent.path())); auto srcMnt = ASSERT_NO_ERRNO_AND_VALUE(Mount("", src.path(), kTmpfs, 0, "", 0)); expectEvent("move setup src mount"); auto dstMnt = ASSERT_NO_ERRNO_AND_VALUE( Mount(src.path(), dst.path(), "", MS_MOVE, "", 0)); srcMnt.Release(); expectEvent("move mount"); expectNoEvent("after move consumed"); } } TEST(MountTest, MountInfoAfterUnshare) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); for (const char* path : {"/proc/self/mounts", "/proc/self/mountinfo"}) { int pipefd[2]; ASSERT_THAT(pipe(pipefd), SyscallSucceeds()); FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_RDONLY)); int epfd_raw = epoll_create1(0); ASSERT_GE(epfd_raw, 0) << "epoll_create1 failed: " << strerror(errno); FileDescriptor epfd(epfd_raw); struct epoll_event ev = {}; ev.events = EPOLLPRI | EPOLLERR; ev.data.fd = fd.get(); ASSERT_THAT(epoll_ctl(epfd.get(), EPOLL_CTL_ADD, fd.get(), &ev), SyscallSucceeds()); // Initially, no events on fd. auto expectNoEvent = [](int epfd_raw) { struct epoll_event events[1] = {}; int nfds = epoll_wait(epfd_raw, events, 1, 0); return nfds == 0; }; ASSERT_TRUE(expectNoEvent(epfd.get())); pid_t child = fork(); if (child == 0) { close(pipefd[1]); auto childExpectEvent = [](int epfd_raw) { struct epoll_event events[1] = {}; int nfds = epoll_wait(epfd_raw, events, 1, 0); TEST_PCHECK(nfds == 1); TEST_CHECK(events[0].events & (EPOLLPRI | EPOLLERR)); }; // Unshare into a new mount namespace. TEST_PCHECK(unshare(CLONE_NEWNS) == 0); // Wait for the parent to perform a mount in the parent's mountns. char c; TEST_PCHECK(read(pipefd[0], &c, 1) == 1); close(pipefd[0]); // The inherited fd was opened before unshare(CLONE_NEWNS), so it should // see the mount event from the parent's mount namespace. childExpectEvent(epfd.get()); // Reading from the inherited fd should still give info about the old // mount namespace (viz., where the parent mounted dir.path()). lseek(fd.get(), 0, SEEK_SET); char buf[65536]; size_t total = 0; int n; while ((total < sizeof(buf) - 1) && (n = read(fd.get(), buf + total, sizeof(buf) - 1 - total)) > 0) { total += n; } TEST_PCHECK(n >= 0); TEST_CHECK(absl::StrContains(absl::string_view(buf, total), dir.path())); _exit(0); } close(pipefd[0]); ASSERT_THAT(child, SyscallSucceeds()); // Perform a mount in the parent's mount namespace. auto mnt = ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), kTmpfs, 0, "", 0)); // Notify the child that the mount is complete. char c = 'a'; ASSERT_THAT(write(pipefd[1], &c, 1), SyscallSucceedsWithValue(1)); close(pipefd[1]); int status; ASSERT_THAT(waitpid(child, &status, 0), SyscallSucceedsWithValue(child)); ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); } } } // namespace } // namespace testing } // namespace gvisor