Skip to content

Commit

Permalink
[release-branch.go1.20] syscall: restore original NOFILE rlimit in ch…
Browse files Browse the repository at this point in the history
…ild process

If we increased the NOFILE rlimit when starting the program,
restore the original rlimit when forking a child process.

In CL 393354 the os package was changed to raise the open file rlimit
at program start. That code is not inherently tied to the os package.
This CL moves it into the syscall package.

This is a backport of CLs 476096 and 476097 from trunk.

For golang#46279
Fixes golang#59064

Change-Id: Ib813de896de0a5d28fa2b29afdf414a89fbe7b2a
Reviewed-on: https://go-review.googlesource.com/c/go/+/478659
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Tobias Klauser <tobias.klauser@gmail.com>
  • Loading branch information
ianlancetaylor authored and bradfitz committed May 25, 2023
1 parent 5c2bf92 commit 253de87
Show file tree
Hide file tree
Showing 66 changed files with 380 additions and 90 deletions.
2 changes: 2 additions & 0 deletions src/runtime/syscall2_solaris.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import _ "unsafe" // for go:linkname
//go:cgo_import_dynamic libc_ioctl ioctl "libc.so"
//go:cgo_import_dynamic libc_setgid setgid "libc.so"
//go:cgo_import_dynamic libc_setgroups setgroups "libc.so"
//go:cgo_import_dynamic libc_setrlimit setrlimit "libc.so"
//go:cgo_import_dynamic libc_setsid setsid "libc.so"
//go:cgo_import_dynamic libc_setuid setuid "libc.so"
//go:cgo_import_dynamic libc_setpgid setpgid "libc.so"
Expand All @@ -34,6 +35,7 @@ import _ "unsafe" // for go:linkname
//go:linkname libc_ioctl libc_ioctl
//go:linkname libc_setgid libc_setgid
//go:linkname libc_setgroups libc_setgroups
//go:linkname libc_setrlimit libc_setrlimit
//go:linkname libc_setsid libc_setsid
//go:linkname libc_setuid libc_setuid
//go:linkname libc_setpgid libc_setpgid
Expand Down
10 changes: 10 additions & 0 deletions src/runtime/syscall_aix.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import "unsafe"
//go:cgo_import_dynamic libc_ioctl ioctl "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_setgid setgid "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_setgroups setgroups "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_setrlimit setrlimit "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_setsid setsid "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_setuid setuid "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_setpgid setpgid "libc.a/shr_64.o"
Expand All @@ -31,6 +32,7 @@ import "unsafe"
//go:linkname libc_ioctl libc_ioctl
//go:linkname libc_setgid libc_setgid
//go:linkname libc_setgroups libc_setgroups
//go:linkname libc_setrlimit libc_setrlimit
//go:linkname libc_setsid libc_setsid
//go:linkname libc_setuid libc_setuid
//go:linkname libc_setpgid libc_setpgid
Expand All @@ -45,6 +47,7 @@ var (
libc_ioctl,
libc_setgid,
libc_setgroups,
libc_setrlimit,
libc_setsid,
libc_setuid,
libc_setpgid libFunc
Expand Down Expand Up @@ -199,6 +202,13 @@ func syscall_setgroups1(ngid, gid uintptr) (err uintptr) {
return
}

//go:linkname syscall_setrlimit1 syscall.setrlimit1
//go:nosplit
func syscall_setrlimit1(which uintptr, lim unsafe.Pointer) (err uintptr) {
_, err = syscall2(&libc_setrlimit, which, uintptr(lim))
return
}

//go:linkname syscall_setsid syscall.setsid
//go:nosplit
func syscall_setsid() (pid, err uintptr) {
Expand Down
14 changes: 14 additions & 0 deletions src/runtime/syscall_solaris.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ var (
libc_ioctl,
libc_setgid,
libc_setgroups,
libc_setrlimit,
libc_setsid,
libc_setuid,
libc_setpgid,
Expand Down Expand Up @@ -234,6 +235,19 @@ func syscall_setgroups(ngid, gid uintptr) (err uintptr) {
return call.err
}

//go:nosplit
//go:linkname syscall_setrlimit
//go:cgo_unsafe_args
func syscall_setrlimit(which uintptr, lim unsafe.Pointer) (err uintptr) {
call := libcall{
fn: uintptr(unsafe.Pointer(&libc_setrlimit)),
n: 2,
args: uintptr(unsafe.Pointer(&which)),
}
asmcgocall(unsafe.Pointer(&asmsysvicall6x), unsafe.Pointer(&call))
return call.err
}

//go:nosplit
//go:linkname syscall_setsid
func syscall_setsid() (pid, err uintptr) {
Expand Down
3 changes: 3 additions & 0 deletions src/syscall/asm_solaris_amd64.s
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ TEXT ·setgid(SB),NOSPLIT,$0
TEXT ·setgroups1(SB),NOSPLIT,$0
JMP runtime·syscall_setgroups(SB)

TEXT ·setrlimit1(SB),NOSPLIT,$0
JMP runtime·syscall_setrlimit(SB)

TEXT ·setsid(SB),NOSPLIT,$0
JMP runtime·syscall_setsid(SB)

Expand Down
7 changes: 7 additions & 0 deletions src/syscall/exec_bsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr
i int
)

rlim, rlimOK := origRlimitNofile.Load().(Rlimit)

// guard against side effects of shuffling fds below.
// Make sure that nextfd is beyond any currently open files so
// that we can't run the risk of overwriting any of them.
Expand Down Expand Up @@ -270,6 +272,11 @@ func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr
}
}

// Restore original rlimit.
if rlimOK && rlim.Cur != 0 {
RawSyscall(SYS_SETRLIMIT, uintptr(RLIMIT_NOFILE), uintptr(unsafe.Pointer(&rlim)), 0)
}

// Time to exec.
_, _, err1 = RawSyscall(SYS_EXECVE,
uintptr(unsafe.Pointer(argv0)),
Expand Down
7 changes: 7 additions & 0 deletions src/syscall/exec_freebsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr
i int
)

rlim, rlimOK := origRlimitNofile.Load().(Rlimit)

// Record parent PID so child can test if it has died.
ppid, _, _ := RawSyscall(SYS_GETPID, 0, 0, 0)

Expand Down Expand Up @@ -283,6 +285,11 @@ func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr
}
}

