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