diff options
Diffstat (limited to 'pkg/sentry/fs/mounts.go')
-rw-r--r-- | pkg/sentry/fs/mounts.go | 258 |
1 files changed, 177 insertions, 81 deletions
diff --git a/pkg/sentry/fs/mounts.go b/pkg/sentry/fs/mounts.go index 01eb4607e..a5c52d7ba 100644 --- a/pkg/sentry/fs/mounts.go +++ b/pkg/sentry/fs/mounts.go @@ -16,6 +16,7 @@ package fs import ( "fmt" + "math" "path" "strings" "sync" @@ -35,6 +36,94 @@ import ( // sane. const DefaultTraversalLimit = 10 +const invalidMountID = math.MaxUint64 + +// Mount represents a mount in the file system. It holds the root dirent for the +// mount. It also points back to the dirent or mount where it was mounted over, +// so that it can be restored when unmounted. The chained mount can be either: +// - Mount: when it's mounted on top of another mount point. +// - Dirent: when it's mounted on top of a dirent. In this case the mount is +// called an "undo" mount and only 'root' is set. All other fields are +// either invalid or nil. +// +// +stateify savable +type Mount struct { + // ID is a unique id for this mount. It may be invalidMountID if this is + // used to cache a dirent that was mounted over. + ID uint64 + + // ParentID is the parent's mount unique id. It may be invalidMountID if this + // is the root mount or if this is used to cache a dirent that was mounted + // over. + ParentID uint64 + + // root is the root Dirent of this mount. A reference on this Dirent must be + // held through the lifetime of the Mount which contains it. + root *Dirent + + // previous is the existing dirent or mount that this object was mounted over. + // It's nil for the root mount and for the last entry in the chain (always an + // "undo" mount). + previous *Mount +} + +// newMount creates a new mount, taking a reference on 'root'. Caller must +// release the reference when it's done with the mount. +func newMount(id, pid uint64, root *Dirent) *Mount { + root.IncRef() + return &Mount{ + ID: id, + ParentID: pid, + root: root, + } +} + +// newRootMount creates a new root mount (no parent), taking a reference on +// 'root'. Caller must release the reference when it's done with the mount. +func newRootMount(id uint64, root *Dirent) *Mount { + root.IncRef() + return &Mount{ + ID: id, + ParentID: invalidMountID, + root: root, + } +} + +// newUndoMount creates a new undo mount, taking a reference on 'd'. Caller must +// release the reference when it's done with the mount. +func newUndoMount(d *Dirent) *Mount { + d.IncRef() + return &Mount{ + ID: invalidMountID, + ParentID: invalidMountID, + root: d, + } +} + +// Root returns the root dirent of this mount. Callers must call DecRef on the +// returned dirent. +func (m *Mount) Root() *Dirent { + m.root.IncRef() + return m.root +} + +// IsRoot returns true if the mount has no parent. +func (m *Mount) IsRoot() bool { + return !m.IsUndo() && m.ParentID == invalidMountID +} + +// IsUndo returns true if 'm' is an undo mount that should be used to restore +// the original dirent during unmount only and it's not a valid mount. +func (m *Mount) IsUndo() bool { + if m.ID == invalidMountID { + if m.ParentID != invalidMountID { + panic(fmt.Sprintf("Undo mount with valid parentID: %+v", m)) + } + return true + } + return false +} + // MountNamespace defines a collection of mounts. // // +stateify savable @@ -55,13 +144,16 @@ type MountNamespace struct { // mu protects mounts and mountID counter. mu sync.Mutex `state:"nosave"` - // mounts is a map of the last mounted Dirent -> stack of old Dirents - // that were mounted over, with the oldest mounted Dirent first and - // more recent mounted Dirents at the end of the slice. - // - // A reference to all Dirents in mounts (keys and values) must be held - // to ensure the Dirents are recoverable when unmounting. - mounts map[*Dirent][]*Dirent + // mounts is a map of mounted Dirent -> Mount object. There are three + // possible cases: + // - Dirent is mounted over a mount point: the stored Mount object will be + // the Mount for that mount point. + // - Dirent is mounted over a regular (non-mount point) Dirent: the stored + // Mount object will be an "undo" mount containing the mounted-over + // Dirent. + // - Dirent is the root mount: the stored Mount object will be a root mount + // containing the Dirent itself. + mounts map[*Dirent]*Mount // mountID is the next mount id to assign. mountID uint64 @@ -72,18 +164,18 @@ type MountNamespace struct { func NewMountNamespace(ctx context.Context, root *Inode) (*MountNamespace, error) { creds := auth.CredentialsFromContext(ctx) - root.MountSource.mu.Lock() - defer root.MountSource.mu.Unlock() - - // Set the root dirent and id on the root mount. + // Set the root dirent and id on the root mount. The reference returned from + // NewDirent will be donated to the MountNamespace constructed below. d := NewDirent(root, "/") - root.MountSource.root = d - root.MountSource.id = 1 + + mnts := map[*Dirent]*Mount{ + d: newRootMount(1, d), + } return &MountNamespace{ userns: creds.UserNamespace, root: d, - mounts: make(map[*Dirent][]*Dirent), + mounts: mnts, mountID: 2, }, nil } @@ -110,10 +202,9 @@ func (mns *MountNamespace) FlushMountSourceRefs() { func (mns *MountNamespace) flushMountSourceRefsLocked() { // Flush mounts' MountSource references. - for current, stack := range mns.mounts { - current.Inode.MountSource.FlushDirentRefs() - for _, prev := range stack { - prev.Inode.MountSource.FlushDirentRefs() + for _, mp := range mns.mounts { + for ; mp != nil; mp = mp.previous { + mp.root.Inode.MountSource.FlushDirentRefs() } } @@ -136,12 +227,11 @@ func (mns *MountNamespace) destroy() { mns.flushMountSourceRefsLocked() // Teardown mounts. - for current, mp := range mns.mounts { + for _, mp := range mns.mounts { // Drop the mount reference on all mounted dirents. - for _, d := range mp { - d.DecRef() + for ; mp != nil; mp = mp.previous { + mp.root.DecRef() } - current.DecRef() } mns.mounts = nil @@ -208,46 +298,34 @@ func (mns *MountNamespace) withMountLocked(node *Dirent, fn func() error) error } // Mount mounts a `inode` over the subtree at `node`. -func (mns *MountNamespace) Mount(ctx context.Context, node *Dirent, inode *Inode) error { - return mns.withMountLocked(node, func() error { - // replacement already has one reference taken; this is the mount - // reference. - replacement, err := node.mount(ctx, inode) +func (mns *MountNamespace) Mount(ctx context.Context, mountPoint *Dirent, inode *Inode) error { + return mns.withMountLocked(mountPoint, func() error { + replacement, err := mountPoint.mount(ctx, inode) if err != nil { return err } - - // Set child/parent dirent relationship. - parentMountSource := node.Inode.MountSource - childMountSource := inode.MountSource - parentMountSource.mu.Lock() - defer parentMountSource.mu.Unlock() - childMountSource.mu.Lock() - defer childMountSource.mu.Unlock() - - parentMountSource.children[childMountSource] = struct{}{} - childMountSource.parent = parentMountSource + defer replacement.DecRef() // Set the mount's root dirent and id. - childMountSource.root = replacement - childMountSource.id = mns.mountID + parentMnt := mns.findMountLocked(mountPoint) + childMnt := newMount(mns.mountID, parentMnt.ID, replacement) mns.mountID++ - // Drop node from its dirent cache. - node.dropExtendedReference() + // Drop mountPoint from its dirent cache. + mountPoint.dropExtendedReference() - // If node is already a mount point, push node on the stack so it can + // If mountPoint is already a mount, push mountPoint on the stack so it can // be recovered on unmount. - if stack, ok := mns.mounts[node]; ok { - mns.mounts[replacement] = append(stack, node) - delete(mns.mounts, node) + if prev := mns.mounts[mountPoint]; prev != nil { + childMnt.previous = prev + mns.mounts[replacement] = childMnt + delete(mns.mounts, mountPoint) return nil } // Was not already mounted, just add another mount point. - // Take a reference on node so it can be recovered on unmount. - node.IncRef() - mns.mounts[replacement] = []*Dirent{node} + childMnt.previous = newUndoMount(mountPoint) + mns.mounts[replacement] = childMnt return nil }) } @@ -268,13 +346,13 @@ func (mns *MountNamespace) Unmount(ctx context.Context, node *Dirent, detachOnly // This takes locks to prevent further walks to Dirents in this mount // under the assumption that `node` is the root of the mount. return mns.withMountLocked(node, func() error { - origs, ok := mns.mounts[node] + orig, ok := mns.mounts[node] if !ok { // node is not a mount point. return syserror.EINVAL } - if len(origs) == 0 { + if orig.previous == nil { panic("cannot unmount initial dirent") } @@ -298,44 +376,62 @@ func (mns *MountNamespace) Unmount(ctx context.Context, node *Dirent, detachOnly } } - // Lock the parent MountSource first, if it exists. We are - // holding mns.Lock, so the parent can not change out - // from under us. - parent := m.Parent() - if parent != nil { - parent.mu.Lock() - defer parent.mu.Unlock() + prev := orig.previous + if err := node.unmount(ctx, prev.root); err != nil { + return err } - // Lock the mount that is being unmounted. - m.mu.Lock() - defer m.mu.Unlock() - - if m.parent != nil { - // Sanity check. - if _, ok := m.parent.children[m]; !ok { - panic(fmt.Sprintf("mount %+v is not a child of parent %+v", m, m.parent)) + if prev.previous == nil { + if !prev.IsUndo() { + panic(fmt.Sprintf("Last mount in the chain must be a undo mount: %+v", prev)) } - delete(m.parent.children, m) + // Drop mount reference taken at the end of MountNamespace.Mount. + prev.root.DecRef() + } else { + mns.mounts[prev.root] = prev } + delete(mns.mounts, node) - original := origs[len(origs)-1] - if err := node.unmount(ctx, original); err != nil { - return err - } + return nil + }) +} + +// FindMount returns the mount that 'd' belongs to. It walks the dirent back +// until a mount is found. It may return nil if no mount was found. +func (mns *MountNamespace) FindMount(d *Dirent) *Mount { + mns.mu.Lock() + defer mns.mu.Unlock() + renameMu.Lock() + defer renameMu.Unlock() - switch { - case len(origs) > 1: - mns.mounts[original] = origs[:len(origs)-1] - case len(origs) == 1: - // Drop mount reference taken at the end of - // MountNamespace.Mount. - original.DecRef() + return mns.findMountLocked(d) +} + +func (mns *MountNamespace) findMountLocked(d *Dirent) *Mount { + for { + if mnt := mns.mounts[d]; mnt != nil { + return mnt + } + if d.parent == nil { + return nil } + d = d.parent + } +} - delete(mns.mounts, node) - return nil - }) +// AllMountsUnder returns a slice of all mounts under the parent, including +// itself. +func (mns *MountNamespace) AllMountsUnder(parent *Mount) []*Mount { + mns.mu.Lock() + defer mns.mu.Unlock() + + var rv []*Mount + for _, mp := range mns.mounts { + if !mp.IsUndo() && mp.root.descendantOf(parent.root) { + rv = append(rv, mp) + } + } + return rv } // FindLink returns an Dirent from a given node, which may be a symlink. |