Snap for 10453563 from e61c65778b22599546e46eee12f857dd1c28f89e to mainline-art-release

Change-Id: Ie8552e039f901587d36f634617efbf0ba1929181
diff --git a/.gitignore b/.gitignore
index 1971ad5..8698f19 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 patches/
 *.o
 *~
+*.cf
diff --git a/Android.bp b/Android.bp
index 8360f69..69773aa 100644
--- a/Android.bp
+++ b/Android.bp
@@ -84,6 +84,8 @@
     apex_available: [
         "//apex_available:platform",
         "com.android.adbd",
+        "com.android.art",
+        "com.android.art.debug",
         "com.android.compos",
         "com.android.media.swcodec",
         "com.android.virt",
@@ -117,13 +119,8 @@
     name: "cap_names.list.h",
     srcs: ["libcap/include/uapi/linux/capability.h"],
     out: ["cap_names.list.h"],
-    tool_files: [":generate_cap_names_list.awk"],
-    cmd: "awk -f $(location :generate_cap_names_list.awk) $(in) > $(out)",
-}
-
-filegroup {
-    name: "generate_cap_names_list.awk",
-    srcs: ["generate_cap_names_list.awk"],
+    tool_files: ["generate_cap_names_list.awk"],
+    cmd: "awk -f $(location generate_cap_names_list.awk) $(in) > $(out)",
 }
 
 //
diff --git a/License b/License
index 8a352bc..43a1297 100644
--- a/License
+++ b/License
@@ -1,8 +1,16 @@
 Unless otherwise *explicitly* stated, the following text describes the
 licensed conditions under which the contents of this libcap release
-may be used and distributed:
+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, with
 or without modification, are permitted provided that the following
 conditions are met:
@@ -20,13 +28,6 @@
    products derived from this software without their specific prior
    written permission.
 
-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.)
-
 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.
@@ -38,7 +39,15 @@
 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.
 
 -------------------------
 Full text of gpl-2.0.txt:
diff --git a/METADATA b/METADATA
index 052422f..d84738c 100644
--- a/METADATA
+++ b/METADATA
@@ -5,14 +5,11 @@
     type: GIT
     value: "https://git.kernel.org/pub/scm/linux/kernel/git/morgan/libcap.git"
   }
-  version: "libcap-2.48"
+  version: "libcap-2.53"
   license_type: NOTICE
   last_upgrade_date {
-    year: 2021
-    month: 2
-    day: 5
-  }
-  security {
-    tag: "NVD-CPE2.3:cpe:/a:tcpdump:libpcap:-"
+    year: 2022
+    month: 10
+    day: 10
   }
 }
diff --git a/Make.Rules b/Make.Rules
index ded9014..125f2aa 100644
--- a/Make.Rules
+++ b/Make.Rules
@@ -1,7 +1,7 @@
 # Common version number defines for libcap
 LIBTITLE=libcap
 VERSION=2
-MINOR=48
+MINOR=53
 
 #
 ## Optional prefixes:
@@ -43,11 +43,11 @@
 PKGCONFIGDIR=$(LIBDIR)/pkgconfig
 GOPKGDIR=$(prefix)/share/gocode/src
 
-# Once go1.16 is released, I plan to set this value to 1 and keep it
-# there. The Go packages should always remain backwardly compatible,
-# but I may have to up it if Go's syntax dramatically changes in a
-# backwards incompatible manner. (Let's hope not.)
-GOMAJOR=0
+# From here on out, the Go module packages should always remain
+# backwardly compatible. I will only resort to using major version 2
+# etc if Go's syntax dramatically changes in a backwards incompatible
+# manner. (Let's hope not.)
+GOMAJOR=1
 
 # Compilation specifics
 
@@ -63,6 +63,7 @@
 BUILD_CFLAGS ?= $(BUILD_COPTS) $(DEFINES) $(IPATH)
 AR := $(CROSS_COMPILE)ar
 RANLIB := $(CROSS_COMPILE)ranlib
+OBJCOPY := $(CROSS_COMPILE)objcopy
 DEBUG = -g #-DDEBUG
 WARNINGS=-Wall -Wwrite-strings \
         -Wpointer-arith -Wcast-qual -Wcast-align \
@@ -78,7 +79,6 @@
 
 SYSTEM_HEADERS = /usr/include
 INCS=$(topdir)/libcap/include/sys/capability.h
-LDFLAGS += -L$(topdir)/libcap
 CFLAGS += -Dlinux $(WARNINGS) $(DEBUG)
 INDENT := $(shell if [ -n "$$(which indent 2>/dev/null)" ]; then echo "| indent -kr" ; fi)
 
@@ -114,7 +114,7 @@
 # Strictly speaking go1.15 doesn't need this, but 1.16 is when the
 # real golang support arrives for non-cgo support, so drop the last
 # vestige of legacy workarounds then.
-CGO_LDFLAGS_ALLOW := -Wl,-?-wrap[=,][^-.@][^,]*
+CGO_LDFLAGS_ALLOW := CGO_LDFLAGS_ALLOW="-Wl,-?-wrap[=,][^-.@][^,]*"
 endif
 CGO_CFLAGS := -I$(topdir)/libcap/include
 CGO_LDFLAGS := -L$(topdir)/libcap
@@ -155,7 +155,7 @@
 #
 # In the context of this tree, on such such systems, a yes setting will
 # guarantee that every user, by default, is able to bless any binary with
-# any capability - a ready made local exploit machanism.
+# any capability - a ready made local exploit mechanism.
 RAISE_SETFCAP := no
 
 # If set to yes, this will cause the go "web" demo app to force the needed p
diff --git a/Makefile b/Makefile
index 7150b9b..d26af01 100644
--- a/Makefile
+++ b/Makefile
@@ -8,7 +8,7 @@
 # flags
 #
 
-all install clean kdebug: %: %-here
+all install clean: %: %-here
 	$(MAKE) -C libcap $@
 ifneq ($(PAM_CAP),no)
 	$(MAKE) -C pam_cap $@
@@ -32,7 +32,7 @@
 distclean: clean
 	$(DISTCLEAN)
 	@echo "CONFIRM Go package cap has right version dependency on cap/psx:"
-	for x in $$(find . -name go.mod); do grep -F -v "module" $$x | fgrep "kernel.org/pub/linux/libs/security/libcap" > /dev/null || continue ; grep -F "v$(GOMAJOR).$(VERSION).$(MINOR)" $$x  > /dev/null && continue ; echo "$$x is not updated to v$(GOMAJOR).$(VERSION).$(MINOR)" ; exit 1 ; done
+	for x in $$(find . -name go.mod); do grep -F -v "module" $$x | fgrep "kernel.org/pub/linux/libs/security/libcap" > /dev/null || continue ; grep -F "v$(GOMAJOR).$(VERSION).$(MINOR)" $$x  > /dev/null && continue ; echo "$$x is not updated. Try running: ./gomods.sh v$(GOMAJOR).$(VERSION).$(MINOR)" ; exit 1 ; done
 	@echo "ALL go.mod files updated"
 	@echo "Now validate that everything is checked in to a clean tree.."
 	test -z "$$(git status --ignored -s)"
@@ -52,6 +52,9 @@
 endif
 	$(MAKE) -C progs $@
 
+ktest: all
+	$(MAKE) -C kdebug test
+
 sudotest: all
 	$(MAKE) -C tests $@
 ifneq ($(PAM_CAP),no)
@@ -65,14 +68,20 @@
 distcheck:
 	./distcheck.sh
 	$(MAKE) DYNAMIC=yes clean all test sudotest
-	$(MAKE) CC=/usr/local/musl/bin/musl-gcc clean all test sudotest
+	$(MAKE) DYNAMIC=no COPTS="-O2 -std=c89" clean all test sudotest
+	$(MAKE) PAM_CAP=no CC=/usr/local/musl/bin/musl-gcc clean all test sudotest
+	$(MAKE) CC=clang clean all test sudotest
 	$(MAKE) clean all test sudotest
 	$(MAKE) distclean
 
 morgangodoc:
-	@echo "Now the release is made, you want to remember to run:"
+	@echo "Now the release is made, you want to remember to run one of:"
 	@echo
-	@echo "GOPROXY=https://proxy.golang.org GO111MODULE=on go get kernel.org/pub/linux/libs/security/libcap/cap@v$(GOMAJOR).$(VERSION).$(MINOR)"
+	@echo "  GOPROXY=https://proxy.golang.org GO111MODULE=on go get kernel.org/pub/linux/libs/security/libcap/cap@v$(GOMAJOR).$(VERSION).$(MINOR)"
+	@echo
+	@echo or press the request button on this page:
+	@echo
+	@echo "  https://pkg.go.dev/kernel.org/pub/linux/libs/security/libcap/cap@v$(GOMAJOR).$(VERSION).$(MINOR)"
 	@echo
 	@echo "This will cause a go.dev documentation update."
 
@@ -82,8 +91,8 @@
 	git tag -u E2CCF3F4 -s libcap-korg-$(VERSION).$(MINOR) -m "This is libcap-$(VERSION).$(MINOR)"
 	@echo "The following are for the Go module tracking."
 	git tag -u D41A6DF2 -s v$(GOMAJOR).$(VERSION).$(MINOR) -m "This is the version tag for the 'libcap' Go base directory associated with libcap-$(VERSION).$(MINOR)."
-	git tag -u D41A6DF2 -s psx/v$(GOMAJOR).$(VERSION).$(MINOR) -m "This is the version tag for the 'psx' Go package associated with libcap-$(VERSION).$(MINOR)."
-	git tag -u D41A6DF2 -s cap/v$(GOMAJOR).$(VERSION).$(MINOR) -m "This is the version tag for the 'cap' Go package associated with libcap-$(VERSION).$(MINOR)."
+	git tag -u D41A6DF2 -s psx/v$(GOMAJOR).$(VERSION).$(MINOR) -m "This is the (stable) version tag for the 'psx' Go package associated with libcap-$(VERSION).$(MINOR)."
+	git tag -u D41A6DF2 -s cap/v$(GOMAJOR).$(VERSION).$(MINOR) -m "This is the (stable) version tag for the 'cap' Go package associated with libcap-$(VERSION).$(MINOR)."
 	$(MAKE) release
 	@echo "sign the tar file using korg key"
 	cd .. && gpg -sba -u E2CCF3F4 libcap-$(VERSION).$(MINOR).tar
diff --git a/README b/README
index 6ba482c..9c4a3ea 100644
--- a/README
+++ b/README
@@ -5,45 +5,53 @@
 
 This library would not have been possible without the help of 
 
-	Aleph1, Roland Buresund and Andrew Main, Alexander Kjeldaas.
+    Aleph1, Roland Buresund and Andrew Main, Alexander Kjeldaas.
 
 More information on capabilities in the Linux kernel, links to the
-official git repostitory for libcap, release notes and how to report
+official git repository for libcap, release notes and how to report
 bugs can be found at:
 
-	http://sites.google.com/site/fullycapable/
+    http://sites.google.com/site/fullycapable/
+
+The primary upstream git repository is this one:
+
+    https://git.kernel.org/pub/scm/libs/libcap/libcap.git/
 
 # BUILDING AND INSTALLATION
 
-	$ make
+    $ make
 
-	       builds the library and the programs that are expected
-	       to work on your system. For example, if you have
-	       Linux-PAM installed, pam_cap is built. A golang
-	       installation is required to build the Go packages.
+       builds the library and the programs that are expected to work
+       on your system. For example, if you have Linux-PAM installed,
+       pam_cap is built. A golang installation is required to build
+       the Go packages.
 
-        $ make test
+    $ make test
 
-	       runs all of the tests not requiring privilege
+       runs all of the tests not requiring privilege
 
-	$ make sudotest
+    $ make sudotest
 
-	       runs all of the tests including those that require privilege.
+       runs all of the tests including those that require privilege.
 
-	$ sudo make install
+    $ sudo make install
 
-	       default installs the library libcap.XX.Y in /lib[64]/
-	       the binaries in /sbin/
-	       the header files in /usr/include
-	       the {libcap,libpsx}.pc files in /usr/lib[64]/pkgconfig
-	       the Go packages (if built) under /usr/share/gocode/src
+       default installs the library libcap.XX.Y in /lib[64]/
+       the binaries in /sbin/
+       the header files in /usr/include
+       the {libcap,libpsx}.pc files in /usr/lib[64]/pkgconfig
+       the Go packages (if built) under /usr/share/gocode/src
 
-For some example C programs look in the progs/ directory. Specifically,
-capsh, getpcaps, setcap and getcap.
+For some example C programs look in the progs/ directory.
+Specifically, capsh, getpcaps, setcap and getcap. There are some C
+tests in the tests/ directory.
 
 Go example programs are to be found in the goapps/ directory. There
 are also some more complicated integration tests in the go/ directory.
 
+There are also some oddball experimental things in the contrib/
+directory, but they are mostly curiosities.
+
 Cheers
 
 Andrew G. Morgan <morgan@kernel.org>
diff --git a/cap/LICENSE b/cap/License
similarity index 98%
rename from cap/LICENSE
rename to cap/License
index 1c65641..a0ec04e 100644
--- a/cap/LICENSE
+++ b/cap/License
@@ -1,8 +1,16 @@
 Unless otherwise *explicitly* stated, the following text describes the
 licensed conditions under which the contents of this libcap/cap release
-may be used and distributed:
+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/cap, with
 or without modification, are permitted provided that the following
 conditions are met:
@@ -20,13 +28,6 @@
    products derived from this software without their specific prior
    written permission.
 
-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.)
-
 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.
@@ -38,7 +39,17 @@
 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:
diff --git a/cap/README b/cap/README
index 3ac8433..f257d44 100644
--- a/cap/README
+++ b/cap/README
@@ -4,7 +4,7 @@
    https://sites.google.com/site/fullycapable/
 
 Like libcap, the cap package is distributed with a "you choose"
-License. Specifically: BSD three clause, or GPL2. See the LICENSE
+License. Specifically: BSD three clause, or GPL2. See the License
 file.
 
 Andrew G. Morgan <morgan@kernel.org>
diff --git a/cap/cap.go b/cap/cap.go
index 5ccef59..908e2bb 100644
--- a/cap/cap.go
+++ b/cap/cap.go
@@ -28,27 +28,51 @@
 //       log.Fatalf("failed to drop privilege: %q -> %q: %v", old, empty, err)
 //   }
 //   now := cap.GetProc()
-//   if cap.Differs(now.Compare(empty)) {
+//   if cf, _ := now.Compare(empty); cf != 0 {
 //       log.Fatalf("failed to fully drop privilege: have=%q, wanted=%q", now, empty)
 //   }
 //
+// The "cap" package operates with POSIX semantics for security
+// state. That is all OS threads are kept in sync at all times. The
+// package "kernel.org/pub/linux/libs/security/libcap/psx" is used to
+// implement POSIX semantics system calls that manipulate thread state
+// uniformly over the whole Go (and any CGo linked) process runtime.
+//
+// Note, if the Go runtime syscall interface contains the Linux
+// variant syscall.AllThreadsSyscall() API (it debuted in go1.16 see
+// https://github.com/golang/go/issues/1435 for its history) then the
+// "libcap/psx" package will use that to invoke Capability setting
+// system calls in pure Go binaries. With such an enhanced Go runtime,
+// to force this behavior, use the CGO_ENABLED=0 environment variable.
+//
+// POSIX semantics are more secure than trying to manage privilege at
+// a thread level when those threads share a common memory image as
+// they do under Linux: it is trivial to exploit a vulnerability in
+// one thread of a process to cause execution on any another
+// thread. So, any imbalance in security state, in such cases will
+// readily create an opportunity for a privilege escalation
+// vulnerability.
+//
+// POSIX semantics also work well with Go, which deliberately tries to
+// insulate the user from worrying about the number of OS threads that
+// are actually running in their program. Indeed, Go can efficiently
+// launch and manage tens of thousands of concurrent goroutines
+// without bogging the program or wider system down. It does this by
+// aggressively migrating idle threads to make progress on unblocked
+// goroutines. So, inconsistent security state across OS threads can
+// also lead to program misbehavior.
+//
+// The only exception to this process-wide common security state is
+// the cap.Launcher related functionality. This briefly locks an OS
+// thread to a goroutine in order to launch another executable - the
+// robust implementation of this kind of support is quite subtle, so
+// please read its documentation carefully, if you find that you need
+// it.
+//
 // See https://sites.google.com/site/fullycapable/ for recent updates,
 // some more complete walk-through examples of ways of using
 // 'cap.Set's etc and information on how to file bugs.
 //
-// For CGo linked binaries, behind the scenes, the package
-// "kernel.org/pub/linux/libs/security/libcap/psx" is used to perform
-// POSIX semantics system calls that manipulate thread state
-// uniformly over the whole Go (and CGo linked) process runtime.
-//
-// Note, if the Go runtime syscall interface contains the Linux
-// variant syscall.AllThreadsSyscall() API (it debuted in go1.16 see
-// https://github.com/golang/go/issues/1435 for its history) then
-// the "psx" package will use that to invoke Capability setting system
-// calls in pure Go binaries. In such an enhanced Go runtime, to force
-// this behavior, use the CGO_ENABLED=0 environment variable.
-//
-//
 // Copyright (c) 2019-21 Andrew G. Morgan <morgan@kernel.org>
 //
 // The cap and psx packages are licensed with a (you choose) BSD
@@ -127,7 +151,7 @@
 )
 
 var (
-	// starUp protects setting of the following values: magic,
+	// startUp protects setting of the following values: magic,
 	// words, maxValues.
 	startUp sync.Once
 
@@ -149,14 +173,6 @@
 	pid   int32
 }
 
-// scwMu is used to fully serialize the write system calls. Note, this
-// is generally not necesary, but in the case of Launch we get into a
-// situation where the launching thread is temporarily allowed to
-// deviate from the kernel state of the rest of the runtime and
-// allowing other threads to perform w* syscalls will potentially
-// interfere with the launching process.
-var scwMu sync.Mutex
-
 // syscaller is a type for abstracting syscalls. The r* variants are
 // for reading state, and can be parallelized, the w* variants need to
 // be serialized so all OS threads can share state.
@@ -245,7 +261,7 @@
 	return int(r), nil
 }
 
-// cInit perfoms the lazy identification of the capability vintage of
+// cInit performs the lazy identification of the capability vintage of
 // the running system.
 func (sc *syscaller) cInit() {
 	h := &header{
@@ -338,10 +354,15 @@
 // process. The kernel will perform permission checks and an error
 // will be returned if the attempt fails. Should the attempt fail
 // no process capabilities will have been modified.
+//
+// Note, the general behavior of this call is to set the
+// process-shared capabilities. However, when called from a callback
+// function as part of a (*Launcher).Launch(), the call only sets the
+// capabilities of the thread being used to perform the launch.
 func (c *Set) SetProc() error {
-	scwMu.Lock()
-	defer scwMu.Unlock()
-	return multisc.setProc(c)
+	state, sc := scwStateSC()
+	defer scwSetState(launchBlocked, state, -1)
+	return sc.setProc(c)
 }
 
 // defines from uapi/linux/prctl.h
@@ -381,9 +402,9 @@
 // ill-defined state. The caller can determine where things went wrong
 // using GetBound().
 func DropBound(val ...Value) error {
-	scwMu.Lock()
-	defer scwMu.Unlock()
-	return multisc.dropBound(val...)
+	state, sc := scwStateSC()
+	defer scwSetState(launchBlocked, state, -1)
+	return sc.dropBound(val...)
 }
 
 // defines from uapi/linux/prctl.h
@@ -428,9 +449,9 @@
 // captures all three inheritable vectors in a single type. Consider
 // using that.
 func SetAmbient(enable bool, val ...Value) error {
-	scwMu.Lock()
-	defer scwMu.Unlock()
-	return multisc.setAmbient(enable, val...)
+	state, sc := scwStateSC()
+	defer scwSetState(launchBlocked, state, -1)
+	return sc.setAmbient(enable, val...)
 }
 
 func (sc *syscaller) resetAmbient() error {
@@ -455,7 +476,7 @@
 // already raised in both the Permitted and Inheritable Set is allowed
 // to be raised by the kernel.
 func ResetAmbient() error {
-	scwMu.Lock()
-	defer scwMu.Unlock()
-	return multisc.resetAmbient()
+	state, sc := scwStateSC()
+	defer scwSetState(launchBlocked, state, -1)
+	return sc.resetAmbient()
 }
diff --git a/cap/cap_test.go b/cap/cap_test.go
index 017c565..d7c7970 100644
--- a/cap/cap_test.go
+++ b/cap/cap_test.go
@@ -106,6 +106,18 @@
 	return nil
 }
 
+func confirmExpectedExport(t *testing.T, info string, c *Set, size uint) {
+	if ex, err := c.Export(); err != nil {
+		t.Fatalf("[%s] failed to export empty set: %v", info, err)
+	} else if n := 5 + 3*size; uint(len(ex)) != n {
+		t.Fatalf("[%s] wrong length: got=%d [%0x] want=%d", info, len(ex), ex, n)
+	} else if im, err := Import(ex); err != nil {
+		t.Fatalf("[%s] failed to import empty set: %v", info, err)
+	} else if got, want := im.String(), c.String(); got != want {
+		t.Fatalf("[%s] import != export: got=%q want=%q [%02x]", info, got, want, ex)
+	}
+}
+
 func TestImportExport(t *testing.T) {
 	wantQ := "=ep cap_chown-e 63+ip"
 	if q, err := FromText(wantQ); err != nil {
@@ -116,15 +128,7 @@
 
 	// Sanity check empty import/export.
 	c := NewSet()
-	if ex, err := c.Export(); err != nil {
-		t.Fatalf("failed to export empty set: %v", err)
-	} else if len(ex) != 5 {
-		t.Fatalf("wrong length: got=%d want=%d", len(ex), 5)
-	} else if im, err := Import(ex); err != nil {
-		t.Fatalf("failed to import empty set: %v", err)
-	} else if got, want := im.String(), c.String(); got != want {
-		t.Fatalf("import != export: got=%q want=%q", got, want)
-	}
+	confirmExpectedExport(t, "empty", c, MinExtFlagSize)
 	// Now keep flipping bits on and off and validate that all
 	// forms of import/export work.
 	for i := uint(0); i < 7000; i += 13 {
@@ -143,6 +147,24 @@
 			t.Fatalf("[%d] miscompare (%q vs. %q): %v", i, got, parsed, err)
 		}
 	}
+
+	oMin := MinExtFlagSize
+	for j := uint(0); j < 5; j++ {
+		t.Logf("exporting with min flag size %d", j)
+		MinExtFlagSize = j
+		c = NewSet()
+		for i := uint(0); i < maxValues; i++ {
+			s := Flag(i % 3)
+			v := Value(i)
+			c.SetFlag(s, true, v)
+			size := 1 + i/8
+			if size < MinExtFlagSize {
+				size = MinExtFlagSize
+			}
+			confirmExpectedExport(t, fmt.Sprintf("%d added %d %v %v", j, i, s, v), c, size)
+		}
+	}
+	MinExtFlagSize = oMin
 }
 
 func TestIAB(t *testing.T) {
@@ -212,3 +234,67 @@
 		}
 	}
 }
+
+func TestFuncLaunch(t *testing.T) {
+	if _, err := FuncLauncher(func(data interface{}) error {
+		return nil
+	}).Launch(nil); err != nil {
+		t.Fatalf("trivial launcher failed: %v", err)
+	}
+
+	for i := 0; i < 100; i++ {
+		expect := i & 1
+		before, err := Prctl(prGetKeepCaps)
+		if err != nil {
+			t.Fatalf("failed to get PR_KEEP_CAPS: %v", err)
+		}
+		if before != expect {
+			t.Fatalf("invalid initial state: got=%d want=%d", before, expect)
+		}
+
+		if _, err := FuncLauncher(func(data interface{}) error {
+			was, ok := data.(int)
+			if !ok {
+				return fmt.Errorf("data was not an int: %v", data)
+			}
+			if _, err := Prctlw(prSetKeepCaps, uintptr(1-was)); err != nil {
+				return err
+			}
+			if v, err := Prctl(prGetKeepCaps); err != nil {
+				return err
+			} else if v == was {
+				return fmt.Errorf("PR_KEEP_CAPS unchanged: got=%d, want=%v", v, 1-was)
+			}
+			// All good.
+			return nil
+		}).Launch(before); err != nil {
+			t.Fatalf("trivial launcher failed: %v", err)
+		}
+
+		// Now validate that the main process is still OK.
+		if after, err := Prctl(prGetKeepCaps); err != nil {
+			t.Fatalf("failed to get PR_KEEP_CAPS: %v", err)
+		} else if before != after {
+			t.Fatalf("FuncLauncher leaked privileged state: got=%v want=%v", after, before)
+		}
+
+		// Now force the other way
+		if _, err := Prctlw(prSetKeepCaps, uintptr(1-expect)); err != nil {
+			t.Fatalf("[%d] attempt to flip PR_KEEP_CAPS failed: %v", i, err)
+		}
+	}
+}
+
+func TestFill(t *testing.T) {
+	c, err := FromText("cap_setfcap=p")
+	if err != nil {
+		t.Fatalf("failed to parse: %v", err)
+	}
+	c.Fill(Effective, Permitted)
+	c.ClearFlag(Permitted)
+	c.Fill(Inheritable, Effective)
+	c.ClearFlag(Effective)
+	if got, want := c.String(), "cap_setfcap=i"; got != want {
+		t.Errorf("Fill failed: got=%q want=%q", got, want)
+	}
+}
diff --git a/cap/convenience.go b/cap/convenience.go
index 9580903..d604ad1 100644
--- a/cap/convenience.go
+++ b/cap/convenience.go
@@ -2,6 +2,7 @@
 
 import (
 	"errors"
+	"fmt"
 	"syscall"
 	"unsafe"
 )
@@ -33,6 +34,7 @@
 
 // defines from uapi/linux/prctl.h
 const (
+	prGetKeepCaps   = 7
 	prSetKeepCaps   = 8
 	prGetSecureBits = 27
 	prSetSecureBits = 28
@@ -57,9 +59,9 @@
 // will raise cap.SETPCAP in order to achieve this operation, and will
 // completely lower the Effective  vector of the process returning.
 func (s Secbits) Set() error {
-	scwMu.Lock()
-	defer scwMu.Unlock()
-	return multisc.setSecbits(s)
+	state, sc := scwStateSC()
+	defer scwSetState(launchBlocked, state, -1)
+	return sc.setSecbits(s)
 }
 
 // Mode summarizes a complicated secure-bits and capability mode in a
@@ -181,9 +183,9 @@
 // permission or because (some of) the Secbits are already locked for
 // the current process.
 func (m Mode) Set() error {
-	scwMu.Lock()
-	defer scwMu.Unlock()
-	return multisc.setMode(m)
+	state, sc := scwStateSC()
+	defer scwSetState(launchBlocked, state, -1)
+	return sc.setMode(m)
 }
 
 // String returns the libcap conventional string for this mode.
@@ -238,9 +240,9 @@
 // performs a change of UID cap.SETUID is available, and the action
 // does not alter the Permitted Flag of the process' Set.
 func SetUID(uid int) error {
-	scwMu.Lock()
-	defer scwMu.Unlock()
-	return multisc.setUID(uid)
+	state, sc := scwStateSC()
+	defer scwSetState(launchBlocked, state, -1)
+	return sc.setUID(uid)
 }
 
 //go:uintptrescapes
@@ -286,7 +288,43 @@
 // completely lower the Effective Flag of the process Set before
 // returning.
 func SetGroups(gid int, suppl ...int) error {
-	scwMu.Lock()
-	defer scwMu.Unlock()
-	return multisc.setGroups(gid, suppl)
+	state, sc := scwStateSC()
+	defer scwSetState(launchBlocked, state, -1)
+	return sc.setGroups(gid, suppl)
+}
+
+//go:uintptrescapes
+
+// Prctlw is a convenience function for performing a syscall.Prctl()
+// call that executes on all the threads of the process. It is called
+// Prctlw because it is only appropriate to call this function when it
+// is writing thread state that the caller wants to set on all OS
+// threads of the process to observe POSIX semantics when Linux
+// doesn't natively honor them. (Check prctl documentation for when it
+// is appropriate to use this vs. a normal syscall.Prctl() call.)
+func Prctlw(prVal uintptr, args ...uintptr) (int, error) {
+	if n := len(args); n > 5 {
+		return -1, fmt.Errorf("prctl supports up to 5 arguments (not %d)", n)
+	}
+	state, sc := scwStateSC()
+	defer scwSetState(launchBlocked, state, -1)
+	as := make([]uintptr, 5)
+	copy(as, args)
+	return sc.prctlwcall6(prVal, as[0], as[1], as[2], as[3], as[4])
+}
+
+//go:uintptrescapes
+
+// Prctl is a convenience function that performs a syscall.Prctl()
+// that either reads state using a single OS thread, or performs a
+// Prctl that is treated as a process wide setting. It is provided for
+// symmetry reasons, but is equivalent to simply calling the
+// corresponding syscall function.
+func Prctl(prVal uintptr, args ...uintptr) (int, error) {
+	if n := len(args); n > 5 {
+		return -1, fmt.Errorf("prctl supports up to 5 arguments (not %d)", n)
+	}
+	as := make([]uintptr, 5)
+	copy(as, args)
+	return singlesc.prctlrcall6(prVal, as[0], as[1], as[2], as[3], as[4])
 }
diff --git a/cap/file.go b/cap/file.go
index 6658f1b..70dae92 100644
--- a/cap/file.go
+++ b/cap/file.go
@@ -65,6 +65,9 @@
 // an irregular (non-executable) file.
 var ErrBadPath = errors.New("file is not a regular executable")
 
+// ErrOutOfRange indicates an erroneous value for MinExtFlagSize.
+var ErrOutOfRange = errors.New("flag length invalid for export")
+
 // digestFileCap unpacks a file capability and returns it in a *Set
 // form.
 func digestFileCap(d []byte, sz int, err error) (*Set, error) {
@@ -264,7 +267,7 @@
 
 //go:uintptrescapes
 
-// SetFile attempts to set the file capabilities of the specfied
+// SetFile attempts to set the file capabilities of the specified
 // filename. This function can also be used to delete a file's
 // capabilities, by calling with c = nil.
 //
@@ -311,7 +314,8 @@
 const ExtMagic = uint32(0x5101c290)
 
 // Import imports a Set from a byte array where it has been stored in
-// a portable (lossless) way.
+// a portable (lossless) way. That is values exported by
+// libcap.cap_copy_ext() and Export().
 func Import(d []byte) (*Set, error) {
 	b := bytes.NewBuffer(d)
 	var m uint32
@@ -344,27 +348,45 @@
 	return c, nil
 }
 
+// To strictly match libcap, this value defaults to 8. Setting it to
+// zero can generate smaller external representations. Such smaller
+// representations can be imported by libcap and the Go package just
+// fine, we just default to the default libcap representation for
+// legacy reasons.
+var MinExtFlagSize = uint(8)
+
 // Export exports a Set into a lossless byte array format where it is
 // stored in a portable way. Note, any namespace owner in the Set
 // content is not exported by this function.
+//
+// Note, Export() generates exported byte streams that are importable
+// by libcap.cap_copy_int() as well as Import().
 func (c *Set) Export() ([]byte, error) {
 	if c == nil {
 		return nil, ErrBadSet
 	}
+	if MinExtFlagSize > 255 {
+		return nil, ErrOutOfRange
+	}
 	b := new(bytes.Buffer)
 	binary.Write(b, binary.LittleEndian, ExtMagic)
 	c.mu.Lock()
 	defer c.mu.Unlock()
-	var n = byte(0)
+	var n = uint(0)
 	for i, f := range c.flat {
-		if u := f[Effective] | f[Permitted] | f[Inheritable]; u != 0 {
-			n = 4 * byte(i)
-			for ; u != 0; u >>= 8 {
-				n++
+		if nn := 4 * uint(i); nn+4 > n {
+			if u := f[Effective] | f[Permitted] | f[Inheritable]; u != 0 {
+				n = nn
+				for ; u != 0; u >>= 8 {
+					n++
+				}
 			}
 		}
 	}
-	b.Write([]byte{n})
+	if n < MinExtFlagSize {
+		n = MinExtFlagSize
+	}
+	b.Write([]byte{byte(n)})
 	for _, f := range c.flat {
 		if n == 0 {
 			break
@@ -382,5 +404,9 @@
 			inh >>= 8
 		}
 	}
+	for n > 0 {
+		n--
+		b.Write([]byte{0, 0, 0})
+	}
 	return b.Bytes(), nil
 }
diff --git a/cap/flags.go b/cap/flags.go
index b800a2d..83a871a 100644
--- a/cap/flags.go
+++ b/cap/flags.go
@@ -75,6 +75,24 @@
 	return nil
 }
 
+// Fill copies the from flag values into the to flag. With this
+// function, you can raise all of the permitted values in the
+// effective flag with c.Fill(cap.Effective, cap.Permitted).
+func (c *Set) Fill(to, from Flag) error {
+	if c == nil || len(c.flat) == 0 {
+		return ErrBadSet
+	}
+	if to > Inheritable || from > Inheritable {
+		return ErrBadValue
+	}
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	for i := range c.flat {
+		c.flat[i][to] = c.flat[i][from]
+	}
+	return nil
+}
+
 // ErrBadValue indicates a bad capability value was specified.
 var ErrBadValue = errors.New("bad capability value")
 
diff --git a/cap/go.mod b/cap/go.mod
index 45e38fa..a299f4c 100644
--- a/cap/go.mod
+++ b/cap/go.mod
@@ -2,4 +2,4 @@
 
 go 1.11
 
-require kernel.org/pub/linux/libs/security/libcap/psx v0.2.48
+require kernel.org/pub/linux/libs/security/libcap/psx v1.2.53
diff --git a/cap/iab.go b/cap/iab.go
index 877ed12..77f2dbc 100644
--- a/cap/iab.go
+++ b/cap/iab.go
@@ -56,7 +56,7 @@
 	}
 }
 
-// IABGetProc summarizes the Inh, Amb and Bound capabilty vectors of
+// IABGetProc summarizes the Inh, Amb and Bound capability vectors of
 // the current process.
 func IABGetProc() *IAB {
 	iab := IABInit()
@@ -188,14 +188,14 @@
 }
 
 // SetProc attempts to change the Inheritable, Ambient and Bounding
-// capabilty vectors of the current process using the content,
+// capability vectors of the current process using the content,
 // iab. The Bounding vector strongly affects the potential for setting
 // other bits, so this function carefully performs the the combined
 // operation in the most flexible manner.
 func (iab *IAB) SetProc() error {
-	scwMu.Lock()
-	defer scwMu.Unlock()
-	return multisc.iabSetProc(iab)
+	state, sc := scwStateSC()
+	defer scwSetState(launchBlocked, state, -1)
+	return sc.iabSetProc(iab)
 }
 
 // GetVector returns the raised state of the specific capability bit
diff --git a/cap/launch.go b/cap/launch.go
index 4ae449c..6145f3e 100644
--- a/cap/launch.go
+++ b/cap/launch.go
@@ -8,19 +8,22 @@
 	"unsafe"
 )
 
-// Launcher holds a configuration for launching a child process with
-// capability state different from (generally more restricted than)
-// the parent.
+// Launcher holds a configuration for executing an optional callback
+// function and/or launching a child process with capability state
+// different from the parent.
 //
 // Note, go1.10 is the earliest version of the Go toolchain that can
 // support this abstraction.
 type Launcher struct {
+	// Note, path and args must be set, or callbackFn. They cannot
+	// both be empty. In such cases .Launch() will error out.
 	path string
 	args []string
 	env  []string
 
 	callbackFn func(pa *syscall.ProcAttr, data interface{}) error
 
+	// The following are only honored when path is non empty.
 	changeUIDs bool
 	uid        int
 
@@ -46,14 +49,64 @@
 	}
 }
 
