Implement the fs.nr_open sysctl

fs/nr_open limits the maximum size of fdtable-s.

PiperOrigin-RevId: 580795874
This commit is contained in:
Andrei Vagin
2023-11-08 23:41:32 -08:00
committed by gVisor bot
parent 9bfd408753
commit 68cdc88378
9 changed files with 126 additions and 5 deletions
+50
View File
@@ -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
}
+8 -5
View File
@@ -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 {
+2
View File
@@ -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.
+13
View File
@@ -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 {
+3
View File
@@ -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
}
+7
View File
@@ -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."
+15
View File
@@ -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)
}
+2
View File
@@ -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",
+26
View File
@@ -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));