diff --git a/pkg/sentry/syscalls/linux/sys_mount.go b/pkg/sentry/syscalls/linux/sys_mount.go index f5576641f..dbb6dd3a1 100644 --- a/pkg/sentry/syscalls/linux/sys_mount.go +++ b/pkg/sentry/syscalls/linux/sys_mount.go @@ -45,8 +45,8 @@ func Mount(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, } // Silently allow MS_NOSUID, since we don't implement set-id bits anyway. - const unsupported = linux.MS_REMOUNT | linux.MS_SLAVE | - linux.MS_UNBINDABLE | linux.MS_MOVE | linux.MS_REC | linux.MS_NODIRATIME + const unsupported = linux.MS_REMOUNT | linux.MS_UNBINDABLE | linux.MS_MOVE | + linux.MS_REC | linux.MS_NODIRATIME // Linux just allows passing any flags to mount(2) - it won't fail when // unknown or unsupported flags are passed. Since we don't implement @@ -79,8 +79,7 @@ func Mount(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, return 0, nil, err } defer sourceTpop.Release(t) - _, err = t.Kernel().VFS().BindAt(t, creds, &sourceTpop.pop, &target.pop) - return 0, nil, err + return 0, nil, t.Kernel().VFS().BindAt(t, creds, &sourceTpop.pop, &target.pop) } const propagationFlags = linux.MS_SHARED | linux.MS_PRIVATE | linux.MS_SLAVE | linux.MS_UNBINDABLE if propFlag := flags & propagationFlags; propFlag != 0 { diff --git a/pkg/sentry/vfs/BUILD b/pkg/sentry/vfs/BUILD index 0b0277ffe..8b7a3d32d 100644 --- a/pkg/sentry/vfs/BUILD +++ b/pkg/sentry/vfs/BUILD @@ -64,6 +64,18 @@ go_template_instance( }, ) +go_template_instance( + name = "mount_list", + out = "mount_list.go", + package = "vfs", + prefix = "follower", + template = "//pkg/ilist:generic_list", + types = { + "Element": "*Mount", + "Linker": "*Mount", + }, +) + go_template_instance( name = "event_list", out = "event_list.go", @@ -141,6 +153,7 @@ go_library( "inotify_mutex.go", "lock.go", "mount.go", + "mount_list.go", "mount_namespace_refs.go", "mount_ring.go", "mount_unsafe.go", diff --git a/pkg/sentry/vfs/mount.go b/pkg/sentry/vfs/mount.go index 8ad994636..1c1dea3eb 100644 --- a/pkg/sentry/vfs/mount.go +++ b/pkg/sentry/vfs/mount.go @@ -23,6 +23,7 @@ import ( "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/atomicbitops" + "gvisor.dev/gvisor/pkg/cleanup" "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/refs" @@ -90,10 +91,19 @@ type Mount struct { // isShared indicates this mount has the MS_SHARED propagation type. isShared bool - // sharedEntry represents an entry in a circular list (ring) of mounts in a - // shared peer group. + // sharedEntry is an entry in a circular list (ring) of mounts in a shared + // peer group. sharedEntry mountEntry + // followerList is a list of mounts which has this mount as its leader. + followerList followerList + + // followerEntry is an entry in a followerList. + followerEntry + + // leader is the mount that this mount receives propagation events from. + leader *Mount + // groupID is the ID for this mount's shared peer group. If the mount is not // in a peer group, this is 0. groupID uint32 @@ -166,15 +176,12 @@ func (mnt *Mount) MountFlags() uint64 { return flags } -func (mnt *Mount) generateOptionalTags() string { - mnt.vfs.lockMounts() - defer mnt.vfs.unlockMounts(context.Background()) - // TODO(b/249777195): Support MS_SLAVE and MS_UNBINDABLE propagation types. - var optional string - if mnt.isShared { - optional = fmt.Sprintf("shared:%d", mnt.groupID) - } - return optional +func (mnt *Mount) isFollower() bool { + return mnt.leader != nil +} + +func (mnt *Mount) neverConnected() bool { + return mnt.ns == nil } // coveringMount returns a mount that completely covers mnt if it exists and nil @@ -234,6 +241,58 @@ func (vfs *VirtualFilesystem) MountDisconnected(ctx context.Context, creds *auth return newMount(vfs, fs, root, nil /* mntns */, opts), nil } +// attachMountLocked attaches mnt to vd and propagates the mount to vd.mount's +// peers and followers. This method is analogous to +// fs/namespace.c:attach_recursive_mnt() in Linux. +// +// +checklocks:vfs.mountMu +func (vfs *VirtualFilesystem) attachMountLocked(ctx context.Context, mnt *Mount, vd VirtualDentry) error { + vdCleanup := cleanup.Make(func() { + vd.DecRef(ctx) + }) + defer vdCleanup.Clean() + // This is equivalent to checking for SB_NOUSER in Linux, which is set on all + // anon mounts and sentry-internal filesystems like pipefs. + if vd.mount.neverConnected() { + return linuxerr.EINVAL + } + if vd.mount.ns.mounts+1 > MountMax { + return linuxerr.ENOSPC + } + if vd.mount.isShared { + if err := vfs.allocMountGroupIDs(mnt, true); err != nil { + return err + } + } + propMnts, err := vfs.doPropagation(ctx, mnt, vd) + cleanup := cleanup.Make(func() { + // Checklocks can't understand lock state within closures, so we have to + // force. + vfs.freeMountGroupIDs(mnt.submountsLocked()) // +checklocksforce + for pmnt := range propMnts { + vfs.abortTree(ctx, pmnt) // +checklocksforce + } + }) + defer cleanup.Clean() + if err != nil { + return err + } + if vd.mount.isShared { + for _, m := range mnt.submountsLocked() { + m.isShared = true + } + } + vdCleanup.Release() + if err := vfs.connectMountAtLocked(ctx, mnt, vd); err != nil { + return err + } + cleanup.Release() + for pmnt := range propMnts { + vfs.commitTree(ctx, pmnt) + } + return nil +} + // ConnectMountAt connects mnt at the path represented by target. // // Preconditions: mnt must be disconnected. @@ -246,28 +305,7 @@ func (vfs *VirtualFilesystem) ConnectMountAt(ctx context.Context, creds *auth.Cr } vfs.lockMounts() defer vfs.unlockMounts(ctx) - // This is equivalent to checking for SB_NOUSER in Linux, which is set on all - // anon mounts and sentry-internal filesystems like pipefs. - if vd.mount.ns == nil { - vfs.delayDecRef(vd) - return linuxerr.EINVAL - } - tree := vfs.preparePropagationTree(mnt, vd) - // Check if the new mount + all the propagation mounts puts us over the max. - if uint32(len(tree)+1)+vd.mount.ns.mounts > MountMax { - // We need to unlock mountMu first because DecRef takes a lock on the - // filesystem mutex in some implementations, which can lead to circular - // locking. - vfs.abortPropagationTree(ctx, tree) - vfs.delayDecRef(vd) - return linuxerr.ENOSPC - } - if err := vfs.connectMountAtLocked(ctx, mnt, vd); err != nil { - vfs.abortPropagationTree(ctx, tree) - return err - } - vfs.commitPropagationTree(ctx, tree) - return nil + return vfs.attachMountLocked(ctx, mnt, vd) } // connectMountAtLocked attaches mnt at vd. This method consumes a reference on @@ -340,22 +378,22 @@ func (vfs *VirtualFilesystem) lockMountpoint(vd VirtualDentry) (VirtualDentry, e } // 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 -// options are copied. -func (vfs *VirtualFilesystem) CloneMountAt(mnt *Mount, root *Dentry, mopts *MountOptions) *Mount { +// mount options. If mount options are nil, mnt's options are copied. The clone +// is added to mnt's peer group if mnt is shared. If not the clone is in a +// shared peer group by itself. +func (vfs *VirtualFilesystem) CloneMountAt(mnt *Mount, root *Dentry, mopts *MountOptions) (*Mount, error) { vfs.lockMounts() defer vfs.unlockMounts(context.Background()) - clone := vfs.cloneMount(mnt, root, mopts) - return clone + return vfs.cloneMount(mnt, root, mopts, makeSharedClone) } // cloneMount returns a new mount with mnt.fs as the filesystem and root as the -// root. The returned mount has an extra reference. +// root, with a propagation type specified by cloneType. The returned mount has +// an extra reference. If mopts is nil, use the options found in mnt. +// This method is analogous to fs/namespace.c:clone_mnt() in Linux. // // +checklocks:vfs.mountMu -// +checklocksalias:mnt.vfs.mountMu=vfs.mountMu -func (vfs *VirtualFilesystem) cloneMount(mnt *Mount, root *Dentry, mopts *MountOptions) *Mount { +func (vfs *VirtualFilesystem) cloneMount(mnt *Mount, root *Dentry, mopts *MountOptions, cloneType int) (*Mount, error) { opts := mopts if opts == nil { opts = &MountOptions{ @@ -364,10 +402,39 @@ func (vfs *VirtualFilesystem) cloneMount(mnt *Mount, root *Dentry, mopts *MountO } } clone := vfs.NewDisconnectedMount(mnt.fs, root, opts) - if mnt.isShared { - vfs.addPeer(mnt, clone) + if cloneType&(makeFollowerClone|makePrivateClone|sharedToFollowerClone) != 0 { + clone.groupID = 0 + } else { + clone.groupID = mnt.groupID } - return clone + if cloneType&makeSharedClone != 0 && clone.groupID == 0 { + gid, err := vfs.allocateGroupID() + if err != nil { + vfs.delayDecRef(clone) + return nil, linuxerr.ENOSPC + } + clone.groupID = gid + } + clone.isShared = mnt.isShared + if cloneType&makeFollowerClone != 0 || (cloneType&sharedToFollowerClone != 0 && mnt.isShared) { + mnt.followerList.PushFront(clone) + clone.leader = mnt + clone.isShared = false + } else if cloneType&makePrivateClone == 0 { + if cloneType&makeSharedClone != 0 || mnt.isShared { + mnt.sharedEntry.Add(&clone.sharedEntry) + } + if mnt.isFollower() { + mnt.leader.followerList.InsertAfter(mnt, clone) + } + clone.leader = mnt.leader + } else { + clone.isShared = false + } + if cloneType&makeSharedClone != 0 { + clone.isShared = true + } + return clone, nil } type cloneTreeNode struct { @@ -376,17 +443,16 @@ type cloneTreeNode struct { } // 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. +// dentry at root. The new descendants are added to mnt's pending mount list. // `cloneFunc` is a callback that is executed for each cloned mount. +// This method is analogous to fs/namespace.c:copy_tree() in Linux. // // +checklocks:vfs.mountMu -func (vfs *VirtualFilesystem) cloneMountTree( - ctx context.Context, - mnt *Mount, - root *Dentry, - cloneFunc func(ctx context.Context, oldmnt, newMnt *Mount), -) (*Mount, error) { - clone := vfs.cloneMount(mnt, root, nil) +func (vfs *VirtualFilesystem) cloneMountTree(ctx context.Context, mnt *Mount, root *Dentry, cloneType int, cloneFunc func(ctx context.Context, oldmnt, newMnt *Mount)) (*Mount, error) { + clone, err := vfs.cloneMount(mnt, root, nil, cloneType) + if err != nil { + return nil, err + } if cloneFunc != nil { cloneFunc(ctx, mnt, clone) } @@ -395,7 +461,14 @@ func (vfs *VirtualFilesystem) cloneMountTree( p := queue[len(queue)-1] queue = queue[:len(queue)-1] for c := range p.prevMount.children { - m := vfs.cloneMount(c, c.root, nil) + if mp := c.getKey(); p.prevMount == mnt && !mp.mount.fs.Impl().IsDescendant(VirtualDentry{mnt, root}, mp) { + continue + } + m, err := vfs.cloneMount(c, c.root, nil, cloneType) + if err != nil { + vfs.abortTree(ctx, clone) + return nil, err + } mp := VirtualDentry{ mount: p.parentMount, dentry: c.point(), @@ -419,42 +492,26 @@ func (vfs *VirtualFilesystem) cloneMountTree( // path. // // TODO(b/249121230): Support recursive bind mounting. -func (vfs *VirtualFilesystem) BindAt(ctx context.Context, creds *auth.Credentials, source, target *PathOperation) (*Mount, error) { +func (vfs *VirtualFilesystem) BindAt(ctx context.Context, creds *auth.Credentials, source, target *PathOperation) error { sourceVd, err := vfs.GetDentryAt(ctx, creds, source, &GetDentryOptions{}) if err != nil { - return nil, err + return err } defer sourceVd.DecRef(ctx) targetVd, err := vfs.GetDentryAt(ctx, creds, target, &GetDentryOptions{}) if err != nil { - return nil, err + return err } + vfs.lockMounts() defer vfs.unlockMounts(ctx) - // This is equivalent to checking for SB_NOUSER in Linux, which is set on all - // anon mounts. - if targetVd.mount.ns == nil { + clone, err := vfs.cloneMount(sourceVd.mount, sourceVd.dentry, nil, 0) + if err != nil { vfs.delayDecRef(targetVd) - return nil, linuxerr.EINVAL + return err } - - clone := vfs.cloneMount(sourceVd.mount, sourceVd.dentry, nil) vfs.delayDecRef(clone) - tree := vfs.preparePropagationTree(clone, targetVd) - if uint32(1+len(tree))+targetVd.mount.ns.mounts > MountMax { - vfs.setPropagation(clone, linux.MS_PRIVATE) - vfs.abortPropagationTree(ctx, tree) - vfs.delayDecRef(targetVd) - return nil, linuxerr.ENOSPC - } - - if err := vfs.connectMountAtLocked(ctx, clone, targetVd); err != nil { - vfs.setPropagation(clone, linux.MS_PRIVATE) - vfs.abortPropagationTree(ctx, tree) - return nil, err - } - vfs.commitPropagationTree(ctx, tree) - return clone, nil + return vfs.attachMountLocked(ctx, clone, targetVd) } // MountAt creates and mounts a Filesystem configured by the given arguments. @@ -527,45 +584,30 @@ func (vfs *VirtualFilesystem) UmountAt(ctx context.Context, creds *auth.Credenti } } - umountTree := []*Mount{vd.mount} - parent, mountpoint := vd.mount.parent(), vd.mount.point() - if parent != nil && parent.isShared { - for peer := parent.sharedEntry.Next(); peer != parent; peer = peer.sharedEntry.Next() { - umountMnt := vfs.mounts.Lookup(peer, mountpoint) - // From https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt: - // If any peer has some child mounts, then that mount is not unmounted, - // but all other mounts are unmounted. - if umountMnt == nil { - continue - } - if len(umountMnt.children) == 0 || umountMnt.coveringMount() != nil { - umountTree = append(umountTree, umountMnt) - } - } + if opts.Flags&linux.MNT_DETACH == 0 && vfs.arePropMountsBusy(vd.mount) { + return linuxerr.EBUSY } // TODO(gvisor.dev/issue/1035): Linux special-cases umount of the caller's // root, which we don't implement yet (we'll just fail it since the caller // holds a reference on it). - vfs.mounts.seq.BeginWrite() - if opts.Flags&linux.MNT_DETACH == 0 { - if len(vd.mount.children) != 0 { - vfs.mounts.seq.EndWrite() - return linuxerr.EBUSY - } - // We are holding a reference on vd.mount. - expectedRefs := int64(1) - if !vd.mount.umounted { - expectedRefs = 2 - } - if vd.mount.refs.Load()&^math.MinInt64 != expectedRefs { // mask out MSB - vfs.mounts.seq.EndWrite() - return linuxerr.EBUSY + propMounts := []*Mount{vd.mount} + if vd.mount.parent() != nil { + for m := nextPropMount(vd.mount.parent(), vd.mount.parent()); m != nil; m = nextPropMount(m, vd.mount.parent()) { + child := vfs.mounts.Lookup(m, vd.mount.point()) + if child == nil { + continue + } + if len(child.children) != 0 && child.coveringMount() == nil { + continue + } + propMounts = append(propMounts, child) } } - for _, mnt := range umountTree { - vfs.umountRecursiveLocked(mnt, &umountRecursiveOptions{ + vfs.mounts.seq.BeginWrite() + for _, m := range propMounts { + vfs.umountRecursiveLocked(m, &umountRecursiveOptions{ eager: opts.Flags&linux.MNT_DETACH == 0, disconnectHierarchy: true, }) @@ -574,6 +616,21 @@ func (vfs *VirtualFilesystem) UmountAt(ctx context.Context, creds *auth.Credenti return nil } +// mountHasExpectedRefs checks that mnt has the correct number of references +// before a umount. It is analogous to fs/pnode.c:do_refcount_check(). +// +// +checklocks:vfs.mountMu +func (vfs *VirtualFilesystem) mountHasExpectedRefs(mnt *Mount) bool { + expectedRefs := int64(1) + if !mnt.umounted { + expectedRefs++ + } + if mnt.coveringMount() != nil { + expectedRefs++ + } + return mnt.refs.Load()&^math.MinInt64 == expectedRefs // mask out MSB +} + // +stateify savable type umountRecursiveOptions struct { // If eager is true, ensure that future calls to Mount.tryIncMountedRef() @@ -617,9 +674,7 @@ func (vfs *VirtualFilesystem) umountRecursiveLocked(mnt *Mount, opts *umountRecu if parent := mnt.parent(); parent != nil && (opts.disconnectHierarchy || !parent.umounted) { vfs.delayDecRef(vfs.disconnectLocked(mnt)) } - if mnt.isShared { - vfs.setPropagation(mnt, linux.MS_PRIVATE) - } + vfs.setPropagation(mnt, linux.MS_PRIVATE) } if opts.eager { for { @@ -1255,7 +1310,7 @@ func (vfs *VirtualFilesystem) GenerateProcMountInfo(ctx context.Context, taskRoo fmt.Fprintf(buf, "%s ", opts) // (7) Optional fields: zero or more fields of the form "tag[:value]". - fmt.Fprintf(buf, "%s ", mnt.generateOptionalTags()) + fmt.Fprintf(buf, "%s", vfs.generateOptionalTags(ctx, mnt, taskRootDir)) // (8) Separator: the end of the optional fields is marked by a single hyphen. fmt.Fprintf(buf, "- ") @@ -1311,25 +1366,30 @@ func superBlockOpts(mountPath string, mnt *Mount) string { return opts } -// allocateGroupID returns a new mount group id if one is available, and -// error otherwise. If the group ID bitmap is full, double the size of the -// bitmap before allocating the new group id. -// -// +checklocks:vfs.mountMu -func (vfs *VirtualFilesystem) allocateGroupID() (uint32, error) { - groupID, err := vfs.groupIDBitmap.FirstZero(1) - if err != nil { - if err := vfs.groupIDBitmap.Grow(uint32(vfs.groupIDBitmap.Size())); err != nil { - return 0, err +func (vfs *VirtualFilesystem) generateOptionalTags(ctx context.Context, mnt *Mount, root VirtualDentry) string { + vfs.lockMounts() + defer vfs.unlockMounts(ctx) + // TODO(b/249777195): Support MS_UNBINDABLE propagation type. + var optionalSb strings.Builder + if mnt.isShared { + optionalSb.WriteString(fmt.Sprintf("shared:%d ", mnt.groupID)) + } + if mnt.isFollower() { + // Per man mount_namespaces(7), propagate_from should not be + // included in optional tags if the leader "is the immediate leader of the + // mount, or if there is no dominant peer group under the same root". A + // dominant peer group is the nearest reachable mount in the leader/follower + // chain. + optionalSb.WriteString(fmt.Sprintf("master:%d ", mnt.leader.groupID)) + var dominant *Mount + for m := mnt.leader; m != nil; m = m.leader { + if dominant = vfs.peerUnderRoot(ctx, m, mnt.ns, root); dominant != nil { + break + } + } + if dominant != nil && dominant != mnt.leader { + optionalSb.WriteString(fmt.Sprintf("propagate_from:%d ", dominant.groupID)) } } - vfs.groupIDBitmap.Add(groupID) - return groupID, nil -} - -// freeGroupID marks a groupID as available for reuse. -// -// +checklocks:vfs.mountMu -func (vfs *VirtualFilesystem) freeGroupID(id uint32) { - vfs.groupIDBitmap.Remove(id) + return optionalSb.String() } diff --git a/pkg/sentry/vfs/namespace.go b/pkg/sentry/vfs/namespace.go index 8e275e870..a8183d941 100644 --- a/pkg/sentry/vfs/namespace.go +++ b/pkg/sentry/vfs/namespace.go @@ -170,13 +170,16 @@ func (vfs *VirtualFilesystem) CloneMountNamespace( vfs.lockMounts() defer vfs.unlockMounts(ctx) - newRoot, err := vfs.cloneMountTree(ctx, ns.root, ns.root.root, + cloneType := 0 + if ns.Owner != newns.Owner { + cloneType = sharedToFollowerClone + } + newRoot, err := vfs.cloneMountTree(ctx, ns.root, ns.root.root, cloneType, func(ctx context.Context, src, dst *Mount) { vfs.updateRootAndCWD(ctx, root, cwd, src, dst) // +checklocksforce: vfs.mountMu is locked. }) if err != nil { newns.DecRef(ctx) - vfs.abortTree(ctx, newRoot) return nil, err } newns.root = newRoot diff --git a/pkg/sentry/vfs/propagation.go b/pkg/sentry/vfs/propagation.go index 76b698e84..aa5a5fe77 100644 --- a/pkg/sentry/vfs/propagation.go +++ b/pkg/sentry/vfs/propagation.go @@ -16,7 +16,6 @@ package vfs import ( "fmt" - "strings" "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/bits" @@ -25,143 +24,18 @@ import ( "gvisor.dev/gvisor/pkg/sentry/kernel/auth" ) -func propTypeToString(pflag uint32) string { - if pflag == 0 { - return "0" - } - var ( - b strings.Builder - sep string - ) - handleFlag := func(flag uint32, str string) { - if pflag&flag != 0 { - fmt.Fprintf(&b, "%s%s", sep, str) - sep = "|" - pflag &^= flag - } - } - handleFlag(linux.MS_SHARED, "shared") - handleFlag(linux.MS_PRIVATE, "private") - handleFlag(linux.MS_SLAVE, "slave") - handleFlag(linux.MS_UNBINDABLE, "unbindable") - if pflag != 0 { - fmt.Fprintf(&b, "%s%#x", sep, pflag) - } - return b.String() -} - -// setPropagation sets the propagation on mnt for a propagation type. -// -// +checklocks:vfs.mountMu -func (vfs *VirtualFilesystem) setPropagation(mnt *Mount, pflag uint32) error { - switch pflag { - case linux.MS_SHARED: - if !mnt.isShared { - id, err := vfs.allocateGroupID() - if err != nil { - return err - } - mnt.groupID = id - mnt.sharedEntry.Init(mnt) - mnt.isShared = true - } - case linux.MS_PRIVATE: - if mnt.isShared { - if mnt.sharedEntry.Empty() { - vfs.freeGroupID(mnt.groupID) - } - mnt.sharedEntry.Remove() - mnt.groupID = 0 - mnt.isShared = false - } - default: - panic(fmt.Sprintf("unsupported propagation type: %s", propTypeToString(pflag))) - } - 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, new *Mount) { - mnt.sharedEntry.Add(&new.sharedEntry) - new.isShared = true - new.groupID = mnt.groupID -} - -// 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.isShared { - return tree - } - if !mnt.isShared { - vfs.setPropagation(mnt, linux.MS_SHARED) - } - for peer := vd.mount.sharedEntry.Next(); peer != vd.mount; peer = peer.sharedEntry.Next() { - // Skip newly added (disconnected) mounts. - if peer.ns == nil { - continue - } - peerVd := VirtualDentry{ - mount: peer, - dentry: vd.dentry, - } - peerVd.IncRef() - clone := vfs.cloneMount(mnt, mnt.root, nil) - tree[clone] = peerVd - } - 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. - for mnt, vd := range tree { - // If there is already a mount at this (parent, point), disconnect it and - // reconnect it to the new mount once it is connected. - vd.dentry.mu.Lock() - child := vfs.mounts.Lookup(vd.mount, vd.dentry) - vfs.mounts.seq.BeginWrite() - if child != nil { - vfs.delayDecRef(vfs.disconnectLocked(child)) - } - vfs.connectLocked(mnt, vd, vd.mount.ns) - vfs.delayDecRef(mnt) - - if child != nil { - newmp := VirtualDentry{mnt, mnt.root} - newmp.IncRef() - vfs.connectLocked(child, newmp, newmp.mount.ns) - vfs.delayDecRef(child) - } - vfs.mounts.seq.EndWrite() - vd.dentry.mu.Unlock() - } -} - -// 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 { - vfs.delayDecRef(vd) - vfs.delayDecRef(mnt) - vfs.setPropagation(mnt, linux.MS_PRIVATE) - } -} +const ( + // The following constants are possible bits for the cloneType argument to + // VirtualFilesystem.cloneMount() and related functions. + // Analogous to CL_MAKE_SHARED in Linux. + makeSharedClone = 1 << iota + // Analogous to CL_SLAVE in Linux. + makeFollowerClone + // Analogous to CL_PRIVATE in Linux. + makePrivateClone + // Analogous to CL_SHARED_TO_SLAVE in Linux. + sharedToFollowerClone +) // +checklocks:vfs.mountMu func (vfs *VirtualFilesystem) commitPendingTree(ctx context.Context, mnt *Mount) { @@ -238,12 +112,412 @@ func (vfs *VirtualFilesystem) SetMountPropagationAt(ctx context.Context, creds * } // SetMountPropagation changes the propagation type of the mount. -func (vfs *VirtualFilesystem) SetMountPropagation(mnt *Mount, propFlags uint32) { +func (vfs *VirtualFilesystem) SetMountPropagation(mnt *Mount, propFlags uint32) error { vfs.lockMounts() defer vfs.unlockMounts(context.Background()) - if propFlags&(linux.MS_SHARED|linux.MS_PRIVATE) != 0 { - vfs.setPropagation(mnt, propFlags) + if propFlags == linux.MS_SHARED { + if err := vfs.allocMountGroupIDs(mnt, false); err != nil { + return fmt.Errorf("allocMountGroupIDs: %v", err) + } + } + vfs.setPropagation(mnt, propFlags) + return nil +} + +// setPropagation sets the propagation on mnt for a propagation type. This +// method is analogous to fs/pnode.c:change_mnt_propagation() in Linux. +// +// +checklocks:vfs.mountMu +func (vfs *VirtualFilesystem) setPropagation(mnt *Mount, propFlags uint32) { + if propFlags == linux.MS_SHARED { + mnt.isShared = true + return + } + // pflag is MS_PRIVATE, MS_SLAVE, or MS_UNBINDABLE. The algorithm is the same + // for MS_PRIVATE/MS_SLAVE/MS_UNBINDABLE, except that in the + // private/unbindable case we clear the leader and followerEntry after the + // procedure is finished. + var leader *Mount + if mnt.sharedEntry.Empty() { + // If mnt is shared and in a peer group with only itself, just make it + // private. + if mnt.isShared { + vfs.freeGroupID(mnt) + mnt.isShared = false + } + // If mnt is not a follower to any other mount, make all of its followers + // also private. + leader = mnt.leader + if leader == nil { + for !mnt.followerList.Empty() { + f := mnt.followerList.Front() + mnt.followerList.Remove(f) + f.leader = nil + } + } } else { - panic(fmt.Sprintf("unsupported propagation type: %s", propTypeToString(propFlags))) + // Pick a suitable new leader. Linux chooses the first peer that shares a + // root dentry, or any peer if none matches that criteria. + leader = mnt.sharedEntry.Next() + for m := mnt.sharedEntry.Next(); m != mnt; m = m.sharedEntry.Next() { + if m.root == mnt.root { + leader = m + break + } + } + // Clear out mnt's shared attributes. + mnt.sharedEntry.Remove() + mnt.groupID = 0 + mnt.isShared = false + } + // Transfer all of mnt's followers to the new leader. + for f := mnt.followerList.Front(); f != nil; f = f.followerEntry.Next() { + f.leader = leader + } + // Remove mnt from its current follower list and add it to the new leader. + if mnt.leader != nil { + mnt.leader.followerList.Remove(mnt) + } + if leader != nil && propFlags == linux.MS_SLAVE { + leader.followerList.PushFront(mnt) + mnt.leader = leader + } else { + mnt.leader = nil + } + + // Add mnts followers to leader's follower list. This also links all their + // followerEntry together. + if !mnt.followerList.Empty() && leader != nil { + leader.followerList.PushBackList(&mnt.followerList) } } + +type propState struct { + origSrc *Mount + prevSrc *Mount + prevDst *Mount + dstLeader *Mount + propList map[*Mount]struct{} + visitedLeaders map[*Mount]struct{} +} + +// doPropagation returns a list of propagated mounts with their mount points +// set. The mounts are clones of src and have an extra reference taken. If +// propagation fails at any point, the method returns all the mounts propagated +// up until that point so they can be properly released. This method is +// analogous to fs/pnode.c:propagate_mnt() in Linux. +// +// +checklocks:vfs.mountMu +func (vfs *VirtualFilesystem) doPropagation(ctx context.Context, src *Mount, dst VirtualDentry) (map[*Mount]struct{}, error) { + if !dst.mount.isShared { + return nil, nil + } + s := propState{ + origSrc: src, + prevSrc: src, + prevDst: dst.mount, + dstLeader: dst.mount.leader, + propList: map[*Mount]struct{}{}, + visitedLeaders: map[*Mount]struct{}{}, + } + for peer := dst.mount.sharedEntry.Next(); peer != dst.mount; peer = peer.sharedEntry.Next() { + if err := vfs.propagateMount(ctx, peer, dst.dentry, &s); err != nil { + return s.propList, err + } + } + for follower := nextFollowerPeerGroup(dst.mount, dst.mount); follower != nil; follower = nextFollowerPeerGroup(follower, dst.mount) { + peer := follower + for { + if err := vfs.propagateMount(ctx, peer, dst.dentry, &s); err != nil { + return s.propList, err + } + peer = peer.sharedEntry.Next() + if peer == follower { + break + } + } + } + return s.propList, nil +} + +// peers returns if two mounts are in the same peer group. +// +// +checklocks:vfs.mountMu +func (vfs *VirtualFilesystem) peers(m1, m2 *Mount) bool { + return m1.groupID == m2.groupID && m1.groupID != 0 +} + +// propagateMount propagates state.srcMount to dstMount at dstPoint. +// This method is analogous to fs/pnode.c:propagate_one() in Linux. +// +// +checklocks:vfs.mountMu +func (vfs *VirtualFilesystem) propagateMount(ctx context.Context, dstMnt *Mount, dstPoint *Dentry, state *propState) error { + // Skip newly added mounts. + if dstMnt.neverConnected() { + return nil + } + mp := VirtualDentry{mount: dstMnt, dentry: dstPoint} + if !mp.mount.fs.Impl().IsDescendant(VirtualDentry{dstMnt, dstMnt.root}, mp) { + return nil + } + cloneType := 0 + if vfs.peers(dstMnt, state.prevDst) { + cloneType = makeSharedClone + } else { + done := false + // Get the most recent leader that we've propagated from in the tree. + var leader, underLeader *Mount + for underLeader = dstMnt; ; underLeader = leader { + leader = underLeader.leader + if _, ok := state.visitedLeaders[leader]; ok { + break + } + if leader == state.dstLeader { + break + } + } + for { + parent := state.prevSrc.parent() + // Check that prevSrc is a follower, not a peer of the original. + if vfs.peers(state.prevSrc, state.origSrc) { + break + } + // Check if the mount prvSrc attached to (aka parent) has the same leader + // as the most recently visited leader in the mount tree. + done = parent.leader == leader + // If the leader under the most recently visited leader is not peers with + // the mount prevSrc attached to, then it's not part of this propagation + // tree and we need to traverse up the tree to get to the real src. + if done && vfs.peers(underLeader, parent) { + break + } + // Traverse back up the propagation tree to get the proper src. We only + // want to propagate from this mount's leader or peers of that leader. + state.prevSrc = state.prevSrc.leader + if done { + break + } + } + cloneType = makeFollowerClone + if dstMnt.isShared { + cloneType |= makeSharedClone + } + } + clone, err := vfs.cloneMount(state.prevSrc, state.prevSrc.root, nil, cloneType) + if err != nil { + return err + } + mp.IncRef() + clone.setKey(mp) + state.propList[clone] = struct{}{} + if dstMnt.leader != state.dstLeader { + state.visitedLeaders[dstMnt.leader] = struct{}{} + } + state.prevDst = dstMnt + state.prevSrc = clone + if uint32(len(state.propList))+dstMnt.ns.mounts > MountMax { + return linuxerr.ENOSPC + } + return nil +} + +// nextFollowerPeerGroup iterates through the propagation tree and returns the +// first mount in each follower peer group under mnt. Once all the groups +// have been iterated through the method returns nil. This method is analogous +// to fs/pnode.c:next_group() in Linux. +func nextFollowerPeerGroup(mnt *Mount, start *Mount) *Mount { + for { + // If mnt has any followers, this loop returns that follower. Otherwise mnt + // is updated until it is the last peer in its peer group. This has the + // effect of moving down the propagation tree until the bottommost follower. + // After that the loop moves across peers (if possible) to the last peer + // in the group. + for { + if !mnt.neverConnected() && !mnt.followerList.Empty() { + return mnt.followerList.Front() + } + next := mnt.sharedEntry.Next() + if mnt.groupID == start.groupID { + if next == start { + return nil + } + // If mnt is shared+slave, its next follower will be the same as its + // next peer. + } else if mnt.isFollower() && mnt.followerEntry.Next() != next { + break + } + mnt = next + } + // At this point mnt is the last peer in its shared+slave peer group. + // This loop returns the next follower in mnt's leader's follower list. Once + // the list of followers is exhausted it sets mnt to be the leader and + // breaks out of the loop. This has the effect of moving across the tree + // branches until all branches are exhausted. Then it moves up the tree to + // the parent. + for { + leader := mnt.leader + if mnt.followerEntry.Next() != nil { + return mnt.followerEntry.Next() + } + mnt = leader.sharedEntry.Next() + if leader.groupID == start.groupID { + break + } + if leader.followerEntry.Next() == mnt { + break + } + mnt = leader + } + if mnt == start { + return nil + } + } +} + +// nextPropMount iterates through the propagation tree rooted at start. It +// returns nil when there are no more mounts in the tree. Otherwise, it returns +// the next mount in the tree. It is analogous to fs/pnode.c:propagation_next() +// in Linux. +func nextPropMount(mnt, start *Mount) *Mount { + m := mnt + if !m.neverConnected() && !m.followerList.Empty() { + return m.followerList.Front() + } + for { + leader := m.leader + if leader == start.leader { + next := m.sharedEntry.Next() + if next == start { + return nil + } + return next + } else if m.followerEntry.Next() != nil { + return m.followerEntry.Next() + } + m = leader + } +} + +// arePropMountsBusy checks if all the mounts that mnt's parents propagate to +// have the correct number of references before a call to umount. It is +// analogous to fs/pnode.c:propagate_mount_busy() in Linux. +// +// +checklocks:vfs.mountMu +func (vfs *VirtualFilesystem) arePropMountsBusy(mnt *Mount) bool { + parent := mnt.parent() + if parent == nil { + return !vfs.mountHasExpectedRefs(mnt) + } + if len(mnt.children) != 0 || !vfs.mountHasExpectedRefs(mnt) { + return true + } + for m := nextPropMount(parent, parent); m != nil; m = nextPropMount(m, parent) { + child := vfs.mounts.Lookup(m, mnt.point()) + if child == nil { + continue + } + if len(child.children) != 0 && child.coveringMount() == nil { + continue + } + if !vfs.mountHasExpectedRefs(child) { + return true + } + } + return false +} + +// allocateGroupID returns a new mount group id if one is available, and +// error otherwise. If the group ID bitmap is full, double the size of the +// bitmap before allocating the new group id. It is analogous to +// fs/namespace.c:mnt_alloc_group_id() in Linux. +// +// +checklocks:vfs.mountMu +func (vfs *VirtualFilesystem) allocateGroupID() (uint32, error) { + groupID, err := vfs.groupIDBitmap.FirstZero(1) + if err != nil { + if err := vfs.groupIDBitmap.Grow(uint32(vfs.groupIDBitmap.Size())); err != nil { + return 0, err + } + } + vfs.groupIDBitmap.Add(groupID) + return groupID, nil +} + +// freeGroupID marks a groupID as available for reuse. It is analogous to +// fs/namespace.c:mnt_release_group_id() in Linux. +// +// +checklocks:vfs.mountMu +func (vfs *VirtualFilesystem) freeGroupID(mnt *Mount) { + vfs.groupIDBitmap.Remove(mnt.groupID) + mnt.groupID = 0 +} + +// freeMountGroupIDs zeroes out all of the mounts' groupIDs and returns them +// to the pool of available ids. It is analogous to +// fs/namespace.c:cleanup_group_ids() in Linux. +// +// +checklocks:vfs.mountMu +func (vfs *VirtualFilesystem) freeMountGroupIDs(mnts []*Mount) { + for _, m := range mnts { + if m.groupID != 0 && m.isShared { + vfs.freeGroupID(m) + } + } +} + +// allocMountGroupIDs allocates a new group id for mnt. If recursive is true, it +// also allocates a new group id for all mounts children. It is analogous to +// fs/namespace.c:invent_group_ids() in Linux. +// +// +checklocks:vfs.mountMu +func (vfs *VirtualFilesystem) allocMountGroupIDs(mnt *Mount, recursive bool) error { + var mnts []*Mount + if recursive { + mnts = mnt.submountsLocked() + } else { + mnts = []*Mount{mnt} + } + for _, m := range mnts { + if m.groupID == 0 && !m.isShared { + gid, err := vfs.allocateGroupID() + m.groupID = gid + if err != nil { + vfs.freeMountGroupIDs(mnts) + return err + } + } + } + return nil +} + +// peerUnderRoot iterates through mnt's peers until it finds a mount that is in +// ns and is reachable from root. This method is analogous to +// fs/pnode.c:get_peer_under_root() in Linux. +// +// +checklocks:vfs.mountMu +func (vfs *VirtualFilesystem) peerUnderRoot(ctx context.Context, mnt *Mount, ns *MountNamespace, root VirtualDentry) *Mount { + m := mnt + for { + if m.ns == ns { + if vfs.isPathReachable(ctx, root, VirtualDentry{mnt, mnt.root}) { + return m + } + } + m = m.sharedEntry.Next() + if m == mnt { + break + } + } + return nil +} + +// isPathReachable returns true if vd is reachable from vfsroot. It is analogous +// to fs/namespace.c:is_path_reachable() in Linux. +// +// +checklocks:vfs.mountMu +func (vfs *VirtualFilesystem) isPathReachable(ctx context.Context, vfsroot VirtualDentry, vd VirtualDentry) bool { + for vd.mount != vfsroot.mount && vd.mount.parent() != nil { + vd = vd.mount.getKey() + } + return vd.mount == vfsroot.mount && vd.mount.fs.Impl().IsDescendant(vfsroot, vd) +} diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 44bcdeab1..57ac2348d 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -1410,6 +1410,7 @@ cc_binary( "@com_google_absl//absl/time", gtest, "//test/util:eventfd_util", + "//test/util:logging", "//test/util:mount_util", "//test/util:multiprocess_util", "//test/util:posix_error", @@ -1418,6 +1419,7 @@ cc_binary( "//test/util:test_main", "//test/util:test_util", "//test/util:thread_util", + "@com_google_absl//absl/strings:str_format", ], ) diff --git a/test/syscalls/linux/mount.cc b/test/syscalls/linux/mount.cc index 984759353..e6d6c0d4b 100644 --- a/test/syscalls/linux/mount.cc +++ b/test/syscalls/linux/mount.cc @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -27,6 +28,8 @@ #include #include +#include +#include #include #include #include @@ -43,6 +46,7 @@ #include "absl/strings/match.h" #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" #include "absl/time/clock.h" @@ -52,6 +56,7 @@ #include "test/util/file_descriptor.h" #include "test/util/fs_util.h" #include "test/util/linux_capability_util.h" +#include "test/util/logging.h" #include "test/util/mount_util.h" #include "test/util/multiprocess_util.h" #include "test/util/posix_error.h" @@ -1132,7 +1137,7 @@ TEST(MountTest, PropagateMountEvent) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt = ASSERT_NO_ERRNO_AND_VALUE( - Mount("", dir1.path().c_str(), "tmpfs", 0, "", 0)); + Mount("", dir1.path().c_str(), "tmpfs", 0, "", MNT_DETACH)); auto const child_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), @@ -1142,7 +1147,7 @@ TEST(MountTest, PropagateMountEvent) { Mount(dir1.path(), dir2.path(), "", MS_BIND, "", MNT_DETACH)); // This mount should propagate to dir2. auto const child_mnt = ASSERT_NO_ERRNO_AND_VALUE( - Mount("", child_dir.path().c_str(), "tmpfs", 0, "", 0)); + Mount("", child_dir.path().c_str(), "tmpfs", 0, "", MNT_DETACH)); const std::string child_path1 = JoinPath(dir1.path(), Basename(child_dir.path())); @@ -1164,7 +1169,7 @@ TEST(MountTest, PropagateUmountEvent) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto const mnt = ASSERT_NO_ERRNO_AND_VALUE( - Mount("", dir1.path().c_str(), "tmpfs", 0, "", 0)); + Mount("", dir1.path().c_str(), "tmpfs", 0, "", MNT_DETACH)); auto const child_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path())); ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), @@ -1176,7 +1181,7 @@ TEST(MountTest, PropagateUmountEvent) { // unmounted, which should also propagate to dir2. { auto const child_mnt = ASSERT_NO_ERRNO_AND_VALUE( - Mount("", child_dir.path().c_str(), "tmpfs", 0, "", 0)); + Mount("", child_dir.path().c_str(), "tmpfs", 0, "", MNT_DETACH)); } const std::string child_path1 = @@ -1428,6 +1433,424 @@ TEST(MountTest, UmountSharedBind) { ASSERT_THAT(umount2(dirpath, MNT_DETACH), SyscallSucceeds()); } +TEST(MountTest, MakeSlave) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); + auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE(Mount( + dir1.path().c_str(), dir1.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), + SyscallSucceeds()); + auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount( + dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + + ASSERT_THAT(mount(0, dir2.path().c_str(), 0, MS_SLAVE, 0), SyscallSucceeds()); + auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); + ASSERT_NE(optionals[dir1.path()][0].shared, 0); + ASSERT_NE(optionals[dir2.path()][0].master, 0); +} + +TEST(MountTest, MakeSharedSlave) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); + + auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE(Mount( + dir1.path().c_str(), dir1.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), + SyscallSucceeds()); + auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount( + dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + + ASSERT_THAT(mount(0, dir2.path().c_str(), 0, MS_SLAVE, 0), SyscallSucceeds()); + ASSERT_THAT(mount(0, dir2.path().c_str(), 0, MS_SHARED, 0), + SyscallSucceeds()); + + auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); + ASSERT_NE(optionals[dir1.path()][0].shared, 0); + ASSERT_NE(optionals[dir2.path()][0].shared, 0); + ASSERT_NE(optionals[dir2.path()][0].master, 0); +} + +TEST(MountTest, PrivateMasterUnslaves) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); + auto const base = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const base_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount("", base.path().c_str(), "tmpfs", 0, "", MNT_DETACH)); + ASSERT_THAT(mount("", base.path().c_str(), "", MS_PRIVATE, 0), + SyscallSucceeds()); + + auto const dir1 = + ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(base.path())); + auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE(Mount( + dir1.path().c_str(), dir1.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), + SyscallSucceeds()); + auto const dir2 = + ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(base.path())); + auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount( + dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + ASSERT_THAT(mount(0, dir2.path().c_str(), 0, MS_SLAVE, 0), SyscallSucceeds()); + ASSERT_THAT(mount(0, dir2.path().c_str(), 0, MS_SHARED, 0), + SyscallSucceeds()); + + ASSERT_THAT(mount(0, dir1.path().c_str(), 0, MS_PRIVATE, 0), + SyscallSucceeds()); + + auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); + ASSERT_EQ(optionals[dir1.path()][0].shared, 0); + ASSERT_EQ(optionals[dir2.path()][0].master, 0); + ASSERT_NE(optionals[dir2.path()][0].shared, 0); +} + +TEST(MountTest, SlaveMaster) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); + + auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE(Mount( + dir1.path().c_str(), dir1.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0), + SyscallSucceeds()); + auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount( + dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + ASSERT_THAT(mount(0, dir2.path().c_str(), 0, MS_SLAVE, 0), SyscallSucceeds()); + ASSERT_THAT(mount(0, dir2.path().c_str(), 0, MS_SHARED, 0), + SyscallSucceeds()); + auto const dir3 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const mnt3 = ASSERT_NO_ERRNO_AND_VALUE(Mount( + dir2.path().c_str(), dir3.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + ASSERT_THAT(mount(0, dir3.path().c_str(), 0, MS_SLAVE, 0), SyscallSucceeds()); + + ASSERT_THAT(mount(0, dir2.path().c_str(), 0, MS_PRIVATE, 0), + SyscallSucceeds()); + + auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); + ASSERT_NE(optionals[dir1.path()][0].shared, 0); + ASSERT_EQ(optionals[dir2.path()][0].shared, 0); + ASSERT_EQ(optionals[dir2.path()][0].master, 0); + ASSERT_EQ(optionals[dir3.path()][0].master, optionals[dir1.path()][0].shared); +} + +TEST(MountTest, BindSharedToSlave) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); + + auto const src = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const src_mnt = ASSERT_NO_ERRNO_AND_VALUE(Mount( + src.path().c_str(), src.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + ASSERT_THAT(mount("", src.path().c_str(), "", MS_SHARED, 0), + SyscallSucceeds()); + auto const dst_master = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const dst_master_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(dst_master.path().c_str(), dst_master.path().c_str(), "", MS_BIND, + "", MNT_DETACH)); + ASSERT_THAT(mount("", dst_master.path().c_str(), "", MS_SHARED, 0), + SyscallSucceeds()); + auto const dst = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const dst_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(dst_master.path().c_str(), dst.path().c_str(), "", MS_BIND, "", + MNT_DETACH)); + ASSERT_THAT(mount("", dst.path().c_str(), "", MS_SLAVE, 0), + SyscallSucceeds()); + + auto const dst_mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount( + src.path().c_str(), dst.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); + + ASSERT_EQ(optionals[src.path()][0].shared, optionals[dst.path()][1].shared); + ASSERT_EQ(optionals[dst.path()][0].master, + optionals[dst_master.path()][0].shared); +} + +TEST(MountTest, BindSlaveToShared) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); + + auto const src = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const src_mnt = ASSERT_NO_ERRNO_AND_VALUE(Mount( + src.path().c_str(), src.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + ASSERT_THAT(mount("", src.path().c_str(), "", MS_SHARED, 0), + SyscallSucceeds()); + auto const src_master = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const src_master_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(src.path().c_str(), src_master.path().c_str(), "", MS_BIND, "", + MNT_DETACH)); + ASSERT_THAT(mount("", src.path().c_str(), "", MS_SLAVE, 0), + SyscallSucceeds()); + + auto const dst = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const dst_mnt = ASSERT_NO_ERRNO_AND_VALUE(Mount( + dst.path().c_str(), dst.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + ASSERT_THAT(mount("", dst.path().c_str(), "", MS_SHARED, 0), + SyscallSucceeds()); + auto const dst_peer = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const dst_peer_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(dst.path().c_str(), dst_peer.path().c_str(), "", MS_BIND, "", + MNT_DETACH)); + + auto const dst_mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount( + src.path().c_str(), dst.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + + auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); + ASSERT_EQ(optionals[dst.path()][1].shared, + optionals[dst_peer.path()][1].shared); + ASSERT_EQ(optionals[dst.path()][1].master, + optionals[dst_peer.path()][1].master); + ASSERT_EQ(optionals[dst.path()][1].master, + optionals[src_master.path()][0].shared); +} + +TEST(MountTest, BindSlaveToSlave) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); + + auto const src = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const src_mnt = ASSERT_NO_ERRNO_AND_VALUE(Mount( + src.path().c_str(), src.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + ASSERT_THAT(mount("", src.path().c_str(), "", MS_SHARED, 0), + SyscallSucceeds()); + auto const src_master = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const src_master_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(src.path().c_str(), src_master.path().c_str(), "", MS_BIND, "", + MNT_DETACH)); + ASSERT_THAT(mount("", src.path().c_str(), "", MS_SLAVE, 0), + SyscallSucceeds()); + + auto const dst = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const dst_mnt = ASSERT_NO_ERRNO_AND_VALUE(Mount( + dst.path().c_str(), dst.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + ASSERT_THAT(mount("", dst.path().c_str(), "", MS_SHARED, 0), + SyscallSucceeds()); + auto const dst_master = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const dst_master_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(dst.path().c_str(), dst_master.path().c_str(), "", MS_BIND, "", + MNT_DETACH)); + ASSERT_THAT(mount("", dst.path().c_str(), "", MS_SLAVE, 0), + SyscallSucceeds()); + + auto const dst_mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount( + src.path().c_str(), dst.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + + auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); + ASSERT_EQ(optionals[dst.path()][1].master, optionals[src.path()][0].master); +} + +// Test that mounting on a slave mount does not propagate to the master. +TEST(MountTest, SlavePropagationEvent) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); + + auto const dst_master = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const dst_master_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(dst_master.path().c_str(), dst_master.path().c_str(), "", MS_BIND, + "", MNT_DETACH)); + ASSERT_THAT(mount("", dst_master.path().c_str(), "", MS_SHARED, 0), + SyscallSucceeds()); + auto const dst = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const dst_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(dst_master.path().c_str(), dst.path().c_str(), "", MS_BIND, "", + MNT_DETACH)); + ASSERT_THAT(mount("", dst.path().c_str(), "", MS_SLAVE, 0), + SyscallSucceeds()); + auto const child = + ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dst_master.path())); + + const std::string master_child_path = + JoinPath(dst_master.path(), Basename(child.path())); + const std::string slave_child_path = + JoinPath(dst.path(), Basename(child.path())); + auto const slave_child_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount("", slave_child_path.c_str(), "tmpfs", 0, "", MNT_DETACH)); + + auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); + ASSERT_EQ(optionals[child.path()].size(), 0); +} + +// We are building a propagation tree that looks like this: +/* + A <--> B <--> C <---> D + /|\ /| |\ + / F G J K H I + / +E<-->O + /|\ + M L N +*/ +// Propagating mount events across this tree should cover most propagation +// cases. +TEST(MountTest, LargeTreePropagationEvent) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); + + // 15 total mounts that should all get propagated to if we mount on A. + auto const a = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const b = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const c = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const d = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const e = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const g = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const h = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const i = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const j = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const k = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const l = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const m = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const n = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const o = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + + auto const a_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(a.path().c_str(), a.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + ASSERT_THAT(mount("", a.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); + + // Place E, F, and G in A's peer group, then make them slaves. + auto const e_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(a.path().c_str(), e.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + auto const f_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(a.path().c_str(), f.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + auto const g_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(a.path().c_str(), g.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + ASSERT_THAT(mount("", e.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); + ASSERT_THAT(mount("", f.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); + ASSERT_THAT(mount("", g.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); + + // Add B to A's shared group. + auto const b_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(a.path().c_str(), b.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + + // Add C to A's shared group and place J and K in C's peer group, then make + // them slaves. + auto const c_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(a.path().c_str(), c.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + auto const j_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(c.path().c_str(), j.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + auto const k_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(c.path().c_str(), k.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + ASSERT_THAT(mount("", j.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); + ASSERT_THAT(mount("", k.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); + + // Add D to A's shared group and place H and I in Ds peer group, then make + // them slaves. + auto const d_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(a.path().c_str(), d.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + auto const h_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(d.path().c_str(), h.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + auto const i_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(d.path().c_str(), i.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + ASSERT_THAT(mount("", h.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); + ASSERT_THAT(mount("", i.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); + + // Make E shared and create a group with O. + ASSERT_THAT(mount("", e.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); + auto const o_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(e.path().c_str(), o.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + + // Add M, L and N to K's shared group and make them slaves of O. + auto const m_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(o.path().c_str(), m.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + auto const l_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(o.path().c_str(), l.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + auto const n_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(o.path().c_str(), n.path().c_str(), "", MS_BIND, "", MNT_DETACH)); + ASSERT_THAT(mount("", m.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); + ASSERT_THAT(mount("", l.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); + ASSERT_THAT(mount("", n.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); + + std::vector mounts = + ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); + + ASSERT_THAT(mount("", a.path().c_str(), "tmpfs", 0, ""), SyscallSucceeds()); + + std::vector mounts_after_mount = + ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); + ASSERT_EQ(mounts_after_mount.size(), mounts.size() + 15); + + auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals()); + + // A, B, C, and D are all mounted over and in a peer group. + ASSERT_NE(optionals[a.path()][1].shared, 0); + ASSERT_EQ(optionals[a.path()][1].shared, optionals[b.path()][1].shared); + ASSERT_EQ(optionals[a.path()][1].shared, optionals[c.path()][1].shared); + ASSERT_EQ(optionals[a.path()][1].shared, optionals[d.path()][1].shared); + + // E, F, G, H, I, J, K are all mounted over and slave to A's peer group. + ASSERT_EQ(optionals[a.path()][1].shared, optionals[e.path()][1].master); + ASSERT_EQ(optionals[a.path()][1].shared, optionals[f.path()][1].master); + ASSERT_EQ(optionals[a.path()][1].shared, optionals[g.path()][1].master); + ASSERT_EQ(optionals[a.path()][1].shared, optionals[h.path()][1].master); + ASSERT_EQ(optionals[a.path()][1].shared, optionals[i.path()][1].master); + ASSERT_EQ(optionals[a.path()][1].shared, optionals[j.path()][1].master); + ASSERT_EQ(optionals[a.path()][1].shared, optionals[k.path()][1].master); + + // E and O are all mounted over and in a peer group. + ASSERT_EQ(optionals[e.path()][1].shared, optionals[o.path()][1].shared); + + // L, M, and N are all mounted over and slaves to E's peer group. + ASSERT_EQ(optionals[e.path()][1].shared, optionals[l.path()][1].master); + ASSERT_EQ(optionals[e.path()][1].shared, optionals[m.path()][1].master); + ASSERT_EQ(optionals[e.path()][1].shared, optionals[n.path()][1].master); + + ASSERT_THAT(umount2(a.path().c_str(), MNT_DETACH), SyscallSucceeds()); + std::vector mounts_after_umount = + ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); + ASSERT_EQ(mounts_after_umount.size(), mounts.size()); +} + +TEST(MountTest, MaxMountsWithSlave) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); + + auto const parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto const parent_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount("test", parent.path(), "tmpfs", 0, "mode=0123", MNT_DETACH)); + auto const a = + ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path())); + auto const b = + ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path())); + auto const c = + ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path())); + + auto const a_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount("test", a.path(), "tmpfs", 0, "mode=0123", MNT_DETACH)); + ASSERT_THAT(mount("", a.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); + + auto const b_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(a.path(), b.path(), "", MS_BIND, "", MNT_DETACH)); + ASSERT_THAT(mount("", b.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); + ASSERT_THAT(mount("", b.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds()); + + auto const c_mnt = ASSERT_NO_ERRNO_AND_VALUE( + Mount(b.path(), c.path(), "", MS_BIND, "", MNT_DETACH)); + ASSERT_THAT(mount("", c.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds()); + + auto const d = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(a.path())); + auto const e = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(a.path())); + + int mount_max = 10000; + bool mount_max_exists = + ASSERT_NO_ERRNO_AND_VALUE(Exists("/proc/sys/fs/mount-max")); + if (mount_max_exists) { + std::string mount_max_string; + ASSERT_NO_ERRNO(GetContents("/proc/sys/fs/mount-max", &mount_max_string)); + ASSERT_TRUE(absl::SimpleAtoi(mount_max_string, &mount_max)); + } + + // Each bind mount doubles the number of mounts in the propagation tree + // starting with 3. The number of binds we can do before failing is + // log2((max_mounts-num_current_mounts)/3). + std::vector mounts = + ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries()); + int num_binds = static_cast(std::log2((mount_max - mounts.size()) / 3)); + + for (int i = 0; i < num_binds; i++) { + ASSERT_THAT( + mount(d.path().c_str(), d.path().c_str(), nullptr, MS_BIND, nullptr), + SyscallSucceeds()); + ASSERT_THAT(mount("", d.path().c_str(), "", MS_SHARED, 0), + SyscallSucceeds()); + } + for (int i = 0; i < 2; i++) { + EXPECT_THAT( + mount(d.path().c_str(), d.path().c_str(), nullptr, MS_BIND, nullptr), + SyscallFailsWithErrno(ENOSPC)); + } +} + TEST(MountTest, MountNamespace) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); @@ -1514,6 +1937,80 @@ TEST(MountTest, MountNamespacePropagation) { EXPECT_THAT(umount2(child_dir.c_str(), MNT_DETACH), SyscallSucceeds()); } +TEST(MountTest, MountNamespaceSlavesNewUserNamespace) { + 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)); + auto child_dir = JoinPath(dir.path(), "test"); + + ASSERT_THAT(mount(NULL, dir.path().c_str(), NULL, MS_SHARED, NULL), + SyscallSucceeds()); + ASSERT_THAT(mkdir(child_dir.c_str(), 0700), SyscallSucceeds()); + ASSERT_THAT(mount("child", child_dir.c_str(), "tmpfs", 0, NULL), + SyscallSucceeds()); + EXPECT_NO_ERRNO(Open(JoinPath(child_dir, "foo"), O_CREAT | O_RDWR, 0777)); + + int uid = geteuid(); + int gid = getegid(); + std::string umap_str = absl::StrFormat("0 %lu 1", uid); + std::string gmap_str = absl::StrFormat("0 %lu 1", gid); + + pid_t child = fork(); + if (child == 0) { + chdir(dir.path().c_str()); + TEST_CHECK(unshare(CLONE_NEWNS | CLONE_NEWUSER) == 0); + + // Setup uid and gid maps for child. + int fd = open("/proc/self/uid_map", O_WRONLY); + TEST_CHECK(fd > 0); + TEST_CHECK(write(fd, umap_str.c_str(), umap_str.size()) > 0); + TEST_CHECK(close(fd) == 0); + + // setgroups isn't implemented in gVisor but is necessary for native tests. + fd = open("/proc/self/setgroups", O_WRONLY); + if (fd > 0) { + TEST_CHECK(write(fd, "deny", 4) > 0); + TEST_CHECK(close(fd) == 0); + } + + fd = open("/proc/self/gid_map", O_WRONLY); + TEST_CHECK(fd > 0); + TEST_CHECK(write(fd, gmap_str.c_str(), gmap_str.size()) > 0); + TEST_CHECK(close(fd) == 0); + + // Wait until uid and gid maps are setup. + TEST_CHECK(setuid(0) == 0); + TEST_CHECK(setgid(0) == 0); + TEST_CHECK(access("test/foo", F_OK) == 0); + + // These mount operations will not propagate to the other namespace because + // it is a slave mount. + TEST_CHECK(mount("test2", "test", "tmpfs", 0, NULL) == 0); + TEST_CHECK(mknod(JoinPath("test", "boo").c_str(), 0777 | S_IFREG, 0) == 0); + + // Check that there is a master entry in mountinfo. + fd = open("/proc/self/mountinfo", O_RDONLY); + TEST_CHECK(fd > 0); + std::string mountinfo; + char child_mountinfo[0x8000]; + TEST_CHECK(read(fd, child_mountinfo, sizeof(child_mountinfo)) > 0); + EXPECT_TRUE(absl::StrContains(child_mountinfo, "master:")); + exit(0); + } + ASSERT_THAT(child, SyscallSucceeds()); + + int status; + ASSERT_THAT(waitpid(child, &status, 0), SyscallSucceedsWithValue(child)); + ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + + // Check that the test mount is still here. + EXPECT_EQ(Open(JoinPath(child_dir, "boo"), O_RDWR).error().errno_value(), + ENOENT); + EXPECT_THAT(umount2(child_dir.c_str(), MNT_DETACH), SyscallSucceeds()); +} + TEST(MountTest, MountFailsOnPseudoFilesystemMountpoint) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); auto const fd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, 0));