Skip to content

Commit

Permalink
[release-branch.go1.20] runtime: implement SUID/SGID protections
Browse files Browse the repository at this point in the history
On Unix platforms, the runtime previously did nothing special when a
program was run with either the SUID or SGID bits set. This can be
dangerous in certain cases, such as when dumping memory state, or
assuming the status of standard i/o file descriptors.

Taking cues from glibc, this change implements a set of protections when
a binary is run with SUID or SGID bits set (or is SUID/SGID-like). On
Linux, whether to enable these protections is determined by whether the
AT_SECURE flag is passed in the auxiliary vector. On platforms which
have the issetugid syscall (the BSDs, darwin, and Solaris/Illumos), that
is used. On the remaining platforms (currently only AIX) we check
!(getuid() == geteuid() && getgid == getegid()).

Currently when we determine a binary is "tainted" (using the glibc
terminology), we implement two specific protections:
  1. we check if the file descriptors 0, 1, and 2 are open, and if they
     are not, we open them, pointing at /dev/null (or fail).
  2. we force GOTRACKBACK=none, and generally prevent dumping of
     trackbacks and registers when a program panics/aborts.

In the future we may add additional protections.

This change requires implementing issetugid on the platforms which
support it, and implementing getuid, geteuid, getgid, and getegid on
AIX.

Thanks to Vincent Dehors from Synacktiv for reporting this issue.

Updates #60272
Fixes #60518
Fixes CVE-2023-29403

Change-Id: Icb620f3f8755791d51b02b5c07fb24f40e19cb80
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1878434
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Run-TryBot: Roland Shoemaker <bracewell@google.com>
Reviewed-by: Russ Cox <rsc@google.com>
(cherry picked from commit 87065663ea6d89cd54f65a515d8f2ed0ef285c19)
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1902232
TryBot-Result: Security TryBots <security-trybots@go-security-trybots.iam.gserviceaccount.com>
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1904344
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/501227
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: David Chase <drchase@google.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
  • Loading branch information
rolandshoemaker authored and gopherbot committed Jun 6, 2023
1 parent 5036ba7 commit 36144ba
Show file tree
Hide file tree
Showing 40 changed files with 557 additions and 0 deletions.
19 changes: 19 additions & 0 deletions src/runtime/extern.go
Expand Up @@ -216,6 +216,25 @@ the set of Go environment variables. They influence the building of Go programs
GOARCH, GOOS, and GOROOT are recorded at compile time and made available by
constants or functions in this package, but they do not influence the execution
of the run-time system.
# Security
On Unix platforms, Go's runtime system behaves slightly differently when a
binary is setuid/setgid or executed with setuid/setgid-like properties, in order
to prevent dangerous behaviors. On Linux this is determined by checking for the
AT_SECURE flag in the auxiliary vector, on the BSDs and Solaris/Illumos it is
determined by checking the issetugid syscall, and on AIX it is determined by
checking if the uid/gid match the effective uid/gid.
When the runtime determines the binary is setuid/setgid-like, it does three main
things:
- The standard input/output file descriptors (0, 1, 2) are checked to be open.
If any of them are closed, they are opened pointing at /dev/null.
- The value of the GOTRACEBACK environment variable is set to 'none'.
- When a signal is received that terminates the program, or the program
encounters an unrecoverable panic that would otherwise override the value
of GOTRACEBACK, the goroutine stack, registers, and other memory related
information are omitted.
*/
package runtime

Expand Down
12 changes: 12 additions & 0 deletions src/runtime/os2_aix.go
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
40 changes: 40 additions & 0 deletions src/runtime/os_aix.go
Expand Up @@ -378,3 +378,43 @@ const sigPerThreadSyscall = 1 << 31
func runPerThreadSyscall() {
throw("runPerThreadSyscall only valid on linux")
}

//go:nosplit
func getuid() int32 {
r, errno := syscall0(&libc_getuid)
if errno != 0 {
print("getuid failed ", errno)
throw("getuid")
}
return int32(r)
}

//go:nosplit
func geteuid() int32 {
r, errno := syscall0(&libc_geteuid)
if errno != 0 {
print("geteuid failed ", errno)
throw("geteuid")
}
return int32(r)
}

//go:nosplit
func getgid() int32 {
r, errno := syscall0(&libc_getgid)
if errno != 0 {
print("getgid failed ", errno)
throw("getgid")
}
return int32(r)
}

//go:nosplit
func getegid() int32 {
r, errno := syscall0(&libc_getegid)
if errno != 0 {
print("getegid failed ", errno)
throw("getegid")
}
return int32(r)
}
2 changes: 2 additions & 0 deletions src/runtime/os_dragonfly.go
Expand Up @@ -66,6 +66,8 @@ func pipe2(flags int32) (r, w int32, errno int32)
func fcntl(fd, cmd, arg int32) (ret int32, errno int32)
func closeonexec(fd int32)

func issetugid() int32

// From DragonFly's <sys/sysctl.h>
const (
_CTL_HW = 6
Expand Down
2 changes: 2 additions & 0 deletions src/runtime/os_freebsd.go
Expand Up @@ -51,6 +51,8 @@ func pipe2(flags int32) (r, w int32, errno int32)
func fcntl(fd, cmd, arg int32) (ret int32, errno int32)
func closeonexec(fd int32)

func issetugid() int32

// From FreeBSD's <sys/sysctl.h>
const (
_CTL_HW = 6
Expand Down
7 changes: 7 additions & 0 deletions src/runtime/os_linux.go
Expand Up @@ -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
)
Expand Down Expand Up @@ -285,6 +286,9 @@ func sysargs(argc int32, argv **byte) {
// the ELF AT_RANDOM auxiliary vector.
var startupRandomData []byte

// secureMode holds the value of AT_SECURE passed in the auxiliary vector.
var secureMode bool

func sysauxv(auxv []uintptr) int {
var i int
for ; auxv[i] != _AT_NULL; i += 2 {
Expand All @@ -297,6 +301,9 @@ func sysauxv(auxv []uintptr) int {

case _AT_PAGESZ:
physPageSize = val

case _AT_SECURE:
secureMode = val == 1
}

archauxv(tag, val)
Expand Down
2 changes: 2 additions & 0 deletions src/runtime/os_netbsd.go
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/runtime/os_openbsd_syscall2.go
Expand Up @@ -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
4 changes: 4 additions & 0 deletions src/runtime/os_solaris.go
Expand Up @@ -267,3 +267,7 @@ func sysvicall6(fn *libcFunc, a1, a2, a3, a4, a5, a6 uintptr) uintptr {
}
return libcall.r1
}

func issetugid() int32 {
return int32(sysvicall0(&libc_issetugid))
}
4 changes: 4 additions & 0 deletions src/runtime/panic.go
Expand Up @@ -1118,6 +1118,10 @@ func fatalthrow(t throwType) {
// Switch to the system stack to avoid any stack growth, which may make
// things worse if the runtime is in a bad state.
systemstack(func() {
if isSecureMode() {
exit(2)
}

startpanic_m()

if dopanic_m(gp, pc, sp) {
Expand Down
1 change: 1 addition & 0 deletions src/runtime/proc.go
Expand Up @@ -726,6 +726,7 @@ func schedinit() {

goargs()
goenvs()
secure()
parsedebugvars()
gcinit()

Expand Down
17 changes: 17 additions & 0 deletions 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
}
19 changes: 19 additions & 0 deletions 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
}
15 changes: 15 additions & 0 deletions 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
}
13 changes: 13 additions & 0 deletions 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() {}

0 comments on commit 36144ba

Please sign in to comment.