-// Callback specifies a callback for Launch() to call before changing
-// privilege. The only thing that is assumed is that the OS thread in
-// use to call this callback function at launch time will be the one
-// that ultimately calls fork. Any returned error value of said
-// function will terminate the launch process. A nil callback (the
-// default) is ignored. The specified callback fn should not call any
-// "cap" package functions since this may deadlock or generate
-// undefined behavior for the parent process.
+// FuncLauncher returns a new launcher whose purpose is to only
+// execute fn in a disposable security context. This is a more bare
+// bones variant of the more elaborate program launcher returned by
+// cap.NewLauncher().
+//
+// Note, this launcher will fully ignore any overrides provided by the
+// (*Launcher).SetUID() etc. methods. Should your fn() code want to
+// run with a different capability state or other privilege, it should
+// use the cap.*() functions to set them directly. The cap package
+// will ensure that their effects are limited to the runtime of this
+// individual function invocation. Warning: executing non-cap.*()
+// syscall functions may corrupt the state of the program runtime and
+// lead to unpredictable results.
+//
+// The properties of fn are similar to those supplied via
+// (*Launcher).Callback(fn) method. However, this launcher is bare
+// bones because, when launching, all privilege management performed
+// by the fn() is fully discarded when the fn() completes
+// execution. That is, it does not end by exec()ing some program.
+func FuncLauncher(fn func(interface{}) error) *Launcher {
+	return &Launcher{
+		callbackFn: func(ignored *syscall.ProcAttr, data interface{}) error {
+			return fn(data)
+		},
+	}
+}
+
+// Callback changes the callback function for Launch() to call before
+// changing privilege. The only thing that is assumed is that the OS
+// thread in use to call this callback function at launch time will be
+// the one that ultimately calls fork to complete the launch of a path
+// specified executable. Any returned error value of said function
+// will terminate the launch process.
+//
+// A nil fn causes there to be no callback function invoked during a
+// Launch() sequence - it will remove any pre-existing callback.
+//
+// If the non-nil fn requires any effective capabilities in order to
+// run, they can be raised prior to calling .Launch() or inside the
+// callback function itself.
+//
+// If the specified callback fn should call any "cap" package
+// functions that change privilege state, these calls will only affect
+// the launch goroutine itself. While the launch is in progress, other
+// (non-launch) goroutines will block if they attempt to change
+// privilege state. These routines will unblock once there are no
+// in-flight launches.
+//
+// Note, the first argument provided to the callback function is the
+// *syscall.ProcAttr value to be used when a process launch is taking
+// place. A non-nil structure pointer can be modified by the callback
+// to enhance the launch. For example, the .Files field can be
+// overridden to affect how the launched process' stdin/out/err are
+// handled.
+//
+// Further, the 2nd argument to the callback function is provided at
+// Launch() invocation and can communicate contextual info to and from
+// the callback and the main process.
 func (attr *Launcher) Callback(fn func(*syscall.ProcAttr, interface{}) error) {
 	attr.callbackFn = fn
 }
@@ -93,7 +146,17 @@
 
 // lResult is used to get the result from the doomed launcher thread.
 type lResult struct {
+	// tid holds the tid of the locked launching thread which dies
+	// as the launch completes.
+	tid int
+
+	// pid is the pid of the launched program (path, args). In
+	// the case of a FuncLaunch() this value is zero on success.
+	// pid holds -1 in the case of error.
 	pid int
+
+	// err is nil on success, but otherwise holds the reason the
+	// launch failed.
 	err error
 }
 
@@ -135,13 +198,21 @@
 	}
 
 	pid := syscall.Getpid()
