From 192832317100ea3e708d15c7ddcc0144ac75a1b4 Mon Sep 17 00:00:00 2001 From: Michael Pratt Date: Tue, 8 Feb 2022 16:45:14 -0500 Subject: [PATCH 1/4] [Backport] runtime: implement SUID/SGID protections Offering: Cloud Core Network CVE: CVE-2023-29403 Reference: https://go-review.googlesource.com/c/go/+/501228 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. Note: When the runtime determines the binary is setuid/setgid-like and a signal is received that terminates the program, set inFatal to true. The buildtag unix was only introduced in go 1.19, so you need to change //go:build unix to the following: //go:build aix || android || darwin || dragonfly || freebsd || hurd || illumos || ios || linux || netbsd || openbsd || solaris Edited-by: tangxi t00586138 Updates #60272 Fixes #60517 Fixes CVE-2023-29403 Change-Id: I057fa7153d29cf26515e7f49fed86e4f8bedd0f0 Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1878434 Reviewed-by: Damien Neil Reviewed-by: Ian Lance Taylor Run-TryBot: Roland Shoemaker Reviewed-by: Russ Cox (cherry picked from commit 87065663ea6d89cd54f65a515d8f2ed0ef285c19) Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1902231 Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1904340 Reviewed-by: Michael Knyszek Reviewed-on: https://go-review.googlesource.com/c/go/+/501228 Auto-Submit: Michael Knyszek TryBot-Result: Gopher Robot Run-TryBot: David Chase Signed-off-by: Tang Xi tangxi6@huawei.com --- src/cmd/compile/internal/base/base.go | 3 +- src/go/build/deps_test.go | 1 + src/runtime/export_darwin_test.go | 7 - src/runtime/export_unix_test.go | 1 + src/runtime/extern.go | 19 +++ src/runtime/internal/syscall/asm_linux_386.s | 34 +++++ .../internal/syscall/asm_linux_amd64.s | 33 ++++ src/runtime/internal/syscall/asm_linux_arm.s | 32 ++++ .../internal/syscall/asm_linux_arm64.s | 29 ++++ .../internal/syscall/defs_linux_386.go | 7 + .../internal/syscall/defs_linux_amd64.go | 7 + .../internal/syscall/defs_linux_arm.go | 7 + .../internal/syscall/defs_linux_arm64.go | 7 + src/runtime/internal/syscall/syscall_linux.go | 12 ++ src/runtime/nbpipe_fcntl_libc_test.go | 19 --- src/runtime/nbpipe_fcntl_unix_test.go | 18 --- src/runtime/nbpipe_test.go | 26 ++-- 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 | 14 ++ src/runtime/os_netbsd.go | 2 + src/runtime/os_openbsd_syscall2.go | 2 + src/runtime/os_solaris.go | 4 + src/runtime/panic.go | 3 + src/runtime/proc.go | 1 + src/runtime/security_aix.go | 17 +++ src/runtime/security_issetugid.go | 20 +++ src/runtime/security_linux.go | 15 ++ src/runtime/security_nonunix.go | 14 ++ src/runtime/security_test.go | 144 ++++++++++++++++++ src/runtime/security_unix.go | 73 +++++++++ src/runtime/signal_unix.go | 4 + src/runtime/sys_darwin.go | 22 ++- 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 +++ 55 files changed, 756 insertions(+), 59 deletions(-) create mode 100644 src/runtime/internal/syscall/asm_linux_386.s create mode 100644 src/runtime/internal/syscall/asm_linux_amd64.s create mode 100644 src/runtime/internal/syscall/asm_linux_arm.s create mode 100644 src/runtime/internal/syscall/asm_linux_arm64.s create mode 100644 src/runtime/internal/syscall/defs_linux_386.go create mode 100644 src/runtime/internal/syscall/defs_linux_amd64.go create mode 100644 src/runtime/internal/syscall/defs_linux_arm.go create mode 100644 src/runtime/internal/syscall/defs_linux_arm64.go create mode 100644 src/runtime/internal/syscall/syscall_linux.go delete mode 100644 src/runtime/nbpipe_fcntl_libc_test.go delete mode 100644 src/runtime/nbpipe_fcntl_unix_test.go create mode 100644 src/runtime/security_aix.go create mode 100644 src/runtime/security_issetugid.go create mode 100644 src/runtime/security_linux.go create mode 100644 src/runtime/security_nonunix.go create mode 100644 src/runtime/security_test.go create mode 100644 src/runtime/security_unix.go create mode 100644 src/runtime/testdata/testsuid/main.go diff --git a/src/cmd/compile/internal/base/base.go b/src/cmd/compile/internal/base/base.go index 4c2516f60e..5fb718571f 100644 --- a/src/cmd/compile/internal/base/base.go +++ b/src/cmd/compile/internal/base/base.go @@ -62,8 +62,9 @@ func Compiling(pkgs []string) bool { // at best instrumentation would cause infinite recursion. var NoInstrumentPkgs = []string{ "runtime/internal/atomic", - "runtime/internal/sys", "runtime/internal/math", + "runtime/internal/sys", + "runtime/internal/syscall", "runtime", "runtime/race", "runtime/msan", diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index 45e2f25df7..610fba2da9 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -86,6 +86,7 @@ var depsRules = ` < internal/itoa < internal/unsafeheader < runtime/internal/sys + < runtime/internal/syscall < runtime/internal/atomic < runtime/internal/math < runtime diff --git a/src/runtime/export_darwin_test.go b/src/runtime/export_darwin_test.go index e9b6eb36da..034c52d603 100644 --- a/src/runtime/export_darwin_test.go +++ b/src/runtime/export_darwin_test.go @@ -4,10 +4,3 @@ package runtime -func Fcntl(fd, cmd, arg uintptr) (uintptr, uintptr) { - r := fcntl(int32(fd), int32(cmd), int32(arg)) - if r < 0 { - return ^uintptr(0), uintptr(-r) - } - return uintptr(r), 0 -} diff --git a/src/runtime/export_unix_test.go b/src/runtime/export_unix_test.go index 215e234286..b5841acc66 100644 --- a/src/runtime/export_unix_test.go +++ b/src/runtime/export_unix_test.go @@ -12,6 +12,7 @@ import "unsafe" var NonblockingPipe = nonblockingPipe var SetNonblock = setNonblock var Closeonexec = closeonexec +var Fcntl = fcntl func sigismember(mask *sigset, i int) bool { clear := *mask diff --git a/src/runtime/extern.go b/src/runtime/extern.go index 48e1e6603b..a4c6f86f57 100644 --- a/src/runtime/extern.go +++ b/src/runtime/extern.go @@ -183,6 +183,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/internal/syscall/asm_linux_386.s b/src/runtime/internal/syscall/asm_linux_386.s new file mode 100644 index 0000000000..15aae4d8bd --- /dev/null +++ b/src/runtime/internal/syscall/asm_linux_386.s @@ -0,0 +1,34 @@ +// Copyright 2022 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. + +#include "textflag.h" + +// See ../sys_linux_386.s for the reason why we always use int 0x80 +// instead of the glibc-specific "CALL 0x10(GS)". +#define INVOKE_SYSCALL INT $0x80 + +// func Syscall6(num, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, errno uintptr) +// +// Syscall # in AX, args in BX CX DX SI DI BP, return in AX +TEXT ·Syscall6(SB),NOSPLIT,$0-40 + MOVL num+0(FP), AX // syscall entry + MOVL a1+4(FP), BX + MOVL a2+8(FP), CX + MOVL a3+12(FP), DX + MOVL a4+16(FP), SI + MOVL a5+20(FP), DI + MOVL a6+24(FP), BP + INVOKE_SYSCALL + CMPL AX, $0xfffff001 + JLS ok + MOVL $-1, r1+28(FP) + MOVL $0, r2+32(FP) + NEGL AX + MOVL AX, errno+36(FP) + RET +ok: + MOVL AX, r1+28(FP) + MOVL DX, r2+32(FP) + MOVL $0, errno+36(FP) + RET diff --git a/src/runtime/internal/syscall/asm_linux_amd64.s b/src/runtime/internal/syscall/asm_linux_amd64.s new file mode 100644 index 0000000000..961d9bd640 --- /dev/null +++ b/src/runtime/internal/syscall/asm_linux_amd64.s @@ -0,0 +1,33 @@ +// Copyright 2022 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. + +#include "textflag.h" + +// func Syscall6(num, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, errno uintptr) +// +// Syscall # in AX, args in DI SI DX R10 R8 R9, return in AX DX. +// +// Note that this differs from "standard" ABI convention, which would pass 4th +// arg in CX, not R10. +TEXT ·Syscall6(SB),NOSPLIT,$0-80 + MOVQ num+0(FP), AX // syscall entry + MOVQ a1+8(FP), DI + MOVQ a2+16(FP), SI + MOVQ a3+24(FP), DX + MOVQ a4+32(FP), R10 + MOVQ a5+40(FP), R8 + MOVQ a6+48(FP), R9 + SYSCALL + CMPQ AX, $0xfffffffffffff001 + JLS ok + MOVQ $-1, r1+56(FP) + MOVQ $0, r2+64(FP) + NEGQ AX + MOVQ AX, errno+72(FP) + RET +ok: + MOVQ AX, r1+56(FP) + MOVQ DX, r2+64(FP) + MOVQ $0, errno+72(FP) + RET diff --git a/src/runtime/internal/syscall/asm_linux_arm.s b/src/runtime/internal/syscall/asm_linux_arm.s new file mode 100644 index 0000000000..dbf1826d94 --- /dev/null +++ b/src/runtime/internal/syscall/asm_linux_arm.s @@ -0,0 +1,32 @@ +// Copyright 2022 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. + +#include "textflag.h" + +// func Syscall6(num, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, errno uintptr) +TEXT ·Syscall6(SB),NOSPLIT,$0-40 + MOVW num+0(FP), R7 // syscall entry + MOVW a1+4(FP), R0 + MOVW a2+8(FP), R1 + MOVW a3+12(FP), R2 + MOVW a4+16(FP), R3 + MOVW a5+20(FP), R4 + MOVW a6+24(FP), R5 + SWI $0 + MOVW $0xfffff001, R6 + CMP R6, R0 + BLS ok + MOVW $-1, R1 + MOVW R1, r1+28(FP) + MOVW $0, R2 + MOVW R2, r2+32(FP) + RSB $0, R0, R0 + MOVW R0, errno+36(FP) + RET +ok: + MOVW R0, r1+28(FP) + MOVW R1, r2+32(FP) + MOVW $0, R0 + MOVW R0, errno+36(FP) + RET diff --git a/src/runtime/internal/syscall/asm_linux_arm64.s b/src/runtime/internal/syscall/asm_linux_arm64.s new file mode 100644 index 0000000000..83e862ff72 --- /dev/null +++ b/src/runtime/internal/syscall/asm_linux_arm64.s @@ -0,0 +1,29 @@ +// Copyright 2022 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. + +#include "textflag.h" + +// func Syscall6(num, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, errno uintptr) +TEXT ·Syscall6(SB),NOSPLIT,$0-80 + MOVD num+0(FP), R8 // syscall entry + MOVD a1+8(FP), R0 + MOVD a2+16(FP), R1 + MOVD a3+24(FP), R2 + MOVD a4+32(FP), R3 + MOVD a5+40(FP), R4 + MOVD a6+48(FP), R5 + SVC + CMN $4095, R0 + BCC ok + MOVD $-1, R4 + MOVD R4, r1+56(FP) + MOVD ZR, r2+64(FP) + NEG R0, R0 + MOVD R0, errno+72(FP) + RET +ok: + MOVD R0, r1+56(FP) + MOVD R1, r2+64(FP) + MOVD ZR, errno+72(FP) + RET diff --git a/src/runtime/internal/syscall/defs_linux_386.go b/src/runtime/internal/syscall/defs_linux_386.go new file mode 100644 index 0000000000..31d704e235 --- /dev/null +++ b/src/runtime/internal/syscall/defs_linux_386.go @@ -0,0 +1,7 @@ +// 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 syscall + +const SYS_FCNTL = 55 diff --git a/src/runtime/internal/syscall/defs_linux_amd64.go b/src/runtime/internal/syscall/defs_linux_amd64.go new file mode 100644 index 0000000000..2368eb03b4 --- /dev/null +++ b/src/runtime/internal/syscall/defs_linux_amd64.go @@ -0,0 +1,7 @@ +// 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 syscall + +const SYS_FCNTL = 72 diff --git a/src/runtime/internal/syscall/defs_linux_arm.go b/src/runtime/internal/syscall/defs_linux_arm.go new file mode 100644 index 0000000000..31d704e235 --- /dev/null +++ b/src/runtime/internal/syscall/defs_linux_arm.go @@ -0,0 +1,7 @@ +// 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 syscall + +const SYS_FCNTL = 55 diff --git a/src/runtime/internal/syscall/defs_linux_arm64.go b/src/runtime/internal/syscall/defs_linux_arm64.go new file mode 100644 index 0000000000..6292c90af5 --- /dev/null +++ b/src/runtime/internal/syscall/defs_linux_arm64.go @@ -0,0 +1,7 @@ +// 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 syscall + +const SYS_FCNTL = 25 diff --git a/src/runtime/internal/syscall/syscall_linux.go b/src/runtime/internal/syscall/syscall_linux.go new file mode 100644 index 0000000000..06d5f21e7c --- /dev/null +++ b/src/runtime/internal/syscall/syscall_linux.go @@ -0,0 +1,12 @@ +// Copyright 2022 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 syscall provides the syscall primitives required for the runtime. +package syscall + +// TODO(https://go.dev/issue/51087): This package is incomplete and currently +// only contains very minimal support for Linux. + +// Syscall6 calls system call number 'num' with arguments a1-6. +func Syscall6(num, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, errno uintptr) diff --git a/src/runtime/nbpipe_fcntl_libc_test.go b/src/runtime/nbpipe_fcntl_libc_test.go deleted file mode 100644 index 076a722eb7..0000000000 --- a/src/runtime/nbpipe_fcntl_libc_test.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2019 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 aix || darwin || solaris -// +build aix darwin solaris - -package runtime_test - -import ( - "runtime" - "syscall" -) - -// Call fcntl libc function rather than calling syscall. -func fcntl(fd uintptr, cmd int, arg uintptr) (uintptr, syscall.Errno) { - res, errno := runtime.Fcntl(fd, uintptr(cmd), arg) - return res, syscall.Errno(errno) -} diff --git a/src/runtime/nbpipe_fcntl_unix_test.go b/src/runtime/nbpipe_fcntl_unix_test.go deleted file mode 100644 index 6d5e4ff021..0000000000 --- a/src/runtime/nbpipe_fcntl_unix_test.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2019 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 dragonfly || freebsd || linux || netbsd || openbsd -// +build dragonfly freebsd linux netbsd openbsd - -package runtime_test - -import ( - "internal/syscall/unix" - "syscall" -) - -func fcntl(fd uintptr, cmd int, arg uintptr) (uintptr, syscall.Errno) { - res, _, err := syscall.Syscall(unix.FcntlSyscall, fd, uintptr(cmd), arg) - return res, err -} diff --git a/src/runtime/nbpipe_test.go b/src/runtime/nbpipe_test.go index 1d6a9b525c..97ed730991 100644 --- a/src/runtime/nbpipe_test.go +++ b/src/runtime/nbpipe_test.go @@ -15,23 +15,29 @@ import ( ) func TestNonblockingPipe(t *testing.T) { - t.Parallel() - // NonblockingPipe is the test name for nonblockingPipe. r, w, errno := runtime.NonblockingPipe() if errno != 0 { t.Fatal(syscall.Errno(errno)) } - defer func() { - runtime.Close(r) - runtime.Close(w) - }() + defer runtime.Close(w) checkIsPipe(t, r, w) checkNonblocking(t, r, "reader") checkCloseonexec(t, r, "reader") checkNonblocking(t, w, "writer") checkCloseonexec(t, w, "writer") + + // Test that fcntl returns an error as expected. + if runtime.Close(r) != 0 { + t.Fatalf("Close(%d) failed", r) + } + val, errno := runtime.Fcntl(r, syscall.F_GETFD, 0) + if val != -1 { + t.Errorf("Fcntl succeeded unexpectedly") + } else if syscall.Errno(errno) != syscall.EBADF { + t.Errorf("Fcntl failed with error %v, expected %v", syscall.Errno(errno), syscall.EBADF) + } } func checkIsPipe(t *testing.T, r, w int32) { @@ -50,8 +56,8 @@ func checkIsPipe(t *testing.T, r, w int32) { func checkNonblocking(t *testing.T, fd int32, name string) { t.Helper() - flags, errno := fcntl(uintptr(fd), syscall.F_GETFL, 0) - if errno != 0 { + flags, errno := runtime.Fcntl(fd, syscall.F_GETFL, 0) + if flags == -1 { t.Errorf("fcntl(%s, F_GETFL) failed: %v", name, syscall.Errno(errno)) } else if flags&syscall.O_NONBLOCK == 0 { t.Errorf("O_NONBLOCK not set in %s flags %#x", name, flags) @@ -60,8 +66,8 @@ func checkNonblocking(t *testing.T, fd int32, name string) { func checkCloseonexec(t *testing.T, fd int32, name string) { t.Helper() - flags, errno := fcntl(uintptr(fd), syscall.F_GETFD, 0) - if errno != 0 { + flags, errno := runtime.Fcntl(fd, syscall.F_GETFD, 0) + if flags == -1 { t.Errorf("fcntl(%s, F_GETFD) failed: %v", name, syscall.Errno(errno)) } else if flags&syscall.FD_CLOEXEC == 0 { t.Errorf("FD_CLOEXEC not set in %s flags %#x", name, flags) diff --git a/src/runtime/os2_aix.go b/src/runtime/os2_aix.go index 4d77f0de6d..08de0cc54f 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 4fb1c8e845..fe5c6cbc31 100644 --- a/src/runtime/os_aix.go +++ b/src/runtime/os_aix.go @@ -360,3 +360,43 @@ func setNonblock(fd int32) { flags := fcntl(fd, _F_GETFL, 0) fcntl(fd, _F_SETFL, flags|_O_NONBLOCK) } + +//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 5c688a3109..c0224702cb 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 closeonexec(fd int32) func setNonblock(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 09dd50ce59..ab9915bec1 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 closeonexec(fd int32) func setNonblock(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 c8b29e396c..266a7d4b8d 100644 --- a/src/runtime/os_linux.go +++ b/src/runtime/os_linux.go @@ -6,6 +6,7 @@ package runtime import ( "runtime/internal/sys" + "runtime/internal/syscall" "unsafe" ) @@ -183,6 +184,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 ) @@ -252,6 +254,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 { @@ -264,6 +269,9 @@ func sysauxv(auxv []uintptr) int { case _AT_PAGESZ: physPageSize = val + + case _AT_SECURE: + secureMode = val == 1 } archauxv(tag, val) @@ -419,6 +427,12 @@ func pipe() (r, w int32, errno int32) func pipe2(flags int32) (r, w int32, errno int32) func setNonblock(fd int32) +//go:nosplit +func fcntl(fd, cmd, arg int32) (ret int32, errno int32) { + r, _, err := syscall.Syscall6(syscall.SYS_FCNTL, uintptr(fd), uintptr(cmd), uintptr(arg), 0, 0, 0) + return int32(r), int32(err) +} + //go:nosplit //go:nowritebarrierrec func setsig(i uint32, fn uintptr) { diff --git a/src/runtime/os_netbsd.go b/src/runtime/os_netbsd.go index 6fbb3aa694..a38eed01bc 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 closeonexec(fd int32) func setNonblock(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 af1997131f..00af61734a 100644 --- a/src/runtime/os_openbsd_syscall2.go +++ b/src/runtime/os_openbsd_syscall2.go @@ -99,3 +99,5 @@ func closeonexec(fd int32) func setNonblock(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 89129e5f1a..a9c081d292 100644 --- a/src/runtime/os_solaris.go +++ b/src/runtime/os_solaris.go @@ -264,3 +264,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 f6c38aafcc..542ccb1989 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -1248,6 +1248,9 @@ func fatalthrow() { // 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 a144b36ec8..e1fe26b9a7 100644 --- a/src/runtime/proc.go +++ b/src/runtime/proc.go @@ -705,6 +705,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..856f50a504 --- /dev/null +++ b/src/runtime/security_issetugid.go @@ -0,0 +1,20 @@ +// 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 +// +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..42c3bf408a --- /dev/null +++ b/src/runtime/security_nonunix.go @@ -0,0 +1,14 @@ +// 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 !aix && !android && !darwin && !dragonfly && !freebsd && !hurd && !illumos && !ios && !linux && !netbsd && !openbsd && !solaris +// +build !aix,!android,!darwin,!dragonfly,!freebsd,!hurd,!illumos,!ios,!linux,!netbsd,!openbsd,!solaris + +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..7e0c7ad245 --- /dev/null +++ b/src/runtime/security_test.go @@ -0,0 +1,144 @@ +// 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 aix || android || darwin || dragonfly || freebsd || hurd || illumos || ios || linux || netbsd || openbsd || solaris +// +build aix android darwin dragonfly freebsd hurd illumos ios linux netbsd openbsd solaris + +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..791590b2a6 --- /dev/null +++ b/src/runtime/security_unix.go @@ -0,0 +1,73 @@ +// 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 aix || android || darwin || dragonfly || freebsd || hurd || illumos || ios || linux || netbsd || openbsd || solaris +// +build aix android darwin dragonfly freebsd hurd illumos ios linux netbsd openbsd solaris + +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 6096760b50..ca171ac2b8 100644 --- a/src/runtime/signal_unix.go +++ b/src/runtime/signal_unix.go @@ -626,6 +626,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=", _g_.m.id, " sigcode=", c.sigcode(), "\n") if _g_.m.lockedg != 0 && _g_.m.ncgo > 0 && gp == _g_.m.g0 { print("signal arrived during cgo execution\n") diff --git a/src/runtime/sys_darwin.go b/src/runtime/sys_darwin.go index 0f91685d6c..2c45329c79 100644 --- a/src/runtime/sys_darwin.go +++ b/src/runtime/sys_darwin.go @@ -339,8 +339,13 @@ func sysctlbyname_trampoline() //go:nosplit //go:cgo_unsafe_args -func fcntl(fd, cmd, arg int32) int32 { - return libcCall(unsafe.Pointer(abi.FuncPCABI0(fcntl_trampoline)), unsafe.Pointer(&fd)) +func fcntl(fd, cmd, arg int32) (ret int32, errno int32) { + args := struct { + fd, cmd, arg int32 + ret, errno int32 + }{fd, cmd, arg, 0, 0} + libcCall(unsafe.Pointer(abi.FuncPCABI0(fcntl_trampoline)), unsafe.Pointer(&args)) + return args.ret, args.errno } func fcntl_trampoline() @@ -419,10 +424,17 @@ func closeonexec(fd int32) { //go:nosplit func setNonblock(fd int32) { - flags := fcntl(fd, _F_GETFL, 0) - fcntl(fd, _F_SETFL, flags|_O_NONBLOCK) + flags, _ := fcntl(fd, _F_GETFL, 0) + if flags != -1 { + 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. @@ -470,3 +482,5 @@ func setNonblock(fd int32) { //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 3bd027f982..0616fc3f0d 100644 --- a/src/runtime/sys_darwin_amd64.s +++ b/src/runtime/sys_darwin_amd64.s @@ -839,3 +839,10 @@ TEXT runtime·syscallNoErr(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 96d2ed1076..f7b4440f68 100644 --- a/src/runtime/sys_darwin_arm64.s +++ b/src/runtime/sys_darwin_arm64.s @@ -755,3 +755,7 @@ TEXT runtime·syscallNoErr(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 d57bc2a7a4..5912cbe501 100644 --- a/src/runtime/sys_dragonfly_amd64.s +++ b/src/runtime/sys_dragonfly_amd64.s @@ -417,3 +417,13 @@ TEXT runtime·setNonblock(SB),NOSPLIT,$0-4 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 97e6d9ab36..bd42d96871 100644 --- a/src/runtime/sys_freebsd_386.s +++ b/src/runtime/sys_freebsd_386.s @@ -470,3 +470,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 $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 71a60cae65..fd02aae3d6 100644 --- a/src/runtime/sys_freebsd_amd64.s +++ b/src/runtime/sys_freebsd_amd64.s @@ -502,3 +502,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 $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 b12e47c576..bfd176556a 100644 --- a/src/runtime/sys_freebsd_arm.s +++ b/src/runtime/sys_freebsd_arm.s @@ -28,6 +28,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) @@ -473,3 +474,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 1aa09e87ca..dcbeafd895 100644 --- a/src/runtime/sys_freebsd_arm64.s +++ b/src/runtime/sys_freebsd_arm64.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 @@ -521,3 +522,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_netbsd_386.s b/src/runtime/sys_netbsd_386.s index 8a33894892..bb7d8318bb 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 @@ -501,3 +502,10 @@ TEXT runtime·setNonblock(SB),NOSPLIT,$16-4 MOVL $92, AX // fcntl INT $0x80 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 02f5b4ba3b..1fc3f00aa7 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 @@ -464,3 +465,13 @@ TEXT runtime·setNonblock(SB),NOSPLIT,$0-4 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 $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 3a763b2a6a..97e615dbbf 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 @@ -443,3 +444,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 2d0b894d47..cc60d2410d 100644 --- a/src/runtime/sys_netbsd_arm64.s +++ b/src/runtime/sys_netbsd_arm64.s @@ -32,6 +32,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 @@ -479,3 +480,9 @@ TEXT runtime·setNonblock(SB),NOSPLIT|NOFRAME,$0-4 MOVD $F_SETFL, R1 // arg 2 - cmd 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 cd1a4e879f..a858ae675e 100644 --- a/src/runtime/sys_openbsd2.go +++ b/src/runtime/sys_openbsd2.go @@ -222,6 +222,14 @@ func setNonblock(fd int32) { fcntl(fd, _F_SETFL, flags|_O_NONBLOCK) } +//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. @@ -254,4 +262,6 @@ func setNonblock(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 7830b61b7d..310ae24ab5 100644 --- a/src/runtime/sys_openbsd_386.s +++ b/src/runtime/sys_openbsd_386.s @@ -966,3 +966,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 522e98cf4f..2f04da3ba8 100644 --- a/src/runtime/sys_openbsd_amd64.s +++ b/src/runtime/sys_openbsd_amd64.s @@ -763,3 +763,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 143fcf0518..eddb20529d 100644 --- a/src/runtime/sys_openbsd_arm.s +++ b/src/runtime/sys_openbsd_arm.s @@ -804,3 +804,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 9b4acc90a5..ce68c2778d 100644 --- a/src/runtime/sys_openbsd_arm64.s +++ b/src/runtime/sys_openbsd_arm64.s @@ -698,3 +698,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 f8ae8e7c30..491d356a1b 100644 --- a/src/runtime/sys_openbsd_mips64.s +++ b/src/runtime/sys_openbsd_mips64.s @@ -398,3 +398,10 @@ TEXT runtime·setNonblock(SB),NOSPLIT|NOFRAME,$0-4 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 \ No newline at end of file diff --git a/src/runtime/syscall2_solaris.go b/src/runtime/syscall2_solaris.go index 3310489202..0b5ebfda02 100644 --- a/src/runtime/syscall2_solaris.go +++ b/src/runtime/syscall2_solaris.go @@ -22,6 +22,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 @@ -39,3 +40,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 094516927f..aff1504489 100644 --- a/src/runtime/syscall_solaris.go +++ b/src/runtime/syscall_solaris.go @@ -22,6 +22,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") +} -- 2.33.0