aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSadaf Ebrahimi <sadafebrahimi@google.com>2022-10-11 15:46:40 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2022-10-11 15:46:40 +0000
commitebeee9aaa20989b2bd07ed343be815661b8ca807 (patch)
treeacb9cff934a953665142b6e8821d628608a7e498
parentb685d2f474d179f7cf80cb2277f835cbf7ca207a (diff)
parentfbfa21662ea4478f4b663a96407f51b8babe4883 (diff)
downloadlibcap-ebeee9aaa20989b2bd07ed343be815661b8ca807.tar.gz
Upgrade libcap to libcap-2.53 am: fbfa21662emain-16k-with-phones
Original change: https://android-review.googlesource.com/c/platform/external/libcap/+/2247493 Change-Id: I0831c6c8b3dc34b8614854f8983bd2d4e5a2ac4c Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--.gitignore1
-rw-r--r--License25
-rw-r--r--METADATA11
-rw-r--r--Make.Rules18
-rw-r--r--Makefile23
-rw-r--r--README48
-rw-r--r--cap/License (renamed from cap/LICENSE)27
-rw-r--r--cap/README2
-rw-r--r--cap/cap.go91
-rw-r--r--cap/cap_test.go104
-rw-r--r--cap/convenience.go62
-rw-r--r--cap/file.go42
-rw-r--r--cap/flags.go18
-rw-r--r--cap/go.mod2
-rw-r--r--cap/iab.go10
-rw-r--r--cap/launch.go176
-rw-r--r--cap/names.go13
-rw-r--r--cap/syscalls.go94
-rw-r--r--contrib/pcaps4convenience18
-rw-r--r--contrib/pcaps4server2
-rw-r--r--contrib/pcaps4suid016
-rw-r--r--contrib/seccomp/explore.go22
-rw-r--r--contrib/seccomp/go.mod2
-rw-r--r--contrib/sucap/Makefile9
-rw-r--r--contrib/sucap/README.md40
-rw-r--r--contrib/sucap/su.c1606
-rw-r--r--contrib/sucap/sucap.pamconfig6
-rw-r--r--doc/Makefile9
-rw-r--r--doc/cap_clear.333
-rw-r--r--doc/cap_copy_ext.316
-rw-r--r--doc/cap_from_text.33
-rw-r--r--doc/cap_func_launcher.31
-rw-r--r--doc/cap_get_file.325
-rw-r--r--doc/cap_get_proc.386
-rw-r--r--doc/cap_iab.3164
-rw-r--r--doc/cap_iab_fill.31
-rw-r--r--doc/cap_iab_from_text.31
-rw-r--r--doc/cap_iab_get_proc.31
-rw-r--r--doc/cap_iab_get_vector.31
-rw-r--r--doc/cap_iab_init.31
-rw-r--r--doc/cap_iab_set_proc.31
-rw-r--r--doc/cap_iab_set_vector.31
-rw-r--r--doc/cap_iab_to_text.31
-rw-r--r--doc/cap_init.316
-rw-r--r--doc/cap_launch.3182
-rw-r--r--doc/cap_launcher_callback.31
-rw-r--r--doc/cap_launcher_set_chroot.31
-rw-r--r--doc/cap_launcher_set_iab.31
-rw-r--r--doc/cap_launcher_set_mode.31
-rw-r--r--doc/cap_launcher_setgroups.31
-rw-r--r--doc/cap_launcher_setuid.31
-rw-r--r--doc/cap_mode.31
-rw-r--r--doc/cap_new_launcher.31
-rw-r--r--doc/capability.notes2
-rw-r--r--doc/capsh.126
-rw-r--r--doc/libcap.393
-rw-r--r--doc/libpsx.321
-rw-r--r--doc/old/_setfilecap.22
-rw-r--r--doc/values/24.txt2
-rw-r--r--doc/values/31.txt5
-rw-r--r--doc/values/5.txt2
-rw-r--r--doc/values/7.txt2
-rw-r--r--doc/values/8.txt2
-rw-r--r--go/.gitignore6
-rw-r--r--go/Makefile92
-rw-r--r--go/compare-cap.go6
-rw-r--r--go/go.mod8
-rw-r--r--goapps/gowns/go.mod2
-rw-r--r--goapps/gowns/gowns.go6
-rw-r--r--goapps/setid/go.mod4
-rw-r--r--goapps/web/README2
-rw-r--r--goapps/web/go.mod2
-rw-r--r--goapps/web/web.go27
-rwxr-xr-xgomods.sh11
-rw-r--r--kdebug/Makefile12
-rw-r--r--kdebug/exit.c36
-rw-r--r--kdebug/test-init.sh9
-rwxr-xr-xkdebug/test-kernel.sh9
-rw-r--r--libcap/.gitignore4
-rw-r--r--libcap/Makefile69
-rw-r--r--libcap/cap_alloc.c16
-rw-r--r--libcap/cap_extint.c100
-rw-r--r--libcap/cap_flag.c25
-rw-r--r--libcap/cap_proc.c82
-rw-r--r--libcap/cap_test.c46
-rw-r--r--libcap/cap_text.c2
-rw-r--r--libcap/empty.c1
-rw-r--r--libcap/execable.c15
-rw-r--r--libcap/execable.h93
-rw-r--r--libcap/include/sys/capability.h21
-rw-r--r--pam_cap/.gitignore2
-rw-r--r--pam_cap/Makefile47
-rw-r--r--pam_cap/capability.conf41
-rw-r--r--pam_cap/execable.c53
-rw-r--r--pam_cap/lazylink.c20
-rw-r--r--pam_cap/pam_cap.c66
-rw-r--r--pam_cap/test_pam_cap.c105
-rw-r--r--progs/.gitignore1
-rw-r--r--progs/Makefile34
-rw-r--r--progs/capsh.c172
-rw-r--r--progs/capshdoc.h413
-rw-r--r--progs/getcap.c4
-rw-r--r--progs/getpcaps.c5
-rwxr-xr-xprogs/mkcapshdoc.sh38
-rwxr-xr-xprogs/quicktest.sh14
-rw-r--r--progs/setcap.c2
-rw-r--r--psx/License (renamed from psx/LICENSE)27
-rw-r--r--psx/README2
-rw-r--r--psx/doc.go8
-rw-r--r--psx/psx.c6
-rw-r--r--psx/psx.go24
-rw-r--r--psx/psx_cgo.go30
-rw-r--r--psx/psx_syscall.h2
-rw-r--r--tests/.gitignore1
-rw-r--r--tests/Makefile43
-rw-r--r--tests/exploit.c9
-rw-r--r--tests/libcap_launch_test.c70
-rw-r--r--tests/uns_test.c158
118 files changed, 4652 insertions, 647 deletions
diff --git a/.gitignore b/.gitignore
index 1971ad5..8698f19 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
patches/
*.o
*~
+*.cf
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 @@ conditions are met:
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 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
+
-------------------------------------------------------------------------
+GPL v2.0:
+---------
+
+ALTERNATIVELY, this product may be distributed under the terms of the
+GNU General Public License (v2.0 - see below), in which case the
+provisions of the GNU GPL are required INSTEAD OF the above
+restrictions.
-------------------------
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 @@ third_party {
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 @@ LIBDIR=$(lib_prefix)/$(lib)
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_COPTS ?= -O2
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 @@ BUILD_GPERF := $(shell which gperf >/dev/null 2>/dev/null && echo yes)
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 @@ ifeq ($(CGO_REQUIRED),1)
# 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 @@ endif
#
# 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 @@ include Make.Rules
# 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 @@ clean-here:
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 @@ ifeq ($(GOLANG),yes)
endif
$(MAKE) -C progs $@
+ktest: all
+ $(MAKE) -C kdebug test
+
sudotest: all
$(MAKE) -C tests $@
ifneq ($(PAM_CAP),no)
@@ -65,14 +68,20 @@ endif
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
+ @echo or press the request button on this page:
@echo
- @echo "GOPROXY=https://proxy.golang.org GO111MODULE=on go get kernel.org/pub/linux/libs/security/libcap/cap@v$(GOMAJOR).$(VERSION).$(MINOR)"
+ @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 @@ morganrelease: distcheck
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 @@ Natively supported languages are C/C++ and Go.
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
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 @@ conditions are met:
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 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
+
-------------------------------------------------------------------------
+GPL v2.0:
+---------
+
+ALTERNATIVELY, this product may be distributed under the terms of the
+GNU General Public License (v2.0 - see below), in which case the
+provisions of the GNU GPL are required INSTEAD OF the above
+restrictions. (This clause is necessary due to a potential conflict
+between the GNU GPL and the restrictions contained in a BSD-style
+copyright.)
-------------------------
Full text of gpl-2.0.txt:
diff --git a/cap/README b/cap/README
index 3ac8433..f257d44 100644
--- a/cap/README
+++ b/cap/README
@@ -4,7 +4,7 @@ Go. The official release announcement site for libcap is:
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,26 +28,50 @@
// 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)
// }
//
-// 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.
+// 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 "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.
+// 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.
//
// Copyright (c) 2019-21 Andrew G. Morgan <morgan@kernel.org>
//
@@ -127,7 +151,7 @@ const (
)
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 @@ type header struct {
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 @@ func (sc *syscaller) prctlwcall6(prVal, v1, v2, v3, v4, v5 uintptr) (int, error)
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 @@ func (sc *syscaller) setProc(c *Set) error {
// 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 @@ func (sc *syscaller) dropBound(val ...Value) error {
// 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 @@ func (sc *syscaller) setAmbient(enable bool, val ...Value) error {
// 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 @@ func (sc *syscaller) resetAmbient() error {
// 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 @@ func same(a, b *Set) error {
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 @@ func TestImportExport(t *testing.T) {
// 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 @@ func TestImportExport(t *testing.T) {
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 TestIAB(t *testing.T) {
}
}
}
+
+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 @@ package cap
import (
"errors"
+ "fmt"
"syscall"
"unsafe"
)
@@ -33,6 +34,7 @@ const (
// defines from uapi/linux/prctl.h
const (
+ prGetKeepCaps = 7
prSetKeepCaps = 8
prGetSecureBits = 27
prSetSecureBits = 28
@@ -57,9 +59,9 @@ func (sc *syscaller) setSecbits(s Secbits) error {
// 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 @@ func (sc *syscaller) setMode(m Mode) error {
// 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 @@ func (sc *syscaller) setUID(uid int) error {
// 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 @@ func (sc *syscaller) setGroups(gid int, suppl []int) error {
// 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 @@ var ErrBadMagic = errors.New("unsupported magic")
// 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 @@ func (c *Set) SetFd(file *os.File) error {
//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 @@ func (c *Set) SetFile(path string) error {
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 @@ func Import(d []byte) (*Set, error) {
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 @@ func (c *Set) Export() ([]byte, error) {
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 @@ func (c *Set) Clear() error {
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 @@ module kernel.org/pub/linux/libs/security/libcap/cap
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 @@ func IABInit() *IAB {
}
}
-// 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 @@ func (sc *syscaller) iabSetProc(iab *IAB) (err error) {
}
// 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 @@ import (
"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 @@ func NewLauncher(path string, args []string, env []string) *Launcher {
}
}
-// 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 @@ func (attr *Launcher) SetChroot(root string) {
// 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 @@ func launch(result chan<- lResult, attr *Launcher, data interface{}, quit chan<-
}
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 @@ func launch(result chan<- lResult, attr *Launcher, data interface{}, quit chan<-
// 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 @@ func launch(result chan<- lResult, attr *Launcher, data interface{}, quit chan<-
// 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 @@ abort:
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 @@ func (attr *Launcher) Launch(data interface{}) (int, error) {
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 @@ const (
// 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 @@ const (
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 @@ const (
// 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 @@ const (
// - 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 @@ const (
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 @@ var singlesc = &syscaller{
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 @@ p4c_test(){
# 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_convert(){
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_app_revert(){
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 @@ p4c_usage(){
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 @@ p4s_test(){
# 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_convert(){
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_app_revert(){
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 @@ p4s_usage(){
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 validateArchitecture() []SockFilter {
}
}
-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 @@ func main() {
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 @@ module explore
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 @@ MAN3S = cap_init.3 cap_free.3 cap_dup.3 \
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 @@ The argument,
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 @@ char *cap_to_text(cap_t caps, ssize_t * length_p);
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 @@ identifies as
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 @@ or,
.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 @@ is deprecated; you should use
.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 @@ the name 'system' who's account is locked with a '*' password. This
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 @@ Display the list of commands supported by
.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 @@ was found via the shell's PATH searching. If the
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 @@ If the exit status does not match the signal being used to kill it, the
.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 @@ vector has capability
.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 @@ https://bugzilla.kernel.org/buglist.cgi?component=libcap&list_id=1047723&product
.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_set_flag, cap_set_proc, cap_size, cap_to_name, cap_to_text, \
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 @@ The following functions are Linux extensions:
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 @@ https://bugzilla.kernel.org/buglist.cgi?component=libcap&list_id=1047723&product
.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 @@ about why this is needed here:
.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 @@ Search permission is denied on a component of the path prefix.
.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 @@ of processes and the system:
- 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 @@ 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 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 @@ web
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 @@ $(DEPS):
../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 @@ endif
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 @@ clean:
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 @@ func tryProcCaps() {
}
}
- // 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 @@ func main() {
}
// 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 @@ module gowns
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 @@ func parseRanges(detail *nsDetail, ids string, id int) []syscall.SysProcIDMap {
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 @@ module setid
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 @@ this code with
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 @@ module web
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 @@ install:
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 done
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/getcap $HERE/../progs/getcap 0755 0 0
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 @@ if [ -f "$HERE/extras.sh" ]; then
. "$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 @@ qemu-system-$(uname -m) -m 1024 \
-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 @@ _makenames
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 @@ include ../Make.Rules
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 @@ ifeq ($(SHARED),yes)
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 @@ USE_GPERF_OUTPUT = $(GPERF_OUTPUT)
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 @@ libcap.pc: libcap.pc.in
-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 @@ $(STAPSXLIBNAME): $(PSXOBJS) include/sys/psx_syscall.h
$(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 @@ endif
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 @@ ifeq ($(FAKEROOT),)
-/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 @@ install-common:
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_launch_t cap_new_launcher(const char *arg0, const char * const *argv,
}
/*
+ * 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 @@ struct cap_ext_struct {
};
/*
- * return size of external capability set
+ * 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;
-ssize_t cap_size(cap_t caps)
+/*
+ * return size of external capability set
+ */
+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_size(cap_t caps)
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 @@ cap_t cap_copy_int(const void *cap_ext)
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 @@ int cap_compare(cap_t a, cap_t b)
}
/*
+ * 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 @@ static void _cap_set_no_new_privs(struct syscaller_s *sc)
}
/*
+ * 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 @@ static int _cap_chroot(struct syscaller_s *sc, const char *root)
/*
* _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 @@ defer:
* 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 @@ defer:
}
/*
- * 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).
+ *
+ * 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.
*
- * 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.
+ * 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 @@ pid_t cap_launch(cap_launch_t attr, void *data) {
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 @@ static int test_cap_bits(void) {
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 @@ char *cap_to_name(cap_value_t cap)
#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 @@ extern int cap_set_flag(cap_t, cap_flag_t, int, const cap_value_t *,
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 @@ extern int cap_reset_ambient(void);
#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_to_name(cap_value_t);
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 const char *cap_mode_name(cap_mode_t flavor);
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 @@ typedef struct cap_launch_s *cap_launch_t;
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_setgroups(cap_launch_t attr, gid_t gid,
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 @@ install: all
mkdir -p -m 0755 $(FAKEROOT)$(LIBDIR)/security
install -m 0755 pam_cap.so $(FAKEROOT)$(LIBDIR)/security
-# Note (as the author of much of the Linux-PAM library, I am confident
-# that this next line does *not* require -lpam on it.) If you think it
-# does, *verify that it does*, and if you observe that it fails as
-# written (and you know why it fails), email me and explain why. Thanks!
+../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 @@ sudotest: test test_pam_cap
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 @@ static int load_groups(const char *user, char ***groups, int *groups_n) {
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 @@ static int set_capabilities(struct pam_cap_s *cs)
? 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 @@ static int set_capabilities(struct pam_cap_s *cs)
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 @@ static int set_capabilities(struct pam_cap_s *cs)
}
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 _pam_log(int err, const char *format, ...)
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 @@ int pam_sm_authenticate(pam_handle_t *pamh, int flags,
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 @@ int pam_sm_authenticate(pam_handle_t *pamh, int flags,
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 @@ int pam_sm_authenticate(pam_handle_t *pamh, int flags,
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 @@ int pam_sm_setcred(pam_handle_t *pamh, int flags,
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 @@ int pam_sm_setcred(pam_handle_t *pamh, int flags,
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 @@ static void load_vectors(unsigned long int bits[3]) {
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 @@ int main(int argc, char *argv[]) {
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 @@ getpcaps
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 @@ include $(topdir)/Make.Rules
#
# 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 @@ endif
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 @@ static void display_prctl_set(const char *name, int (*fn)(cap_value_t))
}
}
-/* 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);
+}
+
+static void display_current_iab(void)
+{
+ cap_iab_t iab;
+ char *text;
- display_prctl_set("Bounding", cap_get_bound);
- display_prctl_set("Ambient", cap_get_ambient);
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 void arg_change_amb(const char *arg_names, cap_flag_value_t set)
*/
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 @@ static char *find_self(const char *arg0)
}
parts = strdup(path);
+ if (parts == NULL) {
+ fprintf(stderr, "insufficient memory for parts of path\n");
+ exit(1);
+ }
+
scratch = malloc(2+strlen(path)+strlen(arg0));
- if (parts == NULL || scratch == NULL) {
+ if (scratch == NULL) {
fprintf(stderr, "insufficient memory for path building\n");
- exit(1);
+ goto free_parts;
}
- for (i=0; (dir = strtok(parts, ":")); parts = NULL) {
+ for (p = parts; (dir = strtok(p, ":")); p = NULL) {
sprintf(scratch, "%s/%s", dir, arg0);
if (access(scratch, X_OK) == 0) {
- return scratch;
+ 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;
+}
- fprintf(stderr, "unable to find executable '%s' in PATH\n", arg0);
- exit(1);
+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 @@ int main(int argc, char *argv[], char *envp[])
* 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 @@ int main(int argc, char *argv[], char *envp[])
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 @@ int main(int argc, char *argv[], char *envp[])
}
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 @@ int main(int argc, char *argv[], char *envp[])
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 @@ int main(int argc, char *argv[], char *envp[])
}
} 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 @@ int main(int argc, char *argv[], char *envp[])
" --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 @@ int main(int argc, char **argv)
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 @@ int main(int argc, char **argv)
!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 () {
}
pass_capsh --print
+pass_capsh --current
# Validate that PATH expansion works
PATH=$(/bin/pwd)/junk:$(/bin/pwd) capsh == == == --modes
@@ -89,7 +90,7 @@ pass_capsh --keep=0 --keep=1 --keep=0 --keep=1 --print
/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 @@ EOF
# 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 @@ else
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 @@ int main(int argc, char **argv)
}
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
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 @@ conditions are met:
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 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
+
-------------------------------------------------------------------------
+GPL v2.0:
+---------
+
+ALTERNATIVELY, this product may be distributed under the terms of the
+GNU General Public License (v2.0 - see below), in which case the
+provisions of the GNU GPL are required INSTEAD OF the above
+restrictions. (This clause is necessary due to a potential conflict
+between the GNU GPL and the restrictions contained in a BSD-style
+copyright.)
-------------------------
Full text of gpl-2.0.txt:
diff --git a/psx/README b/psx/README
index cd9c651..e4f9001 100644
--- a/psx/README
+++ b/psx/README
@@ -23,6 +23,6 @@ The official release announcement site for libcap and libpsx is:
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 @@ static void psx_syscall_start(void) {
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 @@ static void *_psx_start_fn(void *data) {
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"
-)
-
-// Syscall3 and Syscall6 are aliases for syscall.AllThreadsSyscall*
-// when compiled CGO_ENABLED=0.
-var (
- Syscall3 = syscall.AllThreadsSyscall
- Syscall6 = syscall.AllThreadsSyscall6
-)
+import "syscall"
+
+// 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 @@ func setErrno(v int) int {
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 @@ func Syscall3(syscallnr, arg1, arg2, arg3 uintptr) (uintptr, uintptr, syscall.Er
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 @@ long int psx_syscall6(long int syscall_nr,
* 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_launch_test
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 @@ include ../Make.Rules
#
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 @@ ifeq ($(PTHREADS),yes)
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
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 @@ run_psx_test: psx_test
./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 @@ run_libcap_psx_launch_test: libcap_psx_launch_test ../progs/tcapsh-static
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 @@ exploit.o: exploit.c
$(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 @@ int main(int argc, char **argv) {
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 @@ struct test_case_s {
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 @@ struct test_case_s {
#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 @@ int main(int argc, char **argv) {
.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" },
+ .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 @@ int main(int argc, char **argv) {
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 @@ int main(int argc, char **argv) {
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 @@ int main(int argc, char **argv) {
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);
+}