-	// Wait until we are not scheduled on the parent thread.  We
-	// will exit this thread once the child has launched, and
-	// don't want other goroutines to use this thread afterwards.
+	// This code waits until we are not scheduled on the parent
+	// thread.  We will exit this thread once the child has
+	// launched.
 	runtime.LockOSThread()
 	tid := syscall.Gettid()
 	if tid == pid {
-		// Force the go runtime to find a new thread to run on.
+		// Force the go runtime to find a new thread to run
+		// on.  (It is really awkward to have a process'
+		// PID=TID thread in effectively a zombie state. The
+		// Go runtime has support for it, but pstree gives
+		// ugly output since the prSetName value sticks around
+		// after launch completion...
+		//
+		// (Optimize for time to debug by reducing ugly spam
+		// like this.)
 		quit := make(chan struct{})
 		go launch(result, attr, data, quit)
 
@@ -154,6 +225,7 @@
 	// By never releasing the LockOSThread here, we guarantee that
 	// the runtime will terminate the current OS thread once this
 	// function returns.
+	scwSetState(launchIdle, launchActive, tid)
 
 	// Name the launcher thread - transient, but helps to debug if
 	// the callbackFn or something else hangs up.
@@ -163,22 +235,33 @@
 	// completing.
 	defer close(result)
 
-	pa := &syscall.ProcAttr{
-		Files: []uintptr{0, 1, 2},
-	}
+	var pa *syscall.ProcAttr
 	var err error
 	var needChroot bool
 
-	if len(attr.env) != 0 {
-		pa.Env = attr.env
-	} else {
-		pa.Env = os.Environ()
+	// Only prepare a non-nil pa value if a path is provided.
+	if attr.path != "" {
+		// By default the following file descriptors are preserved for
+		// the child. The user should modify them in the callback for
+		// stdin/out/err redirection.
+		pa = &syscall.ProcAttr{
+			Files: []uintptr{0, 1, 2},
+		}
+		if len(attr.env) != 0 {
+			pa.Env = attr.env
+		} else {
+			pa.Env = os.Environ()
+		}
 	}
 
 	if attr.callbackFn != nil {
 		if err = attr.callbackFn(pa, data); err != nil {
 			goto abort
 		}
+		if attr.path == "" {
+			pid = 0
+			goto abort
+		}
 	}
 
 	if needChroot, err = validatePA(pa, attr.chroot); err != nil {
@@ -220,23 +303,51 @@
 	if err != nil {
 		pid = -1
 	}
-	result <- lResult{pid: pid, err: err}
+	result <- lResult{
+		tid: tid,
+		pid: pid,
+		err: err,
+	}
 }
 
-// Launch performs a new program launch with security state specified
-// in the supplied attr settings.
+// Launch performs a callback function and/or new program launch with
+// a disposable security state. The data object, when not nil, can be
+// used to communicate with the callback. It can also be used to
+// return details from the callback functions execution.
+//
+// If the attr was created with NewLauncher(), this present function
+// will return the pid of the launched process, or -1 and a non-nil
+// error.
+//
+// If the attr was created with FuncLauncher(), this present function
+// will return 0, nil if the callback function exits without
+// error. Otherwise it will return -1 and the non-nil error of the
+// callback return value.
+//
+// Note, while the disposable security state thread makes some
+// oprerations seem more isolated - they are *not securely
+// isolated*. Launching is inherently violating the POSIX semantics
+// maintained by the rest of the "libcap/cap" package, so think of
+// launching as a convenience wrapper around fork()ing.
+//
+// Advanced user note: if the caller of this function thinks they know
+// what they are doing by using runtime.LockOSThread() before invoking
+// this function, they should understand that the OS Thread invoking
+// (*Launcher).Launch() is *not guaranteed* to be the one used for the
+// disposable security state to perform the launch. If said caller
+// needs to run something on the disposable security state thread,
+// they should do it via the launch callback function mechanism. (The
+// Go runtime is complicated and this is why this Launch mechanism
+// provides the optional callback function.)
 func (attr *Launcher) Launch(data interface{}) (int, error) {
-	if attr.path == "" || len(attr.args) == 0 {
-		return -1, ErrLaunchFailed
-	}
 	if !LaunchSupported {
 		return -1, ErrNoLaunch
 	}
+	if attr.callbackFn == nil && (attr.path == "" || len(attr.args) == 0) {
+		return -1, ErrLaunchFailed
+	}
 
-	scwMu.Lock()
-	defer scwMu.Unlock()
 	result := make(chan lResult)
-
 	go launch(result, attr, data, nil)
 	for {
 		select {
@@ -244,6 +355,9 @@
 			if !ok {
 				return -1, ErrLaunchFailed
 			}
+			if v.tid != -1 {
+				defer scwSetState(launchActive, launchIdle, v.tid)
+			}
 			return v.pid, v.err
 		default:
 			runtime.Gosched()
diff --git a/cap/names.go b/cap/names.go
index 9e02cd1..8ee96d1 100644
--- a/cap/names.go
+++ b/cap/names.go
@@ -50,7 +50,7 @@
 	// file.
 	FSETID
 
-	// KILL allows a process to sent a kill(2) signal to any other
+	// KILL allows a process to send a kill(2) signal to any other
 	// process - overriding the limitation that there be a
 	// [E]UID match between source and target process.
 	KILL
@@ -63,7 +63,7 @@
 	SETGID
 
 	// SETUID allows a process to freely manipulate its own UIDs:
-	//   - arbitraily set the UID, EUID, REUID and RESUID
+	//   - arbitrarily set the UID, EUID, REUID and RESUID
 	//     values
 	//   - allows the forging of UID credentials passed over a
 	//     socket
@@ -85,7 +85,7 @@
 	// default, as its unsuppressed behavior was not
 	// auditable: it could asynchronously grant its own
 	// Permitted capabilities to and remove capabilities from
-	// other processes arbitraily. The former leads to
+	// other processes arbitrarily. The former leads to
 	// undefined behavior, and the latter is better served by
 	// the kill system call.]
 	SETPCAP
@@ -230,8 +230,6 @@
 	//   - override the maximum number of consoles for console
 	//     allocation
 	//   - override the maximum number of keymaps
-	//
-	//
 	SYS_RESOURCE
 
 	// SYS_TIME allows a process to perform time manipulation of clocks:
@@ -261,6 +259,11 @@
 	AUDIT_CONTROL
 
 	// SETFCAP allows a process to set capabilities on files.
+	// Permits a process to uid_map the uid=0 of the
+	// parent user namespace into that of the child
+	// namespace. Also, permits a process to override
+	// securebits locks through user namespace
+	// creation.
 	SETFCAP
 
 	// MAC_OVERRIDE allows a process to override Manditory Access Control
diff --git a/cap/syscalls.go b/cap/syscalls.go
index ab4bcef..37121e0 100644
--- a/cap/syscalls.go
+++ b/cap/syscalls.go
@@ -1,6 +1,8 @@
 package cap
 
 import (
+	"runtime"
+	"sync"
 	"syscall"
 
 	"kernel.org/pub/linux/libs/security/libcap/psx"
@@ -25,3 +27,95 @@
 	r3: syscall.RawSyscall,
 	r6: syscall.RawSyscall6,
 }
+
+// launchState is used to track which variant of the write syscalls
+// should execute.
+type launchState int
+
+// these states are used to understand when a launch is in progress.
+const (
+	launchIdle launchState = iota
+	launchActive
+	launchBlocked
+)
+
+// scwMu is used to fully serialize the write system calls. Note, this
+// would generally not be necessary, but in the case of Launch we get
+// into a situation where the launching thread is temporarily allowed
+// to deviate from the kernel state of the rest of the runtime and
+// allowing other threads to perform w* syscalls will potentially
+// interfere with the launching process. In pure Go binaries, this
+// will lead inevitably to a panic when the AllThreadsSyscall
+// discovers inconsistent thread state.
+//
+// scwMu protects scwTIDs and scwState
+var scwMu sync.Mutex
+
+// scwTIDs holds the thread IDs of the threads that are executing a
+// launch it is empty when no launches are occurring.
+var scwTIDs = make(map[int]bool)
+
+// scwState captures whether a launch is in progress or not.
+var scwState = launchIdle
+
+// scwCond is used to announce when scwState changes to other
+// goroutines waiting for it to change.
+var scwCond = sync.NewCond(&scwMu)
+
+// scwSetState blocks until a launch state change between states from
+// and to occurs. We use this for more context specific syscaller
+// use. In the case that the caller is requesting a launchActive ->
+// launchIdle transition they are declaring that tid is no longer
+// launching. If another thread is also launching the call will
+// complete, but the launchState will remain launchActive.
+func scwSetState(from, to launchState, tid int) {
+	scwMu.Lock()
+	for scwState != from {
+		if scwState == launchActive && from == launchIdle && to == launchActive {
+			break // This "transition" is also allowed.
+		}
+		scwCond.Wait()
+	}
+	if from == launchIdle && to == launchActive {
+		scwTIDs[tid] = true
+	} else if from == launchActive && to == launchIdle {
+		delete(scwTIDs, tid)
+		if len(scwTIDs) != 0 {
+			to = from // not actually idle
+		}
+	}
+	scwState = to
+	scwCond.Broadcast()
+	scwMu.Unlock()
+}
+
+// scwStateSC blocks until the current syscaller is available for
+// writes, and then marks launchBlocked. Use scwSetState to perform
+// the reverse transition (blocked->returned state value).
+func scwStateSC() (launchState, *syscaller) {
+	sc := multisc
+	scwMu.Lock()
+	for {
+		if scwState == launchIdle {
+			break
+		}
+		runtime.LockOSThread()
+		if scwState == launchActive && scwTIDs[syscall.Gettid()] {
+			sc = singlesc
+			// note, we don't runtime.UnlockOSThread()
+			// here because we have no reason to ever
+			// allow this thread to return to normal use -
+			// we need it dead before we can return to the
+			// launchIdle state.
+			break
+		}
+		runtime.UnlockOSThread()
+		scwCond.Wait()
+	}
+	old := scwState
+	scwState = launchBlocked
+	scwCond.Broadcast()
+	scwMu.Unlock()
+
+	return old, sc
+}
diff --git a/contrib/pcaps4convenience b/contrib/pcaps4convenience
index c46735d..b78a25b 100644
--- a/contrib/pcaps4convenience
+++ b/contrib/pcaps4convenience
@@ -63,22 +63,22 @@
     # are we sane?
     WICH=`which which 2>/dev/null`
     if [ $WICH == "" ]; then
-        # thats bad
+        # that's bad
         echo "Sorry, I haven't found which"
         exit
     fi
 
-    # we needt his apps
+    # we need this app
     SETCAP=`which setcap 2>/dev/null`
     if [ "$SETCAP" == "" ]; then
-        echo "Sorry, I'm missing setcap !"
+        echo "Sorry, I'm missing setcap!"
         exit
     fi
 
-    # checking setcap for SET_SETFCAP PCap ?
+    # checking setcap for SET_SETFCAP PCap?
     # for now we stick to root
     if [ "$( id -u )" != "0" ]; then
-        echo "Sorry, you must be root !"
+        echo "Sorry, you must be root!"
         exit 1
     fi
 }
@@ -113,7 +113,7 @@
 
 
 p4c_app_revert(){
-    # revert a singel app
+    # revert a single app
     # $1 is app name
     APP=`which -a $1 2>/dev/null`
     if [ "$APP" != "" ]; then
@@ -136,7 +136,7 @@
 
 
 p4c_convert(){
-    # we go throug the APPSARRAY and call s2p_app_convert to do the job
+    # we go through the APPSARRAY and call s2p_app_convert to do the job
     COUNTER=0
     let UPPER=${#APPSARRAY[*]}-1
     until [ $COUNTER == $UPPER ]; do
@@ -170,9 +170,9 @@
     echo "through the PAM module pam_cap.so."
     echo "A user who has not the needed PCaps in his Inheritance Set CAN NOT execute"
     echo "these binaries successful."
-    echo "(well, still per sudo or su -c - but thats not the point here)"
+    echo "(well, still per sudo or su -c - but that's not the point here)"
     echo
-    echo "You need and I will check fot the utilities which and setcap."
+    echo "You need and I will check for the utilities which and setcap."
     echo
     echo "Your Filesystem has to support extended attributes and your kernel must have"
     echo "support for POSIX File Capabilities (CONFIG_SECURITY_FILE_CAPABILITIES)."
diff --git a/contrib/pcaps4server b/contrib/pcaps4server
index af6f9ca..f72a4d3 100644
--- a/contrib/pcaps4server
+++ b/contrib/pcaps4server
@@ -8,7 +8,7 @@
 # changelog:
 # 1 - initial release pcaps4convenience
 # 1 - 2007.02.15 - initial release
-# 2 - 2007.11.02 - changed to new setfcaps api; each app is now callable; supressed error of id
+# 2 - 2007.11.02 - changed to new setfcaps api; each app is now callable; suppressed error of id
 # 3 - 2007.12.28 - changed to libcap2 package setcap/getcap
 # 4 - renamed to pcaps4server
 #      removed suid0 and convenience files,
diff --git a/contrib/pcaps4suid0 b/contrib/pcaps4suid0
index 799df28..2cbdcee 100644
--- a/contrib/pcaps4suid0
+++ b/contrib/pcaps4suid0
@@ -77,23 +77,23 @@
     # are we sane?
     WICH=`which which 2>/dev/null`
     if [ $WICH == "" ]; then
-        # thats bad
+        # that's bad
         echo "Sorry, I haven't found which"
         exit
     fi
 
-    # we needt his apps
+    # we need these apps
     CHMOD=`which chmod 2>/dev/null`
     SETCAP=`which setcap 2>/dev/null`
     if [ "$CHMOD" == "" -o "$SETCAP" == "" ]; then
-        echo "Sorry, I'm missing chmod or setcap !"
+        echo "Sorry, I'm missing chmod or setcap!"
         exit
     fi
 
-    # checking setcap for SET_SETFCAP PCap ?
+    # checking setcap for SET_SETFCAP PCap?
     # for now we stick to root
     if [ "$( id -u )" != "0" ]; then
-        echo "Sorry, you must be root !"
+        echo "Sorry, you must be root!"
         exit 1
     fi
 }
@@ -129,7 +129,7 @@
 
 
 p4s_app_revert(){
-    # revert a singel app
+    # revert a single app
     # $1 is app name
     APP=`which -a $1 2>/dev/null`
     if [ "$APP" != "" ]; then
@@ -153,7 +153,7 @@
 
 
 p4s_convert(){
-    # we go throug the APPSARRAY and call s2p_app_convert to do the job
+    # we go through the APPSARRAY and call s2p_app_convert to do the job
     COUNTER=0
     let UPPER=${#APPSARRAY[*]}-1
     until [ $COUNTER == $UPPER ]; do
@@ -190,7 +190,7 @@
     echo "If you are using pam_cap.so, you might want to change the set into the"
     echo "Inherited and Effective set (check for the SET var)."
     echo
-    echo "You need and I will check fot the utilities which, chmod and setcap."
+    echo "You need and I will check for the utilities which, chmod and setcap."
     echo
     echo "Your Filesystem has to support extended attributes and your kernel must have"
     echo "support for POSIX File Capabilities (CONFIG_SECURITY_FILE_CAPABILITIES)."
diff --git a/contrib/seccomp/explore.go b/contrib/seccomp/explore.go
index 37fe97b..8203d4f 100644
--- a/contrib/seccomp/explore.go
+++ b/contrib/seccomp/explore.go
@@ -114,46 +114,46 @@
 	}
 }
 
-func ExamineSyscall() []SockFilter {
+func examineSyscall() []SockFilter {
 	return []SockFilter{
 		bpfStmt(bpfLd+bpfW+bpfAbs, syscallNr),
 	}
 }
 
-func AllowSyscall(syscallNum uint32) []SockFilter {
+func allowSyscall(syscallNum uint32) []SockFilter {
 	return []SockFilter{
 		bpfJump(bpfJmp+bpfJeq+bpfK, syscallNum, 0, 1),
 		bpfStmt(bpfRet+bpfK, seccompRetAllow),
 	}
 }
 
-func DisallowSyscall(syscallNum, errno uint32) []SockFilter {
+func disallowSyscall(syscallNum, errno uint32) []SockFilter {
 	return []SockFilter{
 		bpfJump(bpfJmp+bpfJeq+bpfK, syscallNum, 0, 1),
 		bpfStmt(bpfRet+bpfK, seccompRetErrno|(errno&seccompRetData)),
 	}
 }
 
-func KillProcess() []SockFilter {
+func killProcess() []SockFilter {
 	return []SockFilter{
 		bpfStmt(bpfRet+bpfK, seccompRetKillProcess),
 	}
 }
 
-func NotifyProcessAndDie() []SockFilter {
+func notifyProcessAndDie() []SockFilter {
 	return []SockFilter{
 		bpfStmt(bpfRet+bpfK, seccompRetTrap),
 	}
 }
 
-func TrapOnSyscall(syscallNum uint32) []SockFilter {
+func trapOnSyscall(syscallNum uint32) []SockFilter {
 	return []SockFilter{
 		bpfJump(bpfJmp+bpfJeq+bpfK, syscallNum, 0, 1),
 		bpfStmt(bpfRet+bpfK, seccompRetTrap),
 	}
 }
 
-func AllGood() []SockFilter {
+func allGood() []SockFilter {
 	return []SockFilter{
 		bpfStmt(bpfRet+bpfK, seccompRetAllow),
 	}
@@ -244,20 +244,20 @@
 	filter = append(filter, validateArchitecture()...)
 
 	// Grab the system call number.
-	filter = append(filter, ExamineSyscall()...)
+	filter = append(filter, examineSyscall()...)
 
 	// List disallowed syscalls.
 	for _, x := range []uint32{
 		syscall.SYS_SETUID,
 	} {
 		if *kill {
-			filter = append(filter, TrapOnSyscall(x)...)
+			filter = append(filter, trapOnSyscall(x)...)
 		} else {
-			filter = append(filter, DisallowSyscall(x, uint32(*errno))...)
+			filter = append(filter, disallowSyscall(x, uint32(*errno))...)
 		}
 	}
 
-	filter = append(filter, AllGood()...)
+	filter = append(filter, allGood()...)
 
 	prog := &SockFProg{
 		Len:    uint16(len(filter)),
diff --git a/contrib/seccomp/go.mod b/contrib/seccomp/go.mod
index 86e40c6..8f72409 100644
--- a/contrib/seccomp/go.mod
+++ b/contrib/seccomp/go.mod
@@ -2,4 +2,4 @@
 
 go 1.14
 
-require kernel.org/pub/linux/libs/security/libcap/psx v0.2.48
+require kernel.org/pub/linux/libs/security/libcap/psx v1.2.53
diff --git a/contrib/sucap/Makefile b/contrib/sucap/Makefile
new file mode 100644
index 0000000..91947af
--- /dev/null
+++ b/contrib/sucap/Makefile
@@ -0,0 +1,9 @@
+all: su
+
+su: su.c
+	$(CC) -DPAM_APP_NAME=\"sucap\" -o $@ $< -lpam -lpam_misc -lcap
+	# to permit all ambient capabilities, this needs all permitted.
+	sudo setcap =p ./su
+
+clean:
+	rm -f su su.o *~
diff --git a/contrib/sucap/README.md b/contrib/sucap/README.md
new file mode 100644
index 0000000..586f017
--- /dev/null
+++ b/contrib/sucap/README.md
@@ -0,0 +1,40 @@
+This directory contains a port of the SimplePAMApp su to more
+aggressively use libcap.
+
+The Makefile builds a binary called `su` that registers with PAM as
+the application `sucap`. We've provided a sample `/etc/pam.d/sucap`
+file in this directory named `sucap.pamconfig`.
+
+The point of developing this is to better test the full libcap
+implementation, and to also provide a non-setuid-root worked example
+for testing PAM interaction with libcap and pam_cap.so. The
+expectations for `pam_unix.so` are that it includes this commit:
+
+
+The original sources were found here:
+
+https://kernel.org/pub/linux/libs/pam/pre/applications/SimplePAMApps-0.60.tar.gz
+
+The SimplePAMApps contain the same License as libcap (they were
+originally started by the same authors!). The credited Authors in the
+above tarball were:
+
+-  Andrew [G.] Morgan
+-  Andrey V. Savochkin
+-  Alexei V. Galatenko
+
+The code in this present directory is freely adapted from the above
+tar ball and is thus a derived work from that.
+
+**NOTE** As of the time of writing, this adaptation is likely rife
+  with bugs.
+
+Finally, Andrew would like to apologize to Andrey for removing all of
+the config support he worked to add all those decades ago..! I just
+wanted to make a quick tester for a potential workaround for this
+pam_cap issue:
+
+-  https://bugzilla.kernel.org/show_bug.cgi?id=212945
+
+Andrew G. Morgan <morgan@kernel.org>
+2021-06-30
diff --git a/contrib/sucap/su.c b/contrib/sucap/su.c
new file mode 100644
index 0000000..5c98e5f
--- /dev/null
+++ b/contrib/sucap/su.c
@@ -0,0 +1,1606 @@
+/*
+ * Originally based on an implementation of `su' by
+ *
+ *     Peter Orbaek  <poe@daimi.aau.dk>
+ *
+ * obtained circa 1997 from ftp://ftp.daimi.aau.dk/pub/linux/poe/
+ *
+ * Rewritten for Linux-PAM by Andrew G. Morgan <morgan@linux.kernel.org>
+ * Modified by Andrey V. Savochkin <saw@msu.ru>
+ * Modified for use with libcap by Andrew G. Morgan <morgan@kernel.org>
+ */
+
+/* #define PAM_DEBUG */
+
+#include <sys/prctl.h>
+
+/* non-root user of convenience to block signals */
+#define TEMP_UID                  1
+
+#ifndef PAM_APP_NAME
+#define PAM_APP_NAME              "su"
+#endif /* ndef PAM_APP_NAME */
+
+#define DEFAULT_HOME              "/"
+#define DEFAULT_SHELL             "/bin/bash"
+#define SLEEP_TO_KILL_CHILDREN    3  /* seconds to wait after SIGTERM before
+					SIGKILL */
+#define SU_FAIL_DELAY     2000000    /* usec on authentication failure */
+
+#define RHOST_UNKNOWN_NAME        ""     /* perhaps "[from.where?]" */
+#define DEVICE_FILE_PREFIX        "/dev/"
+#define WTMP_LOCK_TIMEOUT         3      /* in seconds */
+
+#ifndef UT_IDSIZE
+#define UT_IDSIZE 4            /* XXX - this is sizeof(struct utmp.ut_id) */
+#endif
+
+#include <stdlib.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+#include <string.h>
+#include <syslog.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <sys/wait.h>
+#include <utmp.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <netdb.h>
+#include <unistd.h>
+
+#include <security/pam_appl.h>
+#include <security/pam_misc.h>
+#include <sys/capability.h>
+
+#include <security/_pam_macros.h>
+
+/* -------------------------------------------- */
+/* ------ declarations ------------------------ */
+/* -------------------------------------------- */
+
+extern char **environ;
+static pam_handle_t *pamh = NULL;
+static int state;
+
+static int wait_for_child_caught=0;
+static int need_job_control=0;
+static int is_terminal = 0;
+static struct termios stored_mode;        /* initial terminal mode settings */
+static uid_t terminal_uid = (uid_t) -1;
+static uid_t invoked_uid = (uid_t) -1;
+
+/* -------------------------------------------- */
+/* ------ some local (static) functions ------- */
+/* -------------------------------------------- */
+
+/*
+ * We will attempt to transcribe the following env variables
+ * independent of whether we keep the whole environment. Others will
+ * be set elsewhere: either in modules; or after the identity of the
+ * user is known.
+ */
+
+static const char *posix_env[] = {
+    "LANG",
+    "LC_COLLATE",
+    "LC_CTYPE",
+    "LC_MONETARY",
+    "LC_NUMERIC",
+    "TZ",
+    NULL
+};
+
+/*
+ * make_environment transcribes a selection of environment variables
+ * from the invoking user.
+ */
+static int make_environment(pam_handle_t *pamh, int keep_env)
+{
+    const char *tmpe;
+    int i;
+    int retval;
+
+    if (keep_env) {
+	/* preserve the original environment */
+	return pam_misc_paste_env(pamh, (const char * const *)environ);
+    }
+
+    /* we always transcribe some variables anyway */
+    tmpe = getenv("TERM");
+    if (tmpe == NULL) {
+	tmpe = "dumb";
+    }
+    retval = pam_misc_setenv(pamh, "TERM", tmpe, 0);
+    if (retval == PAM_SUCCESS) {
+	retval = pam_misc_setenv(pamh, "PATH", "/bin:/usr/bin", 0);
+    }
+    if (retval != PAM_SUCCESS) {
+	tmpe = NULL;
+	D(("error setting environment variables"));
+	return retval;
+    }
+
+    /* also propagate the POSIX specific ones */
+    for (i=0; retval == PAM_SUCCESS && posix_env[i]; ++i) {
+	tmpe = getenv(posix_env[i]);
+	if (tmpe != NULL) {
+	    retval = pam_misc_setenv(pamh, posix_env[i], tmpe, 0);
+	}
+    }
+    tmpe = NULL;
+
+    return retval;
+}
+
+/*
+ * checkfds ensures that stdout and stderr filedescriptors are
+ * defined. If all else fails, it directs them to /dev/null.
+ */
+static void checkfds(void)
+{
+    struct stat st;
+    int fd;
+
+    if (fstat(1, &st) == -1) {
+        fd = open("/dev/null", O_WRONLY);
+        if (fd == -1) exit(1);
+        if (fd != 1) {
+            if (dup2(fd, 1) == -1) exit(1);
+            if (close(fd) == -1) exit(1);
+        }
+    }
+    if (fstat(2, &st) == -1) {
+        fd = open("/dev/null", O_WRONLY);
+        if (fd == -1) exit(1);
+        if (fd != 2) {
+            if (dup2(fd, 2) == -1) exit(1);
+            if (close(fd) == -1) exit(1);
+        }
+    }
+}
+
+/*
+ * store_terminal_modes captures the current state of the input
+ * terminal. Calling this at the start of the program, we ensure we
+ * can restore these default settings when su exits.
+ */
+static void store_terminal_modes(void)
+{
+    if (isatty(STDIN_FILENO)) {
+	is_terminal = 1;
+	if (tcgetattr(STDIN_FILENO, &stored_mode) != 0) {
+	    fprintf(stderr, PAM_APP_NAME ": couldn't copy terminal mode");
+	    exit(1);
+	}
+	return;
+    }
+    fprintf(stderr, PAM_APP_NAME ": must be run from a terminal\n");
+    exit(1);
+}
+
+/*
+ * restore_terminal_modes resets the terminal to the state it was in
+ * when the program started.
+ *
+ * Returns:
+ *   0     ok
+ *   1     error
+ */
+static int restore_terminal_modes(void)
+{
+    if (is_terminal && tcsetattr(STDIN_FILENO, TCSAFLUSH, &stored_mode) != 0) {
+	fprintf(stderr, PAM_APP_NAME ": cannot restore terminal mode: %s\n",
+		strerror(errno));
+	return 1;
+    } else {
+	return 0;
+    }
+}
+
+/* ------ unexpected signals ------------------ */
+
+struct sigaction old_int_act, old_quit_act, old_tstp_act, old_pipe_act;
+
+/*
+ * disable_terminal_signals attempts to make the process resistant to
+ * being stopped - it helps ensure that the PAM stack can complete
+ * session and auth failure logging etc.
+ */
+static void disable_terminal_signals(void)
+{
+    /*
+     * Protect the process from dangerous terminal signals.
+     * The protection is implemented via sigaction() because
+     * the signals are sent regardless of the process' uid.
+     */
+    struct sigaction act;
+
+    act.sa_handler = SIG_IGN;  /* ignore the signal */
+    sigemptyset(&act.sa_mask); /* no signal blocking on handler
+				  call needed */
+    act.sa_flags = SA_RESTART; /* do not reset after first signal
+				  arriving, restart interrupted
+				  system calls if possible */
+    sigaction(SIGINT, &act, &old_int_act);
+    sigaction(SIGQUIT, &act, &old_quit_act);
+    /*
+     * Ignore SIGTSTP signals. Why? attacker could otherwise stop
+     * a process and a. kill it, or b. wait for the system to
+     * shutdown - either way, nothing appears in syslogs.
+     */
+    sigaction(SIGTSTP, &act, &old_tstp_act);
+    /*
+     * Ignore SIGPIPE. The parent `su' process may print something
+     * on stderr. Killing of the process would be undesired.
+     */
+    sigaction(SIGPIPE, &act, &old_pipe_act);
+}
+
+static void enable_terminal_signals(void)
+{
+    sigaction(SIGINT, &old_int_act, NULL);
+    sigaction(SIGQUIT, &old_quit_act, NULL);
+    sigaction(SIGTSTP, &old_tstp_act, NULL);
+    sigaction(SIGPIPE, &old_pipe_act, NULL);
+}
+
+/* ------ terminal ownership ------------------ */
+
+/*
+ * change_terminal_owner changes the ownership of STDIN if needed.
+ * Returns:
+ *   0     ok,
+ *  -1     fatal error (continuing is impossible),
+ *   1     non-fatal error.
+ * In the case of an error "err_descr" is set to the error message
+ * and "callname" to the name of the failed call.
+ */
+static int change_terminal_owner(uid_t uid, int is_login,
+				 const char **callname, const char **err_descr)
+{
+    /* determine who owns the terminal line */
+    if (is_terminal && is_login) {
+	struct stat stat_buf;
+	cap_t current, working;
+	int status;
+	cap_value_t cchown = CAP_CHOWN;
+
+	if (fstat(STDIN_FILENO, &stat_buf) != 0) {
+            *callname = "fstat to STDIN";
+	    *err_descr = strerror(errno);
+	    return -1;
+	}
+
+	current = cap_get_proc();
+	working = cap_dup(current);
+	cap_set_flag(working, CAP_EFFECTIVE, 1, &cchown, CAP_SET);
+	status = cap_set_proc(working);
+	cap_free(working);
+
+	if (status != 0) {
+	    *callname = "capset CHOWN";
+	} else if ((status = fchown(STDIN_FILENO, uid, -1)) != 0) {
+	    *callname = "fchown of STDIN";
+	} else {
+	    cap_set_proc(current);
+	}
+	cap_free(current);
+
+	if (status != 0) {
+	    *err_descr = strerror(errno);
+	    return 1;
+	}
+
+	terminal_uid = stat_buf.st_uid;
+    }
+    return 0;
+}
+
+/*
+ * restore_terminal_owner changes the terminal owner back to the value
+ * it had when su was started.
+ */
+static void restore_terminal_owner(void)
+{
+    if (terminal_uid != (uid_t) -1) {
+	cap_t current, working;
+	int status;
+	cap_value_t cchown = CAP_CHOWN;
+
+	current = cap_get_proc();
+	working = cap_dup(current);
+	cap_set_flag(working, CAP_EFFECTIVE, 1, &cchown, CAP_SET);
+	status = cap_set_proc(working);
+	cap_free(working);
+
+	if (status == 0) {
+	    status = fchown(STDIN_FILENO, terminal_uid, -1);
+	    cap_set_proc(current);
+	}
+	cap_free(current);
+
+        if (status != 0) {
+            openlog(PAM_APP_NAME, LOG_CONS|LOG_PERROR|LOG_PID, LOG_AUTHPRIV);
+	    syslog(LOG_ALERT, "Terminal owner hasn\'t been restored: %s",
+		   strerror(errno));
+	    closelog();
+        }
+        terminal_uid = (uid_t) -1;
+    }
+}
+
+/*
+ * make_process_unkillable changes the uid of the process. TEMP_UID is
+ * used for this temporary state.
+ *
+ * Returns:
+ *   0     ok,
+ *  -1     fatal error (continue of the work is impossible),
+ *   1     non-fatal error.
+ * In the case of an error "err_descr" is set to the error message
+ * and "callname" to the name of the failed call.
+ */
+int make_process_unkillable(const char **callname, const char **err_descr)
+{
+    invoked_uid = getuid();
+    if (invoked_uid == TEMP_UID) {
+	/* no change needed */
+	return 0;
+    }
+
+    if (cap_setuid(TEMP_UID) != 0) {
+        *callname = "setuid";
+	*err_descr = strerror(errno);
+	return -1;
+    }
+    return 0;
+}
+
+/*
+ * make_process_killable restores the invoking uid to the current
+ * process.
+ */
+void make_process_killable()
+{
+    (void) cap_setuid(invoked_uid);
+}
+
+/* ------ command line parser ----------------- */
+
+void usage(int exit_val)
+{
+    fprintf(stderr,"usage: su [-] [-h] [-c \"command\"] [username]\n");
+    exit(exit_val);
+}
+
+/*
+ * parse_command_line extracts the options from the command line
+ * arguments.
+ */
+void parse_command_line(int argc, char *argv[],
+			int *is_login, const char **user, const char **command)
+{
+    int username_present, command_present;
+
+    *is_login = 0;
+    *user = NULL;
+    *command = NULL;
+    username_present = command_present = 0;
+
+    while ( --argc > 0 ) {
+	const char *token;
+
+	token = *++argv;
+	if (*token == '-') {
+	    switch (*++token) {
+	    case '\0':             /* su as a login shell for the user */
+		if (*is_login)
+		    usage(1);
+		*is_login = 1;
+		break;
+	    case 'c':
+		if (command_present) {
+		    usage(1);
+		} else {               /* indicate we are running commands */
+		    if (*++token != '\0') {
+			command_present = 1;
+			*command = token;
+		    } else if (--argc > 0) {
+			command_present = 1;
+			*command = *++argv;
+		    } else
+			usage(1);
+		}
+		break;
+	    case 'h':
+		usage(0);
+	    default:
+		usage(1);
+	    }
+	} else {                       /* must be username */
+	    if (username_present) {
+		usage(1);
+	    }
+	    username_present = 1;
+	    *user = *argv;
+	}
+    }
+
+    if (!username_present) {
+	fprintf(stderr, PAM_APP_NAME ": requires a username\n");
+	usage(1);
+    }
+}
+
+/*
+ * This following contains code that waits for a child process to die.
+ * It also chooses to intercept a couple of signals that it will
+ * kindly pass on a SIGTERM to the child ;^). Waiting again for the
+ * child to exit. If the child resists dying, it will SIGKILL it!
+ */
+
+static void wait_for_child_catch_sig(int ignore)
+{
+    wait_for_child_caught = 1;
+}
+
+static void prepare_for_job_control(int need_it)
+{
+    sigset_t ourset;
+
+    (void) sigfillset(&ourset);
+    if (sigprocmask(SIG_BLOCK, &ourset, NULL) != 0) {
+	fprintf(stderr,"[trouble blocking signals]\n");
+	wait_for_child_caught = 1;
+	return;
+    }
+    need_job_control = need_it;
+}
+
+int wait_for_child(pid_t child)
+{
+    int retval, status, exit_code;
+    sigset_t ourset;
+
+    exit_code = -1; /* no exit code yet, exit codes could be from 0 to 255 */
+    if (child == -1) {
+	return exit_code;
+    }
+
+    /*
+     * set up signal handling
+     */
+
+    if (!wait_for_child_caught) {
+	struct sigaction action, defaction;
+
+	action.sa_handler = wait_for_child_catch_sig;
+	sigemptyset(&action.sa_mask);
+	action.sa_flags = 0;
+
+	defaction.sa_handler = SIG_DFL;
+	sigemptyset(&defaction.sa_mask);
+	defaction.sa_flags = 0;
+
+	sigemptyset(&ourset);
+
+	if (   sigaddset(&ourset, SIGTERM)
+	    || sigaction(SIGTERM, &action, NULL)
+	    || sigaddset(&ourset, SIGHUP)
+	    || sigaction(SIGHUP, &action, NULL)
+	    || sigaddset(&ourset, SIGALRM)          /* required by sleep(3) */
+            || (need_job_control && sigaddset(&ourset, SIGTSTP))
+            || (need_job_control && sigaction(SIGTSTP, &defaction, NULL))
+            || (need_job_control && sigaddset(&ourset, SIGTTIN))
+            || (need_job_control && sigaction(SIGTTIN, &defaction, NULL))
+            || (need_job_control && sigaddset(&ourset, SIGTTOU))
+            || (need_job_control && sigaction(SIGTTOU, &defaction, NULL))
+	    || (need_job_control && sigaddset(&ourset, SIGCONT))
+            || (need_job_control && sigaction(SIGCONT, &defaction, NULL))
+	    || sigprocmask(SIG_UNBLOCK, &ourset, NULL)
+	    ) {
+	    fprintf(stderr,"[trouble setting signal intercept]\n");
+	    wait_for_child_caught = 1;
+	}
+
+	/* application should be ready for receiving a SIGTERM/HUP now */
+    }
+
+    /*
+     * This code waits for the process to actually die. If it stops,
+     * then the parent attempts to mimic the behavior of the
+     * child.. There is a slight bug in the code when the 'su'd user
+     * attempts to restart the child independently of the parent --
+     * the child dies.
+     */
+    while (!wait_for_child_caught) {
+        /* parent waits for child */
+	if ((retval = waitpid(child, &status, 0)) <= 0) {
+            if (errno == EINTR) {
+                continue;             /* recovering from a 'fg' */
+	    }
+            fprintf(stderr, "[error waiting child: %s]\n", strerror(errno));
+            /*
+             * Break the loop keeping exit_code undefined.
+             * Do we have a chance for a successful wait() call
+             * after kill()? (SAW)
+             */
+            wait_for_child_caught = 1;
+            break;
+        } else {
+	    /* the child is terminated via exit() or a fatal signal */
+	    if (WIFEXITED(status)) {
+		exit_code = WEXITSTATUS(status);
+	    } else {
+		exit_code = 1;
+	    }
+	    break;
+	}
+    }
+
+    if (wait_for_child_caught) {
+	fprintf(stderr,"\nKilling shell...");
+	kill(child, SIGTERM);
+    }
+
+    /*
+     * do we need to wait for the child to catch up?
+     */
+    if (wait_for_child_caught) {
+	sleep(SLEEP_TO_KILL_CHILDREN);
+	kill(child, SIGKILL);
+	fprintf(stderr, "killed\n");
+    }
+
+    /*
+     * collect the zombie the shell was killed by ourself
+     */
+    if (exit_code == -1) {
+	do {
+	    retval = waitpid(child, &status, 0);
+	} while (retval == -1 && errno == EINTR);
+	if (retval == -1) {
+	    fprintf(stderr, PAM_APP_NAME ": the final wait failed: %s\n",
+		    strerror(errno));
+	}
+	if (WIFEXITED(status)) {
+	    exit_code = WEXITSTATUS(status);
+	} else {
+	    exit_code = 1;
+	}
+    }
+
+    return exit_code;
+}
+
+
+/*
+ * Next some code that parses the spawned shell command line.
+ */
+
+static char * const *build_shell_args(const char *pw_shell, int login,
+				      const char *command)
+{
+    int use_default = 1;  /* flag to signal we should use the default shell */
+    const char **args=NULL;             /* array of PATH+ARGS+NULL pointers */
+
+    D(("called."));
+    if (login) {
+        command = NULL;                 /* command always ignored for login */
+    }
+
+    if (pw_shell && *pw_shell != '\0') {
+        char *line;
+        const char *tmp, *tmpb=NULL;
+        int arg_no=0,i;
+
+        /* first find the number of arguments */
+        D(("non-null shell"));
+        for (tmp=pw_shell; *tmp; ++arg_no) {
+
+            /* skip leading spaces */
+            while (isspace(*tmp))
+                ++tmp;
+
+            if (tmpb == NULL)               /* mark beginning token */
+                tmpb = tmp;
+            if (*tmp == '\0')               /* end of line with no token */
+                break;
+
+            /* skip token */
+            while (*tmp && !isspace(*tmp))
+                ++tmp;
+        }
+
+        /*
+         * We disallow shells:
+         *    - without a full specified path;
+         *    - when we are not logging in and the #args != 1
+         *                                         (unlikely a simple shell)
+         */
+
+        D(("shell so far = %s, arg_no = %d", tmpb, arg_no));
+        if (tmpb != NULL && tmpb[0] == '/'    /* something (full path) */
+            && ( login || arg_no == 1 )       /* login, or single arg shells */
+            ) {
+
+            use_default = 0;                  /* we will use this shell */
+            D(("committed to using user's shell"));
+            if (command) {
+                arg_no += 2;                  /* will append "-c" "command" */
+            }
+
+            /* allocate an array of pointers long enough */
+
+            D(("building array of size %d", 2+arg_no));
+            args = (const char **) calloc(2+arg_no, sizeof(const char *));
+            if (args == NULL)
+                return NULL;
+            /* get a string long enough for all the arguments */
+
+            D(("an array of size %d chars", 2+strlen(tmpb)
+                                   + ( command ? 4:0 )));
+            line = (char *) malloc(2+strlen(tmpb)
+                                   + ( command ? 4:0 ));
+            if (line == NULL) {
+                free(args);
+                return NULL;
+            }
+
+            /* fill array - tmpb points to start of first non-space char */
+
+            line[0] = '-';
+            strcpy(line+1, tmpb);
+
+            /* append " -c" to line? */
+            if (command) {
+                strcat(line, " -c");
+            }
+
+            D(("complete command: %s [+] %s", line, command));
+
+            tmp = strtok(line, " \t");
+            D(("command path=%s", line+1));
+            args[0] = line+1;
+
+            if (login) {               /* standard procedure for login shell */
+                D(("argv[0]=%s", line));
+                args[i=1] = line;
+            } else {                 /* not a login shell -- for use with su */
+                D(("argv[0]=%s", line+1));
+                args[i=1] = line+1;
+            }
+
+            while ((tmp = strtok(NULL, " \t"))) {
+                D(("adding argument %d: %s",i,tmp));
+                args[++i] = tmp;
+            }
+            if (command) {
+                D(("appending command [%s]", command));
+                args[++i] = command;
+            }
+            D(("terminating args with NULL"));
+            args[++i] = NULL;
+            D(("list completed."));
+        }
+    }
+
+    /* should we use the default shell instead of specific one? */
+
+    if (use_default && !login) {
+        int last_arg;
+
+        D(("selecting default shell"));
+        last_arg = command ? 5:3;
+
+        args = (const char **) calloc(last_arg--, sizeof(const char *));
+        if (args == NULL) {
+            return NULL;
+        }
+        args[1] = DEFAULT_SHELL;      /* mapped to argv[0] (NOT login shell) */
+        args[0] = args[1];            /* path to program */
+        if (command) {
+            args[2] = "-c";           /* should perform command and exit */
+            args[3] = command;        /* the desired command */
+        }
+        args[last_arg] = NULL;        /* terminate list of args */
+    }
+
+    D(("returning arg list"));
+    return (char * const *) args;
+}
+
+
+/* ------ abnormal termination ---------------- */
+
+static void exit_now(int exit_code, const char *format, ...)
+{
+    va_list args;
+
+    va_start(args, format);
+    vfprintf(stderr, format, args);
+    va_end(args);
+
+    if (pamh != NULL)
+	pam_end(pamh, exit_code ? PAM_ABORT:PAM_SUCCESS);
+
+    /* USER's shell may have completely broken terminal settings
+       restore the sane(?) initial conditions */
+    restore_terminal_modes();
+
+    exit(exit_code);
+}
+
+static void exit_child_now(int exit_code, const char *format, ...)
+{
+    va_list args;
+
+    va_start(args,format);
+    vfprintf(stderr, format, args);
+    va_end(args);
+
+    if (pamh != NULL)
+	pam_end(pamh, (exit_code ? PAM_ABORT:PAM_SUCCESS) | PAM_DATA_SILENT);
+
+    exit(exit_code);
+}
+
+/* ------ PAM setup --------------------------- */
+
+static struct pam_conv conv = {
+    misc_conv,                   /* defined in <pam_misc/libmisc.h> */
+    NULL
+};
+
+static void do_pam_init(const char *user, int is_login)
+{
+    int retval;
+
+    retval = pam_start(PAM_APP_NAME, user, &conv, &pamh);
+    if (retval != PAM_SUCCESS) {
+	/*
+	 * From my point of view failing of pam_start() means that
+	 * pamh isn't a valid handler. Without a handler
+	 * we couldn't call pam_strerror :-(   1998/03/29 (SAW)
+	 */
+	fprintf(stderr, PAM_APP_NAME ": pam_start failed with code %d\n",
+		retval);
+	exit(1);
+    }
+
+    /*
+     * Fill in some blanks
+     */
+
+    retval = make_environment(pamh, !is_login);
+    D(("made_environment returned: %s", pam_strerror(pamh,retval)));
+
+    if (retval == PAM_SUCCESS && is_terminal) {
+	const char *terminal = ttyname(STDIN_FILENO);
+	if (terminal) {
+	    retval = pam_set_item(pamh, PAM_TTY, (const void *)terminal);
+	} else {
+	    retval = PAM_PERM_DENIED;                /* how did we get here? */
+	}
+	terminal = NULL;
+    }
+
+    if (retval == PAM_SUCCESS && is_terminal) {
+	const char *ruser = getlogin();      /* Who is running this program? */
+	if (ruser) {
+	    retval = pam_set_item(pamh, PAM_RUSER, (const void *)ruser);
+	} else {
+	    retval = PAM_PERM_DENIED;             /* must be known to system */
+	}
+	ruser = NULL;
+    }
+
+    if (retval == PAM_SUCCESS) {
+	retval = pam_set_item(pamh, PAM_RHOST, (const void *)"localhost");
+    }
+
+    if (retval != PAM_SUCCESS) {
+	exit_now(1, PAM_APP_NAME ": problem establishing environment\n");
+    }
+
+    /* have to pause on failure. At least this long (doubles..) */
+    retval = pam_fail_delay(pamh, SU_FAIL_DELAY);
+    if (retval != PAM_SUCCESS) {
+	exit_now(1, PAM_APP_NAME ": problem initializing failure delay\n");
+    }
+}
+
+/*
+ * authenticate_user arranges for the PAM authentication stack to run.
+ */
+static int authenticate_user(pam_handle_t *pamh, cap_t all,
+			     int *retval, const char **place,
+			     const char **err_descr)
+{
+    *place = "pre-auth cap_set_proc";
+    if (cap_set_proc(all)) {
+	D(("failed to raise all capabilities"));
+	*err_descr = "cap_set_proc() failed";
+	*retval = PAM_SUCCESS;
+	return 1;
+    }
+
+    D(("attempt to authenticate user"));
+    *place = "pam_authenticate";
+    *retval = pam_authenticate(pamh, 0);
+    return (*retval != PAM_SUCCESS);
+}
+
+/*
+ * user_accounting confirms an authenticated user is permitted service.
+ */
+static int user_accounting(pam_handle_t *pamh, cap_t all,
+			   int *retval, const char **place,
+			   const char **err_descr) {
+    *place = "user_accounting";
+    if (cap_set_proc(all)) {
+	D(("failed to raise all capabilities"));
+	*err_descr = "cap_set_proc() failed";
+	return 1;
+    }
+    *place = "pam_acct_mgmt";
+    *retval = pam_acct_mgmt(pamh, 0);
+    return (*retval != PAM_SUCCESS);
+}
+
+/*
+ * Find entry for this terminal (if there is one).
+ * Utmp file should have been opened and rewinded for the call.
+ *
+ * XXX: the search should be more or less compatible with libc one.
+ * The caller expects that pututline with the same arguments
+ * will replace the found entry.
+ */
+static const struct utmp *find_utmp_entry(const char *ut_line,
+					  const char *ut_id)
+{
+    struct utmp *u_tmp_p;
+
+    while ((u_tmp_p = getutent()) != NULL)
+	if ((u_tmp_p->ut_type == INIT_PROCESS ||
+             u_tmp_p->ut_type == LOGIN_PROCESS ||
+             u_tmp_p->ut_type == USER_PROCESS ||
+             u_tmp_p->ut_type == DEAD_PROCESS) &&
+            !strncmp(u_tmp_p->ut_id, ut_id, UT_IDSIZE) &&
+            !strncmp(u_tmp_p->ut_line, ut_line, UT_LINESIZE))
+                break;
+
+    return u_tmp_p;
+}
+
+/*
+ * Identify the terminal name and the abbreviation we will use.
+ */
+static void set_terminal_name(const char *terminal, char *ut_line, char *ut_id)
+{
+    memset(ut_line, 0, UT_LINESIZE);
+    memset(ut_id, 0, UT_IDSIZE);
+
+    /* set the terminal entry */
+    if ( *terminal == '/' ) {     /* now deal with filenames */
+	int o1, o2;
+
+	o1 = strncmp(DEVICE_FILE_PREFIX, terminal, 5) ? 0 : 5;
+	if (!strncmp("/dev/tty", terminal, 8)) {
+	    o2 = 8;
+	} else {
+	    o2 = strlen(terminal) - sizeof(UT_IDSIZE);
+	    if (o2 < 0)
+		o2 = 0;
+	}
+
+	strncpy(ut_line, terminal + o1, UT_LINESIZE);
+	strncpy(ut_id, terminal + o2, UT_IDSIZE);
+    } else if (strchr(terminal, ':')) {  /* deal with X-based session */
+	const char *suffix;
+
+	suffix = strrchr(terminal,':');
+	strncpy(ut_line, terminal, UT_LINESIZE);
+	strncpy(ut_id, suffix, UT_IDSIZE);
+    } else {	                         /* finally deal with weird terminals */
+	strncpy(ut_line, terminal, UT_LINESIZE);
+	ut_id[0] = '?';
+	strncpy(ut_id + 1, terminal, UT_IDSIZE - 1);
+    }
+}
+
+/*
+ * Append an entry to wtmp. See utmp_open_session for the return convention.
+ * Be careful: the function uses alarm().
+ */
+
+#define WWTMP_STATE_BEGINNING     0
+#define WWTMP_STATE_FILE_OPENED   1
+#define WWTMP_STATE_SIGACTION_SET 2
+#define WWTMP_STATE_LOCK_TAKEN    3
+
+static int write_wtmp(struct utmp *u_tmp_p, const char **callname,
+		      const char **err_descr)
+{
+    int w_tmp_fd;
+    struct flock w_lock;
+    struct sigaction act1, act2;
+    int state;
+    int retval;
+
+    state = WWTMP_STATE_BEGINNING;
+    retval = 1;
+
+    do {
+        D(("writing to wtmp"));
+        w_tmp_fd = open(_PATH_WTMP, O_APPEND|O_WRONLY);
+        if (w_tmp_fd == -1) {
+            *callname = "wtmp open";
+            *err_descr = strerror(errno);
+            break;
+        }
+        state = WWTMP_STATE_FILE_OPENED;
+
+        /* prepare for blocking operation... */
+        act1.sa_handler = SIG_DFL;
+        sigemptyset(&act1.sa_mask);
+        act1.sa_flags = 0;
+        if (sigaction(SIGALRM, &act1, &act2) == -1) {
+            *callname = "sigaction";
+            *err_descr = strerror(errno);
+            break;
+        }
+        alarm(WTMP_LOCK_TIMEOUT);
+        state = WWTMP_STATE_SIGACTION_SET;
+
+        /* now we try to lock this file-rcord exclusively; non-blocking */
+        memset(&w_lock, 0, sizeof(w_lock));
+        w_lock.l_type = F_WRLCK;
+        w_lock.l_whence = SEEK_END;
+        if (fcntl(w_tmp_fd, F_SETLK, &w_lock) < 0) {
+            D(("locking %s failed.", _PATH_WTMP));
+            *callname = "fcntl(F_SETLK)";
+            *err_descr = strerror(errno);
+            break;
+        }
+        alarm(0);
+        sigaction(SIGALRM, &act2, NULL);
+        state = WWTMP_STATE_LOCK_TAKEN;
+
+        if (write(w_tmp_fd, u_tmp_p, sizeof(struct utmp)) != -1) {
+            retval = 0;
+	}
+    } while(0); /* it's not a loop! */
+
+    if (state >= WWTMP_STATE_LOCK_TAKEN) {
+        w_lock.l_type = F_UNLCK;               /* unlock wtmp file */
+        fcntl(w_tmp_fd, F_SETLK, &w_lock);
+    }else if (state >= WWTMP_STATE_SIGACTION_SET) {
+        alarm(0);
+        sigaction(SIGALRM, &act2, NULL);
+    }
+
+    if (state >= WWTMP_STATE_FILE_OPENED) {
+        close(w_tmp_fd);                       /* close wtmp file */
+        D(("wtmp written"));
+    }
+
+    return retval;
+}
+
+/*
+ * XXX - if this gets turned into a module, make this a
+ * pam_data item. You should put the pid in the name so we can
+ * "probably" nest calls more safely...
+ */
+struct utmp *login_stored_utmp=NULL;
+
+/*
+ * Returns:
+ *   0     ok,
+ *   1     non-fatal error
+ *  -1     fatal error
+ *  callname and err_descr will be set
+ * Be careful: the function indirectly uses alarm().
+ */
+static int utmp_do_open_session(const char *user, const char *terminal,
+				const char *rhost, pid_t pid,
+				const char **place, const char **err_descr)
+{
+    struct utmp u_tmp;
+    const struct utmp *u_tmp_p;
+    char ut_line[UT_LINESIZE], ut_id[UT_IDSIZE];
+    int retval;
+
+    set_terminal_name(terminal, ut_line, ut_id);
+
+    utmpname(_PATH_UTMP);
+    setutent();                                           /* rewind file */
+    u_tmp_p = find_utmp_entry(ut_line, ut_id);
+
+    /* reset new entry */
+    memset(&u_tmp, 0, sizeof(u_tmp));                     /* reset new entry */
+    if (u_tmp_p == NULL) {
+	D(("[NEW utmp]"));
+    } else {
+	D(("[OLD utmp]"));
+
+	/*
+	 * here, we make a record of the former entry. If the
+	 * utmp_close_session code is attached to the same process,
+	 * the wtmp will be replaced, otherwise we leave init to pick
+	 * up the pieces.
+	 */
+	if (login_stored_utmp == NULL) {
+	    login_stored_utmp = malloc(sizeof(struct utmp));
+            if (login_stored_utmp == NULL) {
+                *place = "malloc";
+                *err_descr = "fail";
+                endutent();
+                return -1;
+            }
+	}
+        memcpy(login_stored_utmp, u_tmp_p, sizeof(struct utmp));
+    }
+
+    /* we adjust the entry to reflect the current session */
+    {
+	strncpy(u_tmp.ut_line, ut_line, UT_LINESIZE);
+	memset(ut_line, 0, UT_LINESIZE);
+	strncpy(u_tmp.ut_id, ut_id, UT_IDSIZE);
+	memset(ut_id, 0, UT_IDSIZE);
+	strncpy(u_tmp.ut_user, user
+		, sizeof(u_tmp.ut_user));
+	strncpy(u_tmp.ut_host, rhost ? rhost : RHOST_UNKNOWN_NAME
+		, sizeof(u_tmp.ut_host));
+
+	/* try to fill the host address entry */
+	if (rhost != NULL) {
+	    struct hostent *hptr;
+
+	    /* XXX: it isn't good to do DNS lookup here...  1998/05/29  SAW */
+            hptr = gethostbyname(rhost);
+	    if (hptr != NULL && hptr->h_addr_list) {
+		memcpy(&u_tmp.ut_addr, hptr->h_addr_list[0]
+		       , sizeof(u_tmp.ut_addr));
+	    }
+	}
+
+	/* we fill in the remaining info */
+	u_tmp.ut_type = USER_PROCESS;          /* a user process starting */
+	u_tmp.ut_pid = pid;                    /* session identifier */
+	u_tmp.ut_time = time(NULL);
+    }
+
+    setutent();                                /* rewind file (replace old) */
+    pututline(&u_tmp);                         /* write it to utmp */
+    endutent();                                /* close the file */
+
+    retval = write_wtmp(&u_tmp, place, err_descr); /* write to wtmp file */
+    memset(&u_tmp, 0, sizeof(u_tmp));          /* reset entry */
+
+    return retval;
+}
+
+static int utmp_do_close_session(const char *terminal,
+				 const char **place, const char **err_descr)
+{
+    int retval;
+    struct utmp u_tmp;
+    const struct utmp *u_tmp_p;
+    char ut_line[UT_LINESIZE], ut_id[UT_IDSIZE];
+
+    retval = 0;
+
+    set_terminal_name(terminal, ut_line, ut_id);
+
+    utmpname(_PATH_UTMP);
+    setutent();                                              /* rewind file */
+
+    /*
+     * if there was a stored entry, return it to the utmp file, else
+     * if there is a session to close, we close that
+     */
+    if (login_stored_utmp) {
+	pututline(login_stored_utmp);
+
+	memcpy(&u_tmp, login_stored_utmp, sizeof(u_tmp));
+	u_tmp.ut_time = time(NULL);            /* a new time to restart */
+
+        retval = write_wtmp(&u_tmp, place, err_descr);
+
+	memset(login_stored_utmp, 0, sizeof(u_tmp)); /* reset entry */
+	free(login_stored_utmp);
+    } else {
+        u_tmp_p = find_utmp_entry(ut_line, ut_id);
+        if (u_tmp_p != NULL) {
+            memset(&u_tmp, 0, sizeof(u_tmp));
+            strncpy(u_tmp.ut_line, ut_line, UT_LINESIZE);
+            strncpy(u_tmp.ut_id, ut_id, UT_IDSIZE);
+            memset(&u_tmp.ut_user, 0, sizeof(u_tmp.ut_user));
+            memset(&u_tmp.ut_host, 0, sizeof(u_tmp.ut_host));
+            u_tmp.ut_addr = 0;
+            u_tmp.ut_type = DEAD_PROCESS;      /* `old' login process */
+            u_tmp.ut_pid = 0;
+            u_tmp.ut_time = time(NULL);
+            setutent();                        /* rewind file (replace old) */
+            pututline(&u_tmp);                 /* mark as dead */
+
+            retval = write_wtmp(&u_tmp, place, err_descr);
+        }
+    }
+
+    /* clean up */
+    memset(ut_line, 0, UT_LINESIZE);
+    memset(ut_id, 0, UT_IDSIZE);
+
+    endutent();                                /* close utmp file */
+    memset(&u_tmp, 0, sizeof(u_tmp));          /* reset entry */
+
+    return 0;
+}
+
+/*
+ * Returns:
+ *   0     ok,
+ *   1     non-fatal error
+ *  -1     fatal error
+ * place and err_descr will be set
+ * Be careful: the function indirectly uses alarm().
+ */
+static int utmp_open_session(pam_handle_t *pamh, pid_t pid,
+			     int *retval,
+			     const char **place, const char **err_descr)
+{
+    const char *user, *terminal, *rhost;
+
+    *retval = pam_get_item(pamh, PAM_USER, (const void **)&user);
+    if (*retval != PAM_SUCCESS) {
+        return -1;
+    }
+    *retval = pam_get_item(pamh, PAM_TTY, (const void **)&terminal);
+    if (retval != PAM_SUCCESS) {
+        return -1;
+    }
+    *retval = pam_get_item(pamh, PAM_RHOST, (const void **)&rhost);
+    if (retval != PAM_SUCCESS) {
+        rhost = NULL;
+    }
+
+    return utmp_do_open_session(user, terminal, rhost, pid, place, err_descr);
+}
+
+static int utmp_close_session(pam_handle_t *pamh
+                              , const char **place, const char **err_descr)
+{
+    int retval;
+    const char *terminal;
+
+    retval = pam_get_item(pamh, PAM_TTY, (const void **)&terminal);
+    if (retval != PAM_SUCCESS) {
+        *place = "pam_get_item(PAM_TTY)";
+        *err_descr = pam_strerror(pamh, retval);
+        return -1;
+    }
+
+    return utmp_do_close_session(terminal, place, err_descr);
+}
+
+/*
+ * set_credentials raises all of the process and PAM credentials.
+ */
+static int set_credentials(pam_handle_t *pamh, cap_t all, int login,
+			   const char **pw_shell,
+			   int *retval, const char **place,
+			   const char **err_descr)
+{
+    const char *user;
+    char *shell;
+    cap_value_t csetgid = CAP_SETGID;
+    cap_t current;
+    int status;
+    struct passwd *pw;
+    uid_t uid;
+
+    D(("get user from pam"));
+    *place = "set_credentials";
+    *retval = pam_get_item(pamh, PAM_USER, (const void **)&user);
+    if (*retval != PAM_SUCCESS || user == NULL || *user == '\0') {
+	D(("error identifying user from PAM."));
+	*retval = PAM_USER_UNKNOWN;
+	return 1;
+    }
+
+    /*
+     * Add the LOGNAME and HOME environment variables.
+     */
+
+    pw = getpwnam(user);
+    if (pw == NULL || (user = x_strdup(pw->pw_name)) == NULL) {
+	D(("failed to identify user"));
+	*retval = PAM_USER_UNKNOWN;
+	return 1;
+    }
+
+    uid = pw->pw_uid;
+    shell = x_strdup(pw->pw_shell);
+    if (shell == NULL) {
+	D(("user %s has no shell", user));
+	*retval = PAM_CRED_ERR;
+	return 1;
+    }
+
+    if (login) {
+	/* set LOGNAME, HOME */
+	if (pam_misc_setenv(pamh, "LOGNAME", user, 0) != PAM_SUCCESS) {
+	    D(("failed to set LOGNAME"));
+	    *retval = PAM_CRED_ERR;
+	    return 1;
+	}
+	if (pam_misc_setenv(pamh, "HOME", pw->pw_dir, 0) != PAM_SUCCESS) {
+	    D(("failed to set HOME"));
+	    *retval = PAM_CRED_ERR;
+	    return 1;
+	}
+    }
+
+    current = cap_get_proc();
+    cap_set_flag(current, CAP_EFFECTIVE, 1, &csetgid, CAP_SET);
+    status = cap_set_proc(current);
+    cap_free(current);
+    if (status != 0) {
+	*err_descr = "unable to raise CAP_SETGID";
+	return 1;
+    }
+
+    /* initialize groups */
+    if (initgroups(pw->pw_name, pw->pw_gid) != 0 || setgid(pw->pw_gid) != 0) {
+	D(("failed to setgid etc"));
+	*retval = PAM_PERM_DENIED;
+	return 1;
+    }
+    *pw_shell = shell;
+
+    pw = NULL;                                                  /* be tidy */
+
+    D(("desired uid=%d", uid));
+
+    /* assume user's identity - but preserve the permitted set */
+    if (cap_setuid(uid) != 0) {
+	D(("failed to setuid: %v", strerror(errno)));
+	*retval = PAM_PERM_DENIED;
+	return 1;
+    }
+
+    /*
+     * Next, we call the PAM framework to add/enhance the credentials
+     * of this user [it may change the user's home directory in the
+     * pam_env, and add supplemental group memberships...].
+     */
+    D(("setting credentials"));
+    if (cap_set_proc(all)) {
+	D(("failed to raise all capabilities"));
+	*retval = PAM_PERM_DENIED;
+	return 1;
+    }
+
+    D(("calling pam_setcred to establish credentials"));
+    *retval = pam_setcred(pamh, PAM_ESTABLISH_CRED);
+
+    return (*retval != PAM_SUCCESS);
+}
+
+/*
+ * open_session invokes the open session PAM stack.
+ */
+static int open_session(pam_handle_t *pamh, cap_t all,
+			int *retval, const char **place, const char **err_descr)
+{
+    /* Open the su-session */
+    *place = "pam_open_session";
+    if (cap_set_proc(all)) {
+	D(("failed to raise t_caps capabilities"));
+	*err_descr = "capability setting failed";
+	return 1;
+    }
+    *retval = pam_open_session(pamh, 0);     /* Must take care to close */
+    if (*retval != PAM_SUCCESS) {
+	return 1;
+    }
+    return 0;
+}
+
+/* ------ shell invoker ----------------------- */
+
+static int launch_callback_fn(void *h)
+{
+    pam_handle_t *pamh = h;
+    int retval;
+
+    D(("pam_end"));
+    retval = pam_end(pamh, PAM_SUCCESS | PAM_DATA_SILENT);
+    pamh = NULL;
+    if (retval != PAM_SUCCESS) {
+	return -1;
+    }
+
+    /*
+     * Restore a signal status: information if the signal is ignored
+     * is inherited across exec() call.  (SAW)
+     */
+    enable_terminal_signals();
+
+    D(("about to launch"));
+    return 0;
+}
+
+/* Returns PAM_<STATUS>. */
+static int perform_launch_and_cleanup(cap_t all, int is_login,
+				      const char *shell, const char *command)
+{
+    int retval, status;
+    const char *user, *home;
+    uid_t uid;
+    char * const * shell_args;
+    char * const * shell_env;
+    cap_launch_t launcher;
+    pid_t child;
+
+
+    /*
+     * Break up the shell command into a command and arguments
+     */
+    shell_args = build_shell_args(shell, is_login, command);
+    if (shell_args == NULL) {
+	D(("failed to compute shell arguments"));
+	return PAM_SYSTEM_ERR;
+    }
+
+    home = pam_getenv(pamh, "HOME");
+    if ( !home || home[0] == '\0' ) {
+	fprintf(stderr, "setting home directory for %s to %s\n",
+		user, DEFAULT_HOME);
+	home = DEFAULT_HOME;
+	if (pam_misc_setenv(pamh, "HOME", home, 0) != PAM_SUCCESS) {
+	    D(("unable to set $HOME"));
+	    fprintf(stderr,
+		    "Warning: unable to set HOME environment variable\n");
+	}
+    }
+    if (is_login) {
+	if (chdir(home) && chdir(DEFAULT_HOME)) {
+	    D(("failed to change directory"));
+	    return PAM_SYSTEM_ERR;
+	}
+    }
+
+    shell_env = pam_getenvlist(pamh);
+    if (shell_env == NULL) {
+	D(("failed to obtain environment for child"));
+	return PAM_SYSTEM_ERR;
+    }
+
+    launcher = cap_new_launcher(shell_args[0],
+				(const char * const *) &shell_args[1],
+				(const char * const *) shell_env);
+    if (launcher == NULL) {
+	D(("failed to initialize launcher"));
+	return PAM_SYSTEM_ERR;
+    }
+    cap_launcher_set_iab(launcher, cap_iab_get_proc());
+    cap_launcher_callback(launcher, launch_callback_fn);
+
+    child = cap_launch(launcher, pamh);
+    cap_free(launcher);
+
+    /* job control is off for login sessions */
+    prepare_for_job_control(!is_login && command != NULL);
+
+    if (cap_setuid(TEMP_UID) != 0) {
+	fprintf(stderr, "[failed to change monitor UID=%d]\n", TEMP_UID);
+    }
+
+    /* wait for child to terminate */
+    status = wait_for_child(child);
+    if (status != 0) {
+	D(("shell returned %d", status));
+    }
+    return status;
+}
+
+static void close_session(pam_handle_t *pamh, cap_t all)
+{
+    int retval;
+
+    D(("session %p closing", pamh));
+    if (cap_set_proc(all)) {
+	fprintf(stderr, "WARNING: could not raise all caps\n");
+    }
+    retval = pam_close_session(pamh, 0);
+    if (retval != PAM_SUCCESS) {
+	fprintf(stderr, "WARNING: could not close session\n\t%s\n",
+		pam_strerror(pamh,retval));
+    }
+}
+
+/* -------------------------------------------- */
+/* ------ the application itself -------------- */
+/* -------------------------------------------- */
+
+int main(int argc, char *argv[])
+{
+    int retcode, is_login, status;
+    int retval, final_retval; /* PAM_xxx return values */
+    const char *command, *shell;
+    pid_t child;
+    uid_t uid;
+    const char *place = NULL, *err_descr = NULL;
+    cap_t all, t_caps;
+
+    all = cap_get_proc();
+    cap_fill(all, CAP_EFFECTIVE, CAP_PERMITTED);
+
+    checkfds();
+
+    /*
+     * Check whether stdin is a terminal and store terminal modes for later.
+     */
+    store_terminal_modes();
+
+    /* ---------- parse the argument list and --------- */
+    /* ------ initialize the Linux-PAM interface ------ */
+    {
+	const char *user;              /* transient until PAM_USER defined */
+	parse_command_line(argc, argv, &is_login, &user, &command);
+	place = "do_pam_init";
+	do_pam_init(user, is_login);   /* call pam_start and set PAM items */
+    }
+
+    /*
+     * Turn off terminal signals - this is to be sure that su gets a
+     * chance to call pam_end() and restore the terminal modes in
+     * spite of the frustrated user pressing Ctrl-C.
+     */
+    disable_terminal_signals();
+
+    /*
+     * Random exits from here are strictly prohibited :-) (SAW) AGM
+     * achieves this with goto's and a single exit at the end of main.
+     */
+    status = 1;                       /* fake exit status of a child */
+    err_descr = NULL;                 /* errors haven't happened */
+
+    if (make_process_unkillable(&place, &err_descr) != 0) {
+	goto su_exit;
+    }
+
+    if (authenticate_user(pamh, all, &retval, &place, &err_descr) != 0) {
+	goto auth_exit;
+    }
+
+    /*
+     * The user is valid, but should they have access at this
+     * time?
+     */
+    if (user_accounting(pamh, all, &retval, &place, &err_descr) != 0) {
+	goto auth_exit;
+    }
+
+    D(("su attempt is confirmed as authorized"));
+
+    /*
+     * ... setup terminal, ...
+     */
+    retcode = change_terminal_owner(uid, is_login, &place, &err_descr);
+    if (retcode > 0) {
+	fprintf(stderr, PAM_APP_NAME ": %s: %s\n", place, err_descr);
+	err_descr = NULL; /* forget about the problem */
+    } else if (retcode < 0) {
+	D(("terminal owner to uid=%d change failed", uid));
+	goto auth_exit;
+    }
+
+    if (set_credentials(pamh, all, is_login,
+			&shell, &retval, &place, &err_descr) != 0) {
+	D(("failed to set credentials"));
+	goto auth_exit;
+    }
+
+    /*
+     * Here the IAB value is fixed and may differ from all's
+     * Inheritable value. So synthesize what we need to proceed in the
+     * child, for now, in this current process.
+     */
+    place = "preserving inheritable parts";
+    t_caps = cap_get_proc();
+    if (t_caps == NULL) {
+	D(("failed to read capabilities"));
+	err_descr = "capability read failed";
+	goto delete_cred;
+    }
+    if (cap_fill(t_caps, CAP_EFFECTIVE, CAP_PERMITTED)) {
+	D(("failed to fill effective bits"));
+	err_descr = "capability fill failed";
+	goto delete_cred;
+    }
+
+    /*
+     * ... make [uw]tmp entries.
+     */
+    if (is_login) {
+	/*
+	 * Note: we use the parent pid as a session identifier for
+	 * the logging.
+	 */
+	retcode = utmp_open_session(pamh, getpid(),
+				    &retval, &place, &err_descr);
+	if (retcode > 0) {
+	    fprintf(stderr, PAM_APP_NAME ": %s: %s\n", place, err_descr);
+	    err_descr = NULL; /* forget about this non-critical problem */
+	} else if (retcode < 0) {
+	    goto delete_cred;
+	}
+    }
+
+    if (open_session(pamh, t_caps, &retval, &place, &err_descr) != 0) {
+	goto utmp_closer;
+    }
+
+    status = perform_launch_and_cleanup(t_caps, is_login, shell, command);
+    close_session(pamh, all);
+
+utmp_closer:
+    if (is_login) {
+	/* do [uw]tmp cleanup */
+	retcode = utmp_close_session(pamh, &place, &err_descr);
+	if (retcode) {
+	    fprintf(stderr, PAM_APP_NAME ": %s: %s\n", place, err_descr);
+	}
+    }
+
+delete_cred:
+    D(("delete credentials"));
+    if (cap_set_proc(all)) {
+	D(("failed to raise all capabilities"));
+    }
+    retcode = pam_setcred(pamh, PAM_DELETE_CRED);
+    if (retcode != PAM_SUCCESS) {
+	fprintf(stderr, "WARNING: could not delete credentials\n\t%s\n",
+		pam_strerror(pamh, retcode));
+    }
+
+old_owner:
+    D(("return terminal to local control"));
+    restore_terminal_owner();
+
+auth_exit:
+    D(("for clean up we restore the launching user"));
+    make_process_killable();
+
+    D(("all done - closing down pam"));
+    if (retval != PAM_SUCCESS) {      /* PAM has failed */
+	fprintf(stderr, PAM_APP_NAME ": %s\n", pam_strerror(pamh, retval));
+	final_retval = PAM_ABORT;
+    } else if (err_descr != NULL) {   /* a system error has happened */
+	fprintf(stderr, PAM_APP_NAME ": %s: %s\n", place, err_descr);
+	final_retval = PAM_ABORT;
+    } else {
+	final_retval = PAM_SUCCESS;
+    }
+    (void) pam_end(pamh, final_retval);
+    pamh = NULL;
+
+    if (restore_terminal_modes() != 0 && !status) {
+	status = 1;
+    }
+
+su_exit:
+    exit(status);                 /* transparent exit */
+}
diff --git a/contrib/sucap/sucap.pamconfig b/contrib/sucap/sucap.pamconfig
new file mode 100644
index 0000000..02b70f2
--- /dev/null
+++ b/contrib/sucap/sucap.pamconfig
@@ -0,0 +1,6 @@
+#%PAM-1.0
+auth            required        pam_cap.so config=/etc/security/capability.conf 
+auth		required        pam_unix.so
+account		required	pam_unix.so
+password	required	pam_unix.so
+session		required        pam_unix.so
diff --git a/doc/Makefile b/doc/Makefile
index e60f72d..a34cee0 100644
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -10,13 +10,20 @@
 	cap_clear.3 cap_clear_flag.3 cap_get_flag.3 cap_set_flag.3 \
 	cap_compare.3 cap_get_proc.3 cap_get_pid.3 cap_set_proc.3 \
 	cap_get_file.3 cap_get_fd.3 cap_set_file.3 cap_set_fd.3 \
-	cap_copy_ext.3 cap_size.3 cap_copy_int.3 \
+	cap_copy_ext.3 cap_size.3 cap_copy_int.3 cap_mode.3 \
 	cap_from_text.3 cap_to_text.3 cap_from_name.3 cap_to_name.3 \
 	capsetp.3 capgetp.3 libcap.3 \
 	cap_get_bound.3 cap_drop_bound.3 \
 	cap_get_mode.3 cap_set_mode.3 cap_mode_name.3 \
 	cap_get_secbits.3 cap_set_secbits.3 \
 	cap_setuid.3 cap_setgroups.3 \
+	cap_launch.3 cap_func_launcher.3 cap_launcher_callback.3 \
+	cap_launcher_set_chroot.3 cap_launcher_set_mode.3 \
+	cap_launcher_setgroups.3 cap_launcher_setuid.3 \
+	cap_launcher_set_iab.3 cap_new_launcher.3 \
+	cap_iab.3 cap_iab_init.3 cap_iab_get_proc.3 cap_iab_set_proc.3 \
+	cap_iab_to_text.3 cap_iab_from_text.3 cap_iab_get_vector.3 \
+	cap_iab_set_vector.3 cap_iab_fill.3 \
 	psx_syscall.3 psx_syscall3.3 psx_syscall6.3 libpsx.3
 MAN8S = getcap.8 setcap.8 getpcaps.8
 
diff --git a/doc/cap_clear.3 b/doc/cap_clear.3
index 73aac61..6d06049 100644
--- a/doc/cap_clear.3
+++ b/doc/cap_clear.3
@@ -1,25 +1,21 @@
-.TH CAP_CLEAR 3 "2008-05-11" "" "Linux Programmer's Manual"
+.TH CAP_CLEAR 3 "2021-03-06" "" "Linux Programmer's Manual"
 .SH NAME
-cap_clear, cap_clear_flag, cap_get_flag, cap_set_flag, cap_compare \- capability data object manipulation
+cap_clear, cap_clear_flag, cap_get_flag, cap_set_flag, cap_fill, cap_compare \- capability data object manipulation
 .SH SYNOPSIS
 .nf
-.B #include <sys/capability.h>
-.sp
-.BI "int cap_clear(cap_t " cap_p );
-.sp
-.BI "int cap_clear_flag(cap_t " cap_p ", cap_flag_t " flag ");"
-.sp
-.BI "int cap_get_flag(cap_t " cap_p ", cap_value_t " cap ,
-.BI "                 cap_flag_t " flag ", cap_flag_value_t *" value_p ");"
-.sp
-.BI "int cap_set_flag(cap_t " cap_p ", cap_flag_t " flag ", int " ncap ,
-.BI "                 const cap_value_t *" caps \
-", cap_flag_value_t " value ");"
-.sp
-.BI "int cap_compare(cap_t " cap_a ", cap_t " cap_b ");"
+#include <sys/capability.h>
+
+int cap_clear(cap_t cap_p);
+int cap_clear_flag(cap_t cap_p, cap_flag_t flag);
+int cap_get_flag(cap_t cap_p, cap_value_t cap,
+                 cap_flag_t flag, cap_flag_value_t *value_p);
+int cap_set_flag(cap_t cap_p, cap_flag_t flag, int ncap,
+                 const cap_value_t *caps, cap_flag_value_t value);
+int cap_fill(cap_t cap_p, cap_flag_t to, cap_flag_t from);
+int cap_compare(cap_t cap_a, cap_t cap_b);
+.fi
 .sp
 Link with \fI\-lcap\fP.
-.fi
 .SH DESCRIPTION
 These functions work on a capability state held in working storage.
 A
@@ -85,6 +81,9 @@
 is used to specify the number of capabilities in the array,
 .IR caps .
 .PP
+.BR cap_fill ()
+fills the to flag values by copying all of the from flag values.
+.PP
 .BR cap_compare ()
 compares two full capability sets and, in the spirit of
 .BR memcmp (),
diff --git a/doc/cap_copy_ext.3 b/doc/cap_copy_ext.3
index acbb487..0965ad1 100644
--- a/doc/cap_copy_ext.3
+++ b/doc/cap_copy_ext.3
@@ -1,15 +1,15 @@
-.TH CAP_COPY_EXT 3 "2008-05-11" "" "Linux Programmer's Manual"
+.TH CAP_COPY_EXT 3 "2021-03-06" "" "Linux Programmer's Manual"
 .SH NAME
 cap_copy_ext, cap_size, cap_copy_int \- capability state
 external representation translation
 .SH SYNOPSIS
-.B #include <sys/capability.h>
-.sp
-.BI "ssize_t cap_size(cap_t " cap_p );
-.sp
-.BI "ssize_t cap_copy_ext(void *" ext_p ", cap_t " cap_p ", ssize_t " size );
-.sp
-.BI "cap_t cap_copy_int(const void *" ext_p );
+.nf
+#include <sys/capability.h>
+
+ssize_t cap_size(cap_t cap_p);
+ssize_t cap_copy_ext(void *ext_p, cap_t cap_p, ssize_t size);
+cap_t cap_copy_int(const void * ext_p);
+.fi
 .sp
 Link with \fI\-lcap\fP.
 .SH DESCRIPTION
diff --git a/doc/cap_from_text.3 b/doc/cap_from_text.3
index 59724c7..9370e26 100644
--- a/doc/cap_from_text.3
+++ b/doc/cap_from_text.3
@@ -1,7 +1,7 @@
 .\"
 .\" written by Andrew Main <zefram@dcs.warwick.ac.uk>
 .\"
-.TH CAP_FROM_TEXT 3 "2008-05-10" "" "Linux Programmer's Manual"
+.TH CAP_FROM_TEXT 3 "2021-03-06" "" "Linux Programmer's Manual"
 .SH NAME
 cap_from_text, cap_to_text, cap_to_name, cap_from_name \- capability
 state textual representation translation
@@ -14,6 +14,7 @@
 int cap_from_name(const char* name , cap_value_t* cap_p);
 char *cap_to_name(cap_value_t cap);
 .fi
+.sp
 Link with \fI\-lcap\fP.
 .SH DESCRIPTION
 These functions translate a capability state between
diff --git a/doc/cap_func_launcher.3 b/doc/cap_func_launcher.3
new file mode 100644
index 0000000..4f072fc
--- /dev/null
+++ b/doc/cap_func_launcher.3
@@ -0,0 +1 @@
+.so man3/cap_launch.3
diff --git a/doc/cap_get_file.3 b/doc/cap_get_file.3
index 3f73734..4c812fe 100644
--- a/doc/cap_get_file.3
+++ b/doc/cap_get_file.3
@@ -1,24 +1,21 @@
 .\"
 .\" written by Andrew Main <zefram@dcs.warwick.ac.uk>
 .\"
-.TH CAP_GET_FILE 3 "2008-05-11" "" "Linux Programmer's Manual"
+.TH CAP_GET_FILE 3 "2021-03-06" "" "Linux Programmer's Manual"
 .SH NAME
 cap_get_file, cap_set_file, cap_get_fd, cap_set_fd \- capability
 manipulation on files
 .SH SYNOPSIS
-.B #include <sys/capability.h>
-.sp
-.BI "cap_t cap_get_file(const char *" path_p );
-.sp
-.BI "int cap_set_file(const char *" path_p ", cap_t " cap_p );
-.sp
-.BI "cap_t cap_get_fd(int " fd );
-.sp
-.BI "int cap_set_fd(int " fd ", cap_t " caps );
-.sp
-.BI "uid_t cap_get_nsowner(cap_t " caps );
-.sp
-.BI "int cap_set_nsowner(cap_t " caps ", uid_t " rootuid );
+.nf
+#include <sys/capability.h>
+
+cap_t cap_get_file(const char *path_p);
+int cap_set_file(const char *path_p, cap_t cap_p);
+cap_t cap_get_fd(int fd);
+int cap_set_fd(int fd, cap_t caps);
+uid_t cap_get_nsowner(cap_t caps);
+int cap_set_nsowner(cap_t caps, uid_t rootuid);
+.fi
 .sp
 Link with \fI\-lcap\fP.
 .SH DESCRIPTION
diff --git a/doc/cap_get_proc.3 b/doc/cap_get_proc.3
index 74e5e8c..496c06e 100644
--- a/doc/cap_get_proc.3
+++ b/doc/cap_get_proc.3
@@ -1,49 +1,42 @@
-.TH CAP_GET_PROC 3 "2019-12-21" "" "Linux Programmer's Manual"
+.TH CAP_GET_PROC 3 "2021-03-06" "" "Linux Programmer's Manual"
 .SH NAME
 cap_get_proc, cap_set_proc, capgetp, cap_get_bound, cap_drop_bound, \
 cap_get_ambient, cap_set_ambient, cap_reset_ambient, \
 cap_get_secbits, cap_set_secbits, cap_get_mode, cap_set_mode, \
-cap_mode_name, cap_get_pid, cap_setuid, cap_setgroups \
+cap_mode_name, cap_get_pid, cap_setuid, cap_prctl, cap_prctlw, cap_setgroups \
 \- capability manipulation on processes
 .SH SYNOPSIS
-.B #include <sys/capability.h>
-.sp
-.B "cap_t cap_get_proc(void);"
-.sp
-.BI "int cap_set_proc(cap_t " cap_p );
-.sp
-.BI "int cap_get_bound(cap_value_t " cap );
-.sp
-.BI "CAP_IS_SUPPORTED(cap_value_t " cap );
-.sp
-.BI "int cap_drop_bound(cap_value_t " cap );
-.sp
-.BI "int cap_get_ambient(cap_value_t " cap );
-.sp
-.BI "int cap_set_ambient(cap_value_t " cap ", cap_flag_value_t " value );
-.sp
-.B int cap_reset_ambient(void);
-.sp
-.BI CAP_AMBIENT_SUPPORTED();
-.sp
-.B "unsigned cap_get_secbits(void);"
-.sp
-.BI "int cap_set_secbits(unsigned " bits );
-.sp
-.B "cap_mode_t cap_get_mode(void);"
-.sp
-.BI "const char *cap_mode_name(cap_mode_t " mode );
-.sp
-.BI "int cap_set_mode(cap_mode_t " mode );
-.sp
-.B #include <sys/types.h>
-.sp
-.BI "cap_t cap_get_pid(pid_t " pid );
-.sp
-.BI "int cap_setuid(uid_t " uid );
-.sp
-.BI "int cap_setgroups(gid_t " gid ", size_t " ngroups ", const gid_t " \
-groups );
+.nf
+#include <sys/capability.h>
+
+cap_t cap_get_proc(void);
+int cap_set_proc(cap_t cap_p);
+
+int cap_get_bound(cap_value_t cap);
+CAP_IS_SUPPORTED(cap_value_t cap);
+
+int cap_drop_bound(cap_value_t cap);
+int cap_get_ambient(cap_value_t cap);
+int cap_set_ambient(cap_value_t cap, cap_flag_value_t value);
+int cap_reset_ambient(void);
+CAP_AMBIENT_SUPPORTED();
+
+unsigned cap_get_secbits(void);
+int cap_set_secbits(unsigned bits);
+cap_mode_t cap_get_mode(void);
+const char *cap_mode_name(cap_mode_t mode);
+int cap_prctl(long int pr_cmd, long int arg1, long int arg2,
+	      long int arg3, long int arg4, long int arg5);
+int cap_prctlw(long int pr_cmd, long int arg1, long int arg2,
+	       long int arg3, long int arg4, long int arg5);
+int cap_set_mode(cap_mode_t mode);
+
+#include <sys/types.h>
+
+cap_t cap_get_pid(pid_t pid);
+int cap_setuid(uid_t uid);
+int cap_setgroups(gid_t gid, size_t ngroups, const gid_t groups);
+.fi
 .sp
 Link with \fI\-lcap\fP.
 .SH DESCRIPTION