// Restore original rlimit.
if rlimOK && rlim.Cur != 0 {
RawSyscall(SYS_SETRLIMIT, uintptr(RLIMIT_NOFILE), uintptr(unsafe.Pointer(&rlim)), 0)
}

// Time to exec.
_, _, err1 = RawSyscall(SYS_EXECVE,
uintptr(unsafe.Pointer(argv0)),
Expand Down
8 changes: 8 additions & 0 deletions src/syscall/exec_libc.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func getpid() (pid uintptr, err Errno)
func ioctl(fd uintptr, req uintptr, arg uintptr) (err Errno)
func setgid(gid uintptr) (err Errno)
func setgroups1(ngid uintptr, gid uintptr) (err Errno)
func setrlimit1(which uintptr, lim unsafe.Pointer) (err Errno)
func setsid() (pid uintptr, err Errno)
func setuid(uid uintptr) (err Errno)
func setpgid(pid uintptr, pgid uintptr) (err Errno)
Expand Down Expand Up @@ -87,6 +88,8 @@ func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr
i int
)

rlim, rlimOK := origRlimitNofile.Load().(Rlimit)

// guard against side effects of shuffling fds below.
// Make sure that nextfd is beyond any currently open files so
// that we can't run the risk of overwriting any of them.
Expand Down Expand Up @@ -289,6 +292,11 @@ func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr
}
}

// Restore original rlimit.
if rlimOK && rlim.Cur != 0 {
setrlimit1(RLIMIT_NOFILE, unsafe.Pointer(&rlim))
}

// Time to exec.
err1 = execve(
uintptr(unsafe.Pointer(argv0)),
Expand Down
7 changes: 7 additions & 0 deletions src/syscall/exec_libc2.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr
i int
)

rlim, rlimOK := origRlimitNofile.Load().(Rlimit)

