diff --git a/libcontainer/init_linux.go b/libcontainer/init_linux.go index 632d7c3491b..3607e972ca6 100644 --- a/libcontainer/init_linux.go +++ b/libcontainer/init_linux.go @@ -613,6 +613,16 @@ func maybeClearRlimitNofileCache(limits []configs.Rlimit) { func setupRlimits(limits []configs.Rlimit, pid int) error { for _, rlimit := range limits { + // Enforce a minimum RLIMIT_NOFILE to prevent the Go runtime from crashing + // due to lack of file descriptors (e.g. for the network poller). + // See https://github.com/opencontainers/runc/issues/5082. + if rlimit.Type == unix.RLIMIT_NOFILE { + // 32 is a safe "minimum" (Go runtime + runc standard FDs). + // The issue report indicates <14 crashes. + if rlimit.Soft < 32 { + return fmt.Errorf("RLIMIT_NOFILE soft limit %d is too low, must be at least 32", rlimit.Soft) + } + } if err := unix.Prlimit(pid, rlimit.Type, &unix.Rlimit{Max: rlimit.Hard, Cur: rlimit.Soft}, nil); err != nil { return fmt.Errorf("error setting rlimit type %v: %w", rlimit.Type, err) } diff --git a/tests/integration/rlimits.bats b/tests/integration/rlimits.bats index 356a7871069..3a87a99ef6f 100644 --- a/tests/integration/rlimits.bats +++ b/tests/integration/rlimits.bats @@ -86,3 +86,16 @@ function exec_check_nofile() { hard=$soft exec_check_nofile "$soft" "$hard" } + +@test "runc run with low RLIMIT_NOFILE should error (gh-5082)" { + # Set a very low limit (10) which triggers the crash in the issue. + # We assert it fails with our nice error message. + update_config '.process.rlimits = [{"type": "RLIMIT_NOFILE", "soft": 10, "hard": 10}]' + + runc run test_rlimit + + # Expect failure + [ "$status" -ne 0 ] + # Expect our error message + [[ "$output" == *"RLIMIT_NOFILE soft limit 10 is too low"* ]] +}