// Copyright 2021 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <errno.h>
#include <fcntl.h>
#include <linux/capability.h>
#include <stddef.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <syscall.h>
#include <unistd.h>

#include <algorithm>
#include <functional>
#include <string>
#include <vector>

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/cleanup/cleanup.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "test/util/capability_util.h"
#include "test/util/file_descriptor.h"
#include "test/util/fs_util.h"
#include "test/util/linux_capability_util.h"
#include "test/util/logging.h"
#include "test/util/mount_util.h"
#include "test/util/multiprocess_util.h"
#include "test/util/posix_error.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"

namespace gvisor {
namespace testing {

namespace {

constexpr char kTmpfs[] = "tmpfs";

TEST(PivotRootTest, Success) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));

  auto root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  EXPECT_THAT(mount("", root.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path()));
  EXPECT_THAT(mount("", new_root.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  const std::string new_root_path =
      absl::StrCat("/", Basename(new_root.path()));
  auto put_old =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(new_root.path()));
  const std::string put_old_path =
      absl::StrCat(new_root_path, "/", Basename(put_old.path()));

  const auto rest = [&] {
    TEST_CHECK_SUCCESS(chroot(root.path().c_str()));
    TEST_CHECK_SUCCESS(
        syscall(__NR_pivot_root, new_root_path.c_str(), put_old_path.c_str()));
  };
  EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
}

TEST(PivotRootTest, CreatesNewRoot) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));

  auto root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  EXPECT_THAT(mount("", root.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path()));
  EXPECT_THAT(mount("", new_root.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  const std::string new_root_path =
      absl::StrCat("/", Basename(new_root.path()));
  auto put_old =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(new_root.path()));
  const std::string put_old_path =
      absl::StrCat(new_root_path, "/", Basename(put_old.path()));
  auto file_in_new_root =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(new_root.path()));
  const std::string file_in_new_root_path = file_in_new_root.path();
  const std::string file_in_new_root_new_path =
      absl::StrCat("/", Basename(file_in_new_root_path));

  const auto rest = [&] {
    TEST_CHECK_SUCCESS(chroot(root.path().c_str()));
    // pivot_root and switch into new_root.
    TEST_CHECK_SUCCESS(
        syscall(__NR_pivot_root, new_root_path.c_str(), put_old_path.c_str()));
    TEST_CHECK_SUCCESS(chdir("/"));
    // Should not be able to stat file by its full path.
    char buf[1024];
    struct stat statbuf;
    TEST_CHECK_ERRNO(stat(file_in_new_root_path.c_str(), &statbuf), ENOENT);
    // Should be able to stat file at new rooted path.
    TEST_CHECK_SUCCESS(stat(file_in_new_root_new_path.c_str(), &statbuf));
    // getcwd should return "/".
    TEST_CHECK_SUCCESS(syscall(__NR_getcwd, buf, sizeof(buf)));
    TEST_PCHECK(strcmp(buf, "/") == 0);
    // Statting '.', '..', '/', and '/..' all return the same dev and inode.
    struct stat statbuf_dot;
    TEST_CHECK_SUCCESS(stat(".", &statbuf_dot));
    struct stat statbuf_dotdot;
    TEST_CHECK_SUCCESS(stat("..", &statbuf_dotdot));
    TEST_CHECK(statbuf_dot.st_dev == statbuf_dotdot.st_dev);
    TEST_CHECK(statbuf_dot.st_ino == statbuf_dotdot.st_ino);
    struct stat statbuf_slash;
    TEST_CHECK_SUCCESS(stat("/", &statbuf_slash));
    TEST_CHECK(statbuf_dot.st_dev == statbuf_slash.st_dev);
    TEST_CHECK(statbuf_dot.st_ino == statbuf_slash.st_ino);
    struct stat statbuf_slashdotdot;
    TEST_CHECK_SUCCESS(stat("/..", &statbuf_slashdotdot));
    TEST_CHECK(statbuf_dot.st_dev == statbuf_slashdotdot.st_dev);
    TEST_CHECK(statbuf_dot.st_ino == statbuf_slashdotdot.st_ino);
  };
  EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
}

