aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew G. Morgan <morgan@kernel.org>2019-11-30 18:33:42 -0800
committerAndrew G. Morgan <morgan@kernel.org>2019-12-06 23:06:47 -0800
commitb2b267ef1c83f1f3d3105a4bb84f8bebbc130dec (patch)
treed0be8e0daca097a3911006b9eb85fcf4d2607182
parente9f55d90e482f680504487be6b3afb80865691d6 (diff)
downloadlibcap-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.Rules3
-rw-r--r--cap/.gitignore1
-rw-r--r--go/Makefile49
-rwxr-xr-xgo/syscalls.sh67
-rw-r--r--go/web.go20
-rw-r--r--libcap/cap_proc.c95
-rw-r--r--libcap/include/sys/capability.h15
-rw-r--r--libcap/libcap.h1
-rw-r--r--psx/psx.go37
-rw-r--r--psx/psx_test.go24
-rw-r--r--tests/.gitignore1
-rw-r--r--tests/Makefile10
-rw-r--r--tests/libcap_psx_test.c11
13 files changed, 285 insertions, 49 deletions
diff --git a/Make.Rules b/Make.Rules
index 1ccead1..00628e1 100644
--- a/Make.Rules
+++ b/Make.Rules
@@ -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
diff --git a/go/web.go b/go/web.go
index 0d5a943..70a9668 100644
--- a/go/web.go
+++ b/go/web.go
@@ -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);
+}