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

     1  // Copyright 2019 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 <sched.h>
    17  #include <sys/stat.h>
    18  #include <sys/types.h>
    19  #include <unistd.h>
    20  
    21  #include <functional>
    22  #include <string>
    23  #include <tuple>
    24  #include <utility>
    25  #include <vector>
    26  
    27  #include "gtest/gtest.h"
    28  #include "absl/strings/ascii.h"
    29  #include "absl/strings/str_cat.h"
    30  #include "absl/strings/str_split.h"
    31  #include "test/util/capability_util.h"
    32  #include "test/util/cleanup.h"
    33  #include "test/util/file_descriptor.h"
    34  #include "test/util/fs_util.h"
    35  #include "test/util/logging.h"
    36  #include "test/util/multiprocess_util.h"
    37  #include "test/util/posix_error.h"
    38  #include "test/util/save_util.h"
    39  #include "test/util/test_util.h"
    40  #include "test/util/time_util.h"
    41  
    42  namespace gvisor {
    43  namespace testing {
    44  
    45  PosixErrorOr<int> InNewUserNamespace(const std::function<void()>& fn) {
    46    return InForkedProcess([&] {
    47      TEST_PCHECK(unshare(CLONE_NEWUSER) == 0);
    48      MaybeSave();
    49      fn();
    50    });
    51  }
    52  
    53  PosixErrorOr<std::tuple<pid_t, Cleanup>> CreateProcessInNewUserNamespace() {
    54    int pipefd[2];
    55    if (pipe(pipefd) < 0) {
    56      return PosixError(errno, "pipe failed");
    57    }
    58    const auto cleanup_pipe_read =
    59        Cleanup([&] { EXPECT_THAT(close(pipefd[0]), SyscallSucceeds()); });
    60    auto cleanup_pipe_write =
    61        Cleanup([&] { EXPECT_THAT(close(pipefd[1]), SyscallSucceeds()); });
    62    pid_t child_pid = fork();
    63    if (child_pid < 0) {
    64      return PosixError(errno, "fork failed");
    65    }
    66    if (child_pid == 0) {
    67      // Close our copy of the pipe's read end, which doesn't really matter.
    68      TEST_PCHECK(close(pipefd[0]) >= 0);
    69      TEST_PCHECK(unshare(CLONE_NEWUSER) == 0);
    70      MaybeSave();
    71      // Indicate that we've switched namespaces by unblocking the parent's read.
    72      TEST_PCHECK(close(pipefd[1]) >= 0);
    73      while (true) {
    74        SleepSafe(absl::Minutes(1));
    75      }
    76    }
    77    auto cleanup_child = Cleanup([child_pid] {
    78      EXPECT_THAT(kill(child_pid, SIGKILL), SyscallSucceeds());
    79      int status;
    80      ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
    81                  SyscallSucceedsWithValue(child_pid));
    82      EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL)
    83          << "status = " << status;
    84    });
    85    // Close our copy of the pipe's write end, then wait for the child to close
    86    // its copy, indicating that it's switched namespaces.
    87    cleanup_pipe_write.Release()();
    88    char buf;
    89    if (RetryEINTR(read)(pipefd[0], &buf, 1) < 0) {
    90      return PosixError(errno, "reading from pipe failed");
    91    }
    92    MaybeSave();
    93    return std::make_tuple(child_pid, std::move(cleanup_child));
    94  }
    95  
    96  // TEST_CHECK-fails on error, since this function is used in contexts that
    97  // require async-signal-safety.
    98  void DenySetgroupsByPath(const char* path) {
    99    int fd = open(path, O_WRONLY);
   100    if (fd < 0 && errno == ENOENT) {
   101      // On kernels where this file doesn't exist, writing "deny" to it isn't
   102      // necessary to write to gid_map.
   103      return;
   104    }
   105    TEST_PCHECK(fd >= 0);
   106    MaybeSave();
   107    char deny[] = "deny";
   108    TEST_PCHECK(write(fd, deny, sizeof(deny)) == sizeof(deny));
   109    MaybeSave();
   110    TEST_PCHECK(close(fd) == 0);
   111  }
   112  
   113  void DenySelfSetgroups() { DenySetgroupsByPath("/proc/self/setgroups"); }
   114  
   115  void DenyPidSetgroups(pid_t pid) {
   116    DenySetgroupsByPath(absl::StrCat("/proc/", pid, "/setgroups").c_str());
   117  }
   118  
   119  // Returns a valid UID/GID that isn't id.
   120  uint32_t another_id(uint32_t id) { return (id + 1) % 65535; }
   121  
   122  struct TestParam {
   123    std::string desc;
   124    int cap;
   125    std::function<std::string(absl::string_view)> get_map_filename;
   126    std::function<uint32_t()> get_current_id;
   127  };
   128  
   129  std::string DescribeTestParam(const ::testing::TestParamInfo<TestParam>& info) {
   130    return info.param.desc;
   131  }
   132  
   133  std::vector<TestParam> UidGidMapTestParams() {
   134    return {TestParam{"UID", CAP_SETUID,
   135                      [](absl::string_view pid) {
   136                        return absl::StrCat("/proc/", pid, "/uid_map");
   137                      },
   138                      []() -> uint32_t { return getuid(); }},
   139            TestParam{"GID", CAP_SETGID,
   140                      [](absl::string_view pid) {
   141                        return absl::StrCat("/proc/", pid, "/gid_map");
   142                      },
   143                      []() -> uint32_t { return getgid(); }}};
   144  }
   145  
   146  class ProcUidGidMapTest : public ::testing::TestWithParam<TestParam> {
   147   protected:
   148    uint32_t CurrentID() { return GetParam().get_current_id(); }
   149  };
   150  
   151  class ProcSelfUidGidMapTest : public ProcUidGidMapTest {
   152   protected:
   153    PosixErrorOr<int> InNewUserNamespaceWithMapFD(
   154        const std::function<void(int)>& fn) {
   155      std::string map_filename = GetParam().get_map_filename("self");
   156      return InNewUserNamespace([&] {
   157        int fd = open(map_filename.c_str(), O_RDWR);
   158        TEST_PCHECK(fd >= 0);
   159        MaybeSave();
   160        fn(fd);
   161        TEST_PCHECK(close(fd) == 0);
   162      });
   163    }
   164  };
   165  
   166  class ProcPidUidGidMapTest : public ProcUidGidMapTest {
   167   protected:
   168    PosixErrorOr<bool> HaveSetIDCapability() {
   169      return HaveCapability(GetParam().cap);
   170    }
   171  
   172    // Returns true if the caller is running in a user namespace with all IDs
   173    // mapped. This matters for tests that expect to successfully map arbitrary
   174    // IDs into a child user namespace, since even with CAP_SET*ID this is only
   175    // possible if those IDs are mapped into the current one.
   176    PosixErrorOr<bool> AllIDsMapped() {
   177      ASSIGN_OR_RETURN_ERRNO(std::string id_map,
   178                             GetContents(GetParam().get_map_filename("self")));
   179      absl::StripTrailingAsciiWhitespace(&id_map);
   180      std::vector<std::string> id_map_parts =
   181          absl::StrSplit(id_map, ' ', absl::SkipEmpty());
   182      return id_map_parts == std::vector<std::string>({"0", "0", "4294967295"});
   183    }
   184  
   185    PosixErrorOr<FileDescriptor> OpenMapFile(pid_t pid) {
   186      return Open(GetParam().get_map_filename(absl::StrCat(pid)), O_RDWR);
   187    }
   188  };
   189  
   190  TEST_P(ProcSelfUidGidMapTest, IsInitiallyEmpty) {
   191    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace()));
   192    EXPECT_THAT(InNewUserNamespaceWithMapFD([](int fd) {
   193                  char buf[64];
   194                  TEST_PCHECK(read(fd, buf, sizeof(buf)) == 0);
   195                }),
   196                IsPosixErrorOkAndHolds(0));
   197  }
   198  
   199  TEST_P(ProcSelfUidGidMapTest, IdentityMapOwnID) {
   200    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace()));
   201    uint32_t id = CurrentID();
   202    std::string line = absl::StrCat(id, " ", id, " 1");
   203    EXPECT_THAT(InNewUserNamespaceWithMapFD([&](int fd) {
   204                  DenySelfSetgroups();
   205                  ssize_t n;
   206                  TEST_PCHECK((n = write(fd, line.c_str(), line.size())) != -1);
   207                  TEST_CHECK(n == ssize_t(line.size()));
   208                }),
   209                IsPosixErrorOkAndHolds(0));
   210  }
   211  
   212  TEST_P(ProcSelfUidGidMapTest, TrailingNewlineAndNULIgnored) {
   213    // This is identical to IdentityMapOwnID, except that a trailing newline, NUL,
   214    // and an invalid (incomplete) map entry are appended to the valid entry. The
   215    // newline should be accepted, and everything after the NUL should be ignored.
   216    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace()));
   217    uint32_t id = CurrentID();
   218    std::string line = absl::StrCat(id, " ", id, " 1\n\0 4 3");
   219    EXPECT_THAT(InNewUserNamespaceWithMapFD([&](int fd) {
   220                  DenySelfSetgroups();
   221                  // The write should return the full size of the write, even
   222                  // though characters after the NUL were ignored.
   223                  ssize_t n;
   224                  TEST_PCHECK((n = write(fd, line.c_str(), line.size())) != -1);
   225                  TEST_CHECK(n == ssize_t(line.size()));
   226                }),
   227                IsPosixErrorOkAndHolds(0));
   228  }
   229  
   230  TEST_P(ProcSelfUidGidMapTest, NonIdentityMapOwnID) {
   231    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace()));
   232    uint32_t id = CurrentID();
   233    uint32_t id2 = another_id(id);
   234    std::string line = absl::StrCat(id2, " ", id, " 1");
   235    EXPECT_THAT(InNewUserNamespaceWithMapFD([&](int fd) {
   236                  DenySelfSetgroups();
   237                  TEST_PCHECK(static_cast<long unsigned int>(write(
   238                                  fd, line.c_str(), line.size())) == line.size());
   239                }),
   240                IsPosixErrorOkAndHolds(0));
   241  }
   242  
   243  TEST_P(ProcSelfUidGidMapTest, MapOtherID) {
   244    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace()));
   245    // Whether or not we have CAP_SET*ID is irrelevant: the process running in the
   246    // new (child) user namespace won't have any capabilities in the current
   247    // (parent) user namespace, which is needed.
   248    uint32_t id = CurrentID();
   249    uint32_t id2 = another_id(id);
   250    std::string line = absl::StrCat(id, " ", id2, " 1");
   251    EXPECT_THAT(InNewUserNamespaceWithMapFD([&](int fd) {
   252                  DenySelfSetgroups();
   253                  TEST_PCHECK(write(fd, line.c_str(), line.size()) < 0);
   254                  TEST_CHECK(errno == EPERM);
   255                }),
   256                IsPosixErrorOkAndHolds(0));
   257  }
   258  
   259  INSTANTIATE_TEST_SUITE_P(All, ProcSelfUidGidMapTest,
   260                           ::testing::ValuesIn(UidGidMapTestParams()),
   261                           DescribeTestParam);
   262  
   263  TEST_P(ProcPidUidGidMapTest, MapOtherIDPrivileged) {
   264    // Like ProcSelfUidGidMapTest_MapOtherID, but since we have CAP_SET*ID in the
   265    // parent user namespace (this one), we can map IDs that aren't ours.
   266    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace()));
   267    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveSetIDCapability()));
   268    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(AllIDsMapped()));
   269  
   270    pid_t child_pid;
   271    Cleanup cleanup_child;
   272    std::tie(child_pid, cleanup_child) =
   273        ASSERT_NO_ERRNO_AND_VALUE(CreateProcessInNewUserNamespace());
   274  
   275    uint32_t id = CurrentID();
   276    uint32_t id2 = another_id(id);
   277    std::string line = absl::StrCat(id, " ", id2, " 1");
   278    DenyPidSetgroups(child_pid);
   279    auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenMapFile(child_pid));
   280    EXPECT_THAT(write(fd.get(), line.c_str(), line.size()),
   281                SyscallSucceedsWithValue(line.size()));
   282  }
   283  
   284  TEST_P(ProcPidUidGidMapTest, MapAnyIDsPrivileged) {
   285    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace()));
   286    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveSetIDCapability()));
   287    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(AllIDsMapped()));
   288  
   289    pid_t child_pid;
   290    Cleanup cleanup_child;
   291    std::tie(child_pid, cleanup_child) =
   292        ASSERT_NO_ERRNO_AND_VALUE(CreateProcessInNewUserNamespace());
   293  
   294    // Test all of:
   295    //
   296    // - Mapping ranges of length > 1
   297    //
   298    // - Mapping multiple ranges
   299    //
   300    // - Non-identity mappings
   301    char entries[] = "2 0 2\n4 6 2";
   302    DenyPidSetgroups(child_pid);
   303    auto fd = ASSERT_NO_ERRNO_AND_VALUE(OpenMapFile(child_pid));
   304    EXPECT_THAT(write(fd.get(), entries, sizeof(entries)),
   305                SyscallSucceedsWithValue(sizeof(entries)));
   306  }
   307  
   308  INSTANTIATE_TEST_SUITE_P(All, ProcPidUidGidMapTest,
   309                           ::testing::ValuesIn(UidGidMapTestParams()),
   310                           DescribeTestParam);
   311  
   312  }  // namespace testing
   313  }  // namespace gvisor