src/runtime/extern.go | 19 +++++++++++++++++++ src/runtime/os2_aix.go | 12 ++++++++++++ src/runtime/os_aix.go | 40 ++++++++++++++++++++++++++++++++++++++++ src/runtime/os_dragonfly.go | 2 ++ src/runtime/os_freebsd.go | 2 ++ src/runtime/os_linux.go | 7 +++++++ src/runtime/os_netbsd.go | 2 ++ src/runtime/os_openbsd_syscall2.go | 2 ++ src/runtime/os_solaris.go | 4 ++++ src/runtime/panic.go | 4 ++++ src/runtime/proc.go | 1 + src/runtime/security_aix.go | 17 +++++++++++++++++ src/runtime/security_issetugid.go | 19 +++++++++++++++++++ src/runtime/security_linux.go | 15 +++++++++++++++ src/runtime/security_nonunix.go | 13 +++++++++++++ src/runtime/security_test.go | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/runtime/security_unix.go | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/runtime/signal_unix.go | 4 ++++ src/runtime/sys_darwin.go | 7 +++++++ src/runtime/sys_darwin_amd64.s | 7 +++++++ src/runtime/sys_darwin_arm64.s | 4 ++++ src/runtime/sys_dragonfly_amd64.s | 10 ++++++++++ src/runtime/sys_freebsd_386.s | 8 ++++++++ src/runtime/sys_freebsd_amd64.s | 11 +++++++++++ src/runtime/sys_freebsd_arm.s | 8 ++++++++ src/runtime/sys_freebsd_arm64.s | 8 ++++++++ src/runtime/sys_freebsd_riscv64.s | 9 +++++++++ src/runtime/sys_netbsd_386.s | 8 ++++++++ src/runtime/sys_netbsd_amd64.s | 11 +++++++++++ src/runtime/sys_netbsd_arm.s | 7 +++++++ src/runtime/sys_netbsd_arm64.s | 7 +++++++ src/runtime/sys_openbsd2.go | 9 +++++++++ src/runtime/sys_openbsd_386.s | 9 +++++++++ src/runtime/sys_openbsd_amd64.s | 6 ++++++ src/runtime/sys_openbsd_arm.s | 9 +++++++++ src/runtime/sys_openbsd_arm64.s | 6 ++++++ src/runtime/sys_openbsd_mips64.s | 7 +++++++ src/runtime/syscall2_solaris.go | 2 ++ src/runtime/syscall_solaris.go | 1 + src/runtime/testdata/testsuid/main.go | 25 +++++++++++++++++++++++++ diff --git a/src/runtime/extern.go b/src/runtime/extern.go index 6c41c626945a93d01000eafc71486f7713075628..afadc3d17ec341f267750eb02491f2a33d063b50 100644 --- a/src/runtime/extern.go +++ b/src/runtime/extern.go @@ -216,6 +216,25 @@ (see https://golang.org/cmd/go and https://golang.org/pkg/go/build). 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 2efc56554cddcceddbb418e91c5fc7a9d7df9289..0e39b85c42d459ceca1210fc5d7bbbea18431373 100644 --- a/src/runtime/os2_aix.go +++ b/src/runtime/os2_aix.go @@ -55,6 +55,10 @@ //go:cgo_import_dynamic libc_sigaltstack sigaltstack "libc.a/shr_64.o" //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 @@ //go:linkname libc_sigaltstack libc_sigaltstack //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 @@ libc_sigaltstack, 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 7845de1470776f38fc4cbd8213ff0a65276d3635..ad96ac36f8070bb88d4eb19d02c40eff86b31075 100644 --- a/src/runtime/os_aix.go +++ b/src/runtime/os_aix.go @@ -378,3 +378,43 @@ //go:nosplit 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 460dc88abec4467987656c04d1c191d889143130..c14d904e7f0280c86c5139368cdcade170d7396c 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 const ( _CTL_HW = 6 diff --git a/src/runtime/os_freebsd.go b/src/runtime/os_freebsd.go index e77507b70d1cc1d6b3f89fbaee29879d7da4a965..a7288c5653d8660da2a127c7eb96ac505a557005 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 const ( _CTL_HW = 6 diff --git a/src/runtime/os_linux.go b/src/runtime/os_linux.go index e2581fba6548b35c2f9cf58da1b679b802156afb..26db4a0cd944590820e976708e02aac964aeb9a2 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 @@ // startupRandomData holds random bytes initialized at startup. These come from // 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 @@ startupRandomData = (*[16]byte)(unsafe.Pointer(val))[:] 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 d77855ec0cdd80796dd15be8f99cb9867b4e6bd5..d3ae1f810aa10acda1274f3d92406ebfb2140409 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 dcf3b09e6780a86724e96aeb3d072c94cfceb320..8e4859389da2c16b55257d943d6a42b4ed9a5c25 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 9e13c2903afe37c5c49a1870111603a93b28f07a..47edda15978f97e6e53d223215bbf3f3bde20845 100644 --- a/src/runtime/os_solaris.go +++ b/src/runtime/os_solaris.go @@ -267,3 +267,7 @@ mp.libcallsp = 0 } return libcall.r1 } + +func issetugid() int32 { + return int32(sysvicall0(&libc_issetugid)) +} diff --git a/src/runtime/panic.go b/src/runtime/panic.go index 26618db7ceb470473bacdfa2d8969f63b258779f..6a6437de249964ec7a2b48d628621809400030ea 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -1118,6 +1118,10 @@ // 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 554a60d7473eb2a9fdd9411978847f08a879b681..7cd6898a9352a691b6dfaf0b3ccd1d0e6a2e80fe 100644 --- a/src/runtime/proc.go +++ b/src/runtime/proc.go @@ -726,6 +726,7 @@ initSigmask = gp.m.sigmask goargs() goenvs() + secure() parsedebugvars() gcinit() diff --git a/src/runtime/security_aix.go b/src/runtime/security_aix.go new file mode 100644 index 0000000000000000000000000000000000000000..c11b9c3f0169eb78ab17d8374d5263d058e6a869 --- /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 0000000000000000000000000000000000000000..5048632c3a6cf7b29be45c89b5e778ca1325f4e8 --- /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 0000000000000000000000000000000000000000..181f3a184e56fbcade8e94247ac91c07d9bc1cb3 --- /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 0000000000000000000000000000000000000000..fc9571cfcf5e0799936ca8d4eeab80630f9c94f9 --- /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 0000000000000000000000000000000000000000..1d304113d6abf73ae4b5807ffde3f1551f50098f --- /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 0000000000000000000000000000000000000000..16fc87eece5414436d15d3cec38dbd39867bccbc --- /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 c401fc1b7a572ba95f10c6ad5833c1799ac6bec0..c1abe62cb3ca30fc225873f2a471c0648ad4653a 100644 --- a/src/runtime/signal_unix.go +++ b/src/runtime/signal_unix.go @@ -725,6 +725,10 @@ } else { 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 a56a9373b95cbedbb3d0c376c0f3af923714e077..64d7523f247c8641c8886cdc74a2d66af5ab0a0d 100644 --- a/src/runtime/sys_darwin.go +++ b/src/runtime/sys_darwin.go @@ -549,6 +549,11 @@ fcntl(fd, _F_SETFL, flags|_O_NONBLOCK) } } +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 @@ //go:cgo_import_dynamic libc_pthread_cond_signal pthread_cond_signal "/usr/lib/libSystem.B.dylib" //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 2a2aeff77d19f437944da3316c608170ba1984ae..de7ecdfc950dc2335e35c51cc3bba7c995871f90 100644 --- a/src/runtime/sys_darwin_amd64.s +++ b/src/runtime/sys_darwin_amd64.s @@ -943,3 +943,10 @@ XORL AX, AX // no error (it's ignored anyway) 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 e1c61fae55ba105ef110dc67052ce0ec390b5ed1..dc6caf873b1d6a4fb2e6fe09f72cd74e2149f42c 100644 --- a/src/runtime/sys_darwin_arm64.s +++ b/src/runtime/sys_darwin_arm64.s @@ -763,3 +763,7 @@ MOVD (RSP), R2 // pop structure pointer 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 958d712cd3d707ff45ae3ddbcbe6e315b4ab3d79..08f99ca963d78b650ade6fb0ac25c253f5ce5639 100644 --- a/src/runtime/sys_dragonfly_amd64.s +++ b/src/runtime/sys_dragonfly_amd64.s @@ -411,3 +411,13 @@ MOVQ $1, DX // FD_CLOEXEC 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 a5a668cb7003c521d8a11109e7065d0b1c280eee..df0c0739067b68af7d429a6532e589af48ee76ff 100644 --- a/src/runtime/sys_freebsd_386.s +++ b/src/runtime/sys_freebsd_386.s @@ -31,6 +31,7 @@ #define SYS_sysarch 165 #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 @@ MOVL AX, ret+24(FP) 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 bd3c60ec19cc514c1be005fde8bb54e1e9b6247c..95bf07e4f0fbcf15366ef66d567942c8d4d11467 100644 --- a/src/runtime/sys_freebsd_amd64.s +++ b/src/runtime/sys_freebsd_amd64.s @@ -33,6 +33,7 @@ #define SYS_sysarch 165 #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 @@ JCC 2(PC) 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 9b09d9d349fec6dd1ebc52ff1b52218779efd994..bd2e7058aa08b39506b52aae27dd6d649cf7cfb3 100644 --- a/src/runtime/sys_freebsd_arm.s +++ b/src/runtime/sys_freebsd_arm.s @@ -27,6 +27,7 @@ #define SYS_setitimer (SYS_BASE + 83) #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 @@ WORD $0xec510f1e 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 36c106088e0d712a051b9a53dad8c8a79c572280..fe430622703a482350786365d80fe8bb9d4e6db4 100644 --- a/src/runtime/sys_freebsd_arm64.s +++ b/src/runtime/sys_freebsd_arm64.s @@ -34,6 +34,7 @@ #define SYS_setitimer 83 #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 @@ MRS CNTVCT_EL0, R0 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 58173c2cb64cd00ae3e72a6294c232959895cca3..4f581f569b7375c177020fba135bdd230ea03cbd 100644 --- a/src/runtime/sys_freebsd_riscv64.s +++ b/src/runtime/sys_freebsd_riscv64.s @@ -33,6 +33,7 @@ #define SYS_setitimer 83 #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 e649fb13cbbbb9f762883aa739dc0344b97a3604..67a04d7a0d7c5365e3ca8599231b0bbb5c1be267 100644 --- a/src/runtime/sys_netbsd_386.s +++ b/src/runtime/sys_netbsd_386.s @@ -29,6 +29,7 @@ #define SYS_mmap 197 #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 @@ INT $0x80 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 2c2d97c10545187f0379da518d7b4ccc6e97741d..24b30410061ceb00b8d17c9c843f6a931502e0e4 100644 --- a/src/runtime/sys_netbsd_amd64.s +++ b/src/runtime/sys_netbsd_amd64.s @@ -30,6 +30,7 @@ #define SYS_mmap 197 #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 @@ MOVQ $FD_CLOEXEC, DX 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 9d969592c47da949a1ac8809ea0887bc58853de4..263c3f0c8d1ef8c945faaf1bcdd9ca4c2aaf117c 100644 --- a/src/runtime/sys_netbsd_arm.s +++ b/src/runtime/sys_netbsd_arm.s @@ -30,6 +30,7 @@ #define SYS_mmap SWI_OS_NETBSD | 197 #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 @@ MOVM.WP [R1, R2, R3, R12], (R13) 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 0cd9262750f8fb6ac3d0ddb590cea2032144c5ed..c302adb5f9941c30ef5b677943ec73e19929f6ce 100644 --- a/src/runtime/sys_netbsd_arm64.s +++ b/src/runtime/sys_netbsd_arm64.s @@ -33,6 +33,7 @@ #define SYS_mmap 197 #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 @@ MOVW $F_SETFD, R1 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 12a53dfc6d95ecd2eee5f1768b604cdeb4d2d461..c7efeaf6e87fdab82a1c31dde78169cbbc1cd435 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. @@ -294,5 +301,7 @@ //go:cgo_import_dynamic libc_kevent kevent "libc.so" //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 d0d9926ff91ade3ef48b6031f99feb0ee7e5a559..6005c106f9c9a7ec3aa121749da5ccdfa996c3aa 100644 --- a/src/runtime/sys_openbsd_386.s +++ b/src/runtime/sys_openbsd_386.s @@ -979,3 +979,12 @@ MOVL $0, AX // no error (it's ignored anyway) 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 893a92e26f16733c0d360d9dac4127dc0f3b3de0..1177bc1baa22afa4df03beb4bd750b9267f95f00 100644 --- a/src/runtime/sys_openbsd_amd64.s +++ b/src/runtime/sys_openbsd_amd64.s @@ -784,3 +784,9 @@ XORL AX, AX // no error (it's ignored anyway) 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 fc04cf11a4c094cae570f187bbbab491379e75af..61b901bd5277a4d106303885a287fc8d7a059141 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 c40889861a8ad7dd17aa6fb659881e8db2bad7e4..563b88f7cc6bf0f26f5e12c24e2dbb44379d37c6 100644 --- a/src/runtime/sys_openbsd_arm64.s +++ b/src/runtime/sys_openbsd_arm64.s @@ -649,3 +649,9 @@ MOVD R0, (13*8)(R19) // err 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 9238e7d0b0efa0c6b96238085779760b31f38d41..0a45a07323d4d31a84c10ef7e700cc15bec7043c 100644 --- a/src/runtime/sys_openbsd_mips64.s +++ b/src/runtime/sys_openbsd_mips64.s @@ -388,3 +388,10 @@ MOVV $1, R6 // arg 3 - arg (FD_CLOEXEC) 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 d464f284bcb2f42de47286b65537bbddd30349fc..10a4fa07cef42746aae04456cdbc13bb3022dac9 100644 --- a/src/runtime/syscall2_solaris.go +++ b/src/runtime/syscall2_solaris.go @@ -23,6 +23,7 @@ //go:cgo_import_dynamic libc_setuid setuid "libc.so" //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 @@ //go:linkname libc_setuid libc_setuid //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 9faee9ec468952a506253e2f3bb57c9bce602433..11b9c2aadec84d92c5b42a7ee699dfe221e87cbd 100644 --- a/src/runtime/syscall_solaris.go +++ b/src/runtime/syscall_solaris.go @@ -23,6 +23,7 @@ libc_setsid, 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 0000000000000000000000000000000000000000..1949d2d6662930490a829002f9ec44b47601eb02 --- /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") +}