mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add a maximum to the total number of mounts allowed in a namespace.
The limit is the same as a the default for /proc/sys/fs/mount-max. Reported-by: syzbot+ae4591a5d362a6701e40@syzkaller.appspotmail.com PiperOrigin-RevId: 491748452
This commit is contained in:
committed by
gVisor bot
parent
bfbb9fa4cc
commit
ece02b45b5
@@ -151,6 +151,7 @@ go_library(
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/atomicbitops",
|
||||
"//pkg/bitmap",
|
||||
"//pkg/cleanup",
|
||||
"//pkg/context",
|
||||
"//pkg/errors/linuxerr",
|
||||
"//pkg/fd",
|
||||
|
||||
+38
-3
@@ -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/refsvfs2"
|
||||
@@ -46,6 +47,11 @@ const (
|
||||
Child
|
||||
// Unbindable represents the unbindable propagation type.
|
||||
Unbindable
|
||||
|
||||
// MountMax is the maximum number of mounts allowed. In Linux this can be
|
||||
// configured by the user at /proc/sys/fs/mount-max, but the default is
|
||||
// 100,000. We set the gVisor limit to 10,000.
|
||||
MountMax = 10000
|
||||
)
|
||||
|
||||
// PropagationTypeFromLinux returns the PropagationType corresponding to a
|
||||
@@ -273,6 +279,9 @@ type MountNamespace struct {
|
||||
// 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
|
||||
}
|
||||
|
||||
// NewMountNamespace returns a new mount namespace with a root filesystem
|
||||
@@ -349,11 +358,20 @@ func (vfs *VirtualFilesystem) ConnectMountAt(ctx context.Context, creds *auth.Cr
|
||||
vfs.mountMu.Lock()
|
||||
defer vfs.mountMu.Unlock()
|
||||
tree := vfs.preparePropagationTree(mnt, vd)
|
||||
|
||||
cleanup := cleanup.Make(func() {
|
||||
vfs.abortPropagationTree(ctx, tree) // +checklocksforce
|
||||
})
|
||||
defer cleanup.Clean()
|
||||
// Check if the new mount + all the propagation mounts puts us over the max.
|
||||
if uint32(len(tree)+1)+vd.mount.ns.mounts > MountMax {
|
||||
return linuxerr.ENOSPC
|
||||
}
|
||||
if err := vfs.connectMountAt(ctx, mnt, vd); err != nil {
|
||||
vfs.abortPropagationTree(ctx, tree)
|
||||
return err
|
||||
}
|
||||
vfs.commitPropagationTree(ctx, tree)
|
||||
cleanup.Release()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -572,12 +590,21 @@ func (vfs *VirtualFilesystem) BindAt(ctx context.Context, creds *auth.Credential
|
||||
vfs.mergePeerGroup(sourceVd.mount, clone)
|
||||
}
|
||||
}
|
||||
cleanup := cleanup.Make(func() {
|
||||
// Checklocks doesn't work with anon functions.
|
||||
vfs.setPropagation(clone, Private) // +checklocksforce
|
||||
vfs.abortPropagationTree(ctx, tree) // +checklocksforce
|
||||
targetVd.DecRef(ctx)
|
||||
})
|
||||
defer cleanup.Clean()
|
||||
if uint32(1+len(tree))+targetVd.mount.ns.mounts > MountMax {
|
||||
return nil, linuxerr.ENOSPC
|
||||
}
|
||||
if err := vfs.connectMountAt(ctx, clone, targetVd); err != nil {
|
||||
vfs.setPropagation(clone, Private)
|
||||
vfs.abortPropagationTree(ctx, tree)
|
||||
return nil, err
|
||||
}
|
||||
vfs.commitPropagationTree(ctx, tree)
|
||||
cleanup.Release()
|
||||
return clone, nil
|
||||
}
|
||||
|
||||
@@ -795,6 +822,7 @@ func (vfs *VirtualFilesystem) connectLocked(mnt *Mount, vd VirtualDentry, mntns
|
||||
vd.dentry.mounts.Add(1)
|
||||
mnt.ns = mntns
|
||||
mntns.mountpoints[vd.dentry]++
|
||||
mntns.mounts++
|
||||
vfs.mounts.insertSeqed(mnt)
|
||||
vfsmpmounts, ok := vfs.mountpoints[vd.dentry]
|
||||
if !ok {
|
||||
@@ -817,11 +845,18 @@ func (vfs *VirtualFilesystem) disconnectLocked(mnt *Mount) VirtualDentry {
|
||||
if vd.mount != nil {
|
||||
panic("VFS.disconnectLocked called on disconnected mount")
|
||||
}
|
||||
if mnt.ns.mountpoints[vd.dentry] == 0 {
|
||||
panic("VFS.disconnectLocked called on dentry with zero mountpoints.")
|
||||
}
|
||||
if mnt.ns.mounts == 0 {
|
||||
panic("VFS.disconnectLocked called on namespace with zero mounts.")
|
||||
}
|
||||
}
|
||||
mnt.loadKey(VirtualDentry{})
|
||||
delete(vd.mount.children, mnt)
|
||||
vd.dentry.mounts.Add(math.MaxUint32) // -1
|
||||
mnt.ns.mountpoints[vd.dentry]--
|
||||
mnt.ns.mounts--
|
||||
if mnt.ns.mountpoints[vd.dentry] == 0 {
|
||||
delete(mnt.ns.mountpoints, vd.dentry)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/strings/match.h"
|
||||
#include "absl/strings/numbers.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_split.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
@@ -822,6 +823,46 @@ TEST(MountTest, BindToSelf) {
|
||||
ASSERT_TRUE(found);
|
||||
}
|
||||
|
||||
TEST(MountTest, MaxMounts) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
||||
|
||||
auto const parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
ASSERT_THAT(mount("", parent.path().c_str(), "tmpfs", 0, ""),
|
||||
SyscallSucceeds());
|
||||
auto const dir =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path()));
|
||||
ASSERT_THAT(
|
||||
mount(dir.path().c_str(), dir.path().c_str(), nullptr, MS_BIND, nullptr),
|
||||
SyscallSucceeds());
|
||||
ASSERT_THAT(mount("", dir.path().c_str(), "", MS_SHARED, ""),
|
||||
SyscallSucceeds());
|
||||
|
||||
// Each bind mount doubles the number of mounts in the peer group. The number
|
||||
// of binds we can do before failing is log2(max_mounts-num_current_mounts).
|
||||
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));
|
||||
}
|
||||
|
||||
const std::vector<ProcMountInfoEntry> mounts =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries());
|
||||
int num_binds = static_cast<int>(std::log2(mount_max - mounts.size()));
|
||||
|
||||
for (int i = 0; i < num_binds; i++) {
|
||||
ASSERT_THAT(mount(dir.path().c_str(), dir.path().c_str(), nullptr, MS_BIND,
|
||||
nullptr),
|
||||
SyscallSucceeds());
|
||||
}
|
||||
ASSERT_THAT(
|
||||
mount(dir.path().c_str(), dir.path().c_str(), nullptr, MS_BIND, nullptr),
|
||||
SyscallFailsWithErrno(ENOSPC));
|
||||
umount2(parent.path().c_str(), MNT_DETACH);
|
||||
}
|
||||
|
||||
// Tests that it is possible to make a shared mount.
|
||||
TEST(MountTest, MakeShared) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
||||
|
||||
Reference in New Issue
Block a user