// guard against side effects of shuffling fds below.
// Make sure that nextfd is beyond any currently open files so
// that we can't run the risk of overwriting any of them.
Expand Down Expand Up @@ -266,6 +268,11 @@ func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr
}
}

// Restore original rlimit.
if rlimOK && rlim.Cur != 0 {
rawSyscall(abi.FuncPCABI0(libc_setrlimit_trampoline), uintptr(RLIMIT_NOFILE), uintptr(unsafe.Pointer(&rlim)), 0)
}

// Time to exec.
_, _, err1 = rawSyscall(abi.FuncPCABI0(libc_execve_trampoline),
uintptr(unsafe.Pointer(argv0)),
Expand Down
7 changes: 7 additions & 0 deletions src/syscall/exec_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ func forkAndExecInChild1(argv0 *byte, argv, envv []*byte, chroot, dir *byte, att
clone3 *cloneArgs
)

rlim, rlimOK := origRlimitNofile.Load().(Rlimit)

if sys.UidMappings != nil {
puid = []byte("/proc/self/uid_map\000")
uidmap = formatIDMappings(sys.UidMappings)
Expand Down Expand Up @@ -592,6 +594,11 @@ func forkAndExecInChild1(argv0 *byte, argv, envv []*byte, chroot, dir *byte, att
}
}

// Restore original rlimit.
if rlimOK && rlim.Cur != 0 {
rawSetrlimit(RLIMIT_NOFILE, &rlim)
}

// Enable tracing if requested.
// Do this right before exec so that we don't unnecessarily trace the runtime
// setting up after the fork. See issue #21428.
Expand Down
5 changes: 5 additions & 0 deletions src/syscall/exec_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,11 @@ func Exec(argv0 string, argv []string, envv []string) (err error) {
}
runtime_BeforeExec()

rlim, rlimOK := origRlimitNofile.Load().(Rlimit)
if rlimOK && rlim.Cur != 0 {
Setrlimit(RLIMIT_NOFILE, &rlim)
}

var err1 error
if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" || runtime.GOOS == "aix" {
// RawSyscall should never be used on Solaris, illumos, or AIX.
Expand Down
42 changes: 42 additions & 0 deletions src/syscall/exec_unix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@
package syscall_test

import (
"bytes"
"fmt"
"internal/testenv"
"io"
"math/rand"
"os"
"os/exec"
"os/signal"
"strconv"
"syscall"
"testing"
"time"
Expand Down Expand Up @@ -345,3 +348,42 @@ func TestExecHelper(t *testing.T) {

t.Error("syscall.Exec returned")
}

// Test that rlimit values are restored by exec.
func TestRlimitRestored(t *testing.T) {
if os.Getenv("GO_WANT_HELPER_PROCESS") != "" {
fmt.Println(syscall.OrigRlimitNofile().Cur)
os.Exit(0)
}

orig := syscall.OrigRlimitNofile()
if orig.Cur == 0 {
t.Skip("skipping test because rlimit not adjusted at startup")
}

executable, err := os.Executable()
if err != nil {
executable = os.Args[0]
}

cmd := testenv.Command(t, executable, "-test.run=TestRlimitRestored")
cmd = testenv.CleanCmdEnv(cmd)
cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=1")

out, err := cmd.CombinedOutput()
if len(out) > 0 {
t.Logf("%s", out)
}
if err != nil {
t.Fatalf("subprocess failed: %v", err)
}
s := string(bytes.TrimSpace(out))
v, err := strconv.ParseUint(s, 10, 64)
if err != nil {
t.Fatalf("could not parse %q as number: %v", s, v)
}

if v != uint64(orig.Cur) {
t.Errorf("exec rlimit = %d, want %d", v, orig)
}
}
14 changes: 14 additions & 0 deletions src/syscall/export_rlimit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build unix

package syscall

func OrigRlimitNofile() Rlimit {
if rlim, ok := origRlimitNofile.Load().(Rlimit); ok {
return rlim
}
return Rlimit{0, 0}
}
28 changes: 23 additions & 5 deletions src/os/rlimit.go → src/syscall/rlimit.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@

//go:build unix

package os
package syscall

import "syscall"
import (
"sync/atomic"
)

// origRlimitNofile, if not {0, 0}, is the original soft RLIMIT_NOFILE.
// When we can assume that we are bootstrapping with Go 1.19,
// this can be atomic.Pointer[Rlimit].
var origRlimitNofile atomic.Value // of Rlimit

// Some systems set an artificially low soft limit on open file count, for compatibility
// with code that uses select and its hard-coded maximum file descriptor
Expand All @@ -23,10 +30,21 @@ import "syscall"
// Code that really wants Go to leave the limit alone can set the hard limit,
// which Go of course has no choice but to respect.
func init() {
var lim syscall.Rlimit
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err == nil && lim.Cur != lim.Max {
var lim Rlimit
if err := Getrlimit(RLIMIT_NOFILE, &lim); err == nil && lim.Cur != lim.Max {
origRlimitNofile.Store(lim)
lim.Cur = lim.Max
adjustFileLimit(&lim)
syscall.Setrlimit(syscall.RLIMIT_NOFILE, &lim)
setrlimit(RLIMIT_NOFILE, &lim)
}
}

func Setrlimit(resource int, rlim *Rlimit) error {
err := setrlimit(resource, rlim)
if err == nil && resource == RLIMIT_NOFILE {
// Store zeroes in origRlimitNofile to tell StartProcess
// to not adjust the rlimit in the child process.
origRlimitNofile.Store(Rlimit{0, 0})
}
return err
}
8 changes: 3 additions & 5 deletions src/os/rlimit_darwin.go → src/syscall/rlimit_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@

//go:build darwin

package os

import "syscall"
package syscall

// adjustFileLimit adds per-OS limitations on the Rlimit used for RLIMIT_NOFILE. See rlimit.go.
func adjustFileLimit(lim *syscall.Rlimit) {
func adjustFileLimit(lim *Rlimit) {
// On older macOS, setrlimit(RLIMIT_NOFILE, lim) with lim.Cur = infinity fails.
// Set to the value of kern.maxfilesperproc instead.
n, err := syscall.SysctlUint32("kern.maxfilesperproc")
n, err := SysctlUint32("kern.maxfilesperproc")
if err != nil {
return
}
Expand Down
6 changes: 2 additions & 4 deletions src/os/rlimit_stub.go → src/syscall/rlimit_stub.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@

//go:build aix || dragonfly || freebsd || linux || netbsd || openbsd || solaris

package os

import "syscall"
package syscall

// adjustFileLimit adds per-OS limitations on the Rlimit used for RLIMIT_NOFILE. See rlimit.go.
func adjustFileLimit(lim *syscall.Rlimit) {}
func adjustFileLimit(lim *Rlimit) {}
8 changes: 4 additions & 4 deletions src/os/rlimit_test.go → src/syscall/rlimit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package os_test
package syscall_test

import (
. "os"
"os"
"runtime"
"testing"
)
Expand All @@ -24,9 +24,9 @@ func TestOpenFileLimit(t *testing.T) {
fileCount = 768
}

var files []*File
var files []*os.File
for i := 0; i < fileCount; i++ {
f, err := Open("rlimit.go")
f, err := os.Open("rlimit.go")
if err != nil {
t.Error(err)
break
Expand Down
2 changes: 1 addition & 1 deletion src/syscall/syscall_aix.go
Original file line number Diff line number Diff line change
Expand Up @@ -633,7 +633,7 @@ func PtraceDetach(pid int) (err error) { return ptrace64(PT_DETACH, int64(pid),
//sys Setpriority(which int, who int, prio int) (err error)
//sysnb Setregid(rgid int, egid int) (err error)
//sysnb Setreuid(ruid int, euid int) (err error)
//sysnb Setrlimit(which int, lim *Rlimit) (err error)
//sysnb setrlimit(which int, lim *Rlimit) (err error)
//sys Stat(path string, stat *Stat_t) (err error)
//sys Statfs(path string, buf *Statfs_t) (err error)
//sys Symlink(path string, link string) (err error)
Expand Down
Loading

0 comments on commit 253de87

Please sign in to comment.