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 +5/−0
- LICENSE +29/−0
- README.md +18/−0
- cbits/hs-psx.c +33/−0
- cbits/hs-psx.h +20/−0
- cbits/psx/License +396/−0
- cbits/psx/psx.c +765/−0
- cbits/psx/psx_syscall.h +95/−0
- psx.cabal +119/−0
- src/System/PSX.hs +31/−0
- test/TestCases.hsc +172/−0
- test/detect-psx.c +8/−0
- test/detect-psx.h +14/−0
- test/psx-test-no-psx.hsc +48/−0
- test/psx-test-threaded.hs +14/−0
- test/psx-test.hs +14/−0
+ 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+ ]+