Skip to content

Commit d4dd1de

Browse files
rolandshoemakergopherbot
authored andcommitted
runtime: enforce standard file descriptors open on init on unix
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 <[email protected]> Run-TryBot: Roland Shoemaker <[email protected]> Auto-Submit: Roland Shoemaker <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 862fa6d commit d4dd1de

File tree

6 files changed

+165
-38
lines changed

6 files changed

+165
-38
lines changed

src/runtime/fds_nonunix.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build !unix
6+
7+
package runtime
8+
9+
func checkfds() {
10+
// Nothing to do on non-Unix platforms.
11+
}

src/runtime/fds_test.go

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build unix
6+
7+
package runtime_test
8+
9+
import (
10+
"internal/testenv"
11+
"os"
12+
"strings"
13+
"testing"
14+
)
15+
16+
func TestCheckFDs(t *testing.T) {
17+
if *flagQuick {
18+
t.Skip("-quick")
19+
}
20+
21+
testenv.MustHaveGoBuild(t)
22+
23+
fdsBin, err := buildTestProg(t, "testfds")
24+
if err != nil {
25+
t.Fatal(err)
26+
}
27+
28+
i, err := os.CreateTemp(t.TempDir(), "fds-input")
29+
if err != nil {
30+
t.Fatal(err)
31+
}
32+
if _, err := i.Write([]byte("stdin")); err != nil {
33+
t.Fatal(err)
34+
}
35+
if err := i.Close(); err != nil {
36+
t.Fatal(err)
37+
}
38+
39+
o, err := os.CreateTemp(t.TempDir(), "fds-output")
40+
if err != nil {
41+
t.Fatal(err)
42+
}
43+
outputPath := o.Name()
44+
if err := o.Close(); err != nil {
45+
t.Fatal(err)
46+
}
47+
48+
env := []string{"TEST_OUTPUT=" + outputPath}
49+
for _, e := range os.Environ() {
50+
if strings.HasPrefix(e, "GODEBUG=") || strings.HasPrefix(e, "GOTRACEBACK=") {
51+
continue
52+
}
53+
env = append(env, e)
54+
}
55+
56+
proc, err := os.StartProcess(fdsBin, []string{fdsBin}, &os.ProcAttr{
57+
Env: env,
58+
Files: []*os.File{},
59+
})
60+
if err != nil {
61+
t.Fatal(err)
62+
}
63+
ps, err := proc.Wait()
64+
if err != nil {
65+
t.Fatal(err)
66+
}
67+
if ps.ExitCode() != 0 {
68+
t.Fatalf("testfds failed: %d", ps.ExitCode())
69+
}
70+
71+
fc, err := os.ReadFile(outputPath)
72+
if err != nil {
73+
t.Fatal(err)
74+
}
75+
if string(fc) != "" {
76+
t.Errorf("unexpected file content, got: %q", string(fc))
77+
}
78+
}

src/runtime/fds_unix.go

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build unix
6+
7+
package runtime
8+
9+
func checkfds() {
10+
if islibrary || isarchive {
11+
// If the program is actually a library, presumably being consumed by
12+
// another program, we don't want to mess around with the file
13+
// descriptors.
14+
return
15+
}
16+
17+
const (
18+
// F_GETFD, EBADF, O_RDWR are standard across all unixes we support, so
19+
// we define them here rather than in each of the OS specific files.
20+
F_GETFD = 0x01
21+
EBADF = 0x09
22+
O_RDWR = 0x02
23+
)
24+
25+
devNull := []byte("/dev/null\x00")
26+
for i := 0; i < 3; i++ {
27+
ret, errno := fcntl(int32(i), F_GETFD, 0)
28+
if ret >= 0 {
29+
continue
30+
}
31+
if errno != EBADF {
32+
print("runtime: unexpected error while checking standard file descriptor ", i, ", errno=", errno, "\n")
33+
throw("cannot open standard fds")
34+
}
35+
36+
if ret := open(&devNull[0], O_RDWR, 0); ret < 0 {
37+
print("runtime: standard file descriptor ", i, " closed, unable to open /dev/null, errno=", errno, "\n")
38+
throw("cannot open standard fds")
39+
} else if ret != int32(i) {
40+
print("runtime: opened unexpected file descriptor ", ret, " when attempting to open ", i, "\n")
41+
throw("cannot open standard fds")
42+
}
43+
}
44+
}