@@ -172,6 +165,12 @@
 Supported modes are:
 .BR CAP_MODE_NOPRIV ", " CAP_MODE_PURE1E_INIT " and " CAP_MODE_PURE1E .
 .PP
+.BR cap_prctl ()
+can be used to read state via the \fBprctl\fI()\fP system call.
+.PP
+.BR cap_prctlw ()
+can be used to write state via the \fBprctl\fI()\fP system call.
+.PP
 .BR cap_set_mode ()
 can be used to set the desired mode. The permitted capability
 .B CAP_SETPCAP
@@ -250,7 +249,10 @@
 .sp
 When linked this way, due to linker magic, libcap uses
 .BR psx_syscall "(3) and " psx_syscall6 (3)
-to perform state setting system calls.
+to perform state setting system calls. Notably, this also ensures that
+.BI cap_prctlw ()
+can be used to ensure process control bits are shared over all threads
+of a single process.
 .SS capgetp() and capsetp()
 The library also supports the deprecated functions:
 .PP
@@ -269,7 +271,7 @@
 .BR cap_get_pid ().
 .PP
 .BR capsetp ()
-attempts to set the capabilities of the calling porcess or of
+attempts to set the capabilities of the calling process or of
 some other process(es),
 .IR pid .
 Note that setting capabilities of another process is only possible on older
diff --git a/doc/cap_iab.3 b/doc/cap_iab.3
new file mode 100644
index 0000000..a453428
--- /dev/null
+++ b/doc/cap_iab.3
@@ -0,0 +1,164 @@
+.TH CAP_IAB 3 "2021-03-10" "" "Linux Programmer's Manual"
+.SH NAME
+.nf
+#include <sys/capability.h>
+
+cap_iab_t cap_iab_init(void);
+
+cap_iab_t cap_iab_get_proc(void);
+
+int cap_iab_set_proc(cap_iab_t iab);
+
+char *cap_iab_to_text(cap_iab_t iab);
+
+cap_iab_t cap_iab_from_text(const char *text);
+
+cap_flag_value_t cap_iab_get_vector(cap_iab_t iab, cap_iab_vector_t vec,
+    cap_value_t val);
+
+int cap_iab_set_vector(cap_iab_t iab, cap_iab_vector_t vec, cap_value_t val,
+    cap_flag_value_t enable);
+
+int cap_iab_fill(cap_iab_t iab, cap_iab_vector_t vec,
+    cap_t set, cap_flag_t flag);
+
+.fi
+.sp
+Link with \fI\-lcap\fP.
+.SH "DESCRIPTION"
+The functions defined in this man page concern the three naively
+inheritable process capability vectors: Inh, Amb and Bound. This
+\fIIAB\fP 3-tuple of capability vectors, captured in type
+\fIcap_iab_t\fP combine to pass capabilities from one process to
+another through
+.BR execve (2)
+system calls. The convolution rules using the IAB set are a fail over
+set of rules when the executed file has no configured
+\fIfile-capabilities\fP.
+.PP
+There are some constraints enforced by the kernel with respect to the
+three components of an IAB set and the Permitted process capability
+flag. They are: the Inh vector is entirely equal to the process
+Inheritable flag at all times; the the Amb vector contains no more
+capability values than the intersection of the Inh vector and the
+Permitted flag for the process; no Amb value blocked in the Bound
+Vector will survive
+.BR execve (2);
+and the Bound (or \fIblocked\fP) vector is the twos-complement of the
+process bounding set.
+.PP
+In some environments, it is considered desirable to naively inherit
+capabilities. That is pass capabilities, independent of the status of
+the executed binary, from parent to child through exec* system
+calls. The surviving capabilities become the Permitted flag for the
+post-exec process. This method of inheritance differs significantly
+from the handshake inheritance between pre-exec* process and
+file-capability bestowed executable of the traditional capability
+mechanism.
+.PP
+The convolution rules for IAB style inheritance are: I'=I; A'= A & ~B;
+P'=A & ~B. Where P etc are the pre-exec values and P' etc are the
+post-exec values.
+.PP
+With an understanding of these convolution rules, we can explain how
+.BR libcap (3)
+support for the IAB set is managed: the IAB API.
+.PP
+.BR cap_iab_init ()
+returns an empty IAB value. That is a \fImostly-harmless\fP tuple. It
+will not block and capabilities through exec, but it won't bestow any
+either. The returned cap_iab_t should be freed with
+.BR cap_free (3).
+.sp
+.BR cap_iab_get_proc ()
+returns a copy of the IAB value for the current process.  The returned
+cap_iab_t should be freed with
+.BR cap_free (3).
+.sp
+.BR cap_iab_set_proc ()
+can be used to set the IAB value carried by the current process. Such
+a setting will fail if the process is insufficiently capable. The
+process requires CAP_SETPCAP and a superset of P values over the A and
+I vectors.
+.sp
+.BR cap_iab_to_text ()
+will convert an IAB set to a canonical text representation. The
+representation is slightly redundant but libcap will try to generate
+as short a representation as it is able.
+.sp
+.BR cap_iab_from_text ()
+generates an IAB set from a text string (likely generated by the
+previous function). The returned IAB set should be freed with
+.BR cap_free (3).
+.sp
+The text format accepted by
+.BR cap_iab_from_text ()
+is a comma separated list of capability values. Each capability is
+prefixed by nothing (or %) (Inh); ! (Bound); ^ (Amb). Or, some
+combination thereof. Since the Amb vector is constrained to be no
+greater than the Inh set, ^ is equivalent to %^. Further, unless B is
+non-zero, % can be omitted. The following are legal text
+representations: "!%cap_chown" (Bound but Inh),
+"!cap_setuid,^cap_chown" (Bound, Inh+Amb). "cap_setuid,!cap_chown"
+(Inh, Bound). As noted above, this text representation is the syntax
+for the \fIpam_cap.so\fP config file.
+.sp
+.BR cap_iab_get_vector ()
+can be used to determine the specific capability value of an IAB
+vector.
+.sp
+.BR cap_iab_set_vector ()
+can be used to set a specific vector value to the enable setting.
+.BR cap_iab_fill ()
+can be used to wholesale copy a cap_t flag value into the vec vector
+of the IAB set. Copying into Amb in this way may implicitly raise Inh
+values in the IAB set. Similarly copying into the Inh vector may
+implicitly lower Amb values that are not present in the resulting Inh
+vector.
+.SH "ERRORS"
+The functions returning \fIcap_iab_t\fP values or allocated memory in
+the form of a string return NULL on error.
+
+Integer return values are -1 on error and 0 on success.
+
+In the case of error consult \fIerrno\fP.
+.SH "NOTES"
+.PP
+Unlike the traditional \fIcap_t\fP capability set, the
+IAB set, taken together, is incompatible with filesystem capabilities
+created via tools like
+.BR setcap (8).
+That is, the Amb vector of the IAB set is rendered moot when an
+executable with a file capability is executed.
+.PP
+Further, there are libcap
+.BR cap_mode (3)s
+that render the Amb vector and its method of process inheritance
+disabled.
+
+.SH "HISTORY"
+The IAB format for inheritable variants of capabilities was first
+developed as the configuration syntax for the \fIpam_cap.so\fP
+Linux-PAM module in libcap-2.29. It was introduced to extend the
+\fIsimple\fP comma separated list of process Inheritable capabilities,
+that the module could besow on an authenticated process tree, to
+include enforced limits on the Bounding set and introduce support for
+the Amibient set of capability bits.
+
+While the Inheritable and Bounding sets were anticipated by the
+POSIX.1e draft that introduced capabilities, the Ambient set is a
+Linux invention, and incompatible with the POSIX.1e file capability
+model. As such, it was felt that trying to meld together all of the 5
+capability vectors into one text representation was not going to
+work. Instead the \fIpam_cap.so\fP config syntax was generalized into
+a whole set of libcap functions for bundling together all three
+naively inheritable capabilities: the IAB set. The support for this
+debuted in libcap-2.33.
+
+.SH "SEE ALSO"
+.BR libcap (3),
+.BR cap_launch (3),
+.BR cap_init (3),
+.BR capabilities (7)
+and
+.BR errno (3).
diff --git a/doc/cap_iab_fill.3 b/doc/cap_iab_fill.3
new file mode 100644
index 0000000..3e730b1
--- /dev/null
+++ b/doc/cap_iab_fill.3
@@ -0,0 +1 @@
+.so man3/cap_iab.3
diff --git a/doc/cap_iab_from_text.3 b/doc/cap_iab_from_text.3
new file mode 100644
index 0000000..3e730b1
--- /dev/null
+++ b/doc/cap_iab_from_text.3
@@ -0,0 +1 @@
+.so man3/cap_iab.3
diff --git a/doc/cap_iab_get_proc.3 b/doc/cap_iab_get_proc.3
new file mode 100644
index 0000000..3e730b1
--- /dev/null
+++ b/doc/cap_iab_get_proc.3
@@ -0,0 +1 @@
+.so man3/cap_iab.3
diff --git a/doc/cap_iab_get_vector.3 b/doc/cap_iab_get_vector.3
new file mode 100644
index 0000000..3e730b1
--- /dev/null
+++ b/doc/cap_iab_get_vector.3
@@ -0,0 +1 @@
+.so man3/cap_iab.3
diff --git a/doc/cap_iab_init.3 b/doc/cap_iab_init.3
new file mode 100644
index 0000000..3e730b1
--- /dev/null
+++ b/doc/cap_iab_init.3
@@ -0,0 +1 @@
+.so man3/cap_iab.3
diff --git a/doc/cap_iab_set_proc.3 b/doc/cap_iab_set_proc.3
new file mode 100644
index 0000000..3e730b1
--- /dev/null
+++ b/doc/cap_iab_set_proc.3
@@ -0,0 +1 @@
+.so man3/cap_iab.3
diff --git a/doc/cap_iab_set_vector.3 b/doc/cap_iab_set_vector.3
new file mode 100644
index 0000000..3e730b1
--- /dev/null
+++ b/doc/cap_iab_set_vector.3
@@ -0,0 +1 @@
+.so man3/cap_iab.3
diff --git a/doc/cap_iab_to_text.3 b/doc/cap_iab_to_text.3
new file mode 100644
index 0000000..3e730b1
--- /dev/null
+++ b/doc/cap_iab_to_text.3
@@ -0,0 +1 @@
+.so man3/cap_iab.3
diff --git a/doc/cap_init.3 b/doc/cap_init.3
index 362db66..125b529 100644
--- a/doc/cap_init.3
+++ b/doc/cap_init.3
@@ -1,17 +1,17 @@
 .\"
 .\" written by Andrew Main <zefram@dcs.warwick.ac.uk>
 .\"
-.TH CAP_INIT 3 "2008-05-11" "" "Linux Programmer's Manual"
+.TH CAP_INIT 3 "2021-03-06" "" "Linux Programmer's Manual"
 .SH NAME
 cap_init, cap_free, cap_dup \- capability data object storage management
 .SH SYNOPSIS
-.B #include <sys/capability.h>
-.sp
-.B cap_t cap_init(void);
-.sp
-.BI "int cap_free(void *" obj_d );
-.sp
-.BI "cap_t cap_dup(cap_t " cap_p );
+.nf
+#include <sys/capability.h>
+
+cap_t cap_init(void);
+int cap_free(void *obj_d);
+cap_t cap_dup(cap_t cap_p);
+.fi
 .sp
 Link with \fI\-lcap\fP.
 .SH DESCRIPTION
