diff options
author | Cherry Zhang <cherryyz@google.com> | 2020-10-28 09:12:20 -0400 |
---|---|---|
committer | Cherry Zhang <cherryyz@google.com> | 2020-10-28 09:12:20 -0400 |
commit | a16e30d162c1c7408db7821e7b9513cefa09c6ca (patch) | |
tree | af752ba9ba44c547df39bb0af9bff79f610ba9d5 /src/syscall/syscall_linux_test.go | |
parent | 91e4d2d57bc341dd82c98247117114c851380aef (diff) | |
parent | cf6cfba4d5358404dd890f6025e573a4b2156543 (diff) | |
download | go-git-dev.link.tar.gz |
[dev.link] all: merge branch 'master' into dev.linkdev.link
Clean merge.
Change-Id: Ia7b2808bc649790198d34c226a61d9e569084dc5
Diffstat (limited to 'src/syscall/syscall_linux_test.go')
-rw-r--r-- | src/syscall/syscall_linux_test.go | 235 |
1 files changed, 234 insertions, 1 deletions
diff --git a/src/syscall/syscall_linux_test.go b/src/syscall/syscall_linux_test.go index c5008f2913..0742ef5b07 100644 --- a/src/syscall/syscall_linux_test.go +++ b/src/syscall/syscall_linux_test.go @@ -8,6 +8,7 @@ import ( "bufio" "fmt" "io" + "io/fs" "io/ioutil" "os" "os/exec" @@ -353,7 +354,7 @@ func TestSyscallNoError(t *testing.T) { t.Fatalf("failed to chown test binary %q, %v", tmpBinary, err) } - err = os.Chmod(tmpBinary, 0755|os.ModeSetuid) + err = os.Chmod(tmpBinary, 0755|fs.ModeSetuid) if err != nil { t.Fatalf("failed to set setuid bit on test binary %q, %v", tmpBinary, err) } @@ -398,3 +399,235 @@ func syscallNoError() { fmt.Println(uintptr(euid1), "/", int(e), "/", uintptr(euid2)) os.Exit(0) } + +// reference uapi/linux/prctl.h +const ( + PR_GET_KEEPCAPS uintptr = 7 + PR_SET_KEEPCAPS = 8 +) + +// TestAllThreadsSyscall tests that the go runtime can perform +// syscalls that execute on all OSThreads - with which to support +// POSIX semantics for security state changes. +func TestAllThreadsSyscall(t *testing.T) { + if runtime.GOARCH == "ppc64" { + t.Skip("skipping on linux/ppc64; see issue #42178") + } + if _, _, err := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, 0, 0); err == syscall.ENOTSUP { + t.Skip("AllThreadsSyscall disabled with cgo") + } + + fns := []struct { + label string + fn func(uintptr) error + }{ + { + label: "prctl<3-args>", + fn: func(v uintptr) error { + _, _, e := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, v, 0) + if e != 0 { + return e + } + return nil + }, + }, + { + label: "prctl<6-args>", + fn: func(v uintptr) error { + _, _, e := syscall.AllThreadsSyscall6(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, v, 0, 0, 0, 0) + if e != 0 { + return e + } + return nil + }, + }, + } + + waiter := func(q <-chan uintptr, r chan<- uintptr, once bool) { + for x := range q { + runtime.LockOSThread() + v, _, e := syscall.Syscall(syscall.SYS_PRCTL, PR_GET_KEEPCAPS, 0, 0) + if e != 0 { + t.Errorf("tid=%d prctl(PR_GET_KEEPCAPS) failed: %v", syscall.Gettid(), e) + } else if x != v { + t.Errorf("tid=%d prctl(PR_GET_KEEPCAPS) mismatch: got=%d want=%d", syscall.Gettid(), v, x) + } + r <- v + if once { + break + } + runtime.UnlockOSThread() + } + } + + // launches per fns member. + const launches = 11 + question := make(chan uintptr) + response := make(chan uintptr) + defer close(question) + + routines := 0 + for i, v := range fns { + for j := 0; j < launches; j++ { + // Add another goroutine - the closest thing + // we can do to encourage more OS thread + // creation - while the test is running. The + // actual thread creation may or may not be + // needed, based on the number of available + // unlocked OS threads at the time waiter + // calls runtime.LockOSThread(), but the goal + // of doing this every time through the loop + // is to race thread creation with v.fn(want) + // being executed. Via the once boolean we + // also encourage one in 5 waiters to return + // locked after participating in only one + // question response sequence. This allows the + // test to race thread destruction too. + once := routines%5 == 4 + go waiter(question, response, once) + + // Keep a count of how many goroutines are + // going to participate in the + // question/response test. This will count up + // towards 2*launches minus the count of + // routines that have been invoked with + // once=true. + routines++ + + // Decide what value we want to set the + // process-shared KEEPCAPS. Note, there is + // an explicit repeat of 0 when we change the + // variant of the syscall being used. + want := uintptr(j & 1) + + // Invoke the AllThreadsSyscall* variant. + if err := v.fn(want); err != nil { + t.Errorf("[%d,%d] %s(PR_SET_KEEPCAPS, %d, ...): %v", i, j, v.label, j&1, err) + } + + // At this point, we want all launched Go + // routines to confirm that they see the + // wanted value for KEEPCAPS. + for k := 0; k < routines; k++ { + question <- want + } + + // At this point, we should have a large + // number of locked OS threads all wanting to + // reply. + for k := 0; k < routines; k++ { + if got := <-response; got != want { + t.Errorf("[%d,%d,%d] waiter result got=%d, want=%d", i, j, k, got, want) + } + } + + // Provide an explicit opportunity for this Go + // routine to change Ms. + runtime.Gosched() + + if once { + // One waiter routine will have exited. + routines-- + } + + // Whatever M we are now running on, confirm + // we see the wanted value too. + if v, _, e := syscall.Syscall(syscall.SYS_PRCTL, PR_GET_KEEPCAPS, 0, 0); e != 0 { + t.Errorf("[%d,%d] prctl(PR_GET_KEEPCAPS) failed: %v", i, j, e) + } else if v != want { + t.Errorf("[%d,%d] prctl(PR_GET_KEEPCAPS) gave wrong value: got=%v, want=1", i, j, v) + } + } + } +} + +// compareStatus is used to confirm the contents of the thread +// specific status files match expectations. +func compareStatus(filter, expect string) error { + expected := filter + "\t" + expect + pid := syscall.Getpid() + fs, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/task", pid)) + if err != nil { + return fmt.Errorf("unable to find %d tasks: %v", pid, err) + } + for _, f := range fs { + tf := fmt.Sprintf("/proc/%s/status", f.Name()) + d, err := ioutil.ReadFile(tf) + if err != nil { + return fmt.Errorf("unable to read %q: %v", tf, err) + } + lines := strings.Split(string(d), "\n") + for _, line := range lines { + if strings.HasPrefix(line, filter) { + if line != expected { + return fmt.Errorf("%s %s (bad)\n", tf, line) + } + break + } + } + } + return nil +} + +// TestSetuidEtc performs tests on all of the wrapped system calls +// that mirror to the 9 glibc syscalls with POSIX semantics. The test +// here is considered authoritative and should compile and run +// CGO_ENABLED=0 or 1. Note, there is an extended copy of this same +// test in ../../misc/cgo/test/issue1435.go which requires +// CGO_ENABLED=1 and launches pthreads from C that run concurrently +// with the Go code of the test - and the test validates that these +// pthreads are also kept in sync with the security state changed with +// the syscalls. Care should be taken to mirror any enhancements to +// this test here in that file too. +func TestSetuidEtc(t *testing.T) { + if runtime.GOARCH == "ppc64" { + t.Skip("skipping on linux/ppc64; see issue #42178") + } + if syscall.Getuid() != 0 { + t.Skip("skipping root only test") + } + vs := []struct { + call string + fn func() error + filter, expect string + }{ + {call: "Setegid(1)", fn: func() error { return syscall.Setegid(1) }, filter: "Gid:", expect: "0\t1\t0\t1"}, + {call: "Setegid(0)", fn: func() error { return syscall.Setegid(0) }, filter: "Gid:", expect: "0\t0\t0\t0"}, + + {call: "Seteuid(1)", fn: func() error { return syscall.Seteuid(1) }, filter: "Uid:", expect: "0\t1\t0\t1"}, + {call: "Setuid(0)", fn: func() error { return syscall.Setuid(0) }, filter: "Uid:", expect: "0\t0\t0\t0"}, + + {call: "Setgid(1)", fn: func() error { return syscall.Setgid(1) }, filter: "Gid:", expect: "1\t1\t1\t1"}, + {call: "Setgid(0)", fn: func() error { return syscall.Setgid(0) }, filter: "Gid:", expect: "0\t0\t0\t0"}, + + {call: "Setgroups([]int{0,1,2,3})", fn: func() error { return syscall.Setgroups([]int{0, 1, 2, 3}) }, filter: "Groups:", expect: "0 1 2 3 "}, + {call: "Setgroups(nil)", fn: func() error { return syscall.Setgroups(nil) }, filter: "Groups:", expect: " "}, + {call: "Setgroups([]int{0})", fn: func() error { return syscall.Setgroups([]int{0}) }, filter: "Groups:", expect: "0 "}, + + {call: "Setregid(101,0)", fn: func() error { return syscall.Setregid(101, 0) }, filter: "Gid:", expect: "101\t0\t0\t0"}, + {call: "Setregid(0,102)", fn: func() error { return syscall.Setregid(0, 102) }, filter: "Gid:", expect: "0\t102\t102\t102"}, + {call: "Setregid(0,0)", fn: func() error { return syscall.Setregid(0, 0) }, filter: "Gid:", expect: "0\t0\t0\t0"}, + + {call: "Setreuid(1,0)", fn: func() error { return syscall.Setreuid(1, 0) }, filter: "Uid:", expect: "1\t0\t0\t0"}, + {call: "Setreuid(0,2)", fn: func() error { return syscall.Setreuid(0, 2) }, filter: "Uid:", expect: "0\t2\t2\t2"}, + {call: "Setreuid(0,0)", fn: func() error { return syscall.Setreuid(0, 0) }, filter: "Uid:", expect: "0\t0\t0\t0"}, + + {call: "Setresgid(101,0,102)", fn: func() error { return syscall.Setresgid(101, 0, 102) }, filter: "Gid:", expect: "101\t0\t102\t0"}, + {call: "Setresgid(0,102,101)", fn: func() error { return syscall.Setresgid(0, 102, 101) }, filter: "Gid:", expect: "0\t102\t101\t102"}, + {call: "Setresgid(0,0,0)", fn: func() error { return syscall.Setresgid(0, 0, 0) }, filter: "Gid:", expect: "0\t0\t0\t0"}, + + {call: "Setresuid(1,0,2)", fn: func() error { return syscall.Setresuid(1, 0, 2) }, filter: "Uid:", expect: "1\t0\t2\t0"}, + {call: "Setresuid(0,2,1)", fn: func() error { return syscall.Setresuid(0, 2, 1) }, filter: "Uid:", expect: "0\t2\t1\t2"}, + {call: "Setresuid(0,0,0)", fn: func() error { return syscall.Setresuid(0, 0, 0) }, filter: "Uid:", expect: "0\t0\t0\t0"}, + } + + for i, v := range vs { + if err := v.fn(); err != nil { + t.Errorf("[%d] %q failed: %v", i, v.call, err) + continue + } + if err := compareStatus(v.filter, v.expect); err != nil { + t.Errorf("[%d] %q comparison: %v", i, v.call, err) + } + } +} |