TEST(PivotRootTest, MovesOldRoot) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));

  auto root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  EXPECT_THAT(mount("", root.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path()));
  EXPECT_THAT(mount("", new_root.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  const std::string new_root_path =
      absl::StrCat("/", Basename(new_root.path()));
  auto put_old =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(new_root.path()));
  const std::string put_old_path =
      absl::StrCat(new_root_path, "/", Basename(put_old.path()));

  const std::string old_root_new_path =
      absl::StrCat("/", Basename(put_old_path));

  const auto rest = [&] {
    TEST_CHECK_SUCCESS(chroot(root.path().c_str()));
    struct stat statbuf_oldroot;
    TEST_CHECK_SUCCESS(stat("/", &statbuf_oldroot));
    // pivot_root and switch into new_root.
    TEST_CHECK_SUCCESS(
        syscall(__NR_pivot_root, new_root_path.c_str(), put_old_path.c_str()));
    TEST_CHECK_SUCCESS(chdir("/"));
    // Should not be able to stat file by its full path.
    struct stat statbuf;
    TEST_CHECK_ERRNO(stat(put_old_path.c_str(), &statbuf), ENOENT);
    // Should be able to chdir to old root.
    TEST_CHECK_SUCCESS(chdir(old_root_new_path.c_str()));
    // Statting the root dir from before pivot_root and the put_old location
    // should return the same inode and device.
    struct stat statbuf_dot;
    TEST_CHECK_SUCCESS(stat(".", &statbuf_dot));
    TEST_CHECK(statbuf_dot.st_ino == statbuf_oldroot.st_ino);
    TEST_CHECK(statbuf_dot.st_dev == statbuf_oldroot.st_dev);
  };
  EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
}

TEST(PivotRootTest, ChangesCwdForAllProcesses) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));

  auto root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  EXPECT_THAT(mount("", root.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path()));
  EXPECT_THAT(mount("", new_root.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  const std::string new_root_path =
      absl::StrCat("/", Basename(new_root.path()));
  auto put_old =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(new_root.path()));
  const std::string put_old_path =
      absl::StrCat(new_root_path, "/", Basename(put_old.path()));
  const std::string old_root_new_path =
      absl::StrCat("/", Basename(put_old_path));

  struct stat statbuf_newroot;
  TEST_CHECK_SUCCESS(stat(new_root.path().c_str(), &statbuf_newroot));
  // Change cwd to the root path.
  chdir(root.path().c_str());
  const auto rest = [&] {
    TEST_CHECK_SUCCESS(chroot(root.path().c_str()));
    TEST_CHECK_SUCCESS(
        syscall(__NR_pivot_root, new_root_path.c_str(), put_old_path.c_str()));
  };
  EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
  // pivot_root should change the cwd/root directory all threads and processes
  // in the current mount namespace if they pointed the old root.
  struct stat statbuf_cwd_after_syscall;
  EXPECT_THAT(stat(".", &statbuf_cwd_after_syscall), SyscallSucceeds());
  EXPECT_EQ(statbuf_cwd_after_syscall.st_ino, statbuf_newroot.st_ino);
  EXPECT_EQ(statbuf_cwd_after_syscall.st_dev, statbuf_newroot.st_dev);
}

TEST(PivotRootTest, DotDot) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));

  auto root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  EXPECT_THAT(mount("", root.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path()));
  EXPECT_THAT(mount("", new_root.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  const std::string new_root_path =
      absl::StrCat("/", Basename(new_root.path()));

  auto file_in_new_root =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(new_root.path()));
  const std::string file_in_new_root_path =
      std::string(Basename(file_in_new_root.path()));

  const auto rest = [&] {
    TEST_CHECK_SUCCESS(chroot(root.path().c_str()));
    TEST_CHECK_SUCCESS(chdir(new_root_path.c_str()));
    // pivot_root should be able to stack put_old on top of new_root. This
    // allows users to pivot_root without creating a temp directory.
    TEST_CHECK_SUCCESS(syscall(__NR_pivot_root, ".", "."));
    TEST_CHECK_SUCCESS(umount2(".", MNT_DETACH));
    struct stat statbuf;
    TEST_CHECK_SUCCESS(stat(file_in_new_root_path.c_str(), &statbuf));
  };
  EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
}

