diff --git a/pkg/sentry/syscalls/linux/vfs2/mount.go b/pkg/sentry/syscalls/linux/vfs2/mount.go index fd0ab4c76..4fcd9e87c 100644 --- a/pkg/sentry/syscalls/linux/vfs2/mount.go +++ b/pkg/sentry/syscalls/linux/vfs2/mount.go @@ -17,6 +17,7 @@ package vfs2 import ( "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/errors/linuxerr" + "gvisor.dev/gvisor/pkg/fspath" "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/sentry/arch" "gvisor.dev/gvisor/pkg/sentry/kernel" @@ -71,9 +72,8 @@ func Mount(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall return 0, nil, linuxerr.EPERM } - const unsupportedOps = linux.MS_REMOUNT | linux.MS_BIND | - linux.MS_SHARED | linux.MS_PRIVATE | linux.MS_SLAVE | - linux.MS_UNBINDABLE | linux.MS_MOVE + const unsupportedOps = linux.MS_REMOUNT | linux.MS_SHARED | linux.MS_PRIVATE | + linux.MS_SLAVE | linux.MS_UNBINDABLE | linux.MS_MOVE // Silently allow MS_NOSUID, since we don't implement set-id bits // anyway. @@ -109,7 +109,23 @@ func Mount(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall return 0, nil, err } defer target.Release(t) - _, err = t.Kernel().VFS().MountAt(t, creds, source, &target.pop, fsType, &opts) + + if flags&linux.MS_BIND == linux.MS_BIND { + var sourcePath fspath.Path + sourcePath, err = copyInPath(t, sourceAddr) + if err != nil { + return 0, nil, err + } + var sourceTpop taskPathOperation + sourceTpop, err = getTaskPathOperation(t, linux.AT_FDCWD, sourcePath, disallowEmptyPath, nofollowFinalSymlink) + if err != nil { + return 0, nil, err + } + defer sourceTpop.Release(t) + _, err = t.Kernel().VFS().BindAt(t, creds, &sourceTpop.pop, &target.pop) + } else { + _, err = t.Kernel().VFS().MountAt(t, creds, source, &target.pop, fsType, &opts) + } return 0, nil, err } diff --git a/pkg/sentry/vfs/mount.go b/pkg/sentry/vfs/mount.go index 4ef4a90f8..bf5466588 100644 --- a/pkg/sentry/vfs/mount.go +++ b/pkg/sentry/vfs/mount.go @@ -271,6 +271,26 @@ func (vfs *VirtualFilesystem) ConnectMountAt(ctx context.Context, creds *auth.Cr return nil } +// BindAt creates a clone of the source path's parent mount and mounts it at +// the target path. The new mount's root dentry is one pointed to by the source +// path. +// +// TODO(b/249121230): Support recursive bind mounting. +func (vfs *VirtualFilesystem) BindAt(ctx context.Context, creds *auth.Credentials, source, target *PathOperation) (*Mount, error) { + vd, err := vfs.GetDentryAt(ctx, creds, source, &GetDentryOptions{}) + if err != nil { + return nil, err + } + defer vd.DecRef(ctx) + opts := vd.mount.Options() + mnt := vfs.NewDisconnectedMount(vd.mount.fs, vd.dentry, &opts) + defer mnt.DecRef(ctx) + if err := vfs.ConnectMountAt(ctx, creds, mnt, target); err != nil { + return nil, err + } + return mnt, nil +} + // MountAt creates and mounts a Filesystem configured by the given arguments. // The VirtualFilesystem will hold a reference to the Mount until it is // unmounted. diff --git a/test/syscalls/linux/mount.cc b/test/syscalls/linux/mount.cc index bc8d7127b..3ce7cd29c 100644 --- a/test/syscalls/linux/mount.cc +++ b/test/syscalls/linux/mount.cc @@ -759,6 +759,68 @@ TEST(MountTest, TmpfsSizeMmap) { ::testing::KilledBySignal(SIGBUS), ""); EXPECT_THAT(munmap(addr, 2 * kPageSize), SyscallSucceeds()); } + +TEST(MountTest, SimpleBind) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); + + auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const mount = ASSERT_NO_ERRNO_AND_VALUE( + Mount("", dir1.path(), "tmpfs", 0, "mode=0123", 0)); + auto const child1 = + ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); + auto const child2 = + ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); + auto const bind_mount = Mount(dir1.path(), dir2.path(), "", MS_BIND, "", 0); + + // Write to child1 in dir1. + const std::string filename = "foo.txt"; + const std::string contents = "barbaz"; + ASSERT_NO_ERRNO(CreateWithContents(JoinPath(child1.path(), filename), + contents, O_WRONLY)); + // Verify both directories have the same nodes. + std::vector child_names = {std::string(Basename(child1.path())), + std::string(Basename(child2.path()))}; + ASSERT_NO_ERRNO(DirContains(dir1.path(), child_names, {})); + ASSERT_NO_ERRNO(DirContains(dir2.path(), child_names, {})); + + const std::string dir1_filepath = + JoinPath(dir1.path(), Basename(child1.path()), filename); + const std::string dir2_filepath = + JoinPath(dir2.path(), Basename(child1.path()), filename); + + std::string output; + ASSERT_NO_ERRNO(GetContents(dir1_filepath, &output)); + ASSERT_TRUE(output == contents); + ASSERT_NO_ERRNO(GetContents(dir2_filepath, &output)); + ASSERT_TRUE(output == contents); +} + +TEST(MountTest, BindToSelf) { + // Test that we can turn a normal directory into a mount with MS_BIND. + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); + auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + + const std::vector mounts_before = + ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountsEntries()); + for (const auto& e : mounts_before) { + ASSERT_TRUE(e.mount_point != dir.path()); + } + + auto const mount = ASSERT_NO_ERRNO_AND_VALUE( + Mount(dir.path(), dir.path(), "", MS_BIND, "", 0)); + + const std::vector mounts_after = + ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountsEntries()); + bool found = false; + for (const auto& e : mounts_after) { + if (e.mount_point == dir.path()) { + found = true; + } + } + ASSERT_TRUE(found); +} + } // namespace } // namespace testing