src/runtime/proc.go

+1
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,7 @@ func schedinit() {
741741
goargs()
742742
goenvs()
743743
secure()
744+
checkfds()
744745
parsedebugvars()
745746
gcinit()
746747

src/runtime/security_unix.go

+2-38
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,12 @@ func secure() {
1313
return
1414
}
1515

16-
// When secure mode is enabled, we do two things:
17-
// 1. ensure the file descriptors 0, 1, and 2 are open, and if not open them,
18-
// pointing at /dev/null (or fail)
19-
// 2. enforce specific environment variable values (currently we only force
20-
// GOTRACEBACK=none)
16+
// When secure mode is enabled, we do one thing: enforce specific
17+
// environment variable values (currently we only force GOTRACEBACK=none)
2118
//
2219
// Other packages may also disable specific functionality when secure mode
2320
// is enabled (determined by using linkname to call isSecureMode).
24-
//
25-
// NOTE: we may eventually want to enforce (1) regardless of whether secure
26-
// mode is enabled or not.
2721

28-
secureFDs()
2922
secureEnv()
3023
}
3124

@@ -41,32 +34,3 @@ func secureEnv() {
4134
envs = append(envs, "GOTRACEBACK=none")
4235
}
4336
}
44-
45-
func secureFDs() {
46-
const (
47-
// F_GETFD and EBADF are standard across all unixes, define
48-
// them here rather than in each of the OS specific files
49-
F_GETFD = 0x01
50-
EBADF = 0x09
51-
)
52-
53-
devNull := []byte("/dev/null\x00")
54-
for i := 0; i < 3; i++ {
55-
ret, errno := fcntl(int32(i), F_GETFD, 0)
56-
if ret >= 0 {
57-
continue
58-
}
59-
if errno != EBADF {
60-
print("runtime: unexpected error while checking standard file descriptor ", i, ", errno=", errno, "\n")
61-
throw("cannot secure fds")
62-
}
63-
64-
if ret := open(&devNull[0], 2 /* O_RDWR */, 0); ret < 0 {
65-
print("runtime: standard file descriptor ", i, " closed, unable to open /dev/null, errno=", errno, "\n")
66-
throw("cannot secure fds")
67-
} else if ret != int32(i) {
68-
print("runtime: opened unexpected file descriptor ", ret, " when attempting to open ", i, "\n")
69-
throw("cannot secure fds")
70-
}
71-
}
72-
}

src/runtime/testdata/testfds/main.go

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
import (
8+
"fmt"
9+
"io"
10+
"log"
11+
"os"
12+
)
13+
14+
func main() {
15+
f, err := os.OpenFile(os.Getenv("TEST_OUTPUT"), os.O_CREATE|os.O_RDWR, 0600)
16+
if err != nil {
17+
log.Fatalf("os.Open failed: %s", err)
18+
}
19+
defer f.Close()
20+
b, err := io.ReadAll(os.Stdin)
21+
if err != nil {
22+
log.Fatalf("io.ReadAll(os.Stdin) failed: %s", err)
23+
}
24+
if len(b) != 0 {
25+
log.Fatalf("io.ReadAll(os.Stdin) returned non-nil: %x", b)
26+
}
27+
fmt.Fprintf(os.Stdout, "stdout\n")
28+
fmt.Fprintf(os.Stderr, "stderr\n")
29+
}

0 commit comments

Comments
 (0)