summaryrefslogtreecommitdiff
path: root/psx
diff options
context:
space:
mode:
authorAndrew G. Morgan <morgan@kernel.org>2020-07-03 15:27:09 -0700
committerAndrew G. Morgan <morgan@kernel.org>2020-07-03 15:40:58 -0700
commitffa7df01d30b5e4c26eb955d9baf76a7c8e0d1b1 (patch)
tree6f4740f784786289890cedf53521755321defe4a /psx
parentb0d13e87db7ac821e6688cb32078cf7393d3f202 (diff)
downloadlibcap2-ffa7df01d30b5e4c26eb955d9baf76a7c8e0d1b1.tar.gz
Refactored the psx package to build as a Go module.
Cleaned up the Go module redirection html file, now installed at: https://kernel.org/pub/linux/libs/security/libcap/ Note, I've moved the C source for libpsx.a into the psx/ directory, but the libpsx.a file is still built in the libcap subdirectory as before. I also symlinked the C include files from the psx/ directory. This made the source compile in conjuction with the "psx" Go package automatically. It also substantially simplified the go/Makefile. I feel pretty good about this next version from the perspective of a viable "psx" build. Caveat the need for CGO_LDFLAGS_ALLOW on the command line pre-go1.15. Hopefully, the psx package comment is enough for folk to figure that detail out. Signed-off-by: Andrew G. Morgan <morgan@kernel.org>
Diffstat (limited to 'psx')
l---------psx/include1
-rw-r--r--psx/index.html15
-rw-r--r--psx/psx.c550
-rw-r--r--psx/psx.go31
4 files changed, 560 insertions, 37 deletions
diff --git a/psx/include b/psx/include
new file mode 120000
index 0000000..ff69dae
--- /dev/null
+++ b/psx/include
@@ -0,0 +1 @@
+../libcap/include \ No newline at end of file
diff --git a/psx/index.html b/psx/index.html
deleted file mode 100644
index 9400b63..0000000
--- a/psx/index.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-<meta name="go-import" content="kernel.org/pub/linux/libs/security/libcap/psx git git.kernel.org/pub/scm/libs/libcap/libcap.git/psx">
-<meta http-equiv="refresh" content="10; url=https://sites.google.com/site/fullycapable">
-</head>
-<body>
- Redirecting in 10 seconds to
- the <a href="https://sites.google.com/site/fullycapable">Fully
- Capable</a> project page: the home of the
- "kernel.org/pub/linux/libs/security/libcap/psx" and
- "kernel.org/pub/linux/libs/security/libcap/cap" Go packages.
-</body>
-</html>
diff --git a/psx/psx.c b/psx/psx.c
new file mode 100644
index 0000000..92b43dc
--- /dev/null
+++ b/psx/psx.c
@@ -0,0 +1,550 @@
+/*
+ * Copyright (c) 2019,20 Andrew G Morgan <morgan@kernel.org>
+ *
+ * This file contains a collection of routines that perform thread
+ * synchronization to ensure that a whole process is running as a
+ * single privilege entity - independent of the number of pthreads.
+ *
+ * The whole file would be unnecessary if glibc exported an explicit
+ * psx_syscall()-like function that leveraged the nptl:setxid
+ * mechanism to synchronize thread state over the whole process.
+ */
+#undef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 199309L
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <errno.h>
+#include <pthread.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/psx_syscall.h>
+#include <sys/syscall.h>
+
+/*
+ * psx_load_syscalls() is weakly defined so we can have it overriden
+ * by libpsx if it is linked. Specifically, when libcap calls
+ * psx_load_sycalls it will override their defaut values. As can be
+ * seen here this present function is a no-op. However, if libpsx is
+ * linked, the one present in that library (not being weak) will
+ * replace this one.
+ */
+void psx_load_syscalls(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))
+{
+ *syscall_fn = psx_syscall3;
+ *syscall6_fn = psx_syscall6;
+}
+
+/*
+ * type to keep track of registered threads.
+ */
+typedef struct registered_thread_s {
+ struct registered_thread_s *next, *prev;
+ pthread_t thread;
+ pthread_mutex_t mu;
+ int pending;
+} registered_thread_t;
+
+static pthread_once_t psx_tracker_initialized = PTHREAD_ONCE_INIT;
+
+typedef enum {
+ _PSX_IDLE = 0,
+ _PSX_SETUP = 1,
+ _PSX_SYSCALL = 2,
+ _PSX_CREATE = 3,
+ _PSX_INFORK = 4
+} psx_tracker_state_t;
+
+/*
+ * This global structure holds the global coordination state for
+ * libcap's psx_posix_syscall() support.
+ */
+static struct psx_tracker_s {
+ int has_forked;
+
+ pthread_mutex_t state_mu;
+ pthread_cond_t cond; /* this is only used to wait on 'state' changes */
+ psx_tracker_state_t state;
+ int (*creator)(pthread_t *thread, const pthread_attr_t *attr,
+ void *(*start_routine) (void *), void *arg);
+ int initialized;
+ int psx_sig;
+
+ struct {
+ long syscall_nr;
+ long arg1, arg2, arg3, arg4, arg5, arg6;
+ int six;
+ int active;
+ } cmd;
+
+ struct sigaction sig_action;
+ registered_thread_t *root;
+} psx_tracker;
+
+/*
+ * psx_action_key is used for thread local storage of the thread's
+ * registration. */
+pthread_key_t psx_action_key;
+
+/*
+ * psx_do_registration called locked and creates a tracker entry for
+ * the current thread with a TLS specific key pointing at the threads
+ * specific tracker.
+ */
+static void psx_do_registration(void) {
+ registered_thread_t *node = calloc(1, sizeof(registered_thread_t));
+ pthread_mutex_init(&node->mu, NULL);
+ node->thread = pthread_self();
+ pthread_setspecific(psx_action_key, node);
+ node->next = psx_tracker.root;
+ if (node->next) {
+ node->next->prev = node;
+ }
+ psx_tracker.root = node;
+}
+
+/*
+ * psx_posix_syscall_actor performs the system call on the targeted
+ * thread and signals it is no longer pending.
+ */
+static void psx_posix_syscall_actor(int signum, siginfo_t *info, void *ignore) {
+ /* bail early if this isn't something we recognize */
+ if (signum != psx_tracker.psx_sig || !psx_tracker.cmd.active ||
+ info == NULL || info->si_code != SI_TKILL || info->si_pid != getpid()) {
+ return;
+ }
+
+ if (!psx_tracker.cmd.six) {
+ (void) syscall(psx_tracker.cmd.syscall_nr,
+ psx_tracker.cmd.arg1,
+ psx_tracker.cmd.arg2,
+ psx_tracker.cmd.arg3);
+ } else {
+ (void) syscall(psx_tracker.cmd.syscall_nr,
+ psx_tracker.cmd.arg1,
+ psx_tracker.cmd.arg2,
+ psx_tracker.cmd.arg3,
+ psx_tracker.cmd.arg4,
+ psx_tracker.cmd.arg5,
+ psx_tracker.cmd.arg6);
+ }
+
+ /*
+ * This handler can only be called on registered threads which
+ * have had this specific defined at start-up. (But see the
+ * subsequent test.)
+ */
+ registered_thread_t *ref = pthread_getspecific(psx_action_key);
+ if (ref) {
+ pthread_mutex_lock(&ref->mu);
+ ref->pending = 0;
+ pthread_mutex_unlock(&ref->mu);
+ } /*
+ * else thread must be dying and its psx_action_key has already
+ * been cleaned up.
+ */
+}
+
+/*
+ * Some forward declarations for the initialization
+ * psx_syscall_start() routine.
+ */
+static void _psx_prepare_fork(void);
+static void _psx_fork_completed(void);
+static void _psx_forked_child(void);
+int __wrap_pthread_create(pthread_t *thread, const pthread_attr_t *attr,
+ void *(*start_routine) (void *), void *arg);
+
+/*
+ * psx_syscall_start initializes the subsystem including initializing
+ * the mutex.
+ */
+static void psx_syscall_start(void) {
+ pthread_mutex_init(&psx_tracker.state_mu, NULL);
+ pthread_cond_init(&psx_tracker.cond, NULL);
+ pthread_key_create(&psx_action_key, NULL);
+ psx_tracker.creator = (pthread_create == __wrap_pthread_create ?
+ __real_pthread_create : pthread_create);
+ pthread_atfork(_psx_prepare_fork, _psx_fork_completed, _psx_forked_child);
+
+ /*
+ * glibc nptl picks from the SIGRTMIN end, so we pick from the
+ * SIGRTMAX end
+ */
+ psx_tracker.psx_sig = SIGRTMAX;
+ psx_tracker.sig_action.sa_sigaction = psx_posix_syscall_actor;
+ sigemptyset(&psx_tracker.sig_action.sa_mask);
+ psx_tracker.sig_action.sa_flags = SA_SIGINFO | SA_RESTART;;
+ sigaction(psx_tracker.psx_sig, &psx_tracker.sig_action, NULL);
+
+ psx_do_registration(); // register the main thread.
+
+ psx_tracker.initialized = 1;
+}
+
+/*
+ * This is the only way this library globally locks. Note, this is not
+ * to be confused with psx_sig (interrupt) blocking - which is
+ * performed around thread creation.
+ */
+static void psx_lock(void)
+{
+ pthread_once(&psx_tracker_initialized, psx_syscall_start);
+ pthread_mutex_lock(&psx_tracker.state_mu);
+}
+
+/*
+ * This is the only way this library unlocks.
+ */
+static void psx_unlock(void)
+{
+ pthread_mutex_unlock(&psx_tracker.state_mu);
+}
+
+/*
+ * under lock perform a state transition.
+ */
+static void psx_new_state(psx_tracker_state_t was, psx_tracker_state_t is)
+{
+ psx_lock();
+ while (psx_tracker.state != was) {
+ pthread_cond_wait(&psx_tracker.cond, &psx_tracker.state_mu);
+ }
+ psx_tracker.state = is;
+ if (is == _PSX_IDLE) {
+ /* only announce newly idle states since that is all we wait for */
+ pthread_cond_signal(&psx_tracker.cond);
+ }
+ psx_unlock();
+}
+
+long int psx_syscall3(long int syscall_nr,
+ long int arg1, long int arg2, long int arg3) {
+ return psx_syscall(syscall_nr, arg1, arg2, arg3);
+}
+
+long int psx_syscall6(long int syscall_nr,
+ long int arg1, long int arg2, long int arg3,
+ long int arg4, long int arg5, long int arg6) {
+ return psx_syscall(syscall_nr, arg1, arg2, arg3, arg4, arg5, arg6);
+}
+
+static void _psx_prepare_fork(void) {
+ /*
+ * obtain global lock - we don't want any syscalls while the fork
+ * is occurring since it may interfere with the preparation for
+ * the fork.
+ */
+ psx_new_state(_PSX_IDLE, _PSX_INFORK);
+}
+
+static void _psx_fork_completed(void) {
+ /*
+ * The only way we can get here is if state is _PSX_INFORK and was
+ * previously _PSX_IDLE. Now that the fork has completed, the
+ * parent can continue as if it hadn't happened - the forked child
+ * does not tie its security state to that of the parent process
+ * and threads.
+ *
+ * We don't strictly need to change the psx_tracker.state since we
+ * hold the mutex over the fork, but we do to make deadlock
+ * debugging easier.
+ */
+ psx_new_state(_PSX_INFORK, _PSX_IDLE);
+}
+
+static void _psx_forked_child(void) {
+ /*
+ * The only way we can get here is if state is _PSX_INFORK and was
+ * previously _PSX_IDLE. However, none of the registered threads
+ * exist in this newly minted child process, so we have to reset
+ * the tracking structure to avoid any confusion. We also skuttle
+ * any chance of the PSX API working on more than one thread in
+ * the child by leaving the state as _PSX_INFORK. We do support
+ * all psx_syscall()s by reverting to them being direct in the
+ * fork()ed child.
+ *
+ * We do this because the glibc man page for fork() suggests that
+ * only a subset of things will work post fork(). Specifically,
+ * only a "async-signal-safe functions (see signal- safety(7))
+ * until such time as it calls execve(2)" can be relied upon. That
+ * man page suggests that you can't expect mutexes to work: "not
+ * async-signal-safe because it uses pthread_mutex_lock(3)
+ * internally.".
+ */
+ registered_thread_t *next, *old_root;
+ old_root = psx_tracker.root;
+ psx_tracker.root = NULL;
+
+ psx_tracker.has_forked = 1;
+
+ for (; old_root; old_root = next) {
+ next = old_root->next;
+ memset(old_root, 0, sizeof(*old_root));
+ free(old_root);
+ }
+}
+
+/*
+ * called locked to unregister a node from the tracker.
+ */
+static void psx_do_unregister(registered_thread_t *node) {
+ if (psx_tracker.root == node) {
+ psx_tracker.root = node->next;
+ }
+ if (node->next) {
+ node->next->prev = node->prev;
+ }
+ if (node->prev) {
+ node->prev->next = node->next;
+ }
+ pthread_mutex_destroy(&node->mu);
+ memset(node, 0, sizeof(*node));
+ free(node);
+}
+
+/*
+ * psx_register can be used to explicitly register a thread once
+ * created. In general, it shouldn't be needed. Further, it should
+ * never be used to register the main thread.
+ */
+void psx_register(void) {
+ psx_lock();
+ psx_do_registration();
+ psx_unlock();
+}
+
+typedef struct {
+ void *(*fn)(void *);
+ void *arg;
+ sigset_t sigbits;
+} psx_starter_t;
+
+/*
+ * _psx_start_fn is a trampolene for the intended start function, it
+ * is called both blocked and locked, but releases the (b)lock before
+ * calling starter->fn. Before releasing the (b)lock the TLS specific
+ * attribute(s) are initialized for use by the interrupt handler under
+ * the psx mutex, so it doesn't race with an interrupt received by
+ * this thread and the interrupt handler does not need to poll for
+ * that specific attribute to be present (which is problematic during
+ * thread shutdown).
+ */
+static void *_psx_start_fn(void *data) {
+ psx_do_registration();
+ psx_new_state(_PSX_CREATE, _PSX_IDLE);
+
+ psx_starter_t *starter = data;
+ pthread_sigmask(SIG_SETMASK, &starter->sigbits, NULL);
+
+ void *(*fn)(void *) = starter->fn;
+ void *arg = starter->arg;
+ memset(data, 0, sizeof(*starter));
+ free(data);
+
+ return fn(arg);
+}
+
+/*
+ * __wrap_pthread_create is the wrapped destination of all regular
+ * pthread_create calls.
+ */
+int __wrap_pthread_create(pthread_t *thread, const pthread_attr_t *attr,
+ void *(*start_routine) (void *), void *arg) {
+ psx_starter_t *starter = calloc(1, sizeof(psx_starter_t));
+ starter->fn = start_routine;
+ starter->arg = arg;
+ /*
+ * Until we are in the _PSX_IDLE state and locked, we must not
+ * block the psx_sig interrupt for this parent thread. Arrange
+ * that parent thread and newly created one can restore signal
+ * mask.
+ */
+ sigset_t sigbit, orig_sigbits;
+ sigemptyset(&sigbit);
+ pthread_sigmask(SIG_UNBLOCK, &sigbit, &starter->sigbits);
+ sigaddset(&sigbit, psx_tracker.psx_sig);
+ pthread_sigmask(SIG_UNBLOCK, &sigbit, &orig_sigbits);
+
+ psx_new_state(_PSX_IDLE, _PSX_CREATE);
+
+ /*
+ * until the child thread has been blessed with its own TLS
+ * specific attribute(s) we prevent either the parent thread or
+ * the new one from experiencing a PSX interrupt.
+ */
+ pthread_sigmask(SIG_BLOCK, &sigbit, NULL);
+
+ int ret = psx_tracker.creator(thread, attr, _psx_start_fn, starter);
+ if (ret == -1) {
+ psx_new_state(_PSX_CREATE, _PSX_IDLE);
+ memset(starter, 0, sizeof(*starter));
+ free(starter);
+ } /* else unlock happens in _psx_start_fn */
+
+ /* the parent can once again receive psx interrupt signals */
+ pthread_sigmask(SIG_SETMASK, &orig_sigbits, NULL);
+
+ return ret;
+}
+
+/*
+ * psx_pthread_create is a wrapper for pthread_create() that registers
+ * the newly created thread. If your threads are created already, they
+ * can be individually registered with psx_register().
+ */
+int psx_pthread_create(pthread_t *thread, const pthread_attr_t *attr,
+ void *(*start_routine) (void *), void *arg) {
+ return __wrap_pthread_create(thread, attr, start_routine, arg);
+}
+
+/*
+ * __psx_immediate_syscall does one syscall using the current
+ * process.
+ */
+static long int __psx_immediate_syscall(long int syscall_nr,
+ int count, long int *arg) {
+ psx_tracker.cmd.syscall_nr = syscall_nr;
+ psx_tracker.cmd.arg1 = count > 0 ? arg[0] : 0;
+ psx_tracker.cmd.arg2 = count > 1 ? arg[1] : 0;
+ psx_tracker.cmd.arg3 = count > 2 ? arg[2] : 0;
+
+ if (count > 3) {
+ psx_tracker.cmd.six = 1;
+ psx_tracker.cmd.arg1 = arg[3];
+ psx_tracker.cmd.arg2 = count > 4 ? arg[4] : 0;
+ psx_tracker.cmd.arg3 = count > 5 ? arg[5] : 0;
+ return syscall(syscall_nr,
+ psx_tracker.cmd.arg1,
+ psx_tracker.cmd.arg2,
+ psx_tracker.cmd.arg3,
+ psx_tracker.cmd.arg4,
+ psx_tracker.cmd.arg5,
+ psx_tracker.cmd.arg6);
+ }
+
+ psx_tracker.cmd.six = 0;
+ return syscall(syscall_nr, psx_tracker.cmd.arg1,
+ psx_tracker.cmd.arg2, psx_tracker.cmd.arg3);
+}
+
+/*
+ * __psx_syscall performs the syscall on the current thread and if no
+ * error is detected it ensures that the syscall is also performed on
+ * all (other) registered threads. The return code is the value for
+ * the first invocation. It uses a trick to figure out how many
+ * arguments the user has supplied. The other half of the trick is
+ * provided by the macro psx_syscall() in the <sys/psx_syscall.h>
+ * file. The trick is the 7th optional argument (8th over all) to
+ * __psx_syscall is the count of arguments supplied to psx_syscall.
+ *
+ * User:
+ * psx_syscall(nr, a, b);
+ * Expanded by macro to:
+ * __psx_syscall(nr, a, b, 6, 5, 4, 3, 2, 1, 0);
+ * The eighth arg is now ------------------------------------^
+ */
+long int __psx_syscall(long int syscall_nr, ...) {
+ long int arg[7];
+ int i;
+
+ va_list aptr;
+ va_start(aptr, syscall_nr);
+ for (i = 0; i < 7; i++) {
+ arg[i] = va_arg(aptr, long int);
+ }
+ va_end(aptr);
+
+ int count = arg[6];
+ if (count < 0 || count > 6) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (psx_tracker.has_forked) {
+ return __psx_immediate_syscall(syscall_nr, count, arg);
+ }
+
+ psx_new_state(_PSX_IDLE, _PSX_SETUP);
+
+ long int ret;
+
+ ret = __psx_immediate_syscall(syscall_nr, count, arg);;
+ if (ret == -1 || !psx_tracker.initialized) {
+ psx_new_state(_PSX_SETUP, _PSX_IDLE);
+ goto defer;
+ }
+
+ int restore_errno = errno;
+
+ psx_new_state(_PSX_SETUP, _PSX_SYSCALL);
+ psx_tracker.cmd.active = 1;
+
+ pthread_t self = pthread_self();
+ registered_thread_t *next = NULL, *ref;
+
+ psx_lock();
+ for (ref = psx_tracker.root; ref; ref = next) {
+ next = ref->next;
+ if (ref->thread == self) {
+ continue;
+ }
+ pthread_mutex_lock(&ref->mu);
+ ref->pending = 1;
+ pthread_mutex_unlock(&ref->mu);
+ if (pthread_kill(ref->thread, psx_tracker.psx_sig) == 0) {
+ continue;
+ }
+ /*
+ * need to remove invalid thread id from linked list
+ */
+ psx_do_unregister(ref);
+ }
+ psx_unlock();
+
+ for (;;) {
+ int waiting = 0;
+ psx_lock();
+ for (ref = psx_tracker.root; ref; ref = next) {
+ next = ref->next;
+ if (ref->thread == self) {
+ continue;
+ }
+
+ pthread_mutex_lock(&ref->mu);
+ int pending = ref->pending;
+ pthread_mutex_unlock(&ref->mu);
+ if (!pending || pthread_kill(ref->thread, 0) == 0) {
+ waiting += pending;
+ continue;
+ }
+ /*
+ * need to remove invalid thread id from linked list
+ */
+ psx_do_unregister(ref);
+ }
+ psx_unlock();
+ if (!waiting) {
+ break;
+ }
+ sched_yield();
+ }
+
+ errno = restore_errno;
+ psx_tracker.cmd.active = 0;
+ psx_new_state(_PSX_SYSCALL, _PSX_IDLE);
+
+defer:
+ return ret;
+}
diff --git a/psx/psx.go b/psx/psx.go
index 8016f49..222ca5b 100644
--- a/psx/psx.go
+++ b/psx/psx.go
@@ -2,33 +2,19 @@
// that work by calling the C libpsx functions of these names. The
// purpose being to perform system calls symultaneously on all the
// pthreads of the Go (and CGo) combined runtime. Since Go's runtime
-// freely migrates code execution between pthreads. Support of this
+// freely migrates code execution between pthreads, support of this
// type is required for any successful attempt to fully drop or modify
-// user privilege of a Go program under Linux.
-//
-// Correct compilation of this package may require some extra steps:
-//
-// The first is that the package needs to be able to find the libpsx C
-// library and <sys/psx_syscall.h> header files. The official
-// releases of libpsx are bundled with libcap and can be found in
-// releases after libcap-2.28. See the release notes and other libcap
-// related news here:
+// user privilege of a Go program under Linux. More info on how
+// privilege works can be found here:
//
// https://sites.google.com/site/fullycapable
//
-// Without a full system install of the libpsx C library and header,
-// you can download the latest libcap sources (see above site) and
-// type make to build them. Use the Go environment variable overrides
-// to include and link against the libpsx.a static library it
-// builds. Specifically, these:
-//
-// export CGO_CFLAGS="-I /...your-path-to.../libcap/include"
-// export CGO_LDFLAGS="-L /...your-path-to.../libcap"
+// Correct compilation of this package may require an extra step:
//
-// The second, if your Go compiler is pre-go1.15, may be required to
+// If your Go compiler is pre-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. Specifically, for Go
-// releases prior to those that include this patch:
+// package employs some unusual linking flags. You will need to do
+// this for any Go toolchain that that does not include this patch:
//
// https://go-review.googlesource.com/c/go/+/236139/
//
@@ -47,7 +33,8 @@ import (
"syscall"
)
-// #cgo LDFLAGS: -lpsx -lpthread -Wl,-wrap,pthread_create
+// #cgo CFLAGS: -I${SRCDIR}/include
+// #cgo LDFLAGS: -lpthread -Wl,-wrap,pthread_create
//
// #include <errno.h>
// #include <sys/psx_syscall.h>