github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/runtime/security_test.go (about) 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 "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 // We escalate privileges here even if we are root, because for some reason on some builders 44 // (at least freebsd-amd64-13_0) the default PATH doesn't include /usr/sbin, which is where 45 // chown lives, but using 'su root -c' gives us the correct PATH. 46 47 // buildTestProg uses os.MkdirTemp which creates directories with 0700, which prevents 48 // setuid binaries from executing because of the missing g+rx, so we need to set the parent 49 // directory to better permissions before anything else. We created this directory, so we 50 // shouldn't need to do any privilege trickery. 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 // This test is relatively simple, we build a test program which opens a 65 // file passed via the TEST_OUTPUT envvar, prints the value of the 66 // GOTRACEBACK envvar to stdout, and prints "hello" to stderr. We then chown 67 // the program to "nobody" and set u+s on it. We execute the program, only 68 // passing it two files, for stdin and stdout, and passing 69 // GOTRACEBACK=system in the env. 70 // 71 // We expect that the program will trigger the SUID protections, resetting 72 // the value of GOTRACEBACK, and opening the missing stderr descriptor, such 73 // that the program prints "GOTRACEBACK=none" to stdout, and nothing gets 74 // written to the file pointed at by TEST_OUTPUT. 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 // TODO: check the registers aren't leaked? 145 }