Skip to content

Commit 2496653

Browse files
rolandshoemakerdr2chase
authored andcommitted
runtime: implement SUID/SGID protections
On Unix platforms, the runtime previously did nothing special when a program was run with either the SUID or SGID bits set. This can be dangerous in certain cases, such as when dumping memory state, or assuming the status of standard i/o file descriptors. Taking cues from glibc, this change implements a set of protections when a binary is run with SUID or SGID bits set (or is SUID/SGID-like). On Linux, whether to enable these protections is determined by whether the AT_SECURE flag is passed in the auxiliary vector. On platforms which have the issetugid syscall (the BSDs, darwin, and Solaris/Illumos), that is used. On the remaining platforms (currently only AIX) we check !(getuid() == geteuid() && getgid == getegid()). Currently when we determine a binary is "tainted" (using the glibc terminology), we implement two specific protections: 1. we check if the file descriptors 0, 1, and 2 are open, and if they are not, we open them, pointing at /dev/null (or fail). 2. we force GOTRACKBACK=none, and generally prevent dumping of trackbacks and registers when a program panics/aborts. In the future we may add additional protections. This change requires implementing issetugid on the platforms which support it, and implementing getuid, geteuid, getgid, and getegid on AIX. Thanks to Vincent Dehors from Synacktiv for reporting this issue. Fixes #60272 Fixes CVE-2023-29403 Change-Id: I73fc93f2b7a8933c192ce3eabbf1db359db7d5fa Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1878434 Reviewed-by: Damien Neil <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Run-TryBot: Roland Shoemaker <[email protected]> Reviewed-by: Russ Cox <[email protected]> Reviewed-on: https://go-review.googlesource.com/c/go/+/501223 Run-TryBot: David Chase <[email protected]> Reviewed-by: Michael Knyszek <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent b7fc272 commit 2496653

40 files changed

+555
-0
lines changed

src/runtime/extern.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,25 @@ the set of Go environment variables. They influence the building of Go programs
233233
GOARCH, GOOS, and GOROOT are recorded at compile time and made available by
234234
constants or functions in this package, but they do not influence the execution
235235
of the run-time system.
236+
237+
# Security
238+
239+
On Unix platforms, Go's runtime system behaves slightly differently when a
240+
binary is setuid/setgid or executed with setuid/setgid-like properties, in order
241+
to prevent dangerous behaviors. On Linux this is determined by checking for the
242+
AT_SECURE flag in the auxiliary vector, on the BSDs and Solaris/Illumos it is
243+
determined by checking the issetugid syscall, and on AIX it is determined by
244+
checking if the uid/gid match the effective uid/gid.
245+
246+
When the runtime determines the binary is setuid/setgid-like, it does three main
247+
things:
248+
- The standard input/output file descriptors (0, 1, 2) are checked to be open.
249+
If any of them are closed, they are opened pointing at /dev/null.
250+
- The value of the GOTRACEBACK environment variable is set to 'none'.
251+
- When a signal is received that terminates the program, or the program
252+
encounters an unrecoverable panic that would otherwise override the value
253+
of GOTRACEBACK, the goroutine stack, registers, and other memory related
254+
information are omitted.
236255
*/
237256
package runtime
238257

