]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: implement SUID/SGID protections
authorRoland Shoemaker <bracewell@google.com>
Tue, 9 May 2023 18:47:57 +0000 (11:47 -0700)
committerDavid Chase <drchase@google.com>
Tue, 6 Jun 2023 18:49:01 +0000 (18:49 +0000)
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 <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>
Reviewed-on: https://go-review.googlesource.com/c/go/+/501223
Run-TryBot: David Chase <drchase@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>

40 files changed:
src/runtime/extern.go
src/runtime/os2_aix.go
src/runtime/os_aix.go
src/runtime/os_dragonfly.go
src/runtime/os_freebsd.go
src/runtime/os_linux.go
src/runtime/os_netbsd.go
src/runtime/os_openbsd_syscall2.go
src/runtime/os_solaris.go
src/runtime/panic.go
src/runtime/proc.go
src/runtime/security_aix.go [new file with mode: 0644]
src/runtime/security_issetugid.go [new file with mode: 0644]
src/runtime/security_linux.go [new file with mode: 0644]
src/runtime/security_nonunix.go [new file with mode: 0644]
src/runtime/security_test.go [new file with mode: 0644]
src/runtime/security_unix.go [new file with mode: 0644]
src/runtime/signal_unix.go
src/runtime/sys_darwin.go
src/runtime/sys_darwin_amd64.s
src/runtime/sys_darwin_arm64.s
src/runtime/sys_dragonfly_amd64.s
src/runtime/sys_freebsd_386.s
src/runtime/sys_freebsd_amd64.s
src/runtime/sys_freebsd_arm.s
src/runtime/sys_freebsd_arm64.s
src/runtime/sys_freebsd_riscv64.s
src/runtime/sys_netbsd_386.s
src/runtime/sys_netbsd_amd64.s
src/runtime/sys_netbsd_arm.s
src/runtime/sys_netbsd_arm64.s
src/runtime/sys_openbsd2.go
src/runtime/sys_openbsd_386.s
src/runtime/sys_openbsd_amd64.s
src/runtime/sys_openbsd_arm.s
src/runtime/sys_openbsd_arm64.s
src/runtime/sys_openbsd_mips64.s
src/runtime/syscall2_solaris.go
src/runtime/syscall_solaris.go
src/runtime/testdata/testsuid/main.go [new file with mode: 0644]

index 9ad9fb7f3d1da4cea9bed8de6520cd1aa061a5f3..bf0d0f71a6de6e1b24e59fe417784afa9836707f 100644 (file)
@@ -233,6 +233,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
 