diff --git a/doc/cap_launch.3 b/doc/cap_launch.3
new file mode 100644
index 0000000..6d9b8f7
--- /dev/null
+++ b/doc/cap_launch.3
@@ -0,0 +1,182 @@
+.TH CAP_LAUNCH 3 "2021-08-01" "" "Linux Programmer's Manual"
+.SH NAME
+.nf
+#include <sys/capability.h>
+
+cap_launch_t cap_new_launcher(const char *arg0, const char * const *argv,
+    const char * const *envp);
+
+cap_launch_t cap_func_launcher(int (callback_fn)(void *detail));
+
+void cap_launcher_callback(cap_launch_t attr,
+    int (callback_fn)(void *detail));
+void cap_launcher_set_mode(cap_launch_t attr, cap_mode_t flavor);
+cap_iab_t cap_launcher_set_iab(cap_launch_t attr, cap_iab_t iab);
+void cap_launcher_set_chroot(cap_launch_t attr, const char *chroot);
+
+#include <sys/types.h>
+
+pid_t cap_launch(cap_launch_t attr, void *detail);
+void cap_launcher_setuid(cap_launch_t attr, uid_t uid);
+void cap_launcher_setgroups(cap_launch_t attr, gid_t gid,
+.fi
+.sp
+Link with \fI\-lcap\fP.
+.SH DESCRIPTION
+A launcher provides a mechanism for code to execute a callback
+function and/or a program executable in an environment with a modified
+security context. Essentially it provides a mechanism for a program to
+.BR fork (2)
+a new context from that of the main program manipulate capability and other privileged state in that
+.BR fork (2)d
+process before (optionally)
+.BR execve (2)ing
+a new program. When the application links to \fI\-lpsx\fP this support
+is needed to robustly execute the launched code without modifying the
+privilge of the whole (POSIX semantics honoring) main application.
+.PP
+A launcher is defined by one of these two functions:
+.BR cap_new_launcher ()
+or
+.BR cap_func_launcher ().
+The return value being of opaque type
+.B cap_launch_t
+a return value of NULL implies an error has occurred.
+.PP
+Once defined, a
+.B cap_launch_t
+value can be used with
+.BR cap_launch ()
+to execute that \fIlauncher\fP. In such cases, a non-negative return
+value indicates success: zero meaning success without a program being
+invoked; non-zero being equal to the process ID
+.RB ( pid_t )
+of the newly launched program.
+.PP
+A
+.B cap_launch_t
+occupies allocated memory and should be freed with
+.BR cap_free (3).
+Before being
+.BR cap_free (3)d
+a
+.B cap_value_t
+value may be reused for multiple independent launches. The
+.I detail
+argument to
+.BR cap_launch (),
+in conjunction with the launcher's callback function, can be used to
+customize the invocation of the launch. Care must be taken to leverage
+custom shared memory (see
+.BR mmap (2))
+or some other IPC to return values to the main program via
+.I detail
+since the callback and any subsequent program execution will occur
+outside the main process of the calling application. An example of
+this would be to allocate detail as follows:
+.nf
+
+   const *char[] args = { "echo", "hello", NULL };
+   cap_launch_t cmd = cap_new_launcher("/usr/bin/echo", args, NULL);
+   int *detail = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, 
+                      MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+   cap_launcher_callback(cmd, &answer_detail_fn);
+   *detail = 41;
+   pid_t pid = cap_launch(cmd, detail);
+   printf("launcher callback set detail to %d\\n", *detail);
+   munmap(detail, sizeof(int));
+
+.if
+.PP
+Unless modified by the callback function, the launched code will
+execute with the capability and other security context of the
+application.
+
+If the callback function returns anything other than zero, a
+.BR cap_launch ()
+will be aborted. If detail of the failure is important to the caller,
+it should be communicated via the
+.I detail
+argument.
+
+The following functions can be used to instruct the launcher to modify
+the security state of the invoked program without altering the state
+of the calling program. Such modifications must be performed prior to
+calling \fBcap_launch\fP() if they are to have the desired
+effect. Further, they are only invoked after any installed callback
+has completed. For example, one can drop or modify capabilities,
+\fIjust\fP for executing a file.
+.PP
+The following functions instruct the launcher to do some common tasks
+of this sort (note some require permitted capability bits to succeed):
+.sp
+.BR cap_launcher_callback ()
+can be used to install or replace the currently installed callback
+function of the launcher.
+.sp
+.BR cap_launcher_set_mode ()
+can be used to ensure that the libcap-mode of the launched program is
+the desired one.
+.sp
+.BR cap_launcher_set_iab ()
+This function returns the \fBcap_iab_t\fP previously associated with
+the launcher. Calling this function with an IAB value of NULL will
+configure the launcher to not set an IAB value (the default).  See
+\fBcap_iab\fP(3) for details on the IAB set. Note, the launcher is
+associated directly with the supplied \fIiab\fP value, and does not
+make a copy of it. Set with NULL to regain control over the memory
+associated with that IAB value, otherwise the IAB value will be
+\fBcap_free\fI()\fP'd when the launcher is.
+.sp
+.BR cap_launcher_set_chroot ()
+This function causes the launched program executable to be invoked
+inside a chroot \fIroot\fP directory.
+.sp
+.BR cap_launcher_setuid ()
+This function causes the launched program executable to be invoked
+with the specified user identifier (\fIuid_t\fP).
+.sp
+.BR cap_launcher_setgroups ()
+This function causes the launched program executable to be invoked
+with the specified primary and supplementary group IDs.
+.sp
+.PP
+Note, if any of the launcher enhancements made by the above functions
+should fail to take effect (typically for a lack of sufficient
+privilege), the launch will fail and return -1.
+
+.SH "ERRORS"
+A return of NULL for a
+.B cap_launch_t
+should be considered an error.
+.PP
+.BR cap_launch ()
+returns -1 in the case of an error.
+.PP
+In all such cases consult
+.BR errno (3)
+for further details.
+.SH "HISTORY"
+The \fBcap_launch\fP() family of functions were introduced in libcap
+2.33. It primarily addresses a complexity with \fI-lpsx\fP linked
+pthreads(7) applications that use capabilities but also honor POSIX
+semantics.
+
+Using \fI\-lcap\fP and \fI\-lpthread\fP together without the POSIX
+semantics support from \fI\-lpsx\fP introduces a subtle class of
+exposure to privilege escalation. (See
+https://sites.google.com/site/fullycapable/who-ordered-libpsx for an
+explanation.)
+.SH "SEE ALSO"
+.BR libpsx (3),
+.BR psx_syscall (3),
+.BR libcap (3),
+.BR cap_mode (3),
+.BR cap_iab (3),
+.BR capabilities (7),
+.BR errno (3),
+.BR fork (2),
+.BR mmap (2),
+.BR chroot (2),
+and
+.BR munmap (2).
diff --git a/doc/cap_launcher_callback.3 b/doc/cap_launcher_callback.3
new file mode 100644
index 0000000..4f072fc
--- /dev/null
+++ b/doc/cap_launcher_callback.3
@@ -0,0 +1 @@
+.so man3/cap_launch.3
diff --git a/doc/cap_launcher_set_chroot.3 b/doc/cap_launcher_set_chroot.3
new file mode 100644
index 0000000..4f072fc
--- /dev/null
+++ b/doc/cap_launcher_set_chroot.3
@@ -0,0 +1 @@
+.so man3/cap_launch.3
diff --git a/doc/cap_launcher_set_iab.3 b/doc/cap_launcher_set_iab.3
new file mode 100644
index 0000000..4f072fc
--- /dev/null
+++ b/doc/cap_launcher_set_iab.3
@@ -0,0 +1 @@
+.so man3/cap_launch.3
diff --git a/doc/cap_launcher_set_mode.3 b/doc/cap_launcher_set_mode.3
new file mode 100644
index 0000000..4f072fc
--- /dev/null
+++ b/doc/cap_launcher_set_mode.3
@@ -0,0 +1 @@
+.so man3/cap_launch.3
diff --git a/doc/cap_launcher_setgroups.3 b/doc/cap_launcher_setgroups.3
new file mode 100644
index 0000000..4f072fc
--- /dev/null
+++ b/doc/cap_launcher_setgroups.3
@@ -0,0 +1 @@
+.so man3/cap_launch.3
diff --git a/doc/cap_launcher_setuid.3 b/doc/cap_launcher_setuid.3
new file mode 100644
index 0000000..4f072fc
--- /dev/null
+++ b/doc/cap_launcher_setuid.3
@@ -0,0 +1 @@
+.so man3/cap_launch.3
diff --git a/doc/cap_mode.3 b/doc/cap_mode.3
new file mode 100644
index 0000000..65ea3e4
--- /dev/null
+++ b/doc/cap_mode.3
@@ -0,0 +1 @@
+.so man3/cap_get_proc.3
diff --git a/doc/cap_new_launcher.3 b/doc/cap_new_launcher.3
new file mode 100644
index 0000000..4f072fc
--- /dev/null
+++ b/doc/cap_new_launcher.3
@@ -0,0 +1 @@
+.so man3/cap_launch.3
diff --git a/doc/capability.notes b/doc/capability.notes
index b1e5245..4087c80 100644
--- a/doc/capability.notes
+++ b/doc/capability.notes
@@ -22,7 +22,7 @@
 user can be made the owner of all of the system directories on your
 system and critical system binaries too.
 
-Why is this a good idea? In a simple case, the CAP_FUSER capabilty is
+Why is this a good idea? In a simple case, the CAP_FUSER capability is
 required for the superuser to delete files owned by a non-root user in
 a 'sticky-bit' protected non-root owned directory. Thus, the sticky
 bit can help you protect the /lib/ directory from an compromized
diff --git a/doc/capsh.1 b/doc/capsh.1
index ab20c44..e309438 100644
--- a/doc/capsh.1
+++ b/doc/capsh.1
@@ -1,4 +1,4 @@
-.TH CAPSH 1 "2020-10-27" "libcap 2" "User Commands"
+.TH CAPSH 1 "2021-07-01" "libcap 2" "User Commands"
 .SH NAME
 capsh \- capability shell wrapper
 .SH SYNOPSIS
@@ -21,6 +21,9 @@
 .B \-\-print
 Display prevailing capability and related state.
 .TP
+.B \-\-current
+Display prevailing capability state, 1e capabilities and IAB vector.
+.TP
 .BI \-\- " [args]"
 Execute
 .B /bin/bash
@@ -40,7 +43,7 @@
 occurs after a
 .BI \-\-chroot= /some/path
 argument the PATH located binary may not be resolve to the same binary
-as that running initially. This behavior is an intented feature as it
+as that running initially. This behavior is an intended feature as it
 can complete the chroot transition.
 .TP
 .BI \-\-caps= cap-set
@@ -214,6 +217,16 @@
 .B capsh
 program exits with status 1.
 .TP
+.BI \-\-explain= cap_xxx
+Give a brief textual description of what privileges the specified
+capability makes available to a running program. Note, instead of
+\fIcap_xxx\fP, one can provide a decimal number and \fBcapsh\fP will
+look up the corresponding capability's description.
+.TP
+.BI \-\-suggest= phrase
+Scan each of the textual descriptions of capabilities, known to
+\fBcapsh\fP, and display all descriptions that include \fIphrase\fP.
+.TP
 .BI \-\-decode= N
 This is a convenience feature. If you look at
 .B /proc/1/status
@@ -266,6 +279,11 @@
 .B xxx
 raised.
 .TP
+.BI \-\-iab= xxx
+Attempts to set the IAB tuple of inheritable capability vectors.
+The text conventions used for \fIxxx\fP are those of
+.BR cap_iab_from_text (3).
+.TP
 .BI \-\-addamb= xxx
 Adds the specified ambient capability to the running process.
 .TP
@@ -290,6 +308,8 @@
 .SH "SEE ALSO"
 .BR libcap (3),
 .BR getcap (8),
-.BR setcap (8)
+.BR setcap (8),
+.BR cap_from_text (3),
+.BR cap_iab (3)
 and
 .BR capabilities (7).
diff --git a/doc/libcap.3 b/doc/libcap.3
index 730e275..b8c8520 100644
--- a/doc/libcap.3
+++ b/doc/libcap.3
@@ -1,4 +1,4 @@
-.TH LIBCAP 3 "2020-01-07" "" "Linux Programmer's Manual"
+.TH LIBCAP 3 "2021-03-06" "" "Linux Programmer's Manual"
 .SH NAME
 cap_clear, cap_clear_flag, cap_compare, cap_copy_ext, cap_copy_int, \
 cap_free, cap_from_name, cap_from_text, cap_get_fd, cap_get_file, \
@@ -7,54 +7,36 @@
 cap_get_pid, cap_dup \- capability data object manipulation
 .SH SYNOPSIS
 .nf
-.B #include <sys/capability.h>
-.sp
-.BI "int cap_clear(cap_t " cap_p );
-.sp
-.BI "int cap_clear_flag(cap_t " cap_p ", cap_flag_t " flag ");"
-.sp
-.BI "int cap_compare(cap_t " cap_a ", cap_t " cap_b ");"
-.sp
-.BI "ssize_t cap_copy_ext(void *" ext_p ", cap_t " cap_p ", ssize_t " size );
-.sp
-.BI "cap_t cap_copy_int(const void *" ext_p );
-.sp
-.BI "int cap_free(void *" obj_d );
-.sp
-.BI "int cap_from_name(const char *" name ", cap_value_t *" cap_p );
-.sp
-.BI "cap_t cap_from_text(const char *" buf_p );
-.sp
-.BI "cap_t cap_get_fd(int " fd );
-.sp
-.BI "cap_t cap_get_file(const char *" path_p );
-.sp
-.BI "int cap_get_flag(cap_t " cap_p ", cap_value_t " cap ,
-.BI "                 cap_flag_t " flag ", cap_flag_value_t *" value_p ");"
-.sp
-.B #include <sys/types.h>
-.BI "cap_t cap_get_pid(pid_t " pid );
-.sp
-.B "cap_t cap_get_proc(void);"
-.sp
-.BI "int cap_set_fd(int " fd ", cap_t " caps );
-.sp
-.BI "int cap_set_file(const char *" path_p ", cap_t " cap_p );
-.sp
-.sp
-.BI "int cap_set_flag(cap_t " cap_p ", cap_flag_t " flag ", int " ncap ,
-.BI "                 const cap_value_t *" caps ", cap_flag_value_t " value ");"
-.BI "int cap_set_proc(cap_t " cap_p );
-.sp
-.BI "ssize_t cap_size(cap_t " cap_p );
-.sp
-.BI "char *cap_to_name(cap_value_t " cap );
-.sp
-.BI "char *cap_to_text(cap_t " caps ", ssize_t *" length_p );
-.sp
-.BI "cap_t cap_get_pid(pid_t " pid );
-.sp
-.BI "cap_t cap_dup(cap_t " cap_p );
+#include <sys/capability.h>
+
+int cap_clear(cap_t cap_p);
+int cap_clear_flag(cap_t cap_p, cap_flag_t flag);
+int cap_compare(cap_t cap_a, cap_t cap_b);
+ssize_t cap_copy_ext(void *ext_p, cap_t cap_p, ssize_t size);
+cap_t cap_copy_int(const void *ext_p);
+int cap_free(void *obj_d);
+int cap_from_name(const char *name, cap_value_t *cap_p);
+cap_t cap_from_text(const char *buf_p);
+cap_t cap_get_fd(int fd);
+cap_t cap_get_file(const char *path_p);
+int cap_get_flag(cap_t cap_p, cap_value_t cap ,
+                 cap_flag_t flag, cap_flag_value_t *value_p);
+
+#include <sys/types.h>
+
+cap_t cap_get_pid(pid_t pid);
+cap_t cap_get_proc(void);
+int cap_set_fd(int fd, cap_t caps);
+int cap_set_file(const char *path_p, cap_t cap_p);
+int cap_set_flag(cap_t cap_p, cap_flag_t flag, int ncap ,
+                 const cap_value_t *caps, cap_flag_value_t value);
+int cap_set_proc(cap_t cap_p);
+ssize_t cap_size(cap_t cap_p);
+char *cap_to_name(cap_value_t cap);
+char *cap_to_text(cap_t caps, ssize_t *length_p);
+cap_t cap_get_pid(pid_t pid);
+cap_t cap_dup(cap_t cap_p);
+.fi
 .sp
 Link with \fI\-lcap\fP.
 .fi
@@ -102,9 +84,14 @@
 and
 .BR cap_compare ().
 .SH "REPORTING BUGS"
-Please report bugs via:
+The
+.B libcap
+library is distributed from
+https://sites.google.com/site/fullycapable/ where the release notes
+may already cover recent issues.  Please report newly discovered bugs
+via:
 .TP
-https://bugzilla.kernel.org/buglist.cgi?component=libcap&list_id=1047723&product=Tools&resolution=---
+https://bugzilla.kernel.org/buglist.cgi?component=libcap&list_id=1090757
 .SH "SEE ALSO"
 .BR cap_clear (3),
 .BR cap_copy_ext (3),
@@ -113,5 +100,7 @@
 .BR cap_get_proc (3),
 .BR cap_init (3),
 .BR capabilities (7),
-.BR getpid (2)
+.BR getpid (2),
 .BR capsh (1)
+and
+.BR libpsx (3).
diff --git a/doc/libpsx.3 b/doc/libpsx.3
index 61baa88..4ba306b 100644
--- a/doc/libpsx.3
+++ b/doc/libpsx.3
@@ -1,13 +1,13 @@
-.TH LIBPSX 3 "2021-01-31" "" "Linux Programmer's Manual"
+.TH LIBPSX 3 "2021-03-06" "" "Linux Programmer's Manual"
 .SH NAME
 psx_syscall3, psx_syscall6 \- POSIX semantics for system calls
 .SH SYNOPSIS
 .nf
-.B #include <sys/psx_syscall.h>
-.sp
-.BI "long int psx_syscall3(long int" " syscall_nr, " "long int" " arg1, " "long int" " arg2, " "long int" " arg3);"
-.sp
-.BI "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);"
+#include <sys/psx_syscall.h>
+
+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);
+.fi
 .sp
 Link with one of these:
 .sp
@@ -80,9 +80,14 @@
 .TP
 https://sites.google.com/site/fullycapable/who-ordered-libpsx
 .SH "REPORTING BUGS"
-Please report bugs via:
+The
+.B libpsx
+library is distributed from
+https://sites.google.com/site/fullycapable/ where the release notes
+may already cover recent issues.  Please report newly discovered bugs
+via:
 .TP
-https://bugzilla.kernel.org/buglist.cgi?component=libcap&list_id=1047723&product=Tools&resolution=---
+https://bugzilla.kernel.org/buglist.cgi?component=libcap&list_id=1090757
 .SH SEE ALSO
 .BR libcap (3),
 .BR pthreads "(7) and"
diff --git a/doc/old/_setfilecap.2 b/doc/old/_setfilecap.2
index 6a0538c..3c9e374 100644
--- a/doc/old/_setfilecap.2
+++ b/doc/old/_setfilecap.2
@@ -93,7 +93,7 @@
 .TP
 .SB ELOOP
 .I filename
-containes a circular reference (via symlinks).
+contains a circular reference (via symlinks).
 .TP
 .SB EBADF
 .I fd
diff --git a/doc/values/24.txt b/doc/values/24.txt
index bb3bac7..4911e50 100644
--- a/doc/values/24.txt
+++ b/doc/values/24.txt
@@ -12,5 +12,3 @@
   - override the maximum number of consoles for console
     allocation
   - override the maximum number of keymaps
-
-  
diff --git a/doc/values/31.txt b/doc/values/31.txt
index 163b048..ae97df2 100644
--- a/doc/values/31.txt
+++ b/doc/values/31.txt
@@ -1 +1,6 @@
 Allows a process to set capabilities on files.
+Permits a process to uid_map the uid=0 of the
+parent user namespace into that of the child
+namespace. Also, permits a process to override
+securebits locks through user namespace
+creation.
diff --git a/doc/values/5.txt b/doc/values/5.txt
index 1097fe0..c4ded8e 100644
--- a/doc/values/5.txt
+++ b/doc/values/5.txt
@@ -1,3 +1,3 @@
-Allows a process to sent a kill(2) signal to any other
+Allows a process to send a kill(2) signal to any other
 process - overriding the limitation that there be a
 [E]UID match between source and target process.
diff --git a/doc/values/7.txt b/doc/values/7.txt
index 432a97e..fbc1240 100644
--- a/doc/values/7.txt
+++ b/doc/values/7.txt
@@ -1,5 +1,5 @@
 Allows a process to freely manipulate its own UIDs:
-  - arbitraily set the UID, EUID, REUID and RESUID
+  - arbitrarily set the UID, EUID, REUID and RESUID
     values
   - allows the forging of UID credentials passed over a
     socket
diff --git a/doc/values/8.txt b/doc/values/8.txt
index d6d7c1f..d7654f0 100644
--- a/doc/values/8.txt
+++ b/doc/values/8.txt
@@ -14,6 +14,6 @@
 default, as its unsuppressed behavior was not
 auditable: it could asynchronously grant its own
 Permitted capabilities to and remove capabilities from
-other processes arbitraily. The former leads to
+other processes arbitrarily. The former leads to
 undefined behavior, and the latter is better served by
 the kill system call.]
diff --git a/go/.gitignore b/go/.gitignore
index c0a9737..7b811c9 100644
--- a/go/.gitignore
+++ b/go/.gitignore
@@ -10,5 +10,7 @@
 setid
 gowns
 ok
-pkg
-src
+vendor
+go.sum
+PSXGOPACKAGE
+CAPGOPACKAGE
diff --git a/go/Makefile b/go/Makefile
index 757844a..6b69cbe 100644
--- a/go/Makefile
+++ b/go/Makefile
@@ -1,24 +1,21 @@
 # Building the libcap/{cap.psx} Go packages, and examples.
 #
-# Note, we use symlinks to construct a GOPATH friendly src tree. The
+# Note, we use symlinks to construct a go.mod build friendly tree. The
 # packages themselves are intended to be (ultimately) found via proxy
 # as "kernel.org/pub/linux/libs/security/libcap/cap" and
 # "kernel.org/pub/linux/libs/security/libcap/psx". However, to
 # validate their use on these paths, we fake such a structure in the
-# build tree with symlinks.
+# build tree with symlinks and a vendor directory.
 
 topdir=$(realpath ..)
 include $(topdir)/Make.Rules
 
-GOPATH=$(realpath .)
 IMPORTDIR=kernel.org/pub/linux/libs/security/libcap
 PKGDIR=pkg/$(GOOSARCH)/$(IMPORTDIR)
-PSXGOPACKAGE=$(PKGDIR)/psx.a
-CAPGOPACKAGE=$(PKGDIR)/cap.a
 
 DEPS=../libcap/libcap.a ../libcap/libpsx.a
 
-all: $(PSXGOPACKAGE) $(CAPGOPACKAGE) web setid gowns compare-cap try-launching psx-signals
+all: PSXGOPACKAGE CAPGOPACKAGE web setid gowns compare-cap try-launching psx-signals
 
 $(DEPS):
 	make -C ../libcap all
@@ -26,71 +23,80 @@
 ../progs/tcapsh-static:
 	make -C ../progs tcapsh-static
 
-src/$(IMPORTDIR)/psx:
-	mkdir -p "src/$(IMPORTDIR)"
-	ln -s $(topdir)/psx $@
+vendor/$(IMPORTDIR) vendor/modules.txt:
+	mkdir -p "vendor/$(IMPORTDIR)"
+	echo "# $(IMPORTDIR)/psx v$(GOMAJOR).$(VERSION).$(MINOR)" > vendor/modules.txt
+	echo "$(IMPORTDIR)/psx" >> vendor/modules.txt
+	echo "# $(IMPORTDIR)/cap v$(GOMAJOR).$(VERSION).$(MINOR)" >> vendor/modules.txt
+	echo "$(IMPORTDIR)/cap" >> vendor/modules.txt
 
-src/$(IMPORTDIR)/cap:
-	mkdir -p "src/$(IMPORTDIR)"
-	ln -s $(topdir)/cap $@
+vendor/$(IMPORTDIR)/psx: vendor/modules.txt
+	ln -sf $(topdir)/psx vendor/$(IMPORTDIR)
+	touch ../psx
 
-$(topdir)/libcap/cap_names.h: $(DEPS)
-	make -C $(topdir)/libcap all
+vendor/$(IMPORTDIR)/cap: vendor/modules.txt
+	ln -sf $(topdir)/cap vendor/$(IMPORTDIR)
+	touch ../cap
 
-good-names.go: $(topdir)/libcap/cap_names.h src/$(IMPORTDIR)/cap  mknames.go
-	$(GO) run mknames.go --header=$< --textdir=$(topdir)/doc/values | gofmt > $@ || rm -f $@
+$(topdir)/libcap/cap_names.h:
+	make -C $(topdir)/libcap cap_names.h
+
+good-names.go: $(topdir)/libcap/cap_names.h vendor/$(IMPORTDIR)/cap mknames.go
+	CC="$(CC)" $(GO) run -mod=vendor mknames.go --header=$< --textdir=$(topdir)/doc/values | gofmt > $@ || rm -f $@
 	diff -u ../cap/names.go $@
 
-$(PSXGOPACKAGE): src/$(IMPORTDIR)/psx ../psx/*.go $(DEPS)
-	mkdir -p pkg
-	GO111MODULE=off CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH="$(GOPATH)" $(GO) install $(IMPORTDIR)/psx
+PSXGOPACKAGE: vendor/$(IMPORTDIR)/psx ../psx/*.go $(DEPS)
+	touch $@
 
-$(CAPGOPACKAGE): src/$(IMPORTDIR)/cap ../cap/*.go good-names.go $(PSXGOPACKAGE)
-	GO111MODULE=off CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH=$(GOPATH) $(GO) install $(IMPORTDIR)/cap
+CAPGOPACKAGE: vendor/$(IMPORTDIR)/cap ../cap/*.go good-names.go $(PSXGOPACKAGE)
+	touch $@
 
 # Compiles something with this package to compare it to libcap. This
 # tests more when run under sudotest (see ../progs/quicktest.sh for that).
-compare-cap: compare-cap.go $(CAPGOPACKAGE)
-	GO111MODULE=off CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" GOPATH=$(GOPATH) $(GO) build $<
+compare-cap: compare-cap.go CAPGOPACKAGE
+	CC="$(CC)" $(CGO_LDFLAGS_ALLOW) CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -mod=vendor $<
 
-web: ../goapps/web/web.go $(CAPGOPACKAGE)
-	GO111MODULE=off CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH=$(GOPATH) $(GO) build -o $@ $<
+web: ../goapps/web/web.go CAPGOPACKAGE
+	CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) build -mod=vendor -o $@ $<
 ifeq ($(RAISE_GO_FILECAP),yes)
 	make -C ../progs setcap
 	sudo ../progs/setcap cap_setpcap,cap_net_bind_service=p web
 	@echo "NOTE: RAISED cap_setpcap,cap_net_bind_service ON web binary"
 endif
 
-setid: ../goapps/setid/setid.go $(CAPGOPACKAGE) $(PSXGOPACKAGE)
-	GO111MODULE=off CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH=$(GOPATH) $(GO) build -o $@ $<
+setid: ../goapps/setid/setid.go CAPGOPACKAGE PSXGOPACKAGE
+	CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) build -mod=vendor -o $@ $<
 
-gowns: ../goapps/gowns/gowns.go $(CAPGOPACKAGE)
-	GO111MODULE=off CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH=$(GOPATH) $(GO) build -o $@ $<
+gowns: ../goapps/gowns/gowns.go CAPGOPACKAGE
+	CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) build -mod=vendor -o $@ $<
 
 ok: ok.go
-	GO111MODULE=off CGO_ENABLED=0 GOPATH=$(GOPATH) $(GO) build $<
+	CC="$(CC)" CGO_ENABLED=0 $(GO) build -mod=vendor $<
 
-try-launching: try-launching.go $(CAPGOPACKAGE) ok
-	GO111MODULE=off CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH=$(GOPATH) $(GO) build $<
+try-launching: try-launching.go CAPGOPACKAGE ok
+	CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) build -mod=vendor $<
 ifeq ($(CGO_REQUIRED),0)
-	GO111MODULE=off CGO_ENABLED="1" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH=$(GOPATH) $(GO) build -o $@-cgo $<
+	CC="$(CC)" CGO_ENABLED="1" $(CGO_LDFLAGS_ALLOW) $(GO) build -mod=vendor -o $@-cgo $<
 endif
 
-psx-signals: psx-signals.go $(PSXGOPACKAGE)
-	GO111MODULE=off CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" GOPATH=$(GOPATH) $(GO) build $<
+psx-signals: psx-signals.go PSXGOPACKAGE
+	CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -mod=vendor $<
+
 ifeq ($(CGO_REQUIRED),0)
-	GO111MODULE=off CGO_ENABLED="1" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" GOPATH=$(GOPATH) $(GO) build -o $@-cgo $<
+psx-signals-cgo: psx-signals.go PSXGOPACKAGE
+	CC="$(CC)" CGO_ENABLED="1" $(CGO_LDFLAGS_ALLOW) CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -mod=vendor -o $@ $<
 endif
 
-b210613: b210613.go $(CAPGOPACKAGE)
-	GO111MODULE=off CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" GOPATH=$(GOPATH) $(GO) build $<
+b210613: b210613.go CAPGOPACKAGE
+	CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -mod=vendor $<
 
 test: all
-	GO111MODULE=off CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH="$(GOPATH)" $(GO) test $(IMPORTDIR)/psx
-	GO111MODULE=off CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH="$(GOPATH)" $(GO) test $(IMPORTDIR)/cap
+	CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) test -mod=vendor $(IMPORTDIR)/psx
+	CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) test -mod=vendor $(IMPORTDIR)/cap
 	LD_LIBRARY_PATH=../libcap ./compare-cap
 	./psx-signals
 ifeq ($(CGO_REQUIRED),0)
+	$(MAKE) psx-signals-cgo
 	./psx-signals-cgo
 endif
 	./setid --caps=false
@@ -114,10 +120,10 @@
 install: all
 	rm -rf $(FAKEROOT)$(GOPKGDIR)/$(IMPORTDIR)/psx
 	mkdir -p $(FAKEROOT)$(GOPKGDIR)/$(IMPORTDIR)/psx
-	install -m 0644 src/$(IMPORTDIR)/psx/* $(FAKEROOT)$(GOPKGDIR)/$(IMPORTDIR)/psx
+	install -m 0644 vendor/$(IMPORTDIR)/psx/* $(FAKEROOT)$(GOPKGDIR)/$(IMPORTDIR)/psx
 	mkdir -p $(FAKEROOT)$(GOPKGDIR)/$(IMPORTDIR)/cap
 	rm -rf $(FAKEROOT)$(GOPKGDIR)/$(IMPORTDIR)/cap/*
-	install -m 0644 src/$(IMPORTDIR)/cap/* $(FAKEROOT)$(GOPKGDIR)/$(IMPORTDIR)/cap
+	install -m 0644 vendor/$(IMPORTDIR)/cap/* $(FAKEROOT)$(GOPKGDIR)/$(IMPORTDIR)/cap
 
 clean:
 	rm -f *.o *.so *~ mknames ok good-names.go
@@ -125,4 +131,4 @@
 	rm -f compare-cap try-launching try-launching-cgo
 	rm -f $(topdir)/cap/*~ $(topdir)/psx/*~
 	rm -f b210613 psx-signals psx-signals-cgo
-	rm -fr pkg src
+	rm -fr vendor CAPGOPACKAGE PSXGOPACKAGE go.sum
diff --git a/go/compare-cap.go b/go/compare-cap.go
index 4424ebe..f2a7d6b 100644
--- a/go/compare-cap.go
+++ b/go/compare-cap.go
@@ -184,7 +184,7 @@
 		}
 	}
 
-	// The current process is now without any access to privelege.
+	// The current process is now without any access to privilege.
 }
 
 func main() {
@@ -257,12 +257,12 @@
 	}
 
 	// Validate that it can be imported in binary in C
-	iC := C.cap_copy_int(unsafe.Pointer(&iE[0]))
+	iC := C.cap_copy_int_check(unsafe.Pointer(&iE[0]), C.ssize_t(len(iE)))
 	if iC == nil {
 		log.Fatal("c failed to import go binary")
 	}
 	defer C.cap_free(unsafe.Pointer(iC))
-	fC := C.cap_to_text(cC, &tCLen)
+	fC := C.cap_to_text(iC, &tCLen)
 	if fC == nil {
 		log.Fatal("basic c init caps -> text failure")
 	}
diff --git a/go/go.mod b/go/go.mod
new file mode 100644
index 0000000..4c49252
--- /dev/null
+++ b/go/go.mod
@@ -0,0 +1,8 @@
+module main
+
+go 1.11
+
+require (
+	kernel.org/pub/linux/libs/security/libcap/cap v1.2.53
+	kernel.org/pub/linux/libs/security/libcap/psx v1.2.53
+)
diff --git a/goapps/gowns/go.mod b/goapps/gowns/go.mod
index bc534af..92de262 100644
--- a/goapps/gowns/go.mod
+++ b/goapps/gowns/go.mod
@@ -2,4 +2,4 @@
 
 go 1.15
 
-require kernel.org/pub/linux/libs/security/libcap/cap v0.2.48
+require kernel.org/pub/linux/libs/security/libcap/cap v1.2.53
diff --git a/goapps/gowns/gowns.go b/goapps/gowns/gowns.go
index b9a14cd..3d26c34 100644
--- a/goapps/gowns/gowns.go
+++ b/goapps/gowns/gowns.go
@@ -1,5 +1,10 @@
 // Program gowns is a small program to explore and demonstrate using
 // Go to Wrap a child in a NameSpace under Linux.
+//
+// Note, this program is under active development and should not be
+// considered stable. That is, it is more a worked example and may
+// change command line arguments and behavior from release to release.
+// Should it become stable, I'll remove this comment.
 package main
 
 import (
@@ -119,7 +124,6 @@
 
 	base++
 	for _, next := range ranges(ids) {
-		fmt.Println("next:", next)
 		list = append(list,
 			syscall.SysProcIDMap{
 				ContainerID: base,
diff --git a/goapps/setid/go.mod b/goapps/setid/go.mod
index cd2282d..02061c2 100644
--- a/goapps/setid/go.mod
+++ b/goapps/setid/go.mod
@@ -3,6 +3,6 @@
 go 1.11
 
 require (
-	kernel.org/pub/linux/libs/security/libcap/cap v0.2.48
-	kernel.org/pub/linux/libs/security/libcap/psx v0.2.48
+	kernel.org/pub/linux/libs/security/libcap/cap v1.2.53
+	kernel.org/pub/linux/libs/security/libcap/psx v1.2.53
 )
diff --git a/goapps/web/README b/goapps/web/README
index cc3c609..cbabd5d 100644
--- a/goapps/web/README
+++ b/goapps/web/README
@@ -10,7 +10,7 @@
 
 A more complete walk through of what this code does is provided here:
 
-   https://sites.google.com/site/fullycapable/building-go-programs-that-manipulate-capabilities
+   https://sites.google.com/site/fullycapable/getting-started-with-go/building-go-programs-that-manipulate-capabilities
 
 Go compilers prior to go1.11.13 are not expected to work. Report more
 recent issues to:
diff --git a/goapps/web/go.mod b/goapps/web/go.mod
index f7ae28b..4ca6b0e 100644
--- a/goapps/web/go.mod
+++ b/goapps/web/go.mod
@@ -2,4 +2,4 @@
 
 go 1.11
 
-require kernel.org/pub/linux/libs/security/libcap/cap v0.2.48
+require kernel.org/pub/linux/libs/security/libcap/cap v1.2.53
diff --git a/goapps/web/web.go b/goapps/web/web.go
index d184e97..c96e745 100644
--- a/goapps/web/web.go
+++ b/goapps/web/web.go
@@ -1,26 +1,17 @@
-// Progam web provides an example of a webserver using capabilities to
+// Program web provides an example of a webserver using capabilities to
 // bind to a privileged port, and then drop all capabilities before
 // handling the first web request.
 //
-// This program cannot work reliably as a pure Go application without
-// the equivalent of the Go runtime patch that adds a POSIX semantics
-// wrapper around the system calls that change per-thread security
-// state. A patch for the pure Go compiler/runtime to add this support
-// is available here [2019-12-14]:
+// This program can be compiled CGO_ENABLED=0 with the go1.16+
+// toolchain.
 //
-//    https://go-review.googlesource.com/c/go/+/210639/
+// Go versions prior to 1.16 use some cgo support provided by the
+// "kernel.org/pub/linux/libs/security/libcap/psx" package.
 //
-// Until that patch, or something like it, is absorbed into the Go
-// runtime the only way to get capabilities to work reliably on the Go
-// runtime is to use something like libpsx via CGo to do capability
-// setting syscalls in C with POSIX semantics. As of this build of the
-// Go "kernel.org/pub/linux/libs/security/libcap/cap" package,
-// courtesy of the "kernel.org/pub/linux/libs/security/libcap/psx"
-// package, this is how things work.
-//
-// To set this up, compile and empower this binary as follows (read
-// over the detail in the psx package description if this doesn't
-// 'just' work):
+// To set this up, compile and empower this binary as follows (the
+// README contains a pointer to a full writeup for building this
+// package - go versions prior to 1.15 need some environment variable
+// workarounds):
 //
 //   go build web.go
 //   sudo setcap cap_setpcap,cap_net_bind_service=p web
diff --git a/gomods.sh b/gomods.sh
new file mode 100755
index 0000000..890cccd
--- /dev/null
+++ b/gomods.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+version="${1}"
+if [[ -z "${version}" ]]; then
+    echo "usage: supply a cap/psx module version to target"
+    exit 1
+fi
+
+for x in $(find . -name 'go.mod'); do
+    sed -i -e 's@kernel.org/\([^ ]*\) v.*$@kernel.org/\1 '"${version}@" "${x}"
+done
diff --git a/kdebug/Makefile b/kdebug/Makefile
index c710050..0e8c11f 100644
--- a/kdebug/Makefile
+++ b/kdebug/Makefile
@@ -1,9 +1,17 @@
 topdir=$(shell pwd)/..
 include ../Make.Rules
 
-test:
+test: exit
+	rm -f interactive
 	./test-kernel.sh
 
+shell: exit
+	touch interactive
+	./test-kernel.sh
+
+exit: exit.c
+	$(CC) -O2 $< -o $@ --static
+
 all:
 	@echo cd to kdebug to test a kernel build
 
@@ -11,4 +19,4 @@
 
 clean:
 	$(LOCALCLEAN)
-	rm -f fs.conf initramfs.img
+	rm -f fs.conf initramfs.img exit interactive
diff --git a/kdebug/exit.c b/kdebug/exit.c
new file mode 100644
index 0000000..a83232d
--- /dev/null
+++ b/kdebug/exit.c
@@ -0,0 +1,36 @@
+/*
+ * See https://stackoverflow.com/questions/42208228/how-to-automatically-close-the-execution-of-the-qemu-after-end-of-process
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/io.h>
+#include <unistd.h>
+
+#define SHUTDOWN_PORT 0x604
+#define EXIT_PORT     0x501
+
+static void clean_exit(void) {
+    ioperm(SHUTDOWN_PORT, 16, 1);
+    outw(0x2000, SHUTDOWN_PORT);
+}
+
+int main(int argc, char **argv) {
+    int status;
+    if (argc != 2) {
+	clean_exit();
+    }
+    status = atoi(argv[1]);
+    printf("exiting with status %d (in three seconds)\n", status);
+    sleep(3);
+    if (!status) {
+	clean_exit();
+    }
+    ioperm(EXIT_PORT, 8, 1);
+    /*
+     * status returned is 1+(2*orig_status)
+     */
+    outb(status-1, EXIT_PORT);
+    printf("didn't exit.. did you include '-device isa-debug-exit'"
+	   " in qemu command?\n");
+    exit(1);
+}
diff --git a/kdebug/test-init.sh b/kdebug/test-init.sh
index 4b55b51..849d9c7 100644
--- a/kdebug/test-init.sh
+++ b/kdebug/test-init.sh
@@ -10,5 +10,10 @@
 
 echo Hello, World
 cd /root
-./quicktest.sh
-sh -i
+if [ -f ./interactive ]; then
+    ./quicktest.sh
+    sh -i
+else
+    ./quicktest.sh || ./exit 1
+fi
+./exit
diff --git a/kdebug/test-kernel.sh b/kdebug/test-kernel.sh
index 1326cd7..38f5584 100755
--- a/kdebug/test-kernel.sh
+++ b/kdebug/test-kernel.sh
@@ -47,6 +47,8 @@
 file /root/capsh $HERE/../progs/capsh 0755 0 0
 file /root/getpcaps $HERE/../progs/getpcaps 0755 0 0
 file /root/tcapsh-static $HERE/../progs/tcapsh-static 0755 0 0
+file /root/exit $HERE/exit 0755 0 0
+file /root/uns_test $HERE/../tests/uns_test 0755 0 0
 EOF
 
 # convenience for some local experiments
@@ -55,6 +57,10 @@
     . "$HERE/extras.sh"
 fi
 
+if [ -f "$HERE/interactive" ]; then
+    echo "file /root/interactive $HERE/interactive 0755 0 0" >> fs.conf
+fi
+
 COMMANDS="awk cat chmod cp dmesg fgrep id less ln ls mkdir mount pwd rm rmdir sh sort umount uniq vi"
 for f in $COMMANDS; do
     echo slink /bin/$f /sbin/busybox 0755 0 0 >> fs.conf
