diff options
Diffstat (limited to 'test/syscalls/linux')
-rw-r--r-- | test/syscalls/linux/BUILD | 25 | ||||
-rw-r--r-- | test/syscalls/linux/chroot.cc | 425 | ||||
-rw-r--r-- | test/syscalls/linux/getrusage.cc | 96 | ||||
-rw-r--r-- | test/syscalls/linux/open.cc | 53 | ||||
-rw-r--r-- | test/syscalls/linux/open_create.cc | 76 | ||||
-rw-r--r-- | test/syscalls/linux/proc_net_unix.cc | 3 | ||||
-rw-r--r-- | test/syscalls/linux/setgid.cc | 370 | ||||
-rw-r--r-- | test/syscalls/linux/socket_ip_tcp_generic.cc | 5 | ||||
-rw-r--r-- | test/syscalls/linux/uidgid.cc | 107 |
9 files changed, 787 insertions, 373 deletions
diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 2b4b6f348..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", @@ -672,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", @@ -1380,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", @@ -2143,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"], @@ -3827,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", @@ -4082,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/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/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/open.cc b/test/syscalls/linux/open.cc index 733b17834..e65ffee8f 100644 --- a/test/syscalls/linux/open.cc +++ b/test/syscalls/linux/open.cc @@ -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) { 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/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/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/socket_ip_tcp_generic.cc b/test/syscalls/linux/socket_ip_tcp_generic.cc index a73987a7e..2c8e5f6f3 100644 --- a/test/syscalls/linux/socket_ip_tcp_generic.cc +++ b/test/syscalls/linux/socket_ip_tcp_generic.cc @@ -65,6 +65,9 @@ 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()); @@ -87,7 +90,7 @@ TEST_P(TCPSocketPairTest, CheckTcpInfoFields) { SyscallSucceeds()); // Validates the received tcp_info fields. - EXPECT_EQ(opt.tcpi_ca_state, 0); + EXPECT_EQ(opt.tcpi_ca_state, TCP_CA_OPEN); EXPECT_GT(opt.tcpi_snd_cwnd, 0); EXPECT_GT(opt.tcpi_rto, 0); } 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. |