src/runtime/os2_aix.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ var (
5555
//go:cgo_import_dynamic libc_sysconf sysconf "libc.a/shr_64.o"
5656
//go:cgo_import_dynamic libc_usleep usleep "libc.a/shr_64.o"
5757
//go:cgo_import_dynamic libc_write write "libc.a/shr_64.o"
58+
//go:cgo_import_dynamic libc_getuid getuid "libc.a/shr_64.o"
59+
//go:cgo_import_dynamic libc_geteuid geteuid "libc.a/shr_64.o"
60+
//go:cgo_import_dynamic libc_getgid getgid "libc.a/shr_64.o"
61+
//go:cgo_import_dynamic libc_getegid getegid "libc.a/shr_64.o"
5862

5963
//go:cgo_import_dynamic libpthread___pth_init __pth_init "libpthread.a/shr_xpg5_64.o"
6064
//go:cgo_import_dynamic libpthread_attr_destroy pthread_attr_destroy "libpthread.a/shr_xpg5_64.o"
@@ -95,6 +99,10 @@ var (
9599
//go:linkname libc_sysconf libc_sysconf
96100
//go:linkname libc_usleep libc_usleep
97101
//go:linkname libc_write libc_write
102+
//go:linkname libc_getuid libc_getuid
103+
//go:linkname libc_geteuid libc_geteuid
104+
//go:linkname libc_getgid libc_getgid
105+
//go:linkname libc_getegid libc_getegid
98106

99107
//go:linkname libpthread___pth_init libpthread___pth_init
100108
//go:linkname libpthread_attr_destroy libpthread_attr_destroy
@@ -137,6 +145,10 @@ var (
137145
libc_sysconf,
138146
libc_usleep,
139147
libc_write,
148+
libc_getuid,
149+
libc_geteuid,
150+
libc_getgid,
151+
libc_getegid,
140152
//libpthread
141153
libpthread___pth_init,
142154
libpthread_attr_destroy,

src/runtime/os_aix.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,3 +373,43 @@ const sigPerThreadSyscall = 1 << 31
373373
func runPerThreadSyscall() {
374374
throw("runPerThreadSyscall only valid on linux")
375375
}
376+
377+
//go:nosplit
378+
func getuid() int32 {
379+
r, errno := syscall0(&libc_getuid)
380+
if errno != 0 {
381+
print("getuid failed ", errno)
382+
throw("getuid")
383+
}
384+
return int32(r)
385+
}
386+
387+
//go:nosplit
388+
func geteuid() int32 {
389+
r, errno := syscall0(&libc_geteuid)
390+
if errno != 0 {
391+
print("geteuid failed ", errno)
392+
throw("geteuid")
393+
}
394+
return int32(r)
395+
}
396+
397+
//go:nosplit
398+
func getgid() int32 {
399+
r, errno := syscall0(&libc_getgid)
400+
if errno != 0 {
401+
print("getgid failed ", errno)
402+
throw("getgid")
403+
}
404+
return int32(r)
405+
}
406+
407+
//go:nosplit
408+
func getegid() int32 {
409+
r, errno := syscall0(&libc_getegid)
410+
if errno != 0 {
411+
print("getegid failed ", errno)
412+
throw("getegid")
413+
}
414+
return int32(r)
415+
}

src/runtime/os_dragonfly.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ func kevent(kq int32, ch *keventt, nch int32, ev *keventt, nev int32, ts *timesp
6565
func pipe2(flags int32) (r, w int32, errno int32)
6666
func fcntl(fd, cmd, arg int32) (ret int32, errno int32)
6767

68+
func issetugid() int32
69+
6870
// From DragonFly's <sys/sysctl.h>
6971
const (
7072
_CTL_HW = 6

src/runtime/os_freebsd.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ func kevent(kq int32, ch *keventt, nch int32, ev *keventt, nev int32, ts *timesp
5050
func pipe2(flags int32) (r, w int32, errno int32)
5151
func fcntl(fd, cmd, arg int32) (ret int32, errno int32)
5252

53+
func issetugid() int32
54+
5355
// From FreeBSD's <sys/sysctl.h>
5456
const (
5557
_CTL_HW = 6

src/runtime/os_linux.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ const (
216216
_AT_NULL = 0 // End of vector
217217
_AT_PAGESZ = 6 // System physical page size
218218
_AT_HWCAP = 16 // hardware capability bit vector
219+
_AT_SECURE = 23 // secure mode boolean
219220
_AT_RANDOM = 25 // introduced in 2.6.29
220221
_AT_HWCAP2 = 26 // hardware capability bit vector 2
221222
)
@@ -290,6 +291,9 @@ func sysargs(argc int32, argv **byte) {
290291
// the ELF AT_RANDOM auxiliary vector.
291292
var startupRandomData []byte
292293

294+
// secureMode holds the value of AT_SECURE passed in the auxiliary vector.
295+
var secureMode bool
296+
293297
func sysauxv(auxv []uintptr) (pairs int) {
294298
var i int
295299
for ; auxv[i] != _AT_NULL; i += 2 {
@@ -302,6 +306,9 @@ func sysauxv(auxv []uintptr) (pairs int) {
302306

303307
case _AT_PAGESZ:
304308
physPageSize = val
309+
310+
case _AT_SECURE:
311+
secureMode = val == 1
305312
}
306313

307314
archauxv(tag, val)

src/runtime/os_netbsd.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ func kevent(kq int32, ch *keventt, nch int32, ev *keventt, nev int32, ts *timesp
8181
func pipe2(flags int32) (r, w int32, errno int32)
8282
func fcntl(fd, cmd, arg int32) (ret int32, errno int32)
8383

84+
func issetugid() int32
85+
8486
const (
8587
_ESRCH = 3
8688
_ETIMEDOUT = 60

src/runtime/os_openbsd_syscall2.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,5 @@ func sigaltstack(new, old *stackt)
9898
func fcntl(fd, cmd, arg int32) (ret int32, errno int32)
9999

100100
func walltime() (sec int64, nsec int32)
101+
102+
func issetugid() int32

src/runtime/os_solaris.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,3 +267,7 @@ func sysvicall6(fn *libcFunc, a1, a2, a3, a4, a5, a6 uintptr) uintptr {
267267
}
268268
return libcall.r1
269269
}
270+
271+
func issetugid() int32 {
272+
return int32(sysvicall0(&libc_issetugid))
273+
}

src/runtime/panic.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,6 +1148,10 @@ func fatalthrow(t throwType) {
11481148
// Switch to the system stack to avoid any stack growth, which may make
11491149
// things worse if the runtime is in a bad state.
11501150
systemstack(func() {
1151+
if isSecureMode() {
1152+
exit(2)
1153+
}
1154+
11511155
startpanic_m()
11521156

11531157
if dopanic_m(gp, pc, sp) {

src/runtime/proc.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,7 @@ func schedinit() {
740740

741741
goargs()
742742
goenvs()
743+
secure()
743744
parsedebugvars()
744745
gcinit()
745746

src/runtime/security_aix.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package runtime
6+
7+
// secureMode is only ever mutated in schedinit, so we don't need to worry about
8+
// synchronization primitives.
9+
var secureMode bool
10+
11+
func initSecureMode() {
12+
secureMode = !(getuid() == geteuid() && getgid() == getegid())
13+
}
14+
15+
func isSecureMode() bool {
16+
return secureMode
17+
}

src/runtime/security_issetugid.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build darwin || dragonfly || freebsd || illumos || netbsd || openbsd || solaris
6+
7+
package runtime
8+
9+
// secureMode is only ever mutated in schedinit, so we don't need to worry about
10+
// synchronization primitives.
11+
var secureMode bool
12+
13+
func initSecureMode() {
14+
secureMode = issetugid() == 1
15+
}
16+
17+
func isSecureMode() bool {
18+
return secureMode
19+
}

src/runtime/security_linux.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package runtime
6+
7+
import _ "unsafe"
8+
9+
func initSecureMode() {
10+
// We have already initialized the secureMode bool in sysauxv.
11+
}
12+
13+
func isSecureMode() bool {
14+
return secureMode
15+
}

src/runtime/security_nonunix.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build !unix
6+
7+
package runtime
8+
9+
func isSecureMode() bool {
10+
return false
11+
}
12+
13+
func secure() {}

0 commit comments

Comments
 (0)