gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/syscalls/linux/prctl_setuid.cc (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  #include <sched.h>
    16  #include <sys/prctl.h>
    17  
    18  #include <string>
    19  
    20  #include "gtest/gtest.h"
    21  #include "absl/flags/flag.h"
    22  #include "test/util/capability_util.h"
    23  #include "test/util/logging.h"
    24  #include "test/util/multiprocess_util.h"
    25  #include "test/util/posix_error.h"
    26  #include "test/util/test_util.h"
    27  #include "test/util/thread_util.h"
    28  
    29  ABSL_FLAG(int32_t, scratch_uid, 65534, "scratch UID");
    30  // This flag is used to verify that after an exec PR_GET_KEEPCAPS
    31  // returns 0, the return code will be offset by kPrGetKeepCapsExitBase.
    32  ABSL_FLAG(bool, prctl_pr_get_keepcaps, false,
    33            "If true the test will verify that prctl with pr_get_keepcaps"
    34            "returns 0. The test will exit with the result of that check.");
    35  
    36  // These tests exist seperately from prctl because we need to start
    37  // them as root. Setuid() has the behavior that permissions are fully
    38  // removed if one of the UIDs were 0 before a setuid() call. This
    39  // behavior can be changed by using PR_SET_KEEPCAPS and that is what
    40  // is tested here.
    41  //
    42  // Reference setuid(2):
    43  // The setuid() function checks the effective user ID of
    44  // the caller and if it is the superuser, all process-related user ID's
    45  // are set to uid.  After this has occurred, it is impossible for the
    46  // program to regain root privileges.
    47  //
    48  // Thus, a set-user-ID-root program wishing to temporarily drop root
    49  // privileges, assume the identity of an unprivileged user, and then
    50  // regain root privileges afterward cannot use setuid().  You can
    51  // accomplish this with seteuid(2).
    52  namespace gvisor {
    53  namespace testing {
    54  
    55  // Offset added to exit code from test child to distinguish from other abnormal
    56  // exits.
    57  constexpr int kPrGetKeepCapsExitBase = 100;
    58  
    59  namespace {
    60  
    61  class PrctlKeepCapsSetuidTest : public ::testing::Test {
    62   protected:
    63    void SetUp() override {
    64      // PR_GET_KEEPCAPS will only return 0 or 1 (on success).
    65      ASSERT_THAT(original_keepcaps_ = prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0),
    66                  SyscallSucceeds());
    67      ASSERT_TRUE(original_keepcaps_ == 0 || original_keepcaps_ == 1);
    68    }
    69  
    70    void TearDown() override {
    71      // Restore PR_SET_KEEPCAPS.
    72      ASSERT_THAT(prctl(PR_SET_KEEPCAPS, original_keepcaps_, 0, 0, 0),
    73                  SyscallSucceeds());
    74  
    75      // Verify that it was restored.
    76      ASSERT_THAT(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0),
    77                  SyscallSucceedsWithValue(original_keepcaps_));
    78    }
    79  
    80    // The original keep caps value exposed so tests can use it if they need.
    81    int original_keepcaps_ = 0;
    82  };
    83  
    84  // This test will verify that a bad value, eg. not 0 or 1 for
    85  // PR_SET_KEEPCAPS will return EINVAL as required by prctl(2).
    86  TEST_F(PrctlKeepCapsSetuidTest, PrctlBadArgsToKeepCaps) {
    87    ASSERT_THAT(prctl(PR_SET_KEEPCAPS, 2, 0, 0, 0),
    88                SyscallFailsWithErrno(EINVAL));
    89  }
    90  
    91  // This test will verify that a setuid(2) without PR_SET_KEEPCAPS will cause
    92  // all capabilities to be dropped.
    93  TEST_F(PrctlKeepCapsSetuidTest, SetUidNoKeepCaps) {
    94    // getuid(2) never fails.
    95    if (getuid() != 0) {
    96      SKIP_IF(!IsRunningOnGvisor());
    97      FAIL() << "User is not root on gvisor platform.";
    98    }
    99  
   100    // Do setuid in a separate thread so that after finishing this test, the
   101    // process can still open files the test harness created before starting
   102    // this test. Otherwise, the files are created by root (UID before the
   103    // test), but cannot be opened by the `uid` set below after the test. After
   104    // calling setuid(non-zero-UID), there is no way to get root privileges
   105    // back.
   106    ScopedThread([] {
   107      // Start by verifying we have a capability.
   108      TEST_CHECK(HaveCapability(CAP_SYS_ADMIN).ValueOrDie());
   109  
   110      // Verify that PR_GET_KEEPCAPS is disabled.
   111      ASSERT_THAT(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0),
   112                  SyscallSucceedsWithValue(0));
   113  
   114      // Use syscall instead of glibc setuid wrapper because we want this setuid
   115      // call to only apply to this task. POSIX threads, however, require that
   116      // all threads have the same UIDs, so using the setuid wrapper sets all
   117      // threads' real UID.
   118      EXPECT_THAT(syscall(SYS_setuid, absl::GetFlag(FLAGS_scratch_uid)),
   119                  SyscallSucceeds());
   120  
   121      // Verify that we changed uid.
   122      EXPECT_THAT(getuid(),
   123                  SyscallSucceedsWithValue(absl::GetFlag(FLAGS_scratch_uid)));
   124  
   125      // Verify we lost the capability in the effective set, this always happens.
   126      TEST_CHECK(!HaveCapability(CAP_SYS_ADMIN).ValueOrDie());
   127  
   128      // We should have also lost it in the permitted set by the setuid() so
   129      // SetCapability should fail when we try to add it back to the effective set
   130      ASSERT_FALSE(SetCapability(CAP_SYS_ADMIN, true).ok());
   131    });
   132  }
   133  
   134  // This test will verify that a setuid with PR_SET_KEEPCAPS will cause
   135  // capabilities to be retained after we switch away from the root user.
   136  TEST_F(PrctlKeepCapsSetuidTest, SetUidKeepCaps) {
   137    // getuid(2) never fails.
   138    if (getuid() != 0) {
   139      SKIP_IF(!IsRunningOnGvisor());
   140      FAIL() << "User is not root on gvisor platform.";
   141    }
   142  
   143    // Do setuid in a separate thread so that after finishing this test, the
   144    // process can still open files the test harness created before starting
   145    // this test. Otherwise, the files are created by root (UID before the
   146    // test), but cannot be opened by the `uid` set below after the test. After
   147    // calling setuid(non-zero-UID), there is no way to get root privileges
   148    // back.
   149    ScopedThread([] {
   150      // Start by verifying we have a capability.
   151      TEST_CHECK(HaveCapability(CAP_SYS_ADMIN).ValueOrDie());
   152  
   153      // Set PR_SET_KEEPCAPS.
   154      ASSERT_THAT(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0), SyscallSucceeds());
   155  
   156      // Verify PR_SET_KEEPCAPS was set before we proceed.
   157      ASSERT_THAT(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0),
   158                  SyscallSucceedsWithValue(1));
   159  
   160      // Use syscall instead of glibc setuid wrapper because we want this setuid
   161      // call to only apply to this task. POSIX threads, however, require that
   162      // all threads have the same UIDs, so using the setuid wrapper sets all
   163      // threads' real UID.
   164      EXPECT_THAT(syscall(SYS_setuid, absl::GetFlag(FLAGS_scratch_uid)),
   165                  SyscallSucceeds());
   166  
   167      // Verify that we changed uid.
   168      EXPECT_THAT(getuid(),
   169                  SyscallSucceedsWithValue(absl::GetFlag(FLAGS_scratch_uid)));
   170  
   171      // Verify we lost the capability in the effective set, this always happens.
   172      TEST_CHECK(!HaveCapability(CAP_SYS_ADMIN).ValueOrDie());
   173  
   174      // We lost the capability in the effective set, but it will still
   175      // exist in the permitted set so we can elevate the capability.
   176      ASSERT_NO_ERRNO(SetCapability(CAP_SYS_ADMIN, true));
   177  
   178      // Verify we got back the capability in the effective set.
   179      TEST_CHECK(HaveCapability(CAP_SYS_ADMIN).ValueOrDie());
   180    });
   181  }
   182  
   183  // This test will verify that PR_SET_KEEPCAPS is not retained
   184  // across an execve. According to prctl(2):
   185  // "The "keep capabilities" value will  be reset to 0 on subsequent
   186  // calls to execve(2)."
   187  TEST_F(PrctlKeepCapsSetuidTest, NoKeepCapsAfterExec) {
   188    ASSERT_THAT(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0), SyscallSucceeds());
   189  
   190    // Verify PR_SET_KEEPCAPS was set before we proceed.
   191    ASSERT_THAT(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0), SyscallSucceedsWithValue(1));
   192  
   193    pid_t child_pid = -1;
   194    int execve_errno = 0;
   195    // Do an exec and then verify that PR_GET_KEEPCAPS returns 0
   196    // see the body of main below.
   197    auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec(
   198        "/proc/self/exe", {"/proc/self/exe", "--prctl_pr_get_keepcaps"}, {},
   199        nullptr, &child_pid, &execve_errno));
   200  
   201    ASSERT_GT(child_pid, 0);
   202    ASSERT_EQ(execve_errno, 0);
   203  
   204    int status = 0;
   205    ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
   206    ASSERT_TRUE(WIFEXITED(status));
   207    // PR_SET_KEEPCAPS should have been cleared by the exec.
   208    // Success should return gvisor::testing::kPrGetKeepCapsExitBase + 0
   209    ASSERT_EQ(WEXITSTATUS(status), kPrGetKeepCapsExitBase);
   210  }
   211  
   212  TEST_F(PrctlKeepCapsSetuidTest, NoKeepCapsAfterNewUserNamespace) {
   213    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace()));
   214  
   215    // Fork to avoid changing the user namespace of the original test process.
   216    pid_t const child_pid = fork();
   217  
   218    if (child_pid == 0) {
   219      // Verify that the keepcaps flag is set to 0 when we change user namespaces.
   220      TEST_PCHECK(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == 0);
   221      MaybeSave();
   222  
   223      TEST_PCHECK(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0) == 1);
   224      MaybeSave();
   225  
   226      TEST_PCHECK(unshare(CLONE_NEWUSER) == 0);
   227      MaybeSave();
   228  
   229      TEST_PCHECK(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0) == 0);
   230      MaybeSave();
   231  
   232      _exit(0);
   233    }
   234  
   235    int status;
   236    ASSERT_THAT(child_pid, SyscallSucceeds());
   237    ASSERT_THAT(waitpid(child_pid, &status, 0),
   238                SyscallSucceedsWithValue(child_pid));
   239    EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
   240        << "status = " << status;
   241  }
   242  
   243  // This test will verify that PR_SET_KEEPCAPS and PR_GET_KEEPCAPS work correctly
   244  TEST_F(PrctlKeepCapsSetuidTest, PrGetKeepCaps) {
   245    // Set PR_SET_KEEPCAPS to the negation of the original.
   246    ASSERT_THAT(prctl(PR_SET_KEEPCAPS, !original_keepcaps_, 0, 0, 0),
   247                SyscallSucceeds());
   248  
   249    // Verify it was set.
   250    ASSERT_THAT(prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0),
   251                SyscallSucceedsWithValue(!original_keepcaps_));
   252  }
   253  
   254  }  // namespace
   255  
   256  }  // namespace testing
   257  }  // namespace gvisor
   258  
   259  int main(int argc, char** argv) {
   260    gvisor::testing::TestInit(&argc, &argv);
   261  
   262    if (absl::GetFlag(FLAGS_prctl_pr_get_keepcaps)) {
   263      return gvisor::testing::kPrGetKeepCapsExitBase +
   264             prctl(PR_GET_KEEPCAPS, 0, 0, 0, 0);
   265    }
   266  
   267    return gvisor::testing::RunAllTests();
   268  }