packages feed

psx (empty) → 0.1.0.0

raw patch · 16 files changed

+1781/−0 lines, 16 filesdep +asyncdep +basedep +psx

Dependencies added: async, base, psx, tasty, tasty-hunit

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for psx++## 0.1.0.0 -- 2022-08-24++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,29 @@+BSD 3-Clause License++Copyright (c) 2022, Nicolas Trangez+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++1. Redistributions of source code must retain the above copyright notice, this+   list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright notice,+   this list of conditions and the following disclaimer in the documentation+   and/or other materials provided with the distribution.++3. Neither the name of the copyright holder nor the names of its+   contributors may be used to endorse or promote products derived from+   this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,18 @@+# psx: Haskell bindings for libpsx++This library embeds libpsx in a GHC Haskell-compiled application.++Note `libpsx` performs some trickery with signal handling in a process.+Furthermore, when using this library, `sigfillset` will be wrapped so+`SIGSYS` is *not* set, in order for the GHC RTS `ticker` thread not to+block the signal and work properly with `libpsx`.++See+[this GHC proposal](https://github.com/ghc-proposals/ghc-proposals/pull/533)+for a potential future replacement of this library using functionality built+into the GHC RTS.++**Warning:** `libpsx` on current Ubuntu and Debian systems (from `libcap`+2.44) is broken. Hence, this library contains a bundled version of `libpsx`+by default. Disable the `bundled-libpsx` flag to use a system-provided+version of the library, which must be from `libcap` version 2.46 or higher.
+ cbits/hs-psx.c view
@@ -0,0 +1,33 @@+#define _GNU_SOURCE++#include <signal.h>++#ifdef BUNDLED_LIBPSX+# include "psx/psx_syscall.h"+#else+# include <sys/psx_syscall.h>+#endif++#include "hs-psx.h"++/* This will be provided by the dynamic linker when linked with -Wl,-wrap,sigfillset */+extern int __real_sigfillset(sigset_t *set);++int __wrap_sigfillset(sigset_t *set) {+        int rc;++        rc = __real_sigfillset(set);+        if(rc != 0) {+                return rc;+        }++        return sigdelset(set, HS_PSX_SIGNAL);+}++long hs_psx_syscall3(long num, long arg1, long arg2, long arg3) {+        return psx_syscall3(num, arg1, arg2, arg3);+}++long hs_psx_syscall6(long num, long arg1, long arg2, long arg3, long arg4, long arg5, long arg6) {+        return psx_syscall6(num, arg1, arg2, arg3, arg4, arg5, arg6);+}
+ cbits/hs-psx.h view
@@ -0,0 +1,20 @@+#ifndef _HS_PSX_H_+#define _HS_PSX_H_++#include <signal.h>++#ifdef __cplusplus+extern "C" {+#endif++/* Ensure this is in sync with whatever libpsx uses */+#define HS_PSX_SIGNAL SIGSYS++long hs_psx_syscall3(long num, long arg1, long arg2, long arg3);+long hs_psx_syscall6(long num, long arg1, long arg2, long arg3, long arg4, long arg5, long arg6);++#ifdef __cplusplus+}+#endif++#endif
+ cbits/psx/License view
@@ -0,0 +1,396 @@+Unless otherwise *explicitly* stated, the following text describes the+licensed conditions under which the contents of this libcap/psx release+may be used and distributed.++The licensed conditions are one or the other of these two Licenses:++  - BSD 3-clause+  - GPL v2.0++-------------------------------------------------------------------------+BSD 3-clause:+-------------++Redistribution and use in source and binary forms of libcap/psx, with+or without modification, are permitted provided that the following+conditions are met:++1. Redistributions of source code must retain any existing copyright+   notice, and this entire permission notice in its entirety,+   including the disclaimer of warranties.++2. Redistributions in binary form must reproduce all prior and current+   copyright notices, this list of conditions, and the following+   disclaimer in the documentation and/or other materials provided+   with the distribution.++3. The name of any author may not be used to endorse or promote+   products derived from this software without their specific prior+   written permission.++THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.+IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS+OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE+USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH+DAMAGE.++-------------------------------------------------------------------------+GPL v2.0:+---------++ALTERNATIVELY, this product may be distributed under the terms of the+GNU General Public License (v2.0 - see below), in which case the+provisions of the GNU GPL are required INSTEAD OF the above+restrictions.  (This clause is necessary due to a potential conflict+between the GNU GPL and the restrictions contained in a BSD-style+copyright.)++-------------------------+Full text of gpl-2.0.txt:+-------------------------++                    GNU GENERAL PUBLIC LICENSE+                       Version 2, June 1991++ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA+ Everyone is permitted to copy and distribute verbatim copies+ of this license document, but changing it is not allowed.++                            Preamble++  The licenses for most software are designed to take away your+freedom to share and change it.  By contrast, the GNU General Public+License is intended to guarantee your freedom to share and change free+software--to make sure the software is free for all its users.  This+General Public License applies to most of the Free Software+Foundation's software and to any other program whose authors commit to+using it.  (Some other Free Software Foundation software is covered by+the GNU Lesser General Public License instead.)  You can apply it to+your programs, too.++  When we speak of free software, we are referring to freedom, not+price.  Our General Public Licenses are designed to make sure that you+have the freedom to distribute copies of free software (and charge for+this service if you wish), that you receive source code or can get it+if you want it, that you can change the software or use pieces of it+in new free programs; and that you know you can do these things.++  To protect your rights, we need to make restrictions that forbid+anyone to deny you these rights or to ask you to surrender the rights.+These restrictions translate to certain responsibilities for you if you+distribute copies of the software, or if you modify it.++  For example, if you distribute copies of such a program, whether+gratis or for a fee, you must give the recipients all the rights that+you have.  You must make sure that they, too, receive or can get the+source code.  And you must show them these terms so they know their+rights.++  We protect your rights with two steps: (1) copyright the software, and+(2) offer you this license which gives you legal permission to copy,+distribute and/or modify the software.++  Also, for each author's protection and ours, we want to make certain+that everyone understands that there is no warranty for this free+software.  If the software is modified by someone else and passed on, we+want its recipients to know that what they have is not the original, so+that any problems introduced by others will not reflect on the original+authors' reputations.++  Finally, any free program is threatened constantly by software+patents.  We wish to avoid the danger that redistributors of a free+program will individually obtain patent licenses, in effect making the+program proprietary.  To prevent this, we have made it clear that any+patent must be licensed for everyone's free use or not licensed at all.++  The precise terms and conditions for copying, distribution and+modification follow.++                    GNU GENERAL PUBLIC LICENSE+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION++  0. This License applies to any program or other work which contains+a notice placed by the copyright holder saying it may be distributed+under the terms of this General Public License.  The "Program", below,+refers to any such program or work, and a "work based on the Program"+means either the Program or any derivative work under copyright law:+that is to say, a work containing the Program or a portion of it,+either verbatim or with modifications and/or translated into another+language.  (Hereinafter, translation is included without limitation in+the term "modification".)  Each licensee is addressed as "you".++Activities other than copying, distribution and modification are not+covered by this License; they are outside its scope.  The act of+running the Program is not restricted, and the output from the Program+is covered only if its contents constitute a work based on the+Program (independent of having been made by running the Program).+Whether that is true depends on what the Program does.++  1. You may copy and distribute verbatim copies of the Program's+source code as you receive it, in any medium, provided that you+conspicuously and appropriately publish on each copy an appropriate+copyright notice and disclaimer of warranty; keep intact all the+notices that refer to this License and to the absence of any warranty;+and give any other recipients of the Program a copy of this License+along with the Program.++You may charge a fee for the physical act of transferring a copy, and+you may at your option offer warranty protection in exchange for a fee.++  2. You may modify your copy or copies of the Program or any portion+of it, thus forming a work based on the Program, and copy and+distribute such modifications or work under the terms of Section 1+above, provided that you also meet all of these conditions:++    a) You must cause the modified files to carry prominent notices+    stating that you changed the files and the date of any change.++    b) You must cause any work that you distribute or publish, that in+    whole or in part contains or is derived from the Program or any+    part thereof, to be licensed as a whole at no charge to all third+    parties under the terms of this License.++    c) If the modified program normally reads commands interactively+    when run, you must cause it, when started running for such+    interactive use in the most ordinary way, to print or display an+    announcement including an appropriate copyright notice and a+    notice that there is no warranty (or else, saying that you provide+    a warranty) and that users may redistribute the program under+    these conditions, and telling the user how to view a copy of this+    License.  (Exception: if the Program itself is interactive but+    does not normally print such an announcement, your work based on+    the Program is not required to print an announcement.)++These requirements apply to the modified work as a whole.  If+identifiable sections of that work are not derived from the Program,+and can be reasonably considered independent and separate works in+themselves, then this License, and its terms, do not apply to those+sections when you distribute them as separate works.  But when you+distribute the same sections as part of a whole which is a work based+on the Program, the distribution of the whole must be on the terms of+this License, whose permissions for other licensees extend to the+entire whole, and thus to each and every part regardless of who wrote it.++Thus, it is not the intent of this section to claim rights or contest+your rights to work written entirely by you; rather, the intent is to+exercise the right to control the distribution of derivative or+collective works based on the Program.++In addition, mere aggregation of another work not based on the Program+with the Program (or with a work based on the Program) on a volume of+a storage or distribution medium does not bring the other work under+the scope of this License.++  3. You may copy and distribute the Program (or a work based on it,+under Section 2) in object code or executable form under the terms of+Sections 1 and 2 above provided that you also do one of the following:++    a) Accompany it with the complete corresponding machine-readable+    source code, which must be distributed under the terms of Sections+    1 and 2 above on a medium customarily used for software interchange; or,++    b) Accompany it with a written offer, valid for at least three+    years, to give any third party, for a charge no more than your+    cost of physically performing source distribution, a complete+    machine-readable copy of the corresponding source code, to be+    distributed under the terms of Sections 1 and 2 above on a medium+    customarily used for software interchange; or,++    c) Accompany it with the information you received as to the offer+    to distribute corresponding source code.  (This alternative is+    allowed only for noncommercial distribution and only if you+    received the program in object code or executable form with such+    an offer, in accord with Subsection b above.)++The source code for a work means the preferred form of the work for+making modifications to it.  For an executable work, complete source+code means all the source code for all modules it contains, plus any+associated interface definition files, plus the scripts used to+control compilation and installation of the executable.  However, as a+special exception, the source code distributed need not include+anything that is normally distributed (in either source or binary+form) with the major components (compiler, kernel, and so on) of the+operating system on which the executable runs, unless that component+itself accompanies the executable.++If distribution of executable or object code is made by offering+access to copy from a designated place, then offering equivalent+access to copy the source code from the same place counts as+distribution of the source code, even though third parties are not+compelled to copy the source along with the object code.++  4. You may not copy, modify, sublicense, or distribute the Program+except as expressly provided under this License.  Any attempt+otherwise to copy, modify, sublicense or distribute the Program is+void, and will automatically terminate your rights under this License.+However, parties who have received copies, or rights, from you under+this License will not have their licenses terminated so long as such+parties remain in full compliance.++  5. You are not required to accept this License, since you have not+signed it.  However, nothing else grants you permission to modify or+distribute the Program or its derivative works.  These actions are+prohibited by law if you do not accept this License.  Therefore, by+modifying or distributing the Program (or any work based on the+Program), you indicate your acceptance of this License to do so, and+all its terms and conditions for copying, distributing or modifying+the Program or works based on it.++  6. Each time you redistribute the Program (or any work based on the+Program), the recipient automatically receives a license from the+original licensor to copy, distribute or modify the Program subject to+these terms and conditions.  You may not impose any further+restrictions on the recipients' exercise of the rights granted herein.+You are not responsible for enforcing compliance by third parties to+this License.++  7. If, as a consequence of a court judgment or allegation of patent+infringement or for any other reason (not limited to patent issues),+conditions are imposed on you (whether by court order, agreement or+otherwise) that contradict the conditions of this License, they do not+excuse you from the conditions of this License.  If you cannot+distribute so as to satisfy simultaneously your obligations under this+License and any other pertinent obligations, then as a consequence you+may not distribute the Program at all.  For example, if a patent+license would not permit royalty-free redistribution of the Program by+all those who receive copies directly or indirectly through you, then+the only way you could satisfy both it and this License would be to+refrain entirely from distribution of the Program.++If any portion of this section is held invalid or unenforceable under+any particular circumstance, the balance of the section is intended to+apply and the section as a whole is intended to apply in other+circumstances.++It is not the purpose of this section to induce you to infringe any+patents or other property right claims or to contest validity of any+such claims; this section has the sole purpose of protecting the+integrity of the free software distribution system, which is+implemented by public license practices.  Many people have made+generous contributions to the wide range of software distributed+through that system in reliance on consistent application of that+system; it is up to the author/donor to decide if he or she is willing+to distribute software through any other system and a licensee cannot+impose that choice.++This section is intended to make thoroughly clear what is believed to+be a consequence of the rest of this License.++  8. If the distribution and/or use of the Program is restricted in+certain countries either by patents or by copyrighted interfaces, the+original copyright holder who places the Program under this License+may add an explicit geographical distribution limitation excluding+those countries, so that distribution is permitted only in or among+countries not thus excluded.  In such case, this License incorporates+the limitation as if written in the body of this License.++  9. The Free Software Foundation may publish revised and/or new versions+of the General Public License from time to time.  Such new versions will+be similar in spirit to the present version, but may differ in detail to+address new problems or concerns.++Each version is given a distinguishing version number.  If the Program+specifies a version number of this License which applies to it and "any+later version", you have the option of following the terms and conditions+either of that version or of any later version published by the Free+Software Foundation.  If the Program does not specify a version number of+this License, you may choose any version ever published by the Free Software+Foundation.++  10. If you wish to incorporate parts of the Program into other free+programs whose distribution conditions are different, write to the author+to ask for permission.  For software which is copyrighted by the Free+Software Foundation, write to the Free Software Foundation; we sometimes+make exceptions for this.  Our decision will be guided by the two goals+of preserving the free status of all derivatives of our free software and+of promoting the sharing and reuse of software generally.++                            NO WARRANTY++  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,+REPAIR OR CORRECTION.++  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE+POSSIBILITY OF SUCH DAMAGES.++                     END OF TERMS AND CONDITIONS++            How to Apply These Terms to Your New Programs++  If you develop a new program, and you want it to be of the greatest+possible use to the public, the best way to achieve this is to make it+free software which everyone can redistribute and change under these terms.++  To do so, attach the following notices to the program.  It is safest+to attach them to the start of each source file to most effectively+convey the exclusion of warranty; and each file should have at least+the "copyright" line and a pointer to where the full notice is found.++    <one line to give the program's name and a brief idea of what it does.>+    Copyright (C) <year>  <name of author>++    This program is free software; you can redistribute it and/or modify+    it under the terms of the GNU General Public License as published by+    the Free Software Foundation; either version 2 of the License, or+    (at your option) any later version.++    This program is distributed in the hope that it will be useful,+    but WITHOUT ANY WARRANTY; without even the implied warranty of+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the+    GNU General Public License for more details.++    You should have received a copy of the GNU General Public License along+    with this program; if not, write to the Free Software Foundation, Inc.,+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.++Also add information on how to contact you by electronic and paper mail.++If the program is interactive, make it output a short notice like this+when it starts in an interactive mode:++    Gnomovision version 69, Copyright (C) year name of author+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.+    This is free software, and you are welcome to redistribute it+    under certain conditions; type `show c' for details.++The hypothetical commands `show w' and `show c' should show the appropriate+parts of the General Public License.  Of course, the commands you use may+be called something other than `show w' and `show c'; they could even be+mouse-clicks or menu items--whatever suits your program.++You should also get your employer (if you work as a programmer) or your+school, if any, to sign a "copyright disclaimer" for the program, if+necessary.  Here is a sample; alter the names:++  Yoyodyne, Inc., hereby disclaims all copyright interest in the program+  `Gnomovision' (which makes passes at compilers) written by James Hacker.++  <signature of Ty Coon>, 1 April 1989+  Ty Coon, President of Vice++This General Public License does not permit incorporating your program into+proprietary programs.  If your program is a subroutine library, you may+consider it more useful to permit linking proprietary applications with the+library.  If this is what you want to do, use the GNU Lesser General+Public License instead of this License.
+ cbits/psx/psx.c view
@@ -0,0 +1,765 @@+/*+ * Copyright (c) 2019-21 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/syscall.h>++#include "psx_syscall.h"++#ifdef _PSX_DEBUG_MEMORY++static void *_psx_calloc(const char *file, const int line,+			 size_t nmemb, size_t size) {+    void *ptr = calloc(nmemb, size);+    fprintf(stderr, "psx:%d:%s:%d: calloc(%ld, %ld) -> %p\n", gettid(),+	    file, line, (long int)nmemb, (long int)size, ptr);+    return ptr;+}++static void _psx_free(const char *file, const int line, void *ptr) {+    fprintf(stderr, "psx:%d:%s:%d: free(%p)\n", gettid(), file, line, ptr);+    return free(ptr);+}++#define calloc(a, b)  _psx_calloc(__FILE__, __LINE__, a, b)+#define free(a)       _psx_free(__FILE__, __LINE__, a)++#endif /* def _PSX_DEBUG_MEMORY */++/*+ * psx_load_syscalls() can be weakly defined in dependent libraries to+ * provide a mechanism for a library to optionally leverage this psx+ * mechanism. Specifically, when libcap calls psx_load_sycalls() it+ * provides a weakly declared default that maps its system calls to+ * the regular system call functions. However, when linked with psx,+ * this function here overrides the syscalls to be the psx ones.+ */+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;+    int gone;+    long int retval;+    pid_t tid;+} 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_EXITING = 5,+} 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 initialized;+    int psx_sig;+    psx_sensitivity_t sensitivity;++    struct {+	long syscall_nr;+	long arg1, arg2, arg3, arg4, arg5, arg6;+	int six;+	int active;+    } cmd;++    struct sigaction sig_action;+    struct sigaction chained_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));+    if (node == NULL) {+	perror("unable to register psx handler");+	_exit(1);+    }+    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;+    return 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()) {+	if (psx_tracker.chained_action.sa_sigaction != 0) {+	    psx_tracker.chained_action.sa_sigaction(signum, info, ignore);+	}+	return;+    }++    long int retval;+    if (!psx_tracker.cmd.six) {+	retval = syscall(psx_tracker.cmd.syscall_nr,+			 psx_tracker.cmd.arg1,+			 psx_tracker.cmd.arg2,+			 psx_tracker.cmd.arg3);+    } else {+	retval = 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;+	ref->retval = retval;+	ref->tid = syscall(SYS_gettid);+	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_cleanup(void);+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 requires this function to be provided by the linkage wrapping.+ */+extern int __real_pthread_create(pthread_t *thread, const pthread_attr_t *attr,+				 void *(*start_routine) (void *), void *arg);++/*+ * psx_confirm_sigaction reconfirms that the psx handler is the first+ * handler to respond to the psx signal. It assumes that+ * psx_tracker.psx_sig has been set.+ */+static void psx_confirm_sigaction(void) {+    sigset_t mask, orig;+    struct sigaction existing_sa;++    /*+     * Block interrupts while potentially rewriting the handler.+     */+    sigemptyset(&mask);+    sigaddset(&mask, psx_tracker.psx_sig);+    sigprocmask(SIG_BLOCK, &mask, &orig);++    sigaction(psx_tracker.psx_sig, NULL, &existing_sa);+    if (existing_sa.sa_sigaction != psx_posix_syscall_actor) {+	memcpy(&psx_tracker.chained_action, &existing_sa, sizeof(struct sigaction));+	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_ONSTACK | SA_RESTART;+	sigaction(psx_tracker.psx_sig, &psx_tracker.sig_action, NULL);+    }++    sigprocmask(SIG_SETMASK, &orig, NULL);+}++/*+ * 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);+    pthread_atfork(_psx_prepare_fork, _psx_fork_completed, _psx_forked_child);++    /*+     * All sorts of things are assumed by Linux and glibc and/or musl+     * about signal handlers and which can be blocked. Go has its own+     * idiosyncrasies too. We tried SIGRTMAX until+     *+     *   https://bugzilla.kernel.org/show_bug.cgi?id=210533+     *+     * Our current strategy is to aggressively intercept SIGSYS.+     */+    psx_tracker.psx_sig = SIGSYS;++    psx_confirm_sigaction();+    psx_do_registration(); /* register the main thread. */+    atexit(_psx_cleanup);++    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 and when the signal handler is+ * being confirmed.+ */+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. Changing state is generally+ * done via this function. However, there is a single exception in+ * _psx_cleanup().+ */+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 scuttle+     * 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);+}++typedef struct {+    void *(*fn)(void *);+    void *arg;+    sigset_t sigbits;+} psx_starter_t;++/*+ * _psx_exiting is used to cleanup the node for the thread on its exit+ * path. This is needed for musl libc:+ *+ *    https://bugzilla.kernel.org/show_bug.cgi?id=208477+ *+ * and likely wise for glibc too:+ *+ *    https://sourceware.org/bugzilla/show_bug.cgi?id=12889+ */+static void _psx_exiting(void *node) {+    /*+     * Until we are in the _PSX_EXITING state, we must not block the+     * psx_sig interrupt for this dying thread. That is, until this+     * exiting thread can set ref->gone to 1, this dying thread is+     * still participating in the psx syscall distribution.+     *+     * See https://github.com/golang/go/issues/42494 for a situation+     * where this code is called with psx_tracker.psx_sig blocked.+     */+    sigset_t sigbit, orig_sigbits;+    sigemptyset(&sigbit);+    pthread_sigmask(SIG_UNBLOCK, &sigbit, &orig_sigbits);+    sigaddset(&sigbit, psx_tracker.psx_sig);+    pthread_sigmask(SIG_UNBLOCK, &sigbit, NULL);++    /*+     * With psx_tracker.psx_sig unblocked we can wait until this+     * thread can enter the _PSX_EXITING state.+     */+    psx_new_state(_PSX_IDLE, _PSX_EXITING);++    /*+     * We now indicate that this thread is no longer participating in+     * the psx mechanism.+     */+    registered_thread_t *ref = node;+    pthread_mutex_lock(&ref->mu);+    ref->gone = 1;+    pthread_mutex_unlock(&ref->mu);++    /*+     * At this point, we can restore the calling sigmask to whatever+     * the caller thought was appropriate for a dying thread to have.+     */+    pthread_sigmask(SIG_SETMASK, &orig_sigbits, NULL);++    /*+     * Allow the rest of the psx system to carry on as per normal.+     */+    psx_new_state(_PSX_EXITING, _PSX_IDLE);+}++/*+ * _psx_start_fn is a trampoline for the intended start function, it+ * is called blocked (_PSX_CREATE), but releases the block before+ * calling starter->fn. Before releasing the block, the TLS specific+ * attributes 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) {+    void *node = 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);++    void *ret;++    pthread_cleanup_push(_psx_exiting, node);+    ret = fn(arg);+    pthread_cleanup_pop(1);++    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) {+    psx_starter_t *starter = calloc(1, sizeof(psx_starter_t));+    if (starter == NULL) {+	perror("failed at thread creation");+	exit(1);+    }+    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 = __real_pthread_create(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_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.arg4 = arg[3];+	psx_tracker.cmd.arg5 = count > 4 ? arg[4] : 0;+	psx_tracker.cmd.arg6 = 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);+    psx_confirm_sigaction();++    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;+	int gone = ref->gone;+	if (!gone) {+	    gone = pthread_kill(ref->thread, psx_tracker.psx_sig) != 0;+	}+	pthread_mutex_unlock(&ref->mu);+	if (!gone) {+	    continue;+	}+	/*+	 * need to remove invalid thread id from linked list+	 */+	psx_do_unregister(ref);+    }+    psx_unlock();++    int mismatch = 0;+    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;+	    int gone = ref->gone;+	    if (!gone) {+		if (pending) {+		    gone = (pthread_kill(ref->thread, 0) != 0);+		} else {+		    mismatch |= (ref->retval != ret);+		}+	    }+	    pthread_mutex_unlock(&ref->mu);+	    if (!gone) {+		waiting += pending;+		continue;+	    }+	    /*+	     * need to remove invalid thread id from linked list+	     */+	    psx_do_unregister(ref);+	}+	psx_unlock();+	if (!waiting) {+	    break;+	}+	sched_yield();+    }++    psx_tracker.cmd.active = 0;+    if (mismatch) {+	psx_lock();+	switch (psx_tracker.sensitivity) {+	case PSX_IGNORE:+	    break;+	default:+	    fprintf(stderr, "psx_syscall result differs.\n");+	    if (psx_tracker.cmd.six) {+		fprintf(stderr, "trap:%ld a123456=[%ld,%ld,%ld,%ld,%ld,%ld]\n",+			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);+	    } else {+		fprintf(stderr, "trap:%ld a123=[%ld,%ld,%ld]\n",+			psx_tracker.cmd.syscall_nr,+			psx_tracker.cmd.arg1,+			psx_tracker.cmd.arg2,+			psx_tracker.cmd.arg3);+	    }+	    fprintf(stderr, "results:");+	    for (ref = psx_tracker.root; ref; ref = next) {+		next = ref->next;+		if (ref->thread == self) {+		    continue;+		}+		if (ret != ref->retval) {+		    fprintf(stderr, " %d={%ld}", ref->tid, ref->retval);+		}+	    }+	    fprintf(stderr, " wanted={%ld}\n", ret);+	    if (psx_tracker.sensitivity == PSX_WARNING) {+		break;+	    }+	    pthread_kill(self, SIGSYS);+	}+	psx_unlock();+    }+    errno = restore_errno;+    psx_new_state(_PSX_SYSCALL, _PSX_IDLE);++defer:+    return ret;+}++/*+ * _psx_cleanup its called when the program exits. It is used to free+ * any memory used by the thread tracker.+ */+static void _psx_cleanup(void) {+    registered_thread_t *ref, *next;++    /*+     * We enter the exiting state. Unlike exiting a single thread we+     * never leave this state since this cleanup is only done at+     * program exit.+     */+    psx_lock();+    while (psx_tracker.state != _PSX_IDLE && psx_tracker.state != _PSX_INFORK) {+	pthread_cond_wait(&psx_tracker.cond, &psx_tracker.state_mu);+    }+    psx_tracker.state = _PSX_EXITING;+    psx_unlock();++    for (ref = psx_tracker.root; ref; ref = next) {+	next = ref->next;+	psx_do_unregister(ref);+    }+}++/*+ * Change the PSX sensitivity level. If the threads appear to have+ * diverged in behavior, this can cause the library to notify the+ * user.+ */+int psx_set_sensitivity(psx_sensitivity_t level) {+    if (level < PSX_IGNORE || level > PSX_ERROR) {+	errno = EINVAL;+	return -1;+    }+    psx_lock();+    psx_tracker.sensitivity = level;+    psx_unlock();+    return 0;+}
+ cbits/psx/psx_syscall.h view
@@ -0,0 +1,95 @@+/*+ * 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).+ */++#ifndef _SYS_PSX_SYSCALL_H+#define _SYS_PSX_SYSCALL_H++#ifdef __cplusplus+extern "C" {+#endif++#include <pthread.h>++/*+ * psx_syscall performs the specified syscall on all psx registered+ * threads. The mechanism 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__, (long int) 6, (long int) 5, \+		  (long int) 4, (long int) 3, (long int) 2, \+		  (long int) 1, (long int) 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);++/*+ * This function should be used by systems to obtain pointers to the+ * two syscall functions provided by the PSX library. A linkage trick+ * is to define this function as weak in a library that can optionally+ * use libpsx and then, should the caller link -lpsx, that library can+ * implicitly use these POSIX semantics syscalls. See libcap for an+ * example of this usage.+ */+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));++/*+ * psx_sensitivity_t holds the level of paranoia for non-POSIX syscall+ * behavior. The default is PSX_IGNORE: which is best effort - no+ * enforcement; PSX_WARNING will dump to stderr a warning when a+ * syscall's results differ; PSX_ERROR will dump info as per+ * PSX_WARNING and generate a SIGSYS. The current mode can be set with+ * psx_set_sensitivity().+ */+typedef enum {+    PSX_IGNORE = 0,+    PSX_WARNING = 1,+    PSX_ERROR = 2,+} psx_sensitivity_t;++/*+ * psx_set_sensitivity sets the current sensitivity of the PSX+ * mechanism.  The function returns 0 on success and -1 if the+ * requested level is invalid.+ */+int psx_set_sensitivity(psx_sensitivity_t level);++#ifdef __cplusplus+}+#endif++#endif /* _SYS_PSX_SYSCALL_H */
+ psx.cabal view
@@ -0,0 +1,119 @@+Cabal-Version:       2.2+Build-Type:          Simple++Name:                psx+Version:             0.1.0.0+Synopsis:            Integrate @libpsx@ with the GHC RTS+Description:+  This library embeds libpsx in a GHC Haskell-compiled application.+  .+  Note @libpsx@ performs some trickery with signal handling in a process.+  Furthermore, when using this library, @sigfillset@ will be wrapped so+  @SIGSYS@ is /not/ set, in order for the GHC RTS @ticker@ thread not to+  block the signal and work properly with @libpsx@.+  .+  __Warning:__ @libpsx@ on current Ubuntu and Debian systems (from @libcap@+  2.44) is broken. Hence, this library contains a bundled version of @libpsx@+  by default. Disable the @bundled-libpsx@ flag to use a system-provided+  version of the library, which must be from @libcap@ version 2.46 or higher.+  The license of the bundled library can be found in @cbits\/psx\/License@.+Homepage:            https://github.com/NicolasT/landlock-hs+Bug-Reports:         https://github.com/NicolasT/landlock-hs/issues+License:             BSD-3-Clause+License-File:        LICENSE+Author:              Nicolas Trangez+Maintainer:          ikke@nicolast.be+Copyright:           (c) 2022 Nicolas Trangez+Category:            System+Stability:           alpha++Extra-Source-Files:+  CHANGELOG.md+  README.md+  cbits/hs-psx.h+  cbits/psx/License+  cbits/psx/psx_syscall.h+  test/detect-psx.h++Tested-With:         GHC ==8.10.5+                   , GHC ==9.0.2+                   , GHC ==9.2.2++Source-Repository head+  Type:                git+  Location:            https://github.com/NicolasT/landlock-hs.git+  Subdir:              psx+  Branch:              main++Flag bundled-libpsx+  Description:         Use the bundled libpsx instead of a (potentially broken) system-provided version. See @cbits\/psx\/License@.+  Default:             True+  Manual:              True++Library+  -- Despite this library not containing any Haskell code, and+  -- hence not really having any dependency on `base`, it is+  -- only *tested* on particular GHC versions (which we can tie+  -- to some version of `base`). Given the intricate interaction+  -- between this package functionality working as expected (cfr.+  -- the `sigfillset` wrapping) and the GHC RTS, it seems wise to+  -- restrict compatibility with said GHC version(s).+  Exposed-Modules:     System.PSX+  Hs-Source-Dirs:      src+  Build-Depends:       base ^>=4.14.2.0 || ^>=4.15 || ^>=4.16+  Include-Dirs:        cbits+  Install-Includes:    hs-psx.h+  C-Sources:           cbits/hs-psx.c+  Cc-Options:          -Wall+  Ld-Options:          -Wl,-wrap,sigfillset -Wl,-undefined,__wrap_sigfillset+  Default-Language:    Haskell2010+  Ghc-Options:         -Wall+  if flag(bundled-libpsx)+    C-Sources:         cbits/psx/psx.c+    Cc-Options:        -DBUNDLED_LIBPSX+    Ld-Options:        -pthread -Wl,-wrap,pthread_create+  else+    Pkgconfig-Depends: libpsx++Test-Suite psx-test-threaded+  Type:                exitcode-stdio-1.0+  Hs-Source-Dirs:      test+  Main-Is:             psx-test-threaded.hs+  Other-Modules:       TestCases+  C-Sources:           test/detect-psx.c+  Include-Dirs:        test+  Build-Depends:       psx+                     , base+                     , async ^>=2.2.3+                     , tasty ^>=1.4.1+                     , tasty-hunit ^>=0.10.0.3+  Default-Language:    Haskell2010+  Ghc-Options:         -Wall -threaded -with-rtsopts -N2++Test-Suite psx-test+  Type:                exitcode-stdio-1.0+  Hs-Source-Dirs:      test+  Main-Is:             psx-test.hs+  Other-Modules:       TestCases+  C-Sources:           test/detect-psx.c+  Include-Dirs:        test+  Build-Depends:       psx+                     , base+                     , async ^>=2.2.3+                     , tasty ^>=1.4.1+                     , tasty-hunit ^>=0.10.0.3+  Default-Language:    Haskell2010+  Ghc-Options:         -Wall++Test-Suite psx-test-no-psx+  Type:                exitcode-stdio-1.0+  Hs-Source-Dirs:      test+  Main-Is:             psx-test-no-psx.hs+  C-Sources:           test/detect-psx.c+  Include-Dirs:        test+                       cbits+  Build-Depends:       base+                     , tasty ^>=1.4.1+                     , tasty-hunit ^>=0.10.0.3+  Default-Language:    Haskell2010+  Ghc-Options:         -Wall
+ src/System/PSX.hs view
@@ -0,0 +1,31 @@+-- |+-- Module:      System.PSX+-- Description: Haskell bindings for libpsx+-- Copyright:   (c) Nicolas Trangez, 2022+-- License:     BSD-3-Clause+-- Maintainer:  ikke@nicolast.be+-- Stability:   alpha+-- Portability: Linux+--+-- This library embeds libpsx in a GHC Haskell-compiled application.+--+-- It doesn't provide any Haskell functions. It can be (optionally) linked+-- into applications which require @libpsx@-style functionality, potentially+-- from C code. See @test/detect-psx.c@ for an example.+--+-- Note @libpsx@ performs some trickery with signal handling in a process.+-- Furthermore, when using this library, @sigfillset@ will be wrapped so+-- @SIGSYS@ is /not/ set, in order for the GHC RTS @ticker@ thread not to+-- block the signal and work properly with @libpsx@.+--+-- See+-- [this GHC proposal](https://github.com/ghc-proposals/ghc-proposals/pull/533)+-- for a potential future replacement of this library using functionality built+-- into the GHC RTS.+--+-- __Warning:__ @libpsx@ on current Ubuntu and Debian systems (from @libcap@+-- 2.44) is broken. Hence, this library contains a bundled version of @libpsx@+-- by default. Disable the @bundled-libpsx@ flag to use a system-provided+-- version of the library, which must be from @libcap@ version 2.46 or higher.++module System.PSX () where
+ test/TestCases.hsc view
@@ -0,0 +1,172 @@+module TestCases (+      psxSyscall3Works+    , psxSyscall6Works+    , psxWorking+    , sigfillsetWrapped+    , psxDetected+    ) where++#include <signal.h>+#include <unistd.h>+#include <sys/prctl.h>+#include <sys/syscall.h>++#include <hs-psx.h>++import Control.Concurrent.Async (wait, withAsyncBound)+import Control.Concurrent.MVar (newEmptyMVar, putMVar, readMVar)+import Control.Monad (void)+import Data.Int (Int32, Int64)+import Data.Word (Word64)+import Foreign.C.Error (throwErrnoIfMinus1)+import Foreign.Marshal (alloca)+import Foreign.Ptr (Ptr)+import Foreign.Storable (Storable(..))+import Test.Tasty (TestTree)+import Test.Tasty.HUnit ((@?=), assertBool, testCase, testCaseSteps)++foreign import ccall unsafe "hs-psx.h hs_psx_syscall3"+    psxSyscall3 :: #{type long}+                -> #{type long}+                -> #{type long}+                -> #{type long}+                -> IO #{type long}++foreign import ccall unsafe "hs-psx.h hs_psx_syscall6"+    psxSyscall6 :: #{type long}+                -> #{type long}+                -> #{type long}+                -> #{type long}+                -> #{type long}+                -> #{type long}+                -> #{type long}+                -> IO #{type long}++foreign import ccall unsafe "unistd.h getpid"+    c_getpid :: IO #{type pid_t}++getpid :: IO #{type pid_t}+getpid = throwErrnoIfMinus1 "getpid" c_getpid++foreign import ccall unsafe "unistd.h gettid"+    c_gettid :: IO #{type pid_t}++gettid :: IO #{type pid_t}+gettid = throwErrnoIfMinus1 "gettid" c_gettid++psxSyscall3Works :: TestTree+psxSyscall3Works = testCase "hs_psx_syscall3 works" $ do+    pid <- getpid+    tid <- gettid+    let sig = #{const SIGURG}+    void $ throwErrnoIfMinus1 "tgkill" $ psxSyscall3 #{const __NR_tgkill} (fromIntegral pid) (fromIntegral tid) sig++psxSyscall6Works :: TestTree+psxSyscall6Works = testCase "hs_psx_syscall6 works" $ do+    void $ throwErrnoIfMinus1 "prctl" $ psxSyscall6 #{const __NR_prctl} #{const PR_GET_NO_NEW_PRIVS} 0 0 0 0 0+++foreign import ccall unsafe "sys/prctl.h prctl"+    _prctl :: #{type int}+           -> #{type unsigned long}+           -> #{type unsigned long}+           -> #{type unsigned long}+           -> #{type unsigned long}+           -> IO #{type int}++prctl :: #{type int}+      -> #{type unsigned long}+      -> #{type unsigned long}+      -> #{type unsigned long}+      -> #{type unsigned long}+      -> IO #{type int}+prctl option arg2 arg3 arg4 arg5 = throwErrnoIfMinus1 "prctl" $ _prctl option arg2 arg3 arg4 arg5++prctlPsx :: #{type int}+         -> #{type unsigned long}+         -> #{type unsigned long}+         -> #{type unsigned long}+         -> #{type unsigned long}+          -> IO #{type int}+prctlPsx option arg2 arg3 arg4 arg5 = do+    rc <- throwErrnoIfMinus1 "prctl" $+        psxSyscall6 #{const __NR_prctl} (fromIntegral option) (fromIntegral arg2) (fromIntegral arg3) (fromIntegral arg4) (fromIntegral arg5) 0+    return $ fromIntegral rc+++psxWorking :: TestTree+psxWorking = testCaseSteps "psx is working" $ \step -> do+    tidMVar <- newEmptyMVar+    continueMVar <- newEmptyMVar++    step "Launching thread"+    withAsyncBound (thread step tidMVar continueMVar) $ \async -> do+        assertNoNewPrivs 0++        myTid <- gettid+        step "Waiting for thread TID"+        itsTid <- readMVar tidMVar++        step "Comparing TIDs"+        assertBool "TIDs are not different" $ myTid /= itsTid++        step "Setting PR_SET_NO_NEW_PRIVS"+        0 <- prctlPsx #{const PR_SET_NO_NEW_PRIVS} 1 0 0 0++        step "Letting thread continue"+        putMVar continueMVar ()++        assertNoNewPrivs 1++        step "Joining thread"+        wait async+  where+    getNoNewPrivs = prctl #{const PR_GET_NO_NEW_PRIVS} 0 0 0 0+    assertNoNewPrivs v =+        getNoNewPrivs >>= \r -> r @?= v++    thread step tidMVar continueMVar = do+        step "In thread" :: IO ()+        assertNoNewPrivs 0++        myTid <- gettid+        step "Sending TID"+        putMVar tidMVar myTid++        step "Waiting to continue"+        () <- readMVar continueMVar+        assertNoNewPrivs 1++++data SigsetT++instance Storable SigsetT where+    sizeOf _ = #{size sigset_t}+    alignment _ = #{alignment sigset_t}+    peek _ = error "peek not implemented"+    poke _ _ = error "poke not implemented"++foreign import ccall unsafe "signal.h sigfillset"+    sigfillset :: Ptr SigsetT -> IO #{type int}++foreign import ccall unsafe "signal.h sigismember"+    sigismember :: Ptr SigsetT -> #{type int} -> IO #{type int}++sigfillsetWrapped :: TestTree+sigfillsetWrapped = testCase "sigfillset is wrapped" $ alloca $ \set -> do+    0 <- throwErrnoIfMinus1 "sigfillset" $ sigfillset set+    isMember <- throwErrnoIfMinus1 "sigismember" $ sigismember set #{const HS_PSX_SIGNAL}+    isMember @?= 0+++foreign import ccall unsafe "detect-psx.h detect_psx"+    detectPsx :: IO #{type int}++psxDetected :: TestTree+psxDetected = testCase "psx detected by C code" $ do+    assertBool "psx not detected" . toBool =<< detectPsx+  where+    toBool n = n /= 0++
+ test/detect-psx.c view
@@ -0,0 +1,8 @@+#include <stdlib.h>++long __attribute__((weak)) hs_psx_syscall3();+long __attribute__((weak)) hs_psx_syscall6();++int detect_psx(void) {+        return &hs_psx_syscall3 == NULL || &hs_psx_syscall6 == NULL ? 0 : 1;+}
+ test/detect-psx.h view
@@ -0,0 +1,14 @@+#ifndef _DETECT_PSX_H_+#define _DETECT_PSX_H_++#ifdef __cplusplus+extern "C" {+#endif++int detect_psx(void);++#ifdef __cplusplus+}+#endif++#endif
+ test/psx-test-no-psx.hsc view
@@ -0,0 +1,48 @@+module Main (main) where++#include "hs-psx.h"++import Data.Int (Int32)+import Foreign.C.Error (throwErrnoIfMinus1)+import Foreign.Ptr (Ptr)+import Foreign.Marshal (alloca)+import Foreign.Storable (Storable(..))++import Test.Tasty (defaultMain, testGroup)+import Test.Tasty.HUnit ((@?=), assertBool, testCase)++main :: IO ()+main = defaultMain $ testGroup "Tests" [+      testCase "sigfillset sets HS_PSX_SIGNAL" sigfillsetNotWrapped+    , testCase "Haskell psx is not detected" psxNotDetected+    ]++data SigsetT++instance Storable SigsetT where+    sizeOf _ = #{size sigset_t}+    alignment _ = #{alignment sigset_t}+    peek _ = error "peek not implemented"+    poke _ _ = error "poke not implemented"++foreign import ccall unsafe "signal.h sigfillset"+    sigfillset :: Ptr SigsetT -> IO #{type int}++foreign import ccall unsafe "signal.h sigismember"+    sigismember :: Ptr SigsetT -> #{type int} -> IO #{type int}++sigfillsetNotWrapped :: IO ()+sigfillsetNotWrapped = alloca $ \set -> do+    0 <- throwErrnoIfMinus1 "sigfillset" $ sigfillset set+    isMember <- throwErrnoIfMinus1 "sigismember" $ sigismember set #{const HS_PSX_SIGNAL}+    isMember @?= 1+++foreign import ccall unsafe "detect-psx.h detect_psx"+    detectPsx :: IO #{type int}++psxNotDetected :: IO ()+psxNotDetected =+    assertBool "psx detected" . not . toBool =<< detectPsx+  where+    toBool n = n /= 0
+ test/psx-test-threaded.hs view
@@ -0,0 +1,14 @@+module Main (main) where++import Test.Tasty (defaultMain, testGroup)++import qualified TestCases as T++main :: IO ()+main = defaultMain $ testGroup "Tests" [+      T.sigfillsetWrapped+    , T.psxDetected+    , T.psxSyscall3Works+    , T.psxSyscall6Works+    , T.psxWorking+    ]
+ test/psx-test.hs view
@@ -0,0 +1,14 @@+module Main (main) where++import Test.Tasty (defaultMain, testGroup)++import qualified TestCases as T++main :: IO ()+main = defaultMain $ testGroup "Tests" [+      T.sigfillsetWrapped+    , T.psxDetected+    , T.psxSyscall3Works+    , T.psxSyscall6Works+    ]+