aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Make.Rules3
-rw-r--r--Makefile1
-rw-r--r--libcap/.gitignore1
-rw-r--r--libcap/Makefile21
-rw-r--r--libcap/include/sys/psx_syscall.h128
-rw-r--r--libcap/psx.c325
-rw-r--r--pam_cap/Makefile15
-rw-r--r--progs/Makefile3
-rw-r--r--tests/.gitignore2
-rw-r--r--tests/Makefile23
-rw-r--r--tests/psx_test.c108
11 files changed, 614 insertions, 16 deletions
diff --git a/Make.Rules b/Make.Rules
index b685ab2..1ccead1 100644
--- a/Make.Rules
+++ b/Make.Rules
@@ -61,6 +61,9 @@ WARNINGS=-Wall -Wwrite-strings \
-Wnested-externs -Winline -Wshadow
LD=$(CC) -Wl,-x -shared
LDFLAGS := #-g
+LIBCAPLIB := -L$(topdir)/libcap -lcap
+LIBPSXLIB := -L$(topdir)/libcap -lpsx -lpthread
+
BUILD_GPERF := $(shell which gperf >/dev/null 2>/dev/null && echo yes)
SYSTEM_HEADERS = /usr/include
diff --git a/Makefile b/Makefile
index be8f38e..08d2570 100644
--- a/Makefile
+++ b/Makefile
@@ -16,6 +16,7 @@ endif
ifeq ($(GOLANG),yes)
$(MAKE) -C go $@
endif
+ $(MAKE) -C tests $@
$(MAKE) -C progs $@
$(MAKE) -C doc $@
$(MAKE) -C kdebug $@
diff --git a/libcap/.gitignore b/libcap/.gitignore
index 19a6bc9..2a53b2f 100644
--- a/libcap/.gitignore
+++ b/libcap/.gitignore
@@ -3,5 +3,6 @@ cap_names.list.h
_caps_output.gperf
libcap.a
libcap.so*
+libpsx.a
_makenames
libcap.pc
diff --git a/libcap/Makefile b/libcap/Makefile
index d5d36c6..8619972 100644
--- a/libcap/Makefile
+++ b/libcap/Makefile
@@ -9,16 +9,20 @@ include ../Make.Rules
LIBNAME=$(LIBTITLE).so
STALIBNAME=$(LIBTITLE).a
#
+STAPSXLIBNAME=libpsx.a
-FILES=cap_alloc cap_proc cap_extint cap_flag cap_text cap_file
+CAPFILES=cap_alloc cap_proc cap_extint cap_flag cap_text cap_file
+PSXFILES=psx
INCLS=libcap.h cap_names.h $(INCS)
-OBJS=$(addsuffix .o, $(FILES))
+CAPOBJS=$(addsuffix .o, $(CAPFILES))
+PSXOBJS=$(addsuffix .o, $(PSXFILES))
+
MAJLIBNAME=$(LIBNAME).$(VERSION)
MINLIBNAME=$(MAJLIBNAME).$(MINOR)
GPERF_OUTPUT = _caps_output.gperf
-all: $(MINLIBNAME) $(STALIBNAME) libcap.pc
+all: $(MINLIBNAME) $(STALIBNAME) libcap.pc $(STAPSXLIBNAME)
ifeq ($(BUILD_GPERF),yes)
USE_GPERF_OUTPUT = $(GPERF_OUTPUT)
@@ -48,11 +52,15 @@ cap_names.list.h: Makefile $(KERNEL_HEADERS)/linux/capability.h
@echo "=> making $@ from $(KERNEL_HEADERS)/linux/capability.h"
perl -e 'while ($$l=<>) { if ($$l =~ /^\#define[ \t](CAP[_A-Z]+)[ \t]+([0-9]+)\s+$$/) { $$tok=$$1; $$val=$$2; $$tok =~ tr/A-Z/a-z/; print "{\"$$tok\",$$val},\n"; } }' $(KERNEL_HEADERS)/linux/capability.h | fgrep -v 0x > $@
-$(STALIBNAME): $(OBJS)
+$(STALIBNAME): $(CAPOBJS)
+ $(AR) rcs $@ $^
+ $(RANLIB) $@
+
+$(STAPSXLIBNAME): $(PSXOBJS)
$(AR) rcs $@ $^
$(RANLIB) $@
-$(MINLIBNAME): $(OBJS)
+$(MINLIBNAME): $(CAPOBJS)
$(LD) $(CFLAGS) $(LDFLAGS) -Wl,-soname,$(MAJLIBNAME) -o $@ $^
ln -sf $(MINLIBNAME) $(MAJLIBNAME)
ln -sf $(MAJLIBNAME) $(LIBNAME)
@@ -79,6 +87,7 @@ endif
clean:
$(LOCALCLEAN)
- rm -f $(OBJS) $(LIBNAME)* $(STALIBNAME) libcap.pc
+ rm -f $(CAPOBJS) $(LIBNAME)* $(STALIBNAME) libcap.pc
+ rm -f $(PSXOBJS) $(STAPSXLIBNAME)
rm -f cap_names.h cap_names.list.h _makenames $(GPERF_OUTPUT)
cd include/sys && $(LOCALCLEAN)
diff --git a/libcap/include/sys/psx_syscall.h b/libcap/include/sys/psx_syscall.h
new file mode 100644
index 0000000..1df2659
--- /dev/null
+++ b/libcap/include/sys/psx_syscall.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2019 Andrew G. Morgan <morgan@kernel.org>
+ *
+ * This header, and the -lpsx library, provide a number of things to
+ * support POSIX semantics for syscalls associated with the pthread
+ * library. Linking this code is tricky and is done as follows:
+ *
+ * ld ... -lpsx -lpthread --wrap=pthread_create
+ * or, gcc ... -lpsx -lpthread -Wl,-wrap,pthread_create
+ *
+ * glibc provides a subset of this functionality natively through the
+ * nptl:setxid mechanism and could implement psx_syscall() directly
+ * using that style of functionality but, as of 2019-11-30, the setxid
+ * mechanism is limited to 9 specific set*() syscalls that do not
+ * support the syscall6 API (needed for prctl functions and the ambient
+ * capabilities set for example).
+ *
+ * This psx library API also includes explicit registration of threads
+ * if implicit wrapping the pthread_create() function is problematic
+ * for your application via the psx_pthread_create() function. To use
+ * the library in that way, you should include this line in the file
+ * containing your main() function:
+ *
+ * -----------
+ * #include <sys/psx_syscall.h>
+ *
+ * int main(...) {
+ *
+ * ....
+ *
+ * }
+ * PSX_NO_LINKER_WRAPPING
+ * -----------
+ *
+ * This will ensure that your binary can link.
+ */
+
+#ifndef _SYS_PSX_SYSCALL_H
+#define _SYS_PSX_SYSCALL_H
+
+#include <pthread.h>
+
+/*
+ * This function is actually provided by the linker trick:
+ *
+ * gcc ... -lpsx -lpthread -Wl,-wrap,pthread_create
+ */
+int __real_pthread_create(pthread_t *thread, const pthread_attr_t *attr,
+ void *(*start_routine) (void *), void *arg);
+
+#define PSX_NO_LINKER_WRAPPING int \
+ __real_pthread_create(pthread_t *thread, const pthread_attr_t *attr, \
+ void *(*start_routine) (void *), void *arg) { \
+ return -1; \
+ }
+
+#define PSX_DEFAULT_INTERRUPT 42
+
+/*
+ * psx_set_interrupt specifies the non-default interrupt number used
+ * by the psx abstraction. Return status of 0 means success, -1
+ * otherwise and errno is set to the cause.
+ */
+int psx_set_interrupt(int interrupt_number);
+
+/*
+ * psx_syscall performs the specified syscall on all psx registered
+ * threads. The mecanism by which this occurs is much less efficient
+ * than a standard system call on Linux, so it should only be used
+ * when POSIX semantics are required to change process relevant
+ * security state.
+ *
+ * Glibc has native support for POSIX semantics on setgroups() and the
+ * 8 set*[gu]id() functions. So, there is no need to use psx_syscall()
+ * for these calls. This call exists for all the other system calls
+ * that need to maintain parity on all pthreads of a program.
+ *
+ * Some macrology is used to allow the caller to provide only as many
+ * arguments as needed, thus psx_syscall() cannot be used as a
+ * function pointer. For those situations, we define psx_syscall3()
+ * and psx_syscall6().
+ */
+#define psx_syscall(syscall_nr, ...) \
+ __psx_syscall(syscall_nr, __VA_ARGS__, 6, 5, 4, 3, 2, 1, 0)
+long int __psx_syscall(long int syscall_nr, ...);
+long int psx_syscall3(long int syscall_nr,
+ long int arg1, long int arg2, long int 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);
+
+/*
+ * psx_register registers a pthread with the psx abstraction of system
+ * calls.
+ */
+void psx_register(pthread_t thread);
+
+/*
+ * psx_pthread_create() wraps the -lpthread pthread_create() function
+ * call and registers the generated thread with the psx_syscall
+ * infrastructure.
+ *
+ * Note, to transparently redirect all the pthread_create() calls in
+ * your binary to psx_pthread_create(), link with:
+ *
+ * gcc ... -lpsx -lpthread -Wl,-wrap,pthread_create
+ *
+ * [That is, libpsx contains an internal definition for the
+ * __wrap_pthread_create function to invoke psx_pthread_create
+ * functionality instead.]
+ */
+int psx_pthread_create(pthread_t *thread, const pthread_attr_t *attr,
+ void *(*start_routine) (void *), void *arg);
+
+/*
+ * This function is weakly defined as a no-op in libpsx. If code
+ * linking to it provides an alternative implementation, that one is
+ * called instead. For example, libcap provides one of these functions
+ * so psx can share its syscall functions with libcap at start up and
+ * thus pthreads automagically become POSIX semantics compliant.
+ */
+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));
+
+#endif /* _SYS_PSX_SYSCALL_H */
diff --git a/libcap/psx.c b/libcap/psx.c
new file mode 100644
index 0000000..b9bd8a5
--- /dev/null
+++ b/libcap/psx.c
@@ -0,0 +1,325 @@
+/*
+ * Copyright (c) 2019 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 object - 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.
+ */
+
+#include <errno.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/psx_syscall.h>
+#include <unistd.h>
+
+/*
+ * share_psx_syscall() is invoked to advertize the two functions
+ * psx_syscall3() and psx_syscall6(). The linkage is weak here so some
+ * code external to this library can override it transparently during
+ * the linkage process.
+ */
+__attribute__((weak))
+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))
+{
+}
+
+/*
+ * type to keep track of registered threads.
+ */
+typedef struct registered_thread_s {
+ struct registered_thread_s *next, *prev;
+ pthread_t thread;
+} registered_thread_t;
+
+static pthread_once_t psx_tracker_initialized = PTHREAD_ONCE_INIT;
+
+/*
+ * This global structure holds the global coordination state for
+ * libcap's psx_posix_syscall() support.
+ */
+static struct psx_tracker_s {
+ pthread_mutex_t mu;
+
+ int initialized;
+ int psx_sig;
+
+ struct {
+ pthread_mutex_t mu;
+ pthread_cond_t cond;
+ long syscall_nr;
+ long arg1, arg2, arg3, arg4, arg5, arg6;
+ int six;
+ int active;
+ int todo;
+ } cmd;
+
+ struct sigaction sig_action;
+ registered_thread_t *root;
+} psx_tracker;
+
+/*
+ * psx_posix_syscall_handler performs the system call on the targeted
+ * thread and decreases the outstanding syscall counter.
+ */
+static void psx_posix_syscall_handler(int signum) {
+ if (!psx_tracker.cmd.active || signum != psx_tracker.psx_sig) {
+ 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);
+ }
+
+ pthread_mutex_lock(&psx_tracker.cmd.mu);
+ if (! --psx_tracker.cmd.todo) {
+ pthread_cond_signal(&psx_tracker.cmd.cond);
+ }
+ pthread_mutex_unlock(&psx_tracker.cmd.mu);
+}
+
+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);
+}
+
+/*
+ * psx_syscall_start initializes the subsystem.
+ */
+static void psx_syscall_start(void) {
+ psx_tracker.initialized = 1;
+
+ psx_tracker.psx_sig = 42; /* default signal number for syscall syncing */
+ psx_tracker.sig_action.sa_handler = psx_posix_syscall_handler;
+ sigemptyset(&psx_tracker.sig_action.sa_mask);
+ psx_tracker.sig_action.sa_flags = 0;
+
+ sigaction(psx_tracker.psx_sig, &psx_tracker.sig_action, NULL);
+
+ share_psx_syscall(psx_syscall3, psx_syscall6);
+}
+
+static void psx_do_registration(pthread_t thread) {
+ int first_time = !psx_tracker.initialized;
+ (void) pthread_once(&psx_tracker_initialized, psx_syscall_start);
+
+ if (first_time) {
+ // First invocation, use recursion to register main() thread.
+ psx_do_registration(pthread_self());
+ }
+
+ registered_thread_t *node = calloc(1, sizeof(registered_thread_t));
+ node->next = psx_tracker.root;
+ if (node->next) {
+ node->next->prev = node;
+ }
+ node->thread = thread;
+ psx_tracker.root = node;
+}
+
+/*
+ * psx_register registers the a thread as pariticipating in the single
+ * (POSIX) pool of privilege used by the library.
+ *
+ * In normal, expected, use you should never need to call this because
+ * the linker magic wrapping will arrange for this function to be
+ * called implicitly every time a pthread is created with
+ * pthread_create() or psx_pthread_create(). If, however, you are
+ * unable to use the linker trick to wrap pthread_create(), you should
+ * include this line in one and only one place in your code. Just
+ * after the end of the main() function would be a good place, for
+ * example:
+ *
+ * int main(int argc, char **argv) {
+
+ * ...
+ * }
+ *
+ * PSX_NO_LINKER_WRAPPING
+ *
+ * This is required for libpsx to link. It also requires the coder to
+ * explicitly use psx_register() for all threads not started with
+ * psx_pthread_create().
+ *
+ * Note, there is no need to ever register the main() process thread.
+ */
+void psx_register(pthread_t thread) {
+ pthread_mutex_lock(&psx_tracker.mu);
+ psx_do_registration(thread);
+ pthread_mutex_unlock(&psx_tracker.mu);
+}
+
+/* provide a prototype */
+int __wrap_pthread_create(pthread_t *thread, const pthread_attr_t *attr,
+ void *(*start_routine) (void *), void *arg);
+
+/*
+ * 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) {
+ if (pthread_create == __wrap_pthread_create) {
+ return __wrap_pthread_create(thread, attr, start_routine, arg);
+ }
+
+ pthread_mutex_lock(&psx_tracker.mu);
+ int ret = pthread_create(thread, attr, start_routine, arg);
+ if (ret != -1) {
+ psx_do_registration(*thread);
+ }
+ pthread_mutex_unlock(&psx_tracker.mu);
+ return ret;
+}
+
+/*
+ * __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) {
+ pthread_mutex_lock(&psx_tracker.mu);
+ int ret = __real_pthread_create(thread, attr, start_routine, arg);
+ if (ret != -1) {
+ psx_do_registration(*thread);
+ }
+ pthread_mutex_unlock(&psx_tracker.mu);
+ return ret;
+}
+
+/*
+ * __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];
+
+ va_list aptr;
+ va_start(aptr, syscall_nr);
+ for (int 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;
+ }
+
+ pthread_mutex_lock(&psx_tracker.mu);
+ long int ret;
+
+ 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;
+
+ ret = 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);
+ } else {
+ psx_tracker.cmd.six = 0;
+
+ ret = syscall(syscall_nr,
+ psx_tracker.cmd.arg1,
+ psx_tracker.cmd.arg2,
+ psx_tracker.cmd.arg3);
+ }
+
+ if (ret == -1 || !psx_tracker.initialized) {
+ goto defer;
+ }
+
+ int restore_errno = errno;
+ psx_tracker.cmd.active = 1;
+
+ pthread_t self = pthread_self();
+ registered_thread_t *next = NULL;
+ for (registered_thread_t *ref = psx_tracker.root; ref; ref = next) {
+ next = ref->next;
+ if (ref->thread == self) {
+ continue;
+ }
+ if (pthread_kill(ref->thread, psx_tracker.psx_sig) == 0) {
+ pthread_mutex_lock(&psx_tracker.cmd.mu);
+ psx_tracker.cmd.todo++;
+ pthread_mutex_unlock(&psx_tracker.cmd.mu);
+ continue;
+ }
+
+ /* need to remove now invalid thread id from linked list */
+ if (ref == psx_tracker.root) {
+ psx_tracker.root = next;
+ } else if (ref->prev) {
+ ref->prev->next = next;
+ }
+ if (next) {
+ next->prev = ref->prev;
+ }
+ free(ref);
+ }
+
+ pthread_mutex_lock(&psx_tracker.cmd.mu);
+ while (psx_tracker.cmd.todo) {
+ pthread_cond_wait(&psx_tracker.cmd.cond, &psx_tracker.cmd.mu);
+ }
+ pthread_mutex_unlock(&psx_tracker.cmd.mu);
+
+ errno = restore_errno;
+defer:
+
+ psx_tracker.cmd.active = 0;
+ pthread_mutex_unlock(&psx_tracker.mu);
+
+ return ret;
+}
diff --git a/pam_cap/Makefile b/pam_cap/Makefile
index cc32fb6..22f0f81 100644
--- a/pam_cap/Makefile
+++ b/pam_cap/Makefile
@@ -3,12 +3,6 @@
topdir=$(shell pwd)/..
include ../Make.Rules
-# Note (as the author of much of the Linux-PAM library, I am confident
-# that this next line does *not* require -lpam on it.) If you think it
-# does, *verify that it does*, and if you observe that it fails as
-# written (and you know why it fails), email me and explain why. Thanks!
-LDLIBS += -L../libcap -lcap
-
all: pam_cap.so
$(MAKE) testcompile
@@ -16,14 +10,19 @@ install: all
mkdir -p -m 0755 $(FAKEROOT)$(LIBDIR)/security
install -m 0755 pam_cap.so $(FAKEROOT)$(LIBDIR)/security
+# Note (as the author of much of the Linux-PAM library, I am confident
+# that this next line does *not* require -lpam on it.) If you think it
+# does, *verify that it does*, and if you observe that it fails as
+# written (and you know why it fails), email me and explain why. Thanks!
+
pam_cap.so: pam_cap.o
- $(LD) $(LDFLAGS) -o pam_cap.so $< $(LDLIBS)
+ $(LD) -o pam_cap.so $< $(LIBCAPLIB) $(LDFLAGS)
pam_cap.o: pam_cap.c
$(CC) $(CFLAGS) $(IPATH) -c $< -o $@
testcompile: test.c pam_cap.o
- $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $+ -lpam -ldl $(LDLIBS)
+ $(CC) $(CFLAGS) -o $@ $+ -lpam -ldl $(LIBCAPLIB) $(LDFLAGS)
clean:
rm -f *.o *.so testcompile *~
diff --git a/progs/Makefile b/progs/Makefile
index 148b6af..0786ad3 100644
--- a/progs/Makefile
+++ b/progs/Makefile
@@ -11,12 +11,11 @@ BUILD=$(PROGS)
ifneq ($(DYNAMIC),yes)
LDFLAGS += --static
endif
-LDLIBS += -L../libcap -lcap
all: $(BUILD)
$(BUILD): %: %.o
- $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS)
+ $(CC) $(CFLAGS) -o $@ $< $(LIBCAPLIB) $(LDFLAGS)
%.o: %.c $(INCS)
$(CC) $(IPATH) $(CFLAGS) -c $< -o $@
diff --git a/tests/.gitignore b/tests/.gitignore
new file mode 100644
index 0000000..42afee3
--- /dev/null
+++ b/tests/.gitignore
@@ -0,0 +1,2 @@
+psx_test
+psx_test_wrap
diff --git a/tests/Makefile b/tests/Makefile
new file mode 100644
index 0000000..739e864
--- /dev/null
+++ b/tests/Makefile
@@ -0,0 +1,23 @@
+#
+# defines
+#
+topdir=$(shell pwd)/..
+include ../Make.Rules
+#
+
+all: run_psx_test
+
+install: all
+
+run_psx_test: psx_test psx_test_wrap
+ ./psx_test
+ ./psx_test_wrap
+
+psx_test: psx_test.c
+ $(CC) $(CFLAGS) $(IPATH) -DNOWRAP $< -o $@ $(LIBPSXLIB)
+
+psx_test_wrap: psx_test.c
+ $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LIBPSXLIB) -Wl,-wrap,pthread_create
+
+clean:
+ rm -f psx_test psx_test_wrap
diff --git a/tests/psx_test.c b/tests/psx_test.c
new file mode 100644
index 0000000..92b99a0
--- /dev/null
+++ b/tests/psx_test.c
@@ -0,0 +1,108 @@
+#include <pthread.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/prctl.h>
+#include <sys/psx_syscall.h>
+#include <sys/syscall.h>
+
+static void say_hello_expecting(const char *title, int n, int kept) {
+ int keeper = prctl(PR_GET_KEEPCAPS);
+ printf("hello, %s<%d> %lx (keepcaps=%d vs. want=%d)\n",
+ title, n, pthread_self(), keeper, kept);
+ if (keeper != kept) {
+ printf("--> FAILURE %s thread=%lx has wrong keepcaps: got=%d want=%d\n",
+ title, pthread_self(), keeper, kept);
+ exit(1);
+ }
+}
+
+pthread_mutex_t mu;
+pthread_cond_t cond;
+
+int global_kept = 0;
+int step = 0;
+int replies = 0;
+int launched = 0;
+int started = 0;
+
+static void *say_hello(void *args) {
+ int count = 0;
+
+ pthread_mutex_lock(&mu);
+ started++;
+ pthread_cond_broadcast(&cond);
+
+ int this_step = step+1;
+ do {
+ while (this_step > step) {
+ pthread_cond_wait(&cond, &mu);
+ }
+ this_step++;
+
+ say_hello_expecting("thread", count, global_kept);
+
+ replies++;
+ pthread_cond_broadcast(&cond);
+ } while (++count != 3);
+
+ pthread_mutex_unlock(&mu);
+
+ return NULL;
+}
+
+int main(int argc, char **argv) {
+ pthread_t tid[3];
+
+ for (int i = 0; i<10; i++) {
+ printf("iteration: %d\n", i);
+
+ pthread_mutex_lock(&mu);
+ global_kept = !global_kept;
+ replies = 0;
+ step = i;
+ pthread_mutex_unlock(&mu);
+
+ psx_syscall(SYS_prctl, PR_SET_KEEPCAPS, global_kept);
+ step++;
+ pthread_cond_broadcast(&cond);
+
+ say_hello_expecting("main", i, global_kept);
+
+ pthread_mutex_lock(&mu);
+ while (replies < launched) {
+ pthread_cond_wait(&cond, &mu);
+ }
+ pthread_mutex_unlock(&mu);
+
+ if (i < 3) {
+ launched++;
+ if (i == 1) {
+ // Confirm this works whether or not we are WRAPPING.
+ psx_pthread_create(&tid[i], NULL, say_hello, NULL);
+ } else if (i < 3) {
+#ifdef NOWRAP
+ psx_pthread_create(&tid[i], NULL, say_hello, NULL);
+#else
+ pthread_create(&tid[i], NULL, say_hello, NULL);
+#endif
+ }
+ // Confirm that the thread is started.
+ pthread_mutex_lock(&mu);
+ while (started < launched) {
+ pthread_cond_wait(&cond, &mu);
+ }
+ pthread_mutex_unlock(&mu);
+ } else if (i < 6) {
+ // Confirm one thread has finished.
+ pthread_join(tid[i-3], NULL);
+ launched--;
+ }
+ }
+
+ printf("%s PASSED\n", argv[0]);
+ exit(0);
+}
+
+#ifdef NOWRAP
+PSX_NO_LINKER_WRAPPING
+#endif