Repository: google/gvisor
File: pkg/sentry/vfs/mount.go (Go)

Code before:
		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
	}

	var clone *Mount
	if recursive {
		clone, err = vfs.cloneMountTree(ctx, sourceVd.mount, sourceVd.dentry, 0, nil)
	} else {
		if vfs.mountHasLockedChildren(sourceVd.mount, sourceVd) {
			return linuxerr.EINVAL
		}
		clone, err = vfs.cloneMount(sourceVd.mount, sourceVd.dentry, nil, 0)
	}
	if err != nil {
		return err
	}
	cleanup.Release()

	vfs.delayDecRef(clone)
	clone.locked = false
	if err := vfs.attachTreeLocked(ctx, clone, mp); err != nil {
		vfs.abortUncomittedChildren(ctx, clone)
		return err
	}
	return nil
}

// RemountAt changes the mountflags and data of an existing mount without having to unmount and remount the filesystem.
func (vfs *VirtualFilesystem) RemountAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *MountOptions) error {
	vd, err := vfs.getMountpoint(ctx, creds, pop)
	if err != nil {
		return err
	}
	defer vd.DecRef(ctx)
	vfs.lockMounts()
	defer vfs.unlockMounts(ctx)
	mnt := vd.Mount()
	if !vfs.validInMountNS(ctx, mnt) {
		return linuxerr.EINVAL
	}
	return mnt.setMountOptions(opts)
}

Review comment (bug):
Ah there is a subtle bug here. The file type bits in stat.mode is **not** a mask. `S_IFDIR = 040000` and `S_IFSOCK = 0140000`. So if one is a socket and the other is a directory, this code will allow it. Because `S_IFSOCK & S_IFDIR == S_IFDIR`. This entails that the example I linked earlier from pkg/sentry/vfs/opath.go also contains this bug.

`sourceStat` is of type `linux.FileMode`, which supports a `IsDir()` function. We should use that here.

Code after:
		mp.dentry.mu.Unlock()
		vfs.delayDecRef(mp) // +checklocksforce
	})
	defer cleanup.Clean()

	// Linux's graft_tree() (fs/namespace.c) returns ENOTDIR if the source and
	// target have mismatched types (one is a directory, the other is not).
	sourceStat, err := vfs.StatAt(ctx, creds, &PathOperation{
		Root:  sourceVd,
		Start: sourceVd,
	}, &StatOptions{
		Mask: linux.STATX_MODE,
	})
	if err != nil {
		return err
	}
	targetStat, err := vfs.StatAt(ctx, creds, &PathOperation{
		Root:  mp,
		Start: mp,
	}, &StatOptions{
		Mask: linux.STATX_MODE,
	})
	if err != nil {
		return err
	}
	if sourceStat.Mode&linux.S_IFDIR != targetStat.Mode&linux.S_IFDIR {
		return linuxerr.ENOTDIR
	}

	// 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
	}

	var clone *Mount
	if recursive {
		clone, err = vfs.cloneMountTree(ctx, sourceVd.mount, sourceVd.dentry, 0, nil)
	} else {
		if vfs.mountHasLockedChildren(sourceVd.mount, sourceVd) {
			return linuxerr.EINVAL
		}
		clone, err = vfs.cloneMount(sourceVd.mount, sourceVd.dentry, nil, 0)
	}
	if err != nil {
		return err
	}
	cleanup.Release()