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