diff --git a/pkg/sentry/vfs/mount.go b/pkg/sentry/vfs/mount.go index 02cf2c540..af571a7b1 100644 --- a/pkg/sentry/vfs/mount.go +++ b/pkg/sentry/vfs/mount.go @@ -108,6 +108,10 @@ type Mount struct { // Mount.EndWrite(). The MSB of writers is set if MS_RDONLY is in effect. // writers is accessed using atomic memory operations. writers atomicbitops.Int64 + + // pendingChildren is a list of new child mounts that have not yet been + // connected to this mount as the parent. + pendingChildren []*Mount } func newMount(vfs *VirtualFilesystem, fs *Filesystem, root *Dentry, mntns *MountNamespace, opts *MountOptions) *Mount { @@ -304,6 +308,39 @@ func (vfs *VirtualFilesystem) cloneMount(mnt *Mount, root *Dentry, mopts *MountO return clone } +type cloneTreeNode struct { + prevMount *Mount + parentMount *Mount +} + +// cloneMountTree creates a copy of mnt's tree with the specified root +// dentry at root. The new descendents are added to mnt's pending mount list. +// +// +checklocks:vfs.mountMu +func (vfs *VirtualFilesystem) cloneMountTree(ctx context.Context, mnt *Mount, root *Dentry) (*Mount, error) { + clone := vfs.cloneMount(mnt, root, nil) + queue := []cloneTreeNode{{mnt, clone}} + for len(queue) != 0 { + p := queue[len(queue)-1] + queue = queue[:len(queue)-1] + for c := range p.prevMount.children { + m := vfs.cloneMount(c, c.root, nil) + vfs.delayDecRef(m) + mp := VirtualDentry{ + mount: p.parentMount, + dentry: c.point(), + } + mp.IncRef() + m.setKey(mp) + p.parentMount.pendingChildren = append(p.parentMount.pendingChildren, m) + if len(c.children) != 0 { + queue = append(queue, cloneTreeNode{c, m}) + } + } + } + return clone, 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. diff --git a/pkg/sentry/vfs/namespace.go b/pkg/sentry/vfs/namespace.go index 131193fff..133397246 100644 --- a/pkg/sentry/vfs/namespace.go +++ b/pkg/sentry/vfs/namespace.go @@ -15,6 +15,8 @@ package vfs import ( + "fmt" + "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/refs" @@ -125,22 +127,28 @@ func (vfs *VirtualFilesystem) NewMountNamespaceFrom( return mntns } -type cloneEntry struct { - prevMount *Mount - parentMount *Mount -} - // +checklocks:vfs.mountMu -func (vfs *VirtualFilesystem) updateRootAndCWD(ctx context.Context, root *VirtualDentry, cwd *VirtualDentry, src *Mount, dst *Mount) { - if root.mount == src { - vfs.delayDecRef(root.mount) - root.mount = dst - root.mount.IncRef() +func (vfs *VirtualFilesystem) updateRootAndCWD(ctx context.Context, root *VirtualDentry, cwd *VirtualDentry, srcRoot *Mount, dstRoot *Mount) { + // The mount trees are exact copies of each other so submountsLocked will + // return corresponding mounts in the same order. + srcMounts := srcRoot.submountsLocked() + dstMounts := dstRoot.submountsLocked() + if len(srcMounts) != len(dstMounts) { + panic(fmt.Sprintf("mount trees are not the same size: len(srcTree) = %d, len(dstTree) = %d", len(srcMounts), len(dstMounts))) } - if cwd.mount == src { - vfs.delayDecRef(cwd.mount) - cwd.mount = dst - cwd.mount.IncRef() + for i := 0; i < len(srcMounts); i++ { + old := srcMounts[i] + new := dstMounts[i] + if root.mount == old { + vfs.delayDecRef(root.mount) + root.mount = new + root.mount.IncRef() + } + if cwd.mount == old { + vfs.delayDecRef(cwd.mount) + cwd.mount = new + cwd.mount.IncRef() + } } } @@ -170,38 +178,16 @@ func (vfs *VirtualFilesystem) CloneMountNamespace( vfs.lockMounts() defer vfs.unlockMounts(ctx) - 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.isShared { - vfs.addPeer(ns.root, newns.root) + newRoot, err := vfs.cloneMountTree(ctx, ns.root, ns.root.root) + if err != nil { + newns.DecRef(ctx) + vfs.abortTree(ctx, newRoot) + return nil, err } + newns.root = newRoot + newns.root.ns = newns + vfs.commitTree(ctx, newRoot) 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() - - err := vfs.connectMountAtLocked(ctx, m, vd) - vfs.delayDecRef(m) - if err != nil { - newns.DecRef(ctx) - return nil, err - } - vfs.updateRootAndCWD(ctx, root, cwd, c, m) - if len(c.children) != 0 { - queue = append(queue, cloneEntry{c, m}) - } - } - } return newns, nil } diff --git a/pkg/sentry/vfs/propagation.go b/pkg/sentry/vfs/propagation.go index 1d34d9740..8f70af2b9 100644 --- a/pkg/sentry/vfs/propagation.go +++ b/pkg/sentry/vfs/propagation.go @@ -153,6 +153,43 @@ func (vfs *VirtualFilesystem) abortPropagationTree(ctx context.Context, tree map } } +// +checklocks:vfs.mountMu +func (vfs *VirtualFilesystem) commitTree(ctx context.Context, mnt *Mount) { + vfs.mounts.seq.BeginWrite() + for _, c := range mnt.pendingChildren { + vfs.commitTreeSeqed(ctx, c) + } + mnt.pendingChildren = nil + vfs.mounts.seq.EndWrite() +} + +// +checklocks:vfs.mountMu +func (vfs *VirtualFilesystem) commitTreeSeqed(ctx context.Context, mnt *Mount) { + mp := mnt.getKey() + mp.dentry.mu.Lock() + vfs.connectLocked(mnt, mp, mp.mount.ns) + mp.dentry.mu.Unlock() + for _, c := range mnt.pendingChildren { + vfs.commitTreeSeqed(ctx, c) + } + mnt.pendingChildren = nil +} + +// abortTree releases references on a pending mount and all its pending +// descendants. +// +// +checklocks:vfs.mountMu +func (vfs *VirtualFilesystem) abortTree(ctx context.Context, mnt *Mount) { + mp := mnt.getKey() + vfs.delayDecRef(mnt) + vfs.delayDecRef(mp.dentry) + vfs.setPropagation(mnt, linux.MS_PRIVATE) + for _, c := range mnt.pendingChildren { + vfs.abortTree(ctx, c) + } + mnt.pendingChildren = nil +} + // SetMountPropagationAt changes the propagation type of the mount pointed to by // pop. func (vfs *VirtualFilesystem) SetMountPropagationAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, propFlags uint32) error {