gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/syscalls/linux/chown.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/types.h>
    18  #include <unistd.h>
    19  
    20  #include <vector>
    21  
    22  #include "gmock/gmock.h"
    23  #include "gtest/gtest.h"
    24  #include "absl/flags/flag.h"
    25  #include "absl/synchronization/notification.h"
    26  #include "test/util/capability_util.h"
    27  #include "test/util/file_descriptor.h"
    28  #include "test/util/fs_util.h"
    29  #include "test/util/posix_error.h"
    30  #include "test/util/temp_path.h"
    31  #include "test/util/test_util.h"
    32  #include "test/util/thread_util.h"
    33  
    34  ABSL_FLAG(int32_t, scratch_uid1, 65534, "first scratch UID");
    35  ABSL_FLAG(int32_t, scratch_uid2, 65533, "second scratch UID");
    36  ABSL_FLAG(int32_t, scratch_gid, 65534, "first scratch GID");
    37  
    38  namespace gvisor {
    39  namespace testing {
    40  
    41  namespace {
    42  
    43  TEST(ChownTest, FchownBadF) {
    44    ASSERT_THAT(fchown(-1, 0, 0), SyscallFailsWithErrno(EBADF));
    45  }
    46  
    47  TEST(ChownTest, FchownatBadF) {
    48    ASSERT_THAT(fchownat(-1, "fff", 0, 0, 0), SyscallFailsWithErrno(EBADF));
    49  }
    50  
    51  TEST(ChownTest, FchownFileWithOpath) {
    52    auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
    53    FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH));
    54  
    55    ASSERT_THAT(fchown(fd.get(), geteuid(), getegid()),
    56                SyscallFailsWithErrno(EBADF));
    57  }
    58  
    59  TEST(ChownTest, FchownDirWithOpath) {
    60    const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
    61    const auto fd =
    62        ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY | O_PATH));
    63  
    64    ASSERT_THAT(fchown(fd.get(), geteuid(), getegid()),
    65                SyscallFailsWithErrno(EBADF));
    66  }
    67  
    68  TEST(ChownTest, FchownPipeFileSucceeds) {
    69    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_CHOWN)));
    70    const auto uid = absl::GetFlag(FLAGS_scratch_uid1);
    71    const auto gid = absl::GetFlag(FLAGS_scratch_gid);
    72    int fds[2];
    73    ASSERT_THAT(pipe2(fds, O_CLOEXEC), SyscallSucceeds());
    74    for (const auto& fd : fds) {
    75      ASSERT_THAT(fchown(fd, uid, gid), SyscallSucceeds());
    76      struct stat s = {};
    77      ASSERT_THAT(fstat(fd, &s), SyscallSucceeds());
    78      EXPECT_EQ(s.st_uid, uid);
    79      EXPECT_EQ(s.st_gid, gid);
    80    }
    81  }
    82  
    83  TEST(ChownTest, FchownPipeFileFails) {
    84    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID)));
    85    int fds[2];
    86    ASSERT_THAT(pipe2(fds, O_CLOEXEC), SyscallSucceeds());
    87    ScopedThread([&] {
    88      // Drop privileges.
    89      AutoCapability cap(CAP_CHOWN, false);
    90  
    91      // Change EUID and EGID.
    92      //
    93      // See note about POSIX below.
    94      EXPECT_THAT(
    95          syscall(SYS_setresgid, -1, absl::GetFlag(FLAGS_scratch_gid), -1),
    96          SyscallSucceeds());
    97      EXPECT_THAT(
    98          syscall(SYS_setresuid, -1, absl::GetFlag(FLAGS_scratch_uid1), -1),
    99          SyscallSucceeds());
   100      EXPECT_THAT(fchown(fds[1], geteuid(), getegid()),
   101                  SyscallFailsWithErrno(EPERM));
   102    });
   103  }
   104  
   105  TEST(ChownTest, FchownatWithOpath) {
   106    const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   107    auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
   108    const auto dirfd =
   109        ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY | O_PATH));
   110    ASSERT_THAT(
   111        fchownat(dirfd.get(), file.path().c_str(), geteuid(), getegid(), 0),
   112        SyscallSucceeds());
   113  }
   114  
   115  TEST(ChownTest, FchownatWithOpathEmtpyPath) {
   116    const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   117    const auto dirfd =
   118        ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY | O_PATH));
   119    ASSERT_THAT(fchownat(dirfd.get(), "", geteuid(), getegid(), AT_EMPTY_PATH),
   120                SyscallSucceeds());
   121  }
   122  
   123  TEST(ChownTest, FchownatEmptyPath) {
   124    const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   125    const auto fd =
   126        ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY | O_RDONLY));
   127    ASSERT_THAT(fchownat(fd.get(), "", 0, 0, 0), SyscallFailsWithErrno(ENOENT));
   128  }
   129  
   130  using Chown =
   131      std::function<PosixError(const std::string&, uid_t owner, gid_t group)>;
   132  
   133  class ChownParamTest : public ::testing::TestWithParam<Chown> {};
   134  
   135  TEST_P(ChownParamTest, ChownFileSucceeds) {
   136    AutoCapability cap(CAP_CHOWN, false);
   137  
   138    const auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
   139  
   140    // At least *try* setting to a group other than the EGID.
   141    gid_t gid;
   142    EXPECT_THAT(gid = getegid(), SyscallSucceeds());
   143    int num_groups;
   144    EXPECT_THAT(num_groups = getgroups(0, nullptr), SyscallSucceeds());
   145    if (num_groups > 0) {
   146      std::vector<gid_t> list(num_groups);
   147      EXPECT_THAT(getgroups(list.size(), list.data()), SyscallSucceeds());
   148      // Scan the list of groups for a valid gid. Note that if a group is not
   149      // defined in this local user namespace, then we will see 65534, and the
   150      // group will not chown below as expected. So only change if we find a
   151      // valid group in this list.
   152      for (const gid_t other_gid : list) {
   153        if (other_gid != 65534) {
   154          gid = other_gid;
   155          break;
   156        }
   157      }
   158    }
   159  
   160    EXPECT_NO_ERRNO(GetParam()(file.path(), geteuid(), gid));
   161  
   162    struct stat s = {};
   163    ASSERT_THAT(stat(file.path().c_str(), &s), SyscallSucceeds());
   164    EXPECT_EQ(s.st_uid, geteuid());
   165    EXPECT_EQ(s.st_gid, gid);
   166  }
   167  
   168  TEST_P(ChownParamTest, ChownFilePermissionDenied) {
   169    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID)));
   170  
   171    const auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0777));
   172    EXPECT_THAT(chmod(GetAbsoluteTestTmpdir().c_str(), 0777), SyscallSucceeds());
   173  
   174    // Drop privileges and change IDs only in child thread, or else this parent
   175    // thread won't be able to open some log files after the test ends.
   176    ScopedThread([&] {
   177      // Drop privileges.
   178      AutoCapability cap(CAP_CHOWN, false);
   179  
   180      // Change EUID and EGID.
   181      //
   182      // See note about POSIX below.
   183      EXPECT_THAT(
   184          syscall(SYS_setresgid, -1, absl::GetFlag(FLAGS_scratch_gid), -1),
   185          SyscallSucceeds());
   186      EXPECT_THAT(
   187          syscall(SYS_setresuid, -1, absl::GetFlag(FLAGS_scratch_uid1), -1),
   188          SyscallSucceeds());
   189  
   190      EXPECT_THAT(GetParam()(file.path(), geteuid(), getegid()),
   191                  PosixErrorIs(EPERM, ::testing::ContainsRegex("chown")));
   192    });
   193  }
   194  
   195  TEST_P(ChownParamTest, ChownFileSucceedsAsRoot) {
   196    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_CHOWN))));
   197    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_SETUID))));
   198  
   199    const std::string filename = NewTempAbsPath();
   200    EXPECT_THAT(chmod(GetAbsoluteTestTmpdir().c_str(), 0777), SyscallSucceeds());
   201  
   202    absl::Notification fileCreated, fileChowned;
   203    // Change UID only in child thread, or else this parent thread won't be able
   204    // to open some log files after the test ends.
   205    ScopedThread t([&] {
   206      // POSIX requires that all threads in a process share the same UIDs, so
   207      // the NPTL setresuid wrappers use signals to make all threads execute the
   208      // setresuid syscall. However, we want this thread to have its own set of
   209      // credentials different from the parent process, so we use the raw
   210      // syscall.
   211      EXPECT_THAT(
   212          syscall(SYS_setresuid, -1, absl::GetFlag(FLAGS_scratch_uid2), -1),
   213          SyscallSucceeds());
   214  
   215      // Create file and immediately close it.
   216      FileDescriptor fd =
   217          ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_CREAT | O_RDWR, 0644));
   218      fd.reset();  // Close the fd.
   219  
   220      fileCreated.Notify();
   221      fileChowned.WaitForNotification();
   222  
   223      EXPECT_THAT(open(filename.c_str(), O_RDWR), SyscallFailsWithErrno(EACCES));
   224      FileDescriptor fd2 = ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_RDONLY));
   225    });
   226  
   227    fileCreated.WaitForNotification();
   228  
   229    // Set file's owners to someone different.
   230    EXPECT_NO_ERRNO(GetParam()(filename, absl::GetFlag(FLAGS_scratch_uid1),
   231                               absl::GetFlag(FLAGS_scratch_gid)));
   232  
   233    struct stat s;
   234    EXPECT_THAT(stat(filename.c_str(), &s), SyscallSucceeds());
   235    EXPECT_EQ(s.st_uid, absl::GetFlag(FLAGS_scratch_uid1));
   236    EXPECT_EQ(s.st_gid, absl::GetFlag(FLAGS_scratch_gid));
   237  
   238    fileChowned.Notify();
   239  }
   240  
   241  PosixError errorFromReturn(const std::string& name, int ret) {
   242    if (ret == -1) {
   243      return PosixError(errno, absl::StrCat(name, " failed"));
   244    }
   245    return NoError();
   246  }
   247  
   248  INSTANTIATE_TEST_SUITE_P(
   249      ChownKinds, ChownParamTest,
   250      ::testing::Values(
   251          [](const std::string& path, uid_t owner, gid_t group) -> PosixError {
   252            int rc = chown(path.c_str(), owner, group);
   253            MaybeSave();
   254            return errorFromReturn("chown", rc);
   255          },
   256          [](const std::string& path, uid_t owner, gid_t group) -> PosixError {
   257            int rc = lchown(path.c_str(), owner, group);
   258            MaybeSave();
   259            return errorFromReturn("lchown", rc);
   260          },
   261          [](const std::string& path, uid_t owner, gid_t group) -> PosixError {
   262            ASSIGN_OR_RETURN_ERRNO(auto fd, Open(path, O_RDWR));
   263            int rc = fchown(fd.get(), owner, group);
   264            MaybeSave();
   265            return errorFromReturn("fchown", rc);
   266          },
   267          [](const std::string& path, uid_t owner, gid_t group) -> PosixError {
   268            ASSIGN_OR_RETURN_ERRNO(auto fd, Open(path, O_RDWR));
   269            int rc = fchownat(fd.get(), "", owner, group, AT_EMPTY_PATH);
   270            MaybeSave();
   271            return errorFromReturn("fchownat-fd", rc);
   272          },
   273          [](const std::string& path, uid_t owner, gid_t group) -> PosixError {
   274            ASSIGN_OR_RETURN_ERRNO(auto dirfd, Open(std::string(Dirname(path)),
   275                                                    O_DIRECTORY | O_RDONLY));
   276            int rc = fchownat(dirfd.get(), std::string(Basename(path)).c_str(),
   277                              owner, group, 0);
   278            MaybeSave();
   279            return errorFromReturn("fchownat-dirfd", rc);
   280          },
   281          [](const std::string& path, uid_t owner, gid_t group) -> PosixError {
   282            ASSIGN_OR_RETURN_ERRNO(auto dirfd, Open(std::string(Dirname(path)),
   283                                                    O_DIRECTORY | O_PATH));
   284            int rc = fchownat(dirfd.get(), std::string(Basename(path)).c_str(),
   285                              owner, group, 0);
   286            MaybeSave();
   287            return errorFromReturn("fchownat-opathdirfd", rc);
   288          }));
   289  
   290  }  // namespace
   291  
   292  }  // namespace testing
   293  }  // namespace gvisor