summaryrefslogtreecommitdiffhomepage
path: root/test/syscalls/linux/uidgid.cc
diff options
context:
space:
mode:
Diffstat (limited to 'test/syscalls/linux/uidgid.cc')
-rw-r--r--test/syscalls/linux/uidgid.cc277
1 files changed, 277 insertions, 0 deletions
diff --git a/test/syscalls/linux/uidgid.cc b/test/syscalls/linux/uidgid.cc
new file mode 100644
index 000000000..c0c1f2960
--- /dev/null
+++ b/test/syscalls/linux/uidgid.cc
@@ -0,0 +1,277 @@
+// Copyright 2018 Google LLC
+//
+// 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 <errno.h>
+#include <grp.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "gtest/gtest.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_join.h"
+#include "test/util/capability_util.h"
+#include "test/util/posix_error.h"
+#include "test/util/test_util.h"
+#include "test/util/thread_util.h"
+
+DEFINE_int32(scratch_uid1, 65534, "first scratch UID");
+DEFINE_int32(scratch_uid2, 65533, "second scratch UID");
+DEFINE_int32(scratch_gid1, 65534, "first scratch GID");
+DEFINE_int32(scratch_gid2, 65533, "second scratch GID");
+
+using ::testing::UnorderedElementsAreArray;
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+TEST(UidGidTest, Getuid) {
+ uid_t ruid, euid, suid;
+ EXPECT_THAT(getresuid(&ruid, &euid, &suid), SyscallSucceeds());
+ EXPECT_THAT(getuid(), SyscallSucceedsWithValue(ruid));
+ EXPECT_THAT(geteuid(), SyscallSucceedsWithValue(euid));
+}
+
+TEST(UidGidTest, Getgid) {
+ gid_t rgid, egid, sgid;
+ EXPECT_THAT(getresgid(&rgid, &egid, &sgid), SyscallSucceeds());
+ EXPECT_THAT(getgid(), SyscallSucceedsWithValue(rgid));
+ EXPECT_THAT(getegid(), SyscallSucceedsWithValue(egid));
+}
+
+TEST(UidGidTest, Getgroups) {
+ // "If size is zero, list is not modified, but the total number of
+ // supplementary group IDs for the process is returned." - getgroups(2)
+ int nr_groups;
+ ASSERT_THAT(nr_groups = getgroups(0, nullptr), SyscallSucceeds());
+ std::vector<gid_t> list(nr_groups);
+ EXPECT_THAT(getgroups(list.size(), list.data()), SyscallSucceeds());
+
+ // "EINVAL: size is less than the number of supplementary group IDs, but is
+ // not zero."
+ EXPECT_THAT(getgroups(-1, nullptr), SyscallFailsWithErrno(EINVAL));
+
+ // Testing for EFAULT requires actually having groups, which isn't guaranteed
+ // here; see the setgroups test below.
+}
+
+// If the caller's real/effective/saved user/group IDs are all 0, IsRoot returns
+// true. Otherwise IsRoot logs an explanatory message and returns false.
+PosixErrorOr<bool> IsRoot() {
+ uid_t ruid, euid, suid;
+ int rc = getresuid(&ruid, &euid, &suid);
+ MaybeSave();
+ if (rc < 0) {
+ return PosixError(errno, "getresuid");
+ }
+ if (ruid != 0 || euid != 0 || suid != 0) {
+ return false;
+ }
+ gid_t rgid, egid, sgid;
+ rc = getresgid(&rgid, &egid, &sgid);
+ MaybeSave();
+ if (rc < 0) {
+ return PosixError(errno, "getresgid");
+ }
+ if (rgid != 0 || egid != 0 || sgid != 0) {
+ return false;
+ }
+ return true;
+}
+
+// Checks that the calling process' real/effective/saved user IDs are
+// ruid/euid/suid respectively.
+PosixError CheckUIDs(uid_t ruid, uid_t euid, uid_t suid) {
+ uid_t actual_ruid, actual_euid, actual_suid;
+ int rc = getresuid(&actual_ruid, &actual_euid, &actual_suid);
+ MaybeSave();
+ if (rc < 0) {
+ return PosixError(errno, "getresuid");
+ }
+ if (ruid != actual_ruid || euid != actual_euid || suid != actual_suid) {
+ return PosixError(
+ EPERM, absl::StrCat(
+ "incorrect user IDs: got (",
+ absl::StrJoin({actual_ruid, actual_euid, actual_suid}, ", "),
+ ", wanted (", absl::StrJoin({ruid, euid, suid}, ", "), ")"));
+ }
+ return NoError();
+}
+
+PosixError CheckGIDs(gid_t rgid, gid_t egid, gid_t sgid) {
+ gid_t actual_rgid, actual_egid, actual_sgid;
+ int rc = getresgid(&actual_rgid, &actual_egid, &actual_sgid);
+ MaybeSave();
+ if (rc < 0) {
+ return PosixError(errno, "getresgid");
+ }
+ if (rgid != actual_rgid || egid != actual_egid || sgid != actual_sgid) {
+ return PosixError(
+ EPERM, absl::StrCat(
+ "incorrect group IDs: got (",
+ absl::StrJoin({actual_rgid, actual_egid, actual_sgid}, ", "),
+ ", wanted (", absl::StrJoin({rgid, egid, sgid}, ", "), ")"));
+ }
+ return NoError();
+}
+
+// N.B. These tests may break horribly unless run via a gVisor test runner,
+// because changing UID in one test may forfeit permissions required by other
+// tests. (The test runner runs each test in a separate process.)
+
+TEST(UidGidRootTest, Setuid) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot()));
+
+ // 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.
+ ScopedThread([&] {
+ // 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_setuid, -1), SyscallFailsWithErrno(EINVAL));
+
+ const uid_t uid = FLAGS_scratch_uid1;
+ EXPECT_THAT(syscall(SYS_setuid, uid), SyscallSucceeds());
+ // "If the effective UID of the caller is root (more precisely: if the
+ // caller has the CAP_SETUID capability), the real UID and saved set-user-ID
+ // are also set." - setuid(2)
+ EXPECT_NO_ERRNO(CheckUIDs(uid, uid, uid));
+ });
+}
+
+TEST(UidGidRootTest, Setgid) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot()));
+
+ EXPECT_THAT(setgid(-1), SyscallFailsWithErrno(EINVAL));
+
+ const gid_t gid = FLAGS_scratch_gid1;
+ ASSERT_THAT(setgid(gid), SyscallSucceeds());
+ EXPECT_NO_ERRNO(CheckGIDs(gid, gid, gid));
+}
+
+TEST(UidGidRootTest, SetgidNotFromThreadGroupLeader) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot()));
+
+ const gid_t gid = FLAGS_scratch_gid1;
+ // NOTE: 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));
+}
+
+TEST(UidGidRootTest, Setreuid) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot()));
+
+ // "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_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.
+ ScopedThread([&] {
+ const uid_t ruid = FLAGS_scratch_uid1;
+ const uid_t euid = 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)
+ EXPECT_NO_ERRNO(CheckUIDs(ruid, euid, euid));
+ });
+}
+
+TEST(UidGidRootTest, Setregid) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot()));
+
+ EXPECT_THAT(setregid(-1, -1), SyscallSucceeds());
+ EXPECT_NO_ERRNO(CheckGIDs(0, 0, 0));
+
+ const gid_t rgid = FLAGS_scratch_gid1;
+ const gid_t egid = FLAGS_scratch_gid2;
+ ASSERT_THAT(setregid(rgid, egid), SyscallSucceeds());
+ EXPECT_NO_ERRNO(CheckGIDs(rgid, egid, egid));
+}
+
+TEST(UidGidRootTest, Setresuid) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot()));
+
+ // "If one of the arguments equals -1, the corresponding value is not
+ // changed." - setresuid(2)
+ EXPECT_THAT(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.
+ 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.
+ EXPECT_THAT(syscall(SYS_setresuid, ruid, euid, suid), SyscallSucceeds());
+ EXPECT_NO_ERRNO(CheckUIDs(ruid, euid, suid));
+ });
+}
+
+TEST(UidGidRootTest, Setresgid) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot()));
+
+ EXPECT_THAT(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));
+}
+
+TEST(UidGidRootTest, Setgroups) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsRoot()));
+
+ std::vector<gid_t> list = {123, 500};
+ ASSERT_THAT(setgroups(list.size(), list.data()), SyscallSucceeds());
+ std::vector<gid_t> list2(list.size());
+ ASSERT_THAT(getgroups(list2.size(), list2.data()), SyscallSucceeds());
+ EXPECT_THAT(list, UnorderedElementsAreArray(list2));
+
+ // "EFAULT: list has an invalid address."
+ EXPECT_THAT(getgroups(100, reinterpret_cast<gid_t*>(-1)),
+ SyscallFailsWithErrno(EFAULT));
+}
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor