]> Cypherpunks.ru repositories - gostls13.git/commitdiff
runtime: enforce standard file descriptors open on init on unix
authorRoland Shoemaker <roland@golang.org>
Wed, 12 Jul 2023 21:01:26 +0000 (14:01 -0700)
committerGopher Robot <gobot@golang.org>
Tue, 25 Jul 2023 16:33:33 +0000 (16:33 +0000)
On Unix-like platforms, enforce that the standard file descriptions (0,
1, 2) are always open during initialization. If any of the FDs are
closed, we open them pointing at /dev/null, or fail.

Fixes #60641

Change-Id: Iaab6b3f3e5ca44006ae3ba3544d47da9a613f58f
Reviewed-on: https://go-review.googlesource.com/c/go/+/509020
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Roland Shoemaker <roland@golang.org>
Auto-Submit: Roland Shoemaker <roland@golang.org>
Reviewed-by: Ian Lance Taylor <iant@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>

src/runtime/fds_nonunix.go [new file with mode: 0644]
src/runtime/fds_test.go [new file with mode: 0644]
src/runtime/fds_unix.go [new file with mode: 0644]
src/runtime/proc.go
src/runtime/security_unix.go
src/runtime/testdata/testfds/main.go [new file with mode: 0644]

diff --git a/src/runtime/fds_nonunix.go b/src/runtime/fds_nonunix.go
new file mode 100644 (file)
index 0000000..81e59f3
--- /dev/null
@@ -0,0 +1,11 @@
+// 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 runtime
+
+func checkfds() {
+       // Nothing to do on non-Unix platforms.
+}
diff --git a/src/runtime/fds_test.go b/src/runtime/fds_test.go
new file mode 100644 (file)
index 0000000..8d349ec
--- /dev/null
@@ -0,0 +1,78 @@
+// 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 runtime_test
+
+import (
+       "internal/testenv"
+       "os"
+       "strings"
+       "testing"
+)
+
+func TestCheckFDs(t *testing.T) {
+       if *flagQuick {
+               t.Skip("-quick")
+       }
+
+       testenv.MustHaveGoBuild(t)
+
+       fdsBin, err := buildTestProg(t, "testfds")
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       i, err := os.CreateTemp(t.TempDir(), "fds-input")
+       if err != nil {
+               t.Fatal(err)
+       }
+       if _, err := i.Write([]byte("stdin")); err != nil {
+               t.Fatal(err)
+       }
+       if err := i.Close(); err != nil {
+               t.Fatal(err)
+       }
+
+       o, err := os.CreateTemp(t.TempDir(), "fds-output")
+       if err != nil {
+               t.Fatal(err)
+       }
+       outputPath := o.Name()
+       if err := o.Close(); err != nil {
+               t.Fatal(err)
+       }
+
+       env := []string{"TEST_OUTPUT=" + outputPath}
+       for _, e := range os.Environ() {
+               if strings.HasPrefix(e, "GODEBUG=") || strings.HasPrefix(e, "GOTRACEBACK=") {
+                       continue
+               }
+               env = append(env, e)
+       }
+
+       proc, err := os.StartProcess(fdsBin, []string{fdsBin}, &os.ProcAttr{
+               Env:   env,
+               Files: []*os.File{},
+       })
+       if err != nil {
+               t.Fatal(err)
+       }
+       ps, err := proc.Wait()
+       if err != nil {
+               t.Fatal(err)
+       }
+       if ps.ExitCode() != 0 {
+               t.Fatalf("testfds failed: %d", ps.ExitCode())
+       }
+
+       fc, err := os.ReadFile(outputPath)
+       if err != nil {
+               t.Fatal(err)
+       }
+       if string(fc) != "" {
+               t.Errorf("unexpected file content, got: %q", string(fc))
+       }
+}
diff --git a/src/runtime/fds_unix.go b/src/runtime/fds_unix.go
new file mode 100644 (file)
index 0000000..3004e6f
--- /dev/null
@@ -0,0 +1,44 @@
+// 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 runtime
+
+func checkfds() {
+       if islibrary || isarchive {
+               // If the program is actually a library, presumably being consumed by
+               // another program, we don't want to mess around with the file
+               // descriptors.
+               return
+       }
+
+       const (
+               // F_GETFD, EBADF, O_RDWR are standard across all unixes we support, so
+               // we define them here rather than in each of the OS specific files.
+               F_GETFD = 0x01
+               EBADF   = 0x09
+               O_RDWR  = 0x02
+       )
+
+       devNull := []byte("/dev/null\x00")
+       for i := 0; i < 3; i++ {
+               ret, errno := fcntl(int32(i), F_GETFD, 0)
+               if ret >= 0 {
+                       continue
+               }
+               if errno != EBADF {
+                       print("runtime: unexpected error while checking standard file descriptor ", i, ", errno=", errno, "\n")
+                       throw("cannot open standard fds")
+               }
+
+               if ret := open(&devNull[0], O_RDWR, 0); ret < 0 {
+                       print("runtime: standard file descriptor ", i, " closed, unable to open /dev/null, errno=", errno, "\n")
+                       throw("cannot open standard fds")
+               } else if ret != int32(i) {
+                       print("runtime: opened unexpected file descriptor ", ret, " when attempting to open ", i, "\n")
+                       throw("cannot open standard fds")
+               }
+       }
+}
index a0167d333fe7ef51d0419cf924f08502235bbed6..047b359d3df16a86edbabec5011d50fac0121701 100644 (file)
@@ -741,6 +741,7 @@ func schedinit() {
        goargs()
        goenvs()
        secure()
+       checkfds()
        parsedebugvars()
        gcinit()
 
index 16fc87eece5414436d15d3cec38dbd39867bccbc..fa54090df248579d8509e69208f196a376eb7326 100644 (file)
@@ -13,19 +13,12 @@ func secure() {
                return
        }
 
-       // When secure mode is enabled, we do two things:
-       //   1. ensure the file descriptors 0, 1, and 2 are open, and if not open them,
-       //      pointing at /dev/null (or fail)
-       //   2. enforce specific environment variable values (currently we only force
-       //              GOTRACEBACK=none)
+       // When secure mode is enabled, we do one thing: enforce specific
+       // environment variable values (currently we only force GOTRACEBACK=none)
        //
        // Other packages may also disable specific functionality when secure mode
        // is enabled (determined by using linkname to call isSecureMode).
-       //
-       // NOTE: we may eventually want to enforce (1) regardless of whether secure
-       // mode is enabled or not.
 
-       secureFDs()
        secureEnv()
 }
 
@@ -41,32 +34,3 @@ func secureEnv() {
                envs = append(envs, "GOTRACEBACK=none")
        }
 }
-
-func secureFDs() {
-       const (
-               // F_GETFD and EBADF are standard across all unixes, define
-               // them here rather than in each of the OS specific files
-               F_GETFD = 0x01
-               EBADF   = 0x09
-       )
-
-       devNull := []byte("/dev/null\x00")
-       for i := 0; i < 3; i++ {
-               ret, errno := fcntl(int32(i), F_GETFD, 0)
-               if ret >= 0 {
-                       continue
-               }
-               if errno != EBADF {
-                       print("runtime: unexpected error while checking standard file descriptor ", i, ", errno=", errno, "\n")
-                       throw("cannot secure fds")
-               }
-
-               if ret := open(&devNull[0], 2 /* O_RDWR */, 0); ret < 0 {
-                       print("runtime: standard file descriptor ", i, " closed, unable to open /dev/null, errno=", errno, "\n")
-                       throw("cannot secure fds")
-               } else if ret != int32(i) {
-                       print("runtime: opened unexpected file descriptor ", ret, " when attempting to open ", i, "\n")
-                       throw("cannot secure fds")
-               }
-       }
-}
diff --git a/src/runtime/testdata/testfds/main.go b/src/runtime/testdata/testfds/main.go
new file mode 100644 (file)
index 0000000..238ba46
--- /dev/null
@@ -0,0 +1,29 @@
+// 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.
+
+package main
+
+import (
+       "fmt"
+       "io"
+       "log"
+       "os"
+)
+
+func main() {
+       f, err := os.OpenFile(os.Getenv("TEST_OUTPUT"), os.O_CREATE|os.O_RDWR, 0600)
+       if err != nil {
+               log.Fatalf("os.Open failed: %s", err)
+       }
+       defer f.Close()
+       b, err := io.ReadAll(os.Stdin)
+       if err != nil {
+               log.Fatalf("io.ReadAll(os.Stdin) failed: %s", err)
+       }
+       if len(b) != 0 {
+               log.Fatalf("io.ReadAll(os.Stdin) returned non-nil: %x", b)
+       }
+       fmt.Fprintf(os.Stdout, "stdout\n")
+       fmt.Fprintf(os.Stderr, "stderr\n")
+}