github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/test/syscalls/linux/chroot.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 <errno.h> 16 #include <fcntl.h> 17 #include <stddef.h> 18 #include <sys/mman.h> 19 #include <sys/stat.h> 20 #include <syscall.h> 21 #include <unistd.h> 22 23 #include <string> 24 #include <vector> 25 26 #include "gmock/gmock.h" 27 #include "gtest/gtest.h" 28 #include "absl/strings/str_cat.h" 29 #include "absl/strings/str_split.h" 30 #include "absl/strings/string_view.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/mount_util.h" 37 #include "test/util/multiprocess_util.h" 38 #include "test/util/temp_path.h" 39 #include "test/util/test_util.h" 40 41 using ::testing::HasSubstr; 42 using ::testing::Not; 43 44 namespace gvisor { 45 namespace testing { 46 47 namespace { 48 49 TEST(ChrootTest, Success) { 50 SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); 51 52 const auto rest = [] { 53 auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); 54 TEST_CHECK_SUCCESS(chroot(temp_dir.path().c_str())); 55 }; 56 EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); 57 } 58 59 TEST(ChrootTest, PermissionDenied) { 60 SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); 61 62 // CAP_DAC_READ_SEARCH and CAP_DAC_OVERRIDE may override Execute permission 63 // on directories. 64 AutoCapability cap_search(CAP_DAC_READ_SEARCH, false); 65 AutoCapability cap_override(CAP_DAC_OVERRIDE, false); 66 67 auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE( 68 TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0666 /* mode */)); 69 EXPECT_THAT(chroot(temp_dir.path().c_str()), SyscallFailsWithErrno(EACCES)); 70 } 71 72 TEST(ChrootTest, NotDir) { 73 SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); 74 75 auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); 76 EXPECT_THAT(chroot(temp_file.path().c_str()), SyscallFailsWithErrno(ENOTDIR)); 77 } 78 79 TEST(ChrootTest, NotExist) { 80 SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); 81 82 EXPECT_THAT(chroot("/foo/bar"), SyscallFailsWithErrno(ENOENT)); 83 } 84 85 TEST(ChrootTest, WithoutCapability) { 86 SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETPCAP))); 87 88 // Unset CAP_SYS_CHROOT. 89 AutoCapability cap(CAP_SYS_CHROOT, false); 90 91 auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); 92 EXPECT_THAT(chroot(temp_dir.path().c_str()), SyscallFailsWithErrno(EPERM)); 93 } 94 95 TEST(ChrootTest, CreatesNewRoot) { 96 SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); 97 98 // Grab the initial cwd. 99 char initial_cwd[1024]; 100 ASSERT_THAT(syscall(__NR_getcwd, initial_cwd, sizeof(initial_cwd)), 101 SyscallSucceeds()); 102 103 auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); 104 auto file_in_new_root = 105 ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(new_root.path())); 106 107 const auto rest = [&] { 108 // chroot into new_root. 109 TEST_CHECK_SUCCESS(chroot(new_root.path().c_str())); 110 111 // getcwd should return "(unreachable)" followed by the initial_cwd. 112 char cwd[1024]; 113 TEST_CHECK_SUCCESS(syscall(__NR_getcwd, cwd, sizeof(cwd))); 114 std::string expected_cwd = "(unreachable)"; 115 expected_cwd += initial_cwd; 116 TEST_CHECK(strcmp(cwd, expected_cwd.c_str()) == 0); 117 118 // Should not be able to stat file by its full path. 119 struct stat statbuf; 120 TEST_CHECK_ERRNO(stat(file_in_new_root.path().c_str(), &statbuf), ENOENT); 121 122 // Should be able to stat file at new rooted path. 123 auto basename = std::string(Basename(file_in_new_root.path())); 124 auto rootedFile = "/" + basename; 125 TEST_CHECK_SUCCESS(stat(rootedFile.c_str(), &statbuf)); 126 127 // Should be able to stat cwd at '.' even though it's outside root. 128 TEST_CHECK_SUCCESS(stat(".", &statbuf)); 129 130 // chdir into new root. 131 TEST_CHECK_SUCCESS(chdir("/")); 132 133 // getcwd should return "/". 134 TEST_CHECK_SUCCESS(syscall(__NR_getcwd, cwd, sizeof(cwd))); 135 TEST_CHECK_SUCCESS(strcmp(cwd, "/") == 0); 136 137 // Statting '.', '..', '/', and '/..' all return the same dev and inode. 138 struct stat statbuf_dot; 139 TEST_CHECK_SUCCESS(stat(".", &statbuf_dot)); 140 struct stat statbuf_dotdot; 141 TEST_CHECK_SUCCESS(stat("..", &statbuf_dotdot)); 142 TEST_CHECK(statbuf_dot.st_dev == statbuf_dotdot.st_dev); 143 TEST_CHECK(statbuf_dot.st_ino == statbuf_dotdot.st_ino); 144 struct stat statbuf_slash; 145 TEST_CHECK_SUCCESS(stat("/", &statbuf_slash)); 146 TEST_CHECK(statbuf_dot.st_dev == statbuf_slash.st_dev); 147 TEST_CHECK(statbuf_dot.st_ino == statbuf_slash.st_ino); 148 struct stat statbuf_slashdotdot; 149 TEST_CHECK_SUCCESS(stat("/..", &statbuf_slashdotdot)); 150 TEST_CHECK(statbuf_dot.st_dev == statbuf_slashdotdot.st_dev); 151 TEST_CHECK(statbuf_dot.st_ino == statbuf_slashdotdot.st_ino); 152 }; 153 EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); 154 } 155 156 TEST(ChrootTest, DotDotFromOpenFD) { 157 SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); 158 159 auto dir_outside_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); 160 auto fd = ASSERT_NO_ERRNO_AND_VALUE( 161 Open(dir_outside_root.path(), O_RDONLY | O_DIRECTORY)); 162 auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); 163 164 const auto rest = [&] { 165 // chroot into new_root. 166 TEST_CHECK_SUCCESS(chroot(new_root.path().c_str())); 167 168 // openat on fd with path .. will succeed. 169 int other_fd; 170 TEST_CHECK_SUCCESS(other_fd = openat(fd.get(), "..", O_RDONLY)); 171 TEST_CHECK_SUCCESS(close(other_fd)); 172 173 // getdents on fd should not error. 174 char buf[1024]; 175 TEST_CHECK_SUCCESS(syscall(SYS_getdents64, fd.get(), buf, sizeof(buf))); 176 }; 177 EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); 178 } 179 180 // Test that link resolution in a chroot can escape the root by following an 181 // open proc fd. Regression test for b/32316719. 182 TEST(ChrootTest, ProcFdLinkResolutionInChroot) { 183 SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); 184 185 const TempPath file_outside_chroot = 186 ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); 187 const FileDescriptor fd = 188 ASSERT_NO_ERRNO_AND_VALUE(Open(file_outside_chroot.path(), O_RDONLY)); 189 190 const FileDescriptor proc_fd = ASSERT_NO_ERRNO_AND_VALUE( 191 Open("/proc", O_DIRECTORY | O_RDONLY | O_CLOEXEC)); 192 193 const auto rest = [&] { 194 auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); 195 TEST_CHECK_SUCCESS(chroot(temp_dir.path().c_str())); 196 197 // Opening relative to an already open fd to a node outside the chroot 198 // works. 199 const FileDescriptor proc_self_fd = TEST_CHECK_NO_ERRNO_AND_VALUE( 200 OpenAt(proc_fd.get(), "self/fd", O_DIRECTORY | O_RDONLY | O_CLOEXEC)); 201 202 // Proc fd symlinks can escape the chroot if the fd the symlink refers to 203 // refers to an object outside the chroot. 204 struct stat s = {}; 205 TEST_CHECK_SUCCESS( 206 fstatat(proc_self_fd.get(), absl::StrCat(fd.get()).c_str(), &s, 0)); 207 208 // Try to stat the stdin fd. Internally, this is handled differently from a 209 // proc fd entry pointing to a file, since stdin is backed by a host fd, and 210 // isn't a walkable path on the filesystem inside the sandbox. 211 TEST_CHECK_SUCCESS(fstatat(proc_self_fd.get(), "0", &s, 0)); 212 }; 213 EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); 214 } 215 216 // This test will verify that when you hold a fd to proc before entering 217 // a chroot that any files inside the chroot will appear rooted to the 218 // base chroot when examining /proc/self/fd/{num}. 219 TEST(ChrootTest, ProcMemSelfFdsNoEscapeProcOpen) { 220 SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); 221 222 // Get a FD to /proc before we enter the chroot. 223 const FileDescriptor proc = 224 ASSERT_NO_ERRNO_AND_VALUE(Open("/proc", O_RDONLY)); 225 226 const auto rest = [&] { 227 // Create and enter a chroot directory. 228 const auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); 229 TEST_CHECK_SUCCESS(chroot(temp_dir.path().c_str())); 230 231 // Open a file inside the chroot at /foo. 232 const FileDescriptor foo = 233 TEST_CHECK_NO_ERRNO_AND_VALUE(Open("/foo", O_CREAT | O_RDONLY, 0644)); 234 235 // Examine /proc/self/fd/{foo_fd} to see if it exposes the fact that we're 236 // inside a chroot, the path should be /foo and NOT {chroot_dir}/foo. 237 const std::string fd_path = absl::StrCat("self/fd/", foo.get()); 238 char buf[1024] = {}; 239 size_t bytes_read = 0; 240 TEST_CHECK_SUCCESS(bytes_read = readlinkat(proc.get(), fd_path.c_str(), buf, 241 sizeof(buf) - 1)); 242 243 // The link should resolve to something. 244 TEST_CHECK(bytes_read > 0); 245 246 // Assert that the link doesn't contain the chroot path and is only /foo. 247 TEST_CHECK(strcmp(buf, "/foo") == 0); 248 }; 249 EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); 250 } 251 252 // This test will verify that a file inside a chroot when mmapped will not 253 // expose the full file path via /proc/self/maps and instead honor the chroot. 254 TEST(ChrootTest, ProcMemSelfMapsNoEscapeProcOpen) { 255 SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); 256 257 // Get a FD to /proc before we enter the chroot. 258 const FileDescriptor proc = 259 ASSERT_NO_ERRNO_AND_VALUE(Open("/proc", O_RDONLY)); 260 261 const auto rest = [&] { 262 // Create and enter a chroot directory. 263 const auto temp_dir = TEST_CHECK_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); 264 TEST_CHECK_SUCCESS(chroot(temp_dir.path().c_str())); 265 266 // Open a file inside the chroot at /foo. 267 const FileDescriptor foo = 268 TEST_CHECK_NO_ERRNO_AND_VALUE(Open("/foo", O_CREAT | O_RDONLY, 0644)); 269 270 // Mmap the newly created file. 271 void* foo_map = mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, 272 MAP_PRIVATE, foo.get(), 0); 273 TEST_CHECK_SUCCESS(reinterpret_cast<int64_t>(foo_map)); 274 275 // Always unmap. 276 auto cleanup_map = 277 Cleanup([&] { TEST_CHECK_SUCCESS(munmap(foo_map, kPageSize)); }); 278 279 // Examine /proc/self/maps to be sure that /foo doesn't appear to be 280 // mapped with the full chroot path. 281 const FileDescriptor maps = TEST_CHECK_NO_ERRNO_AND_VALUE( 282 OpenAt(proc.get(), "self/maps", O_RDONLY)); 283 284 size_t bytes_read = 0; 285 char buf[8 * 1024] = {}; 286 TEST_CHECK_SUCCESS(bytes_read = ReadFd(maps.get(), buf, sizeof(buf))); 287 288 // The maps file should have something. 289 TEST_CHECK(bytes_read > 0); 290 291 // Finally we want to make sure the maps don't contain the chroot path 292 TEST_CHECK(std::string(buf, bytes_read).find(temp_dir.path()) == 293 std::string::npos); 294 }; 295 EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); 296 } 297 298 // Test that mounts outside the chroot will not appear in /proc/self/mounts or 299 // /proc/self/mountinfo. 300 TEST(ChrootTest, ProcMountsMountinfoNoEscape) { 301 SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); 302 SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); 303 304 // Create nested tmpfs mounts. 305 auto const outer_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); 306 auto const outer_mount = ASSERT_NO_ERRNO_AND_VALUE( 307 Mount("none", outer_dir.path(), "tmpfs", 0, "mode=0700", 0)); 308 309 auto const inner_dir = 310 ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(outer_dir.path())); 311 auto const inner_mount = ASSERT_NO_ERRNO_AND_VALUE( 312 Mount("none", inner_dir.path(), "tmpfs", 0, "mode=0700", 0)); 313 314 const auto rest = [&outer_dir, &inner_dir] { 315 // Filenames that will be checked for mounts, all relative to /proc dir. 316 std::string paths[3] = {"mounts", "self/mounts", "self/mountinfo"}; 317 318 for (const std::string& path : paths) { 319 // We should have both inner and outer mounts. 320 const std::string contents = 321 TEST_CHECK_NO_ERRNO_AND_VALUE(GetContents(JoinPath("/proc", path))); 322 EXPECT_THAT(contents, AllOf(HasSubstr(outer_dir.path()), 323 HasSubstr(inner_dir.path()))); 324 // We better have at least two mounts: the mounts we created plus the 325 // root. 326 std::vector<absl::string_view> submounts = 327 absl::StrSplit(contents, '\n', absl::SkipWhitespace()); 328 TEST_CHECK(submounts.size() > 2); 329 } 330 331 // Get a FD to /proc before we enter the chroot. 332 const FileDescriptor proc = 333 TEST_CHECK_NO_ERRNO_AND_VALUE(Open("/proc", O_RDONLY)); 334 335 // Chroot to outer mount. 336 TEST_CHECK_SUCCESS(chroot(outer_dir.path().c_str())); 337 338 for (const std::string& path : paths) { 339 const FileDescriptor proc_file = 340 TEST_CHECK_NO_ERRNO_AND_VALUE(OpenAt(proc.get(), path, O_RDONLY)); 341 342 // Only two mounts visible from this chroot: the inner and outer. Both 343 // paths should be relative to the new chroot. 344 const std::string contents = 345 TEST_CHECK_NO_ERRNO_AND_VALUE(GetContentsFD(proc_file.get())); 346 EXPECT_THAT(contents, 347 AllOf(HasSubstr(absl::StrCat(Basename(inner_dir.path()))), 348 Not(HasSubstr(outer_dir.path())), 349 Not(HasSubstr(inner_dir.path())))); 350 std::vector<absl::string_view> submounts = 351 absl::StrSplit(contents, '\n', absl::SkipWhitespace()); 352 TEST_CHECK(submounts.size() == 2); 353 } 354 355 // Chroot to inner mount. We must use an absolute path accessible to our 356 // chroot. 357 const std::string inner_dir_basename = 358 absl::StrCat("/", Basename(inner_dir.path())); 359 TEST_CHECK_SUCCESS(chroot(inner_dir_basename.c_str())); 360 361 for (const std::string& path : paths) { 362 const FileDescriptor proc_file = 363 TEST_CHECK_NO_ERRNO_AND_VALUE(OpenAt(proc.get(), path, O_RDONLY)); 364 const std::string contents = 365 TEST_CHECK_NO_ERRNO_AND_VALUE(GetContentsFD(proc_file.get())); 366 367 // Only the inner mount visible from this chroot. 368 std::vector<absl::string_view> submounts = 369 absl::StrSplit(contents, '\n', absl::SkipWhitespace()); 370 TEST_CHECK(submounts.size() == 1); 371 } 372 }; 373 EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); 374 } 375 376 } // namespace 377 378 } // namespace testing 379 } // namespace gvisor