TEST(PivotRootTest, NotDir) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
  auto file2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
  auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  EXPECT_THAT(
      syscall(__NR_pivot_root, file1.path().c_str(), file2.path().c_str()),
      SyscallFailsWithErrno(ENOTDIR));
  EXPECT_THAT(
      syscall(__NR_pivot_root, file1.path().c_str(), dir.path().c_str()),
      SyscallFailsWithErrno(ENOTDIR));
  EXPECT_THAT(
      syscall(__NR_pivot_root, dir.path().c_str(), file2.path().c_str()),
      SyscallFailsWithErrno(ENOTDIR));
}

TEST(PivotRootTest, NotExist) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  EXPECT_THAT(syscall(__NR_pivot_root, "/foo/bar", "/bar/baz"),
              SyscallFailsWithErrno(ENOENT));
  EXPECT_THAT(syscall(__NR_pivot_root, dir.path().c_str(), "/bar/baz"),
              SyscallFailsWithErrno(ENOENT));
  EXPECT_THAT(syscall(__NR_pivot_root, "/foo/bar", dir.path().c_str()),
              SyscallFailsWithErrno(ENOENT));
}

TEST(PivotRootTest, WithoutCapability) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETPCAP)));

  auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  const std::string new_root_path = new_root.path();
  EXPECT_THAT(mount("", new_root_path.c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  auto put_old =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(new_root_path));
  const std::string put_old_path = put_old.path();

  AutoCapability cap(CAP_SYS_ADMIN, false);
  EXPECT_THAT(
      syscall(__NR_pivot_root, new_root_path.c_str(), put_old_path.c_str()),
      SyscallFailsWithErrno(EPERM));
}

TEST(PivotRootTest, NewRootOnRootMount) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));

  auto root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  EXPECT_THAT(mount("", root.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  auto new_root =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path().c_str()));
  const std::string new_root_path =
      absl::StrCat("/", Basename(new_root.path()));

  auto put_old =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(new_root.path()));
  const std::string put_old_path =
      absl::StrCat(new_root_path, "/", Basename(put_old.path()));

  const auto rest = [&] {
    TEST_CHECK_SUCCESS(chroot(root.path().c_str()));
    TEST_CHECK_ERRNO(
        syscall(__NR_pivot_root, new_root_path.c_str(), put_old_path.c_str()),
        EBUSY);
  };
  EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
}

TEST(PivotRootTest, NewRootNotAMountpoint) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));

  auto root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  EXPECT_THAT(mount("", root.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  // Make sure new_root is on a separate mount, otherwise this is the same
  // as the NewRootOnRootMount test.
  auto mountpoint =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path().c_str()));
  EXPECT_THAT(mount("", mountpoint.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  const std::string mountpoint_path =
      absl::StrCat("/", Basename(mountpoint.path()));
  auto new_root =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(mountpoint.path()));
  const std::string new_root_path =
      absl::StrCat(mountpoint_path, "/", Basename(new_root.path()));
  auto put_old =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(new_root.path()));
  const std::string put_old_path =
      absl::StrCat(new_root_path, "/", Basename(put_old.path()));

  const auto rest = [&] {
    TEST_CHECK_SUCCESS(chroot(root.path().c_str()));
    TEST_CHECK_ERRNO(
        syscall(__NR_pivot_root, new_root_path.c_str(), put_old_path.c_str()),
        EINVAL);
  };
  EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
}

TEST(PivotRootTest, PutOldNotUnderNewRoot) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));

  auto root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  EXPECT_THAT(mount("", root.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path()));
  const std::string new_root_path =
      absl::StrCat("/", Basename(new_root.path()));
  EXPECT_THAT(mount("", new_root.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  auto put_old = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path()));
  const std::string put_old_path = absl::StrCat("/", Basename(put_old.path()));
  EXPECT_THAT(mount("", put_old.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());

  const auto rest = [&] {
    TEST_CHECK_SUCCESS(chroot(root.path().c_str()));
    TEST_CHECK_ERRNO(
        syscall(__NR_pivot_root, new_root_path.c_str(), put_old_path.c_str()),
        EINVAL);
  };
  EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
}

