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