diff --git a/pkg/sentry/vfs/BUILD b/pkg/sentry/vfs/BUILD index 6f44ae392..2f45dd809 100644 --- a/pkg/sentry/vfs/BUILD +++ b/pkg/sentry/vfs/BUILD @@ -140,6 +140,7 @@ go_library( "options.go", "pathname.go", "permissions.go", + "propagation.go", "resolving_path.go", "save_restore.go", "shared_list.go", diff --git a/pkg/sentry/vfs/mount.go b/pkg/sentry/vfs/mount.go index 3e82e7825..afd302141 100644 --- a/pkg/sentry/vfs/mount.go +++ b/pkg/sentry/vfs/mount.go @@ -30,46 +30,10 @@ import ( "gvisor.dev/gvisor/pkg/sentry/kernel/auth" ) -// PropagationType is a propagation flavor as described in -// https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt. Child -// and Unbindable are currently unimplemented. -// TODO(b/249777195): Support MS_SLAVE and MS_UNBINDABLE propagation types. -type PropagationType int - -const ( - // Unknown represents an invalid/unknown propagation type. - Unknown PropagationType = iota - // Shared represents the shared propagation type. - Shared - // Private represents the private propagation type. - Private - // Child represents the child propagation type (MS_SLAVE). - Child - // Unbindable represents the unbindable propagation type. - Unbindable - - // MountMax is the maximum number of mounts allowed. In Linux this can be - // configured by the user at /proc/sys/fs/mount-max, but the default is - // 100,000. We set the gVisor limit to 10,000. - MountMax = 10000 -) - -// PropagationTypeFromLinux returns the PropagationType corresponding to a -// linux mount flag, aka MS_SHARED. -func PropagationTypeFromLinux(propFlag uint64) PropagationType { - switch propFlag { - case linux.MS_SHARED: - return Shared - case linux.MS_PRIVATE: - return Private - case linux.MS_SLAVE: - return Child - case linux.MS_UNBINDABLE: - return Unbindable - default: - return Unknown - } -} +// MountMax is the maximum number of mounts allowed. In Linux this can be +// configured by the user at /proc/sys/fs/mount-max, but the default is +// 100,000. We set the gVisor limit to 10,000. +const MountMax = 10000 // A Mount is a replacement of a Dentry (Mount.key.point) from one Filesystem // (Mount.key.parent.fs) with a Dentry (Mount.root) from another Filesystem @@ -196,60 +160,6 @@ func (mnt *Mount) generateOptionalTags() string { return optional } -// addPeer adds oth to mnt's peer group. Both will have the same groupID -// and sharedList. vfs.mountMu must be locked. -// -// +checklocks:vfs.mountMu -func (vfs *VirtualFilesystem) addPeer(mnt *Mount, oth *Mount) { - mnt.sharedList.PushBack(oth) - oth.sharedList = mnt.sharedList - oth.propType = mnt.propType - oth.groupID = mnt.groupID -} - -// mergePeerGroup merges oth and all its peers into mnt's peer group. Oth -// must have propagation type shared and vfs.mountMu must be locked. -// -// +checklocks:vfs.mountMu -func (vfs *VirtualFilesystem) mergePeerGroup(mnt *Mount, oth *Mount) { - peer := oth.sharedList.Front() - for peer != nil { - next := peer.sharedEntry.Next() - vfs.setPropagation(peer, Private) - vfs.addPeer(mnt, peer) - peer = next - } -} - -// setPropagation sets the propagation on mnt for a propagation type. -// -// +checklocks:vfs.mountMu -func (vfs *VirtualFilesystem) setPropagation(mnt *Mount, ptype PropagationType) error { - switch ptype { - case Shared: - id, err := vfs.allocateGroupID() - if err != nil { - return err - } - mnt.groupID = id - mnt.sharedList = &sharedList{} - mnt.sharedList.PushBack(mnt) - case Private: - if mnt.propType == Shared { - mnt.sharedList.Remove(mnt) - if mnt.sharedList.Empty() { - vfs.freeGroupID(mnt.groupID) - } - mnt.sharedList = nil - mnt.groupID = 0 - } - default: - panic(fmt.Sprintf("unsupported propagation type: %v", ptype)) - } - mnt.propType = ptype - return nil -} - // A MountNamespace is a collection of Mounts.// // MountNamespaces are reference-counted. Unless otherwise specified, all // MountNamespace methods require that a reference is held. @@ -375,73 +285,6 @@ func (vfs *VirtualFilesystem) ConnectMountAt(ctx context.Context, creds *auth.Cr return nil } -// preparePropagationTree returns a mapping of propagated mounts to their future -// mountpoints. The new mounts are clones of mnt and are added to mnt's peer -// group if vd.mount and mnt are shared. All the cloned mounts and new -// mountpoints in the tree have an extra reference taken. -// -// +checklocks:vfs.mountMu -// +checklocksalias:mnt.vfs.mountMu=vfs.mountMu -func (vfs *VirtualFilesystem) preparePropagationTree(mnt *Mount, vd VirtualDentry) map[*Mount]VirtualDentry { - tree := map[*Mount]VirtualDentry{} - if vd.mount.propType == Private { - return tree - } - if mnt.propType == Private { - vfs.setPropagation(mnt, Shared) - } - var newPeerGroup []*Mount - for peer := vd.mount.sharedList.Front(); peer != nil; peer = peer.sharedEntry.Next() { - if peer == vd.mount { - continue - } - peerVd := VirtualDentry{ - mount: peer, - dentry: vd.dentry, - } - peerVd.IncRef() - clone := vfs.cloneMount(mnt, mnt.root, nil) - tree[clone] = peerVd - newPeerGroup = append(newPeerGroup, clone) - } - for _, newPeer := range newPeerGroup { - vfs.addPeer(mnt, newPeer) - } - return tree -} - -// commitPropagationTree attaches to mounts in tree to the mountpoints they -// are mapped to. If there is an error attaching a mount, the method panics. -// -// +checklocks:vfs.mountMu -func (vfs *VirtualFilesystem) commitPropagationTree(ctx context.Context, tree map[*Mount]VirtualDentry) { - // The peer mounts should have no way of being dead if we've reached this - // point so its safe to connect without checks. - vfs.mounts.seq.BeginWrite() - for mnt, vd := range tree { - vd.dentry.mu.Lock() - // If mnt isn't connected yet, skip connecting during propagation. - if mntns := vd.mount.ns; mntns != nil { - vfs.connectLocked(mnt, vd, mntns) - } - vd.dentry.mu.Unlock() - mnt.DecRef(ctx) - } - vfs.mounts.seq.EndWrite() -} - -// abortPropagationTree releases any references held by the mounts and -// mountpoints in the tree and removes the mounts from their peer groups. -// -// +checklocks:vfs.mountMu -func (vfs *VirtualFilesystem) abortPropagationTree(ctx context.Context, tree map[*Mount]VirtualDentry) { - for mnt, vd := range tree { - vd.DecRef(ctx) - vfs.setPropagation(mnt, Private) - mnt.DecRef(ctx) - } -} - // connectMountAtLocked attaches mnt at vd. It returns the new mountpoint of mnt // if no error occurred. // @@ -496,44 +339,6 @@ func (vfs *VirtualFilesystem) connectMountAt(ctx context.Context, mnt *Mount, vd return 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, propType PropagationType) error { - vd, err := vfs.GetDentryAt(ctx, creds, pop, &GetDentryOptions{}) - if err != nil { - return err - } - // See the similar defer in UmountAt for why this is in a closure. - defer func() { - vd.DecRef(ctx) - }() - if vd.dentry.isMounted() { - if realmnt := vfs.getMountAt(ctx, vd.mount, vd.dentry); realmnt != nil { - vd.mount.DecRef(ctx) - vd.mount = realmnt - } - } else if vd.dentry != vd.mount.root { - return linuxerr.EINVAL - } - vfs.SetMountPropagation(vd.mount, propType) - return nil -} - -// SetMountPropagation changes the propagation type of the mount. -func (vfs *VirtualFilesystem) SetMountPropagation(mnt *Mount, propType PropagationType) { - vfs.mountMu.Lock() - defer vfs.mountMu.Unlock() - if propType != mnt.propType { - switch propType { - case Shared, Private: - vfs.setPropagation(mnt, propType) - default: - panic(fmt.Sprintf("unsupported propagation type: %v", propType)) - } - } - mnt.propType = propType -} - // CloneMountAt returns a new mount with the same fs, specified root and // mount options. If mnt's propagation type is shared the new mount is // automatically made a peer of mnt. If mount options are nil, mnt's diff --git a/pkg/sentry/vfs/propagation.go b/pkg/sentry/vfs/propagation.go new file mode 100644 index 000000000..b39c939f6 --- /dev/null +++ b/pkg/sentry/vfs/propagation.go @@ -0,0 +1,219 @@ +// Copyright 2022 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 ( + "fmt" + + "gvisor.dev/gvisor/pkg/abi/linux" + "gvisor.dev/gvisor/pkg/context" + "gvisor.dev/gvisor/pkg/errors/linuxerr" + "gvisor.dev/gvisor/pkg/sentry/kernel/auth" +) + +// PropagationType is a propagation flavor as described in +// https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt. Child +// and Unbindable are currently unimplemented. +// TODO(b/249777195): Support MS_SLAVE and MS_UNBINDABLE propagation types. +type PropagationType int + +const ( + // Unknown represents an invalid/unknown propagation type. + Unknown PropagationType = iota + // Shared represents the shared propagation type. + Shared + // Private represents the private propagation type. + Private + // Child represents the child propagation type (MS_SLAVE). + Child + // Unbindable represents the unbindable propagation type. + Unbindable +) + +// PropagationTypeFromLinux returns the PropagationType corresponding to a +// linux mount flag, aka MS_SHARED. +func PropagationTypeFromLinux(propFlag uint64) PropagationType { + switch propFlag { + case linux.MS_SHARED: + return Shared + case linux.MS_PRIVATE: + return Private + case linux.MS_SLAVE: + return Child + case linux.MS_UNBINDABLE: + return Unbindable + default: + return Unknown + } +} + +// setPropagation sets the propagation on mnt for a propagation type. +// +// +checklocks:vfs.mountMu +func (vfs *VirtualFilesystem) setPropagation(mnt *Mount, ptype PropagationType) error { + switch ptype { + case Shared: + id, err := vfs.allocateGroupID() + if err != nil { + return err + } + mnt.groupID = id + mnt.sharedList = &sharedList{} + mnt.sharedList.PushBack(mnt) + case Private: + if mnt.propType == Shared { + mnt.sharedList.Remove(mnt) + if mnt.sharedList.Empty() { + vfs.freeGroupID(mnt.groupID) + } + mnt.sharedList = nil + mnt.groupID = 0 + } + default: + panic(fmt.Sprintf("unsupported propagation type: %v", ptype)) + } + mnt.propType = ptype + return nil +} + +// addPeer adds oth to mnt's peer group. Both will have the same groupID +// and sharedList. vfs.mountMu must be locked. +// +// +checklocks:vfs.mountMu +func (vfs *VirtualFilesystem) addPeer(mnt *Mount, oth *Mount) { + mnt.sharedList.PushBack(oth) + oth.sharedList = mnt.sharedList + oth.propType = mnt.propType + oth.groupID = mnt.groupID +} + +// mergePeerGroup merges oth and all its peers into mnt's peer group. Oth +// must have propagation type shared and vfs.mountMu must be locked. +// +// +checklocks:vfs.mountMu +func (vfs *VirtualFilesystem) mergePeerGroup(mnt *Mount, oth *Mount) { + peer := oth.sharedList.Front() + for peer != nil { + next := peer.sharedEntry.Next() + vfs.setPropagation(peer, Private) + vfs.addPeer(mnt, peer) + peer = next + } +} + +// preparePropagationTree returns a mapping of propagated mounts to their future +// mountpoints. The new mounts are clones of mnt and are added to mnt's peer +// group if vd.mount and mnt are shared. All the cloned mounts and new +// mountpoints in the tree have an extra reference taken. +// +// +checklocks:vfs.mountMu +// +checklocksalias:mnt.vfs.mountMu=vfs.mountMu +func (vfs *VirtualFilesystem) preparePropagationTree(mnt *Mount, vd VirtualDentry) map[*Mount]VirtualDentry { + tree := map[*Mount]VirtualDentry{} + if vd.mount.propType == Private { + return tree + } + if mnt.propType == Private { + vfs.setPropagation(mnt, Shared) + } + var newPeerGroup []*Mount + for peer := vd.mount.sharedList.Front(); peer != nil; peer = peer.sharedEntry.Next() { + if peer == vd.mount { + continue + } + peerVd := VirtualDentry{ + mount: peer, + dentry: vd.dentry, + } + peerVd.IncRef() + clone := vfs.cloneMount(mnt, mnt.root, nil) + tree[clone] = peerVd + newPeerGroup = append(newPeerGroup, clone) + } + for _, newPeer := range newPeerGroup { + vfs.addPeer(mnt, newPeer) + } + return tree +} + +// commitPropagationTree attaches to mounts in tree to the mountpoints they +// are mapped to. If there is an error attaching a mount, the method panics. +// +// +checklocks:vfs.mountMu +func (vfs *VirtualFilesystem) commitPropagationTree(ctx context.Context, tree map[*Mount]VirtualDentry) { + // The peer mounts should have no way of being dead if we've reached this + // point so its safe to connect without checks. + vfs.mounts.seq.BeginWrite() + for mnt, vd := range tree { + vd.dentry.mu.Lock() + // If mnt isn't connected yet, skip connecting during propagation. + if mntns := vd.mount.ns; mntns != nil { + vfs.connectLocked(mnt, vd, mntns) + } + vd.dentry.mu.Unlock() + mnt.DecRef(ctx) + } + vfs.mounts.seq.EndWrite() +} + +// abortPropagationTree releases any references held by the mounts and +// mountpoints in the tree and removes the mounts from their peer groups. +// +// +checklocks:vfs.mountMu +func (vfs *VirtualFilesystem) abortPropagationTree(ctx context.Context, tree map[*Mount]VirtualDentry) { + for mnt, vd := range tree { + vd.DecRef(ctx) + vfs.setPropagation(mnt, Private) + mnt.DecRef(ctx) + } +} + +// SetMountPropagationAt changes the propagation type of the mount pointed to by +// pop. +func (vfs *VirtualFilesystem) SetMountPropagationAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, propType PropagationType) error { + vd, err := vfs.GetDentryAt(ctx, creds, pop, &GetDentryOptions{}) + if err != nil { + return err + } + // See the similar defer in UmountAt for why this is in a closure. + defer func() { + vd.DecRef(ctx) + }() + if vd.dentry.isMounted() { + if realmnt := vfs.getMountAt(ctx, vd.mount, vd.dentry); realmnt != nil { + vd.mount.DecRef(ctx) + vd.mount = realmnt + } + } else if vd.dentry != vd.mount.root { + return linuxerr.EINVAL + } + vfs.SetMountPropagation(vd.mount, propType) + return nil +} + +// SetMountPropagation changes the propagation type of the mount. +func (vfs *VirtualFilesystem) SetMountPropagation(mnt *Mount, propType PropagationType) { + vfs.mountMu.Lock() + defer vfs.mountMu.Unlock() + if propType != mnt.propType { + switch propType { + case Shared, Private: + vfs.setPropagation(mnt, propType) + default: + panic(fmt.Sprintf("unsupported propagation type: %v", propType)) + } + } + mnt.propType = propType +}