TEST(PivotRootTest, CurrentRootNotAMountPoint) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));

  auto root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path()));
  EXPECT_THAT(mount("", new_root.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  const std::string new_root_path =
      absl::StrCat("/", Basename(new_root.path()));
  auto put_old =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(new_root.path()));
  const std::string put_old_path =
      absl::StrCat(new_root_path, "/", Basename(put_old.path()));

  const auto rest = [&] {
    TEST_CHECK_SUCCESS(chroot(root.path().c_str()));
    TEST_CHECK_ERRNO(
        syscall(__NR_pivot_root, new_root_path.c_str(), put_old_path.c_str()),
        EINVAL);
  };
  EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
}

TEST(PivotRootTest, OnRootFS) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));

  std::vector<ProcMountInfoEntry> mounts =
      ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries());
  bool rootFSFound = false;
  for (const auto& e : mounts) {
    if (e.mount_point == "/" && e.id == e.parent_id) {
      rootFSFound = true;
      break;
    }
  }

  SKIP_IF(!rootFSFound);

  auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  const std::string new_root_path = new_root.path();
  EXPECT_THAT(mount("", new_root_path.c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  auto put_old =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(new_root_path));
  const std::string put_old_path = put_old.path();

  const auto rest = [&] {
    TEST_CHECK_ERRNO(
        syscall(__NR_pivot_root, new_root_path.c_str(), put_old_path.c_str()),
        EINVAL);
  };
  EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
}

TEST(PivotRootTest, OnSharedNewRootParent) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));

  auto root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  EXPECT_THAT(mount("", root.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path()));
  EXPECT_THAT(mount("", new_root.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  const std::string new_root_path = JoinPath("/", Basename(new_root.path()));
  auto put_old =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(new_root.path()));
  const std::string put_old_path =
      JoinPath(new_root_path, "/", Basename(put_old.path()));

  // Fails because parent has propagation type shared.
  EXPECT_THAT(mount(nullptr, root.path().c_str(), nullptr, MS_SHARED, nullptr),
              SyscallSucceeds());
  const auto rest = [&] {
    TEST_CHECK_SUCCESS(chroot(root.path().c_str()));
    TEST_CHECK_ERRNO(
        syscall(__NR_pivot_root, new_root_path.c_str(), put_old_path.c_str()),
        EINVAL);
  };
  EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
}

TEST(PivotRootTest, OnSharedNewRoot) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));

  auto root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  EXPECT_THAT(mount("", root.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path()));
  EXPECT_THAT(mount("", new_root.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  const std::string new_root_path = JoinPath("/", Basename(new_root.path()));
  auto put_old =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(new_root.path()));
  const std::string put_old_path =
      JoinPath(new_root_path, "/", Basename(put_old.path()));

  // Fails because new_root has propagation type shared.
  EXPECT_THAT(
      mount(nullptr, new_root.path().c_str(), nullptr, MS_SHARED, nullptr),
      SyscallSucceeds());
  const auto rest = [&] {
    TEST_CHECK_SUCCESS(chroot(root.path().c_str()));
    TEST_CHECK_ERRNO(
        syscall(__NR_pivot_root, new_root_path.c_str(), put_old_path.c_str()),
        EINVAL);
  };
  EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
}

