mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement the fs.nr_open sysctl
fs/nr_open limits the maximum size of fdtable-s. PiperOrigin-RevId: 580795874
This commit is contained in:
@@ -20,6 +20,7 @@ import (
|
||||
"math"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/atomicbitops"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/errors/linuxerr"
|
||||
"gvisor.dev/gvisor/pkg/hostarch"
|
||||
@@ -58,6 +59,9 @@ func (fs *filesystem) newSysDir(ctx context.Context, root *auth.Credentials, k *
|
||||
"ptrace_scope": fs.newYAMAPtraceScopeFile(ctx, k, root),
|
||||
}),
|
||||
}),
|
||||
"fs": fs.newStaticDir(ctx, root, map[string]kernfs.Inode{
|
||||
"nr_open": fs.newInode(ctx, root, 0644, &atomicInt32File{val: &k.MaxFDLimit, min: 8, max: kernel.MaxFdLimit}),
|
||||
}),
|
||||
"vm": fs.newStaticDir(ctx, root, map[string]kernfs.Inode{
|
||||
"max_map_count": fs.newInode(ctx, root, 0444, newStaticFile("2147483647\n")),
|
||||
"mmap_min_addr": fs.newInode(ctx, root, 0444, &mmapMinAddrData{k: k}),
|
||||
@@ -486,3 +490,49 @@ func (pr *portRange) Write(ctx context.Context, _ *vfs.FileDescription, src user
|
||||
*pr.end = uint16(ports[1])
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// atomicInt32File implements vfs.WritableDynamicBytesSource sysctls
|
||||
// represented by int32 atomic objects.
|
||||
//
|
||||
// +stateify savable
|
||||
type atomicInt32File struct {
|
||||
kernfs.DynamicBytesFile
|
||||
|
||||
val *atomicbitops.Int32
|
||||
min, max int32
|
||||
}
|
||||
|
||||
var _ vfs.WritableDynamicBytesSource = (*atomicInt32File)(nil)
|
||||
|
||||
// Generate implements vfs.DynamicBytesSource.Generate.
|
||||
func (f *atomicInt32File) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
_, err := fmt.Fprintf(buf, "%d\n", f.val.Load())
|
||||
return err
|
||||
}
|
||||
|
||||
// Write implements vfs.WritableDynamicBytesSource.Write.
|
||||
func (f *atomicInt32File) Write(ctx context.Context, _ *vfs.FileDescription, src usermem.IOSequence, offset int64) (int64, error) {
|
||||
if offset != 0 {
|
||||
// Ignore partial writes.
|
||||
return 0, linuxerr.EINVAL
|
||||
}
|
||||
if src.NumBytes() == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Limit the amount of memory allocated.
|
||||
src = src.TakeFirst(hostarch.PageSize - 1)
|
||||
|
||||
var v int32
|
||||
n, err := usermem.CopyInt32StringInVec(ctx, src.IO, src.Addrs, &v, src.Opts)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if v < f.min || v > f.max {
|
||||
return 0, linuxerr.EINVAL
|
||||
}
|
||||
|
||||
f.val.Store(v)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
@@ -231,18 +231,18 @@ func (f *FDTable) NewFDs(ctx context.Context, minFD int32, files []*vfs.FileDesc
|
||||
}
|
||||
|
||||
// Default limit.
|
||||
end := MaxFdLimit
|
||||
end := f.k.MaxFDLimit.Load()
|
||||
|
||||
// Ensure we don't get past the provided limit.
|
||||
if limitSet := limits.FromContext(ctx); limitSet != nil {
|
||||
lim := limitSet.Get(limits.NumberOfFiles)
|
||||
// Only set if the limit is smaller than the max to avoid overflow.
|
||||
if lim.Cur != limits.Infinity && lim.Cur < uint64(MaxFdLimit) {
|
||||
if lim.Cur != limits.Infinity && lim.Cur < uint64(end) {
|
||||
end = int32(lim.Cur)
|
||||
}
|
||||
if minFD+int32(len(files)) > end {
|
||||
return nil, unix.EMFILE
|
||||
}
|
||||
}
|
||||
if minFD+int32(len(files)) > end {
|
||||
return nil, unix.EMFILE
|
||||
}
|
||||
|
||||
f.mu.Lock()
|
||||
@@ -329,6 +329,9 @@ func (f *FDTable) newFDAt(ctx context.Context, fd int32, file *vfs.FileDescripti
|
||||
return nil, unix.EBADF
|
||||
}
|
||||
|
||||
if fd >= f.k.MaxFDLimit.Load() {
|
||||
return nil, unix.EMFILE
|
||||
}
|
||||
// Check the limit for the provided file.
|
||||
if limitSet := limits.FromContext(ctx); limitSet != nil {
|
||||
if lim := limitSet.Get(limits.NumberOfFiles); lim.Cur != limits.Infinity && uint64(fd) >= lim.Cur {
|
||||
|
||||
@@ -69,6 +69,8 @@ func runTest(t testing.TB, fn func(ctx context.Context, fdTable *FDTable, fd *vf
|
||||
|
||||
// Create the table.
|
||||
fdTable := new(FDTable)
|
||||
fdTable.k = &Kernel{}
|
||||
fdTable.k.MaxFDLimit.Store(MaxFdLimit)
|
||||
fdTable.init()
|
||||
|
||||
// Run the test.
|
||||
|
||||
@@ -323,6 +323,10 @@ type Kernel struct {
|
||||
// userCountersMap maps auth.KUID into a set of user counters.
|
||||
userCountersMap map[auth.KUID]*UserCounters
|
||||
userCountersMapMu userCountersMutex `state:"nosave"`
|
||||
|
||||
// MaxFDLimit specifies the maximum file descriptor number that can be
|
||||
// used by processes.
|
||||
MaxFDLimit atomicbitops.Int32
|
||||
}
|
||||
|
||||
// InitKernelArgs holds arguments to Init.
|
||||
@@ -367,6 +371,11 @@ type InitKernelArgs struct {
|
||||
|
||||
// PIDNamespace is the root PID namespace.
|
||||
PIDNamespace *PIDNamespace
|
||||
|
||||
// MaxFDLimit specifies the maximum file descriptor number that can be
|
||||
// used by processes. If it is zero, the limit will be set to
|
||||
// unlimited.
|
||||
MaxFDLimit int32
|
||||
}
|
||||
|
||||
// Init initialize the Kernel with no tasks.
|
||||
@@ -420,6 +429,10 @@ func (k *Kernel) Init(args InitKernelArgs) error {
|
||||
k.ptraceExceptions = make(map[*Task]*Task)
|
||||
k.YAMAPtraceScope = atomicbitops.FromInt32(linux.YAMA_SCOPE_RELATIONAL)
|
||||
k.userCountersMap = make(map[auth.KUID]*UserCounters)
|
||||
if args.MaxFDLimit == 0 {
|
||||
args.MaxFDLimit = MaxFdLimit
|
||||
}
|
||||
k.MaxFDLimit.Store(args.MaxFDLimit)
|
||||
|
||||
ctx := k.SupervisorContext()
|
||||
if err := k.vfs.Init(ctx); err != nil {
|
||||
|
||||
@@ -565,6 +565,9 @@ func dup3(t *kernel.Task, oldfd, newfd int32, flags uint32) (uintptr, *kernel.Sy
|
||||
err := t.NewFDAt(newfd, file, kernel.FDFlags{
|
||||
CloseOnExec: flags&linux.O_CLOEXEC != 0,
|
||||
})
|
||||
if linuxerr.Equals(linuxerr.EMFILE, err) {
|
||||
err = linuxerr.EBADF
|
||||
}
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
@@ -108,6 +108,13 @@ func prlimit64(t *kernel.Task, resource limits.LimitType, newLim *limits.Limit)
|
||||
return limits.Limit{}, linuxerr.EPERM
|
||||
}
|
||||
|
||||
switch resource {
|
||||
case limits.NumberOfFiles:
|
||||
if newLim.Max > uint64(t.Kernel().MaxFDLimit.Load()) {
|
||||
return limits.Limit{}, linuxerr.EPERM
|
||||
}
|
||||
}
|
||||
|
||||
// "A privileged process (under Linux: one with the CAP_SYS_RESOURCE
|
||||
// capability in the initial user namespace) may make arbitrary changes
|
||||
// to either limit value."
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
mrand "math/rand"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
gtime "time"
|
||||
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
@@ -446,6 +447,19 @@ func New(args Args) (*Loader, error) {
|
||||
log.Infof("Setting total memory to %.2f GB", float64(args.TotalMem)/(1<<30))
|
||||
}
|
||||
|
||||
maxFDLimit := kernel.MaxFdLimit
|
||||
if args.Spec.Linux != nil && args.Spec.Linux.Sysctl != nil {
|
||||
if val, ok := args.Spec.Linux.Sysctl["fs.nr_open"]; ok {
|
||||
nrOpen, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("setting fs.nr_open=%s: %w", val, err)
|
||||
}
|
||||
if nrOpen <= 0 || nrOpen > int(kernel.MaxFdLimit) {
|
||||
return nil, fmt.Errorf("setting fs.nr_open=%s", val)
|
||||
}
|
||||
maxFDLimit = int32(nrOpen)
|
||||
}
|
||||
}
|
||||
// Initiate the Kernel object, which is required by the Context passed
|
||||
// to createVFS in order to mount (among other things) procfs.
|
||||
if err = k.Init(kernel.InitKernelArgs{
|
||||
@@ -458,6 +472,7 @@ func New(args Args) (*Loader, error) {
|
||||
RootUTSNamespace: kernel.NewUTSNamespace(args.Spec.Hostname, args.Spec.Hostname, creds.UserNamespace),
|
||||
RootIPCNamespace: kernel.NewIPCNamespace(creds.UserNamespace),
|
||||
PIDNamespace: kernel.NewRootPIDNamespace(creds.UserNamespace),
|
||||
MaxFDLimit: maxFDLimit,
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("initializing kernel: %w", err)
|
||||
}
|
||||
|
||||
@@ -621,7 +621,9 @@ cc_binary(
|
||||
"//test/util:eventfd_util",
|
||||
"//test/util:file_descriptor",
|
||||
"@com_google_absl//absl/memory",
|
||||
"@com_google_absl//absl/strings",
|
||||
gtest,
|
||||
"//test/util:capability_util",
|
||||
"//test/util:fs_util",
|
||||
"//test/util:posix_error",
|
||||
"//test/util:temp_path",
|
||||
|
||||
@@ -16,13 +16,16 @@
|
||||
#include <sys/resource.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <memory>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "test/util/eventfd_util.h"
|
||||
#include "test/util/file_descriptor.h"
|
||||
#include "test/util/fs_util.h"
|
||||
#include "test/util/linux_capability_util.h"
|
||||
#include "test/util/posix_error.h"
|
||||
#include "test/util/temp_path.h"
|
||||
#include "test/util/test_util.h"
|
||||
@@ -140,6 +143,29 @@ TEST(DupTest, Rlimit) {
|
||||
EXPECT_EQ(fds.size() + used_fds, kFDLimit - fd.get() - 1);
|
||||
}
|
||||
|
||||
TEST(RlimitTest, DupLimitedByNROpenSysctl) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_RESOURCE)));
|
||||
|
||||
const int kNROpen = 1 << 22;
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/sys/fs/nr_open", O_WRONLY));
|
||||
auto data = absl::StrCat(kNROpen);
|
||||
EXPECT_THAT(write(fd.get(), data.c_str(), data.size()),
|
||||
SyscallSucceedsWithValue(data.size()));
|
||||
|
||||
struct rlimit rl = {
|
||||
.rlim_cur = kNROpen + 1,
|
||||
.rlim_max = kNROpen + 1,
|
||||
};
|
||||
EXPECT_THAT(setrlimit(RLIMIT_NOFILE, &rl), SyscallFailsWithErrno(EPERM));
|
||||
rl.rlim_cur = kNROpen;
|
||||
rl.rlim_max = kNROpen;
|
||||
ASSERT_THAT(setrlimit(RLIMIT_NOFILE, &rl), SyscallSucceeds());
|
||||
|
||||
ASSERT_THAT(dup3(fd.get(), kNROpen, 0), SyscallFailsWithErrno(EBADF));
|
||||
ASSERT_THAT(dup3(fd.get(), kNROpen - 1, 0), SyscallSucceeds());
|
||||
}
|
||||
|
||||
TEST(DupTest, Dup2SameFD) {
|
||||
auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
|
||||
FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDONLY));
|
||||
|
||||
Reference in New Issue
Block a user