diff --git a/pkg/sentry/vfs/mount.go b/pkg/sentry/vfs/mount.go index 2918bbd9e..1eaa21402 100644 --- a/pkg/sentry/vfs/mount.go +++ b/pkg/sentry/vfs/mount.go @@ -1131,6 +1131,10 @@ func (vfs *VirtualFilesystem) PivotRoot(ctx context.Context, creds *auth.Credent if !vfs.isPathReachable(ctx, newRoot, putOld) { return newRoot, oldRoot, linuxerr.EINVAL } + // the new root must be at or underneath the current root. + if !vfs.isPathReachable(ctx, oldRoot, newRoot) { + return newRoot, oldRoot, linuxerr.EINVAL + } // The current root directory must be a mountpoint // (in the case it has been chrooted). if oldRoot.mount.root != oldRoot.dentry { diff --git a/test/syscalls/linux/pivot_root.cc b/test/syscalls/linux/pivot_root.cc index d99c36089..998ba4381 100644 --- a/test/syscalls/linux/pivot_root.cc +++ b/test/syscalls/linux/pivot_root.cc @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -35,6 +36,7 @@ #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" @@ -47,6 +49,8 @@ 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))); @@ -514,6 +518,37 @@ TEST(PivotRootTest, OnSharedPutOldMountpoint) { 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 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)); +} + } // namespace } // namespace testing