CVE-2023-7258 (Go, published 2024-05-15T17:15Z)
Description: value: A denial of service exists in Gvisor Sandbox where a bug in reference counting code in mount point tracking could lead to a panic, making it possible for an attacker running as root and with permission to mount volumes to kill the sandbox. We recommend upgrading past commit 6a112c60a257dadac59962e0bc9e9b5aee70b5b6
Severity: nan (CVSS v3 base score: )
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/google/gvisor
Files changed:
pkg/sentry/vfs/mount.go
pkg/sentry/vfs/propagation.go
test/syscalls/linux/BUILD
test/syscalls/linux/mount.cc
Commit message: Check for mounts marked as unmounted before propagating.

If the namespace were set to nil during umount (such as in Linux), checks
for mnt.neverConnected() would cover this case. We keep mnt.ns as non-nil,
so we need to also check umounted in some cases.

PiperOrigin-RevId: 586747189


Vulnerable code:
const MountMax = 10000
// attachTreeLocked attaches the mount tree at mnt to vd and propagates the
// mount to vd.mount's peers and followers. This method consumes the reference
// on vd. It is analogous to fs/namespace.c:attach_recursive_mnt() in Linux.
func (vfs *VirtualFilesystem) attachTreeLocked(ctx context.Context, mnt *Mount, vd VirtualDentry) error {
	mp, err := vfs.lockMountpoint(vd)
	if err != nil {
		return err
	}
		mp.DecRef(ctx)
	var propMnts map[*Mount]struct{}
	return vfs.attachTreeLocked(ctx, mnt, vd)
		vfs.delayDecRef(targetVd)
	if err := vfs.attachTreeLocked(ctx, clone, targetVd); err != nil {
	if mntns := MountNamespaceFromContext(ctx); mntns != nil {
		vfs.delayDecRef(mntns)
		if mntns != mnt.ns {
			return linuxerr.EINVAL
		}
	if mntns := MountNamespaceFromContext(ctx); mntns != nil {
		vfs.delayDecRef(mntns)
		if mntns != vd.mount.ns {
			return linuxerr.EINVAL
		}

		if vd.mount == vd.mount.ns.root {
			return linuxerr.EINVAL
		}
	return false
	ns := MountNamespaceFromContext(ctx)
	vfs.delayDecRef(ns)
	if oldRoot.mount.ns != ns || newRoot.mount.ns != ns {
	vfs.connectLocked(oldRoot.mount, putOld, ns)
	vfs.connectLocked(newRoot.mount, rootMp, ns)
	if dstMnt.neverConnected() {

Fixed code:
const (
	MountMax     = 10000
	nsfsName     = "nsfs"
	cgroupFsName = "cgroup"
)
// validInMountNS checks if the mount is valid in the current mount namespace. This includes
// checking if has previously been unmounted. It is analogous to fs/namespace.c:check_mnt() in
// Linux.
//
// +checklocks:vfs.mountMu
func (vfs *VirtualFilesystem) validInMountNS(ctx context.Context, mnt *Mount) bool {
	if mntns := MountNamespaceFromContext(ctx); mntns != nil {
		vfs.delayDecRef(mntns)
		return mnt.ns == mntns && !mnt.umounted
	}
	return false
}

// attachTreeLocked attaches the mount tree at mnt to mp and propagates the mount to mp.mount's
// peers and followers. This method consumes the reference on mp. It is analogous to
// fs/namespace.c:attach_recursive_mnt() in Linux. The mount point mp must have its dentry locked
// before calling attachTreeLocked.
func (vfs *VirtualFilesystem) attachTreeLocked(ctx context.Context, mnt *Mount, mp VirtualDentry) error {
		vfs.delayDecRef(mp)
	var (
		propMnts map[*Mount]struct{}
		err      error
	)
	mp, err := vfs.lockMountpoint(vd)
	if err != nil {
		return err
	}
	if mp.mount.neverConnected() || mp.mount.umounted {
		mp.dentry.mu.Unlock()
		vfs.delayDecRef(mp)
		return linuxerr.EINVAL
	}
	return vfs.attachTreeLocked(ctx, mnt, mp)
	mp, err := vfs.lockMountpoint(targetVd)
	if err != nil {
		return err
	}
	cleanup := cleanup.Make(func() {
		mp.dentry.mu.Unlock()
		vfs.delayDecRef(mp) // +checklocksforce
	})
	defer cleanup.Clean()
	// Namespace mounts can be binded to other mount points.
	fsName := sourceVd.mount.Filesystem().FilesystemType().Name()
	if !vfs.validInMountNS(ctx, sourceVd.mount) && fsName != nsfsName && fsName != cgroupFsName {
		return linuxerr.EINVAL
	}
	if !vfs.validInMountNS(ctx, mp.mount) {
		return linuxerr.EINVAL
	}

	cleanup.Release()

	if err := vfs.attachTreeLocked(ctx, clone, mp); err != nil {
	if !vfs.validInMountNS(ctx, mnt) {
		return linuxerr.EINVAL
	if !vfs.validInMountNS(ctx, vd.mount) {
		return linuxerr.EINVAL
	}
	if vd.mount == vd.mount.ns.root {
		return linuxerr.EINVAL
	return true
	if !vfs.validInMountNS(ctx, oldRoot.mount) || !vfs.validInMountNS(ctx, newRoot.mount) {
	vfs.connectLocked(oldRoot.mount, putOld, putOld.mount.ns)
	vfs.connectLocked(newRoot.mount, rootMp, rootMp.mount.ns)
	if dstMnt.neverConnected() || dstMnt.umounted {
        "//test/util:cleanup",
#include "absl/strings/substitute.h"
#include "test/util/cleanup.h"
TEST(MountTest, DetachedMountBindFails) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  const TempPath path1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  Cleanup mount_cleanup = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", path1.path().c_str(), kTmpfs, 0, "", MNT_DETACH));
  mount_cleanup.Release();
  const FileDescriptor fd =
      ASSERT_NO_ERRNO_AND_VALUE(Open(path1.path().c_str(), O_RDONLY));
  std::string fd_path = absl::Substitute("/proc/self/fd/$0", fd.get());
  ASSERT_THAT(umount2(path1.path().c_str(), MNT_DETACH), SyscallSucceeds());
  const TempPath path2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());

  EXPECT_THAT(mount(fd_path.c_str(), path2.path().c_str(), "", MS_BIND, 0),
              SyscallFailsWithErrno(EINVAL));
}