aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoland Shoemaker <bracewell@google.com>2023-05-09 11:47:57 -0700
committerGopher Robot <gobot@golang.org>2023-06-06 17:01:31 +0000
commit36144ba429ef2650940c72e7a0b932af3612d420 (patch)
tree5409a7f0684ba868b9243a78ec4017b705f50b6d
parent5036ba77eb229222cbd5dee436949138ea219adf (diff)
downloadgo-36144ba429ef2650940c72e7a0b932af3612d420.tar.gz
go-36144ba429ef2650940c72e7a0b932af3612d420.zip
[release-branch.go1.20] runtime: implement SUID/SGID protections
On Unix platforms, the runtime previously did nothing special when a program was run with either the SUID or SGID bits set. This can be dangerous in certain cases, such as when dumping memory state, or assuming the status of standard i/o file descriptors. Taking cues from glibc, this change implements a set of protections when a binary is run with SUID or SGID bits set (or is SUID/SGID-like). On Linux, whether to enable these protections is determined by whether the AT_SECURE flag is passed in the auxiliary vector. On platforms which have the issetugid syscall (the BSDs, darwin, and Solaris/Illumos), that is used. On the remaining platforms (currently only AIX) we check !(getuid() == geteuid() && getgid == getegid()). Currently when we determine a binary is "tainted" (using the glibc terminology), we implement two specific protections: 1. we check if the file descriptors 0, 1, and 2 are open, and if they are not, we open them, pointing at /dev/null (or fail). 2. we force GOTRACKBACK=none, and generally prevent dumping of trackbacks and registers when a program panics/aborts. In the future we may add additional protections. This change requires implementing issetugid on the platforms which support it, and implementing getuid, geteuid, getgid, and getegid on AIX. Thanks to Vincent Dehors from Synacktiv for reporting this issue. Updates #60272 Fixes #60518 Fixes CVE-2023-29403 Change-Id: Icb620f3f8755791d51b02b5c07fb24f40e19cb80 Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1878434 Reviewed-by: Damien Neil <dneil@google.com> Reviewed-by: Ian Lance Taylor <iant@google.com> Run-TryBot: Roland Shoemaker <bracewell@google.com> Reviewed-by: Russ Cox <rsc@google.com> (cherry picked from commit 87065663ea6d89cd54f65a515d8f2ed0ef285c19) Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1902232 TryBot-Result: Security TryBots <security-trybots@go-security-trybots.iam.gserviceaccount.com> Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1904344 Reviewed-by: Michael Knyszek <mknyszek@google.com> Reviewed-on: https://go-review.googlesource.com/c/go/+/501227 TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: David Chase <drchase@google.com> Auto-Submit: Michael Knyszek <mknyszek@google.com>
-rw-r--r--src/runtime/extern.go19
-rw-r--r--src/runtime/os2_aix.go12
-rw-r--r--src/runtime/os_aix.go40
-rw-r--r--src/runtime/os_dragonfly.go2
-rw-r--r--src/runtime/os_freebsd.go2
-rw-r--r--src/runtime/os_linux.go7
-rw-r--r--src/runtime/os_netbsd.go2
-rw-r--r--src/runtime/os_openbsd_syscall2.go2
-rw-r--r--src/runtime/os_solaris.go4
-rw-r--r--src/runtime/panic.go4
-rw-r--r--src/runtime/proc.go1
-rw-r--r--src/runtime/security_aix.go17
-rw-r--r--src/runtime/security_issetugid.go19
-rw-r--r--src/runtime/security_linux.go15
-rw-r--r--src/runtime/security_nonunix.go13
-rw-r--r--src/runtime/security_test.go143
-rw-r--r--src/runtime/security_unix.go72
-rw-r--r--src/runtime/signal_unix.go4
-rw-r--r--src/runtime/sys_darwin.go7
-rw-r--r--src/runtime/sys_darwin_amd64.s7
-rw-r--r--src/runtime/sys_darwin_arm64.s4
-rw-r--r--src/runtime/sys_dragonfly_amd64.s10
-rw-r--r--src/runtime/sys_freebsd_386.s8
-rw-r--r--src/runtime/sys_freebsd_amd64.s11
-rw-r--r--src/runtime/sys_freebsd_arm.s8
-rw-r--r--src/runtime/sys_freebsd_arm64.s8
-rw-r--r--src/runtime/sys_freebsd_riscv64.s9
-rw-r--r--src/runtime/sys_netbsd_386.s8
-rw-r--r--src/runtime/sys_netbsd_amd64.s11
-rw-r--r--src/runtime/sys_netbsd_arm.s7
-rw-r--r--src/runtime/sys_netbsd_arm64.s7
-rw-r--r--src/runtime/sys_openbsd2.go9
-rw-r--r--src/runtime/sys_openbsd_386.s9
-rw-r--r--src/runtime/sys_openbsd_amd64.s6
-rw-r--r--src/runtime/sys_openbsd_arm.s9
-rw-r--r--src/runtime/sys_openbsd_arm64.s6
-rw-r--r--src/runtime/sys_openbsd_mips64.s7
-rw-r--r--src/runtime/syscall2_solaris.go2
-rw-r--r--src/runtime/syscall_solaris.go1
-rw-r--r--src/runtime/testdata/testsuid/main.go25
40 files changed, 557 insertions, 0 deletions
diff --git a/src/runtime/extern.go b/src/runtime/extern.go
index 6c41c62694..afadc3d17e 100644
--- a/src/runtime/extern.go
+++ b/src/runtime/extern.go
@@ -216,6 +216,25 @@ the set of Go environment variables. They influence the building of Go programs
GOARCH, GOOS, and GOROOT are recorded at compile time and made available by
constants or functions in this package, but they do not influence the execution
of the run-time system.
+
+# Security
+
+On Unix platforms, Go's runtime system behaves slightly differently when a
+binary is setuid/setgid or executed with setuid/setgid-like properties, in order
+to prevent dangerous behaviors. On Linux this is determined by checking for the
+AT_SECURE flag in the auxiliary vector, on the BSDs and Solaris/Illumos it is
+determined by checking the issetugid syscall, and on AIX it is determined by
+checking if the uid/gid match the effective uid/gid.
+
+When the runtime determines the binary is setuid/setgid-like, it does three main
+things:
+ - The standard input/output file descriptors (0, 1, 2) are checked to be open.
+ If any of them are closed, they are opened pointing at /dev/null.
+ - The value of the GOTRACEBACK environment variable is set to 'none'.
+ - When a signal is received that terminates the program, or the program
+ encounters an unrecoverable panic that would otherwise override the value
+ of GOTRACEBACK, the goroutine stack, registers, and other memory related
+ information are omitted.
*/
package runtime
diff --git a/src/runtime/os2_aix.go b/src/runtime/os2_aix.go
index 2efc56554c..0e39b85c42 100644
--- a/src/runtime/os2_aix.go
+++ b/src/runtime/os2_aix.go
@@ -55,6 +55,10 @@ var (
//go:cgo_import_dynamic libc_sysconf sysconf "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_usleep usleep "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_write write "libc.a/shr_64.o"
+//go:cgo_import_dynamic libc_getuid getuid "libc.a/shr_64.o"
+//go:cgo_import_dynamic libc_geteuid geteuid "libc.a/shr_64.o"
+//go:cgo_import_dynamic libc_getgid getgid "libc.a/shr_64.o"
+//go:cgo_import_dynamic libc_getegid getegid "libc.a/shr_64.o"
//go:cgo_import_dynamic libpthread___pth_init __pth_init "libpthread.a/shr_xpg5_64.o"
//go:cgo_import_dynamic libpthread_attr_destroy pthread_attr_destroy "libpthread.a/shr_xpg5_64.o"
@@ -95,6 +99,10 @@ var (
//go:linkname libc_sysconf libc_sysconf
//go:linkname libc_usleep libc_usleep
//go:linkname libc_write libc_write
+//go:linkname libc_getuid libc_getuid
+//go:linkname libc_geteuid libc_geteuid
+//go:linkname libc_getgid libc_getgid
+//go:linkname libc_getegid libc_getegid
//go:linkname libpthread___pth_init libpthread___pth_init
//go:linkname libpthread_attr_destroy libpthread_attr_destroy
@@ -137,6 +145,10 @@ var (
libc_sysconf,
libc_usleep,
libc_write,
+ libc_getuid,
+ libc_geteuid,
+ libc_getgid,
+ libc_getegid,
//libpthread
libpthread___pth_init,
libpthread_attr_destroy,
diff --git a/src/runtime/os_aix.go b/src/runtime/os_aix.go
index 7845de1470..ad96ac36f8 100644
--- a/src/runtime/os_aix.go
+++ b/src/runtime/os_aix.go
@@ -378,3 +378,43 @@ const sigPerThreadSyscall = 1 << 31
func runPerThreadSyscall() {
throw("runPerThreadSyscall only valid on linux")
}
+
+//go:nosplit
+func getuid() int32 {
+ r, errno := syscall0(&libc_getuid)
+ if errno != 0 {
+ print("getuid failed ", errno)
+ throw("getuid")
+ }
+ return int32(r)
+}
+
+//go:nosplit
+func geteuid() int32 {
+ r, errno := syscall0(&libc_geteuid)
+ if errno != 0 {
+ print("geteuid failed ", errno)
+ throw("geteuid")
+ }
+ return int32(r)
+}
+
+//go:nosplit
+func getgid() int32 {
+ r, errno := syscall0(&libc_getgid)
+ if errno != 0 {
+ print("getgid failed ", errno)
+ throw("getgid")
+ }
+ return int32(r)
+}
+
+//go:nosplit
+func getegid() int32 {
+ r, errno := syscall0(&libc_getegid)
+ if errno != 0 {
+ print("getegid failed ", errno)
+ throw("getegid")
+ }
+ return int32(r)
+}
diff --git a/src/runtime/os_dragonfly.go b/src/runtime/os_dragonfly.go
index 460dc88abe..c14d904e7f 100644
--- a/src/runtime/os_dragonfly.go
+++ b/src/runtime/os_dragonfly.go
@@ -66,6 +66,8 @@ func pipe2(flags int32) (r, w int32, errno int32)
func fcntl(fd, cmd, arg int32) (ret int32, errno int32)
func closeonexec(fd int32)
+func issetugid() int32
+
// From DragonFly's <sys/sysctl.h>
const (
_CTL_HW = 6
diff --git a/src/runtime/os_freebsd.go b/src/runtime/os_freebsd.go
index e77507b70d..a7288c5653 100644
--- a/src/runtime/os_freebsd.go
+++ b/src/runtime/os_freebsd.go
@@ -51,6 +51,8 @@ func pipe2(flags int32) (r, w int32, errno int32)
func fcntl(fd, cmd, arg int32) (ret int32, errno int32)
func closeonexec(fd int32)
+func issetugid() int32
+
// From FreeBSD's <sys/sysctl.h>
const (
_CTL_HW = 6
diff --git a/src/runtime/os_linux.go b/src/runtime/os_linux.go
index e2581fba65..26db4a0cd9 100644
--- a/src/runtime/os_linux.go
+++ b/src/runtime/os_linux.go
@@ -216,6 +216,7 @@ const (
_AT_NULL = 0 // End of vector
_AT_PAGESZ = 6 // System physical page size
_AT_HWCAP = 16 // hardware capability bit vector
+ _AT_SECURE = 23 // secure mode boolean
_AT_RANDOM = 25 // introduced in 2.6.29
_AT_HWCAP2 = 26 // hardware capability bit vector 2
)
@@ -285,6 +286,9 @@ func sysargs(argc int32, argv **byte) {
// the ELF AT_RANDOM auxiliary vector.
var startupRandomData []byte
+// secureMode holds the value of AT_SECURE passed in the auxiliary vector.
+var secureMode bool
+
func sysauxv(auxv []uintptr) int {
var i int
for ; auxv[i] != _AT_NULL; i += 2 {
@@ -297,6 +301,9 @@ func sysauxv(auxv []uintptr) int {
case _AT_PAGESZ:
physPageSize = val
+
+ case _AT_SECURE:
+ secureMode = val == 1
}
archauxv(tag, val)
diff --git a/src/runtime/os_netbsd.go b/src/runtime/os_netbsd.go
index d77855ec0c..d3ae1f810a 100644
--- a/src/runtime/os_netbsd.go
+++ b/src/runtime/os_netbsd.go
@@ -82,6 +82,8 @@ func pipe2(flags int32) (r, w int32, errno int32)
func fcntl(fd, cmd, arg int32) (ret int32, errno int32)
func closeonexec(fd int32)
+func issetugid() int32
+
const (
_ESRCH = 3
_ETIMEDOUT = 60
diff --git a/src/runtime/os_openbsd_syscall2.go b/src/runtime/os_openbsd_syscall2.go
index dcf3b09e67..8e4859389d 100644
--- a/src/runtime/os_openbsd_syscall2.go
+++ b/src/runtime/os_openbsd_syscall2.go
@@ -99,3 +99,5 @@ func fcntl(fd, cmd, arg int32) (ret int32, errno int32)
func closeonexec(fd int32)
func walltime() (sec int64, nsec int32)
+
+func issetugid() int32
diff --git a/src/runtime/os_solaris.go b/src/runtime/os_solaris.go
index 9e13c2903a..47edda1597 100644
--- a/src/runtime/os_solaris.go
+++ b/src/runtime/os_solaris.go
@@ -267,3 +267,7 @@ func sysvicall6(fn *libcFunc, a1, a2, a3, a4, a5, a6 uintptr) uintptr {
}
return libcall.r1
}
+
+func issetugid() int32 {
+ return int32(sysvicall0(&libc_issetugid))
+}
diff --git a/src/runtime/panic.go b/src/runtime/panic.go
index 26618db7ce..6a6437de24 100644
--- a/src/runtime/panic.go
+++ b/src/runtime/panic.go
@@ -1118,6 +1118,10 @@ func fatalthrow(t throwType) {
// Switch to the system stack to avoid any stack growth, which may make
// things worse if the runtime is in a bad state.
systemstack(func() {
+ if isSecureMode() {
+ exit(2)
+ }
+
startpanic_m()
if dopanic_m(gp, pc, sp) {
diff --git a/src/runtime/proc.go b/src/runtime/proc.go
index 554a60d747..7cd6898a93 100644
--- a/src/runtime/proc.go
+++ b/src/runtime/proc.go
@@ -726,6 +726,7 @@ func schedinit() {
goargs()
goenvs()
+ secure()
parsedebugvars()
gcinit()
diff --git a/src/runtime/security_aix.go b/src/runtime/security_aix.go
new file mode 100644
index 0000000000..c11b9c3f01
--- /dev/null
+++ b/src/runtime/security_aix.go
@@ -0,0 +1,17 @@
+// 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 runtime
+
+// secureMode is only ever mutated in schedinit, so we don't need to worry about
+// synchronization primitives.
+var secureMode bool
+
+func initSecureMode() {
+ secureMode = !(getuid() == geteuid() && getgid() == getegid())
+}
+
+func isSecureMode() bool {
+ return secureMode
+}
diff --git a/src/runtime/security_issetugid.go b/src/runtime/security_issetugid.go
new file mode 100644
index 0000000000..5048632c3a
--- /dev/null
+++ b/src/runtime/security_issetugid.go
@@ -0,0 +1,19 @@
+// 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 darwin || dragonfly || freebsd || illumos || netbsd || openbsd || solaris
+
+package runtime
+
+// secureMode is only ever mutated in schedinit, so we don't need to worry about
+// synchronization primitives.
+var secureMode bool
+
+func initSecureMode() {
+ secureMode = issetugid() == 1
+}
+
+func isSecureMode() bool {
+ return secureMode
+}
diff --git a/src/runtime/security_linux.go b/src/runtime/security_linux.go
new file mode 100644
index 0000000000..181f3a184e
--- /dev/null
+++ b/src/runtime/security_linux.go
@@ -0,0 +1,15 @@
+// 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 runtime
+
+import _ "unsafe"
+
+func initSecureMode() {
+ // We have already initialized the secureMode bool in sysauxv.
+}
+
+func isSecureMode() bool {
+ return secureMode
+}
diff --git a/src/runtime/security_nonunix.go b/src/runtime/security_nonunix.go
new file mode 100644
index 0000000000..fc9571cfcf
--- /dev/null
+++ b/src/runtime/security_nonunix.go
@@ -0,0 +1,13 @@
+// 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 isSecureMode() bool {
+ return false
+}
+
+func secure() {}
diff --git a/src/runtime/security_test.go b/src/runtime/security_test.go
new file mode 100644
index 0000000000..1d304113d6
--- /dev/null
+++ b/src/runtime/security_test.go
@@ -0,0 +1,143 @@
+// 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 (
+ "bytes"
+ "context"
+ "fmt"
+ "internal/testenv"
+ "io"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "testing"
+ "time"
+)
+
+func privesc(command string, args ...string) error {
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
+ defer cancel()
+ var cmd *exec.Cmd
+ if runtime.GOOS == "darwin" {
+ cmd = exec.CommandContext(ctx, "sudo", append([]string{"-n", command}, args...)...)
+ } else {
+ cmd = exec.CommandContext(ctx, "su", highPrivUser, "-c", fmt.Sprintf("%s %s", command, strings.Join(args, " ")))
+ }
+ _, err := cmd.CombinedOutput()
+ return err
+}
+
+const highPrivUser = "root"
+
+func setSetuid(t *testing.T, user, bin string) {
+ t.Helper()
+ // We escalate privileges here even if we are root, because for some reason on some builders
+ // (at least freebsd-amd64-13_0) the default PATH doesn't include /usr/sbin, which is where
+ // chown lives, but using 'su root -c' gives us the correct PATH.
+
+ // buildTestProg uses os.MkdirTemp which creates directories with 0700, which prevents
+ // setuid binaries from executing because of the missing g+rx, so we need to set the parent
+ // directory to better permissions before anything else. We created this directory, so we
+ // shouldn't need to do any privilege trickery.
+ if err := privesc("chmod", "0777", filepath.Dir(bin)); err != nil {
+ t.Skipf("unable to set permissions on %q, likely no passwordless sudo/su: %s", filepath.Dir(bin), err)
+ }
+
+ if err := privesc("chown", user, bin); err != nil {
+ t.Skipf("unable to set permissions on test binary, likely no passwordless sudo/su: %s", err)
+ }
+ if err := privesc("chmod", "u+s", bin); err != nil {
+ t.Skipf("unable to set permissions on test binary, likely no passwordless sudo/su: %s", err)
+ }
+}
+
+func TestSUID(t *testing.T) {
+ // This test is relatively simple, we build a test program which opens a
+ // file passed via the TEST_OUTPUT envvar, prints the value of the
+ // GOTRACEBACK envvar to stdout, and prints "hello" to stderr. We then chown
+ // the program to "nobody" and set u+s on it. We execute the program, only
+ // passing it two files, for stdin and stdout, and passing
+ // GOTRACEBACK=system in the env.
+ //
+ // We expect that the program will trigger the SUID protections, resetting
+ // the value of GOTRACEBACK, and opening the missing stderr descriptor, such
+ // that the program prints "GOTRACEBACK=none" to stdout, and nothing gets
+ // written to the file pointed at by TEST_OUTPUT.
+
+ if *flagQuick {
+ t.Skip("-quick")
+ }
+
+ testenv.MustHaveGoBuild(t)
+
+ helloBin, err := buildTestProg(t, "testsuid")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ f, err := os.CreateTemp(t.TempDir(), "suid-output")
+ if err != nil {
+ t.Fatal(err)
+ }
+ tempfilePath := f.Name()
+ f.Close()
+
+ lowPrivUser := "nobody"
+ setSetuid(t, lowPrivUser, helloBin)
+
+ b := bytes.NewBuffer(nil)
+ pr, pw, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ proc, err := os.StartProcess(helloBin, []string{helloBin}, &os.ProcAttr{
+ Env: []string{"GOTRACEBACK=system", "TEST_OUTPUT=" + tempfilePath},
+ Files: []*os.File{os.Stdin, pw},
+ })
+ if err != nil {
+ if os.IsPermission(err) {
+ t.Skip("don't have execute permission on setuid binary, possibly directory permission issue?")
+ }
+ t.Fatal(err)
+ }
+ done := make(chan bool, 1)
+ go func() {
+ io.Copy(b, pr)
+ pr.Close()
+ done <- true
+ }()
+ ps, err := proc.Wait()
+ if err != nil {
+ t.Fatal(err)
+ }
+ pw.Close()
+ <-done
+ output := b.String()
+
+ if ps.ExitCode() == 99 {
+ t.Skip("binary wasn't setuid (uid == euid), unable to effectively test")
+ }
+
+ expected := "GOTRACEBACK=none\n"
+ if output != expected {
+ t.Errorf("unexpected output, got: %q, want %q", output, expected)
+ }
+
+ fc, err := os.ReadFile(tempfilePath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if string(fc) != "" {
+ t.Errorf("unexpected file content, got: %q", string(fc))
+ }
+
+ // TODO: check the registers aren't leaked?
+}
diff --git a/src/runtime/security_unix.go b/src/runtime/security_unix.go
new file mode 100644
index 0000000000..16fc87eece
--- /dev/null
+++ b/src/runtime/security_unix.go
@@ -0,0 +1,72 @@
+// 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 secure() {
+ initSecureMode()
+
+ if !isSecureMode() {
+ 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)
+ //
+ // 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()
+}
+
+func secureEnv() {
+ var hasTraceback bool
+ for i := 0; i < len(envs); i++ {
+ if hasPrefix(envs[i], "GOTRACEBACK=") {
+ hasTraceback = true
+ envs[i] = "GOTRACEBACK=none"
+ }
+ }
+ if !hasTraceback {
+ 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/signal_unix.go b/src/runtime/signal_unix.go
index c401fc1b7a..c1abe62cb3 100644
--- a/src/runtime/signal_unix.go
+++ b/src/runtime/signal_unix.go
@@ -725,6 +725,10 @@ func sighandler(sig uint32, info *siginfo, ctxt unsafe.Pointer, gp *g) {
print("Signal ", sig, "\n")
}
+ if isSecureMode() {
+ exit(2)
+ }
+
print("PC=", hex(c.sigpc()), " m=", mp.id, " sigcode=", c.sigcode(), "\n")
if mp.incgo && gp == mp.g0 && mp.curg != nil {
print("signal arrived during cgo execution\n")
diff --git a/src/runtime/sys_darwin.go b/src/runtime/sys_darwin.go
index a56a9373b9..64d7523f24 100644
--- a/src/runtime/sys_darwin.go
+++ b/src/runtime/sys_darwin.go
@@ -549,6 +549,11 @@ func setNonblock(fd int32) {
}
}
+func issetugid() int32 {
+ return libcCall(unsafe.Pointer(abi.FuncPCABI0(issetugid_trampoline)), nil)
+}
+func issetugid_trampoline()
+
// Tell the linker that the libc_* functions are to be found
// in a system library, with the libc_ prefix missing.
@@ -599,3 +604,5 @@ func setNonblock(fd int32) {
//go:cgo_import_dynamic libc_notify_is_valid_token notify_is_valid_token "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic libc_xpc_date_create_from_current xpc_date_create_from_current "/usr/lib/libSystem.B.dylib"
+
+//go:cgo_import_dynamic libc_issetugid issetugid "/usr/lib/libSystem.B.dylib"
diff --git a/src/runtime/sys_darwin_amd64.s b/src/runtime/sys_darwin_amd64.s
index 2a2aeff77d..de7ecdfc95 100644
--- a/src/runtime/sys_darwin_amd64.s
+++ b/src/runtime/sys_darwin_amd64.s
@@ -943,3 +943,10 @@ TEXT runtime·syscall_x509(SB),NOSPLIT,$0
MOVQ BP, SP
POPQ BP
RET
+
+TEXT runtime·issetugid_trampoline(SB),NOSPLIT,$0
+ PUSHQ BP
+ MOVQ SP, BP
+ CALL libc_issetugid(SB)
+ POPQ BP
+ RET
diff --git a/src/runtime/sys_darwin_arm64.s b/src/runtime/sys_darwin_arm64.s
index e1c61fae55..dc6caf873b 100644
--- a/src/runtime/sys_darwin_arm64.s
+++ b/src/runtime/sys_darwin_arm64.s
@@ -763,3 +763,7 @@ TEXT runtime·syscall_x509(SB),NOSPLIT,$0
ADD $16, RSP
MOVD R0, 56(R2) // save r1
RET
+
+TEXT runtime·issetugid_trampoline(SB),NOSPLIT,$0
+ BL libc_issetugid(SB)
+ RET
diff --git a/src/runtime/sys_dragonfly_amd64.s b/src/runtime/sys_dragonfly_amd64.s
index 958d712cd3..08f99ca963 100644
--- a/src/runtime/sys_dragonfly_amd64.s
+++ b/src/runtime/sys_dragonfly_amd64.s
@@ -411,3 +411,13 @@ TEXT runtime·closeonexec(SB),NOSPLIT,$0
MOVL $92, AX // fcntl
SYSCALL
RET
+
+// func issetugid() int32
+TEXT runtime·issetugid(SB),NOSPLIT,$0
+ MOVQ $0, DI
+ MOVQ $0, SI
+ MOVQ $0, DX
+ MOVL $253, AX
+ SYSCALL
+ MOVL AX, ret+0(FP)
+ RET
diff --git a/src/runtime/sys_freebsd_386.s b/src/runtime/sys_freebsd_386.s
index a5a668cb70..df0c073906 100644
--- a/src/runtime/sys_freebsd_386.s
+++ b/src/runtime/sys_freebsd_386.s
@@ -31,6 +31,7 @@
#define SYS___sysctl 202
#define SYS_clock_gettime 232
#define SYS_nanosleep 240
+#define SYS_issetugid 253
#define SYS_sched_yield 331
#define SYS_sigprocmask 340
#define SYS_kqueue 362
@@ -487,3 +488,10 @@ TEXT runtime·cpuset_getaffinity(SB), NOSPLIT, $0-28
RET
GLOBL runtime·tlsoffset(SB),NOPTR,$4
+
+// func issetugid() int32
+TEXT runtime·issetugid(SB),NOSPLIT,$0
+ MOVL $SYS_issetugid, AX
+ INT $0x80
+ MOVL AX, ret+0(FP)
+ RET
diff --git a/src/runtime/sys_freebsd_amd64.s b/src/runtime/sys_freebsd_amd64.s
index bd3c60ec19..95bf07e4f0 100644
--- a/src/runtime/sys_freebsd_amd64.s
+++ b/src/runtime/sys_freebsd_amd64.s
@@ -33,6 +33,7 @@
#define SYS___sysctl 202
#define SYS_clock_gettime 232
#define SYS_nanosleep 240
+#define SYS_issetugid 253
#define SYS_sched_yield 331
#define SYS_sigprocmask 340
#define SYS_kqueue 362
@@ -588,3 +589,13 @@ TEXT runtime·cpuset_getaffinity(SB), NOSPLIT, $0-44
NEGQ AX
MOVL AX, ret+40(FP)
RET
+
+// func issetugid() int32
+TEXT runtime·issetugid(SB),NOSPLIT,$0
+ MOVQ $0, DI
+ MOVQ $0, SI
+ MOVQ $0, DX
+ MOVL $SYS_issetugid, AX
+ SYSCALL
+ MOVL AX, ret+0(FP)
+ RET
diff --git a/src/runtime/sys_freebsd_arm.s b/src/runtime/sys_freebsd_arm.s
index 9b09d9d349..bd2e7058aa 100644
--- a/src/runtime/sys_freebsd_arm.s
+++ b/src/runtime/sys_freebsd_arm.s
@@ -27,6 +27,7 @@
#define SYS_fcntl (SYS_BASE + 92)
#define SYS___sysctl (SYS_BASE + 202)
#define SYS_nanosleep (SYS_BASE + 240)
+#define SYS_issetugid (SYS_BASE + 253)
#define SYS_clock_gettime (SYS_BASE + 232)
#define SYS_sched_yield (SYS_BASE + 331)
#define SYS_sigprocmask (SYS_BASE + 340)
@@ -455,3 +456,10 @@ TEXT runtime·getCntxct(SB),NOSPLIT|NOFRAME,$0-8
MOVW R0, ret+4(FP)
RET
+
+// func issetugid() int32
+TEXT runtime·issetugid(SB),NOSPLIT,$0
+ MOVW $SYS_issetugid, R7
+ SWI $0
+ MOVW R0, ret+0(FP)
+ RET
diff --git a/src/runtime/sys_freebsd_arm64.s b/src/runtime/sys_freebsd_arm64.s
index 36c106088e..fe43062270 100644
--- a/src/runtime/sys_freebsd_arm64.s
+++ b/src/runtime/sys_freebsd_arm64.s
@@ -34,6 +34,7 @@
#define SYS_fcntl 92
#define SYS___sysctl 202
#define SYS_nanosleep 240
+#define SYS_issetugid 253
#define SYS_clock_gettime 232
#define SYS_sched_yield 331
#define SYS_sigprocmask 340
@@ -485,3 +486,10 @@ TEXT runtime·getCntxct(SB),NOSPLIT,$0
MOVW R0, ret+8(FP)
RET
+
+// func issetugid() int32
+TEXT runtime·issetugid(SB),NOSPLIT|NOFRAME,$0
+ MOVD $SYS_issetugid, R8
+ SVC
+ MOVW R0, ret+0(FP)
+ RET
diff --git a/src/runtime/sys_freebsd_riscv64.s b/src/runtime/sys_freebsd_riscv64.s
index 58173c2cb6..4f581f569b 100644
--- a/src/runtime/sys_freebsd_riscv64.s
+++ b/src/runtime/sys_freebsd_riscv64.s
@@ -33,6 +33,7 @@
#define SYS_fcntl 92
#define SYS___sysctl 202
#define SYS_nanosleep 240
+#define SYS_issetugid 253
#define SYS_clock_gettime 232
#define SYS_sched_yield 331
#define SYS_sigprocmask 340
@@ -451,3 +452,11 @@ TEXT runtime·getCntxct(SB),NOSPLIT|NOFRAME,$0
RDTIME A0
MOVW A0, ret+0(FP)
RET
+
+// func issetugid() int32
+TEXT runtime·issetugid(SB),NOSPLIT|NOFRAME,$0
+ MOV $SYS_issetugid, T0
+ ECALL
+ MOVW A0, ret+0(FP)
+ RET
+
diff --git a/src/runtime/sys_netbsd_386.s b/src/runtime/sys_netbsd_386.s
index e649fb13cb..67a04d7a0d 100644
--- a/src/runtime/sys_netbsd_386.s
+++ b/src/runtime/sys_netbsd_386.s
@@ -29,6 +29,7 @@
#define SYS___sysctl 202
#define SYS___sigaltstack14 281
#define SYS___sigprocmask14 293
+#define SYS_issetugid 305
#define SYS_getcontext 307
#define SYS_setcontext 308
#define SYS__lwp_create 309
@@ -482,3 +483,10 @@ TEXT runtime·closeonexec(SB),NOSPLIT,$32
JAE 2(PC)
NEGL AX
RET
+
+// func issetugid() int32
+TEXT runtime·issetugid(SB),NOSPLIT,$0
+ MOVL $SYS_issetugid, AX
+ INT $0x80
+ MOVL AX, ret+0(FP)
+ RET
diff --git a/src/runtime/sys_netbsd_amd64.s b/src/runtime/sys_netbsd_amd64.s
index 2c2d97c105..24b3041006 100644
--- a/src/runtime/sys_netbsd_amd64.s
+++ b/src/runtime/sys_netbsd_amd64.s
@@ -30,6 +30,7 @@
#define SYS___sysctl 202
#define SYS___sigaltstack14 281
#define SYS___sigprocmask14 293
+#define SYS_issetugid 305
#define SYS_getcontext 307
#define SYS_setcontext 308
#define SYS__lwp_create 309
@@ -458,3 +459,13 @@ TEXT runtime·closeonexec(SB),NOSPLIT,$0
MOVL $SYS_fcntl, AX
SYSCALL
RET
+
+// func issetugid() int32
+TEXT runtime·issetugid(SB),NOSPLIT,$0
+ MOVQ $0, DI
+ MOVQ $0, SI
+ MOVQ $0, DX
+ MOVL $SYS_issetugid, AX
+ SYSCALL
+ MOVL AX, ret+0(FP)
+ RET
diff --git a/src/runtime/sys_netbsd_arm.s b/src/runtime/sys_netbsd_arm.s
index 9d969592c4..263c3f0c8d 100644
--- a/src/runtime/sys_netbsd_arm.s
+++ b/src/runtime/sys_netbsd_arm.s
@@ -30,6 +30,7 @@
#define SYS___sysctl SWI_OS_NETBSD | 202
#define SYS___sigaltstack14 SWI_OS_NETBSD | 281
#define SYS___sigprocmask14 SWI_OS_NETBSD | 293
+#define SYS_issetugid SWI_OS_NETBSD | 305
#define SYS_getcontext SWI_OS_NETBSD | 307
#define SYS_setcontext SWI_OS_NETBSD | 308
#define SYS__lwp_create SWI_OS_NETBSD | 309
@@ -428,3 +429,9 @@ TEXT runtime·read_tls_fallback(SB),NOSPLIT|NOFRAME,$0
SWI $SYS__lwp_getprivate
MOVM.IAW (R13), [R1, R2, R3, R12]
RET
+
+// func issetugid() int32
+TEXT runtime·issetugid(SB),NOSPLIT,$0
+ SWI $SYS_issetugid
+ MOVW R0, ret+0(FP)
+ RET
diff --git a/src/runtime/sys_netbsd_arm64.s b/src/runtime/sys_netbsd_arm64.s
index 0cd9262750..c302adb5f9 100644
--- a/src/runtime/sys_netbsd_arm64.s
+++ b/src/runtime/sys_netbsd_arm64.s
@@ -33,6 +33,7 @@
#define SYS___sysctl 202
#define SYS___sigaltstack14 281
#define SYS___sigprocmask14 293
+#define SYS_issetugid 305
#define SYS_getcontext 307
#define SYS_setcontext 308
#define SYS__lwp_create 309
@@ -444,3 +445,9 @@ TEXT runtime·closeonexec(SB),NOSPLIT,$0
MOVW $FD_CLOEXEC, R2
SVC $SYS_fcntl
RET
+
+// func issetugid() int32
+TEXT runtime·issetugid(SB),NOSPLIT|NOFRAME,$0
+ SVC $SYS_issetugid
+ MOVW R0, ret+0(FP)
+ RET
diff --git a/src/runtime/sys_openbsd2.go b/src/runtime/sys_openbsd2.go
index 12a53dfc6d..c7efeaf6e8 100644
--- a/src/runtime/sys_openbsd2.go
+++ b/src/runtime/sys_openbsd2.go
@@ -263,6 +263,13 @@ func closeonexec(fd int32) {
fcntl(fd, _F_SETFD, _FD_CLOEXEC)
}
+//go:cgo_unsafe_args
+func issetugid() (ret int32) {
+ libcCall(unsafe.Pointer(abi.FuncPCABI0(issetugid_trampoline)), unsafe.Pointer(&ret))
+ return
+}
+func issetugid_trampoline()
+
// Tell the linker that the libc_* functions are to be found
// in a system library, with the libc_ prefix missing.
@@ -295,4 +302,6 @@ func closeonexec(fd int32) {
//go:cgo_import_dynamic libc_sigaction sigaction "libc.so"
//go:cgo_import_dynamic libc_sigaltstack sigaltstack "libc.so"
+//go:cgo_import_dynamic libc_issetugid issetugid "libc.so"
+
//go:cgo_import_dynamic _ _ "libc.so"
diff --git a/src/runtime/sys_openbsd_386.s b/src/runtime/sys_openbsd_386.s
index d0d9926ff9..6005c106f9 100644
--- a/src/runtime/sys_openbsd_386.s
+++ b/src/runtime/sys_openbsd_386.s
@@ -979,3 +979,12 @@ ok:
MOVL BP, SP
POPL BP
RET
+
+TEXT runtime·issetugid_trampoline(SB),NOSPLIT,$0
+ PUSHL BP
+ CALL libc_issetugid(SB)
+ NOP SP // tell vet SP changed - stop checking offsets
+ MOVL 8(SP), DX // pointer to return value
+ MOVL AX, 0(DX)
+ POPL BP
+ RET
diff --git a/src/runtime/sys_openbsd_amd64.s b/src/runtime/sys_openbsd_amd64.s
index 893a92e26f..1177bc1baa 100644
--- a/src/runtime/sys_openbsd_amd64.s
+++ b/src/runtime/sys_openbsd_amd64.s
@@ -784,3 +784,9 @@ ok:
MOVQ BP, SP
POPQ BP
RET
+
+TEXT runtime·issetugid_trampoline(SB),NOSPLIT,$0
+ MOVQ DI, BX // BX is caller-save
+ CALL libc_issetugid(SB)
+ MOVL AX, 0(BX) // return value
+ RET
diff --git a/src/runtime/sys_openbsd_arm.s b/src/runtime/sys_openbsd_arm.s
index fc04cf11a4..61b901bd52 100644
--- a/src/runtime/sys_openbsd_arm.s
+++ b/src/runtime/sys_openbsd_arm.s
@@ -816,3 +816,12 @@ ok:
MOVW $0, R0 // no error (it's ignored anyway)
MOVW R9, R13
RET
+
+TEXT runtime·issetugid_trampoline(SB),NOSPLIT,$0
+ MOVW R13, R9
+ MOVW R0, R8
+ BIC $0x7, R13 // align for ELF ABI
+ BL libc_issetugid(SB)
+ MOVW R0, 0(R8)
+ MOVW R9, R13
+ RET
diff --git a/src/runtime/sys_openbsd_arm64.s b/src/runtime/sys_openbsd_arm64.s
index c40889861a..563b88f7cc 100644
--- a/src/runtime/sys_openbsd_arm64.s
+++ b/src/runtime/sys_openbsd_arm64.s
@@ -649,3 +649,9 @@ TEXT runtime·syscall10X(SB),NOSPLIT,$0
ok:
RET
+
+TEXT runtime·issetugid_trampoline(SB),NOSPLIT,$0
+ MOVD R0, R19 // pointer to args
+ CALL libc_issetugid(SB)
+ MOVW R0, 0(R19) // return value
+ RET
diff --git a/src/runtime/sys_openbsd_mips64.s b/src/runtime/sys_openbsd_mips64.s
index 9238e7d0b0..0a45a07323 100644
--- a/src/runtime/sys_openbsd_mips64.s
+++ b/src/runtime/sys_openbsd_mips64.s
@@ -388,3 +388,10 @@ TEXT runtime·closeonexec(SB),NOSPLIT,$0
MOVV $92, R2 // sys_fcntl
SYSCALL
RET
+
+// func issetugid() int32
+TEXT runtime·issetugid(SB),NOSPLIT,$0
+ MOVV $253, R2 // sys_issetugid
+ SYSCALL
+ MOVW R2, ret+0(FP)
+ RET
diff --git a/src/runtime/syscall2_solaris.go b/src/runtime/syscall2_solaris.go
index d464f284bc..10a4fa07ce 100644
--- a/src/runtime/syscall2_solaris.go
+++ b/src/runtime/syscall2_solaris.go
@@ -23,6 +23,7 @@ import _ "unsafe" // for go:linkname
//go:cgo_import_dynamic libc_setpgid setpgid "libc.so"
//go:cgo_import_dynamic libc_syscall syscall "libc.so"
//go:cgo_import_dynamic libc_wait4 wait4 "libc.so"
+//go:cgo_import_dynamic libc_issetugid issetugid "libc.so"
//go:linkname libc_chdir libc_chdir
//go:linkname libc_chroot libc_chroot
@@ -41,3 +42,4 @@ import _ "unsafe" // for go:linkname
//go:linkname libc_setpgid libc_setpgid
//go:linkname libc_syscall libc_syscall
//go:linkname libc_wait4 libc_wait4
+//go:linkname libc_issetugid libc_issetugid
diff --git a/src/runtime/syscall_solaris.go b/src/runtime/syscall_solaris.go
index 9faee9ec46..11b9c2aade 100644
--- a/src/runtime/syscall_solaris.go
+++ b/src/runtime/syscall_solaris.go
@@ -23,6 +23,7 @@ var (
libc_setuid,
libc_setpgid,
libc_syscall,
+ libc_issetugid,
libc_wait4 libcFunc
)
diff --git a/src/runtime/testdata/testsuid/main.go b/src/runtime/testdata/testsuid/main.go
new file mode 100644
index 0000000000..1949d2d666
--- /dev/null
+++ b/src/runtime/testdata/testsuid/main.go
@@ -0,0 +1,25 @@
+// 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"
+ "log"
+ "os"
+)
+
+func main() {
+ if os.Geteuid() == os.Getuid() {
+ os.Exit(99)
+ }
+
+ fmt.Fprintf(os.Stdout, "GOTRACEBACK=%s\n", os.Getenv("GOTRACEBACK"))
+ 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()
+ fmt.Fprintf(os.Stderr, "hello\n")
+}