@@ -73,4 +79,5 @@
 		   -kernel $KERNEL \
 		   -initrd initramfs.img \
 		   -append "$APPEND" \
-		   -smp sockets=2,dies=1,cores=4
+		   -smp sockets=2,dies=1,cores=4 \
+		   -device isa-debug-exit
diff --git a/libcap/.gitignore b/libcap/.gitignore
index 8f77a0e..a0771d4 100644
--- a/libcap/.gitignore
+++ b/libcap/.gitignore
@@ -9,3 +9,7 @@
 cap_test
 libcap.pc
 libpsx.pc
+empty
+loader.txt
+cap_magic.o
+psx_magic.o
diff --git a/libcap/Makefile b/libcap/Makefile
index 9563d88..b5689d2 100644
--- a/libcap/Makefile
+++ b/libcap/Makefile
@@ -9,11 +9,18 @@
 CAPLIBNAME=$(LIBTITLE).so
 STACAPLIBNAME=$(LIBTITLE).a
 #
-PSXLIBNAME=libpsx.so
-STAPSXLIBNAME=libpsx.a
+PSXTITLE=libpsx
+PSXLIBNAME=$(PSXTITLE).so
+STAPSXLIBNAME=$(PSXTITLE).a
 
 CAPFILES=cap_alloc cap_proc cap_extint cap_flag cap_text cap_file
+CAPMAGICOBJ=cap_magic.o
 PSXFILES=../psx/psx
+PSXMAGICOBJ=psx_magic.o
+
+# The linker magic needed to build a dynamic library as independently
+# executable
+MAGIC=-Wl,-e,__so_start
 
 INCLS=libcap.h cap_names.h $(INCS)
 GPERF_OUTPUT = _caps_output.gperf
@@ -37,9 +44,9 @@
 endif
 endif
 
-pcs: libcap.pc
+pcs: $(LIBTITLE).pc
 ifeq ($(PTHREADS),yes)
-	$(MAKE) libpsx.pc
+	$(MAKE) $(PSXTITLE).pc
 endif
 
 ifeq ($(BUILD_GPERF),yes)
@@ -47,7 +54,7 @@
 INCLUDE_GPERF_OUTPUT = -DINCLUDE_GPERF_OUTPUT='"$(GPERF_OUTPUT)"'
 endif
 
-libcap.pc: libcap.pc.in
+$(LIBTITLE).pc: $(LIBTITLE).pc.in
 	sed -e 's,@prefix@,$(prefix),' \
 		-e 's,@exec_prefix@,$(exec_prefix),' \
 		-e 's,@libdir@,$(LIBDIR),' \
@@ -56,7 +63,7 @@
 		-e 's,@deps@,$(DEPS),' \
 		$< >$@
 
-libpsx.pc: libpsx.pc.in
+$(PSXTITLE).pc: $(PSXTITLE).pc.in
 	sed -e 's,@prefix@,$(prefix),' \
 		-e 's,@exec_prefix@,$(exec_prefix),' \
 		-e 's,@libdir@,$(LIBDIR),' \
@@ -93,13 +100,26 @@
 	$(RANLIB) $@
 
 ifeq ($(SHARED),yes)
-$(CAPLIBNAME) $(MAJCAPLIBNAME) $(MINCAPLIBNAME): $(CAPOBJS)
-	$(LD) $(CFLAGS) $(LDFLAGS) -Wl,-soname,$(MAJCAPLIBNAME) -o $(MINCAPLIBNAME) $^
+
+empty: empty.c
+	$(CC) -o $@ $<
+
+loader.txt: empty
+	$(OBJCOPY) --dump-section .interp=$@ $<
+
+cap_magic.o: execable.h execable.c loader.txt
+	$(CC) $(CFLAGS) $(IPATH) -DLIBRARY_VERSION=\"$(LIBTITLE)-$(VERSION).$(MINOR)\" -DSHARED_LOADER=\"$(shell cat loader.txt)\" -c execable.c -o $@
+
+$(CAPLIBNAME) $(MAJCAPLIBNAME) $(MINCAPLIBNAME): $(CAPOBJS) $(CAPMAGICOBJ)
+	$(LD) $(CFLAGS) $(LDFLAGS) -Wl,-soname,$(MAJCAPLIBNAME) -o $(MINCAPLIBNAME) $^ $(MAGIC)
 	ln -sf $(MINCAPLIBNAME) $(MAJCAPLIBNAME)
 	ln -sf $(MAJCAPLIBNAME) $(CAPLIBNAME)
 
-$(PSXLIBNAME) $(MAJPSXLIBNAME) $(MINPSXLIBNAME): $(PSXOBJS) include/sys/psx_syscall.h
-	$(LD) $(CFLAGS) $(LDFLAGS) -Wl,-soname,$(MAJPSXLIBNAME) -o $(MINPSXLIBNAME) $(PSXOBJS) $(PSXLINKFLAGS)
+psx_magic.o: execable.h execable.c loader.txt
+	$(CC) $(CFLAGS) $(IPATH) -DLIBRARY_VERSION=\"$(PSXTITLE)-$(VERSION).$(MINOR)\" -DSHARED_LOADER=\"$(shell cat loader.txt)\" -c execable.c -o $@
+
+$(PSXLIBNAME) $(MAJPSXLIBNAME) $(MINPSXLIBNAME): $(PSXOBJS) include/sys/psx_syscall.h $(PSXMAGICOBJ)
+	$(LD) $(CFLAGS) $(LDFLAGS) -Wl,-soname,$(MAJPSXLIBNAME) -o $(MINPSXLIBNAME) $(PSXOBJS) $(PSXMAGICOBJ) $(MAGIC) $(PSXLINKFLAGS)
 	ln -sf $(MINPSXLIBNAME) $(MAJPSXLIBNAME)
 	ln -sf $(MAJPSXLIBNAME) $(PSXLIBNAME)
 endif
@@ -110,11 +130,23 @@
 cap_text.o: cap_text.c $(USE_GPERF_OUTPUT) $(INCLS)
 	$(CC) $(CFLAGS) $(IPATH) $(INCLUDE_GPERF_OUTPUT) -c $< -o $@
 
-cap_test: cap_test.c libcap.h
-	$(CC) $(CFLAGS) $(IPATH) $< -o $@
+cap_test: cap_test.c libcap.h $(CAPOBJS)
+	$(CC) $(CFLAGS) $(IPATH) $< $(CAPOBJS) -o $@
+
+libcapsotest: $(CAPLIBNAME)
+	./$(CAPLIBNAME)
+
+libpsxsotest: $(PSXLIBNAME)
+	./$(PSXLIBNAME)
 
 test: cap_test
 	./cap_test
+ifeq ($(SHARED),yes)
+	$(MAKE) libcapsotest
+ifeq ($(PTHREADS),yes)
+	$(MAKE) libpsxsotest
+endif
+endif
 
 install: install-static
 ifeq ($(SHARED),yes)
@@ -163,17 +195,17 @@
 	-/sbin/ldconfig
 endif
 
-install-common-cap: install-common libcap.pc
+install-common-cap: install-common $(LIBTITLE).pc
 	install -m 0644 include/sys/capability.h $(FAKEROOT)$(INCDIR)/sys
-	install -m 0644 libcap.pc $(FAKEROOT)$(PKGCONFIGDIR)/libcap.pc
+	install -m 0644 $(LIBTITLE).pc $(FAKEROOT)$(PKGCONFIGDIR)/$(LIBTITLE).pc
 
 include/sys/psx_syscall.h: ../psx/psx_syscall.h
 	rm -f $@
 	ln -s ../../../psx/psx_syscall.h $@
 
-install-common-psx: install-common libpsx.pc include/sys/psx_syscall.h
+install-common-psx: install-common $(PSXTITLE).pc include/sys/psx_syscall.h
 	install -m 0644 include/sys/psx_syscall.h $(FAKEROOT)$(INCDIR)/sys
-	install -m 0644 libpsx.pc $(FAKEROOT)$(PKGCONFIGDIR)/libpsx.pc
+	install -m 0644 $(PSXTITLE).pc $(FAKEROOT)$(PKGCONFIGDIR)/$(PSXTITLE).pc
 
 install-common:
 	mkdir -p -m 0755 $(FAKEROOT)$(INCDIR)/sys
@@ -182,8 +214,9 @@
 
 clean:
 	$(LOCALCLEAN)
-	rm -f $(CAPOBJS) $(CAPLIBNAME)* $(STACAPLIBNAME) libcap.pc
-	rm -f $(PSXOBJS) $(PSXLIBNAME)* $(STAPSXLIBNAME) libpsx.pc
+	rm -f $(CAPOBJS) $(CAPLIBNAME)* $(STACAPLIBNAME) $(LIBTITLE).pc
+	rm -f $(PSXOBJS) $(PSXLIBNAME)* $(STAPSXLIBNAME) $(PSXTITLE).pc
 	rm -f cap_names.h cap_names.list.h _makenames $(GPERF_OUTPUT) cap_test
 	rm -f include/sys/psx_syscall.h
+	rm -f $(CAPMAGICOBJ) $(PSXMAGICOBJ) empty loader.txt
 	cd include/sys && $(LOCALCLEAN)
diff --git a/libcap/cap_alloc.c b/libcap/cap_alloc.c
index 6dab4e6..88ba6da 100644
--- a/libcap/cap_alloc.c
+++ b/libcap/cap_alloc.c
@@ -147,6 +147,22 @@
 }
 
 /*
+ * cap_func_launcher allocates some memory for a launcher and
+ * initializes it. The purpose of this launcher, unlike one created
+ * with cap_new_launcher(), is to execute some function code from a
+ * forked copy of the program. The forked process will exit when the
+ * callback function, func, returns.
+ */
+cap_launch_t cap_func_launcher(int (callback_fn)(void *detail))
+{
+    __u32 *data = calloc(1, sizeof(__u32) + sizeof(struct cap_launch_s));
+    *(data++) = CAP_LAUNCH_MAGIC;
+    struct cap_launch_s *attr = (struct cap_launch_s *) data;
+    attr->custom_setup_fn = callback_fn;
+    return attr;
+}
+
+/*
  * Scrub and then liberate an internal capability set.
  */
 
diff --git a/libcap/cap_extint.c b/libcap/cap_extint.c
index 7d6e7ad..bf0967b 100644
--- a/libcap/cap_extint.c
+++ b/libcap/cap_extint.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997-8 Andrew G Morgan <morgan@kernel.org>
+ * Copyright (c) 1997-8,2021 Andrew G. Morgan <morgan@kernel.org>
  *
  * This file deals with exchanging internal and external
  * representations of capability sets.
@@ -15,6 +15,11 @@
 #define CAP_EXT_MAGIC_SIZE 4
 const static __u8 external_magic[CAP_EXT_MAGIC_SIZE+1] = CAP_EXT_MAGIC;
 
+/*
+ * This is the largest size libcap can currently export.
+ * cap_size() may return something smaller depending on the
+ * content of its argument cap_t.
+ */
 struct cap_ext_struct {
     __u8 magic[CAP_EXT_MAGIC_SIZE];
     __u8 length_of_capset;
@@ -26,11 +31,44 @@
 };
 
 /*
+ * minimum exported flag size: libcap2 has always exported with flags
+ * this size.
+ */
+static size_t _libcap_min_ext_flag_size = CAP_SET_SIZE < 8 ? CAP_SET_SIZE : 8;
+
+/*
  * return size of external capability set
  */
-
-ssize_t cap_size(cap_t caps)
+ssize_t cap_size(cap_t cap_d)
 {
+    if (good_cap_t(cap_d)) {
+	size_t j, used;
+	for (j=used=0; j<CAP_SET_SIZE; j+=sizeof(__u32)) {
+	    int i;
+	    __u32 val = 0;
+	    for (i=0; i<NUMBER_OF_CAP_SETS; ++i) {
+		val |= cap_d->u[j/sizeof(__u32)].flat[i];
+	    }
+	    if (val == 0) {
+		continue;
+	    }
+	    if (val > 0x0000ffff) {
+		if (val > 0x00ffffff) {
+		    used = j+4;
+		} else {
+		    used = j+3;
+		}
+	    } else if (val > 0x000000ff) {
+		used = j+2;
+	    } else {
+		used = j+1;
+	    }
+	}
+	if (used < _libcap_min_ext_flag_size) {
+	    used = _libcap_min_ext_flag_size;
+	}
+	return (ssize_t)(CAP_EXT_MAGIC_SIZE + 1+ NUMBER_OF_CAP_SETS * used);
+    }
     return ssizeof(struct cap_ext_struct);
 }
 
@@ -43,42 +81,57 @@
 ssize_t cap_copy_ext(void *cap_ext, cap_t cap_d, ssize_t length)
 {
     struct cap_ext_struct *result = (struct cap_ext_struct *) cap_ext;
+    ssize_t csz, len_set;
     int i;
 
     /* valid arguments? */
-    if (!good_cap_t(cap_d) || length < ssizeof(struct cap_ext_struct)
-	|| cap_ext == NULL) {
+    if (!good_cap_t(cap_d) || cap_ext == NULL) {
 	errno = EINVAL;
 	return -1;
     }
 
+    csz = cap_size(cap_d);
+    if (csz > length) {
+	errno = EINVAL;
+	return -1;
+    }
+    len_set = (csz - (CAP_EXT_MAGIC_SIZE+1))/NUMBER_OF_CAP_SETS;
+
     /* fill external capability set */
     memcpy(&result->magic, external_magic, CAP_EXT_MAGIC_SIZE);
-    result->length_of_capset = CAP_SET_SIZE;
+    result->length_of_capset = len_set;
 
     for (i=0; i<NUMBER_OF_CAP_SETS; ++i) {
 	size_t j;
-	for (j=0; j<CAP_SET_SIZE; ) {
+	for (j=0; j<len_set; ) {
 	    __u32 val;
 
 	    val = cap_d->u[j/sizeof(__u32)].flat[i];
 
-	    result->bytes[j++][i] =  val        & 0xFF;
-	    result->bytes[j++][i] = (val >>= 8) & 0xFF;
-	    result->bytes[j++][i] = (val >>= 8) & 0xFF;
-	    result->bytes[j++][i] = (val >> 8)  & 0xFF;
+	    result->bytes[j++][i] =      val        & 0xFF;
+	    if (j < len_set) {
+		result->bytes[j++][i] = (val >>= 8) & 0xFF;
+	    }
+	    if (j < len_set) {
+		result->bytes[j++][i] = (val >>= 8) & 0xFF;
+	    }
+	    if (j < len_set) {
+		result->bytes[j++][i] = (val >> 8)  & 0xFF;
+	    }
 	}
     }
 
     /* All done: return length of external representation */
-    return (ssizeof(struct cap_ext_struct));
+    return csz;
 }
 
 /*
  * Import an external representation to produce an internal rep.
  * the internal rep should be liberated with cap_free().
+ *
+ * Note, this function assumes that cap_ext has a valid length. That
+ * is, feeding garbage to this function will likely crash the program.
  */
-
 cap_t cap_copy_int(const void *cap_ext)
 {
     const struct cap_ext_struct *export =
@@ -121,3 +174,24 @@
     return cap_d;
 }
 
+/*
+ * This function is the same as cap_copy_int() although it requires an
+ * extra argument that is the length of the cap_ext data. Before
+ * running cap_copy_int() the function validates that length is
+ * consistent with the stated length. It returns NULL on error.
+ */
+cap_t cap_copy_int_check(const void *cap_ext, ssize_t length)
+{
+    const struct cap_ext_struct *export =
+	(const struct cap_ext_struct *) cap_ext;
+
+    if (length < 1+CAP_EXT_MAGIC_SIZE) {
+	errno = EINVAL;
+	return NULL;
+    }
+    if (length < 1+CAP_EXT_MAGIC_SIZE + export->length_of_capset * NUMBER_OF_CAP_SETS) {
+	errno = EINVAL;
+	return NULL;
+    }
+    return cap_copy_int(cap_ext);
+}
diff --git a/libcap/cap_flag.c b/libcap/cap_flag.c
index c1ffa0d..51799b0 100644
--- a/libcap/cap_flag.c
+++ b/libcap/cap_flag.c
@@ -147,6 +147,31 @@
 }
 
 /*
+ * cap_fill copies a bit-vector of capability state in a cap_t from
+ * one flag to another.
+ */
+int cap_fill(cap_t cap_d, cap_flag_t to, cap_flag_t from)
+{
+    if (!good_cap_t(cap_d)) {
+	errno = EINVAL;
+	return -1;
+    }
+
+    if (to < CAP_EFFECTIVE || to > CAP_INHERITABLE ||
+	from < CAP_EFFECTIVE || from > CAP_INHERITABLE) {
+	errno = EINVAL;
+	return -1;
+    }
+
+    int i;
+    for (i = 0; i < _LIBCAP_CAPABILITY_U32S; i++) {
+	cap_d->u[i].flat[to] = cap_d->u[i].flat[from];
+    }
+
+    return 0;
+}
+
+/*
  * cap_iab_get_vector reads the single bit value from an IAB vector set.
  */
 cap_flag_value_t cap_iab_get_vector(cap_iab_t iab, cap_iab_vector_t vec,
diff --git a/libcap/cap_proc.c b/libcap/cap_proc.c
index 1329f94..e12c8e6 100644
--- a/libcap/cap_proc.c
+++ b/libcap/cap_proc.c
@@ -406,6 +406,29 @@
 }
 
 /*
+ * cap_prctl performs a prctl() 6 argument call on the current
+ * thread. Use cap_prctlw() if you want to perform a POSIX semantics
+ * prctl() system call.
+ */
+int cap_prctl(long int pr_cmd, long int arg1, long int arg2,
+	      long int arg3, long int arg4, long int arg5)
+{
+    return prctl(pr_cmd, arg1, arg2, arg3, arg4, arg5);
+}
+
+/*
+ * cap_prctlw performs a POSIX semantics prctl() call. That is a 6 arg
+ * prctl() call that executes on all available threads when libpsx is
+ * linked. The suffix 'w' refers to the fact one only ever needs to
+ * invoke this is if the call will write some kernel state.
+ */
+int cap_prctlw(long int pr_cmd, long int arg1, long int arg2,
+	       long int arg3, long int arg4, long int arg5)
+{
+    return _libcap_wprctl6(&multithread, pr_cmd, arg1, arg2, arg3, arg4, arg5);
+}
+
+/*
  * Some predefined constants
  */
 #define CAP_SECURED_BITS_BASIC                                 \
@@ -848,16 +871,22 @@
 
 /*
  * _cap_launch is invoked in the forked child, it cannot return but is
- * required to exit. If the execve fails, it will write the errno value
- * over the filedescriptor, fd, and exit with status 0.
+ * required to exit, if the execve fails. It will write the errno
+ * value for any failure over the filedescriptor, fd, and exit with
+ * status 1.
  */
 __attribute__ ((noreturn))
 static void _cap_launch(int fd, cap_launch_t attr, void *detail) {
     struct syscaller_s *sc = &singlethread;
+    int my_errno;
 
     if (attr->custom_setup_fn && attr->custom_setup_fn(detail)) {
 	goto defer;
     }
+    if (attr->arg0 == NULL) {
+	/* handle the successful cap_func_launcher completion */
+	exit(0);
+    }
 
     if (attr->change_uids && _cap_setuid(sc, attr->uid)) {
 	goto defer;
@@ -891,8 +920,9 @@
      * getting here means an error has occurred and errno is
      * communicated to the parent
      */
+    my_errno = errno;
     for (;;) {
-	int n = write(fd, &errno, sizeof(errno));
+	int n = write(fd, &my_errno, sizeof(my_errno));
 	if (n < 0 && errno == EAGAIN) {
 	    continue;
 	}
@@ -903,36 +933,50 @@
 }
 
 /*
- * cap_launch performs a wrapped fork+exec that works in both an
- * unthreaded environment and also where libcap is linked with
- * psx+pthreads. The function supports dropping privilege in the
- * forked thread, but retaining privilege in the parent thread(s).
+ * cap_launch performs a wrapped fork+(callback and/or exec) that
+ * works in both an unthreaded environment and also where libcap is
+ * linked with psx+pthreads. The function supports dropping privilege
+ * in the forked thread, but retaining privilege in the parent
+ * thread(s).
  *
- * Since the ambient set is fragile with respect to changes in I or P,
- * the function carefully orders setting of these inheritable
- * characteristics, to make sure they stick, or return an error
- * of -1 setting errno because the launch failed.
+ * When applying the IAB vector inside the fork, since the ambient set
+ * is fragile with respect to changes in I or P, the function
+ * carefully orders setting of these inheritable characteristics, to
+ * make sure they stick.
+ *
+ * This function will return an error of -1 setting errno if the
+ * launch failed.
  */
-pid_t cap_launch(cap_launch_t attr, void *data) {
+pid_t cap_launch(cap_launch_t attr, void *detail) {
     int my_errno;
     int ps[2];
+    pid_t child;
+
+    /* The launch must have a purpose */
+    if (attr->custom_setup_fn == NULL &&
+	(attr->arg0 == NULL || attr->argv == NULL)) {
+	errno = EINVAL;
+	return -1;
+    }
 
     if (pipe2(ps, O_CLOEXEC) != 0) {
 	return -1;
     }
 
-    int child = fork();
+    child = fork();
     my_errno = errno;
 
+    if (!child) {
+	close(ps[0]);
+	prctl(PR_SET_NAME, "cap-launcher", 0, 0, 0);
+	_cap_launch(ps[1], attr, detail);
+	/* no return from this function */
+	_exit(1);
+    }
     close(ps[1]);
     if (child < 0) {
 	goto defer;
     }
-    if (!child) {
-	close(ps[0]);
-	/* noreturn from this function: */
-	_cap_launch(ps[1], attr, data);
-    }
 
     /*
      * Extend this function's return codes to include setup failures
@@ -956,5 +1000,5 @@
 defer:
     close(ps[0]);
     errno = my_errno;
-    return (pid_t) child;
+    return child;
 }
diff --git a/libcap/cap_test.c b/libcap/cap_test.c
index 4ea83c8..a717217 100644
--- a/libcap/cap_test.c
+++ b/libcap/cap_test.c
@@ -29,11 +29,55 @@
     return failed;
 }
 
+static int test_cap_flags(void) {
+    cap_t c, d;
+    cap_flag_t f = CAP_INHERITABLE, t;
+    cap_value_t v;
+
+    c = cap_init();
+    if (c == NULL) {
+	printf("test_flags failed to allocate a set\n");
+	return -1;
+    }
+
+    for (v = 0; v < __CAP_MAXBITS; v += 3) {
+	if (cap_set_flag(c, CAP_INHERITABLE, 1, &v, CAP_SET)) {
+	    printf("unable to set inheritable bit %d\n", v);
+	    return -1;
+	}
+    }
+
+    d = cap_dup(c);
+    for (t = CAP_EFFECTIVE; t <= CAP_INHERITABLE; t++) {
+	if (cap_fill(c, t, f)) {
+	    printf("cap_fill failed %d -> %d\n", f, t);
+	    return -1;
+	}
+	if (cap_clear_flag(c, f)) {
+	    printf("cap_fill unable to clear flag %d\n", f);
+	    return -1;
+	}
+	f = t;
+    }
+    if (cap_compare(c, d)) {
+	printf("permuted cap_fill()ing failed to perform net no-op\n");
+	return -1;
+    }
+    cap_free(d);
+    cap_free(c);
+    return 0;
+}
+
 int main(int argc, char **argv) {
     int result = 0;
+
     result = test_cap_bits() | result;
+    result = test_cap_flags() | result;
+
     if (result) {
-	printf("test FAILED\n");
+	printf("cap_test FAILED\n");
 	exit(1);
     }
+    printf("cap_test PASS\n");
+    exit(0);
 }
diff --git a/libcap/cap_text.c b/libcap/cap_text.c
index b0fad9d..87e0838 100644
--- a/libcap/cap_text.c
+++ b/libcap/cap_text.c
@@ -315,7 +315,7 @@
 #endif
 	char *tmp, *result;
 
-	asprintf(&tmp, "%u", cap);
+	(void) asprintf(&tmp, "%u", cap);
 	result = _libcap_strdup(tmp);
 	free(tmp);
 
diff --git a/libcap/empty.c b/libcap/empty.c
new file mode 100644
index 0000000..0314ff1
--- /dev/null
+++ b/libcap/empty.c
@@ -0,0 +1 @@
+int main(int argc, char **argv) { return 0; }
diff --git a/libcap/execable.c b/libcap/execable.c
new file mode 100644
index 0000000..be18914
--- /dev/null
+++ b/libcap/execable.c
@@ -0,0 +1,15 @@
+#include <stdio.h>
+#include "execable.h"
+
+SO_MAIN(int argc, char **argv)
+{
+    const char *cmd = "This library";
+    if (argv != NULL) {
+	cmd = argv[0];
+    }
+    printf("%s is the shared library version: " LIBRARY_VERSION ".\n"
+	   "See the License file for distribution information.\n"
+	   "More information on this library is available from:\n"
+	   "\n"
+	   "    https://sites.google.com/site/fullycapable/\n", cmd);
+}
diff --git a/libcap/execable.h b/libcap/execable.h
new file mode 100644
index 0000000..0bcc5d4
--- /dev/null
+++ b/libcap/execable.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2021 Andrew G. Morgan <morgan@kernel.org>
+ *
+ * Some header magic to help make a shared object run-able as a stand
+ * alone executable binary.
+ *
+ * This is a slightly more sophisticated implementation than the
+ * answer I posted here:
+ *
+ *    https://stackoverflow.com/a/68339111/14760867
+ *
+ * Compile your shared library with:
+ *
+ *   -DSHARED_LOADER="\"ld-linux...\"" (loader for your target system)
+ *   ...
+ *   --entry=__so_start
+ */
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef __EXECABLE_H
+#error "only include execable.h once"
+#endif
+#define __EXECABLE_H
+
+const char __execable_dl_loader[] __attribute((section(".interp"))) =
+    SHARED_LOADER ;
+
+static void __execable_parse_args(int *argc_p, char ***argv_p)
+{
+    int argc = 0;
+    char **argv = NULL;
+    FILE *f = fopen("/proc/self/cmdline", "rb");
+    if (f != NULL) {
+	char *mem = NULL, *p;
+	size_t size = 32, offset;
+	for (offset=0; ; size *= 2) {
+	    char *new_mem = realloc(mem, size+1);
+	    if (new_mem == NULL) {
+		perror("unable to parse arguments");
+		if (mem != NULL) {
+		    free(mem);
+		}
+		exit(1);
+	    }
+	    mem = new_mem;
+	    offset += fread(mem+offset, 1, size-offset, f);
+	    if (offset < size) {
+		size = offset;
+		mem[size] = '\0';
+		break;
+	    }
+	}
+	fclose(f);
+	for (argc=1, p=mem+size-2; p >= mem; p--) {
+	    argc += (*p == '\0');
+	}
+	argv = calloc(argc+1, sizeof(char *));
+	if (argv == NULL) {
+	    perror("failed to allocate memory for argv");
+	    free(mem);
+	    exit(1);
+	}
+	for (p=mem, argc=0, offset=0; offset < size; argc++) {
+	    argv[argc] = mem+offset;
+	    offset += strlen(mem+offset)+1;
+	}
+    }
+    *argc_p = argc;
+    *argv_p = argv;
+}
+
+/*
+ * Note, to avoid any runtime confusion, SO_MAIN is a void static
+ * function.
+ */
+
+#define SO_MAIN						        \
+static void __execable_main(int, char**);                       \
+extern void __so_start(void);		                	\
+void __so_start(void)                                           \
+{                                                               \
+    int argc;                                                   \
+    char **argv;                                                \
+    __execable_parse_args(&argc, &argv);                        \
+    __execable_main(argc, argv);				\
+    if (argc != 0) {                                            \
+	free(argv[0]);                                          \
+	free(argv);                                             \
+    }                                                           \
+    exit(0);                                                    \
+}                                                               \
+static void __execable_main
diff --git a/libcap/include/sys/capability.h b/libcap/include/sys/capability.h
index ac13c12..d172ddc 100644
--- a/libcap/include/sys/capability.h
+++ b/libcap/include/sys/capability.h
@@ -115,6 +115,10 @@
 			    cap_flag_value_t);
 extern int     cap_clear(cap_t);
 extern int     cap_clear_flag(cap_t, cap_flag_t);
+extern int     cap_fill(cap_t, cap_flag_t, cap_flag_t);
+
+#define CAP_DIFFERS(result, flag)  (((result) & (1 << (flag))) != 0)
+extern int     cap_compare(cap_t, cap_t);
 
 extern cap_flag_value_t cap_iab_get_vector(cap_iab_t, cap_iab_vector_t,
 					 cap_value_t);
@@ -145,9 +149,10 @@
 #define CAP_AMBIENT_SUPPORTED() (cap_get_ambient(CAP_CHOWN) >= 0)
 
 /* libcap/cap_extint.c */
-extern ssize_t cap_size(cap_t);
-extern ssize_t cap_copy_ext(void *, cap_t, ssize_t);
-extern cap_t   cap_copy_int(const void *);
+extern ssize_t cap_size(cap_t cap_d);
+extern ssize_t cap_copy_ext(void *cap_ext, cap_t cap_d, ssize_t length);
+extern cap_t   cap_copy_int(const void *cap_ext);
+extern cap_t   cap_copy_int_check(const void *cap_ext, ssize_t length);
 
 /* libcap/cap_text.c */
 extern cap_t   cap_from_text(const char *);
@@ -158,9 +163,6 @@
 extern char *     cap_iab_to_text(cap_iab_t iab);
 extern cap_iab_t  cap_iab_from_text(const char *text);
 
-#define CAP_DIFFERS(result, flag)  (((result) & (1 << (flag))) != 0)
-extern int     cap_compare(cap_t, cap_t);
-
 /* libcap/cap_proc.c */
 extern void cap_set_syscall(long int (*new_syscall)(long int,
 				long int, long int, long int),
@@ -175,6 +177,10 @@
 extern unsigned cap_get_secbits(void);
 extern int cap_set_secbits(unsigned bits);
 
+extern int cap_prctl(long int pr_cmd, long int arg1, long int arg2,
+		     long int arg3, long int arg4, long int arg5);
+extern int cap_prctlw(long int pr_cmd, long int arg1, long int arg2,
+		      long int arg3, long int arg4, long int arg5);
 extern int cap_setuid(uid_t uid);
 extern int cap_setgroups(gid_t gid, size_t ngroups, const gid_t groups[]);
 
@@ -185,6 +191,7 @@
 
 extern cap_launch_t cap_new_launcher(const char *arg0, const char * const *argv,
 				     const char * const *envp);
+extern cap_launch_t cap_func_launcher(int (callback_fn)(void *detail));
 extern void cap_launcher_callback(cap_launch_t attr,
 				  int (callback_fn)(void *detail));
 extern void cap_launcher_setuid(cap_launch_t attr, uid_t uid);
@@ -193,7 +200,7 @@
 extern void cap_launcher_set_mode(cap_launch_t attr, cap_mode_t flavor);
 extern cap_iab_t cap_launcher_set_iab(cap_launch_t attr, cap_iab_t iab);
 extern void cap_launcher_set_chroot(cap_launch_t attr, const char *chroot);
-extern pid_t cap_launch(cap_launch_t attr, void *data);
+extern pid_t cap_launch(cap_launch_t attr, void *detail);
 
 /*
  * system calls - look to libc for function to system call
diff --git a/pam_cap/.gitignore b/pam_cap/.gitignore
index 05e9bbf..ef9e57f 100644
--- a/pam_cap/.gitignore
+++ b/pam_cap/.gitignore
@@ -1,3 +1,5 @@
 pam_cap.so
 testlink
 test_pam_cap
+lazylink.so
+pam_cap_linkopts
diff --git a/pam_cap/Makefile b/pam_cap/Makefile
index 56604fd..689239e 100644
--- a/pam_cap/Makefile
+++ b/pam_cap/Makefile
@@ -10,25 +10,52 @@
 	mkdir -p -m 0755 $(FAKEROOT)$(LIBDIR)/security
 	install -m 0755 pam_cap.so $(FAKEROOT)$(LIBDIR)/security
 
-# Note (as the author of much of the Linux-PAM library, I am confident
-# that this next line does *not* require -lpam on it.) If you think it
-# does, *verify that it does*, and if you observe that it fails as
-# written (and you know why it fails), email me and explain why. Thanks!
+../libcap/loader.txt:
+	$(MAKE) -C ../libcap loader.txt
 
-pam_cap.so: pam_cap.o
-	$(LD) -o pam_cap.so $< $(LIBCAPLIB) $(LDFLAGS)
+execable.o: execable.c ../libcap/execable.h ../libcap/loader.txt
+	$(CC) $(CFLAGS) $(IPATH) -DLIBCAP_VERSION=\"libcap-$(VERSION).$(MINOR)\" -DSHARED_LOADER=\"$(shell cat ../libcap/loader.txt)\" -c execable.c -o $@
+
+pam_cap.so: pam_cap.o execable.o pam_cap_linkopts
+	cat pam_cap_linkopts | xargs -e $(LD) -o $@ pam_cap.o execable.o $(LIBCAPLIB) $(LDFLAGS)
+
+# Some distributions force link everything at compile time, and don't
+# take advantage of libpam's dlopen runtime options to resolve ill
+# defined symbols from its own linkage as needed. (As the original
+# author of that part of libpam, I consider this force linking
+# premature optimization.) We debugged its consequences to pam_cap.so
+# as part of:
+#
+#   https://bugzilla.kernel.org/show_bug.cgi?id=214023
+#
+# If the current build environment is one of those, extend the link
+# options for pam_cap.so to force linkage against libpam and the
+# gazillion other things libpam is linked against...
+pam_cap_linkopts: lazylink.so
+	echo "-Wl,-e,__so_start" > $@
+	./lazylink.so || echo "-lpam" >> $@
+
+lazylink.so: lazylink.c ../libcap/execable.h ../libcap/loader.txt
+	$(LD) -o $@ $(CFLAGS) $(IPATH) lazylink.c -DSHARED_LOADER=\"$(shell cat ../libcap/loader.txt)\" $(LDFLAGS) -Wl,-e,__so_start
 
 pam_cap.o: pam_cap.c
 	$(CC) $(CFLAGS) $(IPATH) -c $< -o $@
 
-test_pam_cap: test_pam_cap.c pam_cap.c
+../libcap/libcap.a:
+	$(MAKE) -C ../libcap libcap.a
+
+test_pam_cap: test_pam_cap.c pam_cap.c ../libcap/libcap.a
 	$(CC) $(CFLAGS) $(IPATH) -o $@ test_pam_cap.c $(LIBCAPLIB) $(LDFLAGS) --static
 
 testlink: test.c pam_cap.o
 	$(CC) $(CFLAGS) -o $@ $+ -lpam -ldl $(LIBCAPLIB) $(LDFLAGS)
 
-test: pam_cap.so
-	make testlink
+test: testlink test_pam_cap pam_cap.so
+	$(MAKE) testlink
+	./test_pam_cap
+	LD_LIBRARY_PATH=../libcap ./pam_cap.so
+	LD_LIBRARY_PATH=../libcap ./pam_cap.so --help
+	@echo "module can be run as an executable!"
 
 sudotest: test test_pam_cap
 	sudo ./test_pam_cap root 0x0 0x0 0x0 config=./capability.conf
@@ -40,4 +67,4 @@
 	sudo ./test_pam_cap delta 0x41 0x80 0x41 config=./sudotest.conf
 
 clean:
-	rm -f *.o *.so testlink test_pam_cap *~
+	rm -f *.o *.so testlink lazylink.so test_pam_cap pam_cap_linkopts *~
diff --git a/pam_cap/capability.conf b/pam_cap/capability.conf
index 09517f8..08c01e1 100644
--- a/pam_cap/capability.conf
+++ b/pam_cap/capability.conf
@@ -6,14 +6,26 @@
 #
 # In order to use this module, it must have been linked with libcap
 # and thus you'll know about Linux's capability support.
-# [If you don't know about libcap, the sources for it are here:
+# [If you don't know about libcap, read more about it here:
 #
-#   http://www.kernel.org/pub/linux/libs/security/linux-privs/
+#   https://sites.google.com/site/fullycapable/
+#
+# There is a page devoted to pam_cap.so here:
+#
+#   https://sites.google.com/site/fullycapable/pam_cap-so
 #
 # .]
 #
 # Here are some sample lines (remove the preceding '#' if you want to
-# use them
+# use them.
+#
+# The pam_cap.so module accepts the following arguments:
+#
+#   debug         - be more verbose logging things (unused by pam_cap for now)
+#   config=<file> - override the default config for the module with file
+#   keepcaps      - workaround for applications that setuid without this
+#   autoauth      - if you want pam_cap.so to always succeed for the auth phase
+#   default=<iab> - provide a fallback IAB value if there is no '*' rule
 
 ## user 'morgan' gets the CAP_SETFCAP inheritable capability (commented out!)
 #cap_setfcap		morgan
@@ -24,20 +36,23 @@
 ## 'everyone else' gets no inheritable capabilities (restrictive config)
 none  *
 
-## if there is no '*' entry, all users not explicitly mentioned will
-## get all available capabilities. This is a permissive default, and
-## possibly not what you want... On first reading, you might think this
-## is a security problem waiting to happen, but it defaults to not being
-## so in this sample file! Further, by 'get', we mean 'get in their inheritable
-## set'. That is, if you look at a random process, even one run by root,
-## you will see it has no inheritable capabilities (by default):
+## if there is no '*' entry, and no "default=<iab>" pam_cap.so module
+## argument to fallback on, all users not explicitly mentioned will
+## get all currently available inheritable capabilities. This is a
+## permissive default, and possibly not what you want... On first
+## reading, you might think this is a security problem waiting to
+## happen, but it defaults to not being so in this sample file!
+## Further, by 'get', we mean 'get in their IAB sets'. That is, if you
+## look at a random process, even one run by root, you will see it has
+## no IAB capabilities (by default):
 ##
 ##   $ /sbin/capsh --decode=$(grep CapInh /proc/1/status|awk '{print $2}')
 ##   0000000000000000=
 ##
-## The pam_cap module simply alters the value of this capability
-## set. Including the 'none *' forces use of this module with an
-## unspecified user to have their inheritable set forced to zero.
+## The pam_cap module simply alters the value of the inheritable
+## capability vactors (IAB). Including the 'none *' forces use of this
+## module with an unspecified user to have their inheritable set
+## forced to zero.
 ##
 ## Omitting the line will cause the inheritable set to be unmodified
 ## from what the parent process had (which is generally 0 unless the
diff --git a/pam_cap/execable.c b/pam_cap/execable.c
new file mode 100644
index 0000000..0bf42d3
--- /dev/null
+++ b/pam_cap/execable.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2021 Andrew G. Morgan <morgan@kernel.org>
+ *
+ * The purpose of this file is to provide an executable mode for the
+ * pam_cap.so binary. If you run it directly, all it does is print
+ * version information.
+ *
+ * It accepts the optional --help argument which causes the executable
+ * to display a summary of all the supported, pam stacked, module
+ * arguments.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../libcap/execable.h"
+
+SO_MAIN(int argc, char **argv)
+{
+    const char *cmd = "<pam_cap.so>";
+    if (argv != NULL) {
+	cmd = argv[0];
+    }
+
+    printf(
+	"%s (version " LIBCAP_VERSION ") is a PAM module to specify\n"
+	"inheritable (IAB) capabilities via the libpam authentication\n"
+	"abstraction. See the libcap License file for licensing information.\n"
+	"\n"
+	"Release notes and feature documentation for libcap and pam_cap.so\n"
+	"can be found at:\n"
+	"\n"
+	"    https://sites.google.com/site/fullycapable/\n", cmd);
+    if (argc <= 1) {
+	return;
+    }
+
+    if (argc > 2 || strcmp(argv[1], "--help")) {
+	printf("\n%s only supports the optional argument --help\n", cmd);
+	exit(1);
+    }
+
+    printf("\n"
+	   "%s supports the following module arguments:\n"
+	   "\n"
+	   "debug         - verbose logging (ignored for now)\n"
+	   "config=<file> - override the default config with file\n"
+	   "keepcaps      - workaround for apps that setuid without this\n"
+	   "autoauth      - pam_cap.so to always succeed for the 'auth' phase\n"
+	   "default=<iab> - fallback IAB value if there is no '*' rule\n",
+	cmd);
+}
diff --git a/pam_cap/lazylink.c b/pam_cap/lazylink.c
new file mode 100644
index 0000000..969c92d
--- /dev/null
+++ b/pam_cap/lazylink.c
@@ -0,0 +1,20 @@
+/*
+ * Test if the provided LDFLAGS support lazy linking
+ */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../libcap/execable.h"
+
+extern int nothing_sets_this(void);
+extern void nothing_uses_this(void);
+
+void nothing_uses_this(void)
+{
+    nothing_sets_this();
+}
+
+SO_MAIN(int argc, char **argv)
+{
+    exit(0);
+}
diff --git a/pam_cap/pam_cap.c b/pam_cap/pam_cap.c
index 6927f7b..162e1f5 100644
--- a/pam_cap/pam_cap.c
+++ b/pam_cap/pam_cap.c
@@ -1,11 +1,11 @@
 /*
- * Copyright (c) 1999,2007,19,20 Andrew G. Morgan <morgan@kernel.org>
+ * Copyright (c) 1999,2007,2019-21 Andrew G. Morgan <morgan@kernel.org>
  *
  * The purpose of this module is to enforce inheritable, bounding and
  * ambient capability sets for a specified user.
  */
 
-/* #define DEBUG */
+/* #define PAM_DEBUG */
 
 #ifndef _DEFAULT_SOURCE
 #define _DEFAULT_SOURCE
@@ -21,6 +21,7 @@
 #include <string.h>
 #include <syslog.h>
 #include <sys/capability.h>
+#include <sys/prctl.h>
 #include <sys/types.h>
 #include <linux/limits.h>
 
@@ -31,10 +32,16 @@
 #define CAP_FILE_BUFFER_SIZE    4096
 #define CAP_FILE_DELIMITERS     " \t\n"
 
+/*
+ * pam_cap_s is used to summarize argument values in a parsed form.
+ */
 struct pam_cap_s {
     int debug;
+    int keepcaps;
+    int autoauth;
     const char *user;
     const char *conf_filename;
+    const char *fallback;
 };
 
 /*
@@ -74,7 +81,7 @@
     return 0;
 }
 
-/* obtain the inheritable capabilities for the current user */
+/* obtain the desired IAB capabilities for the current user */
 
 static char *read_capabilities_for_user(const char *user, const char *source)
 {
@@ -198,7 +205,11 @@
 					   ? cs->conf_filename:USER_CAP_FILE );
     if (conf_caps == NULL) {
 	D(("no capabilities found for user [%s]", cs->user));
-	goto cleanup_cap_s;
+	if (cs->fallback == NULL) {
+	    goto cleanup_cap_s;
+	}
+	conf_caps = strdup(cs->fallback);
+	D(("user [%s] received fallback caps [%s]", cs->user, conf_caps));
     }
 
     ssize_t conf_caps_length = strlen(conf_caps);
@@ -218,7 +229,7 @@
 	if (!cap_set_proc(cap_s)) {
 	    ok = 1;
 	}
-	goto cleanup_cap_s;
+	goto cleanup_conf;
     }
 
     iab = cap_iab_from_text(conf_caps);
@@ -233,15 +244,24 @@
     }
     cap_free(iab);
 
+    if (cs->keepcaps) {
+	/*
+	 * Best effort to set keep caps - this may help work around
+	 * situations where applications are using a capabilities
+	 * unaware setuid() call.
+	 */
+	D(("setting keepcaps"));
+	(void) cap_prctlw(PR_SET_KEEPCAPS, 1, 0, 0, 0, 0);
+    }
+
 cleanup_conf:
     memset(conf_caps, 0, conf_caps_length);
     _pam_drop(conf_caps);
 
 cleanup_cap_s:
-    if (cap_s) {
-	cap_free(cap_s);
-	cap_s = NULL;
-    }
+    cap_free(cap_s);
+    cap_s = NULL;
+
     return ok;
 }
 
@@ -260,12 +280,22 @@
 
 static void parse_args(int argc, const char **argv, struct pam_cap_s *pcs)
 {
+    D(("parsing %d module arg(s)", argc));
+
+    memset(pcs, 0, sizeof(*pcs));
+
     /* step through arguments */
     for (; argc-- > 0; ++argv) {
 	if (!strcmp(*argv, "debug")) {
 	    pcs->debug = 1;
 	} else if (!strncmp(*argv, "config=", 7)) {
 	    pcs->conf_filename = 7 + *argv;
+	} else if (!strcmp(*argv, "keepcaps")) {
+	    pcs->keepcaps = 1;
+	} else if (!strcmp(*argv, "autoauth")) {
+	    pcs->autoauth = 1;
+	} else if (!strncmp(*argv, "default=", 8)) {
+	    pcs->fallback = 8 + *argv;
 	} else {
 	    _pam_log(LOG_ERR, "unknown option; %s", *argv);
 	}
@@ -284,7 +314,6 @@
     struct pam_cap_s pcs;
     char *conf_caps;
 
-    memset(&pcs, 0, sizeof(pcs));
     parse_args(argc, argv, &pcs);
 
     retval = pam_get_user(pamh, &pcs.user, NULL);
@@ -294,6 +323,12 @@
 	return PAM_INCOMPLETE;
     }
 
+    if (pcs.autoauth) {
+	D(("pam_sm_authenticate autoauth = success"));
+	memset(&pcs, 0, sizeof(pcs));
+	return PAM_SUCCESS;
+    }
+
     if (retval != PAM_SUCCESS) {
 	D(("pam_get_user failed: %s", pam_strerror(pamh, retval)));
 	memset(&pcs, 0, sizeof(pcs));
@@ -310,9 +345,11 @@
 	   conf_caps));
 
 	/* We could also store this as a pam_[gs]et_data item for use
-	   by the setcred call to follow. As it is, there is a small
-	   race associated with a redundant read. Oh well, if you
-	   care, send me a patch.. */
+	   by the setcred call to follow. However, this precludes
+	   using pam_cap as just a cred module, and requires that the
+	   'auth' component be called first.  As it is, there is a
+	   small race associated with a redundant read of the
+	   config. */
 
 	_pam_overwrite(conf_caps);
 	_pam_drop(conf_caps);
@@ -342,7 +379,6 @@
 	return PAM_IGNORE;
     }
 
-    memset(&pcs, 0, sizeof(pcs));
     parse_args(argc, argv, &pcs);
 
     retval = pam_get_item(pamh, PAM_USER, (const void **)&pcs.user);
@@ -354,5 +390,5 @@
     retval = set_capabilities(&pcs);
     memset(&pcs, 0, sizeof(pcs));
 
-    return (retval ? PAM_SUCCESS:PAM_IGNORE );
+    return (retval ? PAM_SUCCESS:PAM_IGNORE);
 }