index e55dd9833e097f5263995eb44ddfdd544ff958d7..8af88d1832fa0eb025fc0ff426793087a8a58528 100644 (file)
@@ -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,
index 8f10eb7380b29230e00cb168df5c154f5563ecec..0583e9afdb5185b223f9c50fa29f8c84a88a7bef 100644 (file)
@@ -373,3 +373,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)
+}
index bb53f4a157b4261fd20c5a65eb2408cc84d5313c..8268c7f0fc618c934530390e3e5df2c39a051447 100644 (file)
@@ -65,6 +65,8 @@ func kevent(kq int32, ch *keventt, nch int32, ev *keventt, nev int32, ts *timesp
 func pipe2(flags int32) (r, w int32, errno int32)
 func fcntl(fd, cmd, arg int32) (ret int32, errno int32)
 
+func issetugid() int32
+
 // From DragonFly's <sys/sysctl.h>
 const (
        _CTL_HW      = 6
index b53a70bef002a0886b717bf1faa896d51a279554..3af234e279fca9a058647b89ca82701aa2d895d3 100644 (file)
@@ -50,6 +50,8 @@ func kevent(kq int32, ch *keventt, nch int32, ev *keventt, nev int32, ts *timesp
 func pipe2(flags int32) (r, w int32, errno int32)
 func fcntl(fd, cmd, arg int32) (ret int32, errno int32)
 
+func issetugid() int32
+
 // From FreeBSD's <sys/sysctl.h>
 const (
        _CTL_HW      = 6
index e6833509cc6f1d56de26600c93171224d80e69a3..0b0561039fa25a787423ee396a1ae9c47185985f 100644 (file)
@@ -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
 )
@@ -290,6 +291,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) (pairs int) {
        var i int
        for ; auxv[i] != _AT_NULL; i += 2 {
@@ -302,6 +306,9 @@ func sysauxv(auxv []uintptr) (pairs int) {
 
                case _AT_PAGESZ:
                        physPageSize = val
+
+               case _AT_SECURE:
+                       secureMode = val == 1
                }
 
                archauxv(tag, val)
index 92c02c193fd56755db18b556d607b99fd0801af8..b50ed4b69ef824a707874e5cb57632058d8d2127 100644 (file)
@@ -81,6 +81,8 @@ func kevent(kq int32, ch *keventt, nch int32, ev *keventt, nev int32, ts *timesp
 func pipe2(flags int32) (r, w int32, errno int32)
 func fcntl(fd, cmd, arg int32) (ret int32, errno int32)
 
+func issetugid() int32
+
 const (
        _ESRCH     = 3
        _ETIMEDOUT = 60
index bba89f3cb123c1e68baa75f6f59e7912fe47a913..0b796ade43a6d292c7a1b7925787c804e09500df 100644 (file)
@@ -98,3 +98,5 @@ func sigaltstack(new, old *stackt)
 func fcntl(fd, cmd, arg int32) (ret int32, errno int32)
 
 func walltime() (sec int64, nsec int32)
+
+func issetugid() int32
index 6e7cada0f7d291e0e7a345e555f1147c8645de4e..bc00698cbacf03ebd2cda59682bfa9f8f36277cd 100644 (file)
@@ -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))
+}
index 751ad998c9943ad94f2f7b69354141985e106a85..6d6b05b2012787755ccabac5c922bbc6d5084962 100644 (file)
@@ -1148,6 +1148,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) {
index 0c71c3cfabb86fdad8db68f27e220bd5ee66f061..9a252cfcf53c4ef2bea1e4b6d572d8d67a658387 100644 (file)
@@ -740,6 +740,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 (file)
index 0000000..c11b9c3
--- /dev/null
@@ -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 (file)
index 0000000..5048632
--- /dev/null
@@ -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 (file)
index 0000000..181f3a1
--- /dev/null
@@ -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 (file)
index 0000000..fc9571c
--- /dev/null
@@ -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 (file)
index 0000000..1d30411
--- /dev/null
@@ -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 (file)
index 0000000..16fc87e
--- /dev/null
@@ -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")
+               }
+       }
+}
index 97ef68579713733286c81492d27e7454cc9bbf38..ae842e9f79e0b49c25c0ef83589fc7340fa3eb05 100644 (file)
@@ -783,6 +783,10 @@ func fatalsignal(sig uint32, c *sigctxt, gp *g, mp *m) *g {
                print("Signal ", sig, "\n")
        }
 
+       if isSecureMode() {
+               exit(2)
+       }
+
        print("PC=", hex(c.sigpc()), " m=", mp.id, " sigcode=", c.sigcode(), "\n")
        if mp.incgo && gp == mp.g0 && mp.curg != nil {
                print("signal arrived during cgo execution\n")
index 2229ee87edbe32ca4cd3b4c3fd4a95807e7dc8b2..fa9a2fbd52d5317050f5c17918ed9b209286b120 100644 (file)
@@ -544,6 +544,11 @@ func setNonblock(fd int32) {
        }
 }
 
+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.
 
