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 | 7 +++++++ src/runtime/sys_freebsd_amd64.s | 10 ++++++++++ src/runtime/sys_freebsd_arm.s | 8 ++++++++ src/runtime/sys_freebsd_arm64.s | 8 ++++++++ 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 | 10 ++++++++++ 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 15c519d233502a65a94bf8f6c183d3e81a9ae807..f85b9cb114536028d8ffb7d75a13f6dc49fd60eb 100644 --- a/src/runtime/extern.go +++ b/src/runtime/extern.go @@ -200,6 +200,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 9ad1caa816187af2274ce942e20f8da7f8038e04..37bd32d7a1ae25fa025b04e7c82667b798c73c95 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 21f7cea908fc7cc643f593c84a767a28494c49db..d596a586ea5e9c439de618af3750b37f5a1dc785 100644 --- a/src/runtime/os_aix.go +++ b/src/runtime/os_aix.go @@ -387,3 +387,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 a73db29734f63e8962e951122846518d0206b27b..be4dc681c79500b0002dd6b8eb916817c7ddef7c 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 128e8e3b8b829f84d06dafa0d29898cdf3a5027a..4175dfcd962ccfb3886cf2ead348fd17dd0d74f3 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 3091a90d5928df527f82bac1603a6fb3f852ff6a..b5f459ada2ed7755c9ea649d41b190f2a7e6c4b2 100644 --- a/src/runtime/os_linux.go +++ b/src/runtime/os_linux.go @@ -211,6 +211,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 ) @@ -280,6 +281,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 { @@ -292,6 +296,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 8dbde23b2be4340bff7999ad17e01c871b990f7f..d6cd1a2067388d8994bc0007ddade44bef5a8de4 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 b56190abe4461abf02b40cdf45b1ba2b7811ce8a..b44183ee5ec92c4cdadc99f5538d2ff13b38757a 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 121f2022a4ef2587138a3f059fade138e806fb40..b402c39cacaddccdc94097676116b92a21b29f15 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -1120,6 +1120,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 08fe441a0cd033f16ac8e70756551741c3a0b58c..15fc0f8dc6e6ab329e7db8520b3b4686eff1bc3d 100644 --- a/src/runtime/proc.go +++ b/src/runtime/proc.go @@ -710,6 +710,7 @@ } 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 0be499b2e9a9a9c9946e87df13b19129f93a47d3..bb8acb1df5c437d622a9a38e590b17c1b1ee0406 100644 --- a/src/runtime/signal_unix.go +++ b/src/runtime/signal_unix.go @@ -723,6 +723,10 @@ } else { print("Signal ", sig, "\n") } + if isSecureMode() { + exit(2) + } + print("PC=", hex(c.sigpc()), " m=", _g_.m.id, " sigcode=", c.sigcode(), "\n") if _g_.m.incgo && gp == _g_.m.g0 && _g_.m.curg != nil { print("signal arrived during cgo execution\n") diff --git a/src/runtime/sys_darwin.go b/src/runtime/sys_darwin.go index 03a2294e5e211095a9dd6eaf239b999018c3def0..68686560561d40c946126450db750b3839fad6d8 100644 --- a/src/runtime/sys_darwin.go +++ b/src/runtime/sys_darwin.go @@ -496,6 +496,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. @@ -543,3 +548,5 @@ //go:cgo_import_dynamic libc_pthread_cond_init pthread_cond_init "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic libc_pthread_cond_wait pthread_cond_wait "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic libc_pthread_cond_timedwait_relative_np pthread_cond_timedwait_relative_np "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic libc_pthread_cond_signal pthread_cond_signal "/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 b9c2298d7795facf4bd0762740753bb983845895..1f42470fd7da11c3f6dca0db11fc78a34995870e 100644 --- a/src/runtime/sys_darwin_amd64.s +++ b/src/runtime/sys_darwin_amd64.s @@ -875,3 +875,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 474aedfdbfdc9ca1422e44bf99ac0cd5c68b4a32..d4a6dca1ab681350333715ae0819ba1052bbcb0b 100644 --- a/src/runtime/sys_darwin_arm64.s +++ b/src/runtime/sys_darwin_arm64.s @@ -700,3 +700,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 c99172dff74226590079639fa47e452d18ea9148..b30fe37083947ea3f816d4068f89c9d7f100e9f8 100644 --- a/src/runtime/sys_freebsd_386.s +++ b/src/runtime/sys_freebsd_386.s @@ -451,3 +451,10 @@ MOVL AX, ret+24(FP) RET GLOBL runtime·tlsoffset(SB),NOPTR,$4 + +// func issetugid() int32 +TEXT runtime·issetugid(SB),NOSPLIT,$0 + MOVL $253, 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 d769577ed4dced2ec7cc5699b9a793d3c13c0e39..15db8e4042c04bf6954552d4616a0ecbf1a16a9a 100644 --- a/src/runtime/sys_freebsd_amd64.s +++ b/src/runtime/sys_freebsd_amd64.s @@ -521,3 +521,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 $253, 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 d4a8a09bb857d3ec83e2ecd43063804042fcb7d4..bc4c36109e7dbcfeb1bd4015cef232b7b0a66a02 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 799be4bd5d72579936dd5ba52d9fa810f825ec35..2d68cd59b4c30f17826a4fe601e1a7b00d4eaa9e 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_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 4324049898f1e5cef549664e7cd4e6a572f5fe46..215860b1e3f4fd9bb064ff44d01be948c5e41f71 100644 --- a/src/runtime/sys_openbsd2.go +++ b/src/runtime/sys_openbsd2.go @@ -262,6 +262,14 @@ func closeonexec(fd int32) { fcntl(fd, _F_SETFD, _FD_CLOEXEC) } +//go:nosplit +//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. @@ -293,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 eded88d647581ec651ef3fbb43462dafdea59f24..b98ba519b47c835fdf79c61795473235ace6c75c 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") +}