gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/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 <algorithm>
    24  #include <string>
    25  #include <vector>
    26  
    27  #include "gmock/gmock.h"
    28  #include "gtest/gtest.h"
    29  #include "absl/cleanup/cleanup.h"
    30  #include "absl/strings/str_cat.h"
    31  #include "absl/strings/str_split.h"
    32  #include "absl/strings/string_view.h"
    33  #include "test/util/capability_util.h"
    34  #include "test/util/file_descriptor.h"
    35  #include "test/util/fs_util.h"
    36  #include "test/util/logging.h"
    37  #include "test/util/mount_util.h"
    38  #include "test/util/multiprocess_util.h"
    39  #include "test/util/temp_path.h"
    40  #include "test/util/test_util.h"
    41  
    42  using ::testing::HasSubstr;
    43  using ::testing::Not;
    44  
    45  namespace gvisor {
    46  namespace testing {
    47  
    48  namespace {
    49  
    50  // Async-signal-safe conversion from integer to string, appending the string
    51  // (including a terminating NUL) to buf, which is a buffer of size len bytes.
    52  // Returns the number of bytes written, or 0 if the buffer is too small.
    53  //
    54  // Preconditions: 2 <= radix <= 16.
    55  template <typename T>
    56  size_t SafeItoa(T val, char* buf, size_t len, int radix) {
    57    size_t n = 0;
    58  #define _WRITE_OR_FAIL(c) \
    59    do {                    \
    60      if (len == 0) {       \
    61        return 0;           \
    62      }                     \
    63      buf[n] = (c);         \
    64      n++;                  \
    65      len--;                \
    66    } while (false)
    67    if (val == 0) {
    68      _WRITE_OR_FAIL('0');
    69    } else {
    70      // Write digits in reverse order, then reverse them at the end.
    71      bool neg = val < 0;
    72      while (val != 0) {
    73        // C/C++ define modulo such that the result is negative if exactly one of
    74        // the dividend or divisor is negative, so this handles both positive and
    75        // negative values.
    76        char c = "fedcba9876543210123456789abcdef"[val % radix + 15];
    77        _WRITE_OR_FAIL(c);
    78        val /= 10;
    79      }
    80      if (neg) {
    81        _WRITE_OR_FAIL('-');
    82      }
    83      std::reverse(buf, buf + n);
    84    }
    85    _WRITE_OR_FAIL('\0');
    86    return n;
    87  #undef _WRITE_OR_FAIL
    88  }
    89  
    90  TEST(ChrootTest, Success) {
    91    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));
    92    auto temp_dir = TempPath::CreateDir().ValueOrDie();
    93    const std::string temp_dir_path = temp_dir.path();
    94  
    95    const auto rest = [&] { TEST_CHECK_SUCCESS(chroot(temp_dir_path.c_str())); };
    96    EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
    97  }
    98  
    99  TEST(ChrootTest, PermissionDenied) {
   100    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));
   101  
   102    // CAP_DAC_READ_SEARCH and CAP_DAC_OVERRIDE may override Execute permission
   103    // on directories.
   104    AutoCapability cap_search(CAP_DAC_READ_SEARCH, false);
   105    AutoCapability cap_override(CAP_DAC_OVERRIDE, false);
   106  
   107    auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(
   108        TempPath::CreateDirWith(GetAbsoluteTestTmpdir(), 0666 /* mode */));
   109    EXPECT_THAT(chroot(temp_dir.path().c_str()), SyscallFailsWithErrno(EACCES));
   110  }
   111  
   112  TEST(ChrootTest, NotDir) {
   113    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));
   114  
   115    auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
   116    EXPECT_THAT(chroot(temp_file.path().c_str()), SyscallFailsWithErrno(ENOTDIR));
   117  }
   118  
   119  TEST(ChrootTest, NotExist) {
   120    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));
   121  
   122    EXPECT_THAT(chroot("/foo/bar"), SyscallFailsWithErrno(ENOENT));
   123  }
   124  
   125  TEST(ChrootTest, WithoutCapability) {
   126    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETPCAP)));
   127  
   128    // Unset CAP_SYS_CHROOT.
   129    AutoCapability cap(CAP_SYS_CHROOT, false);
   130  
   131    auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   132    EXPECT_THAT(chroot(temp_dir.path().c_str()), SyscallFailsWithErrno(EPERM));
   133  }
   134  
   135  TEST(ChrootTest, CreatesNewRoot) {
   136    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));
   137  
   138    // Grab the initial cwd.
   139    char initial_cwd[1024];
   140    ASSERT_THAT(syscall(__NR_getcwd, initial_cwd, sizeof(initial_cwd)),
   141                SyscallSucceeds());
   142  
   143    auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   144    const std::string new_root_path = new_root.path();
   145    auto file_in_new_root =
   146        ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(new_root.path()));
   147    const std::string file_in_new_root_path = file_in_new_root.path();
   148  
   149    const auto rest = [&] {
   150      // chroot into new_root.
   151      TEST_CHECK_SUCCESS(chroot(new_root_path.c_str()));
   152  
   153      // getcwd should return "(unreachable)" followed by the initial_cwd.
   154      char buf[1024];
   155      TEST_CHECK_SUCCESS(syscall(__NR_getcwd, buf, sizeof(buf)));
   156      constexpr char kUnreachablePrefix[] = "(unreachable)";
   157      TEST_CHECK(
   158          strncmp(buf, kUnreachablePrefix, sizeof(kUnreachablePrefix) - 1) == 0);
   159      TEST_CHECK(strcmp(buf + sizeof(kUnreachablePrefix) - 1, initial_cwd) == 0);
   160  
   161      // Should not be able to stat file by its full path.
   162      struct stat statbuf;
   163      TEST_CHECK_ERRNO(stat(file_in_new_root_path.c_str(), &statbuf), ENOENT);
   164  
   165      // Should be able to stat file at new rooted path.
   166      buf[0] = '/';
   167      absl::string_view basename = Basename(file_in_new_root_path);
   168      TEST_CHECK(basename.length() < (sizeof(buf) - 2));
   169      memcpy(buf + 1, basename.data(), basename.length());
   170      buf[basename.length() + 1] = '\0';
   171      TEST_CHECK_SUCCESS(stat(buf, &statbuf));
   172  
   173      // Should be able to stat cwd at '.' even though it's outside root.
   174      TEST_CHECK_SUCCESS(stat(".", &statbuf));
   175  
   176      // chdir into new root.
   177      TEST_CHECK_SUCCESS(chdir("/"));
   178  
   179      // getcwd should return "/".
   180      TEST_CHECK_SUCCESS(syscall(__NR_getcwd, buf, sizeof(buf)));
   181      TEST_PCHECK(strcmp(buf, "/") == 0);
   182  
   183      // Statting '.', '..', '/', and '/..' all return the same dev and inode.
   184      struct stat statbuf_dot;
   185      TEST_CHECK_SUCCESS(stat(".", &statbuf_dot));
   186      struct stat statbuf_dotdot;
   187      TEST_CHECK_SUCCESS(stat("..", &statbuf_dotdot));
   188      TEST_CHECK(statbuf_dot.st_dev == statbuf_dotdot.st_dev);
   189      TEST_CHECK(statbuf_dot.st_ino == statbuf_dotdot.st_ino);
   190      struct stat statbuf_slash;
   191      TEST_CHECK_SUCCESS(stat("/", &statbuf_slash));
   192      TEST_CHECK(statbuf_dot.st_dev == statbuf_slash.st_dev);
   193      TEST_CHECK(statbuf_dot.st_ino == statbuf_slash.st_ino);
   194      struct stat statbuf_slashdotdot;
   195      TEST_CHECK_SUCCESS(stat("/..", &statbuf_slashdotdot));
   196      TEST_CHECK(statbuf_dot.st_dev == statbuf_slashdotdot.st_dev);
   197      TEST_CHECK(statbuf_dot.st_ino == statbuf_slashdotdot.st_ino);
   198    };
   199    EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
   200  }
   201  
   202  TEST(ChrootTest, DotDotFromOpenFD) {
   203    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));
   204  
   205    auto dir_outside_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   206    auto fd = ASSERT_NO_ERRNO_AND_VALUE(
   207        Open(dir_outside_root.path(), O_RDONLY | O_DIRECTORY));
   208    auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   209    const std::string new_root_path = new_root.path();
   210  
   211    const auto rest = [&] {
   212      // chroot into new_root.
   213      TEST_CHECK_SUCCESS(chroot(new_root_path.c_str()));
   214  
   215      // openat on fd with path .. will succeed.
   216      int other_fd;
   217      TEST_CHECK_SUCCESS(other_fd = openat(fd.get(), "..", O_RDONLY));
   218      TEST_CHECK_SUCCESS(close(other_fd));
   219  
   220      // getdents on fd should not error.
   221      char buf[1024];
   222      TEST_CHECK_SUCCESS(syscall(SYS_getdents64, fd.get(), buf, sizeof(buf)));
   223    };
   224    EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
   225  }
   226  
   227  // Test that link resolution in a chroot can escape the root by following an
   228  // open proc fd. Regression test for b/32316719.
   229  TEST(ChrootTest, ProcFdLinkResolutionInChroot) {
   230    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));
   231  
   232    const TempPath file_outside_chroot =
   233        ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
   234    const std::string file_outside_chroot_path = file_outside_chroot.path();
   235    const FileDescriptor fd =
   236        ASSERT_NO_ERRNO_AND_VALUE(Open(file_outside_chroot.path(), O_RDONLY));
   237  
   238    const FileDescriptor proc_fd = ASSERT_NO_ERRNO_AND_VALUE(
   239        Open("/proc", O_DIRECTORY | O_RDONLY | O_CLOEXEC));
   240  
   241    auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   242    const std::string temp_dir_path = temp_dir.path();
   243  
   244    const auto rest = [&] {
   245      TEST_CHECK_SUCCESS(chroot(temp_dir_path.c_str()));
   246  
   247      // Opening relative to an already open fd to a node outside the chroot
   248      // works.
   249      const FileDescriptor proc_self_fd = TEST_CHECK_NO_ERRNO_AND_VALUE(
   250          OpenAt(proc_fd.get(), "self/fd", O_DIRECTORY | O_RDONLY | O_CLOEXEC));
   251  
   252      // Proc fd symlinks can escape the chroot if the fd the symlink refers to
   253      // refers to an object outside the chroot.
   254      char fd_buf[11];
   255      TEST_CHECK(SafeItoa(fd.get(), fd_buf, sizeof(fd_buf), 10));
   256      struct stat s = {};
   257      TEST_CHECK_SUCCESS(fstatat(proc_self_fd.get(), fd_buf, &s, 0));
   258  
   259      // Try to stat the stdin fd. Internally, this is handled differently from a
   260      // proc fd entry pointing to a file, since stdin is backed by a host fd, and
   261      // isn't a walkable path on the filesystem inside the sandbox.
   262      TEST_CHECK_SUCCESS(fstatat(proc_self_fd.get(), "0", &s, 0));
   263    };
   264    EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
   265  }
   266  
   267  // This test will verify that when you hold a fd to proc before entering
   268  // a chroot that any files inside the chroot will appear rooted to the
   269  // base chroot when examining /proc/self/fd/{num}.
   270  TEST(ChrootTest, ProcMemSelfFdsNoEscapeProcOpen) {
   271    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));
   272  
   273    // Get a FD to /proc before we enter the chroot.
   274    const FileDescriptor proc =
   275        ASSERT_NO_ERRNO_AND_VALUE(Open("/proc", O_RDONLY));
   276  
   277    const auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   278    const std::string temp_dir_path = temp_dir.path();
   279  
   280    const auto rest = [&] {
   281      // Enter the chroot directory.
   282      TEST_CHECK_SUCCESS(chroot(temp_dir_path.c_str()));
   283  
   284      // Open a file inside the chroot at /foo.
   285      const FileDescriptor foo =
   286          TEST_CHECK_NO_ERRNO_AND_VALUE(Open("/foo", O_CREAT | O_RDONLY, 0644));
   287  
   288      // Examine /proc/self/fd/{foo_fd} to see if it exposes the fact that we're
   289      // inside a chroot, the path should be /foo and NOT {chroot_dir}/foo.
   290      constexpr char kSelfFdRelpath[] = "self/fd/";
   291      char path_buf[20];
   292      strcpy(path_buf, kSelfFdRelpath);  // NOLINT: need async-signal-safety
   293      TEST_CHECK(SafeItoa(foo.get(), path_buf + sizeof(kSelfFdRelpath) - 1,
   294                          sizeof(path_buf) - (sizeof(kSelfFdRelpath) - 1), 10));
   295      char buf[1024] = {};
   296      size_t bytes_read = 0;
   297      TEST_CHECK_SUCCESS(
   298          bytes_read = readlinkat(proc.get(), path_buf, buf, sizeof(buf) - 1));
   299  
   300      // The link should resolve to something.
   301      TEST_CHECK(bytes_read > 0);
   302  
   303      // Assert that the link doesn't contain the chroot path and is only /foo.
   304      TEST_CHECK(strcmp(buf, "/foo") == 0);
   305    };
   306    EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
   307  }
   308  
   309  // This test will verify that when you hold a fd to proc before entering
   310  // a chroot that any files outside the chroot will appear rooted outside the
   311  // chroot when examining /proc/self/fd/{num}.
   312  TEST(ChrootTest, ProcMemSelfFdsYesEscapeProcOpen) {
   313    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));
   314  
   315    // Get a FD to /proc before we enter the chroot.
   316    const FileDescriptor proc =
   317        ASSERT_NO_ERRNO_AND_VALUE(Open("/proc", O_RDONLY));
   318  
   319    const auto temp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   320    const std::string temp_dir_path = temp_dir.path();
   321  
   322    auto temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
   323    const FileDescriptor temp_fd =
   324        ASSERT_NO_ERRNO_AND_VALUE(Open(temp_file.path(), O_RDONLY));
   325  
   326    const auto rest = [&] {
   327      // Enter the chroot directory.
   328      TEST_CHECK_SUCCESS(chroot(temp_dir_path.c_str()));
   329  
   330      // Examine /proc/self/fd/{temp_fd} to see if it exposes the fact that we're
   331      // inside a chroot, the path should be outside the chroot.
   332      constexpr char kSelfFdRelpath[] = "self/fd/";
   333      char path_buf[20];
   334      strcpy(path_buf, kSelfFdRelpath);  // NOLINT: need async-signal-safety
   335      TEST_CHECK(SafeItoa(temp_fd.get(), path_buf + sizeof(kSelfFdRelpath) - 1,
   336                          sizeof(path_buf) - (sizeof(kSelfFdRelpath) - 1), 10));
   337      char buf[1024] = {};
   338      size_t bytes_read = 0;
   339      TEST_CHECK_SUCCESS(
   340          bytes_read = readlinkat(proc.get(), path_buf, buf, sizeof(buf) - 1));
   341  
   342      // The link should resolve to something.
   343      TEST_CHECK(bytes_read > 0);
   344  
   345      // Assert that the link contains full path.
   346      TEST_CHECK(strcmp(buf, temp_file.path().c_str()) == 0);
   347    };
   348    EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
   349  }
   350  
   351  // This test will verify that a file inside a chroot when mmapped will not
   352  // expose the full file path via /proc/self/maps and instead honor the chroot.
   353  TEST(ChrootTest, ProcMemSelfMapsNoEscapeProcOpen) {
   354    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));
   355    // TODO(b/264306751): Remove once FUSE implements mmap.
   356    SKIP_IF(getenv("GVISOR_FUSE_TEST"));
   357  
   358    // Get a FD to /proc before we enter the chroot.
   359    const FileDescriptor proc =
   360        ASSERT_NO_ERRNO_AND_VALUE(Open("/proc", O_RDONLY));
   361  
   362    const auto temp_dir = TEST_CHECK_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   363    const std::string temp_dir_path = temp_dir.path();
   364  
   365    const auto rest = [&] {
   366      // Enter the chroot directory.
   367      TEST_CHECK_SUCCESS(chroot(temp_dir_path.c_str()));
   368  
   369      // Open a file inside the chroot at /foo.
   370      const FileDescriptor foo =
   371          TEST_CHECK_NO_ERRNO_AND_VALUE(Open("/foo", O_CREAT | O_RDONLY, 0644));
   372  
   373      // Mmap the newly created file.
   374      void* foo_map = mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE,
   375                           MAP_PRIVATE, foo.get(), 0);
   376      TEST_CHECK_SUCCESS(reinterpret_cast<int64_t>(foo_map));
   377  
   378      // Always unmap. Since this function is called between fork() and execve(),
   379      // we can't use gvisor::testing::Cleanup, which uses std::function
   380      // and thus may heap-allocate (which is async-signal-unsafe); instead, use
   381      // absl::Cleanup, which is templated on the callback type.
   382      auto cleanup_map = absl::MakeCleanup(
   383          [&] { TEST_CHECK_SUCCESS(munmap(foo_map, kPageSize)); });
   384  
   385      // Examine /proc/self/maps to be sure that /foo doesn't appear to be
   386      // mapped with the full chroot path.
   387      const FileDescriptor maps = TEST_CHECK_NO_ERRNO_AND_VALUE(
   388          OpenAt(proc.get(), "self/maps", O_RDONLY));
   389  
   390      size_t bytes_read = 0;
   391      char buf[8 * 1024] = {};
   392      TEST_CHECK_SUCCESS(bytes_read = ReadFd(maps.get(), buf, sizeof(buf)));
   393  
   394      // The maps file should have something.
   395      TEST_CHECK(bytes_read > 0);
   396  
   397      // Finally we want to make sure the maps don't contain the chroot path
   398      TEST_CHECK(
   399          !absl::StrContains(absl::string_view(buf, bytes_read), temp_dir_path));
   400    };
   401    EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
   402  }
   403  
   404  // Test that mounts outside the chroot will not appear in /proc/self/mounts or
   405  // /proc/self/mountinfo.
   406  TEST(ChrootTest, ProcMountsMountinfoNoEscape) {
   407    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
   408    SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));
   409  
   410    // Create nested tmpfs mounts.
   411    const auto outer_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   412    const std::string outer_dir_path = outer_dir.path();
   413    const auto outer_mount = ASSERT_NO_ERRNO_AND_VALUE(
   414        Mount("none", outer_dir_path, "tmpfs", 0, "mode=0700", 0));
   415  
   416    const auto inner_dir =
   417        ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(outer_dir_path));
   418    const std::string inner_dir_path = inner_dir.path();
   419    const auto inner_mount = ASSERT_NO_ERRNO_AND_VALUE(
   420        Mount("none", inner_dir_path, "tmpfs", 0, "mode=0700", 0));
   421    const std::string inner_dir_in_outer_chroot_path =
   422        absl::StrCat("/", Basename(inner_dir_path));
   423  
   424    // Filenames that will be checked for mounts, all relative to /proc dir.
   425    std::string paths[3] = {"mounts", "self/mounts", "self/mountinfo"};
   426  
   427    for (const std::string& path : paths) {
   428      // We should have both inner and outer mounts.
   429      const std::string contents =
   430          ASSERT_NO_ERRNO_AND_VALUE(GetContents(JoinPath("/proc", path)));
   431      EXPECT_THAT(contents,
   432                  AllOf(HasSubstr(outer_dir_path), HasSubstr(inner_dir_path)));
   433      // We better have at least two mounts: the mounts we created plus the
   434      // root.
   435      std::vector<absl::string_view> submounts =
   436          absl::StrSplit(contents, '\n', absl::SkipWhitespace());
   437      ASSERT_GT(submounts.size(), 2);
   438    }
   439  
   440    // Get a FD to /proc before we enter the chroot.
   441    const FileDescriptor proc =
   442        ASSERT_NO_ERRNO_AND_VALUE(Open("/proc", O_RDONLY));
   443  
   444    const auto rest = [&] {
   445      // Chroot to outer mount.
   446      TEST_CHECK_SUCCESS(chroot(outer_dir_path.c_str()));
   447  
   448      char buf[8 * 1024];
   449      for (const std::string& path : paths) {
   450        const FileDescriptor proc_file =
   451            TEST_CHECK_NO_ERRNO_AND_VALUE(OpenAt(proc.get(), path, O_RDONLY));
   452  
   453        // Only two mounts visible from this chroot: the inner and outer.  Both
   454        // paths should be relative to the new chroot.
   455        ssize_t n = ReadFd(proc_file.get(), buf, sizeof(buf));
   456        TEST_PCHECK(n >= 0);
   457        buf[n] = '\0';
   458        TEST_CHECK(absl::StrContains(buf, Basename(inner_dir_path)));
   459        TEST_CHECK(!absl::StrContains(buf, outer_dir_path));
   460        TEST_CHECK(!absl::StrContains(buf, inner_dir_path));
   461        TEST_CHECK(std::count(buf, buf + n, '\n') == 2);
   462      }
   463  
   464      // Chroot to inner mount.  We must use an absolute path accessible to our
   465      // chroot.
   466      TEST_CHECK_SUCCESS(chroot(inner_dir_in_outer_chroot_path.c_str()));
   467  
   468      for (const std::string& path : paths) {
   469        const FileDescriptor proc_file =
   470            TEST_CHECK_NO_ERRNO_AND_VALUE(OpenAt(proc.get(), path, O_RDONLY));
   471  
   472        // Only the inner mount visible from this chroot.
   473        ssize_t n = ReadFd(proc_file.get(), buf, sizeof(buf));
   474        TEST_PCHECK(n >= 0);
   475        buf[n] = '\0';
   476        TEST_CHECK(std::count(buf, buf + n, '\n') == 1);
   477      }
   478    };
   479    EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
   480  }
   481  
   482  }  // namespace
   483  
   484  }  // namespace testing
   485  }  // namespace gvisor