diff --git a/pam_cap/test_pam_cap.c b/pam_cap/test_pam_cap.c
index 452a27f..4c09a5d 100644
--- a/pam_cap/test_pam_cap.c
+++ b/pam_cap/test_pam_cap.c
@@ -5,6 +5,11 @@
  * it.
  */
 
+#define _DEFAULT_SOURCE
+
+#include <unistd.h>
+#include <sys/types.h>
+
 #include "./pam_cap.c"
 
 const char *test_groups[] = {
@@ -121,12 +126,106 @@
     cap_free(prev);
 }
 
+struct vargs {
+    struct pam_cap_s cs;
+    const char *args[5];
+};
+
+static int test_arg_parsing(void) {
+    static struct vargs vs[] = {
+	{
+	    { 1, 0, 0, NULL, NULL, NULL },
+	    { "debug", NULL }
+	},
+	{
+	    { 0, 1, 0, NULL, NULL, NULL },
+	    { "keepcaps", NULL }
+	},
+	{
+	    { 0, 0, 1, NULL, NULL, NULL },
+	    { "autoauth", NULL }
+	},
+	{
+	    { 1, 0, 1, NULL, NULL, NULL },
+	    { "autoauth", "debug", NULL }
+	},
+	{
+	    { 0, 0, 0, NULL, "/over/there", NULL },
+	    { "config=/over/there", NULL }
+	},
+	{
+	    { 0, 0, 0, NULL, NULL, "^cap_setfcap" },
+	    { "default=^cap_setfcap", NULL }
+	},
+	{
+	    { 0, 0, 0, NULL, NULL, NULL },
+	    { NULL }
+	}
+    };
+    int i;
+
+    for (i=0; ; i++) {
+	int argc;
+	const char **argv;
+	struct vargs *v;
+
+	v = &vs[i];
+	argv = v->args;
+
+	for (argc = 0; argv[argc] != NULL; argc++);
+
+	struct pam_cap_s cs;
+	parse_args(argc, argv, &cs);
+
+	if (cs.debug != v->cs.debug) {
+	    printf("test_arg_parsing[%d]: debug=%d, wanted debug=%d\n",
+		   i, cs.debug, v->cs.debug);
+	    return 1;
+	}
+	if (cs.keepcaps != v->cs.keepcaps) {
+	    printf("test_arg_parsing[%d]: keepcaps=%d, wanted keepcaps=%d\n",
+		   i, cs.keepcaps, v->cs.keepcaps);
+	    return 1;
+	}
+	if (cs.autoauth != v->cs.autoauth) {
+	    printf("test_arg_parsing[%d]: autoauth=%d, wanted autoauth=%d\n",
+		   i, cs.autoauth, v->cs.autoauth);
+	    return 1;
+	}
+	if (cs.conf_filename != v->cs.conf_filename &&
+	    strcmp(cs.conf_filename, v->cs.conf_filename)) {
+	    printf("test_arg_parsing[%d]: conf_filename=[%s], wanted=[%s]\n",
+		   i, cs.conf_filename, v->cs.conf_filename);
+	    return 1;
+	}
+	if (cs.fallback != v->cs.fallback &&
+	    strcmp(cs.fallback, v->cs.fallback)) {
+	    printf("test_arg_parsing[%d]: fallback=[%s], wanted=[%s]\n",
+		   i, cs.fallback, v->cs.fallback);
+	    return 1;
+	}
+
+	if (argc == 0) {
+	    break;
+	}
+    }
+    return 0;
+}
+
 /*
  * args: user a b i config-args...
  */
 int main(int argc, char *argv[]) {
     unsigned long int before[3], change[3], after[3];
 
+    if (test_arg_parsing()) {
+	printf("failed to parse arguments\n");
+	exit(1);
+    }
+    if (read_capabilities_for_user("morgan", "/dev/null") != NULL) {
+	printf("/dev/null is not a valid config file\n");
+    }
+
     /*
      * Start out with a cleared inheritable set.
      */
@@ -134,6 +233,12 @@
     cap_clear_flag(orig, CAP_INHERITABLE);
     cap_set_proc(orig);
 
+    if (getuid() != 0) {
+	cap_free(orig);
+	printf("test_pam_cap: OK! (Skipping privileged tests (uid!=0))\n");
+	exit(0);
+    }
+
     change[A] = strtoul(argv[2], NULL, 0);
     change[B] = strtoul(argv[3], NULL, 0);
     change[I] = strtoul(argv[4], NULL, 0);
diff --git a/progs/.gitignore b/progs/.gitignore
index 978229e..eed1982 100644
--- a/progs/.gitignore
+++ b/progs/.gitignore
@@ -5,3 +5,4 @@
 setcap
 verify-caps
 compare-cap
+uns_test
diff --git a/progs/Makefile b/progs/Makefile
index 1d7fc7a..2c3c993 100644
--- a/progs/Makefile
+++ b/progs/Makefile
@@ -4,17 +4,17 @@
 #
 # Programs: all of the examples that we will compile
 #
-PROGS=getpcaps capsh getcap setcap
+PROGS=getpcaps getcap setcap
 
 BUILD=$(PROGS)
 
-all: $(BUILD)
+all: $(BUILD) capsh
 
 ifeq ($(DYNAMIC),yes)
 LDPATH = LD_LIBRARY_PATH=../libcap
 DEPS = ../libcap/libcap.so
 else
-LDFLAGS += --static
+LDSTATIC = --static
 DEPS = ../libcap/libcap.a
 endif
 
@@ -25,28 +25,40 @@
 	make -C ../libcap libcap.so
 
 $(BUILD): %: %.o $(DEPS)
-	$(CC) $(CFLAGS) -o $@ $< $(LIBCAPLIB) $(LDFLAGS)
+	$(CC) $(CFLAGS) -o $@ $< $(LIBCAPLIB) $(LDSTATIC)
 
 %.o: %.c $(INCS)
-	$(CC) $(IPATH) $(CAPSH_SHELL) $(CFLAGS) -c $< -o $@
+	$(CC) $(IPATH) $(CFLAGS) -c $< -o $@
 
 install: all
 	mkdir -p -m 0755 $(FAKEROOT)$(SBINDIR)
-	for p in $(PROGS) ; do \
+	for p in $(PROGS) capsh ; do \
 		install -m 0755 $$p $(FAKEROOT)$(SBINDIR) ; \
 	done
 ifeq ($(RAISE_SETFCAP),yes)
 	$(FAKEROOT)$(SBINDIR)/setcap cap_setfcap=i $(FAKEROOT)$(SBINDIR)/setcap
 endif
 
-test: $(PROGS)
+test: $(PROGS) capsh
 
-tcapsh-static: capsh.c $(DEPS)
-	$(CC) $(IPATH) $(CAPSH_SHELL) $(CFLAGS) -o $@ $< $(LIBCAPLIB) $(LDFLAGS) --static
+capshdoc.h.cf: capshdoc.h ./mkcapshdoc.sh
+	./mkcapshdoc.sh > $@
+	diff -u capshdoc.h $@ || (rm $@ ; exit 1)
 
-sudotest: test tcapsh-static
+capsh: capsh.c capshdoc.h.cf $(DEPS)
+	$(CC) $(IPATH) $(CAPSH_SHELL) $(CFLAGS) -o $@ $< $(LIBCAPLIB) $(LDSTATIC)
+
+tcapsh-static: capsh.c capshdoc.h.cf $(DEPS)
+	$(CC) $(IPATH) $(CAPSH_SHELL) $(CFLAGS) -o $@ $< $(LIBCAPLIB) --static
+
+uns_test: ../tests/uns_test.c
+	$(MAKE) -C ../tests uns_test
+	cp ../tests/uns_test .
+
+sudotest: test tcapsh-static uns_test
 	sudo $(LDPATH) ./quicktest.sh
 
 clean:
 	$(LOCALCLEAN)
-	rm -f *.o $(BUILD) tcapsh* privileged ping hack.sh compare-cap
+	rm -f *.o $(BUILD) privileged ping hack.sh compare-cap uns_test
+	rm -f capsh tcapsh* capshdoc.h.cf
diff --git a/progs/capsh.c b/progs/capsh.c
index a39ceeb..50c2c99 100644
--- a/progs/capsh.c
+++ b/progs/capsh.c
@@ -14,6 +14,10 @@
 #define _DEFAULT_SOURCE
 #endif
 
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
@@ -32,6 +36,8 @@
 #define SHELL "/bin/bash"
 #endif /* ndef SHELL */
 
+#include "./capshdoc.h"
+
 #define MAX_GROUPS       100   /* max number of supplementary groups for user */
 
 static char *binary(unsigned long value)
@@ -77,33 +83,45 @@
     }
 }
 
-/* arg_print displays the current capability state of the process */
-static void arg_print(void)
+static void display_current(void)
 {
-    long set;
-    int status, j;
     cap_t all;
     char *text;
-    const char *sep;
-    struct group *g;
-    gid_t groups[MAX_GROUPS], gid;
-    uid_t uid, euid;
-    struct passwd *u, *eu;
-    cap_iab_t iab;
 
     all = cap_get_proc();
     text = cap_to_text(all, NULL);
     printf("Current: %s\n", text);
     cap_free(text);
     cap_free(all);
+}
 
