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