diff --git a/pkg/sentry/fsimpl/devtmpfs/devtmpfs.go b/pkg/sentry/fsimpl/devtmpfs/devtmpfs.go index b620de0d1..33c975dc1 100644 --- a/pkg/sentry/fsimpl/devtmpfs/devtmpfs.go +++ b/pkg/sentry/fsimpl/devtmpfs/devtmpfs.go @@ -91,7 +91,7 @@ type Accessor struct { // NewAccessor returns an Accessor that supports creation of device special // files in the devtmpfs instance registered with name fsTypeName in vfsObj. func NewAccessor(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, fsTypeName string) (*Accessor, error) { - mntns, err := vfsObj.NewMountNamespace(ctx, creds, "devtmpfs" /* source */, fsTypeName, &vfs.MountOptions{}) + mntns, err := vfsObj.NewMountNamespace(ctx, creds, "devtmpfs" /* source */, fsTypeName, &vfs.MountOptions{}, nil) if err != nil { return nil, err } diff --git a/pkg/sentry/fsimpl/devtmpfs/devtmpfs_test.go b/pkg/sentry/fsimpl/devtmpfs/devtmpfs_test.go index e058eda7a..7d80db7fd 100644 --- a/pkg/sentry/fsimpl/devtmpfs/devtmpfs_test.go +++ b/pkg/sentry/fsimpl/devtmpfs/devtmpfs_test.go @@ -48,7 +48,7 @@ func setupDevtmpfs(t *testing.T) (context.Context, *auth.Credentials, *vfs.Virtu }) // Create a test mount namespace with devtmpfs mounted at "/dev". - mntns, err := vfsObj.NewMountNamespace(ctx, creds, "tmpfs" /* source */, "tmpfs" /* fsTypeName */, &vfs.MountOptions{}) + mntns, err := vfsObj.NewMountNamespace(ctx, creds, "tmpfs" /* source */, "tmpfs" /* fsTypeName */, &vfs.MountOptions{}, nil) if err != nil { t.Fatalf("failed to create tmpfs root mount: %v", err) } diff --git a/pkg/sentry/fsimpl/fuse/utils_test.go b/pkg/sentry/fsimpl/fuse/utils_test.go index 14f2422de..26f139dc9 100644 --- a/pkg/sentry/fsimpl/fuse/utils_test.go +++ b/pkg/sentry/fsimpl/fuse/utils_test.go @@ -38,7 +38,7 @@ func setup(t *testing.T) *testutil.System { AllowUserMount: true, }) - mntns, err := k.VFS().NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}) + mntns, err := k.VFS().NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}, nil) if err != nil { t.Fatalf("NewMountNamespace(): %v", err) } diff --git a/pkg/sentry/fsimpl/kernfs/kernfs_test.go b/pkg/sentry/fsimpl/kernfs/kernfs_test.go index 1ece17057..e67537bda 100644 --- a/pkg/sentry/fsimpl/kernfs/kernfs_test.go +++ b/pkg/sentry/fsimpl/kernfs/kernfs_test.go @@ -53,7 +53,7 @@ func newTestSystem(t *testing.T, rootFn RootDentryFn) *testutil.System { v.MustRegisterFilesystemType("testfs", &fsType{rootFn: rootFn}, &vfs.RegisterFilesystemTypeOptions{ AllowUserMount: true, }) - mns, err := v.NewMountNamespace(ctx, creds, "", "testfs", &vfs.MountOptions{}) + mns, err := v.NewMountNamespace(ctx, creds, "", "testfs", &vfs.MountOptions{}, nil) if err != nil { t.Fatalf("Failed to create testfs root mount: %v", err) } diff --git a/pkg/sentry/fsimpl/nsfs/nsfs.go b/pkg/sentry/fsimpl/nsfs/nsfs.go index eb2466db8..32a3b11e8 100644 --- a/pkg/sentry/fsimpl/nsfs/nsfs.go +++ b/pkg/sentry/fsimpl/nsfs/nsfs.go @@ -86,7 +86,7 @@ type Inode struct { inodeRefs locks vfs.FileLocks - namespace Namespace + namespace vfs.Namespace mnt *vfs.Mount } @@ -101,14 +101,8 @@ func (i *Inode) Keep() bool { return false } -// Namespace is the namespace interface. -type Namespace interface { - Type() string - Destroy(ctx context.Context) -} - // NewInode creates a new nsfs inode. -func NewInode(ctx context.Context, mnt *vfs.Mount, namespace Namespace) *Inode { +func NewInode(ctx context.Context, mnt *vfs.Mount, namespace vfs.Namespace) *Inode { fs := mnt.Filesystem().Impl().(*filesystem) creds := auth.CredentialsFromContext(ctx) i := &Inode{ @@ -123,7 +117,7 @@ func NewInode(ctx context.Context, mnt *vfs.Mount, namespace Namespace) *Inode { const nsfsMode = linux.S_IFREG | linux.ModeUserRead | linux.ModeGroupRead | linux.ModeOtherRead // Namespace returns the namespace associated with the inode. -func (i *Inode) Namespace() Namespace { +func (i *Inode) Namespace() vfs.Namespace { return i.namespace } diff --git a/pkg/sentry/fsimpl/proc/task.go b/pkg/sentry/fsimpl/proc/task.go index eef55957b..de725d794 100644 --- a/pkg/sentry/fsimpl/proc/task.go +++ b/pkg/sentry/fsimpl/proc/task.go @@ -73,6 +73,7 @@ func (fs *filesystem) newTaskInode(ctx context.Context, task *kernel.Task, pidns "net": fs.newTaskNetDir(ctx, task), "ns": fs.newTaskOwnedDir(ctx, task, fs.NextIno(), 0511, map[string]kernfs.Inode{ "net": fs.newNamespaceSymlink(ctx, task, fs.NextIno(), linux.CLONE_NEWNET), + "mnt": fs.newNamespaceSymlink(ctx, task, fs.NextIno(), linux.CLONE_NEWNS), "pid": fs.newPIDNamespaceSymlink(ctx, task, fs.NextIno()), "user": fs.newFakeNamespaceSymlink(ctx, task, fs.NextIno(), "user"), "ipc": fs.newNamespaceSymlink(ctx, task, fs.NextIno(), linux.CLONE_NEWIPC), diff --git a/pkg/sentry/fsimpl/proc/task_files.go b/pkg/sentry/fsimpl/proc/task_files.go index f2f6e3040..dff44cf78 100644 --- a/pkg/sentry/fsimpl/proc/task_files.go +++ b/pkg/sentry/fsimpl/proc/task_files.go @@ -1273,12 +1273,23 @@ func (fs *filesystem) newFakeNamespaceSymlink(ctx context.Context, task *kernel. func (s *namespaceSymlink) getInode(t *kernel.Task) *nsfs.Inode { switch s.nsType { case linux.CLONE_NEWNET: - return t.GetNetworkNamespace().GetInode() + netns := t.GetNetworkNamespace() + if netns == nil { + return nil + } + return netns.GetInode() case linux.CLONE_NEWIPC: if ipcns := t.GetIPCNamespace(); ipcns != nil { return ipcns.GetInode() } return nil + case linux.CLONE_NEWNS: + mntns := t.GetMountNamespace() + if mntns == nil { + return nil + } + inode, _ := mntns.Refs.(*nsfs.Inode) + return inode default: panic("unknown namespace") } diff --git a/pkg/sentry/fsimpl/proc/tasks_test.go b/pkg/sentry/fsimpl/proc/tasks_test.go index 6d0276670..ae9fa7c3c 100644 --- a/pkg/sentry/fsimpl/proc/tasks_test.go +++ b/pkg/sentry/fsimpl/proc/tasks_test.go @@ -110,7 +110,7 @@ func setup(t *testing.T) *testutil.System { AllowUserMount: true, }) - mntns, err := k.VFS().NewMountNamespace(ctx, creds, "", tmpfs.Name, &vfs.MountOptions{}) + mntns, err := k.VFS().NewMountNamespace(ctx, creds, "", tmpfs.Name, &vfs.MountOptions{}, k) if err != nil { t.Fatalf("NewMountNamespace(): %v", err) } diff --git a/pkg/sentry/fsimpl/sys/sys_test.go b/pkg/sentry/fsimpl/sys/sys_test.go index 343843d79..d70374b68 100644 --- a/pkg/sentry/fsimpl/sys/sys_test.go +++ b/pkg/sentry/fsimpl/sys/sys_test.go @@ -38,7 +38,7 @@ func newTestSystem(t *testing.T) *testutil.System { AllowUserMount: true, }) - mns, err := k.VFS().NewMountNamespace(ctx, creds, "", sys.Name, &vfs.MountOptions{}) + mns, err := k.VFS().NewMountNamespace(ctx, creds, "", sys.Name, &vfs.MountOptions{}, nil) if err != nil { t.Fatalf("Failed to create new mount namespace: %v", err) } diff --git a/pkg/sentry/fsimpl/tmpfs/benchmark_test.go b/pkg/sentry/fsimpl/tmpfs/benchmark_test.go index 8399caf36..33389d489 100644 --- a/pkg/sentry/fsimpl/tmpfs/benchmark_test.go +++ b/pkg/sentry/fsimpl/tmpfs/benchmark_test.go @@ -62,7 +62,7 @@ func BenchmarkTmpfsStat(b *testing.B) { vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{ AllowUserMount: true, }) - mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}) + mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}, nil) if err != nil { b.Fatalf("failed to create tmpfs root mount: %v", err) } @@ -154,7 +154,7 @@ func BenchmarkTmpfsMountStat(b *testing.B) { vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{ AllowUserMount: true, }) - mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}) + mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}, nil) if err != nil { b.Fatalf("failed to create tmpfs root mount: %v", err) } diff --git a/pkg/sentry/fsimpl/tmpfs/pipe_test.go b/pkg/sentry/fsimpl/tmpfs/pipe_test.go index 99afd9817..617e58172 100644 --- a/pkg/sentry/fsimpl/tmpfs/pipe_test.go +++ b/pkg/sentry/fsimpl/tmpfs/pipe_test.go @@ -158,7 +158,7 @@ func setup(t *testing.T) (context.Context, *auth.Credentials, *vfs.VirtualFilesy vfsObj.MustRegisterFilesystemType("tmpfs", FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{ AllowUserMount: true, }) - mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}) + mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}, nil) if err != nil { t.Fatalf("failed to create tmpfs root mount: %v", err) } diff --git a/pkg/sentry/fsimpl/tmpfs/tmpfs_test.go b/pkg/sentry/fsimpl/tmpfs/tmpfs_test.go index df3ad2286..eaf08747b 100644 --- a/pkg/sentry/fsimpl/tmpfs/tmpfs_test.go +++ b/pkg/sentry/fsimpl/tmpfs/tmpfs_test.go @@ -42,7 +42,7 @@ func newTmpfsRoot(ctx context.Context) (*vfs.VirtualFilesystem, vfs.VirtualDentr vfsObj.MustRegisterFilesystemType("tmpfs", FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{ AllowUserMount: true, }) - mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}) + mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}, nil) if err != nil { return nil, vfs.VirtualDentry{}, nil, fmt.Errorf("failed to create tmpfs root mount: %v", err) } diff --git a/pkg/sentry/fsimpl/user/user_test.go b/pkg/sentry/fsimpl/user/user_test.go index 30ea44fee..2dccbf1a9 100644 --- a/pkg/sentry/fsimpl/user/user_test.go +++ b/pkg/sentry/fsimpl/user/user_test.go @@ -120,7 +120,7 @@ func TestGetExecUserHome(t *testing.T) { vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{ AllowUserMount: true, }) - mns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}) + mns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}, nil) if err != nil { t.Fatalf("failed to create tmpfs root mount: %v", err) } diff --git a/pkg/sentry/kernel/kernel.go b/pkg/sentry/kernel/kernel.go index 46d10fd65..8455914e1 100644 --- a/pkg/sentry/kernel/kernel.go +++ b/pkg/sentry/kernel/kernel.go @@ -46,6 +46,7 @@ import ( "gvisor.dev/gvisor/pkg/eventchannel" "gvisor.dev/gvisor/pkg/fspath" "gvisor.dev/gvisor/pkg/log" + "gvisor.dev/gvisor/pkg/refs" "gvisor.dev/gvisor/pkg/sentry/arch" "gvisor.dev/gvisor/pkg/sentry/fsimpl/nsfs" "gvisor.dev/gvisor/pkg/sentry/fsimpl/pipefs" @@ -448,10 +449,9 @@ func (k *Kernel) Init(args InitKernelArgs) error { return fmt.Errorf("failed to create nsfs filesystem: %v", err) } defer nsfsFilesystem.DecRef(ctx) - nsfsMount := k.vfs.NewDisconnectedMount(nsfsFilesystem, nil, &vfs.MountOptions{}) - k.nsfsMount = nsfsMount - k.rootNetworkNamespace.SetInode(nsfs.NewInode(ctx, nsfsMount, k.rootNetworkNamespace)) - k.rootIPCNamespace.SetInode(nsfs.NewInode(ctx, nsfsMount, k.rootIPCNamespace)) + k.nsfsMount = k.vfs.NewDisconnectedMount(nsfsFilesystem, nil, &vfs.MountOptions{}) + k.rootNetworkNamespace.SetInode(nsfs.NewInode(ctx, k.nsfsMount, k.rootNetworkNamespace)) + k.rootIPCNamespace.SetInode(nsfs.NewInode(ctx, k.nsfsMount, k.rootIPCNamespace)) tmpfsOpts := vfs.GetFilesystemOptions{ InternalData: tmpfs.FilesystemOpts{ @@ -1621,9 +1621,9 @@ func (k *Kernel) PipeMount() *vfs.Mount { return k.pipeMount } -// NsfsMount returns the nsfs mount. -func (k *Kernel) NsfsMount() *vfs.Mount { - return k.nsfsMount +// GetNamespaceInode returns a new nsfs inode which serves as a reference counter for the namespace. +func (k *Kernel) GetNamespaceInode(ctx context.Context, ns vfs.Namespace) refs.TryRefCounter { + return nsfs.NewInode(ctx, k.nsfsMount, ns) } // ShmMount returns the tmpfs mount. diff --git a/pkg/sentry/kernel/task.go b/pkg/sentry/kernel/task.go index 200dc1f9c..7cb91cbda 100644 --- a/pkg/sentry/kernel/task.go +++ b/pkg/sentry/kernel/task.go @@ -783,14 +783,22 @@ func (t *Task) WithMuLocked(f func(*Task)) { t.mu.Unlock() } -// MountNamespace returns t's MountNamespace. A reference is taken on the -// returned mount namespace. +// MountNamespace returns t's MountNamespace. func (t *Task) MountNamespace() *vfs.MountNamespace { t.mu.Lock() defer t.mu.Unlock() return t.mountNamespace } +// GetMountNamespace returns t's MountNamespace. A reference is taken on the +// returned mount namespace. +func (t *Task) GetMountNamespace() *vfs.MountNamespace { + t.mu.Lock() + defer t.mu.Unlock() + t.mountNamespace.IncRef() + return t.mountNamespace +} + // AbstractSockets returns t's AbstractSocketNamespace. func (t *Task) AbstractSockets() *AbstractSocketNamespace { return t.abstractSockets diff --git a/pkg/sentry/kernel/task_clone.go b/pkg/sentry/kernel/task_clone.go index 8ecc2bf35..b89e8cd0e 100644 --- a/pkg/sentry/kernel/task_clone.go +++ b/pkg/sentry/kernel/task_clone.go @@ -173,7 +173,7 @@ func (t *Task) Clone(args *linux.CloneArgs) (ThreadID, *SyscallControl, error) { mntns := t.mountNamespace if args.Flags&linux.CLONE_NEWNS != 0 { var err error - mntns, err = t.k.vfs.CloneMountNamespace(t, creds, mntns, &fsContext.root, &fsContext.cwd) + mntns, err = t.k.vfs.CloneMountNamespace(t, creds, mntns, &fsContext.root, &fsContext.cwd, t.k) if err != nil { return 0, nil, err } @@ -463,6 +463,38 @@ func (t *Task) Setns(fd *vfs.FileDescription, flags int32) error { t.mu.Unlock() oldNS.DecRef(t) return nil + case *vfs.MountNamespace: + if flags != 0 && flags != linux.CLONE_NEWNS { + return linuxerr.EINVAL + } + if !t.HasCapabilityIn(linux.CAP_SYS_ADMIN, ns.Owner) || + !t.Credentials().HasCapability(linux.CAP_SYS_CHROOT) || + !t.Credentials().HasCapability(linux.CAP_SYS_ADMIN) { + return linuxerr.EPERM + } + oldFSContext := t.fsContext + // The current task has to be an exclusive owner of its fs context. + if oldFSContext.ReadRefs() != 1 { + return linuxerr.EINVAL + } + fsContext := oldFSContext.Fork() + fsContext.root.DecRef(t) + fsContext.cwd.DecRef(t) + vd := ns.Root() + vd.IncRef() + fsContext.root = vd + vd.IncRef() + fsContext.cwd = vd + + oldNS := t.mountNamespace + ns.IncRef() + t.mu.Lock() + t.mountNamespace = ns + t.fsContext = fsContext + t.mu.Unlock() + oldNS.DecRef(t) + oldFSContext.DecRef(t) + return nil default: return linuxerr.EINVAL } @@ -584,7 +616,7 @@ func (t *Task) Unshare(flags int32) error { return linuxerr.EPERM } oldMountNS := t.mountNamespace - mntns, err := t.k.vfs.CloneMountNamespace(t, creds, oldMountNS, &t.fsContext.root, &t.fsContext.cwd) + mntns, err := t.k.vfs.CloneMountNamespace(t, creds, oldMountNS, &t.fsContext.root, &t.fsContext.cwd, t.k) if err != nil { return err } diff --git a/pkg/sentry/vfs/BUILD b/pkg/sentry/vfs/BUILD index 507837bd3..fc416f95f 100644 --- a/pkg/sentry/vfs/BUILD +++ b/pkg/sentry/vfs/BUILD @@ -93,7 +93,7 @@ go_template_instance( name = "mount_namespace_refs", out = "mount_namespace_refs.go", package = "vfs", - prefix = "MountNamespace", + prefix = "namespace", template = "//pkg/refs:refs_template", types = { "T": "MountNamespace", @@ -145,6 +145,7 @@ go_library( "mount.go", "mount_namespace_refs.go", "mount_unsafe.go", + "namespace.go", "opath.go", "options.go", "pathname.go", diff --git a/pkg/sentry/vfs/mount.go b/pkg/sentry/vfs/mount.go index bf3f6e0b4..9591ecac6 100644 --- a/pkg/sentry/vfs/mount.go +++ b/pkg/sentry/vfs/mount.go @@ -159,147 +159,6 @@ func (mnt *Mount) generateOptionalTags() string { return optional } -// A MountNamespace is a collection of Mounts.// -// MountNamespaces are reference-counted. Unless otherwise specified, all -// MountNamespace methods require that a reference is held. -// -// MountNamespace is analogous to Linux's struct mnt_namespace. -// -// +stateify savable -type MountNamespace struct { - MountNamespaceRefs - - // Owner is the usernamespace that owns this mount namespace. - Owner *auth.UserNamespace - - // root is the MountNamespace's root mount. - root *Mount - - // mountpoints maps all Dentries which are mount points in this namespace - // to the number of Mounts for which they are mount points. mountpoints is - // protected by VirtualFilesystem.mountMu. - // - // mountpoints is used to determine if a Dentry can be moved or removed - // (which requires that the Dentry is not a mount point in the calling - // namespace). - // - // mountpoints is maintained even if there are no references held on the - // MountNamespace; this is required to ensure that - // VFS.PrepareDeleteDentry() and VFS.PrepareRemoveDentry() operate - // correctly on unreferenced MountNamespaces. - mountpoints map[*Dentry]uint32 - - // mounts is the total number of mounts in this mount namespace. - mounts uint32 -} - -// NewMountNamespace returns a new mount namespace with a root filesystem -// configured by the given arguments. A reference is taken on the returned -// MountNamespace. -func (vfs *VirtualFilesystem) NewMountNamespace(ctx context.Context, creds *auth.Credentials, source, fsTypeName string, opts *MountOptions) (*MountNamespace, error) { - rft := vfs.getFilesystemType(fsTypeName) - if rft == nil { - ctx.Warningf("Unknown filesystem type: %s", fsTypeName) - return nil, linuxerr.ENODEV - } - fs, root, err := rft.fsType.GetFilesystem(ctx, vfs, creds, source, opts.GetFilesystemOptions) - if err != nil { - return nil, err - } - return vfs.NewMountNamespaceFrom(ctx, creds, fs, root, opts), nil -} - -// NewMountNamespaceFrom constructs a new mount namespace from an existing -// filesystem and its root dentry. This is similar to NewMountNamespace, but -// uses an existing filesystem instead of constructing a new one. -func (vfs *VirtualFilesystem) NewMountNamespaceFrom(ctx context.Context, creds *auth.Credentials, fs *Filesystem, root *Dentry, opts *MountOptions) *MountNamespace { - mntns := &MountNamespace{ - Owner: creds.UserNamespace, - mountpoints: make(map[*Dentry]uint32), - } - mntns.InitRefs() - mntns.root = newMount(vfs, fs, root, mntns, opts) - return mntns -} - -type cloneEntry struct { - prevMount *Mount - parentMount *Mount -} - -func (vfs *VirtualFilesystem) updateRootAndCWD(ctx context.Context, root *VirtualDentry, cwd *VirtualDentry, src *Mount, dst *Mount) { - if root.mount == src { - root.mount.DecRef(ctx) - root.mount = dst - root.mount.IncRef() - } - if cwd.mount == src { - cwd.mount.DecRef(ctx) - cwd.mount = dst - cwd.mount.IncRef() - } -} - -// CloneMountNamespace makes a copy of the specified mount namespace. -// -// If `root` or `cwd` have mounts in the old namespace, they will be replaced -// with proper mounts from the new namespace. -func (vfs *VirtualFilesystem) CloneMountNamespace(ctx context.Context, creds *auth.Credentials, ns *MountNamespace, root *VirtualDentry, cwd *VirtualDentry) (*MountNamespace, error) { - newns := &MountNamespace{ - Owner: creds.UserNamespace, - mountpoints: make(map[*Dentry]uint32), - } - newns.InitRefs() - - vdsToDecRef := []VirtualDentry{} - defer func() { - for _, vd := range vdsToDecRef { - vd.DecRef(ctx) - } - }() - - vfs.mountMu.Lock() - defer vfs.mountMu.Unlock() - - ns.root.root.IncRef() - ns.root.fs.IncRef() - newns.root = newMount(vfs, ns.root.fs, ns.root.root, newns, &MountOptions{Flags: ns.root.Flags, ReadOnly: ns.root.ReadOnly()}) - if ns.root.propType == Shared { - vfs.addPeer(ns.root, newns.root) - } - vfs.updateRootAndCWD(ctx, root, cwd, ns.root, newns.root) - - queue := []cloneEntry{cloneEntry{ns.root, newns.root}} - for len(queue) != 0 { - p := queue[0] - queue = queue[1:] - for c := range p.prevMount.children { - m := vfs.cloneMount(c, c.root, nil) - vd := VirtualDentry{ - mount: p.parentMount, - dentry: c.point(), - } - vd.IncRef() - - vds, err := vfs.connectMountAtLocked(ctx, m, vd) - m.DecRef(ctx) - vdsToDecRef = append(vdsToDecRef, vds...) - if err != nil { - newns.DecRef(ctx) - return nil, err - } - if c.propType == Shared { - vfs.addPeer(c, m) - } - vfs.updateRootAndCWD(ctx, root, cwd, c, m) - if len(c.children) != 0 { - queue = append(queue, cloneEntry{c, m}) - } - } - } - return newns, nil -} - // NewFilesystem creates a new filesystem object not yet associated with any // mounts. It can be installed into the filesystem tree with ConnectMountAt. // Note that only the filesystem-specific mount options from opts are used by @@ -852,26 +711,6 @@ func (mnt *Mount) LogRefs() bool { return false } -// DecRef decrements mntns' reference count. -func (mntns *MountNamespace) DecRef(ctx context.Context) { - vfs := mntns.root.fs.VirtualFilesystem() - mntns.MountNamespaceRefs.DecRef(func() { - vfs.mountMu.Lock() - vfs.mounts.seq.BeginWrite() - vdsToDecRef, mountsToDecRef := vfs.umountRecursiveLocked(mntns.root, &umountRecursiveOptions{ - disconnectHierarchy: true, - }, nil, nil) - vfs.mounts.seq.EndWrite() - vfs.mountMu.Unlock() - for _, vd := range vdsToDecRef { - vd.DecRef(ctx) - } - for _, mnt := range mountsToDecRef { - mnt.DecRef(ctx) - } - }) -} - // getMountAt returns the last Mount in the stack mounted at (mnt, d). It takes // a reference on the returned Mount. If (mnt, d) is not a mount point, // getMountAt returns nil. @@ -1158,16 +997,6 @@ func (mnt *Mount) Root() *Dentry { return mnt.root } -// Root returns mntns' root. It does not take a reference on the returned -// Dentry. -func (mntns *MountNamespace) Root() VirtualDentry { - vd := VirtualDentry{ - mount: mntns.root, - dentry: mntns.root.root, - } - return vd -} - // GenerateProcMounts emits the contents of /proc/[pid]/mounts for vfs to buf. // // Preconditions: taskRootDir.Ok(). diff --git a/pkg/sentry/vfs/namespace.go b/pkg/sentry/vfs/namespace.go new file mode 100644 index 000000000..1db916e1c --- /dev/null +++ b/pkg/sentry/vfs/namespace.go @@ -0,0 +1,264 @@ +// Copyright 2023 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. + +package vfs + +import ( + "gvisor.dev/gvisor/pkg/context" + "gvisor.dev/gvisor/pkg/errors/linuxerr" + "gvisor.dev/gvisor/pkg/refs" + "gvisor.dev/gvisor/pkg/sentry/kernel/auth" +) + +// A MountNamespace is a collection of Mounts.// +// MountNamespaces are reference-counted. Unless otherwise specified, all +// MountNamespace methods require that a reference is held. +// +// MountNamespace is analogous to Linux's struct mnt_namespace. +// +// +stateify savable +type MountNamespace struct { + // Refs is the reference count for this mount namespace. + Refs refs.TryRefCounter + + // Owner is the usernamespace that owns this mount namespace. + Owner *auth.UserNamespace + + // root is the MountNamespace's root mount. + root *Mount + + // mountpoints maps all Dentries which are mount points in this namespace + // to the number of Mounts for which they are mount points. mountpoints is + // protected by VirtualFilesystem.mountMu. + // + // mountpoints is used to determine if a Dentry can be moved or removed + // (which requires that the Dentry is not a mount point in the calling + // namespace). + // + // mountpoints is maintained even if there are no references held on the + // MountNamespace; this is required to ensure that + // VFS.PrepareDeleteDentry() and VFS.PrepareRemoveDentry() operate + // correctly on unreferenced MountNamespaces. + mountpoints map[*Dentry]uint32 + + // mounts is the total number of mounts in this mount namespace. + mounts uint32 +} + +// Namespace is the namespace interface. +type Namespace interface { + Type() string + Destroy(ctx context.Context) +} + +// NewMountNamespace returns a new mount namespace with a root filesystem +// configured by the given arguments. A reference is taken on the returned +// MountNamespace. +// +// If nsfs is nil, the default reference counter is used. +func (vfs *VirtualFilesystem) NewMountNamespace( + ctx context.Context, + creds *auth.Credentials, + source, fsTypeName string, + opts *MountOptions, + nsfs NamespaceInodeGetter, +) (*MountNamespace, error) { + rft := vfs.getFilesystemType(fsTypeName) + if rft == nil { + ctx.Warningf("Unknown filesystem type: %s", fsTypeName) + return nil, linuxerr.ENODEV + } + fs, root, err := rft.fsType.GetFilesystem(ctx, vfs, creds, source, opts.GetFilesystemOptions) + if err != nil { + return nil, err + } + return vfs.NewMountNamespaceFrom(ctx, creds, fs, root, opts, nsfs), nil +} + +type namespaceDefaultRefs struct { + namespaceRefs + destroy func(ctx context.Context) +} + +func (r *namespaceDefaultRefs) DecRef(ctx context.Context) { + r.namespaceRefs.DecRef( + func() { + r.destroy(ctx) + }, + ) +} + +// NewMountNamespaceFrom constructs a new mount namespace from an existing +// filesystem and its root dentry. This is similar to NewMountNamespace, but +// uses an existing filesystem instead of constructing a new one. +func (vfs *VirtualFilesystem) NewMountNamespaceFrom( + ctx context.Context, + creds *auth.Credentials, + fs *Filesystem, + root *Dentry, + opts *MountOptions, + nsfs NamespaceInodeGetter, +) *MountNamespace { + mntns := &MountNamespace{ + Owner: creds.UserNamespace, + mountpoints: make(map[*Dentry]uint32), + } + if nsfs == nil { + refs := &namespaceDefaultRefs{destroy: mntns.Destroy} + refs.InitRefs() + mntns.Refs = refs + } else { + mntns.Refs = nsfs.GetNamespaceInode(ctx, mntns) + } + mntns.root = newMount(vfs, fs, root, mntns, opts) + return mntns +} + +type cloneEntry struct { + prevMount *Mount + parentMount *Mount +} + +func (vfs *VirtualFilesystem) updateRootAndCWD(ctx context.Context, root *VirtualDentry, cwd *VirtualDentry, src *Mount, dst *Mount) { + if root.mount == src { + root.mount.DecRef(ctx) + root.mount = dst + root.mount.IncRef() + } + if cwd.mount == src { + cwd.mount.DecRef(ctx) + cwd.mount = dst + cwd.mount.IncRef() + } +} + +// NamespaceInodeGetter is an interface that provides the GetNamespaceInode method. +type NamespaceInodeGetter interface { + GetNamespaceInode(ctx context.Context, ns Namespace) refs.TryRefCounter +} + +// CloneMountNamespace makes a copy of the specified mount namespace. +// +// If `root` or `cwd` have mounts in the old namespace, they will be replaced +// with proper mounts from the new namespace. +func (vfs *VirtualFilesystem) CloneMountNamespace( + ctx context.Context, + creds *auth.Credentials, + ns *MountNamespace, + root *VirtualDentry, + cwd *VirtualDentry, + nsfs NamespaceInodeGetter, +) (*MountNamespace, error) { + newns := &MountNamespace{ + Owner: creds.UserNamespace, + mountpoints: make(map[*Dentry]uint32), + } + + newns.Refs = nsfs.GetNamespaceInode(ctx, newns) + vdsToDecRef := []VirtualDentry{} + defer func() { + for _, vd := range vdsToDecRef { + vd.DecRef(ctx) + } + }() + + vfs.mountMu.Lock() + defer vfs.mountMu.Unlock() + + ns.root.root.IncRef() + ns.root.fs.IncRef() + newns.root = newMount(vfs, ns.root.fs, ns.root.root, newns, &MountOptions{Flags: ns.root.Flags, ReadOnly: ns.root.ReadOnly()}) + if ns.root.propType == Shared { + vfs.addPeer(ns.root, newns.root) + } + vfs.updateRootAndCWD(ctx, root, cwd, ns.root, newns.root) + + queue := []cloneEntry{cloneEntry{ns.root, newns.root}} + for len(queue) != 0 { + p := queue[0] + queue = queue[1:] + for c := range p.prevMount.children { + m := vfs.cloneMount(c, c.root, nil) + vd := VirtualDentry{ + mount: p.parentMount, + dentry: c.point(), + } + vd.IncRef() + + vds, err := vfs.connectMountAtLocked(ctx, m, vd) + m.DecRef(ctx) + vdsToDecRef = append(vdsToDecRef, vds...) + if err != nil { + newns.DecRef(ctx) + return nil, err + } + if c.propType == Shared { + vfs.addPeer(c, m) + } + vfs.updateRootAndCWD(ctx, root, cwd, c, m) + if len(c.children) != 0 { + queue = append(queue, cloneEntry{c, m}) + } + } + } + return newns, nil +} + +// Destroy implements nsfs.Namespace.Destroy. +func (mntns *MountNamespace) Destroy(ctx context.Context) { + vfs := mntns.root.fs.VirtualFilesystem() + vfs.mountMu.Lock() + vfs.mounts.seq.BeginWrite() + vdsToDecRef, mountsToDecRef := vfs.umountRecursiveLocked(mntns.root, &umountRecursiveOptions{ + disconnectHierarchy: true, + }, nil, nil) + vfs.mounts.seq.EndWrite() + vfs.mountMu.Unlock() + for _, vd := range vdsToDecRef { + vd.DecRef(ctx) + } + for _, mnt := range mountsToDecRef { + mnt.DecRef(ctx) + } +} + +// Type implements nsfs.Namespace.Type. +func (mntns *MountNamespace) Type() string { + return "mnt" +} + +// IncRef increments mntns' refcount. +func (mntns *MountNamespace) IncRef() { + mntns.Refs.IncRef() +} + +// DecRef decrements mntns' reference count. +func (mntns *MountNamespace) DecRef(ctx context.Context) { + mntns.Refs.DecRef(ctx) +} + +// TryIncRef attempts to increment mntns' reference count. +func (mntns *MountNamespace) TryIncRef() bool { + return mntns.Refs.TryIncRef() +} + +// Root returns mntns' root. It does not take a reference on the returned +// Dentry. +func (mntns *MountNamespace) Root() VirtualDentry { + vd := VirtualDentry{ + mount: mntns.root, + dentry: mntns.root.root, + } + return vd +} diff --git a/runsc/boot/vfs.go b/runsc/boot/vfs.go index 59824363a..5db421829 100644 --- a/runsc/boot/vfs.go +++ b/runsc/boot/vfs.go @@ -504,7 +504,7 @@ func (c *containerMounter) createMountNamespace(ctx context.Context, conf *confi fsName = overlay.Name } - mns, err := c.k.VFS().NewMountNamespace(ctx, creds, "", fsName, opts) + mns, err := c.k.VFS().NewMountNamespace(ctx, creds, "", fsName, opts, c.k) if err != nil { return nil, fmt.Errorf("setting up mount namespace: %w", err) } diff --git a/test/syscalls/linux/mount.cc b/test/syscalls/linux/mount.cc index 48de88704..a5aa3fe55 100644 --- a/test/syscalls/linux/mount.cc +++ b/test/syscalls/linux/mount.cc @@ -1468,6 +1468,30 @@ TEST(MountTest, MountNamespace) { EXPECT_NO_ERRNO(Open(JoinPath(dir.path(), "foo"), O_RDWR)); } +TEST(MountTest, MountNamespaceSetns) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); + + auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount("", dir.path(), "tmpfs", 0, "mode=0700", MNT_DETACH)); + EXPECT_NO_ERRNO(Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777)); + const FileDescriptor nsfd = + ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/thread-self/ns/mnt", O_RDONLY)); + + pid_t child = fork(); + if (child == 0) { + TEST_CHECK(unshare(CLONE_NEWNS) == 0); + TEST_CHECK(umount2(dir.path().c_str(), MNT_DETACH) == 0); + ASSERT_THAT(setns(nsfd.get(), CLONE_NEWNS), SyscallSucceedsWithValue(0)); + TEST_CHECK(access(JoinPath(dir.path(), "foo").c_str(), F_OK) == 0); + exit(0); + } + ASSERT_THAT(child, SyscallSucceeds()); + int status; + ASSERT_THAT(waitpid(child, &status, 0), SyscallSucceedsWithValue(child)); + ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); +} + TEST(MountTest, MountNamespacePropagation) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));