TEST(PivotRootTest, OnSharedPutOldMountpoint) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));

  auto root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  EXPECT_THAT(mount("", root.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path()));
  EXPECT_THAT(mount("", new_root.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  const std::string new_root_path = JoinPath("/", Basename(new_root.path()));
  auto put_old =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(new_root.path()));
  const std::string put_old_path =
      JoinPath(new_root_path, "/", Basename(put_old.path()));

  // Fails because put_old is a mountpoint and has propagation type shared.
  EXPECT_THAT(mount("", put_old.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  EXPECT_THAT(
      mount(nullptr, put_old.path().c_str(), nullptr, MS_SHARED, nullptr),
      SyscallSucceeds());
  const auto rest = [&] {
    TEST_CHECK_SUCCESS(chroot(root.path().c_str()));
    TEST_CHECK_ERRNO(
        syscall(__NR_pivot_root, new_root_path.c_str(), put_old_path.c_str()),
        EINVAL);
  };
  EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
}

TEST(PivotRootTest, UnreachableNewRootFails) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));

  TempPath outside_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  ASSERT_THAT(mount("", outside_root.path().c_str(), kTmpfs, 0, ""),
              SyscallSucceeds());
  TempPath root =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(outside_root.path()));
  ASSERT_THAT(mount("", root.path().c_str(), kTmpfs, 0, ""), SyscallSucceeds());
  TempPath new_root =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path()));
  ASSERT_THAT(mount("", new_root.path().c_str(), kTmpfs, 0, ""),
              SyscallSucceeds());
  const std::string new_root_path =
      absl::StrCat("/", Basename(new_root.path()));
  TempPath put_old =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(new_root.path()));
  const std::string put_old_path =
      JoinPath(new_root_path, Basename(put_old.path()));
  ASSERT_THAT(chdir(JoinPath(root.path(), "..").c_str()), SyscallSucceeds());

  const std::function<void()> rest = [&] {
    TEST_CHECK_SUCCESS(chroot(root.path().c_str()));
    // "." references the directory outside the chroot.
    TEST_CHECK_ERRNO(syscall(__NR_pivot_root, ".", put_old_path.c_str()),
                     EINVAL);
  };
  EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
}

TEST(PivotRootTest, LockedNewRootFails) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));

  auto root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  ASSERT_THAT(mount("", root.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path()));
  ASSERT_THAT(mount("", new_root.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  const std::string new_root_path = JoinPath("/", Basename(new_root.path()));
  auto put_old =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(new_root.path()));
  const std::string put_old_path =
      JoinPath(new_root_path, "/", Basename(put_old.path()));
  ASSERT_THAT(mount("", put_old.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());

  const std::function<void()> rest = [&] {
    TEST_CHECK_SUCCESS(chroot(root.path().c_str()));
    TEST_CHECK_ERRNO(
        syscall(__NR_pivot_root, new_root_path.c_str(), put_old_path.c_str()),
        EINVAL);
  };
  EXPECT_THAT(InForkedUserMountNamespace([] {}, rest),
              IsPosixErrorOkAndHolds(0));
}

TEST(PivotRootTest, OldRootUnlocked) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_CHROOT)));

  auto root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  ASSERT_THAT(mount("", root.path().c_str(), "tmpfs", 0, "mode=0700"),
              SyscallSucceeds());
  auto new_root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path()));
  const std::string new_root_path = JoinPath("/", Basename(new_root.path()));

  // The root mount will be locked when pivot_root is called but the new_root
  // and put_old mounts won't be.
  const std::function<void()> rest = [&] {
    TEST_CHECK_SUCCESS(chroot(root.path().c_str()));
    TEST_CHECK_SUCCESS(mount("", new_root_path.c_str(), "tmpfs", 0, ""));
    std::string put_old_path = JoinPath(new_root_path, "put_old");
    TEST_CHECK_SUCCESS(mkdir(put_old_path.c_str(), 0700));
    TEST_CHECK_SUCCESS(mount("", put_old_path.c_str(), "tmpfs", 0, ""));
    TEST_CHECK_SUCCESS(
        syscall(__NR_pivot_root, new_root_path.c_str(), put_old_path.c_str()));
    // The old root is no longer locked and can be unmounted.
    TEST_CHECK_SUCCESS(
        umount2(JoinPath("/", Basename(put_old_path)).c_str(), MNT_DETACH));
  };
  EXPECT_THAT(InForkedUserMountNamespace([] {}, rest),
              IsPosixErrorOkAndHolds(0));
}

}  // namespace

}  // namespace testing
}  // namespace gvisor
