diff options
author | Andrew G. Morgan <morgan@kernel.org> | 2020-12-11 23:27:50 -0800 |
---|---|---|
committer | Andrew G. Morgan <morgan@kernel.org> | 2020-12-11 23:48:29 -0800 |
commit | 90192cd36471c7dfb44b4ecd6a8ccf7595d26e9d (patch) | |
tree | 527da7c7d23a0ab06fd9cfeac874351ee18badff | |
parent | e7e0e1b9e2cf3378d329174ed5b0c716b0539c72 (diff) | |
download | libcap-90192cd36471c7dfb44b4ecd6a8ccf7595d26e9d.tar.gz |
Refactor the "psx" vs "cap" package cgo-or-not complexity to "psx".
I've decided to put the decision to call syscall.AllThreadsSyscall*()
into "psx" instead of the "cap" package. This should make client use of
"psx" more straightforward and the code will 'just compile' if the
user builds with CGO_ENABLED=1 or CGO_ENABLED=0. It also makes the "psx"
package useful for other applications besides the "cap" package.
Signed-off-by: Andrew G. Morgan <morgan@kernel.org>
-rw-r--r-- | cap/syscalls.go | 14 | ||||
-rw-r--r-- | cap/syscalls_cgo.go | 29 | ||||
-rw-r--r-- | go/Makefile | 15 | ||||
-rw-r--r-- | psx/doc.go | 60 | ||||
-rw-r--r-- | psx/psx.go | 114 | ||||
-rw-r--r-- | psx/psx_cgo.go | 65 | ||||
-rw-r--r-- | psx/psx_cgo_test.go | 40 | ||||
-rw-r--r-- | psx/psx_test.go | 31 |
8 files changed, 190 insertions, 178 deletions
diff --git a/cap/syscalls.go b/cap/syscalls.go index 9c5dd79..ab4bcef 100644 --- a/cap/syscalls.go +++ b/cap/syscalls.go @@ -1,14 +1,18 @@ -// +build linux,allthreadssyscall,!cgo - package cap -import "syscall" +import ( + "syscall" + + "kernel.org/pub/linux/libs/security/libcap/psx" +) // multisc provides syscalls overridable for testing purposes that // support a single kernel security state for all OS threads. +// We use this version when we are cgo compiling because +// we need to manage the native C pthreads too. var multisc = &syscaller{ - w3: syscall.AllThreadsSyscall, - w6: syscall.AllThreadsSyscall6, + w3: psx.Syscall3, + w6: psx.Syscall6, r3: syscall.RawSyscall, r6: syscall.RawSyscall6, } diff --git a/cap/syscalls_cgo.go b/cap/syscalls_cgo.go deleted file mode 100644 index 0dc6a0c..0000000 --- a/cap/syscalls_cgo.go +++ /dev/null @@ -1,29 +0,0 @@ -// +build linux,cgo - -package cap - -import ( - "syscall" - - "kernel.org/pub/linux/libs/security/libcap/psx" -) - -// multisc provides syscalls overridable for testing purposes that -// support a single kernel security state for all OS threads. -// We use this version when we are cgo compiling because -// we need to manage the native C pthreads too. -var multisc = &syscaller{ - w3: psx.Syscall3, - w6: psx.Syscall6, - r3: syscall.RawSyscall, - r6: syscall.RawSyscall6, -} - -// singlesc provides a single threaded implementation. Users should -// take care to ensure the thread is locked and marked nogc. -var singlesc = &syscaller{ - w3: syscall.RawSyscall, - w6: syscall.RawSyscall6, - r3: syscall.RawSyscall, - r6: syscall.RawSyscall6, -} diff --git a/go/Makefile b/go/Makefile index 3bd79c8..05ec7bf 100644 --- a/go/Makefile +++ b/go/Makefile @@ -61,8 +61,8 @@ ifeq ($(RAISE_GO_FILECAP),yes) @echo "NOTE: RAISED cap_setpcap,cap_net_bind_service ON web binary" endif -setid: ../goapps/setid/setid.go $(CAPGOPACKAGE) - GO111MODULE=off CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH=$(GOPATH) $(GO) build -o $@ $(GOBUILDTAG) $< +setid: ../goapps/setid/setid.go $(CAPGOPACKAGE) $(PSXGOPACKAGE) + GO111MODULE=off CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH=$(GOPATH) $(GO) build -o $@ $(GOBUILDTAG) $< gowns: ../goapps/gowns/gowns.go $(CAPGOPACKAGE) GO111MODULE=off CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH=$(GOPATH) $(GO) build -o $@ $(GOBUILDTAG) $< @@ -77,15 +77,18 @@ ifeq ($(CGO_REQUIRED),0) endif # Bug reported issues: +# https://bugzilla.kernel.org/show_bug.cgi?id=210533 (cgo - fixed) +# https://github.com/golang/go/issues/43149 (nocgo - not fixed yet) +# When the latter is fixed we can replace CGO_ENABLED=1 with ="$(CGO_REQUIRED)" psx-signals: psx-signals.go $(PSXGOPACKAGE) - GO111MODULE=off CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" GOPATH=$(GOPATH) $(GO) build $< + GO111MODULE=off CGO_ENABLED=1 CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" GOPATH=$(GOPATH) $(GO) build $< b210613: b210613.go $(CAPGOPACKAGE) - GO111MODULE=off CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" GOPATH=$(GOPATH) $(GO) build $< + GO111MODULE=off CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" GOPATH=$(GOPATH) $(GO) build $< test: all - GO111MODULE=off CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH="$(GOPATH)" $(GO) test $(IMPORTDIR)/psx - GO111MODULE=off CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH="$(GOPATH)" $(GO) test $(IMPORTDIR)/cap + GO111MODULE=off CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH="$(GOPATH)" $(GO) test $(IMPORTDIR)/psx + GO111MODULE=off CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH="$(GOPATH)" $(GO) test $(IMPORTDIR)/cap LD_LIBRARY_PATH=../libcap ./compare-cap ./psx-signals ./setid --caps=false diff --git a/psx/doc.go b/psx/doc.go new file mode 100644 index 0000000..e6f9013 --- /dev/null +++ b/psx/doc.go @@ -0,0 +1,60 @@ +// Package psx provides support for system calls that are run +// simultanously on all threads under Linux. +// +// This property can be used to work around a historical lack of +// native Go support for such a feature. Something that is the subject +// of: +// +// https://github.com/golang/go/issues/1435 +// +// The package works differently depending on whether or not +// CGO_ENABLED is 0 or 1. +// +// In the former case, psx is a low overhead wrapper for the two +// native go calls: syscall.AllThreadsSyscall() and +// syscall.AllThreadsSyscall6() [expected to be] introduced in +// go1.16. We provide this wrapping to minimize client source code +// changes when compiling with or without CGo enabled. +// +// In the latter case, and toolchains prior to go1.16, it works via +// CGo wrappers for system call functions that call the C [lib]psx +// functions of these names. This ensures that the system calls +// execute simultaneously on all the pthreads of the Go (and CGo) +// combined runtime. +// +// With CGo, the psx support works in the following way: the pthread +// that is first asked to execute the syscall does so, and determines +// if it succeeds or fails. If it fails, it returns immediately +// without attempting the syscall on other pthreads. If the initial +// attempt succeeds, however, then the runtime is stopped in order for +// the same system call to be performed on all the remaining pthreads +// of the runtime. Once all pthreads have completed the syscall, the +// return codes are those obtained by the first pthread's invocation +// of the syscall. +// +// Note, there is no need to use this variant of syscall where the +// syscalls only read state from the kernel. However, since Go's +// runtime freely migrates code execution between pthreads, support of +// this type is required for any successful attempt to fully drop or +// modify the privilege of a running Go program under Linux. +// +// More info on how Linux privilege works and examples of using this +// package can be found here: +// +// https://sites.google.com/site/fullycapable +// +// WARNING: For older go toolchains (prior to go1.15), correct +// compilation of this package may require an extra workaround step: +// +// The workaround is to build with the following CGO_LDFLAGS_ALLOW in +// effect (here the syntax is that of bash for defining an environment +// variable): +// +// export CGO_LDFLAGS_ALLOW="-Wl,-?-wrap[=,][^-.@][^,]*" +// +// +// Copyright (c) 2019,20 Andrew G. Morgan <morgan@kernel.org> +// +// The psx package is licensed with a (you choose) BSD 3-clause or +// GPL2. See LICENSE file for details. +package psx // import "kernel.org/pub/linux/libs/security/libcap/psx" @@ -1,113 +1,13 @@ -// Package psx provides support for system calls that are run -// simultanously on all pthreads. -// -// This property can be used to work around a lack of native Go -// support for such a feature. Something that is the subject of: -// -// https://github.com/golang/go/issues/1435 -// -// The package works via CGo wrappers for system call functions that -// call the C [lib]psx functions of these names. This ensures that the -// system calls execute simultaneously on all the pthreads of the Go -// (and CGo) combined runtime. -// -// The psx support works in the following way: the pthread that is -// first asked to execute the syscall does so, and determines if it -// succeeds or fails. If it fails, it returns immediately without -// attempting the syscall on other pthreads. If the initial attempt -// succeeds, however, then the runtime is stopped in order for the -// same system call to be performed on all the remaining pthreads of -// the runtime. Once all pthreads have completed the syscall, the -// return codes are those obtained by the first pthread's invocation -// of the syscall. -// -// Note, there is no need to use this variant of syscall where the -// syscalls only read state from the kernel. However, since Go's -// runtime freely migrates code execution between pthreads, support of -// this type is required for any successful attempt to fully drop or -// modify the privilege of a running Go program under Linux. -// -// More info on how Linux privilege works can be found here: -// -// https://sites.google.com/site/fullycapable -// -// WARNING: Correct compilation of this package may require an extra -// step: -// -// If your Go compiler is older than go1.15, a workaround may be -// required to be able to link this package. In order to do what it -// needs to this package employs some unusual linking flags. -// -// The workaround is to build with the following CGO_LDFLAGS_ALLOW -// in effect: -// -// export CGO_LDFLAGS_ALLOW="-Wl,-?-wrap[=,][^-.@][^,]*" -// -// -// Copyright (c) 2019,20 Andrew G. Morgan <morgan@kernel.org> -// -// The psx package is licensed with a (you choose) BSD 3-clause or -// GPL2. See LICENSE file for details. +// +build linux,!cgo +// +build go1.16 allthreadssyscall + package psx // import "kernel.org/pub/linux/libs/security/libcap/psx" import ( - "runtime" "syscall" ) -// #cgo LDFLAGS: -lpthread -Wl,-wrap,pthread_create -// -// #include <errno.h> -// #include "psx_syscall.h" -// -// long __errno_too(long set_errno) { -// long v = errno; -// if (set_errno >= 0) { -// errno = set_errno; -// } -// return v; -// } -import "C" - -// setErrno returns the current C.errno value and, if v >= 0, sets the -// CGo errno for a random pthread to value v. If you want some -// consistency, this needs to be called from runtime.LockOSThread() -// code. This function is only defined for testing purposes. The psx.c -// code should properly handle the case that a non-zero errno is saved -// and restored independently of what these Syscall[36]() functions -// observe. -func setErrno(v int) int { - return int(C.__errno_too(C.long(v))) -} - -// Syscall3 performs a 3 argument syscall using the libpsx C function -// psx_syscall3(). Syscall3 differs from syscall.[Raw]Syscall() -// insofar as it is simultaneously executed on every pthread of the -// combined Go and CGo runtimes. -func Syscall3(syscallnr, arg1, arg2, arg3 uintptr) (uintptr, uintptr, syscall.Errno) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - v := C.psx_syscall3(C.long(syscallnr), C.long(arg1), C.long(arg2), C.long(arg3)) - var errno syscall.Errno - if v < 0 { - errno = syscall.Errno(C.__errno_too(-1)) - } - return uintptr(v), uintptr(v), errno -} - -// Syscall6 performs a 6 argument syscall using the libpsx C function -// psx_syscall6(). Syscall6 differs from syscall.[Raw]Syscall6() insofar as -// it is simultaneously executed on every pthread of the combined Go -// and CGo runtimes. -func Syscall6(syscallnr, arg1, arg2, arg3, arg4, arg5, arg6 uintptr) (uintptr, uintptr, syscall.Errno) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - v := C.psx_syscall6(C.long(syscallnr), C.long(arg1), C.long(arg2), C.long(arg3), C.long(arg4), C.long(arg5), C.long(arg6)) - var errno syscall.Errno - if v < 0 { - errno = syscall.Errno(C.__errno_too(-1)) - } - return uintptr(v), uintptr(v), errno -} +var ( + Syscall3 = syscall.AllThreadsSyscall + Syscall6 = syscall.AllThreadsSyscall6 +) diff --git a/psx/psx_cgo.go b/psx/psx_cgo.go new file mode 100644 index 0000000..c17b4f3 --- /dev/null +++ b/psx/psx_cgo.go @@ -0,0 +1,65 @@ +// +build linux,cgo + +package psx // import "kernel.org/pub/linux/libs/security/libcap/psx" + +import ( + "runtime" + "syscall" +) + +// #cgo LDFLAGS: -lpthread -Wl,-wrap,pthread_create +// +// #include <errno.h> +// #include "psx_syscall.h" +// +// long __errno_too(long set_errno) { +// long v = errno; +// if (set_errno >= 0) { +// errno = set_errno; +// } +// return v; +// } +import "C" + +// setErrno returns the current C.errno value and, if v >= 0, sets the +// CGo errno for a random pthread to value v. If you want some +// consistency, this needs to be called from runtime.LockOSThread() +// code. This function is only defined for testing purposes. The psx.c +// code should properly handle the case that a non-zero errno is saved +// and restored independently of what these Syscall[36]() functions +// observe. +func setErrno(v int) int { + return int(C.__errno_too(C.long(v))) +} + +// Syscall3 performs a 3 argument syscall using the libpsx C function +// psx_syscall3(). Syscall3 differs from syscall.[Raw]Syscall() +// insofar as it is simultaneously executed on every pthread of the +// combined Go and CGo runtimes. +func Syscall3(syscallnr, arg1, arg2, arg3 uintptr) (uintptr, uintptr, syscall.Errno) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + v := C.psx_syscall3(C.long(syscallnr), C.long(arg1), C.long(arg2), C.long(arg3)) + var errno syscall.Errno + if v < 0 { + errno = syscall.Errno(C.__errno_too(-1)) + } + return uintptr(v), uintptr(v), errno +} + +// Syscall6 performs a 6 argument syscall using the libpsx C function +// psx_syscall6(). Syscall6 differs from syscall.[Raw]Syscall6() insofar as +// it is simultaneously executed on every pthread of the combined Go +// and CGo runtimes. +func Syscall6(syscallnr, arg1, arg2, arg3, arg4, arg5, arg6 uintptr) (uintptr, uintptr, syscall.Errno) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + v := C.psx_syscall6(C.long(syscallnr), C.long(arg1), C.long(arg2), C.long(arg3), C.long(arg4), C.long(arg5), C.long(arg6)) + var errno syscall.Errno + if v < 0 { + errno = syscall.Errno(C.__errno_too(-1)) + } + return uintptr(v), uintptr(v), errno +} diff --git a/psx/psx_cgo_test.go b/psx/psx_cgo_test.go new file mode 100644 index 0000000..090a96a --- /dev/null +++ b/psx/psx_cgo_test.go @@ -0,0 +1,40 @@ +// +build cgo + +package psx + +import ( + "runtime" + "syscall" + "testing" +) + +// The man page for errno indicates that it is never set to zero, so +// validate that it retains its value over a successful Syscall[36]() +// and is overwritten on a failing syscall. +func TestErrno(t *testing.T) { + // This testing is much easier if we don't have to guess which + // thread is running this Go code. + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + // Start from a known bad state and clean up afterwards. + setErrno(int(syscall.EPERM)) + defer setErrno(0) + + v3, _, errno := Syscall3(syscall.SYS_GETUID, 0, 0, 0) + if errno != 0 { + t.Fatalf("psx getuid failed: %v", errno) + } + v6, _, errno := Syscall6(syscall.SYS_GETUID, 0, 0, 0, 0, 0, 0) + if errno != 0 { + t.Fatalf("psx getuid failed: %v", errno) + } + + if v3 != v6 { + t.Errorf("psx getuid failed to match v3=%d, v6=%d", v3, v6) + } + + if v := setErrno(-1); v != int(syscall.EPERM) { + t.Errorf("psx changes prevailing errno got=%v(%d) want=%v", syscall.Errno(v), v, syscall.EPERM) + } +} diff --git a/psx/psx_test.go b/psx/psx_test.go index 3f0445c..4b90f63 100644 --- a/psx/psx_test.go +++ b/psx/psx_test.go @@ -34,37 +34,6 @@ func TestSyscall6(t *testing.T) { } } -// The man page for errno indicates that it is never set to zero, so -// validate that it retains its value over a successful Syscall[36]() -// and is overwritten on a failing syscall. -func TestErrno(t *testing.T) { - // This testing is much easier if we don't have to guess which - // thread is running this Go code. - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - // Start from a known bad state and clean up afterwards. - setErrno(int(syscall.EPERM)) - defer setErrno(0) - - v3, _, errno := Syscall3(syscall.SYS_GETUID, 0, 0, 0) - if errno != 0 { - t.Fatalf("psx getuid failed: %v", errno) - } - v6, _, errno := Syscall6(syscall.SYS_GETUID, 0, 0, 0, 0, 0, 0) - if errno != 0 { - t.Fatalf("psx getuid failed: %v", errno) - } - - if v3 != v6 { - t.Errorf("psx getuid failed to match v3=%d, v6=%d", v3, v6) - } - - if v := setErrno(-1); v != int(syscall.EPERM) { - t.Errorf("psx changes prevailing errno got=%v(%d) want=%v", syscall.Errno(v), v, syscall.EPERM) - } -} - // killAThread locks the goroutine to a thread and exits. This has the // effect of making the go runtime terminate the thread. func killAThread(c <-chan struct{}) { |