mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
e0435b9a53
PiperOrigin-RevId: 721421397
612 lines
25 KiB
C++
612 lines
25 KiB
C++
// 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
|