diff options
author | Andrew G. Morgan <morgan@kernel.org> | 2019-11-30 18:33:42 -0800 |
---|---|---|
committer | Andrew G. Morgan <morgan@kernel.org> | 2019-12-06 23:06:47 -0800 |
commit | b2b267ef1c83f1f3d3105a4bb84f8bebbc130dec (patch) | |
tree | d0be8e0daca097a3911006b9eb85fcf4d2607182 | |
parent | e9f55d90e482f680504487be6b3afb80865691d6 (diff) | |
download | libcap-b2b267ef1c83f1f3d3105a4bb84f8bebbc130dec.tar.gz |
Add support to libcap for overriding system call functions.
Note, this override only supports the system calls that
libcap uses to change kernel state associated with the
current process. This is primarily intended to permit the
user to use libpsx to force all pthreads to mirror capability
and other security relevant state.
Use a weak function definition feature of libpsx share_psx_syscall()
to transparently arrange for libcap to so force itself to use the
psx_syscall() abstraction when linked against -lpsx. This has the
effect of using linker magic to make libcap transparently observe
POSIX semantics for security state setting operations. That is, when
linked as follows:
gcc .... -lcap -lpsx -lpthread -Wl,-wrap,pthread_create
all pthreads maintain a common security state with respect to the
libcap API.
This also adds full capability setting support to the Go package
libcap/cap via a libcap/psx package which uses cgo+libpsx syscalls
that share capabilities over all pthreads including those of the
Go runtime.
Finally, if Go supports syscall.PosixSyscall() etc. then provide
a non-psx mechanism for libcap/cap to "just work" in all Go code.
Signed-off-by: Andrew G. Morgan <morgan@kernel.org>
-rw-r--r-- | Make.Rules | 3 | ||||
-rw-r--r-- | cap/.gitignore | 1 | ||||
-rw-r--r-- | go/Makefile | 49 | ||||
-rwxr-xr-x | go/syscalls.sh | 67 | ||||
-rw-r--r-- | go/web.go | 20 | ||||
-rw-r--r-- | libcap/cap_proc.c | 95 | ||||
-rw-r--r-- | libcap/include/sys/capability.h | 15 | ||||
-rw-r--r-- | libcap/libcap.h | 1 | ||||
-rw-r--r-- | psx/psx.go | 37 | ||||
-rw-r--r-- | psx/psx_test.go | 24 | ||||
-rw-r--r-- | tests/.gitignore | 1 | ||||
-rw-r--r-- | tests/Makefile | 10 | ||||
-rw-r--r-- | tests/libcap_psx_test.c | 11 |
13 files changed, 285 insertions, 49 deletions
@@ -79,6 +79,9 @@ ifeq ($(GOLANG),yes) GOROOT := $(shell go env GOROOT) GOCGO := $(shell if [ "$(shell go env CGO_ENABLED)" = 1 ]; then echo yes ; else echo no ; fi) GOOSARCH := $(shell go env GOHOSTOS)_$(shell go env GOHOSTARCH) +CGO_CFLAGS := -I$(topdir)/libcap/include +CGO_LDFLAGS := -L$(topdir)/libcap +CGO_LDFLAGS_ALLOW := -Wl,-wrap,.+ endif # When installing setcap, set its inheritable bit to be able to place diff --git a/cap/.gitignore b/cap/.gitignore index 1c780ed..fa73894 100644 --- a/cap/.gitignore +++ b/cap/.gitignore @@ -1,2 +1,3 @@ names.go syscalls.go +syscalls_cgo.go diff --git a/go/Makefile b/go/Makefile index 0791768..363b664 100644 --- a/go/Makefile +++ b/go/Makefile @@ -4,15 +4,33 @@ topdir=$(realpath ..) include ../Make.Rules -all: - $(MAKE) compare-cap - $(MAKE) web - ./compare-cap +GOPATH="$(realpath .)" +PSXGOPACKAGE=pkg/$(GOOSARCH)/libcap/psx.a +CAPGOPACKAGE=pkg/$(GOOSARCH)/libcap/cap.a + +all: $(PSXGOPACKAGE) $(CAPGOPACKAGE) web compare-cap + +# $(MAKE) compare-cap +# $(MAKE) web +# ./compare-cap + +src/libcap/psx: + mkdir -p src/libcap + ln -s $(realpath ..)/psx src/libcap/ src/libcap/cap: mkdir -p src/libcap ln -s $(realpath ..)/cap src/libcap/ +$(PSXGOPACKAGE): src/libcap/psx ../psx/psx.go ../psx/psx_test.go + CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" GOPATH="$(GOPATH)" go test libcap/psx + mkdir -p pkg + CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" GOPATH="$(GOPATH)" go build libcap/psx + +$(CAPGOPACKAGE): src/libcap/cap/syscalls.go src/libcap/cap/names.go src/libcap/cap/cap.go src/libcap/cap/text.go + CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" GOPATH=$(realpath .) go test libcap/cap + CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" GOPATH=$(realpath .) go build libcap/cap + install: all # TODO - install the Go package somewhere useful (ex. /usr/share/gocode/src/libcap/cap/ ) @@ -22,25 +40,20 @@ install: all src/libcap/cap/names.go: ../libcap/cap_names.h src/libcap/cap mknames.go go run mknames.go --header=$< | gofmt > $@ || rm -f $@ -src/libcap/cap/syscalls.go: src/libcap/cap ./syscalls.sh - ./syscalls.sh > $@ - -GOPACKAGE=pkg/$(GOOSARCH)/libcap/cap.a -$(GOPACKAGE): src/libcap/cap/syscalls.go src/libcap/cap/names.go src/libcap/cap/cap.go src/libcap/cap/text.go - echo testing Go package - GOPATH=$(realpath .) go test libcap/cap - echo building $(GOPACKAGE) - mkdir -p pkg +src/libcap/cap/syscalls.go: ./syscalls.sh src/libcap/cap + ./syscalls.sh src/libcap/cap # Compile and run something with this package and compare it to libcap. -compare-cap: compare-cap.go $(GOPACKAGE) - GOPATH=$(realpath .) go build $< +compare-cap: compare-cap.go $(CAPGOPACKAGE) + CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" GOPATH=$(realpath .) go build $< -web: web.go $(GOPACKAGE) - GOPATH=$(realpath .) go build $< +web: web.go $(CAPGOPACKAGE) + CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" GOPATH=$(realpath .) go build $< clean: GOPATH=$(realpath .) go clean -x -i libcap/cap 2> /dev/null || exit 0 + GOPATH=$(realpath .) go clean -x -i libcap/psx 2> /dev/null || exit 0 rm -f *.o *.so mknames web compare-cap *~ - rm -f ../cap/*~ ../cap/names.go ../cap/syscalls.go + rm -f ../cap/*~ ../cap/names.go ../cap/syscalls*.go + rm -f ../psx/*~ rm -fr pkg src diff --git a/go/syscalls.sh b/go/syscalls.sh index 4966742..eeba450 100755 --- a/go/syscalls.sh +++ b/go/syscalls.sh @@ -1,27 +1,72 @@ #!/bin/bash -cat <<EOF +dir="$1" +if [[ -z "$dir" ]]; then + echo need an argument directory + exit 1 +fi + +# This is something that we should revisit if golang adopts my +# syscall.PosixSyscall patch. At that stage, we won't need cgo to +# support a pure Go program. However, we will need a to use the cgo +# version if the program being compiled actually needs cgo. That is, +# we should have two permenant files that use +build lines to control +# which one is built based on cgo or not. + +if [ -z "$(go doc syscall 2>/dev/null|grep PosixSyscall)" ]; then + rm -f "${dir}/syscalls_cgo.go" + cat > "${dir}/syscalls.go" <<EOF +// +build linux + package cap -import "syscall" +import ( + "libcap/psx" + "syscall" +) // callKernel variables overridable for testing purposes. +// (Go build tree has no syscall.PosixSyscall support.) +var callWKernel = psx.Syscall3 +var callWKernel6 = psx.Syscall6 +var callRKernel = syscall.RawSyscall +var callRKernel6 = syscall.RawSyscall6 EOF -if [ -n "$(go doc syscall 2>/dev/null|grep PosixSyscall)" ]; then - cat <<EOF -// (Go build tree contains PosixSyscall support.) + exit 0 +fi + +# pure Go support. +cat > "${dir}/syscalls.go" <<EOF +// +build linux,!cgo + +package cap + +import "syscall" + +// callKernel variables overridable for testing purposes. +// (Go build tree contains syscall.PosixSyscall support.) var callWKernel = syscall.PosixSyscall var callWKernel6 = syscall.PosixSyscall6 var callRKernel = syscall.RawSyscall var callRKernel6 = syscall.RawSyscall6 EOF -else - cat <<EOF -// (Go build tree does not contain PosixSyscall support.) -var callWKernel = syscall.RawSyscall -var callWKernel6 = syscall.RawSyscall6 + +cat > "${dir}/syscalls_cgo.go" <<EOF +// +build linux,cgo + +package cap + +import ( + "libcap/psx" + "syscall" +) + +// callKernel variables overridable for testing purposes. +// We use this version when we are cgo compiling because +// we need to manage the native C pthreads too. +var callWKernel = psx.Syscall3 +var callWKernel6 = psx.Syscall6 var callRKernel = syscall.RawSyscall var callRKernel6 = syscall.RawSyscall6 EOF -fi @@ -1,15 +1,23 @@ // Progam web provides an example of a webserver using capabilities to -// bind to a privileged port. +// bind to a privileged port, and then drop all capabilities before +// handling the first web request. // -// This program will not work reliably without the equivalent of -// the Go runtime patch that adds a POSIX semantics wrappers around -// the system calls that change kernel state. A patch for the Go -// compiler/runtime to add this support is available here [2019-11-16]: +// This program cannot work reliably as a pure Go application without +// the equivalent of the Go runtime patch that adds a POSIX semantics +// wrapper around the system calls that change kernel state. A patch +// for the pure Go compiler/runtime to add this support is available +// here [2019-11-16]: // // https://git.kernel.org/pub/scm/libs/libcap/libcap.git/tree/contrib/golang/go.patch // +// Until that patch, or something like it, is absorbed into the Go +// runtime the only way to get capabilities to work reliably on the Go +// runtime is to use something like libpsx to do capability setting +// syscalls in C with POSIX semantics. As of this build of the Go +// libcap/cap package, this is how things work. +// // To set this up, compile and empower this binary as follows (package -// libcap/cap should be installed): +// libcap/cap should be installed, as must libpsx.a): // // go build web.go // sudo setcap cap_net_bind_service=p web diff --git a/libcap/cap_proc.c b/libcap/cap_proc.c index f70b0e3..3f66d58 100644 --- a/libcap/cap_proc.c +++ b/libcap/cap_proc.c @@ -1,13 +1,87 @@ /* - * Copyright (c) 1997-8,2007,2011 Andrew G Morgan <morgan@kernel.org> + * Copyright (c) 1997-8,2007,2011,2019 Andrew G Morgan <morgan@kernel.org> * * This file deals with getting and setting capabilities on processes. */ +#include <sys/syscall.h> #include <sys/prctl.h> +#include <unistd.h> #include "libcap.h" +/* + * libcap uses this abstraction for all system calls that change + * kernel managed capability state. This permits the user to redirect + * it for testing and also to better implement posix semantics when + * using pthreads. + */ + +static long int _cap_syscall(long int syscall_nr, + long int arg1, long int arg2, long int arg3) +{ + return syscall(syscall_nr, arg1, arg2, arg3); +} + +static long int _cap_syscall6(long int syscall_nr, + long int arg1, long int arg2, long int arg3, + long int arg4, long int arg5, long int arg6) +{ + return syscall(syscall_nr, arg1, arg2, arg3, arg4, arg5, arg6); +} + +static long int (*_libcap_syscall)(long int, long int, long int, long int) + = _cap_syscall; +static long int (*_libcap_syscall6)(long int, long int, long int, long int, + long int, long int, long int) = _cap_syscall6; + +void cap_set_syscall(long int (*new_syscall)(long int, + long int, long int, long int), + long int (*new_syscall6)(long int, + long int, long int, long int, + long int, long int, long int)) +{ + _libcap_syscall = new_syscall; + _libcap_syscall6 = new_syscall6; +} + +/* + * libcap<->libpsx subtle linking trick. If -lpsx is linked, then this + * function will get called when psx is initialized. In so doing, + * libcap will opt to use POSIX compliant syscalls for all state + * changing system calls - via psx_syscall(). + */ +void share_psx_syscall(long int (*syscall_fn)(long int, + long int, long int, long int), + long int (*syscall6_fn)(long int, + long int, long int, long int, + long int, long int, long int)); + +void share_psx_syscall(long int (*syscall_fn)(long int, + long int, long int, long int), + long int (*syscall6_fn)(long int, + long int, long int, long int, + long int, long int, long int)) +{ + cap_set_syscall(syscall_fn, syscall6_fn); +} + +static int _libcap_capset(cap_user_header_t header, const cap_user_data_t data) +{ + return _libcap_syscall(SYS_capset, (long int) header, (long int) data, 0); +} + +static int _libcap_prctl(long int pr_cmd, long int arg1, long int arg2) +{ + return _libcap_syscall(SYS_prctl, pr_cmd, arg1, arg2); +} + +static int _libcap_prctl6(long int pr_cmd, long int arg1, long int arg2, + long int arg3, long int arg4, long int arg5) +{ + return _libcap_syscall6(SYS_prctl, pr_cmd, arg1, arg2, arg3, arg4, arg5); +} + cap_t cap_get_proc(void) { cap_t result; @@ -37,7 +111,7 @@ int cap_set_proc(cap_t cap_d) } _cap_debug("setting process capabilities"); - retval = capset(&cap_d->head, &cap_d->u[0].set); + retval = _libcap_capset(&cap_d->head, &cap_d->u[0].set); return retval; } @@ -85,7 +159,10 @@ cap_t cap_get_pid(pid_t pid) return result; } -/* set the caps on a specific process/pg etc.. */ +/* + * set the caps on a specific process/pg etc.. The kernel has long + * since deprecated this asynchronus interface. + */ int capsetp(pid_t pid, cap_t cap_d) { @@ -114,7 +191,7 @@ int cap_get_bound(cap_value_t cap) { int result; - result = prctl(PR_CAPBSET_READ, pr_arg(cap)); + result = _libcap_prctl(PR_CAPBSET_READ, pr_arg(cap), pr_arg(0)); if (result < 0) { errno = -result; return -1; @@ -128,7 +205,7 @@ int cap_drop_bound(cap_value_t cap) { int result; - result = prctl(PR_CAPBSET_DROP, pr_arg(cap)); + result = _libcap_prctl(PR_CAPBSET_DROP, pr_arg(cap), pr_arg(0)); if (result < 0) { errno = -result; return -1; @@ -166,8 +243,8 @@ int cap_set_ambient(cap_value_t cap, cap_flag_value_t set) errno = EINVAL; return -1; } - result = prctl(PR_CAP_AMBIENT, pr_arg(val), pr_arg(cap), - pr_arg(0), pr_arg(0)); + result = _libcap_prctl6(PR_CAP_AMBIENT, pr_arg(val), pr_arg(cap), + pr_arg(0), pr_arg(0), pr_arg(0)); if (result < 0) { errno = -result; return -1; @@ -181,8 +258,8 @@ int cap_reset_ambient() { int result; - result = prctl(PR_CAP_AMBIENT, pr_arg(PR_CAP_AMBIENT_CLEAR_ALL), - pr_arg(0), pr_arg(0), pr_arg(0)); + result = _libcap_prctl6(PR_CAP_AMBIENT, pr_arg(PR_CAP_AMBIENT_CLEAR_ALL), + pr_arg(0), pr_arg(0), pr_arg(0), pr_arg(0)); if (result < 0) { errno = -result; return -1; diff --git a/libcap/include/sys/capability.h b/libcap/include/sys/capability.h index 0be6e64..4bfc7e5 100644 --- a/libcap/include/sys/capability.h +++ b/libcap/include/sys/capability.h @@ -2,7 +2,7 @@ * <sys/capability.h> * * Copyright (C) 1997 Aleph One - * Copyright (C) 1997-8,2008 Andrew G. Morgan <morgan@kernel.org> + * Copyright (C) 1997-8,2008,2019 Andrew G. Morgan <morgan@kernel.org> * * defunct POSIX.1e Standard: 25.2 Capabilities <sys/capability.h> */ @@ -115,7 +115,18 @@ extern char * cap_to_name(cap_value_t); #define CAP_DIFFERS(result, flag) (((result) & (1 << (flag))) != 0) extern int cap_compare(cap_t, cap_t); -/* system calls - look to libc for function to system call mapping */ +/* libcap/cap_proc.c */ +extern void cap_set_syscall(long int (*new_syscall)(long int, + long int, long int, long int), + long int (*new_syscall6)(long int, + long int, long int, long int, + long int, long int, long int)); + +/* + * system calls - look to libc for function to system call + * mapping. Note, libcap does not use capset directly, but permits the + * cap_set_syscall() to redirect the system call function. + */ extern int capget(cap_user_header_t header, cap_user_data_t data); extern int capset(cap_user_header_t header, const cap_user_data_t data); diff --git a/libcap/libcap.h b/libcap/libcap.h index 0156a92..b79159b 100644 --- a/libcap/libcap.h +++ b/libcap/libcap.h @@ -187,7 +187,6 @@ extern char *_libcap_strdup(const char *text); */ extern int capget(cap_user_header_t header, cap_user_data_t data); -extern int capset(cap_user_header_t header, const cap_user_data_t data); extern int capgetp(pid_t pid, cap_t cap_d); extern int capsetp(pid_t pid, cap_t cap_d); diff --git a/psx/psx.go b/psx/psx.go new file mode 100644 index 0000000..e90297d --- /dev/null +++ b/psx/psx.go @@ -0,0 +1,37 @@ +// Package psx provides Go wrappers for two system call functions that +// work by calling the C libpsx functions of these names. +package psx + +import ( + "syscall" +) + +// #cgo LDFLAGS: -lpsx -lpthread -Wl,-wrap,pthread_create +// +// #include <errno.h> +// #include <sys/psx_syscall.h> +// +// long __errno_too() { return errno ; } +import "C" + +// Syscall3 performs a 3 argument syscall using the libpsx C function +// psx_syscall3(). +func Syscall3(syscallnr, arg1, arg2, arg3 uintptr) (uintptr, uintptr, syscall.Errno) { + 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()) + } + return uintptr(v), uintptr(v), errno +} + +// Syscall6 performs a 6 argument syscall using the libpsx C function +// psx_syscall6() +func Syscall6(syscallnr, arg1, arg2, arg3, arg4, arg5, arg6 uintptr) (uintptr, uintptr, syscall.Errno) { + 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()) + } + return uintptr(v), uintptr(v), errno +} diff --git a/psx/psx_test.go b/psx/psx_test.go new file mode 100644 index 0000000..8dd67ca --- /dev/null +++ b/psx/psx_test.go @@ -0,0 +1,24 @@ +package psx + +import ( + "syscall" + "testing" +) + +func TestSyscall3(t *testing.T) { + want := syscall.Getpid() + if got, _, err := Syscall3(syscall.SYS_GETPID, 0, 0, 0); err != 0 { + t.Errorf("failed to get PID via libpsx: %v", err) + } else if int(got) != want { + t.Errorf("pid mismatch: got=%d want=%d", got, want) + } +} + +func TestSyscall6(t *testing.T) { + want := syscall.Getpid() + if got, _, err := Syscall6(syscall.SYS_GETPID, 0, 0, 0, 0, 0, 0); err != 0 { + t.Errorf("failed to get PID via libpsx: %v", err) + } else if int(got) != want { + t.Errorf("pid mismatch: got=%d want=%d", got, want) + } +} diff --git a/tests/.gitignore b/tests/.gitignore index 42afee3..b2a7222 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1,2 +1,3 @@ psx_test psx_test_wrap +libcap_psx_test diff --git a/tests/Makefile b/tests/Makefile index 739e864..b16bdcd 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -5,7 +5,7 @@ topdir=$(shell pwd)/.. include ../Make.Rules # -all: run_psx_test +all: run_psx_test run_libcap_psx_test install: all @@ -19,5 +19,11 @@ psx_test: psx_test.c psx_test_wrap: psx_test.c $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LIBPSXLIB) -Wl,-wrap,pthread_create +run_libcap_psx_test: libcap_psx_test + ./libcap_psx_test + +libcap_psx_test: libcap_psx_test.c + $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LIBCAPLIB) $(LIBPSXLIB) -Wl,-wrap,pthread_create --static + clean: - rm -f psx_test psx_test_wrap + rm -f psx_test psx_test_wrap libcap_psx_test diff --git a/tests/libcap_psx_test.c b/tests/libcap_psx_test.c new file mode 100644 index 0000000..4b09bb0 --- /dev/null +++ b/tests/libcap_psx_test.c @@ -0,0 +1,11 @@ +#include <pthread.h> +#include <stdio.h> +#include <sys/capability.h> +#include <sys/psx_syscall.h> + +int main(int argc, char **argv) { + printf("hello libcap and libpsx\n"); + psx_register(pthread_self()); + cap_t start = cap_get_proc(); + cap_set_proc(start); +} |