gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/syscalls/linux/sticky.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 <fcntl.h>
    16  #include <grp.h>
    17  #include <sys/prctl.h>
    18  #include <sys/types.h>
    19  #include <unistd.h>
    20  
    21  #include <vector>
    22  
    23  #include "gtest/gtest.h"
    24  #include "absl/flags/flag.h"
    25  #include "test/util/capability_util.h"
    26  #include "test/util/file_descriptor.h"
    27  #include "test/util/fs_util.h"
    28  #include "test/util/temp_path.h"
    29  #include "test/util/test_util.h"
    30  #include "test/util/thread_util.h"
    31  
    32  ABSL_FLAG(int32_t, scratch_uid, 65534, "first scratch UID");
    33  ABSL_FLAG(int32_t, scratch_gid, 65534, "first scratch GID");
    34  
    35  namespace gvisor {
    36  namespace testing {
    37  
    38  namespace {
    39  
    40  TEST(StickyTest, StickyBitPermDenied) {
    41    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID)));
    42  
    43    const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
    44    EXPECT_THAT(chmod(parent.path().c_str(), 0777 | S_ISVTX), SyscallSucceeds());
    45  
    46    // After changing credentials below, we need to use an open fd to make
    47    // modifications in the parent dir, because there is no guarantee that we will
    48    // still have the ability to open it.
    49    const FileDescriptor parent_fd =
    50        ASSERT_NO_ERRNO_AND_VALUE(Open(parent.path(), O_DIRECTORY));
    51    ASSERT_THAT(openat(parent_fd.get(), "file", O_CREAT), SyscallSucceeds());
    52    ASSERT_THAT(mkdirat(parent_fd.get(), "dir", 0777), SyscallSucceeds());
    53    ASSERT_THAT(symlinkat("xyz", parent_fd.get(), "link"), SyscallSucceeds());
    54  
    55    // Drop privileges and change IDs only in child thread, or else this parent
    56    // thread won't be able to open some log files after the test ends.
    57    ScopedThread([&] {
    58      // Drop privileges.
    59      AutoCapability cap(CAP_FOWNER, false);
    60  
    61      // Change EUID and EGID.
    62      EXPECT_THAT(
    63          syscall(SYS_setresgid, -1, absl::GetFlag(FLAGS_scratch_gid), -1),
    64          SyscallSucceeds());
    65      EXPECT_THAT(
    66          syscall(SYS_setresuid, -1, absl::GetFlag(FLAGS_scratch_uid), -1),
    67          SyscallSucceeds());
    68  
    69      EXPECT_THAT(renameat(parent_fd.get(), "file", parent_fd.get(), "file2"),
    70                  SyscallFailsWithErrno(EPERM));
    71      EXPECT_THAT(unlinkat(parent_fd.get(), "file", 0),
    72                  SyscallFailsWithErrno(EPERM));
    73      EXPECT_THAT(unlinkat(parent_fd.get(), "dir", AT_REMOVEDIR),
    74                  SyscallFailsWithErrno(EPERM));
    75      EXPECT_THAT(unlinkat(parent_fd.get(), "link", 0),
    76                  SyscallFailsWithErrno(EPERM));
    77    });
    78  }
    79  
    80  TEST(StickyTest, StickyBitSameUID) {
    81    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID)));
    82  
    83    const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
    84    EXPECT_THAT(chmod(parent.path().c_str(), 0777 | S_ISVTX), SyscallSucceeds());
    85  
    86    // After changing credentials below, we need to use an open fd to make
    87    // modifications in the parent dir, because there is no guarantee that we will
    88    // still have the ability to open it.
    89    const FileDescriptor parent_fd =
    90        ASSERT_NO_ERRNO_AND_VALUE(Open(parent.path(), O_DIRECTORY));
    91    ASSERT_THAT(openat(parent_fd.get(), "file", O_CREAT), SyscallSucceeds());
    92    ASSERT_THAT(mkdirat(parent_fd.get(), "dir", 0777), SyscallSucceeds());
    93    ASSERT_THAT(symlinkat("xyz", parent_fd.get(), "link"), SyscallSucceeds());
    94  
    95    // Drop privileges and change IDs only in child thread, or else this parent
    96    // thread won't be able to open some log files after the test ends.
    97    ScopedThread([&] {
    98      // Drop privileges.
    99      AutoCapability cap(CAP_FOWNER, false);
   100  
   101      // Change EGID.
   102      EXPECT_THAT(
   103          syscall(SYS_setresgid, -1, absl::GetFlag(FLAGS_scratch_gid), -1),
   104          SyscallSucceeds());
   105  
   106      // We still have the same EUID.
   107      EXPECT_THAT(renameat(parent_fd.get(), "file", parent_fd.get(), "file2"),
   108                  SyscallSucceeds());
   109      EXPECT_THAT(unlinkat(parent_fd.get(), "file2", 0), SyscallSucceeds());
   110      EXPECT_THAT(unlinkat(parent_fd.get(), "dir", AT_REMOVEDIR),
   111                  SyscallSucceeds());
   112      EXPECT_THAT(unlinkat(parent_fd.get(), "link", 0), SyscallSucceeds());
   113    });
   114  }
   115  
   116  TEST(StickyTest, StickyBitCapFOWNER) {
   117    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID)));
   118  
   119    const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   120    EXPECT_THAT(chmod(parent.path().c_str(), 0777 | S_ISVTX), SyscallSucceeds());
   121  
   122    // After changing credentials below, we need to use an open fd to make
   123    // modifications in the parent dir, because there is no guarantee that we will
   124    // still have the ability to open it.
   125    const FileDescriptor parent_fd =
   126        ASSERT_NO_ERRNO_AND_VALUE(Open(parent.path(), O_DIRECTORY));
   127    ASSERT_THAT(openat(parent_fd.get(), "file", O_CREAT), SyscallSucceeds());
   128    ASSERT_THAT(mkdirat(parent_fd.get(), "dir", 0777), SyscallSucceeds());
   129    ASSERT_THAT(symlinkat("xyz", parent_fd.get(), "link"), SyscallSucceeds());
   130  
   131    // Drop privileges and change IDs only in child thread, or else this parent
   132    // thread won't be able to open some log files after the test ends.
   133    ScopedThread([&] {
   134      // Set PR_SET_KEEPCAPS.
   135      EXPECT_THAT(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0), SyscallSucceeds());
   136  
   137      // Change EUID and EGID.
   138      EXPECT_THAT(
   139          syscall(SYS_setresgid, -1, absl::GetFlag(FLAGS_scratch_gid), -1),
   140          SyscallSucceeds());
   141      EXPECT_THAT(
   142          syscall(SYS_setresuid, -1, absl::GetFlag(FLAGS_scratch_uid), -1),
   143          SyscallSucceeds());
   144  
   145      EXPECT_NO_ERRNO(SetCapability(CAP_FOWNER, true));
   146      EXPECT_THAT(renameat(parent_fd.get(), "file", parent_fd.get(), "file2"),
   147                  SyscallSucceeds());
   148      EXPECT_THAT(unlinkat(parent_fd.get(), "file2", 0), SyscallSucceeds());
   149      EXPECT_THAT(unlinkat(parent_fd.get(), "dir", AT_REMOVEDIR),
   150                  SyscallSucceeds());
   151      EXPECT_THAT(unlinkat(parent_fd.get(), "link", 0), SyscallSucceeds());
   152    });
   153  }
   154  }  // namespace
   155  
   156  }  // namespace testing
   157  }  // namespace gvisor