diff options
Diffstat (limited to 'test/syscalls')
45 files changed, 1691 insertions, 503 deletions
diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index 6ee2b73c1..e43f30ba3 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -557,6 +557,10 @@ syscall_test( ) syscall_test( + test = "//test/syscalls/linux:setgid_test", +) + +syscall_test( add_overlay = True, test = "//test/syscalls/linux:splice_test", ) diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 5bf6e67be..80e2837f8 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -482,7 +482,9 @@ cc_binary( "//test/util:fs_util", "@com_google_absl//absl/strings", gtest, + "//test/util:logging", "//test/util:mount_util", + "//test/util:multiprocess_util", "//test/util:temp_path", "//test/util:test_main", "//test/util:test_util", @@ -582,6 +584,7 @@ cc_binary( "//test/util:eventfd_util", "//test/util:file_descriptor", gtest, + "//test/util:fs_util", "//test/util:posix_error", "//test/util:temp_path", "//test/util:test_main", @@ -671,6 +674,7 @@ cc_binary( gtest, "//test/util:logging", "//test/util:memory_util", + "//test/util:multiprocess_util", "//test/util:signal_util", "//test/util:test_main", "//test/util:test_util", @@ -980,6 +984,7 @@ cc_binary( "//test/util:epoll_util", "//test/util:file_descriptor", "//test/util:fs_util", + "//test/util:multiprocess_util", "//test/util:posix_error", "//test/util:temp_path", "//test/util:test_main", @@ -1378,6 +1383,7 @@ cc_binary( "//test/util:file_descriptor", "//test/util:fs_util", gtest, + "//test/util:posix_error", "//test/util:temp_path", "//test/util:temp_umask", "//test/util:test_main", @@ -2141,6 +2147,24 @@ cc_binary( ) cc_binary( + name = "setgid_test", + testonly = 1, + srcs = ["setgid.cc"], + linkstatic = 1, + deps = [ + "//test/util:capability_util", + "//test/util:cleanup", + "//test/util:fs_util", + "//test/util:posix_error", + "//test/util:temp_path", + "//test/util:test_main", + "//test/util:test_util", + "@com_google_absl//absl/strings", + gtest, + ], +) + +cc_binary( name = "splice_test", testonly = 1, srcs = ["splice.cc"], @@ -3825,6 +3849,8 @@ cc_binary( "@com_google_absl//absl/flags:flag", "@com_google_absl//absl/strings", gtest, + "//test/util:cleanup", + "//test/util:multiprocess_util", "//test/util:posix_error", "//test/util:test_main", "//test/util:test_util", @@ -4080,6 +4106,7 @@ cc_binary( "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", gtest, + "//test/util:cleanup", "//test/util:test_main", "//test/util:test_util", ], diff --git a/test/syscalls/linux/chmod.cc b/test/syscalls/linux/chmod.cc index a06b5cfd6..8233df0f8 100644 --- a/test/syscalls/linux/chmod.cc +++ b/test/syscalls/linux/chmod.cc @@ -98,6 +98,42 @@ TEST(ChmodTest, FchmodatBadF) { ASSERT_THAT(fchmodat(-1, "foo", 0444, 0), SyscallFailsWithErrno(EBADF)); } +TEST(ChmodTest, FchmodFileWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + + ASSERT_THAT(fchmod(fd.get(), 0444), SyscallFailsWithErrno(EBADF)); +} + +TEST(ChmodTest, FchmodDirWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + const auto fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY | O_PATH)); + + ASSERT_THAT(fchmod(fd.get(), 0444), SyscallFailsWithErrno(EBADF)); +} + +TEST(ChmodTest, FchmodatWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + // Drop capabilities that allow us to override file permissions. + ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); + + auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + + const auto parent_fd = ASSERT_NO_ERRNO_AND_VALUE( + Open(GetAbsoluteTestTmpdir().c_str(), O_PATH | O_DIRECTORY)); + + ASSERT_THAT( + fchmodat(parent_fd.get(), std::string(Basename(temp_file.path())).c_str(), + 0444, 0), + SyscallSucceeds()); + + EXPECT_THAT(open(temp_file.path().c_str(), O_RDWR), + SyscallFailsWithErrno(EACCES)); +} + TEST(ChmodTest, FchmodatNotDir) { ASSERT_THAT(fchmodat(-1, "", 0444, 0), SyscallFailsWithErrno(ENOENT)); } diff --git a/test/syscalls/linux/chown.cc b/test/syscalls/linux/chown.cc index 5530ad18f..ff0d39343 100644 --- a/test/syscalls/linux/chown.cc +++ b/test/syscalls/linux/chown.cc @@ -48,6 +48,36 @@ TEST(ChownTest, FchownatBadF) { ASSERT_THAT(fchownat(-1, "fff", 0, 0, 0), SyscallFailsWithErrno(EBADF)); } +TEST(ChownTest, FchownFileWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + + ASSERT_THAT(fchown(fd.get(), geteuid(), getegid()), + SyscallFailsWithErrno(EBADF)); +} + +TEST(ChownTest, FchownDirWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + const auto fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY | O_PATH)); + + ASSERT_THAT(fchown(fd.get(), geteuid(), getegid()), + SyscallFailsWithErrno(EBADF)); +} + +TEST(ChownTest, FchownatWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path())); + const auto dirfd = + ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY | O_PATH)); + ASSERT_THAT( + fchownat(dirfd.get(), file.path().c_str(), geteuid(), getegid(), 0), + SyscallSucceeds()); +} + TEST(ChownTest, FchownatEmptyPath) { const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); const auto fd = @@ -209,6 +239,14 @@ INSTANTIATE_TEST_SUITE_P( owner, group, 0); MaybeSave(); return errorFromReturn("fchownat-dirfd", rc); + }, + [](const std::string& path, uid_t owner, gid_t group) -> PosixError { + ASSIGN_OR_RETURN_ERRNO(auto dirfd, Open(std::string(Dirname(path)), + O_DIRECTORY | O_PATH)); + int rc = fchownat(dirfd.get(), std::string(Basename(path)).c_str(), + owner, group, 0); + MaybeSave(); + return errorFromReturn("fchownat-opathdirfd", rc); })); } // namespace diff --git a/test/syscalls/linux/chroot.cc b/test/syscalls/linux/chroot.cc index 85ec013d5..fab79d300 100644 --- a/test/syscalls/linux/chroot.cc +++ b/test/syscalls/linux/chroot.cc @@ -32,7 +32,9 @@ #include "test/util/cleanup.h" #include "test/util/file_descriptor.h" #include "test/util/fs_util.h" +#include "test/util/logging.h" #include "test/util/mount_util.h" +#include "test/util/multiprocess_util.h" #include "test/util/temp_path.h" #include "test/util/test_util.h" @@ -47,17 +49,20 @@ namespace { TEST(ChrootTest, Success) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); - auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - EXPECT_THAT(chroot(temp_dir.path().c_str()), SyscallSucceeds()); + const auto rest = [] { + auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + TEST_CHECK_SUCCESS(chroot(temp_dir.path().c_str())); + }; + EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); } TEST(ChrootTest, PermissionDenied) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); - // CAP_DAC_READ_SEARCH and CAP_DAC_OVERRIDE may override Execute permission on - // directories. - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); - ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); + // CAP_DAC_READ_SEARCH and CAP_DAC_OVERRIDE may override Execute permission + // on directories. + AutoCapability cap_search(CAP_DAC_READ_SEARCH, false); + AutoCapability cap_override(CAP_DAC_OVERRIDE, false); auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE( TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0666 /* mode */)); @@ -78,8 +83,10 @@ TEST(ChrootTest, NotExist) { } TEST(ChrootTest, WithoutCapability) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETPCAP))); + // Unset CAP_SYS_CHROOT. - ASSERT_NO_ERRNO(SetCapability(CAP_SYS_CHROOT, false)); + AutoCapability cap(CAP_SYS_CHROOT, false); auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); EXPECT_THAT(chroot(temp_dir.path().c_str()), SyscallFailsWithErrno(EPERM)); @@ -97,51 +104,53 @@ TEST(ChrootTest, CreatesNewRoot) { auto file_in_new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(new_root.path())); - // chroot into new_root. - ASSERT_THAT(chroot(new_root.path().c_str()), SyscallSucceeds()); - - // getcwd should return "(unreachable)" followed by the initial_cwd. - char cwd[1024]; - ASSERT_THAT(syscall(__NR_getcwd, cwd, sizeof(cwd)), SyscallSucceeds()); - std::string expected_cwd = "(unreachable)"; - expected_cwd += initial_cwd; - EXPECT_STREQ(cwd, expected_cwd.c_str()); - - // Should not be able to stat file by its full path. - struct stat statbuf; - EXPECT_THAT(stat(file_in_new_root.path().c_str(), &statbuf), - SyscallFailsWithErrno(ENOENT)); - - // Should be able to stat file at new rooted path. - auto basename = std::string(Basename(file_in_new_root.path())); - auto rootedFile = "/" + basename; - ASSERT_THAT(stat(rootedFile.c_str(), &statbuf), SyscallSucceeds()); - - // Should be able to stat cwd at '.' even though it's outside root. - ASSERT_THAT(stat(".", &statbuf), SyscallSucceeds()); - - // chdir into new root. - ASSERT_THAT(chdir("/"), SyscallSucceeds()); - - // getcwd should return "/". - EXPECT_THAT(syscall(__NR_getcwd, cwd, sizeof(cwd)), SyscallSucceeds()); - EXPECT_STREQ(cwd, "/"); - - // Statting '.', '..', '/', and '/..' all return the same dev and inode. - struct stat statbuf_dot; - ASSERT_THAT(stat(".", &statbuf_dot), SyscallSucceeds()); - struct stat statbuf_dotdot; - ASSERT_THAT(stat("..", &statbuf_dotdot), SyscallSucceeds()); - EXPECT_EQ(statbuf_dot.st_dev, statbuf_dotdot.st_dev); - EXPECT_EQ(statbuf_dot.st_ino, statbuf_dotdot.st_ino); - struct stat statbuf_slash; - ASSERT_THAT(stat("/", &statbuf_slash), SyscallSucceeds()); - EXPECT_EQ(statbuf_dot.st_dev, statbuf_slash.st_dev); - EXPECT_EQ(statbuf_dot.st_ino, statbuf_slash.st_ino); - struct stat statbuf_slashdotdot; - ASSERT_THAT(stat("/..", &statbuf_slashdotdot), SyscallSucceeds()); - EXPECT_EQ(statbuf_dot.st_dev, statbuf_slashdotdot.st_dev); - EXPECT_EQ(statbuf_dot.st_ino, statbuf_slashdotdot.st_ino); + const auto rest = [&] { + // chroot into new_root. + TEST_CHECK_SUCCESS(chroot(new_root.path().c_str())); + + // getcwd should return "(unreachable)" followed by the initial_cwd. + char cwd[1024]; + TEST_CHECK_SUCCESS(syscall(__NR_getcwd, cwd, sizeof(cwd))); + std::string expected_cwd = "(unreachable)"; + expected_cwd += initial_cwd; + TEST_CHECK(strcmp(cwd, expected_cwd.c_str()) == 0); + + // Should not be able to stat file by its full path. + struct stat statbuf; + TEST_CHECK_ERRNO(stat(file_in_new_root.path().c_str(), &statbuf), ENOENT); + + // Should be able to stat file at new rooted path. + auto basename = std::string(Basename(file_in_new_root.path())); + auto rootedFile = "/" + basename; + TEST_CHECK_SUCCESS(stat(rootedFile.c_str(), &statbuf)); + + // Should be able to stat cwd at '.' even though it's outside root. + TEST_CHECK_SUCCESS(stat(".", &statbuf)); + + // chdir into new root. + TEST_CHECK_SUCCESS(chdir("/")); + + // getcwd should return "/". + TEST_CHECK_SUCCESS(syscall(__NR_getcwd, cwd, sizeof(cwd))); + TEST_CHECK_SUCCESS(strcmp(cwd, "/") == 0); + + // Statting '.', '..', '/', and '/..' all return the same dev and inode. + struct stat statbuf_dot; + TEST_CHECK_SUCCESS(stat(".", &statbuf_dot)); + struct stat statbuf_dotdot; + TEST_CHECK_SUCCESS(stat("..", &statbuf_dotdot)); + TEST_CHECK(statbuf_dot.st_dev == statbuf_dotdot.st_dev); + TEST_CHECK(statbuf_dot.st_ino == statbuf_dotdot.st_ino); + struct stat statbuf_slash; + TEST_CHECK_SUCCESS(stat("/", &statbuf_slash)); + TEST_CHECK(statbuf_dot.st_dev == statbuf_slash.st_dev); + TEST_CHECK(statbuf_dot.st_ino == statbuf_slash.st_ino); + struct stat statbuf_slashdotdot; + TEST_CHECK_SUCCESS(stat("/..", &statbuf_slashdotdot)); + TEST_CHECK(statbuf_dot.st_dev == statbuf_slashdotdot.st_dev); + TEST_CHECK(statbuf_dot.st_ino == statbuf_slashdotdot.st_ino); + }; + EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); } TEST(ChrootTest, DotDotFromOpenFD) { @@ -152,18 +161,20 @@ TEST(ChrootTest, DotDotFromOpenFD) { Open(dir_outside_root.path(), O_RDONLY | O_DIRECTORY)); auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - // chroot into new_root. - ASSERT_THAT(chroot(new_root.path().c_str()), SyscallSucceeds()); + const auto rest = [&] { + // chroot into new_root. + TEST_CHECK_SUCCESS(chroot(new_root.path().c_str())); - // openat on fd with path .. will succeed. - int other_fd; - ASSERT_THAT(other_fd = openat(fd.get(), "..", O_RDONLY), SyscallSucceeds()); - EXPECT_THAT(close(other_fd), SyscallSucceeds()); + // openat on fd with path .. will succeed. + int other_fd; + TEST_CHECK_SUCCESS(other_fd = openat(fd.get(), "..", O_RDONLY)); + TEST_CHECK_SUCCESS(close(other_fd)); - // getdents on fd should not error. - char buf[1024]; - ASSERT_THAT(syscall(SYS_getdents64, fd.get(), buf, sizeof(buf)), - SyscallSucceeds()); + // getdents on fd should not error. + char buf[1024]; + TEST_CHECK_SUCCESS(syscall(SYS_getdents64, fd.get(), buf, sizeof(buf))); + }; + EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); } // Test that link resolution in a chroot can escape the root by following an @@ -179,24 +190,27 @@ TEST(ChrootTest, ProcFdLinkResolutionInChroot) { const FileDescriptor proc_fd = ASSERT_NO_ERRNO_AND_VALUE( Open("/proc", O_DIRECTORY | O_RDONLY | O_CLOEXEC)); - auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT(chroot(temp_dir.path().c_str()), SyscallSucceeds()); - - // Opening relative to an already open fd to a node outside the chroot works. - const FileDescriptor proc_self_fd = ASSERT_NO_ERRNO_AND_VALUE( - OpenAt(proc_fd.get(), "self/fd", O_DIRECTORY | O_RDONLY | O_CLOEXEC)); - - // Proc fd symlinks can escape the chroot if the fd the symlink refers to - // refers to an object outside the chroot. - struct stat s = {}; - EXPECT_THAT( - fstatat(proc_self_fd.get(), absl::StrCat(fd.get()).c_str(), &s, 0), - SyscallSucceeds()); - - // Try to stat the stdin fd. Internally, this is handled differently from a - // proc fd entry pointing to a file, since stdin is backed by a host fd, and - // isn't a walkable path on the filesystem inside the sandbox. - EXPECT_THAT(fstatat(proc_self_fd.get(), "0", &s, 0), SyscallSucceeds()); + const auto rest = [&] { + auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + TEST_CHECK_SUCCESS(chroot(temp_dir.path().c_str())); + + // Opening relative to an already open fd to a node outside the chroot + // works. + const FileDescriptor proc_self_fd = TEST_CHECK_NO_ERRNO_AND_VALUE( + OpenAt(proc_fd.get(), "self/fd", O_DIRECTORY | O_RDONLY | O_CLOEXEC)); + + // Proc fd symlinks can escape the chroot if the fd the symlink refers to + // refers to an object outside the chroot. + struct stat s = {}; + TEST_CHECK_SUCCESS( + fstatat(proc_self_fd.get(), absl::StrCat(fd.get()).c_str(), &s, 0)); + + // Try to stat the stdin fd. Internally, this is handled differently from a + // proc fd entry pointing to a file, since stdin is backed by a host fd, and + // isn't a walkable path on the filesystem inside the sandbox. + TEST_CHECK_SUCCESS(fstatat(proc_self_fd.get(), "0", &s, 0)); + }; + EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); } // This test will verify that when you hold a fd to proc before entering @@ -209,28 +223,30 @@ TEST(ChrootTest, ProcMemSelfFdsNoEscapeProcOpen) { const FileDescriptor proc = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc", O_RDONLY)); - // Create and enter a chroot directory. - const auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT(chroot(temp_dir.path().c_str()), SyscallSucceeds()); - - // Open a file inside the chroot at /foo. - const FileDescriptor foo = - ASSERT_NO_ERRNO_AND_VALUE(Open("/foo", O_CREAT | O_RDONLY, 0644)); - - // Examine /proc/self/fd/{foo_fd} to see if it exposes the fact that we're - // inside a chroot, the path should be /foo and NOT {chroot_dir}/foo. - const std::string fd_path = absl::StrCat("self/fd/", foo.get()); - char buf[1024] = {}; - size_t bytes_read = 0; - ASSERT_THAT(bytes_read = - readlinkat(proc.get(), fd_path.c_str(), buf, sizeof(buf) - 1), - SyscallSucceeds()); - - // The link should resolve to something. - ASSERT_GT(bytes_read, 0); - - // Assert that the link doesn't contain the chroot path and is only /foo. - EXPECT_STREQ(buf, "/foo"); + const auto rest = [&] { + // Create and enter a chroot directory. + const auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + TEST_CHECK_SUCCESS(chroot(temp_dir.path().c_str())); + + // Open a file inside the chroot at /foo. + const FileDescriptor foo = + TEST_CHECK_NO_ERRNO_AND_VALUE(Open("/foo", O_CREAT | O_RDONLY, 0644)); + + // Examine /proc/self/fd/{foo_fd} to see if it exposes the fact that we're + // inside a chroot, the path should be /foo and NOT {chroot_dir}/foo. + const std::string fd_path = absl::StrCat("self/fd/", foo.get()); + char buf[1024] = {}; + size_t bytes_read = 0; + TEST_CHECK_SUCCESS(bytes_read = readlinkat(proc.get(), fd_path.c_str(), buf, + sizeof(buf) - 1)); + + // The link should resolve to something. + TEST_CHECK(bytes_read > 0); + + // Assert that the link doesn't contain the chroot path and is only /foo. + TEST_CHECK(strcmp(buf, "/foo") == 0); + }; + EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); } // This test will verify that a file inside a chroot when mmapped will not @@ -242,39 +258,41 @@ TEST(ChrootTest, ProcMemSelfMapsNoEscapeProcOpen) { const FileDescriptor proc = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc", O_RDONLY)); - // Create and enter a chroot directory. - const auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - ASSERT_THAT(chroot(temp_dir.path().c_str()), SyscallSucceeds()); - - // Open a file inside the chroot at /foo. - const FileDescriptor foo = - ASSERT_NO_ERRNO_AND_VALUE(Open("/foo", O_CREAT | O_RDONLY, 0644)); - - // Mmap the newly created file. - void* foo_map = mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, - foo.get(), 0); - ASSERT_THAT(reinterpret_cast<int64_t>(foo_map), SyscallSucceeds()); - - // Always unmap. - auto cleanup_map = Cleanup( - [&] { EXPECT_THAT(munmap(foo_map, kPageSize), SyscallSucceeds()); }); - - // Examine /proc/self/maps to be sure that /foo doesn't appear to be - // mapped with the full chroot path. - const FileDescriptor maps = - ASSERT_NO_ERRNO_AND_VALUE(OpenAt(proc.get(), "self/maps", O_RDONLY)); - - size_t bytes_read = 0; - char buf[8 * 1024] = {}; - ASSERT_THAT(bytes_read = ReadFd(maps.get(), buf, sizeof(buf)), - SyscallSucceeds()); - - // The maps file should have something. - ASSERT_GT(bytes_read, 0); - - // Finally we want to make sure the maps don't contain the chroot path - ASSERT_EQ(std::string(buf, bytes_read).find(temp_dir.path()), - std::string::npos); + const auto rest = [&] { + // Create and enter a chroot directory. + const auto temp_dir = TEST_CHECK_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + TEST_CHECK_SUCCESS(chroot(temp_dir.path().c_str())); + + // Open a file inside the chroot at /foo. + const FileDescriptor foo = + TEST_CHECK_NO_ERRNO_AND_VALUE(Open("/foo", O_CREAT | O_RDONLY, 0644)); + + // Mmap the newly created file. + void* foo_map = mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, + MAP_PRIVATE, foo.get(), 0); + TEST_CHECK_SUCCESS(reinterpret_cast<int64_t>(foo_map)); + + // Always unmap. + auto cleanup_map = + Cleanup([&] { TEST_CHECK_SUCCESS(munmap(foo_map, kPageSize)); }); + + // Examine /proc/self/maps to be sure that /foo doesn't appear to be + // mapped with the full chroot path. + const FileDescriptor maps = TEST_CHECK_NO_ERRNO_AND_VALUE( + OpenAt(proc.get(), "self/maps", O_RDONLY)); + + size_t bytes_read = 0; + char buf[8 * 1024] = {}; + TEST_CHECK_SUCCESS(bytes_read = ReadFd(maps.get(), buf, sizeof(buf))); + + // The maps file should have something. + TEST_CHECK(bytes_read > 0); + + // Finally we want to make sure the maps don't contain the chroot path + TEST_CHECK(std::string(buf, bytes_read).find(temp_dir.path()) == + std::string::npos); + }; + EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); } // Test that mounts outside the chroot will not appear in /proc/self/mounts or @@ -283,81 +301,76 @@ TEST(ChrootTest, ProcMountsMountinfoNoEscape) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); - // We are going to create some mounts and then chroot. In order to be able to - // unmount the mounts after the test run, we must chdir to the root and use - // relative paths for all mounts. That way, as long as we never chdir into - // the new root, we can access the mounts via relative paths and unmount them. - ASSERT_THAT(chdir("/"), SyscallSucceeds()); - - // Create nested tmpfs mounts. Note the use of relative paths in Mount calls. + // Create nested tmpfs mounts. auto const outer_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - auto const outer_mount = ASSERT_NO_ERRNO_AND_VALUE(Mount( - "none", JoinPath(".", outer_dir.path()), "tmpfs", 0, "mode=0700", 0)); + auto const outer_mount = ASSERT_NO_ERRNO_AND_VALUE( + Mount("none", outer_dir.path(), "tmpfs", 0, "mode=0700", 0)); auto const inner_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(outer_dir.path())); - auto const inner_mount = ASSERT_NO_ERRNO_AND_VALUE(Mount( - "none", JoinPath(".", inner_dir.path()), "tmpfs", 0, "mode=0700", 0)); - - // Filenames that will be checked for mounts, all relative to /proc dir. - std::string paths[3] = {"mounts", "self/mounts", "self/mountinfo"}; - - for (const std::string& path : paths) { - // We should have both inner and outer mounts. - const std::string contents = - ASSERT_NO_ERRNO_AND_VALUE(GetContents(JoinPath("/proc", path))); - EXPECT_THAT(contents, AllOf(HasSubstr(outer_dir.path()), - HasSubstr(inner_dir.path()))); - // We better have at least two mounts: the mounts we created plus the root. - std::vector<absl::string_view> submounts = - absl::StrSplit(contents, '\n', absl::SkipWhitespace()); - EXPECT_GT(submounts.size(), 2); - } - - // Get a FD to /proc before we enter the chroot. - const FileDescriptor proc = - ASSERT_NO_ERRNO_AND_VALUE(Open("/proc", O_RDONLY)); - - // Chroot to outer mount. - ASSERT_THAT(chroot(outer_dir.path().c_str()), SyscallSucceeds()); - - for (const std::string& path : paths) { - const FileDescriptor proc_file = - ASSERT_NO_ERRNO_AND_VALUE(OpenAt(proc.get(), path, O_RDONLY)); - - // Only two mounts visible from this chroot: the inner and outer. Both - // paths should be relative to the new chroot. - const std::string contents = - ASSERT_NO_ERRNO_AND_VALUE(GetContentsFD(proc_file.get())); - EXPECT_THAT(contents, - AllOf(HasSubstr(absl::StrCat(Basename(inner_dir.path()))), - Not(HasSubstr(outer_dir.path())), - Not(HasSubstr(inner_dir.path())))); - std::vector<absl::string_view> submounts = - absl::StrSplit(contents, '\n', absl::SkipWhitespace()); - EXPECT_EQ(submounts.size(), 2); - } - - // Chroot to inner mount. We must use an absolute path accessible to our - // chroot. - const std::string inner_dir_basename = - absl::StrCat("/", Basename(inner_dir.path())); - ASSERT_THAT(chroot(inner_dir_basename.c_str()), SyscallSucceeds()); - - for (const std::string& path : paths) { - const FileDescriptor proc_file = - ASSERT_NO_ERRNO_AND_VALUE(OpenAt(proc.get(), path, O_RDONLY)); - const std::string contents = - ASSERT_NO_ERRNO_AND_VALUE(GetContentsFD(proc_file.get())); - - // Only the inner mount visible from this chroot. - std::vector<absl::string_view> submounts = - absl::StrSplit(contents, '\n', absl::SkipWhitespace()); - EXPECT_EQ(submounts.size(), 1); - } - - // Chroot back to ".". - ASSERT_THAT(chroot("."), SyscallSucceeds()); + auto const inner_mount = ASSERT_NO_ERRNO_AND_VALUE( + Mount("none", inner_dir.path(), "tmpfs", 0, "mode=0700", 0)); + + const auto rest = [&outer_dir, &inner_dir] { + // Filenames that will be checked for mounts, all relative to /proc dir. + std::string paths[3] = {"mounts", "self/mounts", "self/mountinfo"}; + + for (const std::string& path : paths) { + // We should have both inner and outer mounts. + const std::string contents = + TEST_CHECK_NO_ERRNO_AND_VALUE(GetContents(JoinPath("/proc", path))); + EXPECT_THAT(contents, AllOf(HasSubstr(outer_dir.path()), + HasSubstr(inner_dir.path()))); + // We better have at least two mounts: the mounts we created plus the + // root. + std::vector<absl::string_view> submounts = + absl::StrSplit(contents, '\n', absl::SkipWhitespace()); + TEST_CHECK(submounts.size() > 2); + } + + // Get a FD to /proc before we enter the chroot. + const FileDescriptor proc = + TEST_CHECK_NO_ERRNO_AND_VALUE(Open("/proc", O_RDONLY)); + + // Chroot to outer mount. + TEST_CHECK_SUCCESS(chroot(outer_dir.path().c_str())); + + for (const std::string& path : paths) { + const FileDescriptor proc_file = + TEST_CHECK_NO_ERRNO_AND_VALUE(OpenAt(proc.get(), path, O_RDONLY)); + + // Only two mounts visible from this chroot: the inner and outer. Both + // paths should be relative to the new chroot. + const std::string contents = + TEST_CHECK_NO_ERRNO_AND_VALUE(GetContentsFD(proc_file.get())); + EXPECT_THAT(contents, + AllOf(HasSubstr(absl::StrCat(Basename(inner_dir.path()))), + Not(HasSubstr(outer_dir.path())), + Not(HasSubstr(inner_dir.path())))); + std::vector<absl::string_view> submounts = + absl::StrSplit(contents, '\n', absl::SkipWhitespace()); + TEST_CHECK(submounts.size() == 2); + } + + // Chroot to inner mount. We must use an absolute path accessible to our + // chroot. + const std::string inner_dir_basename = + absl::StrCat("/", Basename(inner_dir.path())); + TEST_CHECK_SUCCESS(chroot(inner_dir_basename.c_str())); + + for (const std::string& path : paths) { + const FileDescriptor proc_file = + TEST_CHECK_NO_ERRNO_AND_VALUE(OpenAt(proc.get(), path, O_RDONLY)); + const std::string contents = + TEST_CHECK_NO_ERRNO_AND_VALUE(GetContentsFD(proc_file.get())); + + // Only the inner mount visible from this chroot. + std::vector<absl::string_view> submounts = + absl::StrSplit(contents, '\n', absl::SkipWhitespace()); + TEST_CHECK(submounts.size() == 1); + } + }; + EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); } } // namespace diff --git a/test/syscalls/linux/dup.cc b/test/syscalls/linux/dup.cc index 4f773bc75..ba4e13fb9 100644 --- a/test/syscalls/linux/dup.cc +++ b/test/syscalls/linux/dup.cc @@ -18,6 +18,7 @@ #include "gtest/gtest.h" #include "test/util/eventfd_util.h" #include "test/util/file_descriptor.h" +#include "test/util/fs_util.h" #include "test/util/posix_error.h" #include "test/util/temp_path.h" #include "test/util/test_util.h" @@ -44,14 +45,6 @@ PosixErrorOr<FileDescriptor> Dup3(const FileDescriptor& fd, int target_fd, return FileDescriptor(new_fd); } -void CheckSameFile(const FileDescriptor& fd1, const FileDescriptor& fd2) { - struct stat stat_result1, stat_result2; - ASSERT_THAT(fstat(fd1.get(), &stat_result1), SyscallSucceeds()); - ASSERT_THAT(fstat(fd2.get(), &stat_result2), SyscallSucceeds()); - EXPECT_EQ(stat_result1.st_dev, stat_result2.st_dev); - EXPECT_EQ(stat_result1.st_ino, stat_result2.st_ino); -} - TEST(DupTest, Dup) { auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDONLY)); @@ -59,7 +52,7 @@ TEST(DupTest, Dup) { // Dup the descriptor and make sure it's the same file. FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup()); ASSERT_NE(fd.get(), nfd.get()); - CheckSameFile(fd, nfd); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); } TEST(DupTest, DupClearsCloExec) { @@ -70,10 +63,24 @@ TEST(DupTest, DupClearsCloExec) { // Duplicate the descriptor. Ensure that it doesn't have FD_CLOEXEC set. FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup()); ASSERT_NE(fd.get(), nfd.get()); - CheckSameFile(fd, nfd); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); EXPECT_THAT(fcntl(nfd.get(), F_GETFD), SyscallSucceedsWithValue(0)); } +TEST(DupTest, DupWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_PATH)); + int flags; + ASSERT_THAT(flags = fcntl(fd.get(), F_GETFL), SyscallSucceeds()); + + // Dup the descriptor and make sure it's the same file. + FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup()); + ASSERT_NE(fd.get(), nfd.get()); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); + EXPECT_THAT(fcntl(nfd.get(), F_GETFL), SyscallSucceedsWithValue(flags)); +} + TEST(DupTest, Dup2) { auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDONLY)); @@ -82,13 +89,13 @@ TEST(DupTest, Dup2) { FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup()); ASSERT_NE(fd.get(), nfd.get()); - CheckSameFile(fd, nfd); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); // Dup over the file above. int target_fd = nfd.release(); FileDescriptor nfd2 = ASSERT_NO_ERRNO_AND_VALUE(Dup2(fd, target_fd)); EXPECT_EQ(target_fd, nfd2.get()); - CheckSameFile(fd, nfd2); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd2)); } TEST(DupTest, Dup2SameFD) { @@ -99,6 +106,28 @@ TEST(DupTest, Dup2SameFD) { ASSERT_THAT(dup2(fd.get(), fd.get()), SyscallSucceedsWithValue(fd.get())); } +TEST(DupTest, Dup2WithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_PATH)); + int flags; + ASSERT_THAT(flags = fcntl(fd.get(), F_GETFL), SyscallSucceeds()); + + // Regular dup once. + FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup()); + + ASSERT_NE(fd.get(), nfd.get()); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); + EXPECT_THAT(fcntl(nfd.get(), F_GETFL), SyscallSucceedsWithValue(flags)); + + // Dup over the file above. + int target_fd = nfd.release(); + FileDescriptor nfd2 = ASSERT_NO_ERRNO_AND_VALUE(Dup2(fd, target_fd)); + EXPECT_EQ(target_fd, nfd2.get()); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd2)); + EXPECT_THAT(fcntl(nfd2.get(), F_GETFL), SyscallSucceedsWithValue(flags)); +} + TEST(DupTest, Dup3) { auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDONLY)); @@ -106,16 +135,16 @@ TEST(DupTest, Dup3) { // Regular dup once. FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup()); ASSERT_NE(fd.get(), nfd.get()); - CheckSameFile(fd, nfd); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); // Dup over the file above, check that it has no CLOEXEC. nfd = ASSERT_NO_ERRNO_AND_VALUE(Dup3(fd, nfd.release(), 0)); - CheckSameFile(fd, nfd); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); EXPECT_THAT(fcntl(nfd.get(), F_GETFD), SyscallSucceedsWithValue(0)); // Dup over the file again, check that it does not CLOEXEC. nfd = ASSERT_NO_ERRNO_AND_VALUE(Dup3(fd, nfd.release(), O_CLOEXEC)); - CheckSameFile(fd, nfd); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); EXPECT_THAT(fcntl(nfd.get(), F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC)); } @@ -127,6 +156,32 @@ TEST(DupTest, Dup3FailsSameFD) { ASSERT_THAT(dup3(fd.get(), fd.get(), 0), SyscallFailsWithErrno(EINVAL)); } +TEST(DupTest, Dup3WithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_PATH)); + EXPECT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(0)); + int flags; + ASSERT_THAT(flags = fcntl(fd.get(), F_GETFL), SyscallSucceeds()); + + // Regular dup once. + FileDescriptor nfd = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup()); + ASSERT_NE(fd.get(), nfd.get()); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); + + // Dup over the file above, check that it has no CLOEXEC. + nfd = ASSERT_NO_ERRNO_AND_VALUE(Dup3(fd, nfd.release(), 0)); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); + EXPECT_THAT(fcntl(nfd.get(), F_GETFD), SyscallSucceedsWithValue(0)); + EXPECT_THAT(fcntl(nfd.get(), F_GETFL), SyscallSucceedsWithValue(flags)); + + // Dup over the file again, check that it does not CLOEXEC. + nfd = ASSERT_NO_ERRNO_AND_VALUE(Dup3(fd, nfd.release(), O_CLOEXEC)); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); + EXPECT_THAT(fcntl(nfd.get(), F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC)); + EXPECT_THAT(fcntl(nfd.get(), F_GETFL), SyscallSucceedsWithValue(flags)); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/fadvise64.cc b/test/syscalls/linux/fadvise64.cc index 2af7aa6d9..ac24c4066 100644 --- a/test/syscalls/linux/fadvise64.cc +++ b/test/syscalls/linux/fadvise64.cc @@ -45,6 +45,17 @@ TEST(FAdvise64Test, Basic) { SyscallSucceeds()); } +TEST(FAdvise64Test, FAdvise64WithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + + ASSERT_THAT(syscall(__NR_fadvise64, fd.get(), 0, 10, POSIX_FADV_NORMAL), + SyscallFailsWithErrno(EBADF)); + ASSERT_THAT(syscall(__NR_fadvise64, fd.get(), 0, 10, POSIX_FADV_NORMAL), + SyscallFailsWithErrno(EBADF)); +} + TEST(FAdvise64Test, InvalidArgs) { auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); const auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); diff --git a/test/syscalls/linux/fallocate.cc b/test/syscalls/linux/fallocate.cc index edd23e063..5c839447e 100644 --- a/test/syscalls/linux/fallocate.cc +++ b/test/syscalls/linux/fallocate.cc @@ -108,6 +108,13 @@ TEST_F(AllocateTest, FallocateReadonly) { EXPECT_THAT(fallocate(fd.get(), 0, 0, 10), SyscallFailsWithErrno(EBADF)); } +TEST_F(AllocateTest, FallocateWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + EXPECT_THAT(fallocate(fd.get(), 0, 0, 10), SyscallFailsWithErrno(EBADF)); +} + TEST_F(AllocateTest, FallocatePipe) { int pipes[2]; EXPECT_THAT(pipe(pipes), SyscallSucceeds()); diff --git a/test/syscalls/linux/fchdir.cc b/test/syscalls/linux/fchdir.cc index 08bcae1e8..c6675802d 100644 --- a/test/syscalls/linux/fchdir.cc +++ b/test/syscalls/linux/fchdir.cc @@ -71,6 +71,18 @@ TEST(FchdirTest, NotDir) { EXPECT_THAT(close(fd), SyscallSucceeds()); } +TEST(FchdirTest, FchdirWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(temp_dir.path(), O_PATH)); + ASSERT_THAT(open(temp_dir.path().c_str(), O_DIRECTORY | O_PATH), + SyscallSucceeds()); + + EXPECT_THAT(fchdir(fd.get()), SyscallSucceeds()); + // Change CWD to a permanent location as temp dirs will be cleaned up. + EXPECT_THAT(chdir("/"), SyscallSucceeds()); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/fcntl.cc b/test/syscalls/linux/fcntl.cc index 75a5c9f17..4fa6751ff 100644 --- a/test/syscalls/linux/fcntl.cc +++ b/test/syscalls/linux/fcntl.cc @@ -207,6 +207,41 @@ PosixErrorOr<Cleanup> SubprocessLock(std::string const& path, bool for_write, return std::move(cleanup); } +TEST(FcntlTest, FcntlDupWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_PATH)); + + int new_fd; + // Dup the descriptor and make sure it's the same file. + EXPECT_THAT(new_fd = fcntl(fd.get(), F_DUPFD, 0), SyscallSucceeds()); + + FileDescriptor nfd = FileDescriptor(new_fd); + ASSERT_NE(fd.get(), nfd.get()); + ASSERT_NO_ERRNO(CheckSameFile(fd, nfd)); + EXPECT_THAT(fcntl(nfd.get(), F_GETFL), SyscallSucceedsWithValue(O_PATH)); +} + +TEST(FcntlTest, SetFileStatusFlagWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_PATH)); + + EXPECT_THAT(fcntl(fd.get(), F_SETFL, 0), SyscallFailsWithErrno(EBADF)); +} + +TEST(FcntlTest, BadFcntlsWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_PATH)); + + EXPECT_THAT(fcntl(fd.get(), F_SETOWN, 0), SyscallFailsWithErrno(EBADF)); + EXPECT_THAT(fcntl(fd.get(), F_GETOWN, 0), SyscallFailsWithErrno(EBADF)); + + EXPECT_THAT(fcntl(fd.get(), F_SETOWN_EX, 0), SyscallFailsWithErrno(EBADF)); + EXPECT_THAT(fcntl(fd.get(), F_GETOWN_EX, 0), SyscallFailsWithErrno(EBADF)); +} + TEST(FcntlTest, SetCloExecBadFD) { // Open an eventfd file descriptor with FD_CLOEXEC descriptor flag not set. FileDescriptor f = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, 0)); @@ -226,6 +261,32 @@ TEST(FcntlTest, SetCloExec) { ASSERT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC)); } +TEST(FcntlTest, SetCloExecWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + // Open a file descriptor with FD_CLOEXEC descriptor flag not set. + TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_PATH)); + ASSERT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(0)); + + // Set the FD_CLOEXEC flag. + ASSERT_THAT(fcntl(fd.get(), F_SETFD, FD_CLOEXEC), SyscallSucceeds()); + ASSERT_THAT(fcntl(fd.get(), F_GETFD), SyscallSucceedsWithValue(FD_CLOEXEC)); +} + +TEST(FcntlTest, DupFDCloExecWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + // Open a file descriptor with FD_CLOEXEC descriptor flag not set. + TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_PATH)); + int nfd; + ASSERT_THAT(nfd = fcntl(fd.get(), F_DUPFD_CLOEXEC, 0), SyscallSucceeds()); + FileDescriptor dup_fd(nfd); + + // Check for the FD_CLOEXEC flag. + ASSERT_THAT(fcntl(dup_fd.get(), F_GETFD), + SyscallSucceedsWithValue(FD_CLOEXEC)); +} + TEST(FcntlTest, ClearCloExec) { // Open an eventfd file descriptor with FD_CLOEXEC descriptor flag set. FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_CLOEXEC)); @@ -267,6 +328,22 @@ TEST(FcntlTest, GetAllFlags) { EXPECT_EQ(rflags, expected); } +// When O_PATH is specified in flags, flag bits other than O_CLOEXEC, +// O_DIRECTORY, and O_NOFOLLOW are ignored. +TEST(FcntlTest, GetOpathFlag) { + SKIP_IF(IsRunningWithVFS1()); + TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + int flags = O_RDWR | O_DIRECT | O_SYNC | O_NONBLOCK | O_APPEND | O_PATH | + O_NOFOLLOW | O_DIRECTORY; + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), flags)); + + int expected = O_PATH | O_NOFOLLOW | O_DIRECTORY; + + int rflags; + EXPECT_THAT(rflags = fcntl(fd.get(), F_GETFL), SyscallSucceeds()); + EXPECT_EQ(rflags, expected); +} + TEST(FcntlTest, SetFlags) { TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), 0)); @@ -395,6 +472,22 @@ TEST_F(FcntlLockTest, SetLockBadOpenFlagsRead) { EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl1), SyscallFailsWithErrno(EBADF)); } +TEST_F(FcntlLockTest, SetLockWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + + struct flock fl0; + fl0.l_type = F_WRLCK; + fl0.l_whence = SEEK_SET; + fl0.l_start = 0; + fl0.l_len = 0; // Lock all file + + // Expect that setting a write lock using a Opath file descriptor + // won't work. + EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl0), SyscallFailsWithErrno(EBADF)); +} + TEST_F(FcntlLockTest, SetLockUnlockOnNothing) { auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); FileDescriptor fd = diff --git a/test/syscalls/linux/getdents.cc b/test/syscalls/linux/getdents.cc index 93c692dd6..2f2b14037 100644 --- a/test/syscalls/linux/getdents.cc +++ b/test/syscalls/linux/getdents.cc @@ -429,6 +429,32 @@ TYPED_TEST(GetdentsTest, NotDir) { SyscallFailsWithErrno(ENOTDIR)); } +// Test that getdents returns EBADF when called on an opath file. +TYPED_TEST(GetdentsTest, OpathFile) { + SKIP_IF(IsRunningWithVFS1()); + + auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + + typename TestFixture::DirentBufferType dirents(256); + EXPECT_THAT(RetryEINTR(syscall)(this->SyscallNum(), fd.get(), dirents.Data(), + dirents.Size()), + SyscallFailsWithErrno(EBADF)); +} + +// Test that getdents returns EBADF when called on an opath directory. +TYPED_TEST(GetdentsTest, OpathDirectory) { + SKIP_IF(IsRunningWithVFS1()); + + auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_PATH | O_DIRECTORY)); + + typename TestFixture::DirentBufferType dirents(256); + ASSERT_THAT(RetryEINTR(syscall)(this->SyscallNum(), fd.get(), dirents.Data(), + dirents.Size()), + SyscallFailsWithErrno(EBADF)); +} + // Test that SEEK_SET to 0 causes getdents to re-read the entries. TYPED_TEST(GetdentsTest, SeekResetsCursor) { // . and .. should be in an otherwise empty directory. diff --git a/test/syscalls/linux/getrusage.cc b/test/syscalls/linux/getrusage.cc index 0e51d42a8..e84cbfdc3 100644 --- a/test/syscalls/linux/getrusage.cc +++ b/test/syscalls/linux/getrusage.cc @@ -23,6 +23,7 @@ #include "absl/time/time.h" #include "test/util/logging.h" #include "test/util/memory_util.h" +#include "test/util/multiprocess_util.h" #include "test/util/signal_util.h" #include "test/util/test_util.h" @@ -93,59 +94,66 @@ TEST(GetrusageTest, Grandchild) { // Verifies that processes ignoring SIGCHLD do not have updated child maxrss // updated. TEST(GetrusageTest, IgnoreSIGCHLD) { - struct sigaction sa; - sa.sa_handler = SIG_IGN; - sa.sa_flags = 0; - auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGCHLD, sa)); - pid_t pid = fork(); - if (pid == 0) { + const auto rest = [] { + struct sigaction sa; + sa.sa_handler = SIG_IGN; + sa.sa_flags = 0; + auto cleanup = TEST_CHECK_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGCHLD, sa)); + pid_t pid = fork(); + if (pid == 0) { + struct rusage rusage_self; + TEST_PCHECK(getrusage(RUSAGE_SELF, &rusage_self) == 0); + // The child has consumed some memory. + TEST_CHECK(rusage_self.ru_maxrss != 0); + _exit(0); + } + TEST_CHECK_SUCCESS(pid); + int status; + TEST_CHECK_ERRNO(RetryEINTR(waitpid)(pid, &status, 0), ECHILD); struct rusage rusage_self; - TEST_PCHECK(getrusage(RUSAGE_SELF, &rusage_self) == 0); - // The child has consumed some memory. - TEST_CHECK(rusage_self.ru_maxrss != 0); - _exit(0); - } - ASSERT_THAT(pid, SyscallSucceeds()); - int status; - ASSERT_THAT(RetryEINTR(waitpid)(pid, &status, 0), - SyscallFailsWithErrno(ECHILD)); - struct rusage rusage_self; - ASSERT_THAT(getrusage(RUSAGE_SELF, &rusage_self), SyscallSucceeds()); - struct rusage rusage_children; - ASSERT_THAT(getrusage(RUSAGE_CHILDREN, &rusage_children), SyscallSucceeds()); - // The parent has consumed some memory. - EXPECT_GT(rusage_self.ru_maxrss, 0); - // The child's maxrss should not have propagated up. - EXPECT_EQ(rusage_children.ru_maxrss, 0); + TEST_CHECK_SUCCESS(getrusage(RUSAGE_SELF, &rusage_self)); + struct rusage rusage_children; + TEST_CHECK_SUCCESS(getrusage(RUSAGE_CHILDREN, &rusage_children)); + // The parent has consumed some memory. + TEST_CHECK(rusage_self.ru_maxrss > 0); + // The child's maxrss should not have propagated up. + TEST_CHECK(rusage_children.ru_maxrss == 0); + }; + // Execute inside a forked process so that rusage_children is clean. + EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); } // Verifies that zombie processes do not update their parent's maxrss. Only // reaped processes should do this. TEST(GetrusageTest, IgnoreZombie) { - pid_t pid = fork(); - if (pid == 0) { + const auto rest = [] { + pid_t pid = fork(); + if (pid == 0) { + struct rusage rusage_self; + TEST_PCHECK(getrusage(RUSAGE_SELF, &rusage_self) == 0); + struct rusage rusage_children; + TEST_PCHECK(getrusage(RUSAGE_CHILDREN, &rusage_children) == 0); + // The child has consumed some memory. + TEST_CHECK(rusage_self.ru_maxrss != 0); + // The child has no children of its own. + TEST_CHECK(rusage_children.ru_maxrss == 0); + _exit(0); + } + TEST_CHECK_SUCCESS(pid); + // Give the child time to exit. Because we don't call wait, the child should + // remain a zombie. + absl::SleepFor(absl::Seconds(5)); struct rusage rusage_self; - TEST_PCHECK(getrusage(RUSAGE_SELF, &rusage_self) == 0); + TEST_CHECK_SUCCESS(getrusage(RUSAGE_SELF, &rusage_self)); struct rusage rusage_children; - TEST_PCHECK(getrusage(RUSAGE_CHILDREN, &rusage_children) == 0); - // The child has consumed some memory. - TEST_CHECK(rusage_self.ru_maxrss != 0); - // The child has no children of its own. + TEST_CHECK_SUCCESS(getrusage(RUSAGE_CHILDREN, &rusage_children)); + // The parent has consumed some memory. + TEST_CHECK(rusage_self.ru_maxrss > 0); + // The child has consumed some memory, but hasn't been reaped. TEST_CHECK(rusage_children.ru_maxrss == 0); - _exit(0); - } - ASSERT_THAT(pid, SyscallSucceeds()); - // Give the child time to exit. Because we don't call wait, the child should - // remain a zombie. - absl::SleepFor(absl::Seconds(5)); - struct rusage rusage_self; - ASSERT_THAT(getrusage(RUSAGE_SELF, &rusage_self), SyscallSucceeds()); - struct rusage rusage_children; - ASSERT_THAT(getrusage(RUSAGE_CHILDREN, &rusage_children), SyscallSucceeds()); - // The parent has consumed some memory. - EXPECT_GT(rusage_self.ru_maxrss, 0); - // The child has consumed some memory, but hasn't been reaped. - EXPECT_EQ(rusage_children.ru_maxrss, 0); + }; + // Execute inside a forked process so that rusage_children is clean. + EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); } TEST(GetrusageTest, Wait4) { diff --git a/test/syscalls/linux/inotify.cc b/test/syscalls/linux/inotify.cc index 8137f0e29..a88c89e20 100644 --- a/test/syscalls/linux/inotify.cc +++ b/test/syscalls/linux/inotify.cc @@ -36,6 +36,7 @@ #include "test/util/epoll_util.h" #include "test/util/file_descriptor.h" #include "test/util/fs_util.h" +#include "test/util/multiprocess_util.h" #include "test/util/posix_error.h" #include "test/util/temp_path.h" #include "test/util/test_util.h" @@ -315,8 +316,7 @@ PosixErrorOr<std::vector<Event>> DrainEvents(int fd) { } PosixErrorOr<FileDescriptor> InotifyInit1(int flags) { - int fd; - EXPECT_THAT(fd = inotify_init1(flags), SyscallSucceeds()); + int fd = inotify_init1(flags); if (fd < 0) { return PosixError(errno, "inotify_init1() failed"); } @@ -325,9 +325,7 @@ PosixErrorOr<FileDescriptor> InotifyInit1(int flags) { PosixErrorOr<int> InotifyAddWatch(int fd, const std::string& path, uint32_t mask) { - int wd; - EXPECT_THAT(wd = inotify_add_watch(fd, path.c_str(), mask), - SyscallSucceeds()); + int wd = inotify_add_watch(fd, path.c_str(), mask); if (wd < 0) { return PosixError(errno, "inotify_add_watch() failed"); } @@ -784,6 +782,38 @@ TEST(Inotify, MoveWatchedTargetGeneratesEvents) { EXPECT_EQ(events[0].cookie, events[1].cookie); } +// Tests that close events are only emitted when a file description drops its +// last reference. +TEST(Inotify, DupFD) { + SKIP_IF(IsRunningWithVFS1()); + + const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor inotify_fd = + ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); + + const int wd = ASSERT_NO_ERRNO_AND_VALUE( + InotifyAddWatch(inotify_fd.get(), file.path(), IN_ALL_EVENTS)); + + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); + FileDescriptor fd2 = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup()); + + std::vector<Event> events = + ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); + EXPECT_THAT(events, Are({ + Event(IN_OPEN, wd), + })); + + fd.reset(); + events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); + EXPECT_THAT(events, Are({})); + + fd2.reset(); + events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); + EXPECT_THAT(events, Are({ + Event(IN_CLOSE_NOWRITE, wd), + })); +} + TEST(Inotify, CoalesceEvents) { const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); const FileDescriptor fd = @@ -1779,11 +1809,9 @@ TEST(Inotify, Sendfile) { EXPECT_THAT(out_events, Are({Event(IN_MODIFY, out_wd)})); } -// On Linux, inotify behavior is not very consistent with splice(2). We try our -// best to emulate Linux for very basic calls to splice. TEST(Inotify, SpliceOnWatchTarget) { - int pipes[2]; - ASSERT_THAT(pipe2(pipes, O_NONBLOCK), SyscallSucceeds()); + int pipefds[2]; + ASSERT_THAT(pipe2(pipefds, O_NONBLOCK), SyscallSucceeds()); const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); const FileDescriptor inotify_fd = @@ -1798,15 +1826,20 @@ TEST(Inotify, SpliceOnWatchTarget) { const int file_wd = ASSERT_NO_ERRNO_AND_VALUE( InotifyAddWatch(inotify_fd.get(), file.path(), IN_ALL_EVENTS)); - EXPECT_THAT(splice(fd.get(), nullptr, pipes[1], nullptr, 1, /*flags=*/0), + EXPECT_THAT(splice(fd.get(), nullptr, pipefds[1], nullptr, 1, /*flags=*/0), SyscallSucceedsWithValue(1)); - // Surprisingly, events are not generated in Linux if we read from a file. + // Surprisingly, events may not be generated in Linux if we read from a file. + // fs/splice.c:generic_file_splice_read, which is used most often, does not + // generate events, whereas fs/splice.c:default_file_splice_read does. std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); - ASSERT_THAT(events, Are({})); + if (IsRunningOnGvisor() && !IsRunningWithVFS1()) { + ASSERT_THAT(events, Are({Event(IN_ACCESS, dir_wd, Basename(file.path())), + Event(IN_ACCESS, file_wd)})); + } - EXPECT_THAT(splice(pipes[0], nullptr, fd.get(), nullptr, 1, /*flags=*/0), + EXPECT_THAT(splice(pipefds[0], nullptr, fd.get(), nullptr, 1, /*flags=*/0), SyscallSucceedsWithValue(1)); events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get())); @@ -1817,8 +1850,8 @@ TEST(Inotify, SpliceOnWatchTarget) { } TEST(Inotify, SpliceOnInotifyFD) { - int pipes[2]; - ASSERT_THAT(pipe2(pipes, O_NONBLOCK), SyscallSucceeds()); + int pipefds[2]; + ASSERT_THAT(pipe2(pipefds, O_NONBLOCK), SyscallSucceeds()); const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); const FileDescriptor fd = @@ -1834,11 +1867,11 @@ TEST(Inotify, SpliceOnInotifyFD) { char buf; EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds()); - EXPECT_THAT(splice(fd.get(), nullptr, pipes[1], nullptr, + EXPECT_THAT(splice(fd.get(), nullptr, pipefds[1], nullptr, sizeof(struct inotify_event) + 1, SPLICE_F_NONBLOCK), SyscallSucceedsWithValue(sizeof(struct inotify_event))); - const FileDescriptor read_fd(pipes[0]); + const FileDescriptor read_fd(pipefds[0]); const std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(read_fd.get())); ASSERT_THAT(events, Are({Event(IN_ACCESS, watcher)})); @@ -1936,24 +1969,29 @@ TEST(Inotify, Xattr) { } TEST(Inotify, Exec) { - const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const TempPath bin = ASSERT_NO_ERRNO_AND_VALUE( - TempPath::CreateSymlinkTo(dir.path(), "/bin/true")); - + SKIP_IF(IsRunningWithVFS1()); const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); const int wd = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), bin.path(), IN_ALL_EVENTS)); + InotifyAddWatch(fd.get(), "/bin/true", IN_ALL_EVENTS)); // Perform exec. - ScopedThread t([&bin]() { - ASSERT_THAT(execl(bin.path().c_str(), bin.path().c_str(), (char*)nullptr), - SyscallSucceeds()); - }); - t.Join(); + pid_t child = -1; + int execve_errno = -1; + auto kill = ASSERT_NO_ERRNO_AND_VALUE( + ForkAndExec("/bin/true", {}, {}, nullptr, &child, &execve_errno)); + ASSERT_EQ(0, execve_errno); + + int status; + ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceeds()); + EXPECT_EQ(0, status); + + // Process cleanup no longer needed. + kill.Release(); std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get())); - EXPECT_THAT(events, Are({Event(IN_OPEN, wd), Event(IN_ACCESS, wd)})); + EXPECT_THAT(events, Are({Event(IN_OPEN, wd), Event(IN_ACCESS, wd), + Event(IN_CLOSE_NOWRITE, wd)})); } // Watches without IN_EXCL_UNLINK, should continue to emit events for file diff --git a/test/syscalls/linux/ioctl.cc b/test/syscalls/linux/ioctl.cc index b0a07a064..9b16d1558 100644 --- a/test/syscalls/linux/ioctl.cc +++ b/test/syscalls/linux/ioctl.cc @@ -76,6 +76,19 @@ TEST_F(IoctlTest, InvalidControlNumber) { EXPECT_THAT(ioctl(STDOUT_FILENO, 0), SyscallFailsWithErrno(ENOTTY)); } +TEST_F(IoctlTest, IoctlWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/null", O_PATH)); + + int set = 1; + EXPECT_THAT(ioctl(fd.get(), FIONBIO, &set), SyscallFailsWithErrno(EBADF)); + + EXPECT_THAT(ioctl(fd.get(), FIONCLEX), SyscallFailsWithErrno(EBADF)); + + EXPECT_THAT(ioctl(fd.get(), FIOCLEX), SyscallFailsWithErrno(EBADF)); +} + TEST_F(IoctlTest, FIONBIOSucceeds) { EXPECT_FALSE(CheckNonBlocking(fd())); int set = 1; diff --git a/test/syscalls/linux/link.cc b/test/syscalls/linux/link.cc index 544681168..4f9ca1a65 100644 --- a/test/syscalls/linux/link.cc +++ b/test/syscalls/linux/link.cc @@ -50,6 +50,8 @@ bool IsSameFile(const std::string& f1, const std::string& f2) { return stat1.st_dev == stat2.st_dev && stat1.st_ino == stat2.st_ino; } +// TODO(b/178640646): Add test for linkat with AT_EMPTY_PATH + TEST(LinkTest, CanCreateLinkFile) { auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); const std::string newname = NewTempAbsPath(); @@ -235,6 +237,59 @@ TEST(LinkTest, AbsPathsWithNonDirFDs) { SyscallSucceeds()); } +TEST(LinkTest, NewDirFDWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const std::string newname_parent = NewTempAbsPath(); + const std::string newname_base = "child"; + const std::string newname = JoinPath(newname_parent, newname_base); + + // Create newname_parent directory, and get an FD. + EXPECT_THAT(mkdir(newname_parent.c_str(), 0777), SyscallSucceeds()); + const FileDescriptor newname_parent_fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(newname_parent, O_DIRECTORY | O_PATH)); + + // Link newname to oldfile, using newname_parent_fd. + EXPECT_THAT(linkat(AT_FDCWD, oldfile.path().c_str(), newname_parent_fd.get(), + newname.c_str(), 0), + SyscallSucceeds()); + + EXPECT_TRUE(IsSameFile(oldfile.path(), newname)); +} + +TEST(LinkTest, RelPathsNonDirFDsWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + + // Create a file that will be passed as the directory fd for old/new names. + TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor file_fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_PATH)); + + // Using file_fd as olddirfd will fail. + EXPECT_THAT(linkat(file_fd.get(), "foo", AT_FDCWD, "bar", 0), + SyscallFailsWithErrno(ENOTDIR)); + + // Using file_fd as newdirfd will fail. + EXPECT_THAT(linkat(AT_FDCWD, oldfile.path().c_str(), file_fd.get(), "bar", 0), + SyscallFailsWithErrno(ENOTDIR)); +} + +TEST(LinkTest, AbsPathsNonDirFDsWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + + auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const std::string newname = NewTempAbsPath(); + + // Create a file that will be passed as the directory fd for old/new names. + TempPath path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor file_fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_PATH)); + + // Using file_fd as the dirfds is OK as long as paths are absolute. + EXPECT_THAT(linkat(file_fd.get(), oldfile.path().c_str(), file_fd.get(), + newname.c_str(), 0), + SyscallSucceeds()); +} + TEST(LinkTest, LinkDoesNotFollowSymlinks) { // Create oldfile, and oldsymlink which points to it. auto oldfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); diff --git a/test/syscalls/linux/madvise.cc b/test/syscalls/linux/madvise.cc index 5a1973f60..6e714b12c 100644 --- a/test/syscalls/linux/madvise.cc +++ b/test/syscalls/linux/madvise.cc @@ -179,9 +179,9 @@ TEST(MadviseDontforkTest, DontforkShared) { // First page is mapped in child and modifications are visible to parent // via the shared mapping. TEST_CHECK(IsMapped(ms1.addr())); - ExpectAllMappingBytes(ms1, 2); + CheckAllMappingBytes(ms1, 2); memset(ms1.ptr(), 1, kPageSize); - ExpectAllMappingBytes(ms1, 1); + CheckAllMappingBytes(ms1, 1); // Second page must not be mapped in child. TEST_CHECK(!IsMapped(ms2.addr())); @@ -222,9 +222,9 @@ TEST(MadviseDontforkTest, DontforkAnonPrivate) { // page. The mapping is private so the modifications are not visible to // the parent. TEST_CHECK(IsMapped(mp1.addr())); - ExpectAllMappingBytes(mp1, 1); + CheckAllMappingBytes(mp1, 1); memset(mp1.ptr(), 11, kPageSize); - ExpectAllMappingBytes(mp1, 11); + CheckAllMappingBytes(mp1, 11); // Verify second page is not mapped. TEST_CHECK(!IsMapped(mp2.addr())); @@ -233,9 +233,9 @@ TEST(MadviseDontforkTest, DontforkAnonPrivate) { // page. The mapping is private so the modifications are not visible to // the parent. TEST_CHECK(IsMapped(mp3.addr())); - ExpectAllMappingBytes(mp3, 3); + CheckAllMappingBytes(mp3, 3); memset(mp3.ptr(), 13, kPageSize); - ExpectAllMappingBytes(mp3, 13); + CheckAllMappingBytes(mp3, 13); }; EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); diff --git a/test/syscalls/linux/mmap.cc b/test/syscalls/linux/mmap.cc index 83546830d..93a6d9cde 100644 --- a/test/syscalls/linux/mmap.cc +++ b/test/syscalls/linux/mmap.cc @@ -930,6 +930,18 @@ TEST_F(MMapFileTest, WriteSharedOnReadOnlyFd) { SyscallFailsWithErrno(EACCES)); } +// Mmap not allowed on O_PATH FDs. +TEST_F(MMapFileTest, MmapFileWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + + uintptr_t addr; + EXPECT_THAT(addr = Map(0, kPageSize, PROT_READ, MAP_PRIVATE, fd.get(), 0), + SyscallFailsWithErrno(EBADF)); +} + // The FD must be readable. TEST_P(MMapFileParamTest, WriteOnlyFd) { const FileDescriptor fd = diff --git a/test/syscalls/linux/open.cc b/test/syscalls/linux/open.cc index fcd162ca2..e65ffee8f 100644 --- a/test/syscalls/linux/open.cc +++ b/test/syscalls/linux/open.cc @@ -45,7 +45,7 @@ namespace { // * O_CREAT // * O_DIRECTORY // * O_NOFOLLOW -// * O_PATH <- Will we ever support this? +// * O_PATH // // Special operations on open: // * O_EXCL @@ -75,55 +75,52 @@ class OpenTest : public FileTest { }; TEST_F(OpenTest, OTrunc) { - auto dirpath = JoinPath(GetAbsoluteTestTmpdir(), "truncd"); - ASSERT_THAT(mkdir(dirpath.c_str(), 0777), SyscallSucceeds()); - ASSERT_THAT(open(dirpath.c_str(), O_TRUNC, 0666), + auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + ASSERT_THAT(open(dir.path().c_str(), O_TRUNC, 0666), SyscallFailsWithErrno(EISDIR)); } TEST_F(OpenTest, OTruncAndReadOnlyDir) { - auto dirpath = JoinPath(GetAbsoluteTestTmpdir(), "truncd"); - ASSERT_THAT(mkdir(dirpath.c_str(), 0777), SyscallSucceeds()); - ASSERT_THAT(open(dirpath.c_str(), O_TRUNC | O_RDONLY, 0666), + auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + ASSERT_THAT(open(dir.path().c_str(), O_TRUNC | O_RDONLY, 0666), SyscallFailsWithErrno(EISDIR)); } TEST_F(OpenTest, OTruncAndReadOnlyFile) { - auto dirpath = JoinPath(GetAbsoluteTestTmpdir(), "truncfile"); - const FileDescriptor existing = - ASSERT_NO_ERRNO_AND_VALUE(Open(dirpath.c_str(), O_RDWR | O_CREAT, 0666)); - const FileDescriptor otrunc = ASSERT_NO_ERRNO_AND_VALUE( - Open(dirpath.c_str(), O_TRUNC | O_RDONLY, 0666)); + auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto path = JoinPath(dir.path(), "foo"); + EXPECT_NO_ERRNO(Open(path, O_RDWR | O_CREAT, 0666)); + EXPECT_NO_ERRNO(Open(path, O_TRUNC | O_RDONLY, 0666)); } TEST_F(OpenTest, OCreateDirectory) { SKIP_IF(IsRunningWithVFS1()); - auto dirpath = GetAbsoluteTestTmpdir(); + auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); // Normal case: existing directory. - ASSERT_THAT(open(dirpath.c_str(), O_RDWR | O_CREAT, 0666), + ASSERT_THAT(open(dir.path().c_str(), O_RDWR | O_CREAT, 0666), SyscallFailsWithErrno(EISDIR)); // Trailing separator on existing directory. - ASSERT_THAT(open(dirpath.append("/").c_str(), O_RDWR | O_CREAT, 0666), + ASSERT_THAT(open(dir.path().append("/").c_str(), O_RDWR | O_CREAT, 0666), SyscallFailsWithErrno(EISDIR)); // Trailing separator on non-existing directory. - ASSERT_THAT(open(JoinPath(dirpath, "non-existent").append("/").c_str(), + ASSERT_THAT(open(JoinPath(dir.path(), "non-existent").append("/").c_str(), O_RDWR | O_CREAT, 0666), SyscallFailsWithErrno(EISDIR)); // "." special case. - ASSERT_THAT(open(JoinPath(dirpath, ".").c_str(), O_RDWR | O_CREAT, 0666), + ASSERT_THAT(open(JoinPath(dir.path(), ".").c_str(), O_RDWR | O_CREAT, 0666), SyscallFailsWithErrno(EISDIR)); } TEST_F(OpenTest, MustCreateExisting) { - auto dirPath = GetAbsoluteTestTmpdir(); + auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); // Existing directory. - ASSERT_THAT(open(dirPath.c_str(), O_RDWR | O_CREAT | O_EXCL, 0666), + ASSERT_THAT(open(dir.path().c_str(), O_RDWR | O_CREAT | O_EXCL, 0666), SyscallFailsWithErrno(EEXIST)); // Existing file. - auto newFile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dirPath)); + auto newFile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path())); ASSERT_THAT(open(newFile.path().c_str(), O_RDWR | O_CREAT | O_EXCL, 0666), SyscallFailsWithErrno(EEXIST)); } @@ -206,7 +203,8 @@ TEST_F(OpenTest, AtAbsPath) { } TEST_F(OpenTest, OpenNoFollowSymlink) { - const std::string link_path = JoinPath(GetAbsoluteTestTmpdir(), "link"); + auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + const std::string link_path = JoinPath(dir.path().c_str(), "link"); ASSERT_THAT(symlink(test_file_name_.c_str(), link_path.c_str()), SyscallSucceeds()); auto cleanup = Cleanup([link_path]() { @@ -227,8 +225,7 @@ TEST_F(OpenTest, OpenNoFollowStillFollowsLinksInPath) { // // We will then open tmp_folder/sym_folder/file with O_NOFOLLOW and it // should succeed as O_NOFOLLOW only applies to the final path component. - auto tmp_path = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(GetAbsoluteTestTmpdir())); + auto tmp_path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto sym_path = ASSERT_NO_ERRNO_AND_VALUE( TempPath::CreateSymlinkTo(GetAbsoluteTestTmpdir(), tmp_path.path())); auto file_path = @@ -246,8 +243,7 @@ TEST_F(OpenTest, OpenNoFollowStillFollowsLinksInPath) { // // open("root/child/symlink/root/child/file") TEST_F(OpenTest, SymlinkRecurse) { - auto root = - ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(GetAbsoluteTestTmpdir())); + auto root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto child = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path())); auto symlink = ASSERT_NO_ERRNO_AND_VALUE( TempPath::CreateSymlinkTo(child.path(), "../..")); @@ -481,12 +477,8 @@ TEST_F(OpenTest, CanTruncateWithStrangePermissions) { ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); const DisableSave ds; // Permissions are dropped. std::string path = NewTempAbsPath(); - int fd; // Create a file without user permissions. - EXPECT_THAT( // SAVE_BELOW - fd = open(path.c_str(), O_CREAT | O_TRUNC | O_WRONLY, 055), - SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); + EXPECT_NO_ERRNO(Open(path, O_CREAT | O_TRUNC | O_WRONLY, 055)); // Cannot open file because we are owner and have no permissions set. EXPECT_THAT(open(path.c_str(), O_RDONLY), SyscallFailsWithErrno(EACCES)); @@ -495,8 +487,7 @@ TEST_F(OpenTest, CanTruncateWithStrangePermissions) { EXPECT_THAT(chmod(path.c_str(), 0755), SyscallSucceeds()); // Now we can open the file again. - EXPECT_THAT(fd = open(path.c_str(), O_RDWR), SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); + EXPECT_NO_ERRNO(Open(path, O_RDWR)); } TEST_F(OpenTest, OpenNonDirectoryWithTrailingSlash) { @@ -517,6 +508,26 @@ TEST_F(OpenTest, OpenWithStrangeFlags) { EXPECT_THAT(read(fd.get(), &c, 1), SyscallFailsWithErrno(EBADF)); } +TEST_F(OpenTest, OpenWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); + ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false)); + const DisableSave ds; // Permissions are dropped. + std::string path = NewTempAbsPath(); + + // Create a file without user permissions. + const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( + Open(path.c_str(), O_CREAT | O_TRUNC | O_WRONLY, 055)); + + // Cannot open file as read only because we are owner and have no permissions + // set. + EXPECT_THAT(open(path.c_str(), O_RDONLY), SyscallFailsWithErrno(EACCES)); + + // Can open file with O_PATH because don't need permissions on the object when + // opening with O_PATH. + ASSERT_NO_ERRNO(Open(path, O_PATH)); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/open_create.cc b/test/syscalls/linux/open_create.cc index 9d63782fb..f8fbea79e 100644 --- a/test/syscalls/linux/open_create.cc +++ b/test/syscalls/linux/open_create.cc @@ -22,6 +22,7 @@ #include "test/util/capability_util.h" #include "test/util/file_descriptor.h" #include "test/util/fs_util.h" +#include "test/util/posix_error.h" #include "test/util/temp_path.h" #include "test/util/temp_umask.h" #include "test/util/test_util.h" @@ -31,85 +32,60 @@ namespace testing { namespace { TEST(CreateTest, TmpFile) { - int fd; - EXPECT_THAT(fd = open(JoinPath(GetAbsoluteTestTmpdir(), "a").c_str(), - O_RDWR | O_CREAT, 0666), - SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); + auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + EXPECT_NO_ERRNO(Open(JoinPath(dir.path(), "a"), O_RDWR | O_CREAT, 0666)); } TEST(CreateTest, ExistingFile) { - int fd; - EXPECT_THAT( - fd = open(JoinPath(GetAbsoluteTestTmpdir(), "ExistingFile").c_str(), - O_RDWR | O_CREAT, 0666), - SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); - - EXPECT_THAT( - fd = open(JoinPath(GetAbsoluteTestTmpdir(), "ExistingFile").c_str(), - O_RDWR | O_CREAT, 0666), - SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); + auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto path = JoinPath(dir.path(), "ExistingFile"); + EXPECT_NO_ERRNO(Open(path, O_RDWR | O_CREAT, 0666)); + EXPECT_NO_ERRNO(Open(path, O_RDWR | O_CREAT, 0666)); } TEST(CreateTest, CreateAtFile) { - int dirfd; - EXPECT_THAT(dirfd = open(GetAbsoluteTestTmpdir().c_str(), O_DIRECTORY, 0666), - SyscallSucceeds()); - EXPECT_THAT(openat(dirfd, "CreateAtFile", O_RDWR | O_CREAT, 0666), + auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto dirfd = ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY, 0666)); + EXPECT_THAT(openat(dirfd.get(), "CreateAtFile", O_RDWR | O_CREAT, 0666), SyscallSucceeds()); - EXPECT_THAT(close(dirfd), SyscallSucceeds()); } TEST(CreateTest, HonorsUmask_NoRandomSave) { const DisableSave ds; // file cannot be re-opened as writable. + auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); TempUmask mask(0222); - int fd; - ASSERT_THAT( - fd = open(JoinPath(GetAbsoluteTestTmpdir(), "UmaskedFile").c_str(), - O_RDWR | O_CREAT, 0666), - SyscallSucceeds()); + auto fd = ASSERT_NO_ERRNO_AND_VALUE( + Open(JoinPath(dir.path(), "UmaskedFile"), O_RDWR | O_CREAT, 0666)); struct stat statbuf; - ASSERT_THAT(fstat(fd, &statbuf), SyscallSucceeds()); + ASSERT_THAT(fstat(fd.get(), &statbuf), SyscallSucceeds()); EXPECT_EQ(0444, statbuf.st_mode & 0777); - EXPECT_THAT(close(fd), SyscallSucceeds()); } TEST(CreateTest, CreateExclusively) { - std::string filename = NewTempAbsPath(); - - int fd; - ASSERT_THAT(fd = open(filename.c_str(), O_CREAT | O_RDWR, 0644), - SyscallSucceeds()); - EXPECT_THAT(close(fd), SyscallSucceeds()); - - EXPECT_THAT(open(filename.c_str(), O_CREAT | O_EXCL | O_RDWR, 0644), + auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto path = JoinPath(dir.path(), "foo"); + EXPECT_NO_ERRNO(Open(path, O_CREAT | O_RDWR, 0644)); + EXPECT_THAT(open(path.c_str(), O_CREAT | O_EXCL | O_RDWR, 0644), SyscallFailsWithErrno(EEXIST)); } TEST(CreateTest, CreatWithOTrunc) { - std::string dirpath = JoinPath(GetAbsoluteTestTmpdir(), "truncd"); - ASSERT_THAT(mkdir(dirpath.c_str(), 0777), SyscallSucceeds()); - ASSERT_THAT(open(dirpath.c_str(), O_CREAT | O_TRUNC, 0666), + auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + ASSERT_THAT(open(dir.path().c_str(), O_CREAT | O_TRUNC, 0666), SyscallFailsWithErrno(EISDIR)); } TEST(CreateTest, CreatDirWithOTruncAndReadOnly) { - std::string dirpath = JoinPath(GetAbsoluteTestTmpdir(), "truncd"); - ASSERT_THAT(mkdir(dirpath.c_str(), 0777), SyscallSucceeds()); - ASSERT_THAT(open(dirpath.c_str(), O_CREAT | O_TRUNC | O_RDONLY, 0666), + auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + ASSERT_THAT(open(dir.path().c_str(), O_CREAT | O_TRUNC | O_RDONLY, 0666), SyscallFailsWithErrno(EISDIR)); } TEST(CreateTest, CreatFileWithOTruncAndReadOnly) { - std::string dirpath = JoinPath(GetAbsoluteTestTmpdir(), "truncfile"); - int dirfd; - ASSERT_THAT(dirfd = open(dirpath.c_str(), O_RDWR | O_CREAT, 0666), - SyscallSucceeds()); - ASSERT_THAT(open(dirpath.c_str(), O_CREAT | O_TRUNC | O_RDONLY, 0666), - SyscallSucceeds()); - ASSERT_THAT(close(dirfd), SyscallSucceeds()); + auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto path = JoinPath(dir.path(), "foo"); + ASSERT_NO_ERRNO(Open(path, O_RDWR | O_CREAT, 0666)); + ASSERT_NO_ERRNO(Open(path, O_CREAT | O_TRUNC | O_RDONLY, 0666)); } TEST(CreateTest, CreateFailsOnDirWithoutWritePerms) { diff --git a/test/syscalls/linux/packet_socket_raw.cc b/test/syscalls/linux/packet_socket_raw.cc index 2ed4f6f9c..d25be0e30 100644 --- a/test/syscalls/linux/packet_socket_raw.cc +++ b/test/syscalls/linux/packet_socket_raw.cc @@ -548,13 +548,7 @@ TEST_P(RawPacketTest, SetSocketSendBuf) { ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &val, &val_len), SyscallSucceeds()); - // Linux doubles the value set by SO_SNDBUF/SO_RCVBUF. - // TODO(gvisor.dev/issue/2926): Remove the gvisor special casing when Netstack - // matches linux behavior. - if (!IsRunningOnGvisor()) { - quarter_sz *= 2; - } - + quarter_sz *= 2; ASSERT_EQ(quarter_sz, val); } diff --git a/test/syscalls/linux/ping_socket.cc b/test/syscalls/linux/ping_socket.cc index a9bfdb37b..999c8ab6b 100644 --- a/test/syscalls/linux/ping_socket.cc +++ b/test/syscalls/linux/ping_socket.cc @@ -31,51 +31,36 @@ namespace gvisor { namespace testing { namespace { -class PingSocket : public ::testing::Test { - protected: - // Creates a socket to be used in tests. - void SetUp() override; - - // Closes the socket created by SetUp(). - void TearDown() override; - - // The loopback address. - struct sockaddr_in addr_; -}; - -void PingSocket::SetUp() { - // On some hosts ping sockets are restricted to specific groups using the - // sysctl "ping_group_range". - int s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); - if (s < 0 && errno == EPERM) { - GTEST_SKIP(); - } - close(s); - - addr_ = {}; - // Just a random port as the destination port number is irrelevant for ping - // sockets. - addr_.sin_port = 12345; - addr_.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - addr_.sin_family = AF_INET; -} - -void PingSocket::TearDown() {} - // Test ICMP port exhaustion returns EAGAIN. // // We disable both random/cooperative S/R for this test as it makes way too many // syscalls. -TEST_F(PingSocket, ICMPPortExhaustion_NoRandomSave) { +TEST(PingSocket, ICMPPortExhaustion_NoRandomSave) { DisableSave ds; + + { + auto s = Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); + if (!s.ok()) { + ASSERT_EQ(s.error().errno_value(), EACCES); + GTEST_SKIP(); + } + } + + const struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_addr = + { + .s_addr = htonl(INADDR_LOOPBACK), + }, + }; + std::vector<FileDescriptor> sockets; constexpr int kSockets = 65536; - addr_.sin_port = 0; for (int i = 0; i < kSockets; i++) { auto s = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)); - int ret = connect(s.get(), reinterpret_cast<struct sockaddr*>(&addr_), - sizeof(addr_)); + int ret = connect(s.get(), reinterpret_cast<const struct sockaddr*>(&addr), + sizeof(addr)); if (ret == 0) { sockets.push_back(std::move(s)); continue; diff --git a/test/syscalls/linux/pread64.cc b/test/syscalls/linux/pread64.cc index bcdbbb044..c74990ba1 100644 --- a/test/syscalls/linux/pread64.cc +++ b/test/syscalls/linux/pread64.cc @@ -77,6 +77,16 @@ TEST_F(Pread64Test, WriteOnlyNotReadable) { EXPECT_THAT(pread64(fd.get(), buf, 1024, 0), SyscallFailsWithErrno(EBADF)); } +TEST_F(Pread64Test, Pread64WithOpath) { + SKIP_IF(IsRunningWithVFS1()); + const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + + char buf[1024]; + EXPECT_THAT(pread64(fd.get(), buf, 1024, 0), SyscallFailsWithErrno(EBADF)); +} + TEST_F(Pread64Test, DirNotReadable) { const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(GetAbsoluteTestTmpdir(), O_RDONLY)); diff --git a/test/syscalls/linux/preadv.cc b/test/syscalls/linux/preadv.cc index 5b0743fe9..1c40f0915 100644 --- a/test/syscalls/linux/preadv.cc +++ b/test/syscalls/linux/preadv.cc @@ -89,6 +89,20 @@ TEST(PreadvTest, MMConcurrencyStress) { // The test passes if it neither deadlocks nor crashes the OS. } +// This test calls preadv with an O_PATH fd. +TEST(PreadvTest, PreadvWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + + struct iovec iov; + iov.iov_base = nullptr; + iov.iov_len = 0; + + EXPECT_THAT(preadv(fd.get(), &iov, 1, 0), SyscallFailsWithErrno(EBADF)); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/preadv2.cc b/test/syscalls/linux/preadv2.cc index 4a9acd7ae..cb58719c4 100644 --- a/test/syscalls/linux/preadv2.cc +++ b/test/syscalls/linux/preadv2.cc @@ -226,6 +226,24 @@ TEST(Preadv2Test, TestUnreadableFile) { SyscallFailsWithErrno(EBADF)); } +// This test calls preadv2 with a file opened with O_PATH. +TEST(Preadv2Test, Preadv2WithOpath) { + SKIP_IF(IsRunningWithVFS1()); + SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); + + const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + + auto iov = absl::make_unique<struct iovec[]>(1); + iov[0].iov_base = nullptr; + iov[0].iov_len = 0; + + EXPECT_THAT(preadv2(fd.get(), iov.get(), /*iovcnt=*/1, /*offset=*/0, + /*flags=*/0), + SyscallFailsWithErrno(EBADF)); +} + // Calling preadv2 with a non-negative offset calls preadv. Calling preadv with // an unseekable file is not allowed. A pipe is used for an unseekable file. TEST(Preadv2Test, TestUnseekableFileInvalid) { diff --git a/test/syscalls/linux/proc_net_unix.cc b/test/syscalls/linux/proc_net_unix.cc index 662c6feb2..d61d94309 100644 --- a/test/syscalls/linux/proc_net_unix.cc +++ b/test/syscalls/linux/proc_net_unix.cc @@ -18,6 +18,7 @@ #include "absl/strings/str_join.h" #include "absl/strings/str_split.h" #include "test/syscalls/linux/unix_domain_socket_test_util.h" +#include "test/util/cleanup.h" #include "test/util/file_descriptor.h" #include "test/util/fs_util.h" #include "test/util/test_util.h" @@ -341,6 +342,8 @@ TEST(ProcNetUnix, StreamSocketStateStateConnectedOnAccept) { int clientfd; ASSERT_THAT(clientfd = accept(sockets->first_fd(), nullptr, nullptr), SyscallSucceeds()); + auto cleanup = Cleanup( + [clientfd]() { ASSERT_THAT(close(clientfd), SyscallSucceeds()); }); // Find the entry for the accepted socket. UDS proc entries don't have a // remote address, so we distinguish the accepted socket from the listen diff --git a/test/syscalls/linux/pty.cc b/test/syscalls/linux/pty.cc index 0b174e2be..85ff258df 100644 --- a/test/syscalls/linux/pty.cc +++ b/test/syscalls/linux/pty.cc @@ -1338,6 +1338,7 @@ TEST_F(JobControlTest, SetTTYDifferentSession) { TEST_PCHECK(waitpid(grandchild, &gcwstatus, 0) == grandchild); TEST_PCHECK(gcwstatus == 0); }); + ASSERT_NO_ERRNO(res); } TEST_F(JobControlTest, ReleaseTTY) { @@ -1515,7 +1516,8 @@ TEST_F(JobControlTest, GetForegroundProcessGroupNonControlling) { // - creates a child process in a new process group // - sets that child as the foreground process group // - kills its child and sets itself as the foreground process group. -TEST_F(JobControlTest, SetForegroundProcessGroup) { +// TODO(gvisor.dev/issue/5357): Fix and enable. +TEST_F(JobControlTest, DISABLED_SetForegroundProcessGroup) { auto res = RunInChild([=]() { TEST_PCHECK(!ioctl(replica_.get(), TIOCSCTTY, 0)); @@ -1557,6 +1559,7 @@ TEST_F(JobControlTest, SetForegroundProcessGroup) { TEST_PCHECK(pgid = getpgid(0) == 0); TEST_PCHECK(!tcsetpgrp(replica_.get(), pgid)); }); + ASSERT_NO_ERRNO(res); } TEST_F(JobControlTest, SetForegroundProcessGroupWrongTTY) { @@ -1576,8 +1579,9 @@ TEST_F(JobControlTest, SetForegroundProcessGroupNegPgid) { ASSERT_NO_ERRNO(ret); } -TEST_F(JobControlTest, SetForegroundProcessGroupEmptyProcessGroup) { - auto ret = RunInChild([=]() { +// TODO(gvisor.dev/issue/5357): Fix and enable. +TEST_F(JobControlTest, DISABLED_SetForegroundProcessGroupEmptyProcessGroup) { + auto res = RunInChild([=]() { TEST_PCHECK(!ioctl(replica_.get(), TIOCSCTTY, 0)); // Create a new process, put it in a new process group, make that group the @@ -1595,6 +1599,7 @@ TEST_F(JobControlTest, SetForegroundProcessGroupEmptyProcessGroup) { TEST_PCHECK(ioctl(replica_.get(), TIOCSPGRP, &grandchild) != 0 && errno == ESRCH); }); + ASSERT_NO_ERRNO(res); } TEST_F(JobControlTest, SetForegroundProcessGroupDifferentSession) { diff --git a/test/syscalls/linux/pwrite64.cc b/test/syscalls/linux/pwrite64.cc index e69794910..1b2f25363 100644 --- a/test/syscalls/linux/pwrite64.cc +++ b/test/syscalls/linux/pwrite64.cc @@ -77,6 +77,17 @@ TEST_F(Pwrite64, Overflow) { EXPECT_THAT(close(fd), SyscallSucceeds()); } +TEST_F(Pwrite64, Pwrite64WithOpath) { + SKIP_IF(IsRunningWithVFS1()); + const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + + std::vector<char> buf(1); + EXPECT_THAT(PwriteFd(fd.get(), buf.data(), 1, 0), + SyscallFailsWithErrno(EBADF)); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/pwritev2.cc b/test/syscalls/linux/pwritev2.cc index 63b686c62..00aed61b4 100644 --- a/test/syscalls/linux/pwritev2.cc +++ b/test/syscalls/linux/pwritev2.cc @@ -283,6 +283,23 @@ TEST(Pwritev2Test, ReadOnlyFile) { SyscallFailsWithErrno(EBADF)); } +TEST(Pwritev2Test, Pwritev2WithOpath) { + SKIP_IF(IsRunningWithVFS1()); + SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); + + const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + + char buf[16]; + struct iovec iov; + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + + EXPECT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1, /*offset=*/0, /*flags=*/0), + SyscallFailsWithErrno(EBADF)); +} + // This test calls pwritev2 with an invalid flag. TEST(Pwritev2Test, InvalidFlag) { SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); diff --git a/test/syscalls/linux/raw_socket.cc b/test/syscalls/linux/raw_socket.cc index 955bcee4b..32924466f 100644 --- a/test/syscalls/linux/raw_socket.cc +++ b/test/syscalls/linux/raw_socket.cc @@ -621,13 +621,7 @@ TEST_P(RawSocketTest, SetSocketSendBuf) { ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &val, &val_len), SyscallSucceeds()); - // Linux doubles the value set by SO_SNDBUF/SO_RCVBUF. - // TODO(gvisor.dev/issue/2926): Remove the gvisor special casing when Netstack - // matches linux behavior. - if (!IsRunningOnGvisor()) { - quarter_sz *= 2; - } - + quarter_sz *= 2; ASSERT_EQ(quarter_sz, val); } diff --git a/test/syscalls/linux/read.cc b/test/syscalls/linux/read.cc index 2633ba31b..98d5e432d 100644 --- a/test/syscalls/linux/read.cc +++ b/test/syscalls/linux/read.cc @@ -112,6 +112,15 @@ TEST_F(ReadTest, ReadDirectoryFails) { EXPECT_THAT(ReadFd(file.get(), buf.data(), 1), SyscallFailsWithErrno(EISDIR)); } +TEST_F(ReadTest, ReadWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + std::vector<char> buf(1); + EXPECT_THAT(ReadFd(fd.get(), buf.data(), 1), SyscallFailsWithErrno(EBADF)); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/readv.cc b/test/syscalls/linux/readv.cc index baaf9f757..86808d255 100644 --- a/test/syscalls/linux/readv.cc +++ b/test/syscalls/linux/readv.cc @@ -251,6 +251,20 @@ TEST_F(ReadvTest, IovecOutsideTaskAddressRangeInNonemptyArray) { SyscallFailsWithErrno(EFAULT)); } +TEST_F(ReadvTest, ReadvWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + char buffer[1024]; + struct iovec iov[1]; + iov[0].iov_base = buffer; + iov[0].iov_len = 1024; + + TempPath tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path().c_str(), O_PATH)); + + ASSERT_THAT(readv(fd.get(), iov, 1), SyscallFailsWithErrno(EBADF)); +} + // This test depends on the maximum extent of a single readv() syscall, so // we can't tolerate interruption from saving. TEST(ReadvTestNoFixture, TruncatedAtMax_NoRandomSave) { diff --git a/test/syscalls/linux/setgid.cc b/test/syscalls/linux/setgid.cc new file mode 100644 index 000000000..bfd91ba4f --- /dev/null +++ b/test/syscalls/linux/setgid.cc @@ -0,0 +1,370 @@ +// Copyright 2020 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 <limits.h> +#include <sys/types.h> +#include <unistd.h> + +#include "gtest/gtest.h" +#include "test/util/capability_util.h" +#include "test/util/cleanup.h" +#include "test/util/fs_util.h" +#include "test/util/posix_error.h" +#include "test/util/temp_path.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +namespace { + +constexpr int kDirmodeMask = 07777; +constexpr int kDirmodeSgid = S_ISGID | 0777; +constexpr int kDirmodeNoExec = S_ISGID | 0767; +constexpr int kDirmodeNoSgid = 0777; + +// Sets effective GID and returns a Cleanup that restores the original. +PosixErrorOr<Cleanup> Setegid(gid_t egid) { + gid_t old_gid = getegid(); + if (setegid(egid) < 0) { + return PosixError(errno, absl::StrFormat("setegid(%d)", egid)); + } + return Cleanup( + [old_gid]() { EXPECT_THAT(setegid(old_gid), SyscallSucceeds()); }); +} + +// Returns a pair of groups that the user is a member of. +PosixErrorOr<std::pair<gid_t, gid_t>> Groups() { + // See whether the user is a member of at least 2 groups. + std::vector<gid_t> groups(64); + for (; groups.size() <= NGROUPS_MAX; groups.resize(groups.size() * 2)) { + int ngroups = getgroups(groups.size(), groups.data()); + if (ngroups < 0 && errno == EINVAL) { + // Need a larger list. + continue; + } + if (ngroups < 0) { + return PosixError(errno, absl::StrFormat("getgroups(%d, %p)", + groups.size(), groups.data())); + } + if (ngroups >= 2) { + return std::pair<gid_t, gid_t>(groups[0], groups[1]); + } + // There aren't enough groups. + break; + } + + // If we're root in the root user namespace, we can set our GID to whatever we + // want. Try that before giving up. + constexpr gid_t kGID1 = 1111; + constexpr gid_t kGID2 = 2222; + auto cleanup1 = Setegid(kGID1); + if (!cleanup1.ok()) { + return cleanup1.error(); + } + auto cleanup2 = Setegid(kGID2); + if (!cleanup2.ok()) { + return cleanup2.error(); + } + return std::pair<gid_t, gid_t>(kGID1, kGID2); +} + +class SetgidDirTest : public ::testing::Test { + protected: + void SetUp() override { + original_gid_ = getegid(); + + // TODO(b/175325250): Enable when setgid directories are supported. + SKIP_IF(IsRunningOnGvisor()); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETGID))); + + temp_dir_ = ASSERT_NO_ERRNO_AND_VALUE( + TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0777 /* mode */)); + groups_ = ASSERT_NO_ERRNO_AND_VALUE(Groups()); + } + + void TearDown() override { + ASSERT_THAT(setegid(original_gid_), SyscallSucceeds()); + } + + void MkdirAsGid(gid_t gid, const std::string& path, mode_t mode) { + auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(Setegid(gid)); + ASSERT_THAT(mkdir(path.c_str(), mode), SyscallSucceeds()); + } + + PosixErrorOr<struct stat> Stat(const std::string& path) { + struct stat stats; + if (stat(path.c_str(), &stats) < 0) { + return PosixError(errno, absl::StrFormat("stat(%s, _)", path)); + } + return stats; + } + + PosixErrorOr<struct stat> Stat(const FileDescriptor& fd) { + struct stat stats; + if (fstat(fd.get(), &stats) < 0) { + return PosixError(errno, "fstat(_, _)"); + } + return stats; + } + + TempPath temp_dir_; + std::pair<gid_t, gid_t> groups_; + gid_t original_gid_; +}; + +// The control test. Files created with a given GID are owned by that group. +TEST_F(SetgidDirTest, Control) { + // Set group to G1 and create a directory. + auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); + ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, 0777)); + + // Set group to G2, create a file in g1owned, and confirm that G2 owns it. + ASSERT_THAT(setegid(groups_.second), SyscallSucceeds()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( + Open(JoinPath(g1owned, "g2owned").c_str(), O_CREAT | O_RDWR, 0777)); + struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); + EXPECT_EQ(stats.st_gid, groups_.second); +} + +// Setgid directories cause created files to inherit GID. +TEST_F(SetgidDirTest, CreateFile) { + // Set group to G1, create a directory, and enable setgid. + auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); + ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, kDirmodeSgid)); + ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeSgid), SyscallSucceeds()); + + // Set group to G2, create a file, and confirm that G1 owns it. + ASSERT_THAT(setegid(groups_.second), SyscallSucceeds()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( + Open(JoinPath(g1owned, "g2created").c_str(), O_CREAT | O_RDWR, 0666)); + struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); + EXPECT_EQ(stats.st_gid, groups_.first); +} + +// Setgid directories cause created directories to inherit GID. +TEST_F(SetgidDirTest, CreateDir) { + // Set group to G1, create a directory, and enable setgid. + auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); + ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, kDirmodeSgid)); + ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeSgid), SyscallSucceeds()); + + // Set group to G2, create a directory, confirm that G1 owns it, and that the + // setgid bit is enabled. + auto g2created = JoinPath(g1owned, "g2created"); + ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.second, g2created, 0666)); + struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(g2created)); + EXPECT_EQ(stats.st_gid, groups_.first); + EXPECT_EQ(stats.st_mode & S_ISGID, S_ISGID); +} + +// Setgid directories with group execution disabled still cause GID inheritance. +TEST_F(SetgidDirTest, NoGroupExec) { + // Set group to G1, create a directory, and enable setgid. + auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); + ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, kDirmodeNoExec)); + ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeNoExec), SyscallSucceeds()); + + // Set group to G2, create a directory, confirm that G2 owns it, and that the + // setgid bit is enabled. + auto g2created = JoinPath(g1owned, "g2created"); + ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.second, g2created, 0666)); + struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(g2created)); + EXPECT_EQ(stats.st_gid, groups_.first); + EXPECT_EQ(stats.st_mode & S_ISGID, S_ISGID); +} + +// Setting the setgid bit on directories with an existing file does not change +// the file's group. +TEST_F(SetgidDirTest, OldFile) { + // Set group to G1 and create a directory. + auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); + ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, kDirmodeNoSgid)); + ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeNoSgid), SyscallSucceeds()); + + // Set group to G2, create a file, confirm that G2 owns it. + ASSERT_THAT(setegid(groups_.second), SyscallSucceeds()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( + Open(JoinPath(g1owned, "g2created").c_str(), O_CREAT | O_RDWR, 0666)); + struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); + EXPECT_EQ(stats.st_gid, groups_.second); + + // Enable setgid. + ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeSgid), SyscallSucceeds()); + + // Confirm that the file's group is still G2. + stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); + EXPECT_EQ(stats.st_gid, groups_.second); +} + +// Setting the setgid bit on directories with an existing subdirectory does not +// change the subdirectory's group. +TEST_F(SetgidDirTest, OldDir) { + // Set group to G1, create a directory, and enable setgid. + auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); + ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, kDirmodeNoSgid)); + ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeNoSgid), SyscallSucceeds()); + + // Set group to G2, create a directory, confirm that G2 owns it. + ASSERT_THAT(setegid(groups_.second), SyscallSucceeds()); + auto g2created = JoinPath(g1owned, "g2created"); + ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.second, g2created, 0666)); + struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(g2created)); + EXPECT_EQ(stats.st_gid, groups_.second); + + // Enable setgid. + ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeSgid), SyscallSucceeds()); + + // Confirm that the file's group is still G2. + stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(g2created)); + EXPECT_EQ(stats.st_gid, groups_.second); +} + +// Chowning a file clears the setgid and setuid bits. +TEST_F(SetgidDirTest, ChownFileClears) { + // Set group to G1, create a directory, and enable setgid. + auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); + ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, kDirmodeMask)); + ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeMask), SyscallSucceeds()); + + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( + Open(JoinPath(g1owned, "newfile").c_str(), O_CREAT | O_RDWR, 0666)); + ASSERT_THAT(fchmod(fd.get(), 0777 | S_ISUID | S_ISGID), SyscallSucceeds()); + struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); + EXPECT_EQ(stats.st_gid, groups_.first); + EXPECT_EQ(stats.st_mode & (S_ISUID | S_ISGID), S_ISUID | S_ISGID); + + // Change the owning group. + ASSERT_THAT(fchown(fd.get(), -1, groups_.second), SyscallSucceeds()); + + // The setgid and setuid bits should be cleared. + stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); + EXPECT_EQ(stats.st_gid, groups_.second); + EXPECT_EQ(stats.st_mode & (S_ISUID | S_ISGID), 0); +} + +// Chowning a file with setgid enabled, but not the group exec bit, does not +// clear the setgid bit. Such files are mandatory locked. +TEST_F(SetgidDirTest, ChownNoExecFileDoesNotClear) { + // Set group to G1, create a directory, and enable setgid. + auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); + ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, kDirmodeNoExec)); + ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeNoExec), SyscallSucceeds()); + + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( + Open(JoinPath(g1owned, "newdir").c_str(), O_CREAT | O_RDWR, 0666)); + ASSERT_THAT(fchmod(fd.get(), 0766 | S_ISUID | S_ISGID), SyscallSucceeds()); + struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); + EXPECT_EQ(stats.st_gid, groups_.first); + EXPECT_EQ(stats.st_mode & (S_ISUID | S_ISGID), S_ISUID | S_ISGID); + + // Change the owning group. + ASSERT_THAT(fchown(fd.get(), -1, groups_.second), SyscallSucceeds()); + + // Only the setuid bit is cleared. + stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); + EXPECT_EQ(stats.st_gid, groups_.second); + EXPECT_EQ(stats.st_mode & (S_ISUID | S_ISGID), S_ISGID); +} + +// Chowning a directory with setgid enabled does not clear the bit. +TEST_F(SetgidDirTest, ChownDirDoesNotClear) { + // Set group to G1, create a directory, and enable setgid. + auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); + ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, kDirmodeMask)); + ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeMask), SyscallSucceeds()); + + // Change the owning group. + ASSERT_THAT(chown(g1owned.c_str(), -1, groups_.second), SyscallSucceeds()); + + struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(g1owned)); + EXPECT_EQ(stats.st_gid, groups_.second); + EXPECT_EQ(stats.st_mode & kDirmodeMask, kDirmodeMask); +} + +struct FileModeTestcase { + std::string name; + mode_t mode; + mode_t result_mode; + + FileModeTestcase(const std::string& name, mode_t mode, mode_t result_mode) + : name(name), mode(mode), result_mode(result_mode) {} +}; + +class FileModeTest : public ::testing::TestWithParam<FileModeTestcase> {}; + +TEST_P(FileModeTest, WriteToFile) { + // TODO(b/175325250): Enable when setgid directories are supported. + SKIP_IF(IsRunningOnGvisor()); + + auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE( + TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0777 /* mode */)); + auto path = JoinPath(temp_dir.path(), GetParam().name); + FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(path.c_str(), O_CREAT | O_RDWR, 0666)); + ASSERT_THAT(fchmod(fd.get(), GetParam().mode), SyscallSucceeds()); + struct stat stats; + ASSERT_THAT(fstat(fd.get(), &stats), SyscallSucceeds()); + EXPECT_EQ(stats.st_mode & kDirmodeMask, GetParam().mode); + + // For security reasons, writing to the file clears the SUID bit, and clears + // the SGID bit when the group executable bit is unset (which is not a true + // SGID binary). + constexpr char kInput = 'M'; + ASSERT_THAT(write(fd.get(), &kInput, sizeof(kInput)), + SyscallSucceedsWithValue(sizeof(kInput))); + + ASSERT_THAT(fstat(fd.get(), &stats), SyscallSucceeds()); + EXPECT_EQ(stats.st_mode & kDirmodeMask, GetParam().result_mode); +} + +TEST_P(FileModeTest, TruncateFile) { + // TODO(b/175325250): Enable when setgid directories are supported. + SKIP_IF(IsRunningOnGvisor()); + + auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE( + TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0777 /* mode */)); + auto path = JoinPath(temp_dir.path(), GetParam().name); + FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(path.c_str(), O_CREAT | O_RDWR, 0666)); + ASSERT_THAT(fchmod(fd.get(), GetParam().mode), SyscallSucceeds()); + struct stat stats; + ASSERT_THAT(fstat(fd.get(), &stats), SyscallSucceeds()); + EXPECT_EQ(stats.st_mode & kDirmodeMask, GetParam().mode); + + // For security reasons, truncating the file clears the SUID bit, and clears + // the SGID bit when the group executable bit is unset (which is not a true + // SGID binary). + ASSERT_THAT(ftruncate(fd.get(), 0), SyscallSucceeds()); + + ASSERT_THAT(fstat(fd.get(), &stats), SyscallSucceeds()); + EXPECT_EQ(stats.st_mode & kDirmodeMask, GetParam().result_mode); +} + +INSTANTIATE_TEST_SUITE_P( + FileModes, FileModeTest, + ::testing::ValuesIn<FileModeTestcase>( + {FileModeTestcase("normal file", 0777, 0777), + FileModeTestcase("setuid", S_ISUID | 0777, 00777), + FileModeTestcase("setgid", S_ISGID | 0777, 00777), + FileModeTestcase("setuid and setgid", S_ISUID | S_ISGID | 0777, 00777), + FileModeTestcase("setgid without exec", S_ISGID | 0767, + S_ISGID | 0767), + FileModeTestcase("setuid and setgid without exec", + S_ISGID | S_ISUID | 0767, S_ISGID | 0767)})); + +} // namespace + +} // namespace testing +} // namespace gvisor diff --git a/test/syscalls/linux/shm.cc b/test/syscalls/linux/shm.cc index d6e8b3e59..baf794152 100644 --- a/test/syscalls/linux/shm.cc +++ b/test/syscalls/linux/shm.cc @@ -256,32 +256,26 @@ TEST(ShmTest, IpcInfo) { } TEST(ShmTest, ShmInfo) { - struct shm_info info; - - // We generally can't know what other processes on a linux machine - // does with shared memory segments, so we can't test specific - // numbers on Linux. When running under gvisor, we're guaranteed to - // be the only ones using shm, so we can easily verify machine-wide - // numbers. - if (IsRunningOnGvisor()) { - ASSERT_NO_ERRNO(Shmctl(0, SHM_INFO, &info)); - EXPECT_EQ(info.used_ids, 0); - EXPECT_EQ(info.shm_tot, 0); - EXPECT_EQ(info.shm_rss, 0); - EXPECT_EQ(info.shm_swp, 0); - } + // Take a snapshot of the system before the test runs. + struct shm_info snap; + ASSERT_NO_ERRNO(Shmctl(0, SHM_INFO, &snap)); const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); + struct shm_info info; ASSERT_NO_ERRNO(Shmctl(1, SHM_INFO, &info)); + // We generally can't know what other processes on a linux machine do with + // shared memory segments, so we can't test specific numbers on Linux. When + // running under gvisor, we're guaranteed to be the only ones using shm, so + // we can easily verify machine-wide numbers. if (IsRunningOnGvisor()) { ASSERT_NO_ERRNO(Shmctl(shm.id(), SHM_INFO, &info)); - EXPECT_EQ(info.used_ids, 1); - EXPECT_EQ(info.shm_tot, kAllocSize / kPageSize); - EXPECT_EQ(info.shm_rss, kAllocSize / kPageSize); + EXPECT_EQ(info.used_ids, snap.used_ids + 1); + EXPECT_EQ(info.shm_tot, snap.shm_tot + (kAllocSize / kPageSize)); + EXPECT_EQ(info.shm_rss, snap.shm_rss + (kAllocSize / kPageSize)); EXPECT_EQ(info.shm_swp, 0); // Gvisor currently never swaps. } @@ -378,18 +372,18 @@ TEST(ShmDeathTest, SegmentNotAccessibleAfterDetach) { SetupGvisorDeathTest(); const auto rest = [&] { - ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE( + ShmSegment shm = TEST_CHECK_NO_ERRNO_AND_VALUE( Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777)); - char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); + char* addr = TEST_CHECK_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0)); // Mark the segment as destroyed so it's automatically cleaned up when we // crash below. We can't rely on the standard cleanup since the destructor // will not run after the SIGSEGV. Note that this doesn't destroy the // segment immediately since we're still attached to it. - ASSERT_NO_ERRNO(shm.Rmid()); + TEST_CHECK_NO_ERRNO(shm.Rmid()); addr[0] = 'x'; - ASSERT_NO_ERRNO(Shmdt(addr)); + TEST_CHECK_NO_ERRNO(Shmdt(addr)); // This access should cause a SIGSEGV. addr[0] = 'x'; diff --git a/test/syscalls/linux/socket.cc b/test/syscalls/linux/socket.cc index 32f583581..b616c2c87 100644 --- a/test/syscalls/linux/socket.cc +++ b/test/syscalls/linux/socket.cc @@ -16,6 +16,7 @@ #include <sys/stat.h> #include <sys/statfs.h> #include <sys/types.h> +#include <sys/wait.h> #include <unistd.h> #include "gtest/gtest.h" @@ -90,8 +91,7 @@ TEST(SocketTest, UnixSocketStat) { EXPECT_EQ(statbuf.st_mode, S_IFSOCK | (sock_perm & ~mask)); // Timestamps should be equal and non-zero. - // TODO(b/158882152): Sockets currently don't implement timestamps. - if (!IsRunningOnGvisor()) { + if (!IsRunningWithVFS1()) { EXPECT_NE(statbuf.st_atime, 0); EXPECT_EQ(statbuf.st_atime, statbuf.st_mtime); EXPECT_EQ(statbuf.st_atime, statbuf.st_ctime); @@ -111,6 +111,77 @@ TEST(SocketTest, UnixSocketStatFS) { EXPECT_EQ(st.f_namelen, NAME_MAX); } +TEST(SocketTest, UnixSCMRightsOnlyPassedOnce_NoRandomSave) { + const DisableSave ds; + + int sockets[2]; + ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), SyscallSucceeds()); + // Send more than what will fit inside the send/receive buffers, so that it is + // split into multiple messages. + constexpr int kBufSize = 0x100000; + + pid_t pid = fork(); + if (pid == 0) { + TEST_PCHECK(close(sockets[0]) == 0); + + // Construct a message with some control message. + struct msghdr msg = {}; + char control[CMSG_SPACE(sizeof(int))] = {}; + std::vector<char> buf(kBufSize); + struct iovec iov = {}; + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + ((int*)CMSG_DATA(cmsg))[0] = sockets[1]; + + iov.iov_base = buf.data(); + iov.iov_len = kBufSize; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + int n = sendmsg(sockets[1], &msg, 0); + TEST_PCHECK(n == kBufSize); + TEST_PCHECK(shutdown(sockets[1], SHUT_RDWR) == 0); + TEST_PCHECK(close(sockets[1]) == 0); + _exit(0); + } + + close(sockets[1]); + + struct msghdr msg = {}; + char control[CMSG_SPACE(sizeof(int))] = {}; + std::vector<char> buf(kBufSize); + struct iovec iov = {}; + msg.msg_control = &control; + msg.msg_controllen = sizeof(control); + + iov.iov_base = buf.data(); + iov.iov_len = kBufSize; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + // The control message should only be present in the first message received. + int n; + ASSERT_THAT(n = recvmsg(sockets[0], &msg, 0), SyscallSucceeds()); + ASSERT_GT(n, 0); + ASSERT_EQ(msg.msg_controllen, CMSG_SPACE(sizeof(int))); + + while (n > 0) { + ASSERT_THAT(n = recvmsg(sockets[0], &msg, 0), SyscallSucceeds()); + ASSERT_EQ(msg.msg_controllen, 0); + } + + close(sockets[0]); + + int status; + ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid)); + ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); +} + using SocketOpenTest = ::testing::TestWithParam<int>; // UDS cannot be opened. diff --git a/test/syscalls/linux/socket_ip_tcp_generic.cc b/test/syscalls/linux/socket_ip_tcp_generic.cc index 831d96262..579e824cd 100644 --- a/test/syscalls/linux/socket_ip_tcp_generic.cc +++ b/test/syscalls/linux/socket_ip_tcp_generic.cc @@ -65,6 +65,37 @@ TEST_P(TCPSocketPairTest, ZeroTcpInfoSucceeds) { SyscallSucceeds()); } +// Copied from include/net/tcp.h. +constexpr int TCP_CA_OPEN = 0; + +TEST_P(TCPSocketPairTest, CheckTcpInfoFields) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + char buf[10] = {}; + ASSERT_THAT(RetryEINTR(send)(sockets->first_fd(), buf, sizeof(buf), 0), + SyscallSucceedsWithValue(sizeof(buf))); + + // Wait until second_fd sees the data and then recv it. + struct pollfd poll_fd = {sockets->second_fd(), POLLIN, 0}; + constexpr int kPollTimeoutMs = 2000; // Wait up to 2 seconds for the data. + ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs), + SyscallSucceedsWithValue(1)); + + ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), buf, sizeof(buf), 0), + SyscallSucceedsWithValue(sizeof(buf))); + + struct tcp_info opt = {}; + socklen_t optLen = sizeof(opt); + ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_TCP, TCP_INFO, &opt, &optLen), + SyscallSucceeds()); + ASSERT_EQ(optLen, sizeof(opt)); + + // Validates the received tcp_info fields. + EXPECT_EQ(opt.tcpi_ca_state, TCP_CA_OPEN); + EXPECT_GT(opt.tcpi_snd_cwnd, 0); + EXPECT_GT(opt.tcpi_rto, 0); +} + // This test validates that an RST is sent instead of a FIN when data is // unread on calls to close(2). TEST_P(TCPSocketPairTest, RSTSentOnCloseWithUnreadData) { diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound.cc b/test/syscalls/linux/socket_ipv4_udp_unbound.cc index e557572a7..8eec31a46 100644 --- a/test/syscalls/linux/socket_ipv4_udp_unbound.cc +++ b/test/syscalls/linux/socket_ipv4_udp_unbound.cc @@ -2513,11 +2513,7 @@ TEST_P(IPv4UDPUnboundSocketTest, SetSocketSendBuf) { ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &val, &val_len), SyscallSucceeds()); - // Linux doubles the value set by SO_SNDBUF/SO_RCVBUF. - if (!IsRunningOnGvisor()) { - quarter_sz *= 2; - } - + quarter_sz *= 2; ASSERT_EQ(quarter_sz, val); } diff --git a/test/syscalls/linux/stat.cc b/test/syscalls/linux/stat.cc index 6e7142a42..72f888659 100644 --- a/test/syscalls/linux/stat.cc +++ b/test/syscalls/linux/stat.cc @@ -221,6 +221,43 @@ TEST_F(StatTest, TrailingSlashNotCleanedReturnsENOTDIR) { EXPECT_THAT(lstat(bad_path.c_str(), &buf), SyscallFailsWithErrno(ENOTDIR)); } +TEST_F(StatTest, FstatFileWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + struct stat st; + TempPath tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path().c_str(), O_PATH)); + + // Stat the directory. + ASSERT_THAT(fstat(fd.get(), &st), SyscallSucceeds()); +} + +TEST_F(StatTest, FstatDirWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + struct stat st; + TempPath tmpdir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + FileDescriptor dirfd = ASSERT_NO_ERRNO_AND_VALUE( + Open(tmpdir.path().c_str(), O_PATH | O_DIRECTORY)); + + // Stat the directory. + ASSERT_THAT(fstat(dirfd.get(), &st), SyscallSucceeds()); +} + +// fstatat with an O_PATH fd +TEST_F(StatTest, FstatatDirWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + TempPath tmpdir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + FileDescriptor dirfd = ASSERT_NO_ERRNO_AND_VALUE( + Open(tmpdir.path().c_str(), O_PATH | O_DIRECTORY)); + TempPath tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + + struct stat st = {}; + EXPECT_THAT(fstatat(dirfd.get(), tmpfile.path().c_str(), &st, 0), + SyscallSucceeds()); + EXPECT_FALSE(S_ISDIR(st.st_mode)); + EXPECT_TRUE(S_ISREG(st.st_mode)); +} + // Test fstatating a symlink directory. TEST_F(StatTest, FstatatSymlinkDir) { // Create a directory and symlink to it. diff --git a/test/syscalls/linux/statfs.cc b/test/syscalls/linux/statfs.cc index f0fb166bd..d4ea8e026 100644 --- a/test/syscalls/linux/statfs.cc +++ b/test/syscalls/linux/statfs.cc @@ -64,6 +64,16 @@ TEST(FstatfsTest, InternalTmpfs) { EXPECT_THAT(fstatfs(fd.get(), &st), SyscallSucceeds()); } +TEST(FstatfsTest, CanStatFileWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(temp_file.path(), O_PATH)); + + struct statfs st; + EXPECT_THAT(fstatfs(fd.get(), &st), SyscallSucceeds()); +} + TEST(FstatfsTest, InternalDevShm) { auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); const FileDescriptor fd = diff --git a/test/syscalls/linux/symlink.cc b/test/syscalls/linux/symlink.cc index 4d9eba7f0..ea219a091 100644 --- a/test/syscalls/linux/symlink.cc +++ b/test/syscalls/linux/symlink.cc @@ -269,6 +269,36 @@ TEST(SymlinkTest, SymlinkAtDegradedPermissions_NoRandomSave) { EXPECT_THAT(close(dirfd), SyscallSucceeds()); } +TEST(SymlinkTest, SymlinkAtDirWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + const std::string filepath = NewTempAbsPathInDir(dir.path()); + const std::string base = std::string(Basename(filepath)); + FileDescriptor dirfd = + ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path().c_str(), O_DIRECTORY | O_PATH)); + + EXPECT_THAT(symlinkat("/dangling", dirfd.get(), base.c_str()), + SyscallSucceeds()); +} + +TEST(SymlinkTest, ReadlinkAtDirWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + const std::string filepath = NewTempAbsPathInDir(dir.path()); + const std::string base = std::string(Basename(filepath)); + ASSERT_THAT(symlink("/dangling", filepath.c_str()), SyscallSucceeds()); + + FileDescriptor dirfd = + ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path().c_str(), O_DIRECTORY | O_PATH)); + + std::vector<char> buf(1024); + int linksize; + EXPECT_THAT( + linksize = readlinkat(dirfd.get(), base.c_str(), buf.data(), 1024), + SyscallSucceeds()); + EXPECT_EQ(0, strncmp("/dangling", buf.data(), linksize)); +} + TEST(SymlinkTest, ReadlinkAtDegradedPermissions_NoRandomSave) { // Drop capabilities that allow us to override file and directory permissions. ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false)); diff --git a/test/syscalls/linux/sync.cc b/test/syscalls/linux/sync.cc index 8aa2525a9..84a2c4ed7 100644 --- a/test/syscalls/linux/sync.cc +++ b/test/syscalls/linux/sync.cc @@ -49,10 +49,20 @@ TEST(SyncTest, SyncFromPipe) { EXPECT_THAT(close(pipes[1]), SyscallSucceeds()); } -TEST(SyncTest, CannotSyncFileSytemAtBadFd) { +TEST(SyncTest, CannotSyncFileSystemAtBadFd) { EXPECT_THAT(syncfs(-1), SyscallFailsWithErrno(EBADF)); } +TEST(SyncTest, CannotSyncFileSystemAtOpathFD) { + SKIP_IF(IsRunningWithVFS1()); + + const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( + GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode)); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH)); + + EXPECT_THAT(syncfs(fd.get()), SyscallFailsWithErrno(EBADF)); +} } // namespace } // namespace testing diff --git a/test/syscalls/linux/tcp_socket.cc b/test/syscalls/linux/tcp_socket.cc index 9028ab024..f56c50e61 100644 --- a/test/syscalls/linux/tcp_socket.cc +++ b/test/syscalls/linux/tcp_socket.cc @@ -1168,6 +1168,42 @@ TEST_P(SimpleTcpSocketTest, SelfConnectSendRecv_NoRandomSave) { EXPECT_EQ(read_bytes, kBufSz); } +TEST_P(SimpleTcpSocketTest, SelfConnectSend_NoRandomSave) { + // Initialize address to the loopback one. + sockaddr_storage addr = + ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); + socklen_t addrlen = sizeof(addr); + + const FileDescriptor s = + ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); + + constexpr int max_seg = 256; + ASSERT_THAT( + setsockopt(s.get(), SOL_TCP, TCP_MAXSEG, &max_seg, sizeof(max_seg)), + SyscallSucceeds()); + + ASSERT_THAT(bind(s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), + SyscallSucceeds()); + // Get the bound port. + ASSERT_THAT( + getsockname(s.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), + SyscallSucceeds()); + ASSERT_THAT(RetryEINTR(connect)( + s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), + SyscallSucceeds()); + + std::vector<char> writebuf(512 << 10); // 512 KiB. + + // Try to send the whole thing. + int n; + ASSERT_THAT(n = SendFd(s.get(), writebuf.data(), writebuf.size(), 0), + SyscallSucceeds()); + + // We should have written the whole thing. + EXPECT_EQ(n, writebuf.size()); + EXPECT_THAT(shutdown(s.get(), SHUT_WR), SyscallSucceedsWithValue(0)); +} + TEST_P(SimpleTcpSocketTest, NonBlockingConnect) { const FileDescriptor listener = ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); diff --git a/test/syscalls/linux/truncate.cc b/test/syscalls/linux/truncate.cc index bfc95ed38..17832c47d 100644 --- a/test/syscalls/linux/truncate.cc +++ b/test/syscalls/linux/truncate.cc @@ -196,6 +196,16 @@ TEST(TruncateTest, FtruncateNonWriteable) { EXPECT_THAT(ftruncate(fd.get(), 0), SyscallFailsWithErrno(EINVAL)); } +TEST(TruncateTest, FtruncateWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( + GetAbsoluteTestTmpdir(), absl::string_view(), 0555 /* mode */)); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(temp_file.path(), O_PATH)); + EXPECT_THAT(ftruncate(fd.get(), 0), AnyOf(SyscallFailsWithErrno(EBADF), + SyscallFailsWithErrno(EINVAL))); +} + // ftruncate(2) should succeed as long as the file descriptor is writeable, // regardless of whether the file permissions allow writing. TEST(TruncateTest, FtruncateWithoutWritePermission_NoRandomSave) { diff --git a/test/syscalls/linux/uidgid.cc b/test/syscalls/linux/uidgid.cc index 64d6d0b8f..4139a18d8 100644 --- a/test/syscalls/linux/uidgid.cc +++ b/test/syscalls/linux/uidgid.cc @@ -23,6 +23,8 @@ #include "absl/strings/str_cat.h" #include "absl/strings/str_join.h" #include "test/util/capability_util.h" +#include "test/util/cleanup.h" +#include "test/util/multiprocess_util.h" #include "test/util/posix_error.h" #include "test/util/test_util.h" #include "test/util/thread_util.h" @@ -33,6 +35,16 @@ ABSL_FLAG(int32_t, scratch_uid2, 65533, "second scratch UID"); ABSL_FLAG(int32_t, scratch_gid1, 65534, "first scratch GID"); ABSL_FLAG(int32_t, scratch_gid2, 65533, "second scratch GID"); +// Force use of syscall instead of glibc set*id() wrappers because we want to +// apply to the current task only. libc sets all threads in a process because +// "POSIX requires that all threads in a process share the same credentials." +#define setuid USE_SYSCALL_INSTEAD +#define setgid USE_SYSCALL_INSTEAD +#define setreuid USE_SYSCALL_INSTEAD +#define setregid USE_SYSCALL_INSTEAD +#define setresuid USE_SYSCALL_INSTEAD +#define setresgid USE_SYSCALL_INSTEAD + using ::testing::UnorderedElementsAreArray; namespace gvisor { @@ -137,21 +149,31 @@ TEST(UidGidRootTest, Setuid) { TEST(UidGidRootTest, Setgid) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot())); - EXPECT_THAT(setgid(-1), SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(syscall(SYS_setgid, -1), SyscallFailsWithErrno(EINVAL)); - const gid_t gid = absl::GetFlag(FLAGS_scratch_gid1); - ASSERT_THAT(setgid(gid), SyscallSucceeds()); - EXPECT_NO_ERRNO(CheckGIDs(gid, gid, gid)); + ScopedThread([&] { + const gid_t gid = absl::GetFlag(FLAGS_scratch_gid1); + EXPECT_THAT(syscall(SYS_setgid, gid), SyscallSucceeds()); + EXPECT_NO_ERRNO(CheckGIDs(gid, gid, gid)); + }); } TEST(UidGidRootTest, SetgidNotFromThreadGroupLeader) { +#pragma push_macro("allow_setgid") +#undef setgid + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot())); + int old_gid = getgid(); + auto clean = Cleanup([old_gid] { setgid(old_gid); }); + const gid_t gid = absl::GetFlag(FLAGS_scratch_gid1); // NOTE(b/64676707): Do setgid in a separate thread so that we can test if // info.si_pid is set correctly. ScopedThread([gid] { ASSERT_THAT(setgid(gid), SyscallSucceeds()); }); EXPECT_NO_ERRNO(CheckGIDs(gid, gid, gid)); + +#pragma pop_macro("allow_setgid") } TEST(UidGidRootTest, Setreuid) { @@ -159,27 +181,25 @@ TEST(UidGidRootTest, Setreuid) { // "Supplying a value of -1 for either the real or effective user ID forces // the system to leave that ID unchanged." - setreuid(2) - EXPECT_THAT(setreuid(-1, -1), SyscallSucceeds()); + EXPECT_THAT(syscall(SYS_setreuid, -1, -1), SyscallSucceeds()); + EXPECT_NO_ERRNO(CheckUIDs(0, 0, 0)); // Do setuid in a separate thread so that after finishing this test, the - // process can still open files the test harness created before starting this - // test. Otherwise, the files are created by root (UID before the test), but - // cannot be opened by the `uid` set below after the test. After calling - // setuid(non-zero-UID), there is no way to get root privileges back. + // process can still open files the test harness created before starting + // this test. Otherwise, the files are created by root (UID before the + // test), but cannot be opened by the `uid` set below after the test. After + // calling setuid(non-zero-UID), there is no way to get root privileges + // back. ScopedThread([&] { const uid_t ruid = absl::GetFlag(FLAGS_scratch_uid1); const uid_t euid = absl::GetFlag(FLAGS_scratch_uid2); - // Use syscall instead of glibc setuid wrapper because we want this setuid - // call to only apply to this task. posix threads, however, require that all - // threads have the same UIDs, so using the setuid wrapper sets all threads' - // real UID. EXPECT_THAT(syscall(SYS_setreuid, ruid, euid), SyscallSucceeds()); // "If the real user ID is set or the effective user ID is set to a value - // not equal to the previous real user ID, the saved set-user-ID will be set - // to the new effective user ID." - setreuid(2) + // not equal to the previous real user ID, the saved set-user-ID will be + // set to the new effective user ID." - setreuid(2) EXPECT_NO_ERRNO(CheckUIDs(ruid, euid, euid)); }); } @@ -187,13 +207,15 @@ TEST(UidGidRootTest, Setreuid) { TEST(UidGidRootTest, Setregid) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot())); - EXPECT_THAT(setregid(-1, -1), SyscallSucceeds()); + EXPECT_THAT(syscall(SYS_setregid, -1, -1), SyscallSucceeds()); EXPECT_NO_ERRNO(CheckGIDs(0, 0, 0)); - const gid_t rgid = absl::GetFlag(FLAGS_scratch_gid1); - const gid_t egid = absl::GetFlag(FLAGS_scratch_gid2); - ASSERT_THAT(setregid(rgid, egid), SyscallSucceeds()); - EXPECT_NO_ERRNO(CheckGIDs(rgid, egid, egid)); + ScopedThread([&] { + const gid_t rgid = absl::GetFlag(FLAGS_scratch_gid1); + const gid_t egid = absl::GetFlag(FLAGS_scratch_gid2); + ASSERT_THAT(syscall(SYS_setregid, rgid, egid), SyscallSucceeds()); + EXPECT_NO_ERRNO(CheckGIDs(rgid, egid, egid)); + }); } TEST(UidGidRootTest, Setresuid) { @@ -201,23 +223,24 @@ TEST(UidGidRootTest, Setresuid) { // "If one of the arguments equals -1, the corresponding value is not // changed." - setresuid(2) - EXPECT_THAT(setresuid(-1, -1, -1), SyscallSucceeds()); + EXPECT_THAT(syscall(SYS_setresuid, -1, -1, -1), SyscallSucceeds()); EXPECT_NO_ERRNO(CheckUIDs(0, 0, 0)); // Do setuid in a separate thread so that after finishing this test, the - // process can still open files the test harness created before starting this - // test. Otherwise, the files are created by root (UID before the test), but - // cannot be opened by the `uid` set below after the test. After calling - // setuid(non-zero-UID), there is no way to get root privileges back. + // process can still open files the test harness created before starting + // this test. Otherwise, the files are created by root (UID before the + // test), but cannot be opened by the `uid` set below after the test. After + // calling setuid(non-zero-UID), there is no way to get root privileges + // back. ScopedThread([&] { const uid_t ruid = 12345; const uid_t euid = 23456; const uid_t suid = 34567; // Use syscall instead of glibc setuid wrapper because we want this setuid - // call to only apply to this task. posix threads, however, require that all - // threads have the same UIDs, so using the setuid wrapper sets all threads' - // real UID. + // call to only apply to this task. posix threads, however, require that + // all threads have the same UIDs, so using the setuid wrapper sets all + // threads' real UID. EXPECT_THAT(syscall(SYS_setresuid, ruid, euid, suid), SyscallSucceeds()); EXPECT_NO_ERRNO(CheckUIDs(ruid, euid, suid)); }); @@ -226,14 +249,16 @@ TEST(UidGidRootTest, Setresuid) { TEST(UidGidRootTest, Setresgid) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot())); - EXPECT_THAT(setresgid(-1, -1, -1), SyscallSucceeds()); + EXPECT_THAT(syscall(SYS_setresgid, -1, -1, -1), SyscallSucceeds()); EXPECT_NO_ERRNO(CheckGIDs(0, 0, 0)); - const gid_t rgid = 12345; - const gid_t egid = 23456; - const gid_t sgid = 34567; - ASSERT_THAT(setresgid(rgid, egid, sgid), SyscallSucceeds()); - EXPECT_NO_ERRNO(CheckGIDs(rgid, egid, sgid)); + ScopedThread([&] { + const gid_t rgid = 12345; + const gid_t egid = 23456; + const gid_t sgid = 34567; + ASSERT_THAT(syscall(SYS_setresgid, rgid, egid, sgid), SyscallSucceeds()); + EXPECT_NO_ERRNO(CheckGIDs(rgid, egid, sgid)); + }); } TEST(UidGidRootTest, Setgroups) { @@ -254,14 +279,14 @@ TEST(UidGidRootTest, Setuid_prlimit) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot())); // Do seteuid in a separate thread so that after finishing this test, the - // process can still open files the test harness created before starting this - // test. Otherwise, the files are created by root (UID before the test), but - // cannot be opened by the `uid` set below after the test. + // process can still open files the test harness created before starting + // this test. Otherwise, the files are created by root (UID before the + // test), but cannot be opened by the `uid` set below after the test. ScopedThread([&] { - // Use syscall instead of glibc setuid wrapper because we want this seteuid - // call to only apply to this task. POSIX threads, however, require that all - // threads have the same UIDs, so using the seteuid wrapper sets all - // threads' UID. + // Use syscall instead of glibc setuid wrapper because we want this + // seteuid call to only apply to this task. POSIX threads, however, + // require that all threads have the same UIDs, so using the seteuid + // wrapper sets all threads' UID. EXPECT_THAT(syscall(SYS_setreuid, -1, 65534), SyscallSucceeds()); // Despite the UID change, we should be able to get our own limits. diff --git a/test/syscalls/linux/write.cc b/test/syscalls/linux/write.cc index 77bcfbb8a..740992d0a 100644 --- a/test/syscalls/linux/write.cc +++ b/test/syscalls/linux/write.cc @@ -218,6 +218,44 @@ TEST_F(WriteTest, PwriteNoChangeOffset) { EXPECT_THAT(lseek(fd, 0, SEEK_CUR), SyscallSucceedsWithValue(bytes_total)); } +TEST_F(WriteTest, WriteWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + TempPath tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor f = + ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path().c_str(), O_PATH)); + int fd = f.get(); + + EXPECT_THAT(WriteBytes(fd, 1024), SyscallFailsWithErrno(EBADF)); +} + +TEST_F(WriteTest, WritevWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + TempPath tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor f = + ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path().c_str(), O_PATH)); + int fd = f.get(); + + char buf[16]; + struct iovec iov; + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + + EXPECT_THAT(writev(fd, &iov, /*__count=*/1), SyscallFailsWithErrno(EBADF)); +} + +TEST_F(WriteTest, PwriteWithOpath) { + SKIP_IF(IsRunningWithVFS1()); + TempPath tmpfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor f = + ASSERT_NO_ERRNO_AND_VALUE(Open(tmpfile.path().c_str(), O_PATH)); + int fd = f.get(); + + const std::string data = "hello world\n"; + + EXPECT_THAT(pwrite(fd, data.data(), data.size(), 0), + SyscallFailsWithErrno(EBADF)); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/xattr.cc b/test/syscalls/linux/xattr.cc index bd3f829c4..a953a55fe 100644 --- a/test/syscalls/linux/xattr.cc +++ b/test/syscalls/linux/xattr.cc @@ -607,6 +607,27 @@ TEST_F(XattrTest, XattrWithFD) { EXPECT_THAT(fremovexattr(fd.get(), name), SyscallSucceeds()); } +TEST_F(XattrTest, XattrWithOPath) { + SKIP_IF(IsRunningWithVFS1()); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_.c_str(), O_PATH)); + const char name[] = "user.test"; + int val = 1234; + size_t size = sizeof(val); + EXPECT_THAT(fsetxattr(fd.get(), name, &val, size, /*flags=*/0), + SyscallFailsWithErrno(EBADF)); + + int buf; + EXPECT_THAT(fgetxattr(fd.get(), name, &buf, size), + SyscallFailsWithErrno(EBADF)); + + char list[sizeof(name)]; + EXPECT_THAT(flistxattr(fd.get(), list, sizeof(list)), + SyscallFailsWithErrno(EBADF)); + + EXPECT_THAT(fremovexattr(fd.get(), name), SyscallFailsWithErrno(EBADF)); +} + TEST_F(XattrTest, TrustedNamespaceWithCapSysAdmin) { // Trusted namespace not supported in VFS1. SKIP_IF(IsRunningWithVFS1()); |