github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/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    SKIP_IF(IsRunningWithVFS1());
    53    auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
    54    FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_PATH));
    55  
    56    ASSERT_THAT(fchown(fd.get(), geteuid(), getegid()),
    57                SyscallFailsWithErrno(EBADF));
    58  }
    59  
    60  TEST(ChownTest, FchownDirWithOpath) {
    61    SKIP_IF(IsRunningWithVFS1());
    62    const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
    63    const auto fd =
    64        ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY | O_PATH));
    65  
    66    ASSERT_THAT(fchown(fd.get(), geteuid(), getegid()),
    67                SyscallFailsWithErrno(EBADF));
    68  }
    69  
    70  TEST(ChownTest, FchownatWithOpath) {
    71    SKIP_IF(IsRunningWithVFS1());
    72    const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
    73    auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
    74    const auto dirfd =
    75        ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY | O_PATH));
    76    ASSERT_THAT(
    77        fchownat(dirfd.get(), file.path().c_str(), geteuid(), getegid(), 0),
    78        SyscallSucceeds());
    79  }
    80  
    81  TEST(ChownTest, FchownatEmptyPath) {
    82    const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
    83    const auto fd =
    84        ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY | O_RDONLY));
    85    ASSERT_THAT(fchownat(fd.get(), "", 0, 0, 0), SyscallFailsWithErrno(ENOENT));
    86  }
    87  
    88  using Chown =
    89      std::function<PosixError(const std::string&, uid_t owner, gid_t group)>;
    90  
    91  class ChownParamTest : public ::testing::TestWithParam<Chown> {};
    92  
    93  TEST_P(ChownParamTest, ChownFileSucceeds) {
    94    AutoCapability cap(CAP_CHOWN, false);
    95  
    96    const auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
    97  
    98    // At least *try* setting to a group other than the EGID.
    99    gid_t gid;
   100    EXPECT_THAT(gid = getegid(), SyscallSucceeds());
   101    int num_groups;
   102    EXPECT_THAT(num_groups = getgroups(0, nullptr), SyscallSucceeds());
   103    if (num_groups > 0) {
   104      std::vector<gid_t> list(num_groups);
   105      EXPECT_THAT(getgroups(list.size(), list.data()), SyscallSucceeds());
   106      // Scan the list of groups for a valid gid. Note that if a group is not
   107      // defined in this local user namespace, then we will see 65534, and the
   108      // group will not chown below as expected. So only change if we find a
   109      // valid group in this list.
   110      for (const gid_t other_gid : list) {
   111        if (other_gid != 65534) {
   112          gid = other_gid;
   113          break;
   114        }
   115      }
   116    }
   117  
   118    EXPECT_NO_ERRNO(GetParam()(file.path(), geteuid(), gid));
   119  
   120    struct stat s = {};
   121    ASSERT_THAT(stat(file.path().c_str(), &s), SyscallSucceeds());
   122    EXPECT_EQ(s.st_uid, geteuid());
   123    EXPECT_EQ(s.st_gid, gid);
   124  }
   125  
   126  TEST_P(ChownParamTest, ChownFilePermissionDenied) {
   127    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID)));
   128  
   129    const auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0777));
   130    EXPECT_THAT(chmod(GetAbsoluteTestTmpdir().c_str(), 0777), SyscallSucceeds());
   131  
   132    // Drop privileges and change IDs only in child thread, or else this parent
   133    // thread won't be able to open some log files after the test ends.
   134    ScopedThread([&] {
   135      // Drop privileges.
   136      AutoCapability cap(CAP_CHOWN, false);
   137  
   138      // Change EUID and EGID.
   139      //
   140      // See note about POSIX below.
   141      EXPECT_THAT(
   142          syscall(SYS_setresgid, -1, absl::GetFlag(FLAGS_scratch_gid), -1),
   143          SyscallSucceeds());
   144      EXPECT_THAT(
   145          syscall(SYS_setresuid, -1, absl::GetFlag(FLAGS_scratch_uid1), -1),
   146          SyscallSucceeds());
   147  
   148      EXPECT_THAT(GetParam()(file.path(), geteuid(), getegid()),
   149                  PosixErrorIs(EPERM, ::testing::ContainsRegex("chown")));
   150    });
   151  }
   152  
   153  TEST_P(ChownParamTest, ChownFileSucceedsAsRoot) {
   154    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_CHOWN))));
   155    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_SETUID))));
   156  
   157    const std::string filename = NewTempAbsPath();
   158    EXPECT_THAT(chmod(GetAbsoluteTestTmpdir().c_str(), 0777), SyscallSucceeds());
   159  
   160    absl::Notification fileCreated, fileChowned;
   161    // Change UID only in child thread, or else this parent thread won't be able
   162    // to open some log files after the test ends.
   163    ScopedThread t([&] {
   164      // POSIX requires that all threads in a process share the same UIDs, so
   165      // the NPTL setresuid wrappers use signals to make all threads execute the
   166      // setresuid syscall. However, we want this thread to have its own set of
   167      // credentials different from the parent process, so we use the raw
   168      // syscall.
   169      EXPECT_THAT(
   170          syscall(SYS_setresuid, -1, absl::GetFlag(FLAGS_scratch_uid2), -1),
   171          SyscallSucceeds());
   172  
   173      // Create file and immediately close it.
   174      FileDescriptor fd =
   175          ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_CREAT | O_RDWR, 0644));
   176      fd.reset();  // Close the fd.
   177  
   178      fileCreated.Notify();
   179      fileChowned.WaitForNotification();
   180  
   181      EXPECT_THAT(open(filename.c_str(), O_RDWR), SyscallFailsWithErrno(EACCES));
   182      FileDescriptor fd2 = ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_RDONLY));
   183    });
   184  
   185    fileCreated.WaitForNotification();
   186  
   187    // Set file's owners to someone different.
   188    EXPECT_NO_ERRNO(GetParam()(filename, absl::GetFlag(FLAGS_scratch_uid1),
   189                               absl::GetFlag(FLAGS_scratch_gid)));
   190  
   191    struct stat s;
   192    EXPECT_THAT(stat(filename.c_str(), &s), SyscallSucceeds());
   193    EXPECT_EQ(s.st_uid, absl::GetFlag(FLAGS_scratch_uid1));
   194    EXPECT_EQ(s.st_gid, absl::GetFlag(FLAGS_scratch_gid));
   195  
   196    fileChowned.Notify();
   197  }
   198  
   199  PosixError errorFromReturn(const std::string& name, int ret) {
   200    if (ret == -1) {
   201      return PosixError(errno, absl::StrCat(name, " failed"));
   202    }
   203    return NoError();
   204  }
   205  
   206  INSTANTIATE_TEST_SUITE_P(
   207      ChownKinds, ChownParamTest,
   208      ::testing::Values(
   209          [](const std::string& path, uid_t owner, gid_t group) -> PosixError {
   210            int rc = chown(path.c_str(), owner, group);
   211            MaybeSave();
   212            return errorFromReturn("chown", rc);
   213          },
   214          [](const std::string& path, uid_t owner, gid_t group) -> PosixError {
   215            int rc = lchown(path.c_str(), owner, group);
   216            MaybeSave();
   217            return errorFromReturn("lchown", rc);
   218          },
   219          [](const std::string& path, uid_t owner, gid_t group) -> PosixError {
   220            ASSIGN_OR_RETURN_ERRNO(auto fd, Open(path, O_RDWR));
   221            int rc = fchown(fd.get(), owner, group);
   222            MaybeSave();
   223            return errorFromReturn("fchown", rc);
   224          },
   225          [](const std::string& path, uid_t owner, gid_t group) -> PosixError {
   226            ASSIGN_OR_RETURN_ERRNO(auto fd, Open(path, O_RDWR));
   227            int rc = fchownat(fd.get(), "", owner, group, AT_EMPTY_PATH);
   228            MaybeSave();
   229            return errorFromReturn("fchownat-fd", rc);
   230          },
   231          [](const std::string& path, uid_t owner, gid_t group) -> PosixError {
   232            ASSIGN_OR_RETURN_ERRNO(auto dirfd, Open(std::string(Dirname(path)),
   233                                                    O_DIRECTORY | O_RDONLY));
   234            int rc = fchownat(dirfd.get(), std::string(Basename(path)).c_str(),
   235                              owner, group, 0);
   236            MaybeSave();
   237            return errorFromReturn("fchownat-dirfd", rc);
   238          },
   239          [](const std::string& path, uid_t owner, gid_t group) -> PosixError {
   240            ASSIGN_OR_RETURN_ERRNO(auto dirfd, Open(std::string(Dirname(path)),
   241                                                    O_DIRECTORY | O_PATH));
   242            int rc = fchownat(dirfd.get(), std::string(Basename(path)).c_str(),
   243                              owner, group, 0);
   244            MaybeSave();
   245            return errorFromReturn("fchownat-opathdirfd", rc);
   246          }));
   247  
   248  }  // namespace
   249  
   250  }  // namespace testing
   251  }  // namespace gvisor