Source file
src/runtime/security_test.go
1
2
3
4
5
6
7 package runtime_test
8
9 import (
10 "bytes"
11 "context"
12 "fmt"
13 "internal/testenv"
14 "io"
15 "os"
16 "os/exec"
17 "path/filepath"
18 "runtime"
19 "strings"
20 "testing"
21 "time"
22 )
23
24 func privesc(command string, args ...string) error {
25 ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
26 defer cancel()
27 var cmd *exec.Cmd
28 if runtime.GOOS == "darwin" {
29 cmd = exec.CommandContext(ctx, "sudo", append([]string{"-n", command}, args...)...)
30 } else if runtime.GOOS == "openbsd" {
31 cmd = exec.CommandContext(ctx, "doas", append([]string{"-n", command}, args...)...)
32 } else {
33 cmd = exec.CommandContext(ctx, "su", highPrivUser, "-c", fmt.Sprintf("%s %s", command, strings.Join(args, " ")))
34 }
35 _, err := cmd.CombinedOutput()
36 return err
37 }
38
39 const highPrivUser = "root"
40
41 func setSetuid(t *testing.T, user, bin string) {
42 t.Helper()
43
44
45
46
47
48
49
50
51 if err := privesc("chmod", "0777", filepath.Dir(bin)); err != nil {
52 t.Skipf("unable to set permissions on %q, likely no passwordless sudo/su: %s", filepath.Dir(bin), err)
53 }
54
55 if err := privesc("chown", user, bin); err != nil {
56 t.Skipf("unable to set permissions on test binary, likely no passwordless sudo/su: %s", err)
57 }
58 if err := privesc("chmod", "u+s", bin); err != nil {
59 t.Skipf("unable to set permissions on test binary, likely no passwordless sudo/su: %s", err)
60 }
61 }
62
63 func TestSUID(t *testing.T) {
64
65
66
67
68
69
70
71
72
73
74
75
76 if *flagQuick {
77 t.Skip("-quick")
78 }
79
80 testenv.MustHaveGoBuild(t)
81
82 helloBin, err := buildTestProg(t, "testsuid")
83 if err != nil {
84 t.Fatal(err)
85 }
86
87 f, err := os.CreateTemp(t.TempDir(), "suid-output")
88 if err != nil {
89 t.Fatal(err)
90 }
91 tempfilePath := f.Name()
92 f.Close()
93
94 lowPrivUser := "nobody"
95 setSetuid(t, lowPrivUser, helloBin)
96
97 b := bytes.NewBuffer(nil)
98 pr, pw, err := os.Pipe()
99 if err != nil {
100 t.Fatal(err)
101 }
102
103 proc, err := os.StartProcess(helloBin, []string{helloBin}, &os.ProcAttr{
104 Env: []string{"GOTRACEBACK=system", "TEST_OUTPUT=" + tempfilePath},
105 Files: []*os.File{os.Stdin, pw},
106 })
107 if err != nil {
108 if os.IsPermission(err) {
109 t.Skip("don't have execute permission on setuid binary, possibly directory permission issue?")
110 }
111 t.Fatal(err)
112 }
113 done := make(chan bool, 1)
114 go func() {
115 io.Copy(b, pr)
116 pr.Close()
117 done <- true
118 }()
119 ps, err := proc.Wait()
120 if err != nil {
121 t.Fatal(err)
122 }
123 pw.Close()
124 <-done
125 output := b.String()
126
127 if ps.ExitCode() == 99 {
128 t.Skip("binary wasn't setuid (uid == euid), unable to effectively test")
129 }
130
131 expected := "GOTRACEBACK=none\n"
132 if output != expected {
133 t.Errorf("unexpected output, got: %q, want %q", output, expected)
134 }
135
136 fc, err := os.ReadFile(tempfilePath)
137 if err != nil {
138 t.Fatal(err)
139 }
140 if string(fc) != "" {
141 t.Errorf("unexpected file content, got: %q", string(fc))
142 }
143
144
145 }
146
View as plain text