Skip to content

Commit a7b1cd4

Browse files
rolandshoemakergopherbot
authored andcommitted
[release-branch.go1.19] 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. 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 <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Run-TryBot: Roland Shoemaker <[email protected]> Reviewed-by: Russ Cox <[email protected]> (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 <[email protected]> Reviewed-on: https://go-review.googlesource.com/c/go/+/501228 Auto-Submit: Michael Knyszek <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Run-TryBot: David Chase <[email protected]>
1 parent ed9db1d commit a7b1cd4

39 files changed

+547
-0
lines changed

src/runtime/extern.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,25 @@ the set of Go environment variables. They influence the building of Go programs
200200
GOARCH, GOOS, and GOROOT are recorded at compile time and made available by
201201
constants or functions in this package, but they do not influence the execution
202202
of the run-time system.
203+
204+
# Security
205+
206+
On Unix platforms, Go's runtime system behaves slightly differently when a
207+
binary is setuid/setgid or executed with setuid/setgid-like properties, in order
208+
to prevent dangerous behaviors. On Linux this is determined by checking for the
209+
AT_SECURE flag in the auxiliary vector, on the BSDs and Solaris/Illumos it is
210+
determined by checking the issetugid syscall, and on AIX it is determined by
211+
checking if the uid/gid match the effective uid/gid.
212+
213+
When the runtime determines the binary is setuid/setgid-like, it does three main
214+
things:
215+
- The standard input/output file descriptors (0, 1, 2) are checked to be open.
216+
If any of them are closed, they are opened pointing at /dev/null.
217+
- The value of the GOTRACEBACK environment variable is set to 'none'.
218+
- When a signal is received that terminates the program, or the program
219+
encounters an unrecoverable panic that would otherwise override the value
220+
of GOTRACEBACK, the goroutine stack, registers, and other memory related
221+
information are omitted.
203222
*/
204223
package runtime
205224

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
@@ -387,3 +387,43 @@ const sigPerThreadSyscall = 1 << 31
387387
func runPerThreadSyscall() {
388388
throw("runPerThreadSyscall only valid on linux")
389389
}
390+
391+
//go:nosplit
392+
func getuid() int32 {
393+
r, errno := syscall0(&libc_getuid)
394+
if errno != 0 {
395+
print("getuid failed ", errno)
396+
throw("getuid")
397+
}
398+
return int32(r)
399+
}
400+
401+
//go:nosplit
402+
func geteuid() int32 {
403+
r, errno := syscall0(&libc_geteuid)
404+
if errno != 0 {
405+
print("geteuid failed ", errno)
406+
throw("geteuid")
407+
}
408+
return int32(r)
409+
}
410+
411+
//go:nosplit
412+
func getgid() int32 {
413+
r, errno := syscall0(&libc_getgid)
414+
if errno != 0 {
415+
print("getgid failed ", errno)
416+
throw("getgid")
417+
}
418+
return int32(r)
419+
}
420+
421+
//go:nosplit
422+
func getegid() int32 {
423+
r, errno := syscall0(&libc_getegid)
424+
if errno != 0 {
425+
print("getegid failed ", errno)
426+
throw("getegid")
427+
}
428+
return int32(r)
429+
}

src/runtime/os_dragonfly.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ func pipe2(flags int32) (r, w int32, errno int32)
6666
func fcntl(fd, cmd, arg int32) (ret int32, errno int32)
6767
func closeonexec(fd int32)
6868

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

src/runtime/os_freebsd.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ func pipe2(flags int32) (r, w int32, errno int32)
5151
func fcntl(fd, cmd, arg int32) (ret int32, errno int32)
5252
func closeonexec(fd int32)
5353

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

src/runtime/os_linux.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ const (
211211
_AT_NULL = 0 // End of vector
212212
_AT_PAGESZ = 6 // System physical page size
213213
_AT_HWCAP = 16 // hardware capability bit vector
214+
_AT_SECURE = 23 // secure mode boolean
214215
_AT_RANDOM = 25 // introduced in 2.6.29
215216
_AT_HWCAP2 = 26 // hardware capability bit vector 2
216217
)
@@ -280,6 +281,9 @@ func sysargs(argc int32, argv **byte) {
280281
// the ELF AT_RANDOM auxiliary vector.
281282
var startupRandomData []byte
282283

284+
// secureMode holds the value of AT_SECURE passed in the auxiliary vector.
285+
var secureMode bool
286+
283287
func sysauxv(auxv []uintptr) int {
284288
var i int
285289
for ; auxv[i] != _AT_NULL; i += 2 {
@@ -292,6 +296,9 @@ func sysauxv(auxv []uintptr) int {
292296

293297
case _AT_PAGESZ:
294298
physPageSize = val
299+
300+
case _AT_SECURE:
301+
secureMode = val == 1
295302
}
296303

297304
archauxv(tag, val)

src/runtime/os_netbsd.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ func pipe2(flags int32) (r, w int32, errno int32)
8282
func fcntl(fd, cmd, arg int32) (ret int32, errno int32)
8383
func closeonexec(fd int32)
8484

85+
func issetugid() int32
86+
8587
const (
8688
_ESRCH = 3
8789
_ETIMEDOUT = 60

src/runtime/os_openbsd_syscall2.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,5 @@ func fcntl(fd, cmd, arg int32) (ret int32, errno int32)
9999
func closeonexec(fd int32)
100100

101101
func walltime() (sec int64, nsec int32)
102+
103+
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
@@ -1120,6 +1120,10 @@ func fatalthrow(t throwType) {
11201120
// Switch to the system stack to avoid any stack growth, which may make
11211121
// things worse if the runtime is in a bad state.
11221122
systemstack(func() {
1123+
if isSecureMode() {
1124+
exit(2)
1125+
}
1126+
11231127
startpanic_m()
11241128

11251129
if dopanic_m(gp, pc, sp) {

src/runtime/proc.go

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

711711
goargs()
712712
goenvs()
713+
secure()
713714
parsedebugvars()
714715
gcinit()
715716

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)