// Copyright 2018 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 <sched.h> #include <sys/prctl.h> #include <string> #include "gtest/gtest.h" #include "absl/flags/flag.h" #include "test/util/capability_util.h" #include "test/util/logging.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" ABSL_FLAG(int32_t, scratch_uid, 65534, "scratch UID"); // This flag is used to verify that after an exec PR_GET_KEEPCAPS // returns 0, the return code will be offset by kPrGetKeepCapsExitBase. ABSL_FLAG(bool, prctl_pr_get_keepcaps, false, "If true the test will verify that prctl with pr_get_keepcaps" "returns 0. The test will exit with the result of that check."); // These tests exist seperately from prctl because we need to start // them as root. Setuid() has the behavior that permissions are fully // removed if one of the UIDs were 0 before a setuid() call. This // behavior can be changed by using PR_SET_KEEPCAPS and that is what // is tested here. // // Reference setuid(2): // The setuid() function checks the effective user ID of // the caller and if it is the superuser, all process-related user ID's // are set to uid. After this has occurred, it is impossible for the // program to regain root privileges. // // Thus, a set-user-ID-root program wishing to temporarily drop root // privileges, assume the identity of an unprivileged user, and then // regain root privileges afterward cannot use setuid(). You can // accomplish this with seteuid(2). namespace gvisor { namespace testing { // Offset added to exit code from test child to distinguish from other abnormal // exits. constexpr int kPrGetKeepCapsExitBase = 100; namespace { class PrctlKeepCapsSetuidTest : public ::testing::Test { protected: void SetUp() override { // PR_GET_KEEPCAPS will only return 0 or 1 (on success). ASSERT_THAT(original_keepcaps_ = prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0), SyscallSucceeds()); ASSERT_TRUE(original_keepcaps_ == 0 || original_keepcaps_ == 1); } void TearDown() override { // Restore PR_SET_KEEPCAPS. ASSERT_THAT(prctl(PR_SET_KEEPCAPS, original_keepcaps_, 0, 0, 0), SyscallSucceeds()); // Verify that it was restored. ASSERT_THAT(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0), SyscallSucceedsWithValue(original_keepcaps_)); } // The original keep caps value exposed so tests can use it if they need. int original_keepcaps_ = 0; }; // This test will verify that a bad value, eg. not 0 or 1 for // PR_SET_KEEPCAPS will return EINVAL as required by prctl(2). TEST_F(PrctlKeepCapsSetuidTest, PrctlBadArgsToKeepCaps) { ASSERT_THAT(prctl(PR_SET_KEEPCAPS, 2, 0, 0, 0), SyscallFailsWithErrno(EINVAL)); } // This test will verify that a setuid(2) without PR_SET_KEEPCAPS will cause // all capabilities to be dropped. TEST_F(PrctlKeepCapsSetuidTest, SetUidNoKeepCaps) { // getuid(2) never fails. if (getuid() != 0) { SKIP_IF(!IsRunningOnGvisor()); FAIL() << "User is not root on gvisor platform."; } // 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([] { // Start by verifying we have a capability. TEST_CHECK(HaveCapability(CAP_SYS_ADMIN).ValueOrDie()); // Verify that PR_GET_KEEPCAPS is disabled. ASSERT_THAT(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0), SyscallSucceedsWithValue(0)); // 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, absl::GetFlag(FLAGS_scratch_uid)), SyscallSucceeds()); // Verify that we changed uid. EXPECT_THAT(getuid(), SyscallSucceedsWithValue(absl::GetFlag(FLAGS_scratch_uid))); // Verify we lost the capability in the effective set, this always happens. TEST_CHECK(!HaveCapability(CAP_SYS_ADMIN).ValueOrDie()); // We should have also lost it in the permitted set by the setuid() so // SetCapability should fail when we try to add it back to the effective set ASSERT_FALSE(SetCapability(CAP_SYS_ADMIN, true).ok()); }); } // This test will verify that a setuid with PR_SET_KEEPCAPS will cause // capabilities to be retained after we switch away from the root user. TEST_F(PrctlKeepCapsSetuidTest, SetUidKeepCaps) { // getuid(2) never fails. if (getuid() != 0) { SKIP_IF(!IsRunningOnGvisor()); FAIL() << "User is not root on gvisor platform."; } // 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([] { // Start by verifying we have a capability. TEST_CHECK(HaveCapability(CAP_SYS_ADMIN).ValueOrDie()); // Set PR_SET_KEEPCAPS. ASSERT_THAT(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0), SyscallSucceeds()); // Verify PR_SET_KEEPCAPS was set before we proceed. ASSERT_THAT(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0), SyscallSucceedsWithValue(1)); // 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, absl::GetFlag(FLAGS_scratch_uid)), SyscallSucceeds()); // Verify that we changed uid. EXPECT_THAT(getuid(), SyscallSucceedsWithValue(absl::GetFlag(FLAGS_scratch_uid))); // Verify we lost the capability in the effective set, this always happens. TEST_CHECK(!HaveCapability(CAP_SYS_ADMIN).ValueOrDie()); // We lost the capability in the effective set, but it will still // exist in the permitted set so we can elevate the capability. ASSERT_NO_ERRNO(SetCapability(CAP_SYS_ADMIN, true)); // Verify we got back the capability in the effective set. TEST_CHECK(HaveCapability(CAP_SYS_ADMIN).ValueOrDie()); }); } // This test will verify that PR_SET_KEEPCAPS is not retained // across an execve. According to prctl(2): // "The "keep capabilities" value will be reset to 0 on subsequent // calls to execve(2)." TEST_F(PrctlKeepCapsSetuidTest, NoKeepCapsAfterExec) { ASSERT_THAT(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0), SyscallSucceeds()); // Verify PR_SET_KEEPCAPS was set before we proceed. ASSERT_THAT(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0), SyscallSucceedsWithValue(1)); pid_t child_pid = -1; int execve_errno = 0; // Do an exec and then verify that PR_GET_KEEPCAPS returns 0 // see the body of main below. auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec( "/proc/self/exe", {"/proc/self/exe", "--prctl_pr_get_keepcaps"}, {}, nullptr, &child_pid, &execve_errno)); ASSERT_GT(child_pid, 0); ASSERT_EQ(execve_errno, 0); int status = 0; ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds()); ASSERT_TRUE(WIFEXITED(status)); // PR_SET_KEEPCAPS should have been cleared by the exec. // Success should return gvisor::testing::kPrGetKeepCapsExitBase + 0 ASSERT_EQ(WEXITSTATUS(status), kPrGetKeepCapsExitBase); } TEST_F(PrctlKeepCapsSetuidTest, NoKeepCapsAfterNewUserNamespace) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace())); // Fork to avoid changing the user namespace of the original test process. pid_t const child_pid = fork(); if (child_pid == 0) { // Verify that the keepcaps flag is set to 0 when we change user namespaces. TEST_PCHECK(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == 0); MaybeSave(); TEST_PCHECK(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0) == 1); MaybeSave(); TEST_PCHECK(unshare(CLONE_NEWUSER) == 0); MaybeSave(); TEST_PCHECK(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0) == 0); MaybeSave(); _exit(0); } int status; ASSERT_THAT(child_pid, SyscallSucceeds()); ASSERT_THAT(waitpid(child_pid, &status, 0), SyscallSucceedsWithValue(child_pid)); EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) << "status = " << status; } // This test will verify that PR_SET_KEEPCAPS and PR_GET_KEEPCAPS work correctly TEST_F(PrctlKeepCapsSetuidTest, PrGetKeepCaps) { // Set PR_SET_KEEPCAPS to the negation of the original. ASSERT_THAT(prctl(PR_SET_KEEPCAPS, !original_keepcaps_, 0, 0, 0), SyscallSucceeds()); // Verify it was set. ASSERT_THAT(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0), SyscallSucceedsWithValue(!original_keepcaps_)); } } // namespace } // namespace testing } // namespace gvisor int main(int argc, char** argv) { gvisor::testing::TestInit(&argc, &argv); if (absl::GetFlag(FLAGS_prctl_pr_get_keepcaps)) { return gvisor::testing::kPrGetKeepCapsExitBase + prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0); } return gvisor::testing::RunAllTests(); }