@@ -594,3 +599,5 @@ func setNonblock(fd int32) {
 
 //go:cgo_import_dynamic libc_notify_is_valid_token notify_is_valid_token "/usr/lib/libSystem.B.dylib"
 //go:cgo_import_dynamic libc_xpc_date_create_from_current xpc_date_create_from_current "/usr/lib/libSystem.B.dylib"
+
+//go:cgo_import_dynamic libc_issetugid issetugid "/usr/lib/libSystem.B.dylib"
index f4e33f0bf6358d036290d4ea4732cb1819d8c4c5..8e8ad9c8f7d004c575ff8265041673135c6bc621 100644 (file)
@@ -792,3 +792,7 @@ TEXT runtime·syscall_x509(SB),NOSPLIT,$16
 
        XORL    AX, AX        // no error (it's ignored anyway)
        RET
+
+TEXT runtime·issetugid_trampoline(SB),NOSPLIT,$0
+       CALL    libc_issetugid(SB)
+       RET
index e1c61fae55ba105ef110dc67052ce0ec390b5ed1..dc6caf873b1d6a4fb2e6fe09f72cd74e2149f42c 100644 (file)
@@ -763,3 +763,7 @@ TEXT runtime·syscall_x509(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
index 4e16c9d66299debf96a6ab033dffaab29f718f9e..a223c2cf76bd1def59c8282c179a58b51c679963 100644 (file)
@@ -400,3 +400,13 @@ noerr:
        MOVL    AX, ret+16(FP)
        MOVL    $0, errno+20(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
index 3d5531fecbbc5c88e581f229d4b3e394cc6e3a02..184cd14b8a8fa373393b2f3fdbe01f34da7592bf 100644 (file)
@@ -29,6 +29,7 @@
 #define SYS___sysctl           202
 #define SYS_clock_gettime      232
 #define SYS_nanosleep          240
+#define SYS_issetugid          253
 #define SYS_sched_yield                331
 #define SYS_sigprocmask                340
 #define SYS_kqueue             362
@@ -472,3 +473,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    $SYS_issetugid, AX
+       INT     $0x80
+       MOVL    AX, ret+0(FP)
+       RET
index ff39d15a03695eeab8aea73c9dc7789320413699..977ea093d247ac367b3239ba3f2af492359bae1a 100644 (file)
@@ -31,6 +31,7 @@
 #define SYS___sysctl           202
 #define SYS_clock_gettime      232
 #define SYS_nanosleep          240
+#define SYS_issetugid          253
 #define SYS_sched_yield                331
 #define SYS_sigprocmask                340
 #define SYS_kqueue             362
@@ -575,3 +576,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    $SYS_issetugid, AX
+       SYSCALL
+       MOVL    AX, ret+0(FP)
+       RET
index 3b76cc84a7b8024f6ad02b5707254f4e8376e1d5..44430f5eae458e2c3e54dc179d1777e2416493ba 100644 (file)
@@ -27,6 +27,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)
@@ -446,3 +447,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
index e9b2abcb655e68337f1c1239eccbf68eb5ba175c..8fb46f42b58d0e4bdee503a85e609e7b195dea08 100644 (file)
@@ -29,6 +29,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
@@ -466,3 +467,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
index fd69ba695e77ec04cb93c056b2c083716a6c4483..cbf920c2ab2e4535a363b505a6adaaa449200ffa 100644 (file)
@@ -28,6 +28,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
@@ -437,3 +438,11 @@ TEXT runtime·getCntxct(SB),NOSPLIT|NOFRAME,$0
        RDTIME  A0
        MOVW    A0, ret+0(FP)
        RET
+
+// func issetugid() int32
+TEXT runtime·issetugid(SB),NOSPLIT|NOFRAME,$0
+       MOV $SYS_issetugid, T0
+       ECALL
+       MOVW    A0, ret+0(FP)
+       RET
+
index dbfc4b552e2c6ab0db030e3af15fabee8062287e..f4875cd3eefa7a20b570f3cea2842941d9286c1a 100644 (file)
@@ -27,6 +27,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
@@ -467,3 +468,10 @@ noerr:
        MOVL    AX, ret+12(FP)
        MOVL    $0, errno+16(FP)
        RET
+
+// func issetugid() int32
+TEXT runtime·issetugid(SB),NOSPLIT,$0
+       MOVL    $SYS_issetugid, AX
+       INT     $0x80
+       MOVL    AX, ret+0(FP)
+       RET
index 948f62b8c59c0a71a9d119e078fcf56d91eb3385..2f1ddcdc89755cd00f144d0b6dcd1d22a723ea8b 100644 (file)
@@ -28,6 +28,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
@@ -445,3 +446,13 @@ noerr:
        MOVL    AX, ret+16(FP)
        MOVL    $0, errno+20(FP)
        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
index 91ec00e424c5d56031a30be76047bf8c47bf9935..960c419526078563ba23ad914cd4508131bd84e6 100644 (file)
@@ -28,6 +28,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
@@ -418,3 +419,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
index 2c0324b7a65732d82d677739b7b0b1a916879e4f..23e749424136e5a9bbff8f257be9d5c81ee8153b 100644 (file)
@@ -28,6 +28,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
@@ -426,3 +427,9 @@ noerr:
        MOVW    R0, ret+16(FP)
        MOVW    $0, errno+20(FP)
        RET
+
+// func issetugid() int32
+TEXT runtime·issetugid(SB),NOSPLIT|NOFRAME,$0
+       SVC $SYS_issetugid
+       MOVW    R0, ret+0(FP)
+       RET
index e69bfc334806090886127b068e39d8444bcb58aa..b38e49ee6f597f12e0a9860c892b3780f4cda702 100644 (file)
@@ -258,6 +258,14 @@ func exitThread(wait *atomic.Uint32) {
        throw("exitThread")
 }
 
+//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.
 
@@ -290,4 +298,6 @@ func exitThread(wait *atomic.Uint32) {
 //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"
index d0d9926ff91ade3ef48b6031f99feb0ee7e5a559..6005c106f9c9a7ec3aa121749da5ccdfa996c3aa 100644 (file)
@@ -979,3 +979,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
index cda9edc4a08b54bbfd1a62c275fad4b46894ea70..ff0bc2416aa4bcc225c72573f32bee1c62e87a98 100644 (file)
@@ -658,3 +658,9 @@ TEXT runtime·syscall10X(SB),NOSPLIT,$48
 ok:
        XORL    AX, AX        // no error (it's ignored anyway)
        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
index fc04cf11a4c094cae570f187bbbab491379e75af..61b901bd5277a4d106303885a287fc8d7a059141 100644 (file)
@@ -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
index df7643e87e495e136d082d0873a7ef32d21e2031..6667dad1580e09898e11c31b9ff8e9ddd6922257 100644 (file)
@@ -644,3 +644,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
index bea20f2433fbcb4b04304f4a49339bd73e576803..7ac0db048078a17f73a06839482dd4ff7c38c3d2 100644 (file)
@@ -379,3 +379,10 @@ noerr:
        MOVW    R2, ret+16(FP)
        MOVW    R4, errno+20(FP)
        RET
+
+// func issetugid() int32
+TEXT runtime·issetugid(SB),NOSPLIT,$0
+       MOVV    $253, R2        // sys_issetugid
+       SYSCALL
+       MOVW    R2, ret+0(FP)
+       RET
index d464f284bcb2f42de47286b65537bbddd30349fc..10a4fa07cef42746aae04456cdbc13bb3022dac9 100644 (file)
@@ -23,6 +23,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
@@ -41,3 +42,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
index 9faee9ec468952a506253e2f3bb57c9bce602433..11b9c2aadec84d92c5b42a7ee699dfe221e87cbd 100644 (file)
@@ -23,6 +23,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 (file)
index 0000000..1949d2d
--- /dev/null
@@ -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")
+}