From 82dd91778b9f862b12f5e6fbd1eec9b7ec6b6c79 Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Fri, 12 May 2023 09:49:17 -0700 Subject: [PATCH] Test that readlink for /proc/self/fd/ can escape chroot If the file descriptor references a file outside the chroot, then calling readlink on that file descriptor's entry in /proc/self/fd/ returns the full path, even though that path does not exist in the chroot. This tests the opposite case from ChrootTest.ProcMemSelfFdsNoEscapeProcOpen, which tests what path is returned when the file is inside the chroot. PiperOrigin-RevId: 531532246 --- test/syscalls/linux/chroot.cc | 42 +++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/test/syscalls/linux/chroot.cc b/test/syscalls/linux/chroot.cc index e9462338f..31debb412 100644 --- a/test/syscalls/linux/chroot.cc +++ b/test/syscalls/linux/chroot.cc @@ -306,6 +306,48 @@ TEST(ChrootTest, ProcMemSelfFdsNoEscapeProcOpen) { EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); } +// This test will verify that when you hold a fd to proc before entering +// a chroot that any files outside the chroot will appear rooted outside the +// chroot when examining /proc/self/fd/{num}. +TEST(ChrootTest, ProcMemSelfFdsYesEscapeProcOpen) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT))); + + // Get a FD to /proc before we enter the chroot. + const FileDescriptor proc = + ASSERT_NO_ERRNO_AND_VALUE(Open("/proc", O_RDONLY)); + + const auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + const std::string temp_dir_path = temp_dir.path(); + + auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor temp_fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(temp_file.path(), O_RDONLY)); + + const auto rest = [&] { + // Enter the chroot directory. + TEST_CHECK_SUCCESS(chroot(temp_dir_path.c_str())); + + // Examine /proc/self/fd/{temp_fd} to see if it exposes the fact that we're + // inside a chroot, the path should be outside the chroot. + constexpr char kSelfFdRelpath[] = "self/fd/"; + char path_buf[20]; + strcpy(path_buf, kSelfFdRelpath); // NOLINT: need async-signal-safety + TEST_CHECK(SafeItoa(temp_fd.get(), path_buf + sizeof(kSelfFdRelpath) - 1, + sizeof(path_buf) - (sizeof(kSelfFdRelpath) - 1), 10)); + char buf[1024] = {}; + size_t bytes_read = 0; + TEST_CHECK_SUCCESS( + bytes_read = readlinkat(proc.get(), path_buf, buf, sizeof(buf) - 1)); + + // The link should resolve to something. + TEST_CHECK(bytes_read > 0); + + // Assert that the link contains full path. + TEST_CHECK(strcmp(buf, temp_file.path().c_str()) == 0); + }; + EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0)); +} + // This test will verify that a file inside a chroot when mmapped will not // expose the full file path via /proc/self/maps and instead honor the chroot. TEST(ChrootTest, ProcMemSelfMapsNoEscapeProcOpen) {