-    display_prctl_set("Bounding", cap_get_bound);
-    display_prctl_set("Ambient", cap_get_ambient);
+static void display_current_iab(void)
+{
+    cap_iab_t iab;
+    char *text;
+
     iab = cap_iab_get_proc();
     text = cap_iab_to_text(iab);
     printf("Current IAB: %s\n", text);
     cap_free(text);
     cap_free(iab);
+}
+
+/* arg_print displays the current capability state of the process */
+static void arg_print(void)
+{
+    long set;
+    int status, j;
+    const char *sep;
+    struct group *g;
+    gid_t groups[MAX_GROUPS], gid;
+    uid_t uid, euid;
+    struct passwd *u, *eu;
+
+    display_current();
+    display_prctl_set("Bounding", cap_get_bound);
+    display_prctl_set("Ambient", cap_get_ambient);
+    display_current_iab();
 
     set = cap_get_secbits();
     if (set >= 0) {
@@ -336,8 +354,8 @@
  */
 static char *find_self(const char *arg0)
 {
-    int i;
-    char *parts, *dir, *scratch;
+    int i, status=1;
+    char *p = NULL, *parts, *dir, *scratch;
     const char *path;
 
     for (i = strlen(arg0)-1; i >= 0 && arg0[i] != '/'; i--);
@@ -352,21 +370,61 @@
     }
 
     parts = strdup(path);
-    scratch = malloc(2+strlen(path)+strlen(arg0));
-    if (parts == NULL || scratch == NULL) {
-        fprintf(stderr, "insufficient memory for path building\n");
+    if (parts == NULL) {
+        fprintf(stderr, "insufficient memory for parts of path\n");
 	exit(1);
     }
 
-    for (i=0; (dir = strtok(parts, ":")); parts = NULL) {
-        sprintf(scratch, "%s/%s", dir, arg0);
-	if (access(scratch, X_OK) == 0) {
-            return scratch;
-	}
+    scratch = malloc(2+strlen(path)+strlen(arg0));
+    if (scratch == NULL) {
+        fprintf(stderr, "insufficient memory for path building\n");
+	goto free_parts;
     }
 
-    fprintf(stderr, "unable to find executable '%s' in PATH\n", arg0);
-    exit(1);
+    for (p = parts; (dir = strtok(p, ":")); p = NULL) {
+        sprintf(scratch, "%s/%s", dir, arg0);
+	if (access(scratch, X_OK) == 0) {
+	    status = 0;
+	    break;
+	}
+    }
+    if (status) {
+	fprintf(stderr, "unable to find executable '%s' in PATH\n", arg0);
+	free(scratch);
+    }
+
+free_parts:
+    free(parts);
+    if (status) {
+	exit(status);
+    }
+    return scratch;
+}
+
+static long safe_sysconf(int name)
+{
+    long ans = sysconf(name);
+    if (ans <= 0) {
+	fprintf(stderr, "sysconf(%d) returned a non-positive number: %ld\n", name, ans);
+	exit(1);
+    }
+    return ans;
+}
+
+static void describe(cap_value_t cap) {
+    int j;
+    const char **lines = explanations[cap];
+    char *name = cap_to_name(cap);
+    if (cap < cap_max_bits()) {
+	printf("%s (%d)", name, cap);
+    } else {
+	printf("<reserved for> %s (%d)", name, cap);
+    }
+    cap_free(name);
+    printf(" [/proc/self/status:CapXXX: 0x%016llx]\n\n", 1ULL<<cap);
+    for (j=0; lines[j]; j++) {
+	printf("    %s\n", lines[j]);
+    }
 }
 
 int main(int argc, char *argv[], char *envp[])
@@ -617,7 +675,9 @@
 	     * Given we are now in a new directory tree, its good practice
 	     * to start off in a sane location
 	     */
-	    status = chdir("/");
+	    if (status == 0) {
+		status = chdir("/");
+	    }
 
 	    cap_free(orig);
 
@@ -718,14 +778,14 @@
 	  gid_t *group_list;
 	  int g_count;
 
-	  length = sysconf(_SC_GETGR_R_SIZE_MAX);
+	  length = safe_sysconf(_SC_GETGR_R_SIZE_MAX);
 	  buf = calloc(1, length);
 	  if (NULL == buf) {
 	    fprintf(stderr, "No memory for [%s] operation\n", argv[i]);
 	    exit(1);
 	  }
 
-	  max_groups = sysconf(_SC_NGROUPS_MAX);
+	  max_groups = safe_sysconf(_SC_NGROUPS_MAX);
 	  group_list = calloc(max_groups, sizeof(gid_t));
 	  if (NULL == group_list) {
 	    fprintf(stderr, "No memory for gid list\n");
@@ -741,8 +801,7 @@
 	    }
 	    if (!isdigit(*ptr)) {
 	      struct group *g, grp;
-	      getgrnam_r(ptr, &grp, buf, length, &g);
-	      if (NULL == g) {
+	      if (getgrnam_r(ptr, &grp, buf, length, &g) || NULL == g) {
 		fprintf(stderr, "Failed to identify gid for group [%s]\n", ptr);
 		exit(1);
 	      }
@@ -835,6 +894,7 @@
 	    argv[argc] = NULL;
 	    execve(argv[i], argv+i, envp);
 	    fprintf(stderr, "execve '%s' failed!\n", argv[i]);
+	    free(argv[i]);
 	    exit(1);
 	} else if (!strncmp("--shell=", argv[i], 8)) {
 	    shell = argv[i]+8;
@@ -923,26 +983,77 @@
 	    }
 	} else if (!strcmp("--license", argv[i])) {
 	    printf(
-		"%s has a you choose license: BSD 3-clause or GPL2\n"
-		"Copyright (c) 2008-11,16,19,2020 Andrew G. Morgan"
+		"%s see LICENSE file for details.\n"
+		"Copyright (c) 2008-11,16,19-21 Andrew G. Morgan"
 		" <morgan@kernel.org>\n", argv[0]);
 	    exit(0);
+	} else if (!strncmp("--explain=", argv[i], 10)) {
+	    cap_value_t cap;
+	    if (cap_from_name(argv[i]+10, &cap) != 0) {
+		fprintf(stderr, "unrecognised value '%s'\n", argv[i]+10);
+		exit(1);
+	    }
+	    if (cap < 0) {
+		fprintf(stderr, "negative capability (%d) invalid\n", cap);
+		exit(1);
+	    }
+	    if (cap < CAPSH_DOC_LIMIT) {
+		describe(cap);
+		continue;
+	    }
+	    if (cap < cap_max_bits()) {
+		printf("<unnamed in libcap> (%d)", cap);
+	    } else {
+		printf("<unsupported> (%d)", cap);
+	    }
+	    printf(" [/proc/self/status:CapXXX: 0x%016llx]\n", 1ULL<<cap);
+	} else if (!strncmp("--suggest=", argv[i], 10)) {
+	    cap_value_t cap;
+	    int hits = 0;
+	    for (cap=0; cap < CAPSH_DOC_LIMIT; cap++) {
+		const char **lines = explanations[cap];
+		int j;
+		char *name = cap_to_name(cap);
+		char *match = strcasestr(name, argv[i]+10);
+		cap_free(name);
+		if (match != NULL) {
+		    if (hits++) {
+			printf("\n");
+		    }
+		    describe(cap);
+		    continue;
+		}
+		for (j=0; lines[j]; j++) {
+		    if (strcasestr(lines[j], argv[i]+10) != NULL) {
+			if (hits++) {
+			    printf("\n");
+			}
+			describe(cap);
+			break;
+		    }
+		}
+	    }
+	} else if (strcmp("--current", argv[i]) == 0) {
+	    display_current();
+	    display_current_iab();
 	} else {
 	usage:
 	    printf("usage: %s [args ...]\n"
-		   "  --has-a=xxx    exit 1 if capability xxx not ambient\n"
-		   "  --has-ambient  exit 1 unless ambient vector supported\n"
 		   "  --addamb=xxx   add xxx,... capabilities to ambient set\n"
 		   "  --cap-uid=<n>  use libcap cap_setuid() to change uid\n"
 		   "  --caps=xxx     set caps as per cap_from_text()\n"
 		   "  --chroot=path  chroot(2) to this path\n"
+		   "  --current      show current caps and IAB vectors\n"
 		   "  --decode=xxx   decode a hex string to a list of caps\n"
 		   "  --delamb=xxx   remove xxx,... capabilities from ambient\n"
+		   "  --explain=xxx  explain what capability xxx permits\n"
 		   "  --forkfor=<n>  fork and make child sleep for <n> sec\n"
 		   "  --gid=<n>      set gid to <n> (hint: id <username>)\n"
 		   "  --groups=g,... set the supplemental groups\n"
-		   "  --has-p=xxx    exit 1 if capability xxx not permitted\n"
+		   "  --has-a=xxx    exit 1 if capability xxx not ambient\n"
+		   "  --has-ambient  exit 1 unless ambient vector supported\n"
 		   "  --has-i=xxx    exit 1 if capability xxx not inheritable\n"
+		   "  --has-p=xxx    exit 1 if capability xxx not permitted\n"
 		   "  --has-no-new-privs  exit 1 if privs not limited\n"
 		   "  --help, -h     this message (or try 'man capsh')\n"
 		   "  --iab=...      use cap_iab_from_text() to set iab\n"
@@ -960,6 +1071,7 @@
 		   "  --print        display capability relevant state\n"
 		   "  --secbits=<n>  write a new value for securebits\n"
 		   "  --shell=/xx/yy use /xx/yy instead of " SHELL " for --\n"
+		   "  --suggest=text search cap descriptions for text\n"
 		   "  --supports=xxx exit 1 if capability xxx unsupported\n"
 		   "  --uid=<n>      set uid to <n> (hint: id <username>)\n"
                    "  --user=<name>  set uid,gid and groups to that of user\n"
diff --git a/progs/capshdoc.h b/progs/capshdoc.h
new file mode 100644
index 0000000..c182144
--- /dev/null
+++ b/progs/capshdoc.h
@@ -0,0 +1,413 @@
+#ifdef CAPSHDOC
+#error "don't include this twice"
+#endif
+#define CAPSHDOC
+
+/*
+ * A line by line explanation of each named capability value
+ */
+static const char *explanation0[] = {  /* cap_chown = 0 */
+    "Allows a process to arbitrarily change the user and",
+    "group ownership of a file.",
+    NULL
+};
+static const char *explanation1[] = {  /* cap_dac_override = 1 */
+    "Allows a process to override of all Discretionary",
+    "Access Control (DAC) access, including ACL execute",
+    "access. That is read, write or execute files that the",
+    "process would otherwise not have access to. This",
+    "excludes DAC access covered by CAP_LINUX_IMMUTABLE.",
+    NULL
+};
+static const char *explanation2[] = {  /* cap_dac_read_search = 2 */
+    "Allows a process to override all DAC restrictions",
+    "limiting the read and search of files and",
+    "directories. This excludes DAC access covered by",
+    "CAP_LINUX_IMMUTABLE.",
+    NULL
+};
+static const char *explanation3[] = {  /* cap_fowner = 3 */
+    "Allows a process to perform operations on files, even",
+    "where file owner ID should otherwise need be equal to",
+    "the UID, except where CAP_FSETID is applicable. It",
+    "doesn't override MAC and DAC restrictions.",
+    NULL
+};
+static const char *explanation4[] = {  /* cap_fsetid = 4 */
+    "Allows a process to set the S_ISUID and S_ISUID bits of",
+    "the file permissions, even when the process' effective",
+    "UID or GID/supplementary GIDs do not match that of the",
+    "file.",
+    NULL
+};
+static const char *explanation5[] = {  /* cap_kill = 5 */
+    "Allows a process to send a kill(2) signal to any other",
+    "process - overriding the limitation that there be a",
+    "[E]UID match between source and target process.",
+    NULL
+};
+static const char *explanation6[] = {  /* cap_setgid = 6 */
+    "Allows a process to freely manipulate its own GIDs:",
+    "  - arbitrarily set the GID, EGID, REGID, RESGID values",
+    "  - arbitrarily set the supplementary GIDs",
+    "  - allows the forging of GID credentials passed over a",
+    "    socket",
+    NULL
+};
+static const char *explanation7[] = {  /* cap_setuid = 7 */
+    "Allows a process to freely manipulate its own UIDs:",
+    "  - arbitrarily set the UID, EUID, REUID and RESUID",
+    "    values",
+    "  - allows the forging of UID credentials passed over a",
+    "    socket",
+    NULL
+};
+static const char *explanation8[] = {  /* cap_setpcap = 8 */
+    "Allows a process to freely manipulate its inheritable",
+    "capabilities.  Linux supports the POSIX.1e Inheritable",
+    "set, as well as Bounding and Ambient Linux extension",
+    "vectors. This capability permits dropping bits from the",
+    "Bounding vector. It also permits the process to raise",
+    "Ambient vector bits that are both raised in the",
+    "Permitted and Inheritable sets of the process. This",
+    "capability cannot be used to raise Permitted bits, or",
+    "Effective bits beyond those already present in the",
+    "process' permitted set.",
+    "",
+    "[Historical note: prior to the advent of file",
+    "capabilities (2008), this capability was suppressed by",
+    "default, as its unsuppressed behavior was not",
+    "auditable: it could asynchronously grant its own",
+    "Permitted capabilities to and remove capabilities from",
+    "other processes arbitrarily. The former leads to",
+    "undefined behavior, and the latter is better served by",
+    "the kill system call.]",
+    NULL
+};
+static const char *explanation9[] = {  /* cap_linux_immutable = 9 */
+    "Allows a process to modify the S_IMMUTABLE and",
+    "S_APPEND file attributes.",
+    NULL
+};
+static const char *explanation10[] = {  /* cap_net_bind_service = 10 */
+    "Allows a process to bind to privileged ports:",
+    "  - TCP/UDP sockets below 1024",
+    "  - ATM VCIs below 32",
+    NULL
+};
+static const char *explanation11[] = {  /* cap_net_broadcast = 11 */
+    "Allows a process to broadcast to the network and to",
+    "listen to multicast.",
+    NULL
+};
+static const char *explanation12[] = {  /* cap_net_admin = 12 */
+    "Allows a process to perform network configuration",
+    "operations:",
+    "  - interface configuration",
+    "  - administration of IP firewall, masquerading and",
+    "    accounting",
+    "  - setting debug options on sockets",
+    "  - modification of routing tables",
+    "  - setting arbitrary process, and process group",
+    "    ownership on sockets",
+    "  - binding to any address for transparent proxying",
+    "    (this is also allowed via CAP_NET_RAW)",
+    "  - setting TOS (Type of service)",
+    "  - setting promiscuous mode",
+    "  - clearing driver statistics",
+    "  - multicasing",
+    "  - read/write of device-specific registers",
+    "  - activation of ATM control sockets",
+    NULL
+};
+static const char *explanation13[] = {  /* cap_net_raw = 13 */
+    "Allows a process to use raw networking:",
+    "  - RAW sockets",
+    "  - PACKET sockets",
+    "  - binding to any address for transparent proxying",
+    "    (also permitted via CAP_NET_ADMIN)",
+    NULL
+};
+static const char *explanation14[] = {  /* cap_ipc_lock = 14 */
+    "Allows a process to lock shared memory segments for IPC",
+    "purposes.  Also enables mlock and mlockall system",
+    "calls.",
+    NULL
+};
+static const char *explanation15[] = {  /* cap_ipc_owner = 15 */
+    "Allows a process to override IPC ownership checks.",
+    NULL
+};
+static const char *explanation16[] = {  /* cap_sys_module = 16 */
+    "Allows a process to initiate the loading and unloading",
+    "of kernel modules. This capability can effectively",
+    "modify kernel without limit.",
+    NULL
+};
+static const char *explanation17[] = {  /* cap_sys_rawio = 17 */
+    "Allows a process to perform raw IO:",
+    "  - permit ioper/iopl access",
+    "  - permit sending USB messages to any device via",
+    "    /dev/bus/usb",
+    NULL
+};
+static const char *explanation18[] = {  /* cap_sys_chroot = 18 */
+    "Allows a process to perform a chroot syscall to change",
+    "the effective root of the process' file system:",
+    "redirect to directory \"/\" to some other location.",
+    NULL
+};
+static const char *explanation19[] = {  /* cap_sys_ptrace = 19 */
+    "Allows a process to perform a ptrace() of any other",
+    "process.",
+    NULL
+};
+static const char *explanation20[] = {  /* cap_sys_pacct = 20 */
+    "Allows a process to configure process accounting.",
+    NULL
+};
+static const char *explanation21[] = {  /* cap_sys_admin = 21 */
+    "Allows a process to perform a somewhat arbitrary",
+    "grab-bag of privileged operations. Over time, this",
+    "capability should weaken as specific capabilities are",
+    "created for subsets of CAP_SYS_ADMINs functionality:",
+    "  - configuration of the secure attention key",
+    "  - administration of the random device",
+    "  - examination and configuration of disk quotas",
+    "  - setting the domainname",
+    "  - setting the hostname",
+    "  - calling bdflush()",
+    "  - mount() and umount(), setting up new SMB connection",
+    "  - some autofs root ioctls",
+    "  - nfsservctl",
+    "  - VM86_REQUEST_IRQ",
+    "  - to read/write pci config on alpha",
+    "  - irix_prctl on mips (setstacksize)",
+    "  - flushing all cache on m68k (sys_cacheflush)",
+    "  - removing semaphores",
+    "  - Used instead of CAP_CHOWN to \"chown\" IPC message",
+    "    queues, semaphores and shared memory",
+    "  - locking/unlocking of shared memory segment",
+    "  - turning swap on/off",
+    "  - forged pids on socket credentials passing",
+    "  - setting readahead and flushing buffers on block",
+    "    devices",
+    "  - setting geometry in floppy driver",
+    "  - turning DMA on/off in xd driver",
+    "  - administration of md devices (mostly the above, but",
+    "    some extra ioctls)",
+    "  - tuning the ide driver",
+    "  - access to the nvram device",
+    "  - administration of apm_bios, serial and bttv (TV)",
+    "    device",
+    "  - manufacturer commands in isdn CAPI support driver",
+    "  - reading non-standardized portions of PCI",
+    "    configuration space",
+    "  - DDI debug ioctl on sbpcd driver",
+    "  - setting up serial ports",
+    "  - sending raw qic-117 commands",
+    "  - enabling/disabling tagged queuing on SCSI",
+    "    controllers and sending arbitrary SCSI commands",
+    "  - setting encryption key on loopback filesystem",
+    "  - setting zone reclaim policy",
+    NULL
+};
+static const char *explanation22[] = {  /* cap_sys_boot = 22 */
+    "Allows a process to initiate a reboot of the system.",
+    NULL
+};
+static const char *explanation23[] = {  /* cap_sys_nice = 23 */
+    "Allows a process to maipulate the execution priorities",
+    "of arbitrary processes:",
+    "  - those involving different UIDs",
+    "  - setting their CPU affinity",
+    "  - alter the FIFO vs. round-robin (realtime)",
+    "    scheduling for itself and other processes.",
+    NULL
+};
+static const char *explanation24[] = {  /* cap_sys_resource = 24 */
+    "Allows a process to adjust resource related parameters",
+    "of processes and the system:",
+    "  - set and override resource limits",
+    "  - override quota limits",
+    "  - override the reserved space on ext2 filesystem",
+    "    (this can also be achieved via CAP_FSETID)",
+    "  - modify the data journaling mode on ext3 filesystem,",
+    "    which uses journaling resources",
+    "  - override size restrictions on IPC message queues",
+    "  - configure more than 64Hz interrupts from the",
+    "    real-time clock",
+    "  - override the maximum number of consoles for console",
+    "    allocation",
+    "  - override the maximum number of keymaps",
+    NULL
+};
+static const char *explanation25[] = {  /* cap_sys_time = 25 */
+    "Allows a process to perform time manipulation of clocks:",
+    "  - alter the system clock",
+    "  - enable irix_stime on MIPS",
+    "  - set the real-time clock",
+    NULL
+};
+static const char *explanation26[] = {  /* cap_sys_tty_config = 26 */
+    "Allows a process to manipulate tty devices:",
+    "  - configure tty devices",
+    "  - perform vhangup() of a tty",
+    NULL
+};
+static const char *explanation27[] = {  /* cap_mknod = 27 */
+    "Allows a process to perform privileged operations with",
+    "the mknod() system call.",
+    NULL
+};
+static const char *explanation28[] = {  /* cap_lease = 28 */
+    "Allows a process to take leases on files.",
+    NULL
+};
+static const char *explanation29[] = {  /* cap_audit_write = 29 */
+    "Allows a process to write to the audit log via a",
+    "unicast netlink socket.",
+    NULL
+};
+static const char *explanation30[] = {  /* cap_audit_control = 30 */
+    "Allows a process to configure audit logging via a",
+    "unicast netlink socket.",
+    NULL
+};
+static const char *explanation31[] = {  /* cap_setfcap = 31 */
+    "Allows a process to set capabilities on files.",
+    "Permits a process to uid_map the uid=0 of the",
+    "parent user namespace into that of the child",
+    "namespace. Also, permits a process to override",
+    "securebits locks through user namespace",
+    "creation.",
+    NULL
+};
+static const char *explanation32[] = {  /* cap_mac_override = 32 */
+    "Allows a process to override Manditory Access Control",
+    "(MAC) access. Not all kernels are configured with a MAC",
+    "mechanism, but this is the capability reserved for",
+    "overriding them.",
+    NULL
+};
+static const char *explanation33[] = {  /* cap_mac_admin = 33 */
+    "Allows a process to configure the Mandatory Access",
+    "Control (MAC) policy. Not all kernels are configured",
+    "with a MAC enabled, but if they are this capability is",
+    "reserved for code to perform administration tasks.",
+    NULL
+};
+static const char *explanation34[] = {  /* cap_syslog = 34 */
+    "Allows a process to configure the kernel's syslog",
+    "(printk) behavior.",
+    NULL
+};
+static const char *explanation35[] = {  /* cap_wake_alarm = 35 */
+    "Allows a process to trigger something that can wake the",
+    "system up.",
+    NULL
+};
+static const char *explanation36[] = {  /* cap_block_suspend = 36 */
+    "Allows a process to block system suspends - prevent the",
+    "system from entering a lower power state.",
+    NULL
+};
+static const char *explanation37[] = {  /* cap_audit_read = 37 */
+    "Allows a process to read the audit log via a multicast",
+    "netlink socket.",
+    NULL
+};
+static const char *explanation38[] = {  /* cap_perfmon = 38 */
+    "Allows a process to enable observability of privileged",
+    "operations related to performance. The mechanisms",
+    "include perf_events, i915_perf and other kernel",
+    "subsystems.",
+    NULL
+};
+static const char *explanation39[] = {  /* cap_bpf = 39 */
+    "Allows a process to manipulate aspects of the kernel",
+    "enhanced Berkeley Packet Filter (BPF) system. This is",
+    "an execution subsystem of the kernel, that manages BPF",
+    "programs. CAP_BPF permits a process to:",
+    "  - create all types of BPF maps",
+    "  - advanced verifier features:",
+    "    - indirect variable access",
+    "    - bounded loops",
+    "    - BPF to BPF function calls",
+    "    - scalar precision tracking",
+    "    - larger complexity limits",
+    "    - dead code elimination",
+    "    - potentially other features",
+    "",
+    "Other capabilities can be used together with CAP_BFP to",
+    "further manipulate the BPF system:",
+    "  - CAP_PERFMON relaxes the verifier checks as follows:",
+    "    - BPF programs can use pointer-to-integer",
+    "      conversions",
+    "    - speculation attack hardening measures can be",
+    "      bypassed",
+    "    - bpf_probe_read to read arbitrary kernel memory is",
+    "      permitted",
+    "    - bpf_trace_printk to print the content of kernel",
+    "      memory",
+    "  - CAP_SYS_ADMIN permits the following:",
+    "    - use of bpf_probe_write_user",
+    "    - iteration over the system-wide loaded programs,",
+    "      maps, links BTFs and convert their IDs to file",
+    "      descriptors.",
+    "  - CAP_PERFMON is required to load tracing programs.",
+    "  - CAP_NET_ADMIN is required to load networking",
+    "    programs.",
+    NULL
+};
+static const char *explanation40[] = {  /* cap_checkpoint_restore = 40 */
+    "Allows a process to perform checkpoint",
+    "and restore operations. Also permits",
+    "explicit PID control via clone3() and",
+    "also writing to ns_last_pid.",
+    NULL
+};
+static const char **explanations[] = {
+    explanation0,
+    explanation1,
+    explanation2,
+    explanation3,
+    explanation4,
+    explanation5,
+    explanation6,
+    explanation7,
+    explanation8,
+    explanation9,
+    explanation10,
+    explanation11,
+    explanation12,
+    explanation13,
+    explanation14,
+    explanation15,
+    explanation16,
+    explanation17,
+    explanation18,
+    explanation19,
+    explanation20,
+    explanation21,
+    explanation22,
+    explanation23,
+    explanation24,
+    explanation25,
+    explanation26,
+    explanation27,
+    explanation28,
+    explanation29,
+    explanation30,
+    explanation31,
+    explanation32,
+    explanation33,
+    explanation34,
+    explanation35,
+    explanation36,
+    explanation37,
+    explanation38,
+    explanation39,
+    explanation40,
+};
+#define CAPSH_DOC_LIMIT 41
diff --git a/progs/getcap.c b/progs/getcap.c
index 208bd6a..eec733b 100644
--- a/progs/getcap.c
+++ b/progs/getcap.c
@@ -96,8 +96,8 @@
 	case 'h':
 	    usage(0);
 	case 'l':
-	    printf("%s has a you choose license: BSD 3-clause or GPL2\n"
-		"Copyright (c) 1997,2007 Andrew G. Morgan"
+	    printf("%s see LICENSE file for details.\n"
+		"Copyright (c) 1997,2007,2021 Andrew G. Morgan"
 		" <morgan@kernel.org>\n", argv[0]);
 	    exit(0);
 	default:
diff --git a/progs/getpcaps.c b/progs/getpcaps.c
index 5bc511e..5e78487 100644
--- a/progs/getpcaps.c
+++ b/progs/getpcaps.c
@@ -44,8 +44,9 @@
 	    !strcmp(argv[0], "-h")) {
 	    usage(0);
 	} else if (!strcmp(argv[0], "--license")) {
-	    printf("%s has a you choose license: BSD 3-clause or GPL2\n"
-"[Copyright (c) 1997-8,2007,2019 Andrew G. Morgan <morgan@kernel.org>]\n",
+	    printf("%s see LICENSE file for details.\n"
+		   "[Copyright (c) 1997-8,2007,19,21"
+		   " Andrew G. Morgan <morgan@kernel.org>]\n",
 		   argv[0]);
 	    exit(0);
 	} else if (!strcmp(argv[0], "--verbose")) {
diff --git a/progs/mkcapshdoc.sh b/progs/mkcapshdoc.sh
new file mode 100755
index 0000000..705d526
--- /dev/null
+++ b/progs/mkcapshdoc.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+# This script generates some C code for inclusion in the capsh binary.
+# The Makefile generally only generates the .h code and compares it
+# with the checked in code in the progs directory.
+
+cat<<EOF
+#ifdef CAPSHDOC
+#error "don't include this twice"
+#endif
+#define CAPSHDOC
+
+/*
+ * A line by line explanation of each named capability value
+ */
+EOF
+
+let x=0
+while [ -f "../doc/values/${x}.txt" ]; do
+    name=$(fgrep ",${x}}" ../libcap/cap_names.list.h|sed -e 's/{"//' -e 's/",/ = /' -e 's/},//')
+    echo "static const char *explanation${x}[] = {  /* ${name} */"
+    sed -e 's/"/\\"/g' -e 's/^/    "/' -e 's/$/",/' "../doc/values/${x}.txt"
+    let x=1+${x}
+    echo "    NULL"
+    echo "};"
+done
+
+cat<<EOF
+static const char **explanations[] = {
+EOF
+let y=0
+while [ "${y}" -lt "${x}" ]; do
+    echo "    explanation${y},"
+    let y=1+${y}
+done
+cat<<EOF
+};
+#define CAPSH_DOC_LIMIT ${x}
+EOF
diff --git a/progs/quicktest.sh b/progs/quicktest.sh
index 6aa2598..ba64ab5 100755
--- a/progs/quicktest.sh
+++ b/progs/quicktest.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 #
 # Run through a series of tests to try out the various capability
-# manipulations posible through exec.
+# manipulations possible through exec.
 #
 # [Run this as root in a root-enabled process tree.]
 
@@ -43,6 +43,7 @@
 }
 
 pass_capsh --print
+pass_capsh --current
 
 # Validate that PATH expansion works
 PATH=$(/bin/pwd)/junk:$(/bin/pwd) capsh == == == --modes
@@ -89,7 +90,7 @@
 /bin/chmod u+s tcapsh
 /bin/ls -l tcapsh
 
-# leverage keep caps to maintain capabilities accross a change of euid
+# leverage keep caps to maintain capabilities across a change of euid
 # from setuid root to capable luser (as per wireshark/dumpcap 0.99.7)
 # This test is subtle. It is testing that a change to self, dropping
 # euid=0 back to that of the luser keeps capabilities.
@@ -204,7 +205,7 @@
 
     # Next force the privileged binary to have an empty capability set.
     # This is sort of the opposite of privileged - it should ensure that
-    # the file can never aquire privilege by the ambient method.
+    # the file can never acquire privilege by the ambient method.
     ./setcap = ./privileged
     fail_capsh --keep=1 --uid=$nouid --inh=cap_setuid --addamb=cap_setuid -- -c "./privileged --print --uid=1"
 
@@ -259,4 +260,11 @@
 fi
 rm -f compare-cap
 
+echo "attempt to exploit kernel bug"
+./uns_test
+if [ $? -ne 0 ]; then
+    echo "upgrade your kernel"
+    exit 1
+fi
+
 echo "ALL TESTS PASSED!"
diff --git a/progs/setcap.c b/progs/setcap.c
index 930429a..e28b1c7 100644
--- a/progs/setcap.c
+++ b/progs/setcap.c
@@ -94,7 +94,7 @@
 	}
 	if (!strcmp("--license", *argv)) {
 	    printf(
-		"%s has a you choose license: BSD 3-clause or GPL2\n"
+		"%s see LICENSE file for details.\n"
 		"Copyright (c) 1997,2007-8,2020 Andrew G. Morgan"
 		" <morgan@kernel.org>\n", argv[0]);
 	    exit(0);
diff --git a/psx/LICENSE b/psx/License
similarity index 98%
rename from psx/LICENSE
rename to psx/License
index e2574a7..2645a87 100644
--- a/psx/LICENSE
+++ b/psx/License
@@ -1,8 +1,16 @@
 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:
+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:
@@ -20,13 +28,6 @@
    products derived from this software without their specific prior
    written permission.
 
-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.)
-
 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.
@@ -38,7 +39,17 @@
 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:
diff --git a/psx/README b/psx/README
index cd9c651..e4f9001 100644
--- a/psx/README
+++ b/psx/README
@@ -23,6 +23,6 @@
 
 Like libcap/libpsx itself, the "psx" package is distributed with a
 "you choose" License. Specifically: BSD three clause, or GPL2. See the
-LICENSE file.
+License file.
 
 Andrew G. Morgan <morgan@kernel.org>
diff --git a/psx/doc.go b/psx/doc.go
index e6f9013..c4ba829 100644
--- a/psx/doc.go
+++ b/psx/doc.go
@@ -1,5 +1,5 @@
 // Package psx provides support for system calls that are run
-// simultanously on all threads under Linux.
+// simultaneously on all threads under Linux.
 //
 // This property can be used to work around a historical lack of
 // native Go support for such a feature. Something that is the subject
@@ -12,9 +12,9 @@
 //
 // In the former case, psx is a low overhead wrapper for the two
 // native go calls: syscall.AllThreadsSyscall() and
-// syscall.AllThreadsSyscall6() [expected to be] introduced in
-// go1.16. We provide this wrapping to minimize client source code
-// changes when compiling with or without CGo enabled.
+// syscall.AllThreadsSyscall6() introduced in go1.16. We provide this
+// wrapping to minimize client source code changes when compiling with
+// or without CGo enabled.
 //
 // In the latter case, and toolchains prior to go1.16, it works via
 // CGo wrappers for system call functions that call the C [lib]psx
diff --git a/psx/psx.c b/psx/psx.c
index 4de3653..90dcc50 100644
--- a/psx/psx.c
+++ b/psx/psx.c
@@ -229,7 +229,7 @@
     psx_tracker.psx_sig = SIGSYS;
 
     psx_confirm_sigaction();
-    psx_do_registration(); // register the main thread.
+    psx_do_registration(); /* register the main thread. */
 
     psx_tracker.initialized = 1;
 }
@@ -454,6 +454,10 @@
 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;
     /*
diff --git a/psx/psx.go b/psx/psx.go
index 529f19d..77648e2 100644
--- a/psx/psx.go
+++ b/psx/psx.go
@@ -3,13 +3,17 @@
 
 package psx // import "kernel.org/pub/linux/libs/security/libcap/psx"
 
-import (
-	"syscall"
-)
+import "syscall"
 
-// Syscall3 and Syscall6 are aliases for syscall.AllThreadsSyscall*
-// when compiled CGO_ENABLED=0.
-var (
-	Syscall3 = syscall.AllThreadsSyscall
-	Syscall6 = syscall.AllThreadsSyscall6
-)
+// Documentation for these functions are provided in the psx_cgo.go
+// file.
+
+//go:uintptrescapes
+func Syscall3(syscallnr, arg1, arg2, arg3 uintptr) (uintptr, uintptr, syscall.Errno) {
+	return syscall.AllThreadsSyscall(syscallnr, arg1, arg2, arg3)
+}
+
+//go:uintptrescapes
+func Syscall6(syscallnr, arg1, arg2, arg3, arg4, arg5, arg6 uintptr) (uintptr, uintptr, syscall.Errno) {
+	return syscall.AllThreadsSyscall6(syscallnr, arg1, arg2, arg3, arg4, arg5, arg6)
+}
diff --git a/psx/psx_cgo.go b/psx/psx_cgo.go
index c17b4f3..26aa15a 100644
--- a/psx/psx_cgo.go
+++ b/psx/psx_cgo.go
@@ -32,11 +32,21 @@
 	return int(C.__errno_too(C.long(v)))
 }
 
-// Syscall3 performs a 3 argument syscall using the libpsx C function
-// psx_syscall3(). Syscall3 differs from syscall.[Raw]Syscall()
-// insofar as it is simultaneously executed on every pthread of the
-// combined Go and CGo runtimes.
+//go:uintptrescapes
+
+// Syscall3 performs a 3 argument syscall. Syscall3 differs from
+// syscall.[Raw]Syscall() insofar as it is simultaneously executed on
+// every thread of the combined Go and CGo runtimes. It works
+// differently depending on whether CGO_ENABLED is 1 or 0 at compile
+// time.
+//
+// If CGO_ENABLED=1 it uses the libpsx function C.psx_syscall3().
+//
+// If CGO_ENABLED=0 it redirects to the go1.16+
+// syscall.AllThreadsSyscall() function.
 func Syscall3(syscallnr, arg1, arg2, arg3 uintptr) (uintptr, uintptr, syscall.Errno) {
+	// We lock to the OSThread here because we may need errno to
+	// be the one for this thread.
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
@@ -48,11 +58,15 @@
 	return uintptr(v), uintptr(v), errno
 }
 
-// Syscall6 performs a 6 argument syscall using the libpsx C function
-// psx_syscall6(). Syscall6 differs from syscall.[Raw]Syscall6() insofar as
-// it is simultaneously executed on every pthread of the combined Go
-// and CGo runtimes.
+//go:uintptrescapes
+
+// Syscall6 performs a 6 argument syscall on every thread of the
+// combined Go and CGo runtimes. Other than the number of syscall
+// arguments, its behavior is identical to that of Syscall3() - see
+// above for the full documentation.
 func Syscall6(syscallnr, arg1, arg2, arg3, arg4, arg5, arg6 uintptr) (uintptr, uintptr, syscall.Errno) {
+	// We lock to the OSThread here because we may need errno to
+	// be the one for this thread.
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
diff --git a/psx/psx_syscall.h b/psx/psx_syscall.h
index 4aacfab..3987d59 100644
--- a/psx/psx_syscall.h
+++ b/psx/psx_syscall.h
@@ -59,7 +59,7 @@
  * 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 useage.
+ * example of this usage.
  */
 void psx_load_syscalls(long int (**syscall_fn)(long int,
 					       long int, long int, long int),
diff --git a/tests/.gitignore b/tests/.gitignore
index ac7ffb0..d0b3f15 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -5,3 +5,4 @@
 libcap_psx_launch_test
 exploit
 noexploit
+uns_test
diff --git a/tests/Makefile b/tests/Makefile
index 1e7039d..7ce8776 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -8,9 +8,9 @@
 #
 
 all:
-	make libcap_launch_test
+	$(MAKE) libcap_launch_test uns_test
 ifeq ($(PTHREADS),yes)
-	make psx_test libcap_psx_test libcap_psx_launch_test
+	$(MAKE) psx_test libcap_psx_test libcap_psx_launch_test
 endif
 
 install: all
@@ -22,7 +22,7 @@
 DEPS += ../libcap/libpsx.so
 endif
 else
-LDFLAGS += --static
+LDSTATIC = --static
 DEPS=../libcap/libcap.a
 ifeq ($(PTHREADS),yes)
 DEPS +=  ../libcap/libpsx.a
@@ -30,31 +30,32 @@
 endif
 
 ../libcap/libcap.so:
-	make -C ../libcap libcap.so
+	$(MAKE) -C ../libcap libcap.so
 
 ../libcap/libcap.a:
-	make -C ../libcap libcap.a
+	$(MAKE) -C ../libcap libcap.a
 
 ifeq ($(PTHREADS),yes)
 ../libcap/libpsx.so:
-	make -C ../libcap libpsx.so
+	$(MAKE) -C ../libcap libpsx.so
 
 ../libcap/libpsx.a:
-	make -C ../libcap libpsx.a
+	$(MAKE) -C ../libcap libpsx.a
 endif
 
 ../progs/tcapsh-static:
-	make -C ../progs tcapsh-static
+	$(MAKE) -C ../progs tcapsh-static
 
 test:
 ifeq ($(PTHREADS),yes)
-	make run_psx_test run_libcap_psx_test
+	$(MAKE) run_psx_test run_libcap_psx_test
 endif
 
 sudotest: test
-	make run_libcap_launch_test
+	$(MAKE) run_uns_test
+	$(MAKE) run_libcap_launch_test
 ifeq ($(PTHREADS),yes)
-	make run_libcap_psx_launch_test run_exploit_test
+	$(MAKE) run_libcap_psx_launch_test run_exploit_test
 endif
 
 # unprivileged
@@ -62,15 +63,21 @@
 	./psx_test
 
 psx_test: psx_test.c $(DEPS)
-	$(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LDFLAGS)
+	$(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LDSTATIC)
 
 run_libcap_psx_test: libcap_psx_test
 	./libcap_psx_test
 
 libcap_psx_test: libcap_psx_test.c $(DEPS)
-	$(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) $(LDFLAGS)
+	$(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) $(LDSTATIC)
 
 # privileged
+uns_test: uns_test.c $(DEPS)
+	$(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LDSTATIC)
+
+run_uns_test: uns_test
+	echo exit | sudo ./uns_test
+
 run_libcap_launch_test: libcap_launch_test noop ../progs/tcapsh-static
 	sudo ./libcap_launch_test
 
@@ -78,13 +85,13 @@
 	sudo ./libcap_psx_launch_test
 
 libcap_launch_test: libcap_launch_test.c $(DEPS)
-	$(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LDFLAGS)
+	$(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LDSTATIC)
 
 # This varies only slightly from the above insofar as it currently
 # only links in the pthreads fork support. TODO() we need to change
 # the source to do something interesting with pthreads.
 libcap_psx_launch_test: libcap_launch_test.c $(DEPS)
-	$(CC) $(CFLAGS) $(IPATH) -DWITH_PTHREADS $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) $(LDFLAGS)
+	$(CC) $(CFLAGS) $(IPATH) -DWITH_PTHREADS $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) $(LDSTATIC)
 
 
 # This test demonstrates that libpsx is needed to secure multithreaded
@@ -99,18 +106,18 @@
 	$(CC) $(CFLAGS) $(IPATH) -c $<
 
 exploit: exploit.o $(DEPS)
-	$(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) -lpthread $(LDFLAGS)
+	$(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) -lpthread $(LDSTATIC)
 
 # Note, for some reason, the order of libraries is important to avoid
 # the exploit working for dynamic linking.
 noexploit: exploit.o $(DEPS)
-	$(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LIBCAPLIB) $(LDFLAGS)
+	$(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LIBCAPLIB) $(LDSTATIC)
 
 # This one runs in a chroot with no shared library files.
 noop: noop.c
 	$(CC) $(CFLAGS) $< -o $@ --static
 
 clean:
-	rm -f psx_test libcap_psx_test libcap_launch_test *~
+	rm -f psx_test libcap_psx_test libcap_launch_test uns_test *~
 	rm -f libcap_launch_test libcap_psx_launch_test core noop
 	rm -f exploit noexploit exploit.o
diff --git a/tests/exploit.c b/tests/exploit.c
index 28bac88..814337c 100644
--- a/tests/exploit.c
+++ b/tests/exploit.c
@@ -16,6 +16,10 @@
  * to execute arbitrary code. As such, if all but one thread drops
  * privilege, privilege escalation is somewhat trivial.
  */
+
+/* as per "man sigaction" */
+#define _POSIX_C_SOURCE 200809L
+
 #include <pthread.h>
 #include <signal.h>
 #include <stdio.h>
@@ -148,7 +152,8 @@
     if (greatest_len != 1) {
 	printf("exploit succeeded\n");
 	exit(1);
-    } else {
-	printf("exploit failed\n");
     }
+
+    printf("exploit failed\n");
+    exit(0);
 }
diff --git a/tests/libcap_launch_test.c b/tests/libcap_launch_test.c
index bba38c6..f45b2b7 100644
--- a/tests/libcap_launch_test.c
+++ b/tests/libcap_launch_test.c
@@ -24,7 +24,9 @@
     const char **envp;
     const char *iab;
     cap_mode_t mode;
+    int launch_abort;
     int result;
+    int (*callback_fn)(void *detail);
 };
 
 #ifdef WITH_PTHREADS
@@ -32,6 +34,17 @@
 #else /* WITH_PTHREADS */
 #endif /* WITH_PTHREADS */
 
+/*
+ * clean_out drops all process capabilities.
+ */
+static int clean_out(void *data) {
+    cap_t empty;
+    empty = cap_init();
+    cap_set_proc(empty);
+    cap_free(empty);
+    return 0;
+}
+
 int main(int argc, char **argv) {
     static struct test_case_s vs[] = {
 	{
@@ -39,13 +52,32 @@
 	    .result = 0
 	},
 	{
+	    .args = { "../progs/tcapsh-static", "--", "-c", "echo hello" },
+	    .callback_fn = &clean_out,
+	    .result = 0
+	},
+	{
+	    .callback_fn = &clean_out,
+	    .result = 0
+	},
+	{
 	    .args = { "../progs/tcapsh-static", "--is-uid=123" },
 	    .result = 256
 	},
 	{
+	    .args = { "/", "won't", "work" },
+	    .launch_abort = 1,
+	},
+	{
 	    .args = { "../progs/tcapsh-static", "--is-uid=123" },
-	    .result = 0,
 	    .uid = 123,
+	    .result = 0,
+	},
+	{
+	    .args = { "../progs/tcapsh-static", "--is-uid=123" },
+	    .callback_fn = &clean_out,
+	    .uid = 123,
+	    .launch_abort = 1,
 	},
 	{
 	    .args = { "../progs/tcapsh-static", "--is-gid=123" },
@@ -91,8 +123,16 @@
     for (i=0; vs[i].pass_on != NO_MORE; i++) {
 	const struct test_case_s *v = &vs[i];
 	printf("[%d] test should %s\n", i,
-	       v->result ? "generate error" : "work");
-	cap_launch_t attr = cap_new_launcher(v->args[0], v->args, v->envp);
+	       v->result || v->launch_abort ? "generate error" : "work");
+	cap_launch_t attr;
+	if (v->args[0] != NULL) {
+	    attr = cap_new_launcher(v->args[0], v->args, v->envp);
+	    if (v->callback_fn != NULL) {
+		cap_launcher_callback(attr, v->callback_fn);
+	    }
+	} else {
+	    attr = cap_func_launcher(v->callback_fn);
+	}
 	if (v->chroot) {
 	    cap_launcher_set_chroot(attr, v->chroot);
 	}
@@ -125,28 +165,30 @@
 	pid_t child = cap_launch(attr, NULL);
 
 	if (child <= 0) {
-	    fprintf(stderr, "[%d] failed to launch", i);
-	    perror(":");
-	    success = 0;
+	    fprintf(stderr, "[%d] failed to launch: ", i);
+	    perror("");
+	    if (!v->launch_abort) {
+		success = 0;
+	    }
 	    continue;
 	}
 	if (cap_free(attr)) {
-	    fprintf(stderr, "[%d] failed to free launcher", i);
-	    perror(":");
+	    fprintf(stderr, "[%d] failed to free launcher: ", i);
+	    perror("");
 	    success = 0;
 	}
 	int result;
 	int ret = waitpid(child, &result, 0);
 	if (ret != child) {
-	    fprintf(stderr, "[%d] failed to wait", i);
-	    perror(":");
+	    fprintf(stderr, "[%d] failed to wait: ", i);
+	    perror("");
 	    success = 0;
 	    continue;
 	}
 	if (result != v->result) {
-	    fprintf(stderr, "[%d] bad result: got=%d want=%d", i, result,
+	    fprintf(stderr, "[%d] bad result: got=%d want=%d: ", i, result,
 		    v->result);
-	    perror(":");
+	    perror("");
 	    success = 0;
 	    continue;
 	}
@@ -164,10 +206,10 @@
     cap_free(final);
     cap_free(orig);
 
-    if (success) {
-	printf("cap_launch_test: PASSED\n");
-    } else {
+    if (!success) {
 	printf("cap_launch_test: FAILED\n");
 	exit(1);
     }
+    printf("cap_launch_test: PASSED\n");
+    exit(0);
 }
diff --git a/tests/uns_test.c b/tests/uns_test.c
new file mode 100644
index 0000000..d8f5415
--- /dev/null
+++ b/tests/uns_test.c
@@ -0,0 +1,158 @@
+/*
+ * Try unsharing where we remap the root user by rotating uids (0,1,2)
+ * and the corresponding gids too.
+ */
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/capability.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#define STACK_RESERVED 10*1024
+
+struct my_pipe {
+    int to[2];
+    int from[2];
+};
+
+static int child(void *data) {
+    struct my_pipe *fdsp = data;
+    static const char * const args[] = {"bash", NULL};
+
+    close(fdsp->to[1]);
+    close(fdsp->from[0]);
+    if (write(fdsp->from[1], "1", 1) != 1) {
+	fprintf(stderr, "failed to confirm setuid(1)\n");
+	exit(1);
+    }
+    close(fdsp->from[1]);
+
+    char datum[1];
+    if (read(fdsp->to[0], datum, 1) != 1) {
+	fprintf(stderr, "failed to wait for parent\n");
+	exit(1);
+    }
+    close(fdsp->to[0]);
+    if (datum[0] == '!') {
+	/* parent failed */
+	exit(0);
+    }
+
+    setsid();
+
+    execv("/bin/bash", (const void *) args);
+    perror("execv failed");
+    exit(1);
+}
+
+int main(int argc, char **argv)
+{
+    static const char *file_formats[] = {
+	"/proc/%d/uid_map",
+	"/proc/%d/gid_map"
+    };
+    static const char id_map[] = "0 1 1\n1 2 1\n2 0 1\n3 3 49999997\n";
+    cap_value_t fscap = CAP_SETFCAP;
+    cap_t orig = cap_get_proc();
+
+    /* Run with this one lowered */
+    cap_set_flag(orig, CAP_EFFECTIVE, 1, &fscap, CAP_CLEAR);
+
+    struct my_pipe fds;
+    if (pipe(&fds.from[0]) || pipe(&fds.to[0])) {
+	perror("no pipes");
+	exit(1);
+    }
+
+    char *stack = mmap(NULL, STACK_RESERVED, PROT_READ|PROT_WRITE,
+		       MAP_ANONYMOUS|MAP_PRIVATE|MAP_STACK, -1, 0);
+    if (stack == MAP_FAILED) {
+	perror("no map for stack");
+	exit(1);
+    }
+
+    if (cap_setuid(1)) {
+	perror("failed to cap_setuid(1)");
+	exit(1);
+    }
+
+    if (cap_set_proc(orig)) {
+	perror("failed to raise caps again");
+	exit(1);
+    }
+
+    pid_t pid = clone(&child, stack+STACK_RESERVED, CLONE_NEWUSER|SIGCHLD, &fds);
+    if (pid == -1) {
+	perror("clone failed");
+	exit(1);
+    }
+
+    close(fds.from[1]);
+    close(fds.to[0]);
+
+    if (cap_setuid(0)) {
+	perror("failed to cap_setuid(0)");
+	exit(1);
+    }
+
+    if (cap_set_proc(orig)) {
+	perror("failed to raise caps again");
+	exit(1);
+    }
+
+    char datum[1];
+    if (read(fds.from[0], datum, 1) != 1 || datum[0] != '1') {
+	fprintf(stderr, "failed to read child status\n");
+	exit(1);
+    }
+    close(fds.from[0]);
+
+    int i;
+    for (i=0; i<2; i++) {
+	char *map_file;
+	if (asprintf(&map_file, file_formats[i], pid) < 0) {
+	    perror("allocate string");
+	    exit(1);
+	}
+
+	FILE *f = fopen(map_file, "w");
+	free(map_file);
+	if (f == NULL) {
+	    perror("fopen failed");
+	    exit(1);
+	}
+	int len = fwrite(id_map, 1, strlen(id_map), f);
+	if (len != strlen(id_map)) {
+	    goto bailok;
+	}
+	if (fclose(f)) {
+	    goto bailok;
+	}
+    }
+
+    if (write(fds.to[1], ".", 1) != 1) {
+	perror("failed to write '.'");
+	exit(1);
+    }
+    close(fds.to[1]);
+
+    fprintf(stderr, "user namespace launched exploit worked - upgrade kernel\n");
+    if (wait(NULL) == pid) {
+	exit(1);
+    }
+    perror("launch failed");
+    exit(1);
+
+bailok:
+    fprintf(stderr, "exploit attempt failed\n");
+    (void) write(fds.to[1], "!", 1);
+    exit(0);
+}