Files
Konstantin BogomolovandgVisor bot 58e263886b Implement /proc/sys/fs/mount-max.
It's just a matter of exposing the option via sysfs. Capped to same max as
Linux, to follow convention of what we're already doing with nr_open.

PiperOrigin-RevId: 972901017
2026-08-28 19:02:13 -07:00

410 lines
12 KiB
Go

// Copyright 2023 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 (
"gvisor.dev/gvisor/pkg/cleanup"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/refs"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
)
// A MountNamespace is a collection of Mounts.//
// MountNamespaces are reference-counted. Unless otherwise specified, all
// MountNamespace methods require that a reference is held.
//
// MountNamespace is analogous to Linux's struct mnt_namespace.
//
// +stateify savable
type MountNamespace struct {
// ID is the immutable mount namespace ID.
ID uint64
// Refs is the reference count for this mount namespace.
Refs refs.TryRefCounter
// Owner is the usernamespace that owns this mount namespace.
Owner *auth.UserNamespace
// vfs is the vfs this namespace belongs to.
// vfs is immutable.
vfs *VirtualFilesystem
// root is the MountNamespace's root mount.
root *Mount
// mountpoints maps all Dentries which are mount points in this namespace
// to the number of Mounts for which they are mount points. mountpoints is
// protected by VirtualFilesystem.mountMu.
//
// mountpoints is used to determine if a Dentry can be moved or removed
// (which requires that the Dentry is not a mount point in the calling
// namespace).
//
// mountpoints is maintained even if there are no references held on the
// MountNamespace; this is required to ensure that
// VFS.PrepareDeleteDentry() and VFS.PrepareRemoveDentry() operate
// correctly on unreferenced MountNamespaces.
mountpoints map[*Dentry]uint32
// mounts is the total number of mounts in this mount namespace.
mounts uint32
// pending is the total number of pending mounts in this mount namespace.
pending uint32
// anon indicates whether the mount namespace is anonymous.
anon bool
// For anonymous mount namespaces, originatorID is the ID of the mount
// namespace where the tree originated from. Used for permission checks.
// 0 is a special value that indicates "no permission checks required."
// All non-anonymous mount namespaces will have originatorID == 0. Some
// anonymous mount namespaces may have originatorID == 0 (such as "fresh"
// trees created using fsmount(2)).
originatorID uint64
// Poller is notified on every mount namespace change (mount, unmount,
// remount, move). Used by /proc/[pid]/mounts and /proc/[pid]/mountinfo
// to support poll().
Poller DynamicBytesPoller
}
// Namespace is the namespace interface.
type Namespace interface {
Type() string
Destroy(ctx context.Context)
UserNamespace() *auth.UserNamespace
}
// newMountNamespace initializes a new mount namespace.
// This method is not intended to be used directly; instead, use one of the
// NewMountNamespace*() methods, which will set up the ns root as well.
func (vfs *VirtualFilesystem) newMountNamespace(owner *auth.UserNamespace, anon bool) *MountNamespace {
return &MountNamespace{
ID: vfs.lastMountNamespaceID.Add(1),
vfs: vfs,
Owner: owner,
mountpoints: make(map[*Dentry]uint32),
anon: anon,
}
}
// NewMountNamespace returns a new mount namespace with a root filesystem
// configured by the given arguments. A reference is taken on the returned
// MountNamespace.
//
// If nsfs is nil, the default reference counter is used.
func (vfs *VirtualFilesystem) NewMountNamespace(
ctx context.Context,
creds *auth.Credentials,
source, fsTypeName string,
opts *MountOptions,
nsfs NamespaceInodeGetter,
) (*MountNamespace, error) {
rft := vfs.getFilesystemType(fsTypeName)
if rft == nil {
ctx.Warningf("Unknown filesystem type: %s", fsTypeName)
return nil, linuxerr.ENODEV
}
fs, root, err := rft.fsType.GetFilesystem(ctx, vfs, creds, source, opts.GetFilesystemOptions)
if err != nil {
return nil, err
}
return vfs.NewMountNamespaceFrom(ctx, creds, fs, root, opts, nsfs, false /* anon */), nil
}
type namespaceDefaultRefs struct {
namespaceRefs
destroy func(ctx context.Context)
}
func (r *namespaceDefaultRefs) DecRef(ctx context.Context) {
r.namespaceRefs.DecRef(
func() {
r.destroy(ctx)
},
)
}
// NewMountNamespaceFrom constructs a new mount namespace from an existing
// filesystem and its root dentry. This is similar to NewMountNamespace, but
// uses an existing filesystem instead of constructing a new one.
func (vfs *VirtualFilesystem) NewMountNamespaceFrom(
ctx context.Context,
creds *auth.Credentials,
fs *Filesystem,
root *Dentry,
opts *MountOptions,
nsfs NamespaceInodeGetter,
anon bool,
) *MountNamespace {
mntns := vfs.newMountNamespace(creds.UserNamespace, anon)
if nsfs == nil {
refs := &namespaceDefaultRefs{destroy: mntns.Destroy}
refs.InitRefs()
mntns.Refs = refs
} else {
mntns.Refs = nsfs.GetNamespaceInode(ctx, mntns)
}
mntns.root = newMount(vfs, fs, root, mntns, opts)
return mntns
}
type cloneEntry struct {
prevMount *Mount
parentMount *Mount
}
// +checklocks:vfs.mountMu
func (vfs *VirtualFilesystem) updateRootAndCWD(ctx context.Context, root *VirtualDentry, cwd *VirtualDentry, src *Mount, dst *Mount) {
if root.mount == src {
vfs.delayDecRef(root.mount)
root.mount = dst
root.mount.IncRef()
}
if cwd.mount == src {
vfs.delayDecRef(cwd.mount)
cwd.mount = dst
cwd.mount.IncRef()
}
}
// NamespaceInodeGetter is an interface that provides the GetNamespaceInode method.
type NamespaceInodeGetter interface {
GetNamespaceInode(ctx context.Context, ns Namespace) refs.TryRefCounter
}
// CloneMountNamespace makes a copy of the specified mount namespace.
//
// If `root` or `cwd` have mounts in the old namespace, they will be replaced
// with proper mounts from the new namespace.
func (vfs *VirtualFilesystem) CloneMountNamespace(
ctx context.Context,
uns *auth.UserNamespace,
ns *MountNamespace,
root *VirtualDentry,
cwd *VirtualDentry,
nsfs NamespaceInodeGetter,
) (*MountNamespace, error) {
newns := vfs.newMountNamespace(uns, false)
newns.Refs = nsfs.GetNamespaceInode(ctx, newns)
vfs.lockMounts()
defer vfs.unlockMounts(ctx)
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)
return nil, err
}
newns.root = newRoot
newns.root.ns = newns
vfs.commitChildren(ctx, newRoot)
if ns.Owner != newns.Owner {
vfs.lockMountTree(newRoot)
}
return newns, nil
}
// CloneTreeToAnonNS implements open_tree(2)'s OPEN_TREE_CLONE. It makes a copy of the existing
// mount tree at fromVd, placing it at the root of a new anonymous mount namespace.
func (vfs *VirtualFilesystem) CloneTreeToAnonNS(
ctx context.Context,
taskMountNs *MountNamespace,
fromVd VirtualDentry,
nsfs NamespaceInodeGetter,
recursive bool,
) (*MountNamespace, error) {
newNs := vfs.newMountNamespace(taskMountNs.Owner, true)
newNs.Refs = nsfs.GetNamespaceInode(ctx, newNs)
newNsCleanup := cleanup.Make(func() {
newNs.DecRef(ctx)
})
defer newNsCleanup.Clean()
fromMnt := fromVd.mount
vfs.lockMounts()
defer vfs.unlockMounts(ctx)
// Keep track of the originator of this anon ns for later permission checking.
if fromMnt.ns != nil {
if fromMnt.ns.anon {
newNs.originatorID = fromMnt.ns.originatorID
} else {
newNs.originatorID = fromMnt.ns.ID
}
}
// Sanity checks
// TODO(b/305893463): When MS_UNBINDABLE is added,
// MS_UNBINDABLE mounts should be rejected here.
fsName := fromMnt.Filesystem().FilesystemType().Name()
// fromMnt must not have been detached from its mount tree (e.g. via
// umount(MNT_DETACH)). Note that fromMnt.ns is not cleared on detach,
// only fromMnt.umounted is set, so this must be checked independently
// of the namespace-membership check below.
if fromMnt.umounted {
return nil, linuxerr.EINVAL
}
// fromMnt must be either:
// - In the same mount ns as the current task
// - In an appropriate anonymous mount namespace
// nsfs mounts are exempted from these requirements.
// TODO(b/513023394): when pidfd-fs is implemented, it will also be exempted.
if fromMnt.ns != taskMountNs && (fromMnt.ns == nil || !fromMnt.ns.anonCanBeOperatedOn(taskMountNs)) && fsName != nsfsName {
return nil, linuxerr.EINVAL
}
if !recursive && vfs.mountHasLockedChildren(fromMnt, fromVd) {
return nil, linuxerr.EINVAL
}
var newRoot *Mount
var err error
if recursive {
newRoot, err = vfs.cloneMountTree(ctx, fromMnt, fromVd.dentry, 0, nil)
} else {
newRoot, err = vfs.cloneMount(fromMnt, fromVd.dentry, nil, 0)
}
if err != nil {
return nil, err
}
newNs.root = newRoot
newNs.root.ns = newNs
vfs.commitChildren(ctx, newRoot)
newNsCleanup.Release()
return newNs, nil
}
// Destroy implements nsfs.Namespace.Destroy.
func (mntns *MountNamespace) Destroy(ctx context.Context) {
vfs := mntns.vfs
vfs.lockMounts()
if mntns.root != nil {
vfs.umountTreeLocked(mntns.root, &umountRecursiveOptions{
disconnectHierarchy: true,
})
}
vfs.unlockMounts(ctx)
}
// Type implements nsfs.Namespace.Type.
func (mntns *MountNamespace) Type() string {
return "mnt"
}
// UserNamespace implements nsfs.Namespace.UserNamespace.
func (mntns *MountNamespace) UserNamespace() *auth.UserNamespace {
return mntns.Owner
}
// IncRef increments mntns' refcount.
func (mntns *MountNamespace) IncRef() {
mntns.Refs.IncRef()
}
// DecRef decrements mntns' reference count.
func (mntns *MountNamespace) DecRef(ctx context.Context) {
mntns.Refs.DecRef(ctx)
}
// TryIncRef attempts to increment mntns' reference count.
func (mntns *MountNamespace) TryIncRef() bool {
return mntns.Refs.TryIncRef()
}
// Anon returns whether the namespace is anonymous or not.
func (mntns *MountNamespace) Anon() bool {
return mntns.anon
}
// Root returns mntns' root. If the root is over-mounted, it returns the top
// mount.
// May return an empty virtual dentry if mntns is an anonymous mount namespace and its root
// has been moved to another mountpoint.
func (mntns *MountNamespace) Root(ctx context.Context) VirtualDentry {
if mntns.root == nil {
return VirtualDentry{}
}
vfs := mntns.root.fs.VirtualFilesystem()
vd := VirtualDentry{
mount: mntns.root,
dentry: mntns.root.root,
}
vd.IncRef()
if !vd.dentry.isMounted() {
return vd
}
m := vfs.getMountAt(ctx, vd.mount, vd.dentry)
if m == nil {
return vd
}
vd.DecRef(ctx)
vd.mount = m
vd.dentry = m.root
vd.dentry.IncRef()
return vd
}
func (mntns *MountNamespace) checkMountCount(ctx context.Context, mnt *Mount) error {
mountMax := uint64(mnt.vfs.MountMax.Load())
cur := uint64(mntns.mounts)
if cur > mountMax {
return linuxerr.ENOSPC
}
pending := uint64(mntns.pending)
if cur+pending > mountMax {
return linuxerr.ENOSPC
}
mnts := mnt.countSubmountsLocked()
if cur+pending+uint64(mnts) > mountMax {
return linuxerr.ENOSPC
}
mntns.pending += mnts
return nil
}
// anonCanBeOperatedOn checks whether the mount namespace is both anonymous
// and accessible by the mount namespace `by`.
//
// It is analogous to fs/namespace.c:check_anonymous_mnt() in Linux.
func (mntns *MountNamespace) anonCanBeOperatedOn(by *MountNamespace) bool {
if !mntns.anon {
return false
}
return mntns.originatorID == 0 || mntns.originatorID == by.ID
}
// notify notifies poll waiters of a mount namespace change.
// Analogous to Linux's touch_mnt_namespace().
func (mntns *MountNamespace) notify() {
if mntns == nil {
return
}
mntns.Poller.Notify()
}