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