summaryrefslogtreecommitdiffhomepage
path: root/test/syscalls
diff options
context:
space:
mode:
Diffstat (limited to 'test/syscalls')
-rw-r--r--test/syscalls/BUILD4
-rw-r--r--test/syscalls/linux/BUILD25
-rw-r--r--test/syscalls/linux/chroot.cc425
-rw-r--r--test/syscalls/linux/getrusage.cc96
-rw-r--r--test/syscalls/linux/open.cc53
-rw-r--r--test/syscalls/linux/open_create.cc76
-rw-r--r--test/syscalls/linux/proc_net_unix.cc3
-rw-r--r--test/syscalls/linux/setgid.cc370
-rw-r--r--test/syscalls/linux/socket_ip_tcp_generic.cc5
-rw-r--r--test/syscalls/linux/uidgid.cc107
10 files changed, 791 insertions, 373 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 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.