gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/syscalls/linux/setgid.cc (about) 1 // Copyright 2020 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 <limits.h> 16 #include <sys/types.h> 17 #include <unistd.h> 18 19 #include "gtest/gtest.h" 20 #include "absl/flags/flag.h" 21 #include "test/util/capability_util.h" 22 #include "test/util/cleanup.h" 23 #include "test/util/fs_util.h" 24 #include "test/util/posix_error.h" 25 #include "test/util/temp_path.h" 26 #include "test/util/test_util.h" 27 28 ABSL_FLAG(std::vector<std::string>, groups, std::vector<std::string>({}), 29 "groups the test can use"); 30 31 constexpr gid_t kNobody = 65534; 32 33 namespace gvisor { 34 namespace testing { 35 36 namespace { 37 38 constexpr int kDirmodeMask = 07777; 39 constexpr int kDirmodeSgid = S_ISGID | 0777; 40 constexpr int kDirmodeNoExec = S_ISGID | 0767; 41 constexpr int kDirmodeNoSgid = 0777; 42 43 // Sets effective GID and returns a Cleanup that restores the original. 44 PosixErrorOr<Cleanup> Setegid(gid_t egid) { 45 gid_t old_gid = getegid(); 46 if (setegid(egid) < 0) { 47 return PosixError(errno, absl::StrFormat("setegid(%d)", egid)); 48 } 49 return Cleanup( 50 [old_gid]() { EXPECT_THAT(setegid(old_gid), SyscallSucceeds()); }); 51 } 52 53 // Returns a pair of groups that the user is a member of. 54 PosixErrorOr<std::pair<gid_t, gid_t>> Groups() { 55 // Were we explicitly passed GIDs? 56 std::vector<std::string> flagged_groups = absl::GetFlag(FLAGS_groups); 57 if (flagged_groups.size() >= 2) { 58 int group1; 59 int group2; 60 if (!absl::SimpleAtoi(flagged_groups[0], &group1) || 61 !absl::SimpleAtoi(flagged_groups[1], &group2)) { 62 return PosixError(EINVAL, "failed converting group flags to ints"); 63 } 64 return std::pair<gid_t, gid_t>(group1, group2); 65 } 66 67 // See whether the user is a member of at least 2 groups. 68 std::vector<gid_t> groups(64); 69 for (; groups.size() <= NGROUPS_MAX; groups.resize(groups.size() * 2)) { 70 int ngroups = getgroups(groups.size(), groups.data()); 71 if (ngroups < 0 && errno == EINVAL) { 72 // Need a larger list. 73 continue; 74 } 75 if (ngroups < 0) { 76 return PosixError(errno, absl::StrFormat("getgroups(%d, %p)", 77 groups.size(), groups.data())); 78 } 79 80 if (ngroups < 2) { 81 // There aren't enough groups. 82 break; 83 } 84 85 // TODO(b/181878080): Read /proc/sys/fs/overflowgid once it is supported in 86 // gVisor. 87 if (groups[0] == kNobody || groups[1] == kNobody) { 88 // These groups aren't mapped into our user namespace, so we can't use 89 // them. 90 break; 91 } 92 return std::pair<gid_t, gid_t>(groups[0], groups[1]); 93 } 94 95 // If we're running in gVisor and are root in the root user namespace, we can 96 // set our GID to whatever we want. Try that before giving up. 97 // 98 // This won't work in native tests, as despite having CAP_SETGID, the gofer 99 // process will be sandboxed and unable to change file GIDs. 100 if (!IsRunningOnGvisor()) { 101 return PosixError(EPERM, "no valid groups for native testing"); 102 } 103 PosixErrorOr<bool> capable = HaveCapability(CAP_SETGID); 104 if (!capable.ok()) { 105 return capable.error(); 106 } 107 if (!capable.ValueOrDie()) { 108 return PosixError(EPERM, "missing CAP_SETGID"); 109 } 110 return std::pair<gid_t, gid_t>(getegid(), kNobody); 111 } 112 113 class SetgidDirTest : public ::testing::Test { 114 protected: 115 void SetUp() override { 116 original_gid_ = getegid(); 117 118 // If we can't find two usable groups, we're in an unsupporting environment. 119 // Skip the test. 120 PosixErrorOr<std::pair<gid_t, gid_t>> groups = Groups(); 121 SKIP_IF(!groups.ok()); 122 groups_ = groups.ValueOrDie(); 123 124 // Ensure we can actually use both groups. 125 auto cleanup1 = Setegid(groups_.first); 126 SKIP_IF(!cleanup1.ok()); 127 auto cleanup2 = Setegid(groups_.second); 128 SKIP_IF(!cleanup2.ok()); 129 130 auto cleanup = Setegid(groups_.first); 131 temp_dir_ = ASSERT_NO_ERRNO_AND_VALUE( 132 TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0777 /* mode */)); 133 } 134 135 void TearDown() override { 136 EXPECT_THAT(setegid(original_gid_), SyscallSucceeds()); 137 } 138 139 void MkdirAsGid(gid_t gid, const std::string& path, mode_t mode) { 140 auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(Setegid(gid)); 141 ASSERT_THAT(mkdir(path.c_str(), mode), SyscallSucceeds()); 142 } 143 144 PosixErrorOr<struct stat> Stat(const std::string& path) { 145 struct stat stats; 146 if (stat(path.c_str(), &stats) < 0) { 147 return PosixError(errno, absl::StrFormat("stat(%s, _)", path)); 148 } 149 return stats; 150 } 151 152 PosixErrorOr<struct stat> Stat(const FileDescriptor& fd) { 153 struct stat stats; 154 if (fstat(fd.get(), &stats) < 0) { 155 return PosixError(errno, "fstat(_, _)"); 156 } 157 return stats; 158 } 159 160 TempPath temp_dir_; 161 std::pair<gid_t, gid_t> groups_; 162 gid_t original_gid_; 163 }; 164 165 // The control test. Files created with a given GID are owned by that group. 166 TEST_F(SetgidDirTest, Control) { 167 // Set group to G1 and create a directory. 168 auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); 169 ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, 0777)); 170 171 // Set group to G2, create a file in g1owned, and confirm that G2 owns it. 172 auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(Setegid(groups_.second)); 173 FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( 174 Open(JoinPath(g1owned, "g2owned").c_str(), O_CREAT | O_RDWR, 0777)); 175 struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); 176 EXPECT_EQ(stats.st_gid, groups_.second); 177 } 178 179 // Setgid directories cause created files to inherit GID. 180 TEST_F(SetgidDirTest, CreateFile) { 181 // Set group to G1, create a directory, and enable setgid. 182 auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); 183 ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, kDirmodeSgid)); 184 ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeSgid), SyscallSucceeds()); 185 186 // Set group to G2, create a file, and confirm that G1 owns it. 187 auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(Setegid(groups_.second)); 188 FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( 189 Open(JoinPath(g1owned, "g2created").c_str(), O_CREAT | O_RDWR, 0666)); 190 struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); 191 EXPECT_EQ(stats.st_gid, groups_.first); 192 } 193 194 // Setgid directories cause created directories to inherit GID. 195 TEST_F(SetgidDirTest, CreateDir) { 196 // Set group to G1, create a directory, and enable setgid. 197 auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); 198 ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, kDirmodeSgid)); 199 ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeSgid), SyscallSucceeds()); 200 201 // Set group to G2, create a directory, confirm that G1 owns it, and that the 202 // setgid bit is enabled. 203 auto g2created = JoinPath(g1owned, "g2created"); 204 ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.second, g2created, 0666)); 205 struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(g2created)); 206 EXPECT_EQ(stats.st_gid, groups_.first); 207 EXPECT_EQ(stats.st_mode & S_ISGID, S_ISGID); 208 } 209 210 // Setgid directories with group execution disabled still cause GID inheritance. 211 TEST_F(SetgidDirTest, NoGroupExec) { 212 // Set group to G1, create a directory, and enable setgid. 213 auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); 214 ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, kDirmodeNoExec)); 215 ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeNoExec), SyscallSucceeds()); 216 217 // Set group to G2, create a directory, confirm that G2 owns it, and that the 218 // setgid bit is enabled. 219 auto g2created = JoinPath(g1owned, "g2created"); 220 ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.second, g2created, 0666)); 221 struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(g2created)); 222 EXPECT_EQ(stats.st_gid, groups_.first); 223 EXPECT_EQ(stats.st_mode & S_ISGID, S_ISGID); 224 } 225 226 // Setting the setgid bit on directories with an existing file does not change 227 // the file's group. 228 TEST_F(SetgidDirTest, OldFile) { 229 // Set group to G1 and create a directory. 230 auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); 231 ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, kDirmodeNoSgid)); 232 ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeNoSgid), SyscallSucceeds()); 233 234 // Set group to G2, create a file, confirm that G2 owns it. 235 auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(Setegid(groups_.second)); 236 FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( 237 Open(JoinPath(g1owned, "g2created").c_str(), O_CREAT | O_RDWR, 0666)); 238 struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); 239 EXPECT_EQ(stats.st_gid, groups_.second); 240 241 // Enable setgid. 242 ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeSgid), SyscallSucceeds()); 243 244 // Confirm that the file's group is still G2. 245 stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); 246 EXPECT_EQ(stats.st_gid, groups_.second); 247 } 248 249 // Setting the setgid bit on directories with an existing subdirectory does not 250 // change the subdirectory's group. 251 TEST_F(SetgidDirTest, OldDir) { 252 // Set group to G1, create a directory, and enable setgid. 253 auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); 254 ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, kDirmodeNoSgid)); 255 ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeNoSgid), SyscallSucceeds()); 256 257 // Set group to G2, create a directory, confirm that G2 owns it. 258 auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(Setegid(groups_.second)); 259 auto g2created = JoinPath(g1owned, "g2created"); 260 ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.second, g2created, 0666)); 261 struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(g2created)); 262 EXPECT_EQ(stats.st_gid, groups_.second); 263 264 // Enable setgid. 265 ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeSgid), SyscallSucceeds()); 266 267 // Confirm that the file's group is still G2. 268 stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(g2created)); 269 EXPECT_EQ(stats.st_gid, groups_.second); 270 } 271 272 // Chowning a file clears the setgid and setuid bits. 273 TEST_F(SetgidDirTest, ChownFileClears) { 274 // Set group to G1, create a directory, and enable setgid. 275 auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); 276 ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, kDirmodeMask)); 277 ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeMask), SyscallSucceeds()); 278 279 FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( 280 Open(JoinPath(g1owned, "newfile").c_str(), O_CREAT | O_RDWR, 0666)); 281 ASSERT_THAT(fchmod(fd.get(), 0777 | S_ISUID | S_ISGID), SyscallSucceeds()); 282 struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); 283 EXPECT_EQ(stats.st_gid, groups_.first); 284 EXPECT_EQ(stats.st_mode & (S_ISUID | S_ISGID), S_ISUID | S_ISGID); 285 286 // Change the owning group. 287 ASSERT_THAT(fchown(fd.get(), -1, groups_.second), SyscallSucceeds()); 288 289 // The setgid and setuid bits should be cleared. 290 stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); 291 EXPECT_EQ(stats.st_gid, groups_.second); 292 EXPECT_EQ(stats.st_mode & (S_ISUID | S_ISGID), 0); 293 } 294 295 // Chowning a file with setgid enabled, but not the group exec bit, clears the 296 // setuid bit and not the setgid bit. Such files are mandatory locked. 297 TEST_F(SetgidDirTest, ChownNoExecFileDoesNotClear) { 298 // Set group to G1, create a directory, and enable setgid. 299 auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); 300 ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, kDirmodeNoExec)); 301 ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeNoExec), SyscallSucceeds()); 302 303 FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE( 304 Open(JoinPath(g1owned, "newdir").c_str(), O_CREAT | O_RDWR, 0666)); 305 ASSERT_THAT(fchmod(fd.get(), 0766 | S_ISUID | S_ISGID), SyscallSucceeds()); 306 struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); 307 EXPECT_EQ(stats.st_gid, groups_.first); 308 EXPECT_EQ(stats.st_mode & (S_ISUID | S_ISGID), S_ISUID | S_ISGID); 309 310 // Change the owning group. 311 ASSERT_THAT(fchown(fd.get(), -1, groups_.second), SyscallSucceeds()); 312 313 // Only the setuid bit is cleared. 314 stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(fd)); 315 EXPECT_EQ(stats.st_gid, groups_.second); 316 EXPECT_EQ(stats.st_mode & (S_ISUID | S_ISGID), S_ISGID); 317 } 318 319 // Chowning a directory with setgid enabled does not clear the bit. 320 TEST_F(SetgidDirTest, ChownDirDoesNotClear) { 321 // Set group to G1, create a directory, and enable setgid. 322 auto g1owned = JoinPath(temp_dir_.path(), "g1owned/"); 323 ASSERT_NO_FATAL_FAILURE(MkdirAsGid(groups_.first, g1owned, kDirmodeMask)); 324 ASSERT_THAT(chmod(g1owned.c_str(), kDirmodeMask), SyscallSucceeds()); 325 326 // Change the owning group. 327 ASSERT_THAT(chown(g1owned.c_str(), -1, groups_.second), SyscallSucceeds()); 328 329 struct stat stats = ASSERT_NO_ERRNO_AND_VALUE(Stat(g1owned)); 330 EXPECT_EQ(stats.st_gid, groups_.second); 331 EXPECT_EQ(stats.st_mode & kDirmodeMask, kDirmodeMask); 332 } 333 334 struct FileModeTestcase { 335 std::string name; 336 mode_t mode; 337 mode_t result_mode; 338 339 FileModeTestcase(const std::string& name, mode_t mode, mode_t result_mode) 340 : name(name), mode(mode), result_mode(result_mode) {} 341 }; 342 343 class FileModeTest : public ::testing::TestWithParam<FileModeTestcase> {}; 344 345 TEST_P(FileModeTest, WriteToFile) { 346 PosixErrorOr<std::pair<gid_t, gid_t>> groups = Groups(); 347 SKIP_IF(!groups.ok()); 348 349 auto cleanup = Setegid(groups.ValueOrDie().first); 350 auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE( 351 TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0777 /* mode */)); 352 auto path = JoinPath(temp_dir.path(), GetParam().name); 353 FileDescriptor fd = 354 ASSERT_NO_ERRNO_AND_VALUE(Open(path.c_str(), O_CREAT | O_RDWR, 0666)); 355 ASSERT_THAT(fchmod(fd.get(), GetParam().mode), SyscallSucceeds()); 356 struct stat stats; 357 ASSERT_THAT(fstat(fd.get(), &stats), SyscallSucceeds()); 358 EXPECT_EQ(stats.st_mode & kDirmodeMask, GetParam().mode); 359 360 // For security reasons, writing to the file clears the SUID bit, and clears 361 // the SGID bit when the group executable bit is unset (which is not a true 362 // SGID binary). 363 constexpr char kInput = 'M'; 364 ASSERT_THAT(write(fd.get(), &kInput, sizeof(kInput)), 365 SyscallSucceedsWithValue(sizeof(kInput))); 366 367 ASSERT_THAT(fstat(fd.get(), &stats), SyscallSucceeds()); 368 EXPECT_EQ(stats.st_mode & kDirmodeMask, GetParam().result_mode); 369 } 370 371 TEST_P(FileModeTest, TruncateFile) { 372 PosixErrorOr<std::pair<gid_t, gid_t>> groups = Groups(); 373 SKIP_IF(!groups.ok()); 374 375 auto cleanup = Setegid(groups.ValueOrDie().first); 376 auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE( 377 TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0777 /* mode */)); 378 auto path = JoinPath(temp_dir.path(), GetParam().name); 379 FileDescriptor fd = 380 ASSERT_NO_ERRNO_AND_VALUE(Open(path.c_str(), O_CREAT | O_RDWR, 0666)); 381 382 // Write something to the file, as truncating an empty file is a no-op. 383 constexpr char c = 'M'; 384 ASSERT_THAT(write(fd.get(), &c, sizeof(c)), 385 SyscallSucceedsWithValue(sizeof(c))); 386 ASSERT_THAT(fchmod(fd.get(), GetParam().mode), SyscallSucceeds()); 387 388 // For security reasons, truncating the file clears the SUID bit, and clears 389 // the SGID bit when the group executable bit is unset (which is not a true 390 // SGID binary). 391 ASSERT_THAT(ftruncate(fd.get(), 0), SyscallSucceeds()); 392 393 struct stat stats; 394 ASSERT_THAT(fstat(fd.get(), &stats), SyscallSucceeds()); 395 EXPECT_EQ(stats.st_mode & kDirmodeMask, GetParam().result_mode); 396 } 397 398 INSTANTIATE_TEST_SUITE_P( 399 FileModes, FileModeTest, 400 ::testing::ValuesIn<FileModeTestcase>( 401 {FileModeTestcase("normal file", 0777, 0777), 402 FileModeTestcase("setuid", S_ISUID | 0777, 00777), 403 FileModeTestcase("setgid", S_ISGID | 0777, 00777), 404 FileModeTestcase("setuid and setgid", S_ISUID | S_ISGID | 0777, 00777), 405 FileModeTestcase("setgid without exec", S_ISGID | 0767, 406 S_ISGID | 0767), 407 FileModeTestcase("setuid and setgid without exec", 408 S_ISGID | S_ISUID | 0767, S_ISGID | 0767)})); 409 410 } // namespace 411 412 } // namespace testing 413 } // namespace gvisor