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>
This commit is contained in:
Andrew G. Morgan 2020-12-11 23:27:50 -08:00
parent e7e0e1b9e2
commit 90192cd364
8 changed files with 190 additions and 178 deletions

View File

@ -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,
}

View File

@ -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,
}

View File

@ -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

60
psx/doc.go Normal file
View File

@ -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"

View File

@ -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
)

65
psx/psx_cgo.go Normal file
View File

@ -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
}

40
psx/psx_cgo_test.go Normal file
View File

@ -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)
}
}

View File

@ -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{}) {