Merge SP1A.201105.002
Change-Id: I78ca02bed17acc1981e75eb163504a6540394700
diff --git a/METADATA b/METADATA
index 8a3445f..21ee963 100644
--- a/METADATA
+++ b/METADATA
@@ -5,11 +5,11 @@
type: GIT
value: "https://git.kernel.org/pub/scm/linux/kernel/git/morgan/libcap.git"
}
- version: "libcap-2.42"
+ version: "libcap-2.45"
license_type: NOTICE
last_upgrade_date {
year: 2020
- month: 8
- day: 3
+ month: 11
+ day: 2
}
}
diff --git a/Make.Rules b/Make.Rules
index 8440e18..cc6f95b 100644
--- a/Make.Rules
+++ b/Make.Rules
@@ -1,3 +1,8 @@
+# Common version number defines for libcap
+LIBTITLE=libcap
+VERSION=2
+MINOR=45
+
#
## Optional prefixes:
#
@@ -38,11 +43,6 @@
PKGCONFIGDIR=$(LIBDIR)/pkgconfig
GOPKGDIR=$(prefix)/share/gocode/src
-# Common version number defines for libcap
-LIBTITLE=libcap
-VERSION=2
-MINOR=42
-
# Go modules have their own semantics. I plan to leave this value at 0
# and keep it there. The Go packages should always remain backwardly
# compatible, but I may have to up it if Go's syntax changes in a
@@ -54,13 +54,15 @@
KERNEL_HEADERS := $(topdir)/libcap/include/uapi
IPATH += -fPIC -I$(KERNEL_HEADERS) -I$(topdir)/libcap/include
-CC ?= $(CROSS_COMPILE)gcc
+CC := $(CROSS_COMPILE)gcc
DEFINES := -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64
-CFLAGS ?= -O2 $(DEFINES)
+COPTS ?= -O2
+CFLAGS ?= $(COPTS) $(DEFINES)
BUILD_CC ?= $(CC)
-BUILD_CFLAGS ?= -O2 $(DEFINES) $(IPATH)
-AR ?= $(CROSS_COMPILE)ar
-RANLIB ?= $(CROSS_COMPILE)ranlib
+BUILD_COPTS ?= -O2
+BUILD_CFLAGS ?= $(BUILD_COPTS) $(DEFINES) $(IPATH)
+AR := $(CROSS_COMPILE)ar
+RANLIB := $(CROSS_COMPILE)ranlib
DEBUG = -g #-DDEBUG
WARNINGS=-Wall -Wwrite-strings \
-Wpointer-arith -Wcast-qual -Wcast-align \
@@ -69,7 +71,8 @@
LD=$(CC) -Wl,-x -shared
LDFLAGS ?= #-g
LIBCAPLIB := -L$(topdir)/libcap -lcap
-LIBPSXLIB := -L$(topdir)/libcap -lpsx -lpthread
+PSXLINKFLAGS := -lpthread -Wl,-wrap,pthread_create
+LIBPSXLIB := -L$(topdir)/libcap -lpsx $(PSXLINKFLAGS)
BUILD_GPERF := $(shell which gperf >/dev/null 2>/dev/null && echo yes)
@@ -77,10 +80,29 @@
INCS=$(topdir)/libcap/include/sys/capability.h
LDFLAGS += -L$(topdir)/libcap
CFLAGS += -Dlinux $(WARNINGS) $(DEBUG)
-PAM_CAP ?= $(shell if [ -f /usr/include/security/pam_modules.h ]; then echo yes ; else echo no ; fi)
INDENT := $(shell if [ -n "$$(which indent 2>/dev/null)" ]; then echo "| indent -kr" ; fi)
-DYNAMIC := $(shell if [ ! -d "$(topdir)/.git" ]; then echo yes; fi)
+# SHARED tracks whether or not the SHARED libraries (libcap.so,
+# libpsx.so and pam_cap.so) are built. (Some environments don't
+# support shared libraries.)
+SHARED ?= yes
+# DYNAMIC controls how capsh etc are linked - to shared or static libraries
+DYNAMIC := $(shell if [ ! -d "$(topdir)/.git" ]; then echo $(SHARED); else echo no ; fi)
+
+PAM_CAP ?= $(shell if [ -f /usr/include/security/pam_modules.h ]; then echo $(SHARED) ; else echo no ; fi)
+
+# If your system does not support pthreads, override this as "no".
+#
+# make PTHREADS=no ...
+#
+# This implies no Go support and no C/C++ libpsx build. Why might you
+# need libpsx for non-Go use? Tl;dr for POSIX semantics security:
+#
+# https://sites.google.com/site/fullycapable/who-ordered-libpsx
+#
+PTHREADS ?= yes
+
+ifeq ($(PTHREADS),yes)
GO := go
GOLANG := $(shell if [ -n "$(shell $(GO) version 2>/dev/null)" ]; then echo yes ; else echo no ; fi)
ifeq ($(GOLANG),yes)
@@ -96,6 +118,7 @@
GOBUILDTAG=-tags allthreadssyscall
endif
endif
+endif
# If you want capsh to launch with something other than /bin/bash
# build like this:
diff --git a/Makefile b/Makefile
index 03d7748..8e8d5f3 100644
--- a/Makefile
+++ b/Makefile
@@ -34,6 +34,9 @@
@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
@echo "ALL go.mod files updated"
+ @echo "Now validate that everything is checked in to a clean tree.."
+ test -z "$$(git status -s)"
+ @echo "All good!"
release: distclean
cd .. && ln -s libcap libcap-$(VERSION).$(MINOR) && tar cvf libcap-$(VERSION).$(MINOR).tar --exclude patches libcap-$(VERSION).$(MINOR)/* && rm libcap-$(VERSION).$(MINOR)
@@ -61,10 +64,18 @@
distcheck:
./distcheck.sh
+ make DYNAMIC=yes clean all test sudotest
make CC=/usr/local/musl/bin/musl-gcc 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
+ @echo "GOPROXY=https://proxy.golang.org GO111MODULE=on go get kernel.org/pub/linux/libs/security/libcap/cap@v$(GOMAJOR).$(VERSION).$(MINOR)"
+ @echo
+ @echo "This will cause a go.dev documentation update."
+
morganrelease: distcheck
@echo "sign the main library tag twice: older DSA key; and newer RSA (kernel.org) key"
git tag -u D41A6DF2 -s libcap-$(VERSION).$(MINOR) -m "This is libcap-$(VERSION).$(MINOR)"
@@ -76,3 +87,4 @@
make release
@echo "sign the tar file using korg key"
cd .. && gpg -sba -u E2CCF3F4 libcap-$(VERSION).$(MINOR).tar
+ make morgangodoc
diff --git a/cap/cap.go b/cap/cap.go
index ed087e1..76f74df 100644
--- a/cap/cap.go
+++ b/cap/cap.go
@@ -42,7 +42,7 @@
// uniformly over the whole Go (and CGo linked) process runtime.
//
// Note, if the Go runtime syscall interface contains the Linux
-// variant syscall.AllThreadsSyscall() API (it is not in go1.15rc1
+// variant syscall.AllThreadsSyscall() API (it is not in go1.15
// for example, but see https://github.com/golang/go/issues/1435 for
// current status) then this present package can use that to invoke
// Capability setting system calls in pure Go binaries. In such an
diff --git a/cap/go.mod b/cap/go.mod
index da5b4e5..ec997a8 100644
--- a/cap/go.mod
+++ b/cap/go.mod
@@ -2,4 +2,4 @@
go 1.11
-require kernel.org/pub/linux/libs/security/libcap/psx v0.2.42
+require kernel.org/pub/linux/libs/security/libcap/psx v0.2.45
diff --git a/cap/names.go b/cap/names.go
index ff60017..9e02cd1 100644
--- a/cap/names.go
+++ b/cap/names.go
@@ -12,7 +12,7 @@
// FWIW the userspace tool '/sbin/capsh' also contains a runtime check
// for the condition that libcap is behind the running kernel in this
// way.
-const NamedCount = 40
+const NamedCount = 41
// CHOWN etc., are the named capability values of the Linux
// kernel. The canonical source for each name is the
@@ -331,90 +331,98 @@
// - cap.NET_ADMIN is required to load networking
// programs.
BPF
+
+ // CHECKPOINT_RESTORE allows a process to perform checkpoint
+ // and restore operations. Also permits
+ // explicit PID control via clone3() and
+ // also writing to ns_last_pid.
+ CHECKPOINT_RESTORE
)
var names = map[Value]string{
- CHOWN: "cap_chown",
- DAC_OVERRIDE: "cap_dac_override",
- DAC_READ_SEARCH: "cap_dac_read_search",
- FOWNER: "cap_fowner",
- FSETID: "cap_fsetid",
- KILL: "cap_kill",
- SETGID: "cap_setgid",
- SETUID: "cap_setuid",
- SETPCAP: "cap_setpcap",
- LINUX_IMMUTABLE: "cap_linux_immutable",
- NET_BIND_SERVICE: "cap_net_bind_service",
- NET_BROADCAST: "cap_net_broadcast",
- NET_ADMIN: "cap_net_admin",
- NET_RAW: "cap_net_raw",
- IPC_LOCK: "cap_ipc_lock",
- IPC_OWNER: "cap_ipc_owner",
- SYS_MODULE: "cap_sys_module",
- SYS_RAWIO: "cap_sys_rawio",
- SYS_CHROOT: "cap_sys_chroot",
- SYS_PTRACE: "cap_sys_ptrace",
- SYS_PACCT: "cap_sys_pacct",
- SYS_ADMIN: "cap_sys_admin",
- SYS_BOOT: "cap_sys_boot",
- SYS_NICE: "cap_sys_nice",
- SYS_RESOURCE: "cap_sys_resource",
- SYS_TIME: "cap_sys_time",
- SYS_TTY_CONFIG: "cap_sys_tty_config",
- MKNOD: "cap_mknod",
- LEASE: "cap_lease",
- AUDIT_WRITE: "cap_audit_write",
- AUDIT_CONTROL: "cap_audit_control",
- SETFCAP: "cap_setfcap",
- MAC_OVERRIDE: "cap_mac_override",
- MAC_ADMIN: "cap_mac_admin",
- SYSLOG: "cap_syslog",
- WAKE_ALARM: "cap_wake_alarm",
- BLOCK_SUSPEND: "cap_block_suspend",
- AUDIT_READ: "cap_audit_read",
- PERFMON: "cap_perfmon",
- BPF: "cap_bpf",
+ CHOWN: "cap_chown",
+ DAC_OVERRIDE: "cap_dac_override",
+ DAC_READ_SEARCH: "cap_dac_read_search",
+ FOWNER: "cap_fowner",
+ FSETID: "cap_fsetid",
+ KILL: "cap_kill",
+ SETGID: "cap_setgid",
+ SETUID: "cap_setuid",
+ SETPCAP: "cap_setpcap",
+ LINUX_IMMUTABLE: "cap_linux_immutable",
+ NET_BIND_SERVICE: "cap_net_bind_service",
+ NET_BROADCAST: "cap_net_broadcast",
+ NET_ADMIN: "cap_net_admin",
+ NET_RAW: "cap_net_raw",
+ IPC_LOCK: "cap_ipc_lock",
+ IPC_OWNER: "cap_ipc_owner",
+ SYS_MODULE: "cap_sys_module",
+ SYS_RAWIO: "cap_sys_rawio",
+ SYS_CHROOT: "cap_sys_chroot",
+ SYS_PTRACE: "cap_sys_ptrace",
+ SYS_PACCT: "cap_sys_pacct",
+ SYS_ADMIN: "cap_sys_admin",
+ SYS_BOOT: "cap_sys_boot",
+ SYS_NICE: "cap_sys_nice",
+ SYS_RESOURCE: "cap_sys_resource",
+ SYS_TIME: "cap_sys_time",
+ SYS_TTY_CONFIG: "cap_sys_tty_config",
+ MKNOD: "cap_mknod",
+ LEASE: "cap_lease",
+ AUDIT_WRITE: "cap_audit_write",
+ AUDIT_CONTROL: "cap_audit_control",
+ SETFCAP: "cap_setfcap",
+ MAC_OVERRIDE: "cap_mac_override",
+ MAC_ADMIN: "cap_mac_admin",
+ SYSLOG: "cap_syslog",
+ WAKE_ALARM: "cap_wake_alarm",
+ BLOCK_SUSPEND: "cap_block_suspend",
+ AUDIT_READ: "cap_audit_read",
+ PERFMON: "cap_perfmon",
+ BPF: "cap_bpf",
+ CHECKPOINT_RESTORE: "cap_checkpoint_restore",
}
var bits = map[string]Value{
- "cap_chown": CHOWN,
- "cap_dac_override": DAC_OVERRIDE,
- "cap_dac_read_search": DAC_READ_SEARCH,
- "cap_fowner": FOWNER,
- "cap_fsetid": FSETID,
- "cap_kill": KILL,
- "cap_setgid": SETGID,
- "cap_setuid": SETUID,
- "cap_setpcap": SETPCAP,
- "cap_linux_immutable": LINUX_IMMUTABLE,
- "cap_net_bind_service": NET_BIND_SERVICE,
- "cap_net_broadcast": NET_BROADCAST,
- "cap_net_admin": NET_ADMIN,
- "cap_net_raw": NET_RAW,
- "cap_ipc_lock": IPC_LOCK,
- "cap_ipc_owner": IPC_OWNER,
- "cap_sys_module": SYS_MODULE,
- "cap_sys_rawio": SYS_RAWIO,
- "cap_sys_chroot": SYS_CHROOT,
- "cap_sys_ptrace": SYS_PTRACE,
- "cap_sys_pacct": SYS_PACCT,
- "cap_sys_admin": SYS_ADMIN,
- "cap_sys_boot": SYS_BOOT,
- "cap_sys_nice": SYS_NICE,
- "cap_sys_resource": SYS_RESOURCE,
- "cap_sys_time": SYS_TIME,
- "cap_sys_tty_config": SYS_TTY_CONFIG,
- "cap_mknod": MKNOD,
- "cap_lease": LEASE,
- "cap_audit_write": AUDIT_WRITE,
- "cap_audit_control": AUDIT_CONTROL,
- "cap_setfcap": SETFCAP,
- "cap_mac_override": MAC_OVERRIDE,
- "cap_mac_admin": MAC_ADMIN,
- "cap_syslog": SYSLOG,
- "cap_wake_alarm": WAKE_ALARM,
- "cap_block_suspend": BLOCK_SUSPEND,
- "cap_audit_read": AUDIT_READ,
- "cap_perfmon": PERFMON,
- "cap_bpf": BPF,
+ "cap_chown": CHOWN,
+ "cap_dac_override": DAC_OVERRIDE,
+ "cap_dac_read_search": DAC_READ_SEARCH,
+ "cap_fowner": FOWNER,
+ "cap_fsetid": FSETID,
+ "cap_kill": KILL,
+ "cap_setgid": SETGID,
+ "cap_setuid": SETUID,
+ "cap_setpcap": SETPCAP,
+ "cap_linux_immutable": LINUX_IMMUTABLE,
+ "cap_net_bind_service": NET_BIND_SERVICE,
+ "cap_net_broadcast": NET_BROADCAST,
+ "cap_net_admin": NET_ADMIN,
+ "cap_net_raw": NET_RAW,
+ "cap_ipc_lock": IPC_LOCK,
+ "cap_ipc_owner": IPC_OWNER,
+ "cap_sys_module": SYS_MODULE,
+ "cap_sys_rawio": SYS_RAWIO,
+ "cap_sys_chroot": SYS_CHROOT,
+ "cap_sys_ptrace": SYS_PTRACE,
+ "cap_sys_pacct": SYS_PACCT,
+ "cap_sys_admin": SYS_ADMIN,
+ "cap_sys_boot": SYS_BOOT,
+ "cap_sys_nice": SYS_NICE,
+ "cap_sys_resource": SYS_RESOURCE,
+ "cap_sys_time": SYS_TIME,
+ "cap_sys_tty_config": SYS_TTY_CONFIG,
+ "cap_mknod": MKNOD,
+ "cap_lease": LEASE,
+ "cap_audit_write": AUDIT_WRITE,
+ "cap_audit_control": AUDIT_CONTROL,
+ "cap_setfcap": SETFCAP,
+ "cap_mac_override": MAC_OVERRIDE,
+ "cap_mac_admin": MAC_ADMIN,
+ "cap_syslog": SYSLOG,
+ "cap_wake_alarm": WAKE_ALARM,
+ "cap_block_suspend": BLOCK_SUSPEND,
+ "cap_audit_read": AUDIT_READ,
+ "cap_perfmon": PERFMON,
+ "cap_bpf": BPF,
+ "cap_checkpoint_restore": CHECKPOINT_RESTORE,
}
diff --git a/doc/capsh.1 b/doc/capsh.1
index b02793b..ab20c44 100644
--- a/doc/capsh.1
+++ b/doc/capsh.1
@@ -1,4 +1,4 @@
-.TH CAPSH 1 "2020-01-07" "libcap 2" "User Commands"
+.TH CAPSH 1 "2020-10-27" "libcap 2" "User Commands"
.SH NAME
capsh \- capability shell wrapper
.SH SYNOPSIS
@@ -33,7 +33,15 @@
.B capsh
again with the remaining arguments. Useful for testing
.BR exec ()
-behavior.
+behavior. Note, PATH is searched when the running
+.B capsh
+was found via the shell's PATH searching. If the
+.B exec
+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
+can complete the chroot transition.
.TP
.BI \-\-caps= cap-set
Set the prevailing process capabilities to those specified by
@@ -179,7 +187,7 @@
in any of the formats permitted by
.BR strtoul (3).
.TP
-.BI \-\-chroot= path
+.BI \-\-chroot= /some/path
Execute the
.BR chroot (2)
system call with the new root-directory (/) equal to
diff --git a/doc/libpsx.3 b/doc/libpsx.3
index a907d8b..a3535a6 100644
--- a/doc/libpsx.3
+++ b/doc/libpsx.3
@@ -1,4 +1,4 @@
-.TH LIBPSX 3 "2020-01-07" "" "Linux Programmer's Manual"
+.TH LIBPSX 3 "2020-10-13" "" "Linux Programmer's Manual"
.SH NAME
psx_syscall3, psx_syscall6 \- POSIX semantics for system calls
.SH SYNOPSIS
@@ -25,43 +25,47 @@
all of the threads associated with the current process. However, other
credential state is not supported by this abstraction. To support
these extended kernel managed security attributes,
-.BR libpsx (3)
+.B libpsx
provides a more generic pair of wrapping system call functions:
-.BR psx_syscall3 "(3) and " psx_syscall6 (3).
+.BR psx_syscall3 "() and " psx_syscall6 ().
Like the
.B setxid
-mechanism, the coordination of thread state is arranged by a realtime
-signal SIGRTMAX which is usurped for this process.
+mechanism, the coordination of thread state is mediated by a realtime
+signal. Whereas the
+.B nptl:setxid
+mechanism uses signo=33 (which is hidden by glibc below a redefined
+SIGRTMIN),
+.B libpsx
+usurps SIGRTMAX for this process.
.PP
A linker trick of
.I wrapping
the
.BR pthread_create ()
-call with a psx thread registration function is used to allow
+call with a psx thread registration function is used to ensure
.B libpsx
-to keep track of all pthreads. If that trick is not usable by your application, then the much more cumbersome and fragile
-.B <sys/psx_syscall.h>
-header file.
+can keep track of all pthreads.
.PP
-An inefficient macrology trick supports the psx_syscall() pseudo
-function which takes 1 to 7 arguments, depending on the needs of the
-caller. The macrology pads out the call to actually use
-.BR psx_syscall3 (3)
+An inefficient macrology trick supports the
+.BR psx_syscall ()
+pseudo function which takes 1 to 7 arguments, depending on the needs
+of the caller. The macrology pads out the call to actually use
+.BR psx_syscall3 ()
or
-.BR psx_syscall6 (3)
+.BR psx_syscall6 ()
with zeros filling the missing arguments. While using this in source
code will make it appear clean, the actual code footprint is
larger. You are encouraged to use the more explicit
-.BR psx_syscall3 (3)
+.BR psx_syscall3 ()
and
-.BR psx_syscall6 (3)
-functions.
+.BR psx_syscall6 ()
+functions as needed.
.SH RETURN VALUE
The return value for system call functions is generally the value
returned by the kernel, or \-1 in the case of an error. In such cases
.BR errno (3)
is set to the detailed error value. The
-.BR psx_syscall3 " and " psx_syscall6
+.BR psx_syscall3 "() and " psx_syscall6 ()
functions attempt a single threaded system call and return immediately
in the case of an error. Should this call succeed, then the same
system calls are executed from a signal handler on each of the other
diff --git a/doc/values/40.txt b/doc/values/40.txt
new file mode 100644
index 0000000..c5993cf
--- /dev/null
+++ b/doc/values/40.txt
@@ -0,0 +1,4 @@
+Allows a process to perform checkpoint
+and restore operations. Also permits
+explicit PID control via clone3() and
+also writing to ns_last_pid.
diff --git a/go/Makefile b/go/Makefile
index c5ad7aa..eee379e 100644
--- a/go/Makefile
+++ b/go/Makefile
@@ -23,8 +23,8 @@
$(DEPS):
make -C ../libcap all
-../progs/capsh:
- make -C ../progs capsh
+../progs/tcapsh-static:
+ make -C ../progs tcapsh-static
src/$(IMPORTDIR)/psx:
mkdir -p "src/$(IMPORTDIR)"
@@ -43,18 +43,18 @@
$(PSXGOPACKAGE): src/$(IMPORTDIR)/psx ../psx/*.go $(DEPS)
mkdir -p pkg
- CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH="$(GOPATH)" $(GO) install $(IMPORTDIR)/psx
+ GO111MODULE=off CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH="$(GOPATH)" $(GO) install $(IMPORTDIR)/psx
$(CAPGOPACKAGE): src/$(IMPORTDIR)/cap ../cap/*.go good-names.go $(PSXGOPACKAGE)
- CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH=$(GOPATH) $(GO) install $(IMPORTDIR)/cap
+ GO111MODULE=off CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH=$(GOPATH) $(GO) install $(IMPORTDIR)/cap
# 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)
- CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" GOPATH=$(GOPATH) $(GO) build $<
+ GO111MODULE=off CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" GOPATH=$(GOPATH) $(GO) build $<
web: ../goapps/web/web.go $(CAPGOPACKAGE)
- CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH=$(GOPATH) $(GO) build -o $@ $(GOBUILDTAG) $<
+ GO111MODULE=off CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH=$(GOPATH) $(GO) build -o $@ $(GOBUILDTAG) $<
ifeq ($(RAISE_GO_FILECAP),yes)
make -C ../progs setcap
sudo ../progs/setcap cap_setpcap,cap_net_bind_service=p web
@@ -62,24 +62,24 @@
endif
ok: ok.go
- CGO_ENABLED=0 GOPATH=$(GOPATH) $(GO) build $<
+ GO111MODULE=off CGO_ENABLED=0 GOPATH=$(GOPATH) $(GO) build $<
try-launching: try-launching.go $(CAPGOPACKAGE) ok
- CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH=$(GOPATH) $(GO) build $(GOBUILDTAG) $<
+ GO111MODULE=off CGO_ENABLED="$(CGO_REQUIRED)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH=$(GOPATH) $(GO) build $(GOBUILDTAG) $<
ifeq ($(CGO_REQUIRED),0)
- CGO_ENABLED="1" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH=$(GOPATH) $(GO) build -o $@-cgo $<
+ GO111MODULE=off CGO_ENABLED="1" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH=$(GOPATH) $(GO) build -o $@-cgo $<
endif
-test: all ../progs/capsh
- CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH="$(GOPATH)" $(GO) test $(IMPORTDIR)/psx
- CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH="$(GOPATH)" $(GO) test $(IMPORTDIR)/cap
+test: all
+ GO111MODULE=off CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH="$(GOPATH)" $(GO) test $(IMPORTDIR)/psx
+ GO111MODULE=off CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" GOPATH="$(GOPATH)" $(GO) test $(IMPORTDIR)/cap
LD_LIBRARY_PATH=../libcap ./compare-cap
+
+sudotest: test ../progs/tcapsh-static
./try-launching
ifeq ($(CGO_REQUIRED),0)
./try-launching-cgo
endif
-
-sudotest: test
sudo ./try-launching
ifeq ($(CGO_REQUIRED),0)
sudo ./try-launching-cgo
diff --git a/go/try-launching.go b/go/try-launching.go
index 1c3d477..272fd0a 100644
--- a/go/try-launching.go
+++ b/go/try-launching.go
@@ -32,7 +32,7 @@
}{
{args: []string{root + "/go/ok"}},
{
- args: []string{root + "/progs/capsh", "--dropped=cap_chown", "--is-uid=123", "--is-gid=456", "--has-a=cap_setuid"},
+ args: []string{root + "/progs/tcapsh-static", "--dropped=cap_chown", "--is-uid=123", "--is-gid=456", "--has-a=cap_setuid"},
iab: "!cap_chown,^cap_setuid,cap_sys_admin",
uid: 123,
gid: 456,
diff --git a/goapps/setid/go.mod b/goapps/setid/go.mod
index 2573dec..881f9e9 100644
--- a/goapps/setid/go.mod
+++ b/goapps/setid/go.mod
@@ -3,6 +3,6 @@
go 1.11
require (
- kernel.org/pub/linux/libs/security/libcap/cap v0.2.42
- kernel.org/pub/linux/libs/security/libcap/psx v0.2.42
+ kernel.org/pub/linux/libs/security/libcap/cap v0.2.45
+ kernel.org/pub/linux/libs/security/libcap/psx v0.2.45
)
diff --git a/goapps/web/go.mod b/goapps/web/go.mod
index 84c8074..4b2eb4f 100644
--- a/goapps/web/go.mod
+++ b/goapps/web/go.mod
@@ -2,4 +2,4 @@
go 1.11
-require kernel.org/pub/linux/libs/security/libcap/cap v0.2.42
+require kernel.org/pub/linux/libs/security/libcap/cap v0.2.45
diff --git a/libcap/.gitignore b/libcap/.gitignore
index 000c694..8f77a0e 100644
--- a/libcap/.gitignore
+++ b/libcap/.gitignore
@@ -4,6 +4,7 @@
libcap.a
libcap.so*
libpsx.a
+libpsx.so*
_makenames
cap_test
libcap.pc
diff --git a/libcap/Makefile b/libcap/Makefile
index 81b089e..230be39 100644
--- a/libcap/Makefile
+++ b/libcap/Makefile
@@ -6,25 +6,41 @@
#
# Library version
#
-LIBNAME=$(LIBTITLE).so
+CAPLIBNAME=$(LIBTITLE).so
STACAPLIBNAME=$(LIBTITLE).a
#
+PSXLIBNAME=libpsx.so
STAPSXLIBNAME=libpsx.a
CAPFILES=cap_alloc cap_proc cap_extint cap_flag cap_text cap_file
PSXFILES=../psx/psx
INCLS=libcap.h cap_names.h $(INCS)
-CAPOBJS=$(addsuffix .o, $(CAPFILES))
-PSXOBJS=$(addsuffix .o, $(PSXFILES))
-
-MAJLIBNAME=$(LIBNAME).$(VERSION)
-MINLIBNAME=$(MAJLIBNAME).$(MINOR)
GPERF_OUTPUT = _caps_output.gperf
-all: $(MINLIBNAME) $(STACAPLIBNAME) pcs $(STAPSXLIBNAME)
+CAPOBJS=$(addsuffix .o, $(CAPFILES))
+MAJCAPLIBNAME=$(CAPLIBNAME).$(VERSION)
+MINCAPLIBNAME=$(MAJCAPLIBNAME).$(MINOR)
-pcs: libcap.pc libpsx.pc
+PSXOBJS=$(addsuffix .o, $(PSXFILES))
+MAJPSXLIBNAME=$(PSXLIBNAME).$(VERSION)
+MINPSXLIBNAME=$(MAJPSXLIBNAME).$(MINOR)
+
+all: pcs $(STACAPLIBNAME)
+ifeq ($(SHARED),yes)
+ make $(CAPLIBNAME)
+endif
+ifeq ($(PTHREADS),yes)
+ make $(STAPSXLIBNAME)
+ifeq ($(SHARED),yes)
+ make $(PSXLIBNAME)
+endif
+endif
+
+pcs: libcap.pc
+ifeq ($(PTHREADS),yes)
+ make libpsx.pc
+endif
ifeq ($(BUILD_GPERF),yes)
USE_GPERF_OUTPUT = $(GPERF_OUTPUT)
@@ -76,10 +92,17 @@
$(AR) rcs $@ $^
$(RANLIB) $@
-$(MINLIBNAME): $(CAPOBJS)
- $(LD) $(CFLAGS) $(LDFLAGS) -Wl,-soname,$(MAJLIBNAME) -o $@ $^
- ln -sf $(MINLIBNAME) $(MAJLIBNAME)
- ln -sf $(MAJLIBNAME) $(LIBNAME)
+ifeq ($(SHARED),yes)
+$(CAPLIBNAME) $(MAJCAPLIBNAME) $(MINCAPLIBNAME): $(CAPOBJS)
+ $(LD) $(CFLAGS) $(LDFLAGS) -Wl,-soname,$(MAJCAPLIBNAME) -o $(MINCAPLIBNAME) $^
+ ln -sf $(MINCAPLIBNAME) $(MAJCAPLIBNAME)
+ ln -sf $(MAJCAPLIBNAME) $(CAPLIBNAME)
+
+$(PSXLIBNAME) $(MAJPSXLIBNAME) $(MINPSXLIBNAME): $(PSXOBJS)
+ $(LD) $(CFLAGS) $(LDFLAGS) -Wl,-soname,$(MAJPSXLIBNAME) -o $(MINPSXLIBNAME) $^ $(PSXLINKFLAGS)
+ ln -sf $(MINPSXLIBNAME) $(MAJPSXLIBNAME)
+ ln -sf $(MAJPSXLIBNAME) $(PSXLIBNAME)
+endif
%.o: %.c $(INCLS)
$(CC) $(CFLAGS) $(IPATH) -c $< -o $@
@@ -93,32 +116,69 @@
test: cap_test
./cap_test
-install: install-shared install-static
+install: install-static
+ifeq ($(SHARED),yes)
+ make install-shared
+endif
-install-common: pcs
- mkdir -p -m 0755 $(FAKEROOT)$(INCDIR)/sys
- install -m 0644 include/sys/capability.h $(FAKEROOT)$(INCDIR)/sys
- install -m 0644 include/sys/psx_syscall.h $(FAKEROOT)$(INCDIR)/sys
- mkdir -p -m 0755 $(FAKEROOT)$(PKGCONFIGDIR)
- install -m 0644 libcap.pc $(FAKEROOT)$(PKGCONFIGDIR)/libcap.pc
- install -m 0644 libpsx.pc $(FAKEROOT)$(PKGCONFIGDIR)/libpsx.pc
+install-static: install-static-cap
+ifeq ($(PTHREADS),yes)
+ make install-static-psx
+endif
-install-static: $(STACAPLIBNAME) $(STAPSXLIBNAME) install-common
- mkdir -p -m 0755 $(FAKEROOT)$(LIBDIR)
+install-shared: install-shared-cap
+ifeq ($(PTHREADS),yes)
+ make install-shared-psx
+endif
+
+install-cap: install-static-cap
+ifeq ($(SHARED),yes)
+ make install-shared-cap
+endif
+
+install-psx: install-static-psx
+ifeq ($(SHARED),yes)
+ make install-shared-psx
+endif
+
+install-static-cap: install-common-cap $(STACAPLIBNAME)
install -m 0644 $(STACAPLIBNAME) $(FAKEROOT)$(LIBDIR)/$(STACAPLIBNAME)
- install -m 0644 $(STAPSXLIBNAME) $(FAKEROOT)$(LIBDIR)/$(STAPSXLIBNAME)
-install-shared: $(MINLIBNAME) install-common
- install -m 0644 $(MINLIBNAME) $(FAKEROOT)$(LIBDIR)/$(MINLIBNAME)
- ln -sf $(MINLIBNAME) $(FAKEROOT)$(LIBDIR)/$(MAJLIBNAME)
- ln -sf $(MAJLIBNAME) $(FAKEROOT)$(LIBDIR)/$(LIBNAME)
+install-shared-cap: install-common-cap $(MINCAPLIBNAME)
+ install -m 0644 $(MINCAPLIBNAME) $(FAKEROOT)$(LIBDIR)/$(MINCAPLIBNAME)
+ ln -sf $(MINCAPLIBNAME) $(FAKEROOT)$(LIBDIR)/$(MAJCAPLIBNAME)
+ ln -sf $(MAJCAPLIBNAME) $(FAKEROOT)$(LIBDIR)/$(CAPLIBNAME)
ifeq ($(FAKEROOT),)
-/sbin/ldconfig
endif
+install-static-psx: install-common-psx $(STAPSXLIBNAME)
+ install -m 0644 $(STAPSXLIBNAME) $(FAKEROOT)$(LIBDIR)/$(STAPSXLIBNAME)
+
+install-shared-psx: install-common-psx $(MINPSXLIBNAME)
+ install -m 0644 $(MINPSXLIBNAME) $(FAKEROOT)$(LIBDIR)/$(MINPSXLIBNAME)
+ ln -sf $(MINPSXLIBNAME) $(FAKEROOT)$(LIBDIR)/$(MAJPSXLIBNAME)
+ ln -sf $(MAJPSXLIBNAME) $(FAKEROOT)$(LIBDIR)/$(PSXLIBNAME)
+ifeq ($(FAKEROOT),)
+ -/sbin/ldconfig
+endif
+
+install-common-cap: install-common libcap.pc
+ install -m 0644 include/sys/capability.h $(FAKEROOT)$(INCDIR)/sys
+ install -m 0644 libcap.pc $(FAKEROOT)$(PKGCONFIGDIR)/libcap.pc
+
+install-common-psx: install-common libpsx.pc
+ install -m 0644 include/sys/psx_syscall.h $(FAKEROOT)$(INCDIR)/sys
+ install -m 0644 libpsx.pc $(FAKEROOT)$(PKGCONFIGDIR)/libpsx.pc
+
+install-common:
+ mkdir -p -m 0755 $(FAKEROOT)$(INCDIR)/sys
+ mkdir -p -m 0755 $(FAKEROOT)$(PKGCONFIGDIR)
+ mkdir -p -m 0755 $(FAKEROOT)$(LIBDIR)
+
clean:
$(LOCALCLEAN)
- rm -f $(CAPOBJS) $(LIBNAME)* $(STACAPLIBNAME) libcap.pc libpsx.pc
- rm -f $(PSXOBJS) $(STAPSXLIBNAME)
+ rm -f $(CAPOBJS) $(CAPLIBNAME)* $(STACAPLIBNAME) libcap.pc
+ rm -f $(PSXOBJS) $(PSXLIBNAME)* $(STAPSXLIBNAME) libpsx.pc
rm -f cap_names.h cap_names.list.h _makenames $(GPERF_OUTPUT) cap_test
cd include/sys && $(LOCALCLEAN)
diff --git a/libcap/include/uapi/linux/capability.h b/libcap/include/uapi/linux/capability.h
index 6856f1f..09b5563 100644
--- a/libcap/include/uapi/linux/capability.h
+++ b/libcap/include/uapi/linux/capability.h
@@ -405,7 +405,13 @@
#define CAP_BPF 39
-#define CAP_LAST_CAP CAP_BPF
+/* Allow checkpoint/restore related operations */
+/* Allow PID selection during clone3() */
+/* Allow writing to ns_last_pid */
+
+#define CAP_CHECKPOINT_RESTORE 40
+
+#define CAP_LAST_CAP CAP_CHECKPOINT_RESTORE
#define cap_valid(x) ((x) >= 0 && (x) <= CAP_LAST_CAP)
diff --git a/progs/.gitignore b/progs/.gitignore
index 1c7ff23..978229e 100644
--- a/progs/.gitignore
+++ b/progs/.gitignore
@@ -1,4 +1,5 @@
capsh
+tcapsh-static
getcap
getpcaps
setcap
diff --git a/progs/Makefile b/progs/Makefile
index 076e44f..1d7fc7a 100644
--- a/progs/Makefile
+++ b/progs/Makefile
@@ -8,16 +8,21 @@
BUILD=$(PROGS)
-ifneq ($(DYNAMIC),yes)
-LDFLAGS += --static
-endif
-
-DEPS=../libcap/libcap.a ../libcap/libpsx.a
-
all: $(BUILD)
-$(DEPS):
- make -C ../libcap all
+ifeq ($(DYNAMIC),yes)
+LDPATH = LD_LIBRARY_PATH=../libcap
+DEPS = ../libcap/libcap.so
+else
+LDFLAGS += --static
+DEPS = ../libcap/libcap.a
+endif
+
+../libcap/libcap.a:
+ make -C ../libcap libcap.a
+
+../libcap/libcap.so:
+ make -C ../libcap libcap.so
$(BUILD): %: %.o $(DEPS)
$(CC) $(CFLAGS) -o $@ $< $(LIBCAPLIB) $(LDFLAGS)
@@ -36,9 +41,12 @@
test: $(PROGS)
-sudotest: test
- sudo ./quicktest.sh
+tcapsh-static: capsh.c $(DEPS)
+ $(CC) $(IPATH) $(CAPSH_SHELL) $(CFLAGS) -o $@ $< $(LIBCAPLIB) $(LDFLAGS) --static
+
+sudotest: test tcapsh-static
+ sudo $(LDPATH) ./quicktest.sh
clean:
$(LOCALCLEAN)
- rm -f *.o $(BUILD) tcapsh ping hack.sh compare-cap
+ rm -f *.o $(BUILD) tcapsh* privileged ping hack.sh compare-cap
diff --git a/progs/capsh.c b/progs/capsh.c
index 7bed98e..899f79c 100644
--- a/progs/capsh.c
+++ b/progs/capsh.c
@@ -324,6 +324,49 @@
free(names);
}
+/*
+ * find_self locates and returns the full pathname of the named binary
+ * that is running. Importantly, it looks in the context of the
+ * prevailing CHROOT. Further, it does not fail over to invoking a
+ * shell if the target binary looks like something other than a
+ * executable. If an executable is not found, the function terminates
+ * the program with an error.
+ */
+static char *find_self(const char *arg0)
+{
+ int i;
+ char *parts, *dir, *scratch;
+ const char *path;
+
+ for (i = strlen(arg0)-1; i >= 0 && arg0[i] != '/'; i--);
+ if (i >= 0) {
+ return strdup(arg0);
+ }
+
+ path = getenv("PATH");
+ if (path == NULL) {
+ fprintf(stderr, "no PATH environment variable found for re-execing\n");
+ exit(1);
+ }
+
+ parts = strdup(path);
+ scratch = malloc(2+strlen(path)+strlen(arg0));
+ if (parts == NULL || scratch == NULL) {
+ fprintf(stderr, "insufficient memory for path building\n");
+ exit(1);
+ }
+
+ for (i=0; (dir = strtok(parts, ":")); parts = NULL) {
+ sprintf(scratch, "%s/%s", dir, arg0);
+ if (access(scratch, X_OK) == 0) {
+ return scratch;
+ }
+ }
+
+ fprintf(stderr, "unable to find executable '%s' in PATH\n", arg0);
+ exit(1);
+}
+
int main(int argc, char *argv[], char *envp[])
{
pid_t child;
@@ -782,10 +825,14 @@
} else if (!strcmp("--print", argv[i])) {
arg_print();
} else if ((!strcmp("--", argv[i])) || (!strcmp("==", argv[i]))) {
- argv[i] = strdup(argv[i][0] == '-' ? shell : argv[0]);
+ if (argv[i][0] == '=') {
+ argv[i] = find_self(argv[0]);
+ } else {
+ argv[i] = strdup(shell);
+ }
argv[argc] = NULL;
execve(argv[i], argv+i, envp);
- fprintf(stderr, "execve '%s' failed!\n", shell);
+ fprintf(stderr, "execve '%s' failed!\n", argv[i]);
exit(1);
} else if (!strncmp("--shell=", argv[i], 8)) {
shell = argv[i]+8;
diff --git a/progs/quicktest.sh b/progs/quicktest.sh
index fbe98a6..1c21bb4 100755
--- a/progs/quicktest.sh
+++ b/progs/quicktest.sh
@@ -44,8 +44,15 @@
pass_capsh --print
+# Validate that PATH expansion works
+PATH=$(/bin/pwd)/junk:$(/bin/pwd) capsh == == == --modes
+if [ $? -ne 0 ]; then
+ echo "Failed to execute capsh consecutively for capability manipulation"
+ exit 1
+fi
+
# Make a local non-setuid-0 version of capsh and call it privileged
-cp ./capsh ./privileged && /bin/chmod -s ./privileged
+cp ./tcapsh-static ./privileged && /bin/chmod -s ./privileged
if [ $? -ne 0 ]; then
echo "Failed to copy capsh for capability manipulation"
exit 1
@@ -77,7 +84,7 @@
pass_capsh --keep=0 --keep=1 --keep=0 --keep=1 --print
/bin/rm -f tcapsh
-/bin/cp capsh tcapsh
+/bin/cp tcapsh-static tcapsh
/bin/chown root.root tcapsh
/bin/chmod u+s tcapsh
/bin/ls -l tcapsh
@@ -166,7 +173,7 @@
# Verify we can chroot
pass_capsh --chroot=$(/bin/pwd)
-pass_capsh --chroot=$(/bin/pwd) ==
+pass_capsh -- -c "./tcapsh-static --chroot=$(/bin/pwd) =="
fail_capsh --chroot=$(/bin/pwd) -- -c "echo oops"
./capsh --has-ambient
@@ -216,7 +223,7 @@
# nsprivileged capsh will have an ns rootid value (this is
# the same setup as an earlier test but with a ns file cap).
rm -f nsprivileged
-cp ./capsh ./nsprivileged && /bin/chmod -s ./nsprivileged
+cp ./tcapsh-static ./nsprivileged && /bin/chmod -s ./nsprivileged
./setcap -n 1 all=ep ./nsprivileged
if [ $? -eq 0 ]; then
./getcap -n ./nsprivileged | fgrep "[rootid=1]"
diff --git a/psx/include/sys/psx_syscall.h b/psx/include/sys/psx_syscall.h
index 8044fbd..d1159e2 100644
--- a/psx/include/sys/psx_syscall.h
+++ b/psx/include/sys/psx_syscall.h
@@ -26,14 +26,6 @@
#include <pthread.h>
/*
- * This function is actually provided by the linker trick:
- *
- * gcc ... -lpsx -lpthread -Wl,-wrap,pthread_create
- */
-int __real_pthread_create(pthread_t *thread, const pthread_attr_t *attr,
- void *(*start_routine) (void *), void *arg);
-
-/*
* psx_syscall performs the specified syscall on all psx registered
* threads. The mechanism by which this occurs is much less efficient
* than a standard system call on Linux, so it should only be used
diff --git a/psx/psx.c b/psx/psx.c
index b494072..72c2098 100644
--- a/psx/psx.c
+++ b/psx/psx.c
@@ -78,8 +78,6 @@
pthread_mutex_t state_mu;
pthread_cond_t cond; /* this is only used to wait on 'state' changes */
psx_tracker_state_t state;
- int (*creator)(pthread_t *thread, const pthread_attr_t *attr,
- void *(*start_routine) (void *), void *arg);
int initialized;
int psx_sig;
@@ -170,6 +168,12 @@
void *(*start_routine) (void *), void *arg);
/*
+ * psx requires this function to be provided by the linkage wrapping.
+ */
+extern int __real_pthread_create(pthread_t *thread, const pthread_attr_t *attr,
+ void *(*start_routine) (void *), void *arg);
+
+/*
* psx_syscall_start initializes the subsystem including initializing
* the mutex.
*/
@@ -177,8 +181,6 @@
pthread_mutex_init(&psx_tracker.state_mu, NULL);
pthread_cond_init(&psx_tracker.cond, NULL);
pthread_key_create(&psx_action_key, NULL);
- psx_tracker.creator = (pthread_create == __wrap_pthread_create ?
- __real_pthread_create : pthread_create);
pthread_atfork(_psx_prepare_fork, _psx_fork_completed, _psx_forked_child);
/*
@@ -404,7 +406,7 @@
*/
pthread_sigmask(SIG_BLOCK, &sigbit, NULL);
- int ret = psx_tracker.creator(thread, attr, _psx_start_fn, starter);
+ int ret = __real_pthread_create(thread, attr, _psx_start_fn, starter);
if (ret == -1) {
psx_new_state(_PSX_CREATE, _PSX_IDLE);
memset(starter, 0, sizeof(*starter));
diff --git a/psx/psx.go b/psx/psx.go
index 7eaad55..6a17334 100644
--- a/psx/psx.go
+++ b/psx/psx.go
@@ -36,14 +36,7 @@
//
// If your Go compiler is older than go1.15, a workaround may be
// required to be able to link this package. In order to do what it
-// needs to this package employs some unusual linking flags. You will
-// need to do this for any Go toolchain that that does not include
-// this patch:
-//
-// https://go-review.googlesource.com/c/go/+/236139/
-//
-// As of the time of writing, that is all release tags prior to
-// go1.15rc1 .
+// needs to this package employs some unusual linking flags.
//
// The workaround is to build with the following CGO_LDFLAGS_ALLOW
// in effect:
diff --git a/tests/.gitignore b/tests/.gitignore
index dc6af52..ac7ffb0 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -1,6 +1,7 @@
noop
psx_test
-psx_test_wrap
libcap_psx_test
libcap_launch_test
libcap_psx_launch_test
+exploit
+noexploit
diff --git a/tests/Makefile b/tests/Makefile
index bfedbc2..fc39fee 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -1,51 +1,116 @@
#
-# defines
+# NOTE the built tests are all designed to be run from this
+# working directory when built DYNAMIC=yes. That is, they
+# link to the shared libraries in ../libcap/ .
#
topdir=$(shell pwd)/..
include ../Make.Rules
#
-DEPS=../libcap/libcap.a ../libcap/libpsx.a
-
-all: psx_test psx_test_wrap libcap_psx_test libcap_launch_test
-
-$(DEPS):
- make -C ../libcap all
-
-test: run_psx_test run_libcap_psx_test
-
-sudotest: test run_libcap_launch_test run_libcap_launch_test
+all:
+ make libcap_launch_test
+ifeq ($(PTHREADS),yes)
+ make psx_test libcap_psx_test libcap_psx_launch_test
+endif
install: all
+ifeq ($(DYNAMIC),yes)
+LINKEXTRA=-Wl,-rpath,../libcap
+DEPS=../libcap/libcap.so ../progs/tcapsh-static
+ifeq ($(PTHREADS),yes)
+DEPS += ../libcap/libpsx.so
+endif
+else
+LDFLAGS += --static
+DEPS=../libcap/libcap.a ../progs/tcapsh-static
+ifeq ($(PTHREADS),yes)
+DEPS += ../libcap/libpsx.a
+endif
+endif
+
+../libcap/libcap.so:
+ make -C ../libcap libcap.so
+
+../libcap/libcap.a:
+ make -C ../libcap libcap.a
+
+ifeq ($(PTHREADS),yes)
+../libcap/libpsx.so:
+ make -C ../libcap libpsx.so
+
+../libcap/libpsx.a:
+ make -C ../libcap libpsx.a
+endif
+
+../progs/tcapsh-static:
+ make -C ../progs tcapsh-static
+
+test:
+ifeq ($(PTHREADS),yes)
+ make run_psx_test run_libcap_psx_test
+endif
+
+sudotest: test
+ make run_libcap_launch_test
+ifeq ($(PTHREADS),yes)
+ make run_libcap_psx_launch_test run_exploit_test
+endif
+
+# unprivileged
run_psx_test: psx_test
./psx_test
psx_test: psx_test.c $(DEPS)
- $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LIBPSXLIB) -Wl,-wrap,pthread_create
+ $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LDFLAGS)
run_libcap_psx_test: libcap_psx_test
./libcap_psx_test
libcap_psx_test: libcap_psx_test.c $(DEPS)
- $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LIBCAPLIB) $(LIBPSXLIB) -Wl,-wrap,pthread_create --static
+ $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) $(LDFLAGS)
-run_libcap_launch_test: libcap_launch_test libcap_psx_launch_test noop
+# privileged
+run_libcap_launch_test: libcap_launch_test noop
sudo ./libcap_launch_test
+
+run_libcap_psx_launch_test: libcap_psx_launch_test
sudo ./libcap_psx_launch_test
libcap_launch_test: libcap_launch_test.c $(DEPS)
- $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LIBCAPLIB) --static
+ $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LDFLAGS)
-# this varies only slightly from the above insofar as it currently
+# 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 $@ $(LIBCAPLIB) $(LIBPSXLIB) -Wl,-wrap,pthread_create --static
+ $(CC) $(CFLAGS) $(IPATH) -DWITH_PTHREADS $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) $(LDFLAGS)
+
+# This test demonstrates that libpsx is needed to secure multithreaded
+# programs that link against libcap.
+run_exploit_test: exploit noexploit
+ @echo exploit should succeed
+ sudo ./exploit ; if [ $$? -ne 0 ]; then exit 0; else exit 1 ; fi
+ @echo exploit should fail
+ sudo ./noexploit ; if [ $$? -eq 0 ]; then exit 0; else exit 1 ; fi
+
+exploit.o: exploit.c
+ $(CC) $(CFLAGS) $(IPATH) -c $<
+
+exploit: exploit.o $(DEPS)
+ $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) -lpthread $(LDFLAGS)
+
+# 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)
+
+# 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 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
new file mode 100644
index 0000000..28bac88
--- /dev/null
+++ b/tests/exploit.c
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2020 Andrew G Morgan <morgan@kernel.org>
+ *
+ * This program exploit demonstrates why libcap alone in a
+ * multithreaded C/C++ program is inherently vulnerable to privilege
+ * escalation.
+ *
+ * The code also serves as a demonstration of how linking with libpsx
+ * can eliminate this vulnerability by maintaining a process wide
+ * common security state.
+ *
+ * The basic idea (which is well known and why POSIX stipulates "posix
+ * semantics" for security relevant state at the abstraction of a
+ * process) is that, because of shared memory, if a single thread alone
+ * is vulnerable to code injection, then it can cause any other thread
+ * to execute arbitrary code. As such, if all but one thread drops
+ * privilege, privilege escalation is somewhat trivial.
+ */
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/capability.h>
+#include <sys/types.h>
+
+/* thread coordination */
+pthread_mutex_t mu;
+pthread_cond_t cond;
+int hits;
+
+/* evidence of highest privilege attained */
+ssize_t greatest_len;
+char *text;
+
+/*
+ * interrupt handler - potentially watching for an opportunity to
+ * perform an exploit when invoked as a privileged thread.
+ */
+static void handler(int signum, siginfo_t *info, void *ignore) {
+ ssize_t length;
+ char *working;
+ pthread_mutex_lock(&mu);
+
+ cap_t caps = cap_get_proc();
+ working = cap_to_text(caps, &length);
+ if (length > greatest_len) {
+ /*
+ * This is where the exploit code might go.
+ */
+ cap_free(text);
+ text = working;
+ greatest_len = length;
+ }
+ cap_free(caps);
+ hits++;
+
+ pthread_cond_signal(&cond);
+ pthread_mutex_unlock(&mu);
+
+}
+
+/*
+ * privileged thread code (imagine it doing whatever needs privilege).
+ */
+static void *victim(void *args) {
+ pthread_mutex_lock(&mu);
+ hits = 1;
+ printf("started privileged thread\n");
+ pthread_cond_signal(&cond);
+ pthread_mutex_unlock(&mu);
+
+ pthread_mutex_lock(&mu);
+ while (hits < 2) {
+ pthread_cond_wait(&cond, &mu);
+ }
+ pthread_mutex_unlock(&mu);
+
+ return NULL;
+}
+
+int main(int argc, char **argv) {
+ pthread_t peer;
+ cap_t caps = cap_init();
+ struct sigaction sig_action;
+
+ printf("program starting\n");
+ if (pthread_create(&peer, NULL, victim, NULL)) {
+ perror("unable to start the victim thread");
+ exit(1);
+ }
+
+ /*
+ * Wait until the peer thread is fully up.
+ */
+ pthread_mutex_lock(&mu);
+ while (hits < 1) {
+ pthread_cond_wait(&cond, &mu);
+ }
+ pthread_mutex_unlock(&mu);
+
+ printf("dropping privilege from main process thread\n");
+
+ if (cap_set_proc(caps)) {
+ perror("unable to drop capabilities from main process thread");
+ exit(1);
+ }
+ cap_free(caps);
+
+ /* confirm the low privilege of the process' main thread */
+
+ caps = cap_get_proc();
+ text = cap_to_text(caps, &greatest_len);
+ cap_free(caps);
+
+ printf("no privilege in main process thread: len:%ld, caps:\"%s\"\n",
+ greatest_len, text);
+ if (greatest_len != 1) {
+ printf("failed to lower privilege as expected\n");
+ exit(1);
+ }
+
+ /*
+ * So, we have confirmed that this running thread has no
+ * privilege. From this thread we setup an interrupt handler and
+ * then trigger it on the privileged peer thread.
+ */
+
+ sig_action.sa_sigaction = &handler;
+ sigemptyset(&sig_action.sa_mask);
+ sig_action.sa_flags = SA_SIGINFO | SA_RESTART;;
+ sigaction(SIGRTMIN, &sig_action, NULL);
+
+ pthread_kill(peer, SIGRTMIN);
+
+ /*
+ * Wait for the thread to exit.
+ */
+ pthread_join(peer, NULL);
+
+ /*
+ * Let's see how we did with the exploit.
+ */
+
+ printf("greatest privilege in main process thread: len:%ld, caps:\"%s\"\n",
+ greatest_len, text);
+
+ cap_free(text);
+ if (greatest_len != 1) {
+ printf("exploit succeeded\n");
+ exit(1);
+ } else {
+ printf("exploit failed\n");
+ }
+}
diff --git a/tests/libcap_launch_test.c b/tests/libcap_launch_test.c
index be33abc..c9ef205 100644
--- a/tests/libcap_launch_test.c
+++ b/tests/libcap_launch_test.c
@@ -1,4 +1,5 @@
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include <sys/capability.h>
#include <sys/types.h>
@@ -34,20 +35,20 @@
int main(int argc, char **argv) {
static struct test_case_s vs[] = {
{
- .args = { "../progs/capsh", "--", "-c", "echo hello" },
+ .args = { "../progs/tcapsh-static", "--", "-c", "echo hello" },
.result = 0
},
{
- .args = { "../progs/capsh", "--is-uid=123" },
+ .args = { "../progs/tcapsh-static", "--is-uid=123" },
.result = 256
},
{
- .args = { "../progs/capsh", "--is-uid=123" },
+ .args = { "../progs/tcapsh-static", "--is-uid=123" },
.result = 0,
.uid = 123,
},
{
- .args = { "../progs/capsh", "--is-gid=123" },
+ .args = { "../progs/tcapsh-static", "--is-gid=123" },
.result = 0,
.gid = 123,
.ngroups = 1,
@@ -55,13 +56,13 @@
.iab = "",
},
{
- .args = { "../progs/capsh", "--dropped=cap_chown",
+ .args = { "../progs/tcapsh-static", "--dropped=cap_chown",
"--has-i=cap_chown" },
.result = 0,
.iab = "!%cap_chown"
},
{
- .args = { "../progs/capsh", "--dropped=cap_chown",
+ .args = { "../progs/tcapsh-static", "--dropped=cap_chown",
"--has-i=cap_chown", "--is-uid=234",
"--has-a=cap_chown", "--has-p=cap_chown" },
.uid = 234,
@@ -69,7 +70,7 @@
.iab = "!^cap_chown"
},
{
- .args = { "../progs/capsh", "--inmode=NOPRIV" },
+ .args = { "../progs/tcapsh-static", "--inmode=NOPRIV" },
.result = 0,
.mode = CAP_MODE_NOPRIV
},
@@ -166,5 +167,6 @@
printf("cap_launch_test: PASSED\n");
} else {
printf("cap_launch_test: FAILED\n");
+ exit(1);
}
}