aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-09-21 21:57:11 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-09-21 21:57:11 +0000
commit357ed048debba31da3ee1c0a24a3915fbe2d6a43 (patch)
tree14cfbb9d58780e8b97fa7ed642ea8b7dc1233c6c
parente61c65778b22599546e46eee12f857dd1c28f89e (diff)
parentd7d1a0a38c5be06a7e7d6391d140b54878836f48 (diff)
downloadlibcap-android14-qpr2-s2-release.tar.gz
Change-Id: I9c49381008e4ea940690b36fd9cde1c2078adc75
-rw-r--r--License2
-rw-r--r--METADATA14
-rw-r--r--Make.Rules89
-rw-r--r--Makefile38
-rw-r--r--cap/License2
-rw-r--r--cap/cap.go33
-rw-r--r--cap/cap_test.go10
-rw-r--r--cap/convenience.go26
-rw-r--r--cap/file.go61
-rw-r--r--cap/flags.go160
-rw-r--r--cap/go.mod2
-rw-r--r--cap/iab.go218
-rw-r--r--cap/launch.go117
-rw-r--r--cap/names.go47
-rw-r--r--cap/text.go3
-rw-r--r--contrib/Makefile2
-rw-r--r--contrib/bug216610/.gitignore3
-rw-r--r--contrib/bug216610/Dockerfile13
-rw-r--r--contrib/bug216610/Makefile30
-rw-r--r--contrib/bug216610/README.md139
-rwxr-xr-xcontrib/bug216610/c/build.sh10
-rw-r--r--contrib/bug216610/c/fib.c20
-rwxr-xr-xcontrib/bug216610/c/gcc.sh61
-rw-r--r--contrib/bug216610/go/.gitignore5
-rw-r--r--contrib/bug216610/go/fibber/fib.go32
-rw-r--r--contrib/bug216610/go/fibber/fibs_linux_amd64.s21
-rw-r--r--contrib/bug216610/go/fibber/fibs_linux_arm.s23
-rw-r--r--contrib/bug216610/go/go.mod5
-rw-r--r--contrib/bug216610/go/main.go29
-rwxr-xr-xcontrib/bug216610/mkdocker.sh18
-rwxr-xr-xcontrib/bug216610/package_fns.sh47
-rw-r--r--contrib/bug400591/Makefile4
-rw-r--r--contrib/capso/.gitignore2
-rw-r--r--contrib/capso/Makefile23
-rw-r--r--contrib/capso/README.md21
-rw-r--r--contrib/capso/bind.c29
-rw-r--r--contrib/capso/capso.c368
-rw-r--r--contrib/capso/capso.h16
-rw-r--r--contrib/seccomp/go.mod2
-rw-r--r--contrib/sucap/Makefile15
-rw-r--r--contrib/sucap/README.md22
-rw-r--r--contrib/sucap/su.c202
-rw-r--r--doc/Makefile23
-rw-r--r--doc/__psx_syscall.31
-rw-r--r--doc/cap_clear.361
-rw-r--r--doc/cap_copy_ext.319
-rw-r--r--doc/cap_copy_int_check.31
-rw-r--r--doc/cap_fill.31
-rw-r--r--doc/cap_fill_flag.31
-rw-r--r--doc/cap_from_text.312
-rw-r--r--doc/cap_get_file.36
-rw-r--r--doc/cap_get_nsowner.31
-rw-r--r--doc/cap_get_proc.356
-rw-r--r--doc/cap_iab.3140
-rw-r--r--doc/cap_iab_compare.31
-rw-r--r--doc/cap_iab_dup.31
-rw-r--r--doc/cap_iab_get_pid.31
-rw-r--r--doc/cap_launch.337
-rw-r--r--doc/cap_max_bits.31
-rw-r--r--doc/cap_prctl.31
-rw-r--r--doc/cap_prctlw.31
-rw-r--r--doc/cap_proc_root.31
-rw-r--r--doc/cap_set_nsowner.31
-rw-r--r--doc/cap_set_syscall.31
-rw-r--r--doc/capability.md (renamed from doc/capability.notes)51
-rw-r--r--doc/capsh.182
-rw-r--r--doc/captree.875
-rwxr-xr-xdoc/crosslink.sh15
-rw-r--r--doc/getcap.812
-rw-r--r--doc/getpcaps.818
-rw-r--r--doc/libcap.3112
-rw-r--r--doc/libpsx.359
-rwxr-xr-xdoc/mkmd.sh18
-rw-r--r--doc/psx_load_syscalls.31
-rw-r--r--doc/psx_set_sensitivity.31
-rw-r--r--doc/setcap.88
-rw-r--r--doc/values/3.txt4
-rw-r--r--doc/values/8.txt39
-rw-r--r--go/.gitignore8
-rw-r--r--go/Makefile125
-rw-r--r--go/b215283.go47
-rw-r--r--go/compare-cap.go16
-rw-r--r--go/go.mod4
-rw-r--r--go/mismatch.go15
-rw-r--r--go/mknames.go4
-rw-r--r--go/psx-fd.go25
-rw-r--r--go/try-launching.go4
-rw-r--r--goapps/captrace/captrace.go230
-rw-r--r--goapps/captrace/go.mod5
-rw-r--r--goapps/captree/captree.go468
-rw-r--r--goapps/captree/go.mod5
-rw-r--r--goapps/gowns/go.mod2
-rw-r--r--goapps/setid/go.mod4
-rw-r--r--goapps/web/README18
-rw-r--r--goapps/web/README.md28
-rw-r--r--goapps/web/go.mod2
-rw-r--r--goapps/web/web.go2
-rw-r--r--kdebug/Makefile2
-rwxr-xr-xkdebug/test-kernel.sh7
-rw-r--r--libcap/Makefile53
-rw-r--r--libcap/_makenames.c8
-rw-r--r--libcap/cap_alloc.c238
-rw-r--r--libcap/cap_extint.c72
-rw-r--r--libcap/cap_file.c40
-rw-r--r--libcap/cap_flag.c124
-rw-r--r--libcap/cap_names.header5
-rw-r--r--libcap/cap_proc.c244
-rw-r--r--libcap/cap_test.c248
-rw-r--r--libcap/cap_text.c192
-rw-r--r--libcap/execable.c51
-rw-r--r--libcap/execable.h49
-rw-r--r--libcap/include/sys/capability.h50
-rw-r--r--libcap/include/uapi/linux/capability.h5
-rw-r--r--libcap/libcap.h46
-rw-r--r--libcap/psx_exec.c15
-rw-r--r--pam_cap/.gitignore2
-rw-r--r--pam_cap/License2
-rw-r--r--pam_cap/Makefile69
-rw-r--r--pam_cap/execable.c7
-rw-r--r--pam_cap/pam_cap.c111
-rw-r--r--pam_cap/test_pam_cap.c49
-rw-r--r--pgp.keys.asc251
-rw-r--r--progs/Makefile43
-rw-r--r--progs/capsh.c385
-rw-r--r--progs/capshdoc.c422
-rw-r--r--progs/capshdoc.h410
-rw-r--r--progs/getcap.c10
-rw-r--r--progs/getpcaps.c93
-rwxr-xr-xprogs/mkcapshdoc.sh16
-rwxr-xr-xprogs/quicktest.sh55
-rw-r--r--progs/setcap.c102
-rw-r--r--psx/License2
-rw-r--r--psx/psx.c157
-rw-r--r--psx/psx.go16
-rw-r--r--psx/psx_cgo.go12
-rw-r--r--psx/psx_syscall.h21
-rw-r--r--psx/psx_test.go59
-rw-r--r--tests/Makefile42
-rw-r--r--tests/libcap_launch_test.c28
-rw-r--r--tests/libcap_psx_test.c19
-rw-r--r--tests/uns_test.c15
141 files changed, 6024 insertions, 1682 deletions
diff --git a/License b/License
index 43a1297..2398977 100644
--- a/License
+++ b/License
@@ -1,3 +1,5 @@
+/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-only */
+
Unless otherwise *explicitly* stated, the following text describes the
licensed conditions under which the contents of this libcap release
may be used and distributed.
diff --git a/METADATA b/METADATA
index d84738c..a9e33e3 100644
--- a/METADATA
+++ b/METADATA
@@ -1,15 +1,19 @@
+# This project was upgraded with external_updater.
+# Usage: tools/external_updater/updater.sh update libcap
+# For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md
+
name: "libcap"
description: "This is a library for getting and setting POSIX.1e (formerly POSIX 6) draft 15 capabilities."
third_party {
url {
type: GIT
- value: "https://git.kernel.org/pub/scm/linux/kernel/git/morgan/libcap.git"
+ value: "https://git.kernel.org/pub/scm/libs/libcap/libcap.git"
}
- version: "libcap-2.53"
+ version: "libcap-2.69"
license_type: NOTICE
last_upgrade_date {
- year: 2022
- month: 10
- day: 10
+ year: 2023
+ month: 8
+ day: 23
}
}
diff --git a/Make.Rules b/Make.Rules
index 125f2aa..721fc7a 100644
--- a/Make.Rules
+++ b/Make.Rules
@@ -1,7 +1,7 @@
# Common version number defines for libcap
LIBTITLE=libcap
VERSION=2
-MINOR=53
+MINOR=69
#
## Optional prefixes:
@@ -18,7 +18,15 @@ FAKEROOT=$(DESTDIR)
# administrative operations that could be needed to recover a system.
ifndef lib
-lib=$(shell ldd /usr/bin/ld|egrep "ld-linux|ld.so"|cut -d/ -f2)
+lib=$(shell ldd /usr/bin/ld|grep -E "ld-linux|ld.so"|cut -d/ -f2)
+endif
+
+ifndef sbin
+sbin=sbin
+endif
+
+ifdef sbindir
+sbin=$(sbindir)
endif
ifdef prefix
@@ -37,7 +45,7 @@ endif
# Target directories
MANDIR=$(man_prefix)/man
-SBINDIR=$(exec_prefix)/sbin
+SBINDIR=$(exec_prefix)/$(sbin)
INCDIR=$(inc_prefix)/include
LIBDIR=$(lib_prefix)/$(lib)
PKGCONFIGDIR=$(LIBDIR)/pkgconfig
@@ -46,40 +54,63 @@ GOPKGDIR=$(prefix)/share/gocode/src
# 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.)
+# manner. (Let's hope not. If that happens, I'll also drop deprecated
+# API functions.)
GOMAJOR=1
# Compilation specifics
KERNEL_HEADERS := $(topdir)/libcap/include/uapi
-IPATH += -fPIC -I$(KERNEL_HEADERS) -I$(topdir)/libcap/include
+LIBCAP_INCLUDES = -I$(KERNEL_HEADERS) -I$(topdir)/libcap/include
+DEFINES := -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64
+SYSTEM_HEADERS = /usr/include
+SUDO := sudo
CC := $(CROSS_COMPILE)gcc
-DEFINES := -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64
-COPTS ?= -O2
-CFLAGS ?= $(COPTS) $(DEFINES)
-BUILD_CC ?= $(CC)
-BUILD_COPTS ?= -O2
-BUILD_CFLAGS ?= $(BUILD_COPTS) $(DEFINES) $(IPATH)
+LD := $(CC) -Wl,-x -shared
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 \
- -Wstrict-prototypes -Wmissing-prototypes \
- -Wnested-externs -Winline -Wshadow
-LD=$(CC) -Wl,-x -shared
-LDFLAGS ?= #-g
+
+# Reference:
+# CPPFLAGS used for building .o files from .c & .h files
+# CFLAGS used when building libraries from .o, .c and .h files
+
+DEBUG = # -g -DDEBUG
+WARNINGS=-Wall -Wwrite-strings -Wpointer-arith -Wcast-qual -Wcast-align \
+ -Wstrict-prototypes -Wmissing-prototypes -Wnested-externs \
+ -Winline -Wshadow -Wunreachable-code
+COPTS ?= -O2
+CFLAGS ?= $(COPTS) $(DEBUG)
+CFLAGS += $(WARNINGS)
+CPPFLAGS += -Dlinux $(DEFINES) $(LIBCAP_INCLUDES)
+LDFLAGS ?= # -g
+
+BUILD_CC ?= $(CC)
+BUILD_LD ?= $(BUILD_CC) -Wl,-x -shared
+BUILD_COPTS ?= $(COPTS)
+BUILD_CFLAGS ?= $(BUILD_COPTS)
+BUILD_CPPFLAGS += -Dlinux $(WARNINGS) $(DEBUG) $(DEFINES) $(LIBCAP_INCLUDES)
+BUILD_LDFLAGS ?= $(LDFLAGS)
+BUILD_SED ?= sed
+BUILD_GREP ?= grep
+BUILD_EGREP ?= $(BUILD_GREP) -E
+BUILD_FGREP ?= $(BUILD_GREP) -F
+
+# Plan to eventually redefine BUILD_GPERF to be the actual gperf tool
+# alias as per above. Typical distributions are upto a year behind
+# HEAD so we'll not do that before 2023-01-01.
+ifdef BUILD_GPERF
+$(error BUILD_GPERF is now reserved, please use USE_GPERF=yes or no instead)
+endif
+
+USE_GPERF ?= $(shell which gperf >/dev/null 2>/dev/null && echo yes)
+
LIBCAPLIB := -L$(topdir)/libcap -lcap
PSXLINKFLAGS := -lpthread -Wl,-wrap,pthread_create
LIBPSXLIB := -L$(topdir)/libcap -lpsx $(PSXLINKFLAGS)
-BUILD_GPERF := $(shell which gperf >/dev/null 2>/dev/null && echo yes)
-
-SYSTEM_HEADERS = /usr/include
INCS=$(topdir)/libcap/include/sys/capability.h
-CFLAGS += -Dlinux $(WARNINGS) $(DEBUG)
INDENT := $(shell if [ -n "$$(which indent 2>/dev/null)" ]; then echo "| indent -kr" ; fi)
# SHARED tracks whether or not the SHARED libraries (libcap.so,
@@ -87,6 +118,7 @@ INDENT := $(shell if [ -n "$$(which indent 2>/dev/null)" ]; then echo "| indent
# support shared libraries.)
SHARED ?= yes
# DYNAMIC controls how capsh etc are linked - to shared or static libraries
+# Force enabled with "make DYNAMIC=yes ...".
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)
@@ -109,15 +141,16 @@ ifeq ($(GOLANG),yes)
GOROOT ?= $(shell $(GO) env GOROOT)
GOCGO ?= $(shell if [ "$(shell $(GO) env CGO_ENABLED)" = 1 ]; then echo yes ; else echo no ; fi)
GOOSARCH ?= $(shell $(GO) env GOHOSTOS)_$(shell $(GO) env GOHOSTARCH)
-CGO_REQUIRED=$(shell $(topdir)/go/cgo-required.sh $(GO))
+CGO_REQUIRED := $(shell $(topdir)/go/cgo-required.sh $(GO))
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 := CGO_LDFLAGS_ALLOW="-Wl,-?-wrap[=,][^-.@][^,]*"
endif
-CGO_CFLAGS := -I$(topdir)/libcap/include
+CGO_CFLAGS := $(LIBCAP_INCLUDES)
CGO_LDFLAGS := -L$(topdir)/libcap
+GO_BUILD_FLAGS :=
endif
endif
@@ -136,11 +169,11 @@ endif
#
# make RAISE_SETFCAP=yes install
#
-# This is now defaulted to no because some distributions have started
-# shipping with all users blessed with full inheritable sets which makes
-# no sense whatsoever!
+# This is now defaulted to no because some distributions started
+# shipping with all users blessed with full inheritable sets which
+# makes no sense whatsoever!
#
-# Indeed, it looks alarmingly like these distributions are recreating
+# Indeed, it looked alarmingly like these distributions were recreating
# the environment for what became known as the sendmail-capabilities
# bug from 2000:
#
diff --git a/Makefile b/Makefile
index d26af01..fca2b58 100644
--- a/Makefile
+++ b/Makefile
@@ -8,7 +8,7 @@ include Make.Rules
# flags
#
-all install clean: %: %-here
+all test sudotest install clean: %: %-here
$(MAKE) -C libcap $@
ifneq ($(PAM_CAP),no)
$(MAKE) -C pam_cap $@
@@ -20,10 +20,13 @@ endif
$(MAKE) -C tests $@
$(MAKE) -C progs $@
$(MAKE) -C doc $@
- $(MAKE) -C kdebug $@
all-here:
+test-here:
+
+sudotest-here:
+
install-here:
clean-here:
@@ -32,8 +35,11 @@ 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. Try running: ./gomods.sh v$(GOMAJOR).$(VERSION).$(MINOR)" ; exit 1 ; done
+ for x in $$(find . -name go.mod); do $(BUILD_FGREP) -v "module" $$x | $(BUILD_FGREP) "kernel.org/pub/linux/libs/security/libcap" > /dev/null || continue ; $(BUILD_FGREP) "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 "Confirm headers export current version"
+ $(BUILD_FGREP) "#define LIBCAP_MAJOR $(VERSION)" libcap/include/sys/capability.h
+ $(BUILD_FGREP) "#define LIBCAP_MINOR $(MINOR)" libcap/include/sys/capability.h
@echo "Now validate that everything is checked in to a clean tree.."
test -z "$$(git status --ignored -s)"
@echo "All good!"
@@ -41,35 +47,15 @@ distclean: clean
release: distclean
cd .. && ln -s libcap libcap-$(VERSION).$(MINOR) && tar cvf libcap-$(VERSION).$(MINOR).tar --exclude patches libcap-$(VERSION).$(MINOR)/* && rm libcap-$(VERSION).$(MINOR)
-test: all
- $(MAKE) -C libcap $@
- $(MAKE) -C tests $@
-ifneq ($(PAM_CAP),no)
- $(MAKE) -C pam_cap $@
-endif
-ifeq ($(GOLANG),yes)
- $(MAKE) -C go $@
-endif
- $(MAKE) -C progs $@
-
ktest: all
$(MAKE) -C kdebug test
-sudotest: all
- $(MAKE) -C tests $@
-ifneq ($(PAM_CAP),no)
- $(MAKE) -C pam_cap $@
-endif
-ifeq ($(GOLANG),yes)
- $(MAKE) -C go $@
-endif
- $(MAKE) -C progs $@
-
-distcheck:
+distcheck: distclean
./distcheck.sh
+ $(MAKE) DYNAMIC=no COPTS="-D_FORTIFY_SOURCE=2 -O1 -g" clean test
$(MAKE) DYNAMIC=yes 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) PAM_CAP=no CC=musl-gcc clean all test sudotest
$(MAKE) CC=clang clean all test sudotest
$(MAKE) clean all test sudotest
$(MAKE) distclean
diff --git a/cap/License b/cap/License
index a0ec04e..095f754 100644
--- a/cap/License
+++ b/cap/License
@@ -1,3 +1,5 @@
+/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-only */
+
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.
diff --git a/cap/cap.go b/cap/cap.go
index 908e2bb..784bf65 100644
--- a/cap/cap.go
+++ b/cap/cap.go
@@ -28,7 +28,7 @@
// log.Fatalf("failed to drop privilege: %q -> %q: %v", old, empty, err)
// }
// now := cap.GetProc()
-// if cf, _ := now.Compare(empty); cf != 0 {
+// if cf, _ := now.Cf(empty); cf != 0 {
// log.Fatalf("failed to fully drop privilege: have=%q, wanted=%q", now, empty)
// }
//
@@ -103,6 +103,15 @@ const (
Inheritable
)
+// Diff summarizes the result of the (*Set).Cf() function.
+type Diff uint
+
+const (
+ effectiveDiff Diff = 1 << Effective
+ permittedDiff Diff = 1 << Permitted
+ inheritableDiff Diff = 1 << Inheritable
+)
+
// String identifies a Flag value by its conventional "e", "p" or "i"
// string abbreviation.
func (f Flag) String() string {
@@ -309,10 +318,18 @@ func NewSet() *Set {
// request of the Set is invalid in some way.
var ErrBadSet = errors.New("bad capability set")
+// good confirms that c looks valid.
+func (c *Set) good() error {
+ if c == nil || len(c.flat) == 0 {
+ return ErrBadSet
+ }
+ return nil
+}
+
// Dup returns a copy of the specified capability set.
func (c *Set) Dup() (*Set, error) {
- if c == nil || len(c.flat) == 0 {
- return nil, ErrBadSet
+ if err := c.good(); err != nil {
+ return nil, err
}
n := NewSet()
c.mu.RLock()
@@ -343,10 +360,9 @@ func GetProc() *Set {
return c
}
+// setProc uses syscaller to set process capabilities. Note, c is
+// either private to or (read) locked by the caller.
func (sc *syscaller) setProc(c *Set) error {
- if c == nil || len(c.flat) == 0 {
- return ErrBadSet
- }
return sc.capwcall(syscall.SYS_CAPSET, &header{magic: magic}, c.flat)
}
@@ -360,8 +376,13 @@ func (sc *syscaller) setProc(c *Set) error {
// 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 {
+ if err := c.good(); err != nil {
+ return err
+ }
state, sc := scwStateSC()
defer scwSetState(launchBlocked, state, -1)
+ c.mu.RLock()
+ defer c.mu.RUnlock()
return sc.setProc(c)
}
diff --git a/cap/cap_test.go b/cap/cap_test.go
index d7c7970..52afd43 100644
--- a/cap/cap_test.go
+++ b/cap/cap_test.go
@@ -199,18 +199,20 @@ func TestIAB(t *testing.T) {
if err != nil {
t.Fatalf("failed to get init's capabilities: %v", err)
}
- iab := IABInit()
- iab.Fill(Amb, one, Permitted)
+ iab := NewIAB()
+ if err := iab.Fill(Amb, one, Permitted); err != nil {
+ t.Fatalf("failed to fill Amb from Permitted: %v", err)
+ }
for i := 0; i < words; i++ {
if iab.i[i] != iab.a[i] {
- t.Errorf("[%d] i=0x%08x != a=0x%08x", i, iab.i[i], iab.a[i])
+ t.Errorf("[%d: %q] i=0x%08x != a=0x%08x", i, one, iab.i[i], iab.a[i])
}
}
one.ClearFlag(Inheritable)
iab.Fill(Inh, one, Inheritable)
for i := 0; i < words; i++ {
if iab.i[i] != iab.a[i] {
- t.Errorf("[%d] i=0x%08x != a=0x%08x", i, iab.i[i], iab.a[i])
+ t.Errorf("[%d: %q] i=0x%08x != a=0x%08x", i, one, iab.i[i], iab.a[i])
}
}
diff --git a/cap/convenience.go b/cap/convenience.go
index d604ad1..a31ac09 100644
--- a/cap/convenience.go
+++ b/cap/convenience.go
@@ -57,7 +57,7 @@ func (sc *syscaller) setSecbits(s Secbits) error {
// Set attempts to force the process Secbits to a value. This function
// will raise cap.SETPCAP in order to achieve this operation, and will
-// completely lower the Effective vector of the process returning.
+// completely lower the Effective Flag of the process upon returning.
func (s Secbits) Set() error {
state, sc := scwStateSC()
defer scwSetState(launchBlocked, state, -1)
@@ -75,6 +75,7 @@ const (
ModeNoPriv
ModePure1EInit
ModePure1E
+ ModeHybrid
)
// GetMode assesses the current process state and summarizes it as
@@ -82,6 +83,9 @@ const (
// declared ModeUncertain.
func GetMode() Mode {
b := GetSecbits()
+ if b == 0 {
+ return ModeHybrid
+ }
if b&securedBasicBits != securedBasicBits {
return ModeUncertain
}
@@ -101,12 +105,12 @@ func GetMode() Mode {
w := GetProc()
e := NewSet()
- cf, _ := w.Compare(e)
+ cf, _ := w.Cf(e)
- if Differs(cf, Inheritable) {
+ if cf.Has(Inheritable) {
return ModePure1E
}
- if Differs(cf, Permitted) || Differs(cf, Effective) {
+ if cf.Has(Permitted) || cf.Has(Effective) {
return ModePure1EInit
}
@@ -141,6 +145,10 @@ func (sc *syscaller) setMode(m Mode) error {
return err
}
+ if m == ModeHybrid {
+ return sc.setSecbits(0)
+ }
+
if m == ModeNoPriv || m == ModePure1EInit {
w.ClearFlag(Inheritable)
} else if m != ModePure1E {
@@ -199,6 +207,8 @@ func (m Mode) String() string {
return "PURE1E_INIT"
case ModePure1E:
return "PURE1E"
+ case ModeHybrid:
+ return "HYBRID"
default:
return "UNKNOWN"
}
@@ -234,11 +244,11 @@ func (sc *syscaller) setUID(uid int) error {
// all other variants of UID (EUID etc) to the specified value without
// dropping the privilege of the current process. This function will
// raise cap.SETUID in order to achieve this operation, and will
-// completely lower the Effective vector of the process before
+// completely lower the Effective Flag of the process before
// returning. Unlike the traditional method of dropping privilege when
-// changing from [E]UID=0 to some other UID, this function only
-// performs a change of UID cap.SETUID is available, and the action
-// does not alter the Permitted Flag of the process' Set.
+// changing from [E]UID=0 to some other UID, this function only can
+// perform any change of UID if cap.SETUID is available, and this
+// operation will not alter the Permitted Flag of the process' Set.
func SetUID(uid int) error {
state, sc := scwStateSC()
defer scwSetState(launchBlocked, state, -1)
diff --git a/cap/file.go b/cap/file.go
index 70dae92..d92548f 100644
--- a/cap/file.go
+++ b/cap/file.go
@@ -50,7 +50,7 @@ type vfsCaps3 struct {
RootID uint32
}
-// ErrBadSize indicates the the loaded file capability has
+// ErrBadSize indicates the loaded file capability has
// an invalid number of bytes in it.
var ErrBadSize = errors.New("filecap bad size")
@@ -164,6 +164,8 @@ func (c *Set) GetNSOwner() (int, error) {
if magic < kv3 {
return 0, ErrBadMagic
}
+ c.mu.RLock()
+ defer c.mu.RUnlock()
return c.nsRoot, nil
}
@@ -187,6 +189,9 @@ func (c *Set) SetNSOwner(uid int) {
// attributes, the process is a little lossy with respect to effective
// bits.
func (c *Set) packFileCap() ([]byte, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
var magic uint32
switch words {
case 1:
@@ -229,20 +234,22 @@ func (c *Set) packFileCap() ([]byte, error) {
// (*os.File).Fd(). This function can also be used to delete a file's
// capabilities, by calling with c = nil.
//
-// Note, Linux does not store the full Effective Value Flag in the
-// metadata for the file. Only a single Effective bit is stored in
-// this metadata. This single bit is non-zero if the Effective vector
-// has any overlapping bits with the Permitted or Inheritable vector
-// of c. This may appear suboptimal, but the reasoning behind it is
-// sound. Namely, the purpose of the Effective bit it to support
-// capabability unaware binaries that will only work if they magically
-// launch with the needed bits already raised (this bit is sometimes
-// referred to simply as the 'legacy' bit). Without *full* support for
-// capability manipulation, as it is provided in this "../libcap/cap"
-// package, this was the only way for Go programs to make use of
+// Note, Linux does not store the full Effective Flag in the metadata
+// for the file. Only a single Effective bit is stored in this
+// metadata. This single bit is non-zero if the Effective Flag has any
+// overlapping bits with the Permitted or Inheritable Flags of c. This
+// may appear suboptimal, but the reasoning behind it is sound.
+// Namely, the purpose of the Effective bit it to support capabability
+// unaware binaries that will only work if they magically launch with
+// the needed Values already raised (this bit is sometimes referred to
+// simply as the 'legacy' bit).
+//
+// Historical note: without *full* support for runtime capability
+// manipulation, as it is provided in this "../libcap/cap" package,
+// this was previously the only way for Go programs to make use of
// file capabilities.
//
-// The preferred way a binary will actually manipulate its
+// The preferred way that a binary will actually manipulate its
// file-acquired capabilities is to carefully and deliberately use
// this package (or libcap, assisted by libpsx, for threaded C/C++
// family code).
@@ -253,8 +260,8 @@ func (c *Set) SetFd(file *os.File) error {
}
return nil
}
- c.mu.Lock()
- defer c.mu.Unlock()
+ c.mu.RLock()
+ defer c.mu.RUnlock()
d, err := c.packFileCap()
if err != nil {
return err
@@ -272,7 +279,7 @@ func (c *Set) SetFd(file *os.File) error {
// capabilities, by calling with c = nil.
//
// Note, see the comment for SetFd() for some non-obvious behavior of
-// Linux for the Effective Value vector on the modified file.
+// Linux for the Effective Flag on the modified file.
func (c *Set) SetFile(path string) error {
fi, err := os.Stat(path)
if err != nil {
@@ -295,8 +302,8 @@ func (c *Set) SetFile(path string) error {
}
return nil
}
- c.mu.Lock()
- defer c.mu.Unlock()
+ c.mu.RLock()
+ defer c.mu.RUnlock()
d, err := c.packFileCap()
if err != nil {
return err
@@ -348,11 +355,11 @@ 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.
+// MinExtFlagSize defaults to 8 in order to be equivalent to libcap
+// defaults. 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
@@ -362,16 +369,16 @@ var MinExtFlagSize = uint(8)
// 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 err := c.good(); err != nil {
+ return nil, err
}
if MinExtFlagSize > 255 {
return nil, ErrOutOfRange
}
b := new(bytes.Buffer)
binary.Write(b, binary.LittleEndian, ExtMagic)
- c.mu.Lock()
- defer c.mu.Unlock()
+ c.mu.RLock()
+ defer c.mu.RUnlock()
var n = uint(0)
for i, f := range c.flat {
if nn := 4 * uint(i); nn+4 > n {
diff --git a/cap/flags.go b/cap/flags.go
index 83a871a..c4c2ed8 100644
--- a/cap/flags.go
+++ b/cap/flags.go
@@ -2,13 +2,13 @@ package cap
import "errors"
-// GetFlag determines if the requested bit is enabled in the Flag
-// vector of the capability Set.
+// GetFlag determines if the requested Value is enabled in the
+// specified Flag of the capability Set.
func (c *Set) GetFlag(vec Flag, val Value) (bool, error) {
- if c == nil || len(c.flat) == 0 {
+ if err := c.good(); err != nil {
// Checked this first, because otherwise we are sure
// cInit has been called.
- return false, ErrBadSet
+ return false, err
}
offset, mask, err := bitOf(vec, val)
if err != nil {
@@ -25,10 +25,10 @@ func (c *Set) GetFlag(vec Flag, val Value) (bool, error) {
// bits be checked for validity and permission by the kernel. If the
// function returns an error, the Set will not be modified.
func (c *Set) SetFlag(vec Flag, enable bool, val ...Value) error {
- if c == nil || len(c.flat) == 0 {
+ if err := c.good(); err != nil {
// Checked this first, because otherwise we are sure
// cInit has been called.
- return ErrBadSet
+ return err
}
c.mu.Lock()
defer c.mu.Unlock()
@@ -62,8 +62,8 @@ func (c *Set) SetFlag(vec Flag, enable bool, val ...Value) error {
// Clear fully clears a capability set.
func (c *Set) Clear() error {
- if c == nil || len(c.flat) == 0 {
- return ErrBadSet
+ if err := c.good(); err != nil {
+ return err
}
// startUp.Do(cInit) is not called here because c cannot be
// initialized except via this package and doing that will
@@ -75,31 +75,52 @@ 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
+// FillFlag copies the from flag values of ref into the to flag of
+// c. With this function, you can raise all of the permitted values in
+// the c Set from those in ref with c.Fill(cap.Permitted, ref,
+// cap.Permitted).
+func (c *Set) FillFlag(to Flag, ref *Set, from Flag) error {
+ if err := c.good(); err != nil {
+ return err
+ }
+ if err := ref.good(); err != nil {
+ return err
}
if to > Inheritable || from > Inheritable {
return ErrBadValue
}
+
+ // Avoid deadlock by using a copy.
+ if c != ref {
+ var err error
+ ref, err = ref.Dup()
+ if err != nil {
+ return err
+ }
+ }
+
c.mu.Lock()
defer c.mu.Unlock()
for i := range c.flat {
- c.flat[i][to] = c.flat[i][from]
+ c.flat[i][to] = ref.flat[i][from]
}
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 {
+ return c.FillFlag(to, c, from)
+}
+
// ErrBadValue indicates a bad capability value was specified.
var ErrBadValue = errors.New("bad capability value")
-// bitOf converts from a Value into the offset and mask for a
-// specific Value bit in the compressed (kernel ABI) representation of
-// a capability vector. If the requested bit is unsupported, an error
-// is returned.
+// bitOf converts from a Value into the offset and mask for a specific
+// Value bit in the compressed (kernel ABI) representation of a
+// capabilities. If the requested bit is unsupported, an error is
+// returned.
func bitOf(vec Flag, val Value) (uint, uint32, error) {
if vec > Inheritable || val > Value(words*32) {
return 0, 0, ErrBadValue
@@ -126,9 +147,12 @@ func allMask(index uint) (mask uint32) {
}
// forceFlag sets 'all' capability values (supported by the kernel) of
-// a flag vector to enable.
+// a specified Flag to enable.
func (c *Set) forceFlag(vec Flag, enable bool) error {
- if c == nil || len(c.flat) == 0 || vec > Inheritable {
+ if err := c.good(); err != nil {
+ return err
+ }
+ if vec > Inheritable {
return ErrBadSet
}
m := uint32(0)
@@ -143,37 +167,105 @@ func (c *Set) forceFlag(vec Flag, enable bool) error {
return nil
}
-// ClearFlag clears a specific vector of Values associated with the
-// specified Flag.
+// ClearFlag clears all the Values associated with the specified Flag.
func (c *Set) ClearFlag(vec Flag) error {
return c.forceFlag(vec, false)
}
-// Compare returns 0 if c and d are identical in content. Otherwise,
-// this function returns a non-zero value of 3 independent bits:
-// (differE ? 1:0) | (differP ? 2:0) | (differI ? 4:0). The Differs()
-// function can be used to test for a difference in a specific Flag.
-func (c *Set) Compare(d *Set) (uint, error) {
- if c == nil || len(c.flat) == 0 || d == nil || len(d.flat) == 0 {
- return 0, ErrBadSet
+// Cf returns 0 if c and d are identical. A non-zero Diff value
+// captures a simple macroscopic summary of how they differ. The
+// (Diff).Has() function can be used to determine how the two
+// capability sets differ.
+func (c *Set) Cf(d *Set) (Diff, error) {
+ if err := c.good(); err != nil {
+ return 0, err
}
- var cf uint
+ if c == d {
+ return 0, nil
+ }
+ d, err := d.Dup()
+ if err != nil {
+ return 0, err
+ }
+
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
+ var cf Diff
for i := 0; i < words; i++ {
if c.flat[i][Effective]^d.flat[i][Effective] != 0 {
- cf |= (1 << Effective)
+ cf |= effectiveDiff
}
if c.flat[i][Permitted]^d.flat[i][Permitted] != 0 {
- cf |= (1 << Permitted)
+ cf |= permittedDiff
}
if c.flat[i][Inheritable]^d.flat[i][Inheritable] != 0 {
- cf |= (1 << Inheritable)
+ cf |= inheritableDiff
}
}
return cf, nil
}
+// Compare returns 0 if c and d are identical in content.
+//
+// Deprecated: Replace with (*Set).Cf().
+//
+// Example, replace this:
+//
+// diff, err := a.Compare(b)
+// if err != nil {
+// return err
+// }
+// if diff == 0 {
+// return nil
+// }
+// if diff & (1 << Effective) {
+// log.Print("a and b difference includes Effective values")
+// }
+//
+// with this:
+//
+// diff, err := a.Cf(b)
+// if err != nil {
+// return err
+// }
+// if diff == 0 {
+// return nil
+// }
+// if diff.Has(Effective) {
+// log.Print("a and b difference includes Effective values")
+// }
+func (c *Set) Compare(d *Set) (uint, error) {
+ u, err := c.Cf(d)
+ return uint(u), err
+}
+
// Differs processes the result of Compare and determines if the
// Flag's components were different.
+//
+// Deprecated: Replace with (Diff).Has().
+//
+// Example, replace this:
+//
+// diff, err := a.Compare(b)
+// ...
+// if diff & (1 << Effective) {
+// ... different effective capabilities ...
+// }
+//
+// with this:
+//
+// diff, err := a.Cf(b)
+// ...
+// if diff.Has(Effective) {
+// ... different effective capabilities ...
+// }
func Differs(cf uint, vec Flag) bool {
return cf&(1<<vec) != 0
}
+
+// Has processes the Diff result of (*Set).Cf() and determines if the
+// Flag's components were different in that result.
+func (cf Diff) Has(vec Flag) bool {
+ return uint(cf)&(1<<vec) != 0
+}
diff --git a/cap/go.mod b/cap/go.mod
index a299f4c..bfabf0c 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 v1.2.53
+require kernel.org/pub/linux/libs/security/libcap/psx v1.2.69
diff --git a/cap/iab.go b/cap/iab.go
index 77f2dbc..da189be 100644
--- a/cap/iab.go
+++ b/cap/iab.go
@@ -1,6 +1,12 @@
package cap
-import "strings"
+import (
+ "fmt"
+ "io/ioutil"
+ "strconv"
+ "strings"
+ "sync"
+)
// omask returns the offset and mask for a specific capability.
func omask(c Value) (uint, uint32) {
@@ -15,6 +21,7 @@ func omask(c Value) (uint, uint32) {
// Value from the process' Bounding set. This convention is used to
// support the empty IAB as being mostly harmless.
type IAB struct {
+ mu sync.RWMutex
a, i, nb []uint32
}
@@ -31,6 +38,24 @@ const (
Bound
)
+// IABDiff holds the non-error result of an (*IAB).Cf()
+// function call. It can be interpreted with the function
+// (IABDiff).Has().
+type IABDiff uint
+
+// iBits, iBits and bBits track the (semi-)independent parts of an
+// IABDiff.
+const (
+ iBits IABDiff = 1 << Inh
+ aBits IABDiff = 1 << Amb
+ bBits IABDiff = 1 << Bound
+)
+
+// Has determines if an IAB comparison differs in a specific vector.
+func (d IABDiff) Has(v Vector) bool {
+ return d&(1<<v) != 0
+}
+
// String identifies a Vector value by its conventional I A or B
// string abbreviation.
func (v Vector) String() string {
@@ -46,8 +71,8 @@ func (v Vector) String() string {
}
}
-// IABInit returns an empty IAB.
-func IABInit() *IAB {
+// NewIAB returns an empty IAB.
+func NewIAB() *IAB {
startUp.Do(multisc.cInit)
return &IAB{
i: make([]uint32, words),
@@ -56,10 +81,47 @@ func IABInit() *IAB {
}
}
+// good confirms the iab looks to be initialized.
+func (iab *IAB) good() error {
+ if iab == nil || len(iab.i) == 0 || len(iab.i) != words || len(iab.a) != words || len(iab.nb) != words {
+ return ErrBadValue
+ }
+ return nil
+}
+
+// Dup returns a duplicate copy of the IAB.
+func (iab *IAB) Dup() (*IAB, error) {
+ if err := iab.good(); err != nil {
+ return nil, err
+ }
+ v := NewIAB()
+ iab.mu.RLock()
+ defer iab.mu.RUnlock()
+ copy(v.i, iab.i)
+ copy(v.a, iab.a)
+ copy(v.nb, iab.nb)
+ return v, nil
+}
+
+// IABInit allocates a new IAB tuple.
+//
+// Deprecated: Replace with NewIAB.
+//
+// Example, replace this:
+//
+// iab := IABInit()
+//
+// with this:
+//
+// iab := NewIAB()
+func IABInit() *IAB {
+ return NewIAB()
+}
+
// IABGetProc summarizes the Inh, Amb and Bound capability vectors of
// the current process.
func IABGetProc() *IAB {
- iab := IABInit()
+ iab := NewIAB()
current := GetProc()
iab.Fill(Inh, current, Inheritable)
for c := MaxBits(); c > 0; {
@@ -78,7 +140,7 @@ func IABGetProc() *IAB {
// IABFromText parses a string representing an IAB, as generated
// by IAB.String(), to generate an IAB.
func IABFromText(text string) (*IAB, error) {
- iab := IABInit()
+ iab := NewIAB()
if len(text) == 0 {
return iab, nil
}
@@ -119,7 +181,12 @@ func IABFromText(text string) (*IAB, error) {
// String serializes an IAB to a string format.
func (iab *IAB) String() string {
+ if err := iab.good(); err != nil {
+ return "<invalid>"
+ }
var vs []string
+ iab.mu.RLock()
+ defer iab.mu.RUnlock()
for c := Value(0); c < Value(maxValues); c++ {
offset, mask := omask(c)
i := (iab.i[offset] & mask) != 0
@@ -141,6 +208,8 @@ func (iab *IAB) String() string {
return strings.Join(vs, ",")
}
+// iabSetProc uses a syscaller to apply an IAB tuple to the process.
+// The iab is known to be locked by the caller.
func (sc *syscaller) iabSetProc(iab *IAB) (err error) {
temp := GetProc()
var raising uint32
@@ -190,20 +259,30 @@ func (sc *syscaller) iabSetProc(iab *IAB) (err error) {
// SetProc attempts to change the Inheritable, Ambient and Bounding
// 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
+// other bits, so this function carefully performs the combined
// operation in the most flexible manner.
func (iab *IAB) SetProc() error {
+ if err := iab.good(); err != nil {
+ return err
+ }
state, sc := scwStateSC()
defer scwSetState(launchBlocked, state, -1)
+ iab.mu.RLock()
+ defer iab.mu.RUnlock()
return sc.iabSetProc(iab)
}
// GetVector returns the raised state of the specific capability bit
// of the indicated vector.
func (iab *IAB) GetVector(vec Vector, val Value) (bool, error) {
+ if err := iab.good(); err != nil {
+ return false, err
+ }
if val >= MaxBits() {
return false, ErrBadValue
}
+ iab.mu.RLock()
+ defer iab.mu.RUnlock()
offset, mask := omask(val)
switch vec {
case Inh:
@@ -225,6 +304,11 @@ func (iab *IAB) GetVector(vec Vector, val Value) (bool, error) {
// equivalent to lowering the Bounding vector of the process (when
// successfully applied with (*IAB).SetProc()).
func (iab *IAB) SetVector(vec Vector, raised bool, vals ...Value) error {
+ if err := iab.good(); err != nil {
+ return err
+ }
+ iab.mu.Lock()
+ defer iab.mu.Unlock()
for _, val := range vals {
if val >= Value(maxValues) {
return ErrBadValue
@@ -265,18 +349,25 @@ func (iab *IAB) SetVector(vec Vector, raised bool, vals ...Value) error {
// the bits are inverted from what you might expect - that is lowered
// bits from the Set will be raised in the Bound vector.
func (iab *IAB) Fill(vec Vector, c *Set, flag Flag) error {
- if len(c.flat) != 0 || flag > Inheritable {
- return ErrBadSet
+ if err := iab.good(); err != nil {
+ return err
+ }
+ // work with a copy to avoid potential deadlock.
+ s, err := c.Dup()
+ if err != nil {
+ return err
}
+ iab.mu.Lock()
+ defer iab.mu.Unlock()
for i := 0; i < words; i++ {
- flat := c.flat[i][flag]
+ flat := s.flat[i][flag]
switch vec {
case Inh:
iab.i[i] = flat
- iab.a[i] &= ^flat
+ iab.a[i] &= flat
case Amb:
iab.a[i] = flat
- iab.i[i] |= ^flat
+ iab.i[i] |= flat
case Bound:
iab.nb[i] = ^flat
default:
@@ -285,3 +376,108 @@ func (iab *IAB) Fill(vec Vector, c *Set, flag Flag) error {
}
return nil
}
+
+// Cf compares two IAB values. Its return value is 0 if the compared
+// tuples are considered identical. The macroscopic differences can be
+// investigated with (IABDiff).Has().
+func (iab *IAB) Cf(alt *IAB) (IABDiff, error) {
+ if err := iab.good(); err != nil {
+ return 0, err
+ }
+ if iab == alt {
+ return 0, nil
+ }
+ // Avoid holding two locks at once.
+ ref, err := alt.Dup()
+ if err != nil {
+ return 0, err
+ }
+ iab.mu.RLock()
+ defer iab.mu.RUnlock()
+
+ var cf IABDiff
+ for i := 0; i < words; i++ {
+ if iab.i[i] != ref.i[i] {
+ cf |= iBits
+ }
+ if iab.a[i] != ref.a[i] {
+ cf |= aBits
+ }
+ if iab.nb[i] != ref.nb[i] {
+ cf |= bBits
+ }
+ }
+ return cf, nil
+}
+
+// parseHex converts the /proc/*/status string into an array of
+// uint32s suitable for storage in an IAB structure.
+func parseHex(hex string, invert bool) []uint32 {
+ if len(hex) != 8*words {
+ // Invalid string
+ return nil
+ }
+ var result []uint32
+ for i := 0; i < words; i++ {
+ upper := 8 * (words - i)
+ raw, err := strconv.ParseUint(hex[upper-8:upper], 16, 32)
+ if err != nil {
+ return nil
+ }
+ if invert {
+ raw = ^raw
+ }
+ bits := allMask(uint(i)) & uint32(raw)
+ result = append(result, bits)
+ }
+ return result
+}
+
+var procRoot = "/proc"
+
+// ProcRoot sets the local mount point for the Linux /proc filesystem.
+// It defaults to "/proc", but might be mounted elsewhere on any given
+// system. The function returns the previous value of the local mount
+// point. If the user attempts to set it to "", the value is left
+// unchanged.
+func ProcRoot(path string) string {
+ was := procRoot
+ if path != "" {
+ procRoot = path
+ }
+ return was
+}
+
+// IABGetPID returns the IAB tuple of a specified process. The kernel
+// ABI does not support this query via system calls, so the function
+// works by parsing the /proc/<pid>/status file content.
+func IABGetPID(pid int) (*IAB, error) {
+ tf := fmt.Sprintf("%s/%d/status", procRoot, pid)
+ d, err := ioutil.ReadFile(tf)
+ if err != nil {
+ return nil, err
+ }
+ iab := &IAB{}
+ for _, line := range strings.Split(string(d), "\n") {
+ if !strings.HasPrefix(line, "Cap") {
+ continue
+ }
+ flavor := line[3:]
+ if strings.HasPrefix(flavor, "Inh:\t") {
+ iab.i = parseHex(line[8:], false)
+ continue
+ }
+ if strings.HasPrefix(flavor, "Bnd:\t") {
+ iab.nb = parseHex(line[8:], true)
+ continue
+ }
+ if strings.HasPrefix(flavor, "Amb:\t") {
+ iab.a = parseHex(line[8:], false)
+ continue
+ }
+ }
+ if len(iab.i) != words || len(iab.a) != words || len(iab.nb) != words {
+ return nil, ErrBadValue
+ }
+ return iab, nil
+}
diff --git a/cap/launch.go b/cap/launch.go
index 6145f3e..de7fd90 100644
--- a/cap/launch.go
+++ b/cap/launch.go
@@ -4,6 +4,7 @@ import (
"errors"
"os"
"runtime"
+ "sync"
"syscall"
"unsafe"
)
@@ -15,6 +16,8 @@ import (
// Note, go1.10 is the earliest version of the Go toolchain that can
// support this abstraction.
type Launcher struct {
+ mu sync.RWMutex
+
// Note, path and args must be set, or callbackFn. They cannot
// both be empty. In such cases .Launch() will error out.
path string
@@ -108,11 +111,21 @@ func FuncLauncher(fn func(interface{}) error) *Launcher {
// 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) {
+ if attr == nil {
+ return
+ }
+ attr.mu.Lock()
+ defer attr.mu.Unlock()
attr.callbackFn = fn
}
// SetUID specifies the UID to be used by the launched command.
func (attr *Launcher) SetUID(uid int) {
+ if attr == nil {
+ return
+ }
+ attr.mu.Lock()
+ defer attr.mu.Unlock()
attr.changeUIDs = true
attr.uid = uid
}
@@ -120,6 +133,11 @@ func (attr *Launcher) SetUID(uid int) {
// SetGroups specifies the GID and supplementary groups for the
// launched command.
func (attr *Launcher) SetGroups(gid int, groups []int) {
+ if attr == nil {
+ return
+ }
+ attr.mu.Lock()
+ defer attr.mu.Unlock()
attr.changeGIDs = true
attr.gid = gid
attr.groups = groups
@@ -127,25 +145,46 @@ func (attr *Launcher) SetGroups(gid int, groups []int) {
// SetMode specifies the libcap Mode to be used by the launched command.
func (attr *Launcher) SetMode(mode Mode) {
+ if attr == nil {
+ return
+ }
+ attr.mu.Lock()
+ defer attr.mu.Unlock()
attr.changeMode = true
attr.mode = mode
}
-// SetIAB specifies the AIB capability vectors to be inherited by the
+// SetIAB specifies the IAB capability vectors to be inherited by the
// launched command. A nil value means the prevailing vectors of the
-// parent will be inherited.
+// parent will be inherited. Note, a duplicate of the provided IAB
+// tuple is actually stored, so concurrent modification of the iab
+// value does not affect the launcher.
func (attr *Launcher) SetIAB(iab *IAB) {
- attr.iab = iab
+ if attr == nil {
+ return
+ }
+ attr.mu.Lock()
+ defer attr.mu.Unlock()
+ attr.iab, _ = iab.Dup()
}
// SetChroot specifies the chroot value to be used by the launched
// command. An empty value means no-change from the prevailing value.
func (attr *Launcher) SetChroot(root string) {
+ if attr == nil {
+ return
+ }
+ attr.mu.Lock()
+ defer attr.mu.Unlock()
attr.chroot = root
}
// lResult is used to get the result from the doomed launcher thread.
type lResult struct {
+ // tgid holds the thread group id, which is an alias for the
+ // shared process id of the parent program.
+ tgid int
+
// tid holds the tid of the locked launching thread which dies
// as the launch completes.
tid int
@@ -197,13 +236,15 @@ func launch(result chan<- lResult, attr *Launcher, data interface{}, quit chan<-
defer close(quit)
}
- pid := syscall.Getpid()
+ // Thread group ID is the process ID.
+ tgid := syscall.Getpid()
+
// 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 {
+ if tid == tgid {
// 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
@@ -222,6 +263,12 @@ func launch(result chan<- lResult, attr *Launcher, data interface{}, quit chan<-
return
}
+ // Provide a way to serialize the caller on the thread
+ // completing. This should be done by the one locked tid that
+ // does the ForkExec(). All the other threads have a different
+ // security context.
+ defer close(result)
+
// By never releasing the LockOSThread here, we guarantee that
// the runtime will terminate the current OS thread once this
// function returns.
@@ -231,10 +278,6 @@ func launch(result chan<- lResult, attr *Launcher, data interface{}, quit chan<-
// the callbackFn or something else hangs up.
singlesc.prctlrcall(prSetName, uintptr(unsafe.Pointer(&lName[0])), 0)
- // Provide a way to serialize the caller on the thread
- // completing.
- defer close(result)
-
var pa *syscall.ProcAttr
var err error
var needChroot bool
@@ -254,12 +297,12 @@ func launch(result chan<- lResult, attr *Launcher, data interface{}, quit chan<-
}
}
+ var pid int
if attr.callbackFn != nil {
if err = attr.callbackFn(pa, data); err != nil {
goto abort
}
if attr.path == "" {
- pid = 0
goto abort
}
}
@@ -283,6 +326,8 @@ func launch(result chan<- lResult, attr *Launcher, data interface{}, quit chan<-
}
}
if attr.iab != nil {
+ // Note, since .iab is a private copy we don't need to
+ // lock it around this call.
if err = singlesc.iabSetProc(attr.iab); err != nil {
goto abort
}
@@ -304,16 +349,30 @@ abort:
pid = -1
}
result <- lResult{
- tid: tid,
- pid: pid,
- err: err,
+ tgid: tgid,
+ tid: tid,
+ pid: pid,
+ err: err,
}
}
+// pollForThreadExit waits for a thread to terminate. Only after the
+// thread has safely exited is it safe to resume POSIX semantics
+// security state mirroring for the rest of the process threads.
+func (v lResult) pollForThreadExit() {
+ if v.tid == -1 {
+ return
+ }
+ for syscall.Tgkill(v.tgid, v.tid, 0) == nil {
+ runtime.Gosched()
+ }
+ scwSetState(launchActive, launchIdle, v.tid)
+}
+
// 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.
+// return details from the callback function's 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
@@ -325,15 +384,15 @@ abort:
// callback return value.
//
// Note, while the disposable security state thread makes some
-// oprerations seem more isolated - they are *not securely
+// operations 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
+// 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
@@ -343,24 +402,22 @@ func (attr *Launcher) Launch(data interface{}) (int, error) {
if !LaunchSupported {
return -1, ErrNoLaunch
}
+ if attr == nil {
+ return -1, ErrLaunchFailed
+ }
+ attr.mu.RLock()
+ defer attr.mu.RUnlock()
if attr.callbackFn == nil && (attr.path == "" || len(attr.args) == 0) {
return -1, ErrLaunchFailed
}
result := make(chan lResult)
go launch(result, attr, data, nil)
- for {
- select {
- case v, ok := <-result:
- if !ok {
- return -1, ErrLaunchFailed
- }
- if v.tid != -1 {
- defer scwSetState(launchActive, launchIdle, v.tid)
- }
- return v.pid, v.err
- default:
- runtime.Gosched()
- }
+ v, ok := <-result
+ if !ok {
+ return -1, ErrLaunchFailed
}
+ <-result // blocks until the launch() goroutine exits
+ v.pollForThreadExit()
+ return v.pid, v.err
}
diff --git a/cap/names.go b/cap/names.go
index 8ee96d1..2655380 100644
--- a/cap/names.go
+++ b/cap/names.go
@@ -2,8 +2,8 @@ package cap
/* ** DO NOT EDIT THIS FILE. IT WAS AUTO-GENERATED BY LIBCAP'S GO BUILDER (mknames.go) ** */
-// NamedCount holds the number of capability values with official
-// names known at the time this libcap/cap version, was released. The
+// NamedCount holds the number of capability values, with official
+// names, known at the time this libcap/cap version was released. The
// "../libcap/cap" package is fully able to manipulate higher numbered
// capability values by numerical value. However, if you find
// cap.NamedCount < cap.MaxBits(), it is probably time to upgrade this
@@ -42,6 +42,10 @@ const (
// 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.
+ //
+ // This capability permits the deletion of a file owned
+ // by another UID in a directory protected by the sticky
+ // (t) bit.
FOWNER
// FSETID allows a process to set the S_ISUID and S_ISUID bits of
@@ -70,24 +74,29 @@ const (
SETUID
// SETPCAP 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.
+ // capabilities.
+ //
+ // Linux supports the POSIX.1e Inheritable set, the POXIX.1e (X
+ // vector) known in Linux as the Bounding vector, as well as
+ // the Linux extension Ambient vector.
+ //
+ // This capability permits dropping bits from the Bounding
+ // vector (ie. raising B bits in the libcap IAB
+ // representation). 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, Effective bits beyond those
+ // already present in the process' permitted set, or
+ // Inheritable bits beyond those present in the Bounding
+ // vector.
//
- // [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.]
+ // [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.]
SETPCAP
// LINUX_IMMUTABLE allows a process to modify the S_IMMUTABLE and
diff --git a/cap/text.go b/cap/text.go
index cf11a2d..42cb7fc 100644
--- a/cap/text.go
+++ b/cap/text.go
@@ -48,6 +48,7 @@ const (
var combos = []string{"", "e", "p", "ep", "i", "ei", "ip", "eip"}
// histo generates a histogram of flag state combinations.
+// Note: c is locked by or private to the caller.
func (c *Set) histo(bins []int, patterns []uint, from, limit Value) uint {
for v := from; v < limit; v++ {
b := uint(v & 31)
@@ -84,7 +85,7 @@ func (c *Set) histo(bins []int, patterns []uint, from, limit Value) uint {
// any given release. Further, it will always be an inverse of
// cap.FromText().
func (c *Set) String() string {
- if c == nil || len(c.flat) == 0 {
+ if err := c.good(); err != nil {
return "<invalid>"
}
bins := make([]int, 8)
diff --git a/contrib/Makefile b/contrib/Makefile
index 4749630..a4b5008 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -1,3 +1,3 @@
.PHONY: all clean
all clean:
- for x in bug* ; do make -C $$x $@ || exit 1 ; done
+ for x in bug* ; do $(MAKE) -C $$x $@ || exit 1 ; done
diff --git a/contrib/bug216610/.gitignore b/contrib/bug216610/.gitignore
new file mode 100644
index 0000000..1478d58
--- /dev/null
+++ b/contrib/bug216610/.gitignore
@@ -0,0 +1,3 @@
+*~
+arms
+Dockerfile
diff --git a/contrib/bug216610/Dockerfile b/contrib/bug216610/Dockerfile
new file mode 100644
index 0000000..5502b71
--- /dev/null
+++ b/contrib/bug216610/Dockerfile
@@ -0,0 +1,13 @@
+FROM debian:latest
+
+# A directory to share files via.
+RUN mkdir /shared
+
+RUN apt-get update
+RUN apt-get install -y gcc-arm-linux-gnueabi binutils-arm-linux-gnueabi
+RUN apt-get install -y gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu
+
+# create a builder user
+RUN echo "builder:x:1000:1000:,,,:/home/builder:/bin/bash" >> /etc/passwd
+RUN echo "builder:*:19289:0:99999:7:::" >> /etc/shadow
+RUN mkdir -p /home/builder && chown builder.bin /home/builder
diff --git a/contrib/bug216610/Makefile b/contrib/bug216610/Makefile
new file mode 100644
index 0000000..ce96fb3
--- /dev/null
+++ b/contrib/bug216610/Makefile
@@ -0,0 +1,30 @@
+topdir=$(shell pwd)/../..
+include ../../Make.Rules
+
+GOTARGET=$(shell eval $$(go env) ; echo $${GOHOSTOS}_$${GOARCH})
+
+all: go/fib
+
+go/fib: go/main.go go/fibber/fib.go go/fibber/linkage.go go/fibber/fibs_$(GOTARGET).s go/fibber/fib_$(GOTARGET).syso
+ cd go && CGO_ENABLED=0 go build
+
+# Build the host native version.
+go/fibber/fib_$(GOTARGET).syso go/fibber/linkage.go: c/fib.c ./c/gcc.sh ./package_fns.sh
+ GCC=gcc ./c/gcc.sh -O3 c/fib.c -c -o go/fibber/fib_$(GOTARGET).syso
+ ./package_fns.sh fibber go/fibber/fib_$(GOTARGET).syso > go/fibber/linkage.go
+
+Dockerfile: Makefile ./mkdocker.sh
+ ./mkdocker.sh > $@
+
+# Use this build target (make arms) to extend support to include arm
+# and arm64 GOARCH values.
+arms: Dockerfile Makefile ./c/gcc.sh ./c/build.sh ./c/fib.c
+ docker run --rm -v $$PWD/c:/shared:z -h debian -u $$(id -u) -it expt shared/build.sh
+ mv c/*.syso go/fibber/
+ touch arms
+
+clean:
+ rm -f *~ arms
+ rm -f c/*.o c/*~
+ rm -f go/fib go/*~
+ rm -f go/fibber/*.syso go/fibber/*~ go/fibber/linkage.go
diff --git a/contrib/bug216610/README.md b/contrib/bug216610/README.md
new file mode 100644
index 0000000..4425715
--- /dev/null
+++ b/contrib/bug216610/README.md
@@ -0,0 +1,139 @@
+# Linking psx and C code without cgo
+
+## Overview
+
+In some embedded situations, there is a desire to compile Go binaries
+to include some C code, but not `libc` etc. For a long time, I had
+assumed this was not possible, since using `cgo` *requires* `libc` and
+`libpthread` linkage.
+
+This _embedded compilation_ need was referenced in a [bug
+filed](https://bugzilla.kernel.org/show_bug.cgi?id=216610) against the
+[`"psx"`](https://pkg.go.dev/kernel.org/pub/linux/libs/security/libcap/psx)
+package. The bug-filer was seeking an alternative to `CGO_ENABLED=1`
+compilation _requiring_ the `cgo` variant of `psx` build. However, the
+go `"runtime"` package will always
+[`panic()`](https://cs.opensource.google/go/go/+/refs/tags/go1.19.2:src/runtime/os_linux.go;l=717-720)
+if you try this because it needs `libpthread` and `[g]libc` to work.
+
+In researching that bug report, however, I have learned there is a
+trick to combining a non-CGO built binary with compiled C code. I
+learned about it from a brief reference in the [Go Programming
+Language
+Wiki](https://zchee.github.io/golang-wiki/GcToolchainTricks/).
+
+This present directory evolved from my attempt to understand and
+hopefully resolve what was going on as reported in that bug into an
+example of this _trick_. I was unable to resolve the problem as
+reported because of the aformentioned `panic()` in the Go
+runtime. However, I was able to demonstrate embedding C code in a Go
+binary _without_ use of cgo. In such a binary, the Go-native version
+of `"psx"` is thus achievable. This is what the example in this
+present directory demonstrates.
+
+*Caveat Emptor*: this example is very fragile. The Go team only
+supports `cgo` linking against C. That being said, I'd certainly like
+to receive bug fixes, etc for this directory if you find you need to
+evolve it to make it work for your use case.
+
+## Content
+
+In this example we have:
+
+- Some C code for the functions `fib_init()` and `fib_next()` that
+combine to implement a _compute engine_ to determine [Fibonacci
+Numbers](https://en.wikipedia.org/wiki/Fibonacci_number). The source
+for this is in the sub directory `c/fib.c`.
+
+- Some Go code, in the directory `go/fibber` that uses this C compiled
+compute kernel.
+
+- `c/gcc.sh` which is a wrapper for `gcc` that adjusts the compilation
+to be digestible by Go's (internal) linker (the one that gets invoked
+when compiling `CGO_ENABLED=0`. Using `gcc` directly instead of this
+wrapper generates an incomplete binary - which miscomputes the
+expected answers. See the discussion below for what seems to be going
+on.
+
+- A top level `Makefile` to build it all.
+
+## Building and running the built binary
+
+Set things up with:
+```
+$ git clone git://git.kernel.org/pub/scm/libs/libcap/libcap.git
+$ cd libcap
+$ make all
+$ cd contrib/bug216610
+$ make clean all
+```
+When you run `./go/fib` it should generate the following output:
+```
+$ ./go/fib
+psx syscall result: PID=<nnnnn>
+fib: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...
+$
+```
+Where `<nnnnn>` is the PID of the program at runtime and will be
+different each time the program is invoked.
+
+## Discussion
+
+The Fibonacci detail of what is going on is mostly uninteresting. The
+reason for developing this example was to explore the build issues in
+the reported [Bug
+216610](https://bugzilla.kernel.org/show_bug.cgi?id=216610). Ultimately,
+this example offers an alternative path to building a `nocgo` program
+that links to compute kernel of C code.
+
+The reason we have added the `c/gcc.sh` wrapper for `gcc` is that
+we've found the Go linker has a hard time digesting the
+cross-sectional `%rip` based data addressing that various optimization
+modes of gcc like to use. Specifically, in the x86_64/amd64
+architecture, if a `R_X86_64_PC32` relocation entry made in a `.text`
+section refers to an `.rodata.cst8` section in a generated `.syso`
+file, the Go linker seems to [replace this reference with a `0` offset
+to
+`(%rip)`](https://github.com/golang/go/issues/24321#issuecomment-1296084103). What
+our wrapper script does is rewrite the generated assembly to store
+these data references to the `.text` section. The Go linker has no
+problem with this _same section_ relative addressing and is able to
+link the resulting objects without problems.
+
+If you want to cross compile, we have support for 32-bit arm
+compilation: what is needed for the Raspberry PI. To get this support,
+try:
+```
+$ make clean all arms
+$ cd go
+$ GOARCH=arm CGO_ENABLED=0 go build
+```
+The generated `fib` binary runs on a 32-bit Raspberry Pi.
+
+## Future thoughts
+
+At present, this example only works on Linux with `x86_64` and `arm`
+build architectures. (In go-speak that is `linux_amd64` and
+`linux_arm`). This is because I have only provided some bridging
+assembly for Go to C calling conventions for those architecture
+targets: `./go/fibber/fibs_linux_amd64.s` and
+`./go/fibber/fibs_linux_arm.s`. The non-native, `make arms`, cross
+compilation requires the `docker` command to be available.
+
+I intend to implement an `arm64` build, when I have a system on which
+to test it.
+
+**Note** The Fedora system on which I've been developing this has some
+ SELINUX impediment to naively using the `docker -v ...` bind mount
+ option. I need the `:z` suffix for bind mounting. I don't know how
+ common an issue this is. On Fedora, building the arm variants of the
+ .syso file can be performed as follows:
+```
+$ docker run --rm -v $PWD/c:/shared:z -h debian -u $(id -u) -it expt shared/build.sh
+```
+
+## Reporting bugs
+
+Please report issues or offer improvements to this example via the
+[Fully Capable `libcap`](https://sites.google.com/site/fullycapable/)
+website.
diff --git a/contrib/bug216610/c/build.sh b/contrib/bug216610/c/build.sh
new file mode 100755
index 0000000..7458fb1
--- /dev/null
+++ b/contrib/bug216610/c/build.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+#
+# Builds the following .syso files to the directory containing this script:
+#
+# fib_linux_arm.syso
+# fib_linux_arm64.syso
+
+cd ${0%/*}
+GCC=arm-linux-gnueabi-gcc ./gcc.sh -O3 fib.c -c -o fib_linux_arm.syso
+GCC=aarch64-linux-gnu-gcc ./gcc.sh -O3 fib.c -c -o fib_linux_arm64.syso
diff --git a/contrib/bug216610/c/fib.c b/contrib/bug216610/c/fib.c
new file mode 100644
index 0000000..bd665c7
--- /dev/null
+++ b/contrib/bug216610/c/fib.c
@@ -0,0 +1,20 @@
+#include <inttypes.h>
+
+struct state {
+ uint32_t b, a;
+};
+
+void fib_init(struct state *s);
+void fib_init(struct state *s)
+{
+ s->a = 0;
+ s->b = 1;
+}
+
+void fib_next(struct state *s);
+void fib_next(struct state *s)
+{
+ uint32_t next = s->a + s->b;
+ s->a = s->b;
+ s->b = next;
+}
diff --git a/contrib/bug216610/c/gcc.sh b/contrib/bug216610/c/gcc.sh
new file mode 100755
index 0000000..33655d6
--- /dev/null
+++ b/contrib/bug216610/c/gcc.sh
@@ -0,0 +1,61 @@
+#!/bin/bash
+#
+# The Go linker does not seem to know what to do with relative
+# addressing of rodata.* offset from %rip. GCC likes to use this
+# addressing mode on this architecture, so we quickly run into
+# mis-computation when the relative addressing used in a .syso file of
+# symbol located data is resolved to completely the wrong place by the
+# Go (internal) linker.
+#
+# As a workaround for this, we can modify the assembly source code
+# generated by GCC to not point at problematic '.rodata.*' sections,
+# and place this data in the good old '.text' section where Go's
+# linker can make sense of it.
+#
+# This script exists to generate a '.syso' file from some '*.c' files.
+# It works by recognizing the '*.c' command line arguments and
+# converting them into fixed-up '*.s' files. It then performs the
+# compilation for the collection of the '*.s' files. Upon success, it
+# purges the intermediate '*.s' files.
+#
+# The fragile aspect of this present script is which compiler
+# arguments should be used for the compilation from '.c' -> '.s'
+# files. What we do is accumulate arguments until we encounter our
+# first '*.c' file and use those to perform the '.c' -> '.s'
+# compilation. We build up a complete command line for gcc
+# substituting '.s' files for '.c' files in the original command
+# line. Then with the new command line assembled we invoke gcc with
+# those. If that works, we remove all of the intermediate '.s' files.
+
+GCC="${GCC:=gcc}"
+setup=0
+args=()
+final=()
+ses=()
+
+for arg in "$@"; do
+ if [[ "${arg##*.}" = "c" ]]; then
+ setup=1
+ s="${arg%.*}.s"
+ "${GCC}" "${args[@]}" -S -o "${s}" "${arg}"
+ sed -i -e 's/.*\.rodata\..*/\t.text/' "${s}"
+ final+=("${s}")
+ ses+=("${s}")
+ else
+ if [[ $setup -eq 0 ]]; then
+ args+=("${arg}")
+ fi
+ final+=("${arg}")
+ fi
+done
+
+#echo final: "${final[@]}"
+#echo args: "${args[@]}"
+#echo ses: "${ses[@]}"
+
+"${GCC}" "${final[@]}"
+if [[ $? -ne 0 ]]; then
+ echo "failed to compile"
+ exit 1
+fi
+rm -f "${ses[@]}"
diff --git a/contrib/bug216610/go/.gitignore b/contrib/bug216610/go/.gitignore
new file mode 100644
index 0000000..ae14305
--- /dev/null
+++ b/contrib/bug216610/go/.gitignore
@@ -0,0 +1,5 @@
+fib
+*.syso
+main
+go.sum
+linkage.go
diff --git a/contrib/bug216610/go/fibber/fib.go b/contrib/bug216610/go/fibber/fib.go
new file mode 100644
index 0000000..49757cd
--- /dev/null
+++ b/contrib/bug216610/go/fibber/fib.go
@@ -0,0 +1,32 @@
+// Package fibber implements a Fibonacci sequence generator using a C
+// coded compute kernel (a .syso file).
+package fibber
+
+import (
+ "unsafe"
+)
+
+// State is the native Go form of the C.state structure.
+type State struct {
+ B, A uint32
+}
+
+// cPtr converts State into a C pointer suitable as an argument for
+// sysoCaller.
+func (s *State) cPtr() unsafe.Pointer {
+ return unsafe.Pointer(&s.B)
+}
+
+// NewState initializes a Fibonacci Number sequence generator. Upon
+// return s.A=0 and s.B=1 are the first two numbers in the sequence.
+func NewState() *State {
+ s := &State{}
+ syso__fib_init.call(s.cPtr())
+ return s
+}
+
+// Next advances the state to the next number in the sequence. Upon
+// return, s.B is the most recently calculated value.
+func (s *State) Next() {
+ syso__fib_next.call(s.cPtr())
+}
diff --git a/contrib/bug216610/go/fibber/fibs_linux_amd64.s b/contrib/bug216610/go/fibber/fibs_linux_amd64.s
new file mode 100644
index 0000000..5992d09
--- /dev/null
+++ b/contrib/bug216610/go/fibber/fibs_linux_amd64.s
@@ -0,0 +1,21 @@
+// To transition from a Go call to a C function call, we are skating
+// on really thin ice... Ceveat Emptor!
+//
+// Ref:
+// https://gitlab.com/x86-psABIs/x86-64-ABI/-/wikis/home
+//
+// This is not strictly needed, but it makes gdb debugging less
+// confusing because spacer ends up being an alias for the TEXT
+// section start.
+TEXT ·spacer(SB),$0
+ RET
+
+#define RINDEX(n) (8*n)
+
+// Header to this function wrapper is the last time we can voluntarily
+// yield to some other goroutine.
+TEXT ·syso(SB),$0-16
+ MOVQ cFn+RINDEX(0)(FP), SI
+ MOVQ state+RINDEX(1)(FP), DI
+ CALL *SI
+ RET
diff --git a/contrib/bug216610/go/fibber/fibs_linux_arm.s b/contrib/bug216610/go/fibber/fibs_linux_arm.s
new file mode 100644
index 0000000..39640a5
--- /dev/null
+++ b/contrib/bug216610/go/fibber/fibs_linux_arm.s
@@ -0,0 +1,23 @@
+// To transition from a Go call to a C function call, we are skating
+// on really thin ice... Ceveat Emptor!
+//
+// Ref:
+// https://stackoverflow.com/questions/261419/what-registers-to-save-in-the-arm-c-calling-convention
+//
+// This is not strictly needed, but it makes gdb debugging less
+// confusing because spacer ends up being an alias for the TEXT
+// section start.
+TEXT ·spacer(SB),$0
+ RET
+
+#define FINDEX(n) (8*n)
+
+// Header to this function wrapper is the last time we can voluntarily
+// yield to some other goroutine.
+//
+// Conventions: PC == R15, SP == R13, LR == R14, IP (scratch) = R12
+TEXT ·syso(SB),$0-8
+ MOVW cFn+0(FP), R14
+ MOVW state+4(FP), R0
+ BL (R14)
+ RET
diff --git a/contrib/bug216610/go/go.mod b/contrib/bug216610/go/go.mod
new file mode 100644
index 0000000..8531994
--- /dev/null
+++ b/contrib/bug216610/go/go.mod
@@ -0,0 +1,5 @@
+module fib
+
+go 1.18
+
+require kernel.org/pub/linux/libs/security/libcap/psx v1.2.69
diff --git a/contrib/bug216610/go/main.go b/contrib/bug216610/go/main.go
new file mode 100644
index 0000000..65121f6
--- /dev/null
+++ b/contrib/bug216610/go/main.go
@@ -0,0 +1,29 @@
+// Program fib uses the psx package once, and then prints the first
+// ten Fibonacci numbers.
+package main
+
+import (
+ "fmt"
+ "log"
+ "syscall"
+
+ "fib/fibber"
+
+ "kernel.org/pub/linux/libs/security/libcap/psx"
+)
+
+func main() {
+ pid, _, err := psx.Syscall3(syscall.SYS_GETPID, 0, 0, 0)
+ if err != 0 {
+ log.Fatalf("failed to get PID via psx: %v", err)
+ }
+ fmt.Print("psx syscall result: PID=")
+ fmt.Println(pid)
+ s := fibber.NewState()
+ fmt.Print("fib: ", s.A, ", ", s.B)
+ for i := 0; i < 8; i++ {
+ s.Next()
+ fmt.Print(", ", s.B)
+ }
+ fmt.Println(", ...")
+}
diff --git a/contrib/bug216610/mkdocker.sh b/contrib/bug216610/mkdocker.sh
new file mode 100755
index 0000000..860c198
--- /dev/null
+++ b/contrib/bug216610/mkdocker.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# This script generates a Dockerfile to be used for cross-compilation
+cat <<EOF
+FROM debian:latest
+
+# A directory to share files via.
+RUN mkdir /shared
+
+RUN apt-get update
+RUN apt-get install -y gcc-arm-linux-gnueabi binutils-arm-linux-gnueabi
+RUN apt-get install -y gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu
+
+# create a builder user
+RUN echo "builder:x:$(id -u):$(id -g):,,,:/home/builder:/bin/bash" >> /etc/passwd
+RUN echo "builder:*:19289:0:99999:7:::" >> /etc/shadow
+RUN mkdir -p /home/builder && chown builder.bin /home/builder
+EOF
diff --git a/contrib/bug216610/package_fns.sh b/contrib/bug216610/package_fns.sh
new file mode 100755
index 0000000..0f4b91c
--- /dev/null
+++ b/contrib/bug216610/package_fns.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+#
+# Generate some Go code to make calling into the C code of the .syso
+# file easier.
+
+package="${1}"
+syso="${2}"
+
+if [[ -z "${syso}" ]]; then
+ echo "usage: $0 <package> <.....syso>" >&2
+ exit 1
+fi
+
+if [[ "${syso%.syso}" == "${syso}" ]]; then
+ echo "2nd argument should be a .syso file" >&2
+ exit 1
+fi
+
+cat<<EOF
+package ${package}
+
+import (
+ "unsafe"
+)
+
+// syso is how we call, indirectly, into the C-code.
+func syso(cFn, state unsafe.Pointer)
+
+type sysoCaller struct {
+ ptr unsafe.Pointer
+}
+
+// call calls the syso linked C-function, $sym().
+func (s *sysoCaller) call(data unsafe.Pointer) {
+ syso(s.ptr, data)
+}
+EOF
+
+for sym in $(objdump -x "${syso}" | grep -F 'g F' | awk '{print $6}'); do
+ cat<<EOF
+
+//go:linkname _${sym} ${sym}
+var _${sym} byte
+var syso__${sym} = &sysoCaller{ptr: unsafe.Pointer(&_${sym})}
+
+EOF
+done
diff --git a/contrib/bug400591/Makefile b/contrib/bug400591/Makefile
index 320610c..bb2e59d 100644
--- a/contrib/bug400591/Makefile
+++ b/contrib/bug400591/Makefile
@@ -1,8 +1,8 @@
all: bug
bug: bug.c ../../libcap Makefile
- make -C ../../libcap
- cc -g -I../../libcap/include --static -o $@ $< -L../../libcap -lcap
+ $(MAKE) -C ../../libcap
+ $(CC) $(CFLAGS) $(CPPFLAGS) -g -I../../libcap/include --static -o $@ $< -L../../libcap -lcap
./bug
clean:
diff --git a/contrib/capso/.gitignore b/contrib/capso/.gitignore
new file mode 100644
index 0000000..222d35d
--- /dev/null
+++ b/contrib/capso/.gitignore
@@ -0,0 +1,2 @@
+capso.so
+bind
diff --git a/contrib/capso/Makefile b/contrib/capso/Makefile
new file mode 100644
index 0000000..70af7f9
--- /dev/null
+++ b/contrib/capso/Makefile
@@ -0,0 +1,23 @@
+topdir=$(shell pwd)/../..
+include ../../Make.Rules
+
+# Always build sources this way:
+CFLAGS += -fPIC $(CAPSO_DEBUG)
+
+all: bind
+
+bind: bind.c capso.so
+ $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ bind.c capso.so -L../../libcap -lcap
+
+../../libcap/loader.txt:
+ $(MAKE) -C ../../libcap loader.txt
+
+capso.o: capso.c capso.h ../../libcap/execable.h ../../libcap/loader.txt
+ $(CC) $(CFLAGS) $(CPPFLAGS) -DLIBCAP_VERSION=\"libcap-$(VERSION).$(MINOR)\" -DSHARED_LOADER=\"$(shell cat ../../libcap/loader.txt)\" -c capso.c -o $@
+
+capso.so: capso.o
+ $(LD) $(LDFLAGS) -o $@ $< $(LIBCAPLIB) -ldl -Wl,-e,__so_start
+ sudo setcap cap_net_bind_service=p $@
+
+clean:
+ rm -f bind capso.o capso.so *~
diff --git a/contrib/capso/README.md b/contrib/capso/README.md
new file mode 100644
index 0000000..df2e878
--- /dev/null
+++ b/contrib/capso/README.md
@@ -0,0 +1,21 @@
+# Leveraging file capabilities on shared libraries
+
+This directory contains an example of a shared library (`capso.so`)
+that can be installed with file capabilities. When the library is
+linked against an unprivileged program, it includes internal support
+for re-invoking itself as a child subprocess to execute a privileged
+operation on bahalf of the parent.
+
+The idea for doing this was evolved from the way `pam_unix.so` is able
+to leverage a separate program, and `libcap`'s recently added support
+for supporting binary execution of all the `.so` files built by the
+package.
+
+The actual program example `./bind` leverages the
+`"cap_net_bind_service=p"` enabled `./capso.so` file to bind to the
+privileged port 80.
+
+A writeup of how to build and explore the behavior of this example is
+provided on the `libcap` distribution website:
+
+https://sites.google.com/site/fullycapable/capable-shared-objects
diff --git a/contrib/capso/bind.c b/contrib/capso/bind.c
new file mode 100644
index 0000000..609e4e4
--- /dev/null
+++ b/contrib/capso/bind.c
@@ -0,0 +1,29 @@
+/*
+ * Unprivileged program that binds to port 80. It does this by
+ * leveraging a file capable shared library.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "capso.h"
+
+int main(int argc, char **argv) {
+ int f = bind80("127.0.0.1");
+ if (f < 0) {
+ perror("unable to bind to port 80");
+ exit(1);
+ }
+ if (listen(f, 10) == -1) {
+ perror("unable to listen to port 80");
+ exit(1);
+ }
+ printf("Webserver code to use filedes = %d goes here.\n"
+ "(Sleeping for 60s... Try 'netstat -tlnp|grep :80')\n", f);
+ fflush(stdout);
+ sleep(60);
+ close(f);
+ printf("Done.\n");
+}
diff --git a/contrib/capso/capso.c b/contrib/capso/capso.c
new file mode 100644
index 0000000..7ca3427
--- /dev/null
+++ b/contrib/capso/capso.c
@@ -0,0 +1,368 @@
+/*
+ * Worked example for a shared object with a file capability on it
+ * leveraging itself for preprogrammed functionality.
+ *
+ * This example implements a shared library that can bind to
+ * the privileged port. ":80".
+ *
+ * The shared library needs to be installed with
+ * cap_net_bind_service=p. As a shared library, it provides the
+ * function bind80().
+ */
+
+#define _GNU_SOURCE
+
+#include <dlfcn.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/capability.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "capso.h"
+
+extern char **environ;
+
+/*
+ * fake_exploit is some dedicated code to simulate a shell escape type
+ * exploit. This is obviously not something serious to include in code
+ * that has actually been audited for security, but we use it to
+ * demonstrate an aspect of file capabilities vs. setuid root for
+ * granting privilege.
+ */
+static void fake_exploit(void) {
+#ifdef ALLOW_EXPLOIT
+ const char *exploit = getenv("TRIGGER_EXPLOIT");
+ if (exploit == NULL) {
+ return;
+ }
+
+ switch (*exploit) {
+ case '^':
+ case '%':
+ exploit++;
+ cap_value_t caps = CAP_NET_BIND_SERVICE;
+ cap_t c = cap_get_proc();
+ cap_set_flag(c, CAP_INHERITABLE, 1, &caps, CAP_SET);
+ if (cap_set_proc(c)) {
+ perror("Failed to raise inheritable capability");
+ exit(1);
+ }
+ if (*(exploit-1) == '%') {
+ break;
+ }
+ cap_free(c);
+ if (cap_set_ambient(caps, CAP_SET) != 0) {
+ perror("Unable to raise ambient capability");
+ exit(1);
+ }
+ break;
+ }
+
+ char *ts = strdup(exploit);
+ if (ts == NULL) {
+ perror("Failed to duplicate exploit string");
+ exit(1);
+ }
+
+ int i, j, n = 1;
+ for (i = 0; ts[i]; i++) {
+ switch (ts[i]) {
+ case ' ':
+ case '\t':
+ n++;
+ ts[i] = '\0';
+ }
+ }
+ char **argv = calloc(n, sizeof(char *));
+ for (i = 0, j = 0; j < n; j++) {
+ char *s = ts+i;
+ argv[j] = s;
+ i += 1 + strlen(s);
+ printf("execv argv[%d] = \"%s\"\n", j, s);
+ }
+
+ execv(argv[0], argv);
+ perror("Execv failed");
+ exit(1);
+#endif /* def ALLOW_EXPLOIT */
+}
+
+/*
+ * where_am_i determines the full path for the shared libary that
+ * contains this function. It allocates the path in strdup()d memory
+ * that should be free()d by the caller. If it can't find itself, it
+ * returns NULL.
+ */
+static char *where_am_i(void)
+{
+ Dl_info info;
+ if (dladdr(where_am_i, &info) == 0) {
+ return NULL;
+ }
+ return strdup(info.dli_fname);
+}
+
+/*
+ * try_bind80 attempts to reuseably bind to port 80 with the given
+ * hostname. It returns a bound filedescriptor or -1 on error.
+ */
+static int try_bind80(const char *hostname)
+{
+ struct addrinfo *conf, *detail = NULL;
+ int err, ret = -1, one = 1;
+
+ conf = calloc(1, sizeof(*conf));
+ if (conf == NULL) {
+ return -1;
+ }
+
+ conf->ai_family = PF_UNSPEC;
+ conf->ai_socktype = SOCK_STREAM;
+ conf->ai_protocol = 0;
+ conf->ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
+
+ err = getaddrinfo(hostname, "80", conf, &detail);
+ if (err != 0) {
+ goto done;
+ }
+
+ ret = socket(detail->ai_family, detail->ai_socktype, detail->ai_protocol);
+ if (ret == -1) {
+ goto done;
+ }
+
+ if (setsockopt(ret, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) {
+ close(ret);
+ ret = -1;
+ goto done;
+ }
+
+ if (bind(ret, detail->ai_addr, detail->ai_addrlen)) {
+ close(ret);
+ ret = -1;
+ goto done;
+ }
+
+ done:
+ if (detail != NULL) {
+ freeaddrinfo(detail);
+ }
+ free(conf);
+
+ return ret;
+}
+
+/*
+ * set_fd3 forces file descriptor 3 to be associated with a unix
+ * socket that can be used to send a file descriptor back to the
+ * parent program.
+ */
+static int set_fd3(void *detail)
+{
+ int *sp = detail;
+
+ close(sp[0]);
+ if (dup2(sp[1], 3) != 3) {
+ return -1;
+ }
+ close(sp[1]);
+
+ return 0;
+}
+
+/*
+ * bind80 returns a socket filedescriptor that is bound to port 80 of
+ * the provided service address.
+ *
+ * Example:
+ *
+ * int fd = bind80("localhost");
+ *
+ * fd < 0 in the case of error.
+ */
+int bind80(const char *hostname)
+{
+ cap_launch_t helper;
+ pid_t child;
+ char const *args[3];
+ char *path;
+ int fd, ignored;
+ int sp[2];
+ char junk[1];
+ const int rec_buf_len = CMSG_SPACE(sizeof(int));
+ char *rec_buf[CMSG_SPACE(sizeof(int))];
+ struct iovec *iov;
+ struct msghdr *msg;
+
+ fd = try_bind80(hostname);
+ if (fd >= 0) {
+ return fd;
+ }
+
+#ifdef CAPSO_DEBUG
+ printf("application bind80(%s) attempt failed\n", hostname);
+ sleep(30);
+#endif
+
+ iov = calloc(1, sizeof(struct iovec));
+ if (iov == NULL) {
+ return -1;
+ }
+ msg = calloc(1, sizeof(struct msghdr));
+ if (msg == NULL) {
+ free(iov);
+ return -1;
+ }
+
+ /*
+ * Initial attempt didn't work, so try launching the shared
+ * library as an executable and getting it to yield a bound
+ * filedescriptor for us via a unix socket pair.
+ */
+ path = where_am_i();
+ if (path == NULL) {
+ perror("Unable to find self");
+ goto drop_alloc;
+ }
+
+ args[0] = "bind80-helper";
+ args[1] = hostname;
+ args[2] = NULL;
+
+ helper = cap_new_launcher(path, args, (void *) environ);
+ if (helper == NULL) {
+ goto drop_path;
+ }
+
+ if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sp)) {
+ goto drop_helper;
+ }
+
+ cap_launcher_callback(helper, set_fd3);
+ child = cap_launch(helper, sp);
+ close(sp[1]);
+
+ if (child <= 0) {
+ goto drop_sp;
+ }
+
+ iov[0].iov_base = junk;
+ iov[0].iov_len = 1;
+
+ msg->msg_name = NULL;
+ msg->msg_namelen = 0;
+ msg->msg_iov = iov;
+ msg->msg_iovlen = 1;
+ msg->msg_control = rec_buf;
+ msg->msg_controllen = rec_buf_len;
+
+ if (recvmsg(sp[0], msg, 0) != -1) {
+ fd = * (int *) CMSG_DATA(CMSG_FIRSTHDR(msg));
+ }
+ waitpid(child, &ignored, 0);
+
+ drop_sp:
+ close(sp[0]);
+
+ drop_helper:
+ cap_free(helper);
+
+ drop_path:
+ free(path);
+
+ drop_alloc:
+ free(msg);
+ free(iov);
+
+ return fd;
+}
+
+#include "../../libcap/execable.h"
+//#define SO_MAIN int main
+
+SO_MAIN(int argc, char **argv)
+{
+ const char *cmd = "<capso.so>";
+ const cap_value_t cap_net_bind_service = CAP_NET_BIND_SERVICE;
+ cap_t working;
+ int fd;
+ struct msghdr msg;
+ struct cmsghdr *ctrl;
+ struct iovec payload;
+ char data[CMSG_SPACE(sizeof(fd))];
+ char junk[1];
+
+#ifdef CAPSO_DEBUG
+ printf("invoking %s standalone\n", argv[0]);
+ sleep(30);
+#endif
+
+ if (argv != NULL) {
+ cmd = argv[0];
+ }
+
+ if (argc != 2 || argv[1] == NULL || !strcmp(argv[1], "--help")) {
+ fprintf(stderr, "usage: %s <hostname>\n", cmd);
+ exit(1);
+ }
+
+ working = cap_get_proc();
+ if (working == NULL) {
+ perror("Unable to read capabilities");
+ exit(1);
+ }
+
+ if (cap_set_flag(working, CAP_EFFECTIVE, 1,
+ &cap_net_bind_service, CAP_SET) != 0) {
+ perror("Unable to raise CAP_NET_BIND_SERVICE");
+ exit(1);
+ }
+
+ if (cap_set_proc(working) != 0) {
+ perror("Problem with cap_set_proc");
+ fprintf(stderr, "Try: sudo setcap cap_net_bind_service=p %s\n",
+ argv[0]);
+ exit(1);
+ }
+
+ fd = try_bind80(argv[1]);
+
+ memset(data, 0, sizeof(data));
+ memset(&payload, 0, sizeof(payload));
+
+ payload.iov_base = junk;
+ payload.iov_len = 1;
+
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = &payload;
+ msg.msg_iovlen = 1;
+ msg.msg_control = data;
+ msg.msg_controllen = sizeof(data);
+
+ ctrl = CMSG_FIRSTHDR(&msg);
+ ctrl->cmsg_level = SOL_SOCKET;
+ ctrl->cmsg_type = SCM_RIGHTS;
+ ctrl->cmsg_len = CMSG_LEN(sizeof(fd));
+
+ *((int *) CMSG_DATA(ctrl)) = fd;
+
+ if (sendmsg(3, &msg, 0) < 0) {
+ perror("Failed to write fd");
+ }
+
+ fake_exploit();
+
+#ifdef CAPSO_DEBUG
+ printf("exiting standalone %s\n", argv[0]);
+ sleep(30);
+#endif
+
+ exit(0);
+}
diff --git a/contrib/capso/capso.h b/contrib/capso/capso.h
new file mode 100644
index 0000000..ae18f3a
--- /dev/null
+++ b/contrib/capso/capso.h
@@ -0,0 +1,16 @@
+#ifndef CAPSO_H
+#define CAPSO_H
+
+/*
+ * bind80 returns a socket filedescriptor that is bound to port 80 of
+ * the provided service address.
+ *
+ * Example:
+ *
+ * int fd = bind80("localhost");
+ *
+ * fd < 0 in the case of error.
+ */
+extern int bind80(const char *hostname);
+
+#endif /* ndef CAPSO_H */
diff --git a/contrib/seccomp/go.mod b/contrib/seccomp/go.mod
index 8f72409..ecf18d9 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 v1.2.53
+require kernel.org/pub/linux/libs/security/libcap/psx v1.2.69
diff --git a/contrib/sucap/Makefile b/contrib/sucap/Makefile
index 91947af..df61ed5 100644
--- a/contrib/sucap/Makefile
+++ b/contrib/sucap/Makefile
@@ -1,9 +1,18 @@
+topdir=$(shell pwd)/../..
+include ../../Make.Rules
+
+# This line is here to link against the in-tree copy of libcap.so
+LINKEXTRA=-Wl,-rpath,$(topdir)/libcap
+DEPS=../../libcap/libcap.so
+
all: su
-su: su.c
- $(CC) -DPAM_APP_NAME=\"sucap\" -o $@ $< -lpam -lpam_misc -lcap
+su: su.c $(DEPS)
+ $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -DPAM_APP_NAME=\"sucap\" $< -o $@ $(LINKEXTRA) -lpam -lpam_misc $(LIBCAPLIB)
# to permit all ambient capabilities, this needs all permitted.
- sudo setcap =p ./su
+ # sudo setcap =p ./su
+ # to permit all inheritable, as CAP_PURE1E needs, we don't need as much
+ sudo setcap cap_chown,cap_setgid,cap_setuid,cap_dac_read_search,cap_setpcap=p ./su
clean:
rm -f su su.o *~
diff --git a/contrib/sucap/README.md b/contrib/sucap/README.md
index 586f017..5cc0dcc 100644
--- a/contrib/sucap/README.md
+++ b/contrib/sucap/README.md
@@ -1,21 +1,21 @@
-This directory contains a port of the SimplePAMApp su to more
-aggressively use libcap.
+# A fully capable version of `su`
-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`.
+This directory contains a port of the `SimplePAMApp` `su` one that can
+work in a `PURE1E` `libcap`-_mode_ environment.
-The point of developing this is to better test the full libcap
+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:
+for testing PAM interaction with `libcap` and `pam_cap.so`. The
+required expectations for `pam_unix.so` are that it include this
+commit:
+https://github.com/linux-pam/linux-pam/pull/373/commits/bf9b1d8ad909634000a7356af2d865a79d3f86f3
-The original sources were found here:
+The original sources for this version of `su` 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
+The `SimplePAMApps` contain the same License as `libcap` (they were
originally started by the same authors!). The credited Authors in the
above tarball were:
@@ -32,7 +32,7 @@ tar ball and is thus a derived work from that.
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:
+`pam_cap.so` issue:
- https://bugzilla.kernel.org/show_bug.cgi?id=212945
diff --git a/contrib/sucap/su.c b/contrib/sucap/su.c
index 5c98e5f..e3dfe70 100644
--- a/contrib/sucap/su.c
+++ b/contrib/sucap/su.c
@@ -67,7 +67,6 @@
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;
@@ -101,7 +100,7 @@ static const char *posix_env[] = {
* make_environment transcribes a selection of environment variables
* from the invoking user.
*/
-static int make_environment(pam_handle_t *pamh, int keep_env)
+static int make_environment(int keep_env)
{
const char *tmpe;
int i;
@@ -150,20 +149,26 @@ static void checkfds(void)
if (fstat(1, &st) == -1) {
fd = open("/dev/null", O_WRONLY);
- if (fd == -1) exit(1);
+ if (fd == -1) goto badfds;
if (fd != 1) {
- if (dup2(fd, 1) == -1) exit(1);
- if (close(fd) == -1) exit(1);
+ if (dup2(fd, 1) == -1) goto badfds;
+ if (close(fd) == -1) goto badfds;
}
}
if (fstat(2, &st) == -1) {
fd = open("/dev/null", O_WRONLY);
- if (fd == -1) exit(1);
+ if (fd == -1) goto badfds;
if (fd != 2) {
- if (dup2(fd, 2) == -1) exit(1);
- if (close(fd) == -1) exit(1);
+ if (dup2(fd, 2) == -1) goto badfds;
+ if (close(fd) == -1) goto badfds;
}
}
+
+ return;
+
+badfds:
+ perror("bad filedes");
+ exit(1);
}
/*
@@ -347,7 +352,8 @@ static void restore_terminal_owner(void)
* 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)
+static int make_process_unkillable(const char **callname,
+ const char **err_descr)
{
invoked_uid = getuid();
if (invoked_uid == TEMP_UID) {
@@ -367,14 +373,14 @@ int make_process_unkillable(const char **callname, const char **err_descr)
* make_process_killable restores the invoking uid to the current
* process.
*/
-void make_process_killable()
+static void make_process_killable(void)
{
(void) cap_setuid(invoked_uid);
}
/* ------ command line parser ----------------- */
-void usage(int exit_val)
+static void usage(int exit_val)
{
fprintf(stderr,"usage: su [-] [-h] [-c \"command\"] [username]\n");
exit(exit_val);
@@ -384,8 +390,8 @@ void usage(int 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)
+static void parse_command_line(int argc, char *argv[], int *is_login,
+ const char **user, const char **command)
{
int username_present, command_present;
@@ -464,7 +470,7 @@ static void prepare_for_job_control(int need_it)
need_job_control = need_it;
}
-int wait_for_child(pid_t child)
+static int wait_for_child(pid_t child)
{
int retval, status, exit_code;
sigset_t ourset;
@@ -585,8 +591,8 @@ int wait_for_child(pid_t child)
* 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)
+static const 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 */
@@ -714,7 +720,7 @@ static char * const *build_shell_args(const char *pw_shell, int login,
}
D(("returning arg list"));
- return (char * const *) args;
+ return (const char * const *) args;
}
@@ -738,20 +744,6 @@ static void exit_now(int exit_code, const char *format, ...)
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 = {
@@ -779,8 +771,8 @@ static void do_pam_init(const char *user, int is_login)
* Fill in some blanks
*/
- retval = make_environment(pamh, !is_login);
- D(("made_environment returned: %s", pam_strerror(pamh,retval)));
+ retval = make_environment(!is_login);
+ D(("made_environment returned: %s", pam_strerror(pamh, retval)));
if (retval == PAM_SUCCESS && is_terminal) {
const char *terminal = ttyname(STDIN_FILENO);
@@ -820,8 +812,7 @@ static void do_pam_init(const char *user, int is_login)
/*
* 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,
+static int authenticate_user(cap_t all, int *retval, const char **place,
const char **err_descr)
{
*place = "pre-auth cap_set_proc";
@@ -841,8 +832,7 @@ static int authenticate_user(pam_handle_t *pamh, cap_t all,
/*
* 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,
+static int user_accounting(cap_t all, int *retval, const char **place,
const char **err_descr) {
*place = "user_accounting";
if (cap_set_proc(all)) {
@@ -1092,13 +1082,10 @@ static int utmp_do_open_session(const char *user, const char *terminal,
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);
@@ -1114,7 +1101,7 @@ static int utmp_do_close_session(const char *terminal,
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);
+ write_wtmp(&u_tmp, place, err_descr);
memset(login_stored_utmp, 0, sizeof(u_tmp)); /* reset entry */
free(login_stored_utmp);
@@ -1133,7 +1120,7 @@ static int utmp_do_close_session(const char *terminal,
setutent(); /* rewind file (replace old) */
pututline(&u_tmp); /* mark as dead */
- retval = write_wtmp(&u_tmp, place, err_descr);
+ write_wtmp(&u_tmp, place, err_descr);
}
}
@@ -1155,8 +1142,7 @@ static int utmp_do_close_session(const char *terminal,
* 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,
+static int utmp_open_session(pid_t pid, int *retval,
const char **place, const char **err_descr)
{
const char *user, *terminal, *rhost;
@@ -1177,8 +1163,7 @@ static int utmp_open_session(pam_handle_t *pamh, pid_t pid,
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)
+static int utmp_close_session(const char **place, const char **err_descr)
{
int retval;
const char *terminal;
@@ -1194,12 +1179,12 @@ static int utmp_close_session(pam_handle_t *pamh
}
/*
- * set_credentials raises all of the process and PAM credentials.
+ * set_credentials raises 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)
+static int set_credentials(cap_t all, int login,
+ const char **user_p, uid_t *uid_p,
+ const char **pw_shell, int *retval,
+ const char **place, const char **err_descr)
{
const char *user;
char *shell;
@@ -1217,6 +1202,7 @@ static int set_credentials(pam_handle_t *pamh, cap_t all, int login,
*retval = PAM_USER_UNKNOWN;
return 1;
}
+ *user_p = user;
/*
* Add the LOGNAME and HOME environment variables.
@@ -1230,6 +1216,13 @@ static int set_credentials(pam_handle_t *pamh, cap_t all, int login,
}
uid = pw->pw_uid;
+ if (uid == 0) {
+ D(("user is superuser: %s", user));
+ *retval = PAM_CRED_ERR;
+ return 1;
+ }
+ *uid_p = uid;
+
shell = x_strdup(pw->pw_shell);
if (shell == NULL) {
D(("user %s has no shell", user));
@@ -1244,11 +1237,18 @@ static int set_credentials(pam_handle_t *pamh, cap_t all, int login,
*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;
- }
+ }
+
+ /* bash requires these be set to the target user values */
+ if (pam_misc_setenv(pamh, "HOME", pw->pw_dir, 0) != PAM_SUCCESS) {
+ D(("failed to set HOME"));
+ *retval = PAM_CRED_ERR;
+ return 1;
+ }
+ if (pam_misc_setenv(pamh, "USER", user, 0) != PAM_SUCCESS) {
+ D(("failed to set USER"));
+ *retval = PAM_CRED_ERR;
+ return 1;
}
current = cap_get_proc();
@@ -1300,8 +1300,8 @@ static int set_credentials(pam_handle_t *pamh, cap_t all, int login,
/*
* 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)
+static int open_session(cap_t all, int *retval, const char **place,
+ const char **err_descr)
{
/* Open the su-session */
*place = "pam_open_session";
@@ -1321,11 +1321,11 @@ static int open_session(pam_handle_t *pamh, cap_t all,
static int launch_callback_fn(void *h)
{
- pam_handle_t *pamh = h;
+ pam_handle_t *my_pamh = h;
int retval;
D(("pam_end"));
- retval = pam_end(pamh, PAM_SUCCESS | PAM_DATA_SILENT);
+ retval = pam_end(my_pamh, PAM_SUCCESS | PAM_DATA_SILENT);
pamh = NULL;
if (retval != PAM_SUCCESS) {
return -1;
@@ -1337,22 +1337,34 @@ static int launch_callback_fn(void *h)
*/
enable_terminal_signals();
+#ifdef PAM_DEBUG
+ cap_iab_t iab = cap_iab_get_proc();
+ char *text = cap_iab_to_text(iab);
+ D(("iab = %s", text));
+ cap_free(text);
+ cap_free(iab);
+ cap_t cap = cap_get_proc();
+ text = cap_to_text(cap, NULL);
+ D(("cap = %s", text));
+ cap_free(text);
+ cap_free(cap);
+#endif
+
D(("about to launch"));
return 0;
}
/* Returns PAM_<STATUS>. */
-static int perform_launch_and_cleanup(cap_t all, int is_login,
+static int perform_launch_and_cleanup(cap_t all, int is_login, const char *user,
const char *shell, const char *command)
{
- int retval, status;
- const char *user, *home;
- uid_t uid;
- char * const * shell_args;
+ int status;
+ const char *home;
+ const char * const * shell_args;
char * const * shell_env;
cap_launch_t launcher;
pid_t child;
-
+ cap_iab_t iab;
/*
* Break up the shell command into a command and arguments
@@ -1387,6 +1399,12 @@ static int perform_launch_and_cleanup(cap_t all, int is_login,
return PAM_SYSTEM_ERR;
}
+ iab = cap_iab_get_proc();
+ if (iab == NULL) {
+ D(("failed to read IAB value of process"));
+ return PAM_SYSTEM_ERR;
+ }
+
launcher = cap_new_launcher(shell_args[0],
(const char * const *) &shell_args[1],
(const char * const *) shell_env);
@@ -1394,12 +1412,16 @@ static int perform_launch_and_cleanup(cap_t all, int is_login,
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);
+ if (cap_set_proc(all) != 0) {
+ D(("failed to restore process capabilities"));
+ return PAM_SYSTEM_ERR;
+ }
+
/* job control is off for login sessions */
prepare_for_job_control(!is_login && command != NULL);
@@ -1415,7 +1437,7 @@ static int perform_launch_and_cleanup(cap_t all, int is_login,
return status;
}
-static void close_session(pam_handle_t *pamh, cap_t all)
+static void close_session(cap_t all)
{
int retval;
@@ -1439,13 +1461,14 @@ 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;
+ const char *user;
all = cap_get_proc();
cap_fill(all, CAP_EFFECTIVE, CAP_PERMITTED);
+ cap_clear_flag(all, CAP_INHERITABLE);
checkfds();
@@ -1457,10 +1480,10 @@ int main(int argc, char *argv[])
/* ---------- 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 */
+ user = NULL; /* transient until PAM_USER defined */
}
/*
@@ -1481,7 +1504,7 @@ int main(int argc, char *argv[])
goto su_exit;
}
- if (authenticate_user(pamh, all, &retval, &place, &err_descr) != 0) {
+ if (authenticate_user(all, &retval, &place, &err_descr) != 0) {
goto auth_exit;
}
@@ -1489,12 +1512,18 @@ int main(int argc, char *argv[])
* The user is valid, but should they have access at this
* time?
*/
- if (user_accounting(pamh, all, &retval, &place, &err_descr) != 0) {
+ if (user_accounting(all, &retval, &place, &err_descr) != 0) {
goto auth_exit;
}
D(("su attempt is confirmed as authorized"));
+ if (set_credentials(all, is_login, &user, &uid, &shell,
+ &retval, &place, &err_descr) != 0) {
+ D(("failed to set credentials"));
+ goto auth_exit;
+ }
+
/*
* ... setup terminal, ...
*/
@@ -1507,12 +1536,6 @@ int main(int argc, char *argv[])
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
@@ -1539,8 +1562,7 @@ int main(int argc, char *argv[])
* Note: we use the parent pid as a session identifier for
* the logging.
*/
- retcode = utmp_open_session(pamh, getpid(),
- &retval, &place, &err_descr);
+ retcode = utmp_open_session(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 */
@@ -1549,17 +1571,25 @@ int main(int argc, char *argv[])
}
}
- if (open_session(pamh, t_caps, &retval, &place, &err_descr) != 0) {
+#ifdef PAM_DEBUG
+ cap_iab_t iab = cap_iab_get_proc();
+ char *text = cap_iab_to_text(iab);
+ D(("pre-session open iab = %s", text));
+ cap_free(text);
+ cap_free(iab);
+#endif
+
+ if (open_session(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);
+ status = perform_launch_and_cleanup(all, is_login, user, shell, command);
+ close_session(all);
utmp_closer:
if (is_login) {
/* do [uw]tmp cleanup */
- retcode = utmp_close_session(pamh, &place, &err_descr);
+ retcode = utmp_close_session(&place, &err_descr);
if (retcode) {
fprintf(stderr, PAM_APP_NAME ": %s: %s\n", place, err_descr);
}
@@ -1576,7 +1606,6 @@ delete_cred:
pam_strerror(pamh, retcode));
}
-old_owner:
D(("return terminal to local control"));
restore_terminal_owner();
@@ -1602,5 +1631,8 @@ auth_exit:
}
su_exit:
+ if (status != 0) {
+ perror(PAM_APP_NAME " failed");
+ }
exit(status); /* transparent exit */
}
diff --git a/doc/Makefile b/doc/Makefile
index a34cee0..6919488 100644
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -8,9 +8,12 @@ include $(topdir)/Make.Rules
MAN1S = capsh.1
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_fill.3 cap_fill_flag.3 cap_max_bits.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_set_nsowner.3 cap_get_nsowner.3 \
cap_copy_ext.3 cap_size.3 cap_copy_int.3 cap_mode.3 \
+ cap_copy_int_check.3 cap_set_syscall.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 \
@@ -21,22 +24,32 @@ MAN3S = cap_init.3 cap_free.3 cap_dup.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.3 cap_iab_init.3 cap_iab_dup.3 cap_iab_compare.3 \
+ cap_iab_get_proc.3 cap_iab_get_pid.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
+ cap_iab_set_vector.3 cap_iab_fill.3 cap_proc_root.3 \
+ cap_prctl.3 cap_prctlw.3 \
+ psx_syscall.3 psx_syscall3.3 psx_syscall6.3 psx_set_sensitivity.3 \
+ psx_load_syscalls.3 __psx_syscall.3 \
+ libpsx.3
+MAN8S = getcap.8 setcap.8 getpcaps.8 captree.8
MANS = $(MAN1S) $(MAN3S) $(MAN8S)
all: $(MANS)
+test:
+ @echo no doc tests available
+
+sudotest:
+ @echo no doc sudotests available
+
.PHONY: html
html:
mkdir -p html
for man in $(MANS) ; \
do \
- egrep '^\.so man' $$man > /dev/null || \
+ $(BUILD_EGREP) '^\.so man' $$man > /dev/null || \
groff -man -Thtml $$man > html/$$man.html ; \
done
diff --git a/doc/__psx_syscall.3 b/doc/__psx_syscall.3
new file mode 100644
index 0000000..663420c
--- /dev/null
+++ b/doc/__psx_syscall.3
@@ -0,0 +1 @@
+.so man3/libpsx.3
diff --git a/doc/cap_clear.3 b/doc/cap_clear.3
index 6d06049..b8dbc30 100644
--- a/doc/cap_clear.3
+++ b/doc/cap_clear.3
@@ -1,6 +1,6 @@
-.TH CAP_CLEAR 3 "2021-03-06" "" "Linux Programmer's Manual"
+.TH CAP_CLEAR 3 "2022-10-16" "" "Linux Programmer's Manual"
.SH NAME
-cap_clear, cap_clear_flag, cap_get_flag, cap_set_flag, cap_fill, cap_compare \- capability data object manipulation
+cap_clear, cap_clear_flag, cap_get_flag, cap_set_flag, cap_fill_flag, cap_fill, cap_compare, cap_max_bits \- capability data object manipulation
.SH SYNOPSIS
.nf
#include <sys/capability.h>
@@ -11,8 +11,11 @@ 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_flag(cap_t cap_p, cap_flag_t to,
+ const cap_t ref, cap_flag_t from);
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);
+cap_value_t cap_max_bits();
.fi
.sp
Link with \fI\-lcap\fP.
@@ -20,7 +23,7 @@ Link with \fI\-lcap\fP.
These functions work on a capability state held in working storage.
A
.I cap_t
-holds information about the capabilities in each of the three sets,
+holds information about the capabilities in each of the three flags,
Permitted, Inheritable, and Effective.
Each capability in a set may be clear (disabled, 0) or set (enabled, 1).
.PP
@@ -32,7 +35,7 @@ identifies a capability, such as
.TP
.I cap_flag_t
identifies one of the three flags associated with a capability
-(i.e., it identifies one of the three capability sets).
+(i.e., it identifies one of the three capability dimensions).
Valid values for this type are
.BR CAP_EFFECTIVE ,
.B CAP_INHERITABLE
@@ -81,6 +84,10 @@ The argument,
is used to specify the number of capabilities in the array,
.IR caps .
.PP
+.BR cap_fill_flag ()
+fills the to flag of one capability set, with the values in the from
+flag of a reference capability set.
+.PP
.BR cap_fill ()
fills the to flag values by copying all of the from flag values.
.PP
@@ -88,18 +95,28 @@ fills the to flag values by copying all of the from flag values.
compares two full capability sets and, in the spirit of
.BR memcmp (),
returns zero if the two capability sets are identical. A positive
-return value,
-.BR status ,
-indicates there is a difference between them. The
-returned value carries further information about which of three sets,
-.I cap_flag_t
-.BR flag ,
-differ. Specifically, the macro
+return
+.I value
+indicates there is a difference between them. The returned
+.I value
+carries further information about the
+.BI "cap_flag_t " flag
+differences. Specifically, the macro
.B CAP_DIFFERS
-.RI ( status ", " flag )
-evaluates to non-zero if the returned status differs in its
+.RI ( value ", " flag )
+evaluates to non-zero if the returned
+.I value
+differs in its
.I flag
components.
+.PP
+.BR cap_max_bits ()
+returns the number of capability values known to the running
+kernel. This may differ from libcap's list known at compilation
+time. Unnamed, at compilation time, capabilites can be referred to
+numerically and libcap will handle them appropriately. Note, the
+running kernel wins and it gets to define what "all" capabilities
+means.
.SH "RETURN VALUE"
.BR cap_clear (),
.BR cap_clear_flag (),
@@ -109,7 +126,12 @@ and
.BR cap_compare ()
return zero on success, and \-1 on failure. Other return values for
.BR cap_compare ()
-are described above.
+are described above. The function
+.BR cap_max_bits ()
+returns a numeric value of type
+.B cap_value_t
+that is one larger than the largest actual value known to the running
+kernel.
.PP
On failure,
.I errno
@@ -117,11 +139,14 @@ is set to
.BR EINVAL ,
indicating that one of the arguments is invalid.
.SH "CONFORMING TO"
-These functions are as per the withdrawn POSIX.1e draft specification.
-.BR cap_clear_flag ()
-and
+These functions are mostly as per specified in the withdrawn POSIX.1e
+draft specification. The following are Linux extensions:
+.BR cap_fill (),
+.BR cap_fill_flag (),
+.BR cap_clear_flag (),
.BR cap_compare ()
-are Linux extensions.
+and
+.BR cap_max_bits ().
.SH "SEE ALSO"
.BR libcap (3),
.BR cap_copy_ext (3),
diff --git a/doc/cap_copy_ext.3 b/doc/cap_copy_ext.3
index 0965ad1..b863442 100644
--- a/doc/cap_copy_ext.3
+++ b/doc/cap_copy_ext.3
@@ -9,6 +9,7 @@ external representation translation
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);
+cap_t cap_copy_int_check(const void *cap_ext, ssize_t length);
.fi
.sp
Link with \fI\-lcap\fP.
@@ -56,9 +57,9 @@ state. The function initializes the capability state and then copies
the capability state from the record pointed to by
.I ext_p
into the capability state, converting, if necessary, the data from a
-contiguous, persistent format to an undefined, internal format. Once
-copied into internal format, the object can be manipulated by the capability
-state manipulation functions (see
+contiguous, persistent format to an opaque, internal format. Once
+copied into internal format, the object can be manipulated by the
+capability state manipulation functions (see
.BR cap_clear (3)).
Note that the record pointed to by
.I ext_p
@@ -71,6 +72,12 @@ longer required, by calling
with the
.I cap_t
as an argument.
+.PP
+.BR cap_copy_int_check ()
+performs the same operation as
+.BR cap_copy_int ()
+but additionally checks that the provided external data's size is not
+larger than the noted length.
.SH "RETURN VALUE"
.BR cap_size ()
returns the length required to hold a capability data record on success,
@@ -82,8 +89,10 @@ returns the number of bytes placed in the user managed space pointed to by
on success, and \-1 on failure.
.PP
.BR cap_copy_int ()
-returns a pointer to the newly created capability state in working storage
-on success, and NULL on failure.
+and
+.BR cap_copy_int_check ()
+return a pointer to the newly created capability state in working
+storage on success, and NULL on failure.
.PP
On failure,
.BR errno
diff --git a/doc/cap_copy_int_check.3 b/doc/cap_copy_int_check.3
new file mode 100644
index 0000000..2e6e89c
--- /dev/null
+++ b/doc/cap_copy_int_check.3
@@ -0,0 +1 @@
+.so man3/cap_copy_ext.3
diff --git a/doc/cap_fill.3 b/doc/cap_fill.3
new file mode 100644
index 0000000..db506c6
--- /dev/null
+++ b/doc/cap_fill.3
@@ -0,0 +1 @@
+.so man3/cap_clear.3
diff --git a/doc/cap_fill_flag.3 b/doc/cap_fill_flag.3
new file mode 100644
index 0000000..db506c6
--- /dev/null
+++ b/doc/cap_fill_flag.3
@@ -0,0 +1 @@
+.so man3/cap_clear.3
diff --git a/doc/cap_from_text.3 b/doc/cap_from_text.3
index 9370e26..1a01c7c 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 "2021-03-06" "" "Linux Programmer's Manual"
+.TH CAP_FROM_TEXT 3 "2022-09-22" "" "Linux Programmer's Manual"
.SH NAME
cap_from_text, cap_to_text, cap_to_name, cap_from_name \- capability
state textual representation translation
@@ -9,9 +9,9 @@ state textual representation translation
.nf
#include <sys/capability.h>
-cap_t cap_from_text(const char* buf_p );
-char *cap_to_text(cap_t caps, ssize_t * length_p);
-int cap_from_name(const char* name , cap_value_t* cap_p);
+cap_t cap_from_text(const char *buf_p);
+char *cap_to_text(cap_t caps, ssize_t *len_p);
+int cap_from_name(const char *name, cap_value_t *cap_p);
char *cap_to_name(cap_value_t cap);
.fi
.sp
@@ -46,7 +46,7 @@ is both set and cleared within a single clause.
.PP
.BR cap_to_text ()
converts the capability state in working storage identified by
-.I cap_p
+.I caps
into a nul-terminated human-readable string. This function allocates
any memory necessary to contain the string, and returns a pointer to
the string. If the pointer
@@ -56,7 +56,7 @@ the function shall also return the full length of the string (not including
the nul terminator) in the location pointed to by
.IR len_p .
The capability state in working storage, identified by
-.IR cap_p ,
+.IR caps ,
is completely represented in the character string.
When the capability state in working storage is no longer required,
the caller should free any releasable memory by calling
diff --git a/doc/cap_get_file.3 b/doc/cap_get_file.3
index 4c812fe..985236c 100644
--- a/doc/cap_get_file.3
+++ b/doc/cap_get_file.3
@@ -1,10 +1,10 @@
.\"
.\" written by Andrew Main <zefram@dcs.warwick.ac.uk>
.\"
-.TH CAP_GET_FILE 3 "2021-03-06" "" "Linux Programmer's Manual"
+.TH CAP_GET_FILE 3 "2022-10-16" "" "Linux Programmer's Manual"
.SH NAME
-cap_get_file, cap_set_file, cap_get_fd, cap_set_fd \- capability
-manipulation on files
+cap_get_file, cap_set_file, cap_get_fd, cap_set_fd, cap_get_nsowner, \
+cap_set_nsowner \- capability manipulation on files
.SH SYNOPSIS
.nf
#include <sys/capability.h>
diff --git a/doc/cap_get_nsowner.3 b/doc/cap_get_nsowner.3
new file mode 100644
index 0000000..3970c34
--- /dev/null
+++ b/doc/cap_get_nsowner.3
@@ -0,0 +1 @@
+.so man3/cap_get_file.3
diff --git a/doc/cap_get_proc.3 b/doc/cap_get_proc.3
index 496c06e..91fb705 100644
--- a/doc/cap_get_proc.3
+++ b/doc/cap_get_proc.3
@@ -1,4 +1,4 @@
-.TH CAP_GET_PROC 3 "2021-03-06" "" "Linux Programmer's Manual"
+.TH CAP_GET_PROC 3 "2022-04-28" "" "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, \
@@ -65,18 +65,40 @@ the function will fail, and the capability state of the process will remain
unchanged.
.PP
.BR cap_get_pid ()
-returns
+returns a
.IR cap_t ,
see
.BR cap_init (3),
-with the process capabilities of the process indicated by
+with the process capabilities of the process known to the caller as
.IR pid .
-(If
+If
.I pid
-is 0, then the calling process's capabilities are returned.)
+is 0, then the calling process's capabilities are returned.
This information can also be obtained from the
.I /proc/<pid>/status
-file.
+file. (The entries in that file can be translated with the
+.BI "capsh \-\-decode=" XXX
+command line.) When the caller is operating within a
+.RB ( CLONE_NEWPID )
+namespace, the numerical
+.I pid
+argument is interpreted in the range of that namespace. As such, the
+caller's idea of the target
+.I pid
+may differ from that of the target process when they are operating in
+different pid namespaces. See
+.BR pid_namespaces (7)
+for details.
+Further, the returned
+.I cap_t
+value holds the capabilities that the target
+.I pid
+thinks it has. If the target is operating in a
+.RB ( CLONE_NEWUSER )
+namespace, the system wide privilege of those user namespace
+capabilities my be substantially reduced. See
+.BR user_namespaces (7)
+for details.
.PP
.BR cap_get_bound ()
with a
@@ -120,16 +142,13 @@ raised ambient bits will only be retained as long as this remains true.
.PP
.BR cap_reset_ambient ()
resets all of the ambient capabilities for the calling process to
-their lowered value. To complete successfully, the prevailing
-.I effective
-capability set must have a raised
-.BR CAP_SETPCAP .
-Note, the ambient set is intended to operate in a legacy environment
-where the application has limited awareness of capabilities in
-general. Executing a file with associated filesystem capabilities, the
-kernel will implicitly reset the ambient set of the process. Also,
-changes to the inheritable set by the program code without explicitly
-fixing up the ambient set can also drop ambient bits.
+their lowered value. Note, the ambient set is intended to operate in a
+legacy environment where the application has limited awareness of
+capabilities in general. Executing a file, with associated filesystem
+capabilities, the kernel will implicitly reset the ambient set of the
+process. Further, changes to the inheritable set by the program code
+without explicitly fixing up the ambient set can also drop ambient
+bits.
.PP
.BR cap_get_secbits ()
returns the securebits of the calling process. These bits affect the
@@ -378,7 +397,7 @@ Note, the above sequence can be performed by the
.B capsh
tool as follows:
.sp
-.B sudo /sbin/capsh \-\-user=nobody \-\-mode=NOPRIV \-\-print
+.B sudo capsh \-\-user=nobody \-\-mode=NOPRIV \-\-print
.sp
where
.B \-\-print
@@ -392,5 +411,8 @@ displays the resulting privilege state.
.BR cap_from_text (3),
.BR cap_get_file (3),
.BR cap_init (3),
+.BR namespaces (7),
+.BR pid_namespaces (7),
+.BR user_namespaces (7),
.BR psx_syscall (3),
.BR capabilities (7).
diff --git a/doc/cap_iab.3 b/doc/cap_iab.3
index a453428..3e6282d 100644
--- a/doc/cap_iab.3
+++ b/doc/cap_iab.3
@@ -1,27 +1,28 @@
-.TH CAP_IAB 3 "2021-03-10" "" "Linux Programmer's Manual"
+.TH CAP_IAB 3 "2022-10-16" "" "Linux Programmer's Manual"
.SH NAME
+cap_iab_init, cap_iab_dup, cap_iab_get_proc, cap_iab_get_pid, \
+cap_iab_set_proc, cap_iab_to_text, cap_iab_from_text, \
+cap_iab_get_vector, cap_iab_compare, cap_iab_set_vector, \
+cap_iab_fill, cap_proc_root \- inheritable IAB tuple support functions
+.SH SYNOPSIS
.nf
#include <sys/capability.h>
cap_iab_t cap_iab_init(void);
-
+cap_iab_t cap_iab_dup(cap_iab_t iab);
cap_iab_t cap_iab_get_proc(void);
-
+cap_iab_t cap_iab_get_pid(pid_t pid);
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_compare(cap_iab_t a, cap_iab_t b);
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);
-
+char *cap_proc_root(const char *root);
.fi
.sp
Link with \fI\-lcap\fP.
@@ -32,42 +33,45 @@ inheritable process capability vectors: Inh, Amb and Bound. This
\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
+system calls. The convolution rules using the IAB tuple 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
+three components of an IAB tuple 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
+Inheritable flag at all times; 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.
+Permitted flag for the process; and the Bound (or \fIblocked\fP)
+vector is the twos-complement of the process bounding vector.
.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.
+In some environments, it is considered desirable to \fInaively\fP
+inherit capabilities. That is pass capabilities, independent of the
+status of the executed binary, from parent to child through
+\fBexec*\fP 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 a
+pre-exec* process and a file-capability bestowed executable of the
+traditional (POSIX.1e) 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
+The convolution rules for IAB style inheritance are: I'=I; A'=A&I;
+P'=A&I&P. 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.
+support for the IAB tuple 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
+will not block any Permitted file capabilities through exec, but it
+won't bestow any either. The returned \fIcap_iab_t\fP should be freed
+with
+.BR cap_free (3).
+.sp
+.BR cap_iab_dup ()
+returns a copy of the specified IAB value. The returned cap_iab_t
+should be freed with
.BR cap_free (3).
.sp
.BR cap_iab_get_proc ()
@@ -75,31 +79,41 @@ 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_get_pid ()
+returns a copy of the IAB value for the specified process. The returned
+cap_iab_t should be freed with
+.BR cap_free (3).
+This function defaults to searching
+.BR /proc/ <PID> /status
+for the IAB information, but that location can be overridden using the
+.BR cap_proc_root ()
+function.
+.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.
+process requires CAP_SETPCAP raised in the E flag and a superset of P
+and I values over those in the A vectors.
.sp
.BR cap_iab_to_text ()
-will convert an IAB set to a canonical text representation. The
+will convert an IAB tuple 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
+generates an IAB tuple from a text string (likely generated by the
+previous function). The returned IAB tuple 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"
+prefixed by nothing (or %) (Inh); ! (Bound, but think Blocked); ^
+(Amb). Or, some combination thereof. Since the Amb vector is
+constrained to be no greater than the Inh vector, ^ is equivalent to
+%^. Further, unless B is non-zero, % can be omitted. The following are
+legal text representations: "!%cap_chown" (Bound but Inh),
+"!cap_chown,^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
@@ -107,14 +121,35 @@ for the \fIpam_cap.so\fP config file.
can be used to determine the specific capability value of an IAB
vector.
.sp
+.BR cap_iab_compare ()
+can be used to compare two cap_iab_t tuples. When the return value is
+non-zero, the macro \fBCAP_IAB_DIFFERS\fR(\fIstatus\fR, \fIvector\fR)
+evaluates to non-zero if the returned status differs in its
+.I vector
+components.
+.sp
.BR cap_iab_set_vector ()
can be used to set a specific vector value to the enable setting.
+.sp
.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
+of the IAB tuple. Copying into Amb in this way may implicitly raise Inh
+values in the IAB tuple. Similarly copying into the Inh vector may
implicitly lower Amb values that are not present in the resulting Inh
vector.
+.sp
+.BR cap_proc_root ()
+can be used to determine the current location queried by
+.BR cap_iab_get_pid ().
+Returned values should be released with
+.BR cap_free (3).
+If the argument to
+.BR cap_proc_root ()
+is not \fBNULL\fP, a copy of it will become the replacement for
+.BR /proc .
+Note, this function is \fInot\fP thread safe with respect to
+concurrent calls to
+.BR cap_iab_get_pid ().
.SH "ERRORS"
The functions returning \fIcap_iab_t\fP values or allocated memory in
the form of a string return NULL on error.
@@ -125,10 +160,10 @@ 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
+IAB tuple, 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
+That is, the Amb vector of the IAB tuple is rendered moot when an
executable with a file capability is executed.
.PP
Further, there are libcap
@@ -142,19 +177,22 @@ 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.
+include enforced limits on the Bounding vector and introduce support
+for the Amibient vector of capability bits.
-While the Inheritable and Bounding sets were anticipated by the
-POSIX.1e draft that introduced capabilities, the Ambient set is a
+While the Inheritable and Bounding vectors were anticipated by the
+POSIX.1e draft that introduced capabilities, the Ambient vector 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
+naively inheritable capabilities: the IAB tuple. The support for this
debuted in libcap-2.33.
-
+.SH "REPORTING BUGS"
+Please report bugs via:
+.TP
+https://bugzilla.kernel.org/buglist.cgi?component=libcap&list_id=1090757
.SH "SEE ALSO"
.BR libcap (3),
.BR cap_launch (3),
diff --git a/doc/cap_iab_compare.3 b/doc/cap_iab_compare.3
new file mode 100644
index 0000000..3e730b1
--- /dev/null
+++ b/doc/cap_iab_compare.3
@@ -0,0 +1 @@
+.so man3/cap_iab.3
diff --git a/doc/cap_iab_dup.3 b/doc/cap_iab_dup.3
new file mode 100644
index 0000000..3e730b1
--- /dev/null
+++ b/doc/cap_iab_dup.3
@@ -0,0 +1 @@
+.so man3/cap_iab.3
diff --git a/doc/cap_iab_get_pid.3 b/doc/cap_iab_get_pid.3
new file mode 100644
index 0000000..3e730b1
--- /dev/null
+++ b/doc/cap_iab_get_pid.3
@@ -0,0 +1 @@
+.so man3/cap_iab.3
diff --git a/doc/cap_launch.3 b/doc/cap_launch.3
index 6d9b8f7..2d186eb 100644
--- a/doc/cap_launch.3
+++ b/doc/cap_launch.3
@@ -1,24 +1,30 @@
.TH CAP_LAUNCH 3 "2021-08-01" "" "Linux Programmer's Manual"
.SH NAME
+cap_new_launcher, cap_func_launcher, cap_launcher_callback, \
+cap_launcher_set_mode, cap_launcher_set_iab, cap_launcher_set_chroot, \
+cap_launch, cap_launcher_setuid, cap_launcher_setgroups \
+\- libcap launch functionality
+.SH SYNOPSYS
.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_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 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);
+int 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);
+int 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,
+int cap_launcher_setuid(cap_launch_t attr, uid_t uid);
+int cap_launcher_setgroups(cap_launch_t attr, gid_t gid,
+ int ngroups, const gid_t *groups);
.fi
.sp
Link with \fI\-lcap\fP.
@@ -76,9 +82,9 @@ 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 };
+ 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,
+ 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;
@@ -86,7 +92,7 @@ this would be to allocate detail as follows:
printf("launcher callback set detail to %d\\n", *detail);
munmap(detail, sizeof(int));
-.if
+.fi
.PP
Unless modified by the callback function, the launched code will
execute with the capability and other security context of the
@@ -124,9 +130,11 @@ 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.
+make a copy of it. This iab value is locked to the laucher and cannot
+be modified while associated with the launcher. 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
@@ -153,7 +161,8 @@ should be considered an error.
.BR cap_launch ()
returns -1 in the case of an error.
.PP
-In all such cases consult
+In all such cases a return value of 0 implies success. In other cases,
+consult
.BR errno (3)
for further details.
.SH "HISTORY"
diff --git a/doc/cap_max_bits.3 b/doc/cap_max_bits.3
new file mode 100644
index 0000000..db506c6
--- /dev/null
+++ b/doc/cap_max_bits.3
@@ -0,0 +1 @@
+.so man3/cap_clear.3
diff --git a/doc/cap_prctl.3 b/doc/cap_prctl.3
new file mode 100644
index 0000000..65ea3e4
--- /dev/null
+++ b/doc/cap_prctl.3
@@ -0,0 +1 @@
+.so man3/cap_get_proc.3
diff --git a/doc/cap_prctlw.3 b/doc/cap_prctlw.3
new file mode 100644
index 0000000..65ea3e4
--- /dev/null
+++ b/doc/cap_prctlw.3
@@ -0,0 +1 @@
+.so man3/cap_get_proc.3
diff --git a/doc/cap_proc_root.3 b/doc/cap_proc_root.3
new file mode 100644
index 0000000..3e730b1
--- /dev/null
+++ b/doc/cap_proc_root.3
@@ -0,0 +1 @@
+.so man3/cap_iab.3
diff --git a/doc/cap_set_nsowner.3 b/doc/cap_set_nsowner.3
new file mode 100644
index 0000000..3970c34
--- /dev/null
+++ b/doc/cap_set_nsowner.3
@@ -0,0 +1 @@
+.so man3/cap_get_file.3
diff --git a/doc/cap_set_syscall.3 b/doc/cap_set_syscall.3
new file mode 100644
index 0000000..48a44fa
--- /dev/null
+++ b/doc/cap_set_syscall.3
@@ -0,0 +1 @@
+.so man3/libcap.3
diff --git a/doc/capability.notes b/doc/capability.md
index 4087c80..cfad4c0 100644
--- a/doc/capability.notes
+++ b/doc/capability.md
@@ -1,5 +1,12 @@
-Overview
---------
+# Notes concerning wider use of capabilities
+
+## Overview
+
+**NOTE** These notes were added to the libcap package in
+libcap-1.03. They pre-date file capability support, but fully
+anticipate it. They are some thoughts on how to restructure a system
+to better leverage capability support. I've updated them to render as
+an `.md` formatted file.
As of Linux 2.2.0, the power of the superuser has been partitioned
into a set of discrete capabilities (in other places, these
@@ -11,48 +18,46 @@ can be protected (with wrappers) or rewritten to take advantage of
this fine grained approach to constraining the danger to your system
from programs running as 'root'.
-Notes on securing your system
------------------------------
+## Notes on securing your system
-Adopting a role approach to system security:
+### Adopting a role approach to system security
-changing all of the system binaries and directories to be owned by
+Changing all of the system binaries and directories to be owned by
some user that cannot log on. You might like to create a user with
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 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
+Why is this a good idea? In a simple case, the `CAP_FOWNER` 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 a compromized
daemon where the directory and the files it contains are owned by the
-system user. It can be protected by using a wrapper like execcap to
-ensure that the daemon is not running with the CAP_FUSER capability...
+system user. It can be protected to ensure that the daemon is not
+running with the `CAP_FOWNER` capability...
-
-Limiting the damage:
+### Limiting the damage
If your daemon only needs to be setuid-root in order to bind to a low
numbered port. You should restrict it to only having access to the
-CAP_NET_BIND_SERVICE capability. Coupled with not having any files on
-the system owned by root, it becomes significantly harder for such a
-daemon to damage your system.
+`CAP_NET_BIND_SERVICE` capability. Coupled with not having any files
+on the system owned by root, it becomes significantly harder for such
+a daemon to damage your system.
Note, you should think of this kind of trick as making things harder
for a potential attacker to exploit a hole in a daemon of this
type. Being able to bind to any privileged port is still a formidable
-privilege and can lead to difficult but 'interesting' man in the
-middle attacks -- hijack the telnet port for example and masquerade as
-the login program... Collecting passwords for another day.
-
+privilege and can lead to difficult but _interesting_
+man-in-the-middle attacks -- hijack the telnet port for example and
+masquerade as the login program... Collecting passwords for another
+day.
-The /proc/ filesystem:
+### The /proc/ filesystem
This Linux-specific directory tree holds most of the state of the
system in a form that can sometimes be manipulated by file
read/writes. Take care to ensure that the filesystem is not mounted
with uid=0, since root (with no capabilities) would still be able to
-read sensitive files in the /proc/ tree - kcore for example.
+read sensitive files in the `/proc/` tree - `kcore` for example.
[Patch is available for 2.2.1 - I just wrote it!]
diff --git a/doc/capsh.1 b/doc/capsh.1
index e309438..4f3aaae 100644
--- a/doc/capsh.1
+++ b/doc/capsh.1
@@ -1,4 +1,4 @@
-.TH CAPSH 1 "2021-07-01" "libcap 2" "User Commands"
+.TH CAPSH 1 "2021-10-22" "libcap" "User Commands"
.SH NAME
capsh \- capability shell wrapper
.SH SYNOPSIS
@@ -31,7 +31,12 @@ with trailing arguments. Note, you can use
.B \-c 'command to execute'
for specific commands.
.TP
-.B ==
+.BI \-\+ " [args]"
+Uses \fBcap_launch\fP(3) to fork a child to execute the shell. When
+the child exits, \fBcapsh\fP exits with the status of the child or 1
+in the case that the child was terminated by a signal.
+.TP
+.BI == " [args]"
Execute
.B capsh
again with the remaining arguments. Useful for testing
@@ -46,6 +51,12 @@ argument the PATH located binary may not be resolve to the same binary
as that running initially. This behavior is an intended feature as it
can complete the chroot transition.
.TP
+.BI =\+ " [args]"
+Uses \fBcap_launch\fP(3) to fork a child to re-execute
+\fBcapsh\fP. When this child exits, \fBcapsh\fP exits with the status
+of the child or 1 in the case that the child was terminated by a
+signal.
+.TP
.BI \-\-caps= cap-set
Set the prevailing process capabilities to those specified by
.IR cap-set .
@@ -91,9 +102,10 @@ Following this command, the effective capabilities will be cleared,
but the permitted set will not be, so the running program is still
privileged.
.TP
-.B \-\-modes
-Lists all of the libcap modes supported by
-.BR \-\-mode .
+.B \-\-mode
+Display the prevailing libcap mode as guessed by the
+.BR cap_get_mode (3)
+function.
.TP
.BR \-\-mode= <mode>
Force the program into a
@@ -101,6 +113,10 @@ Force the program into a
security mode. This is a set of securebits and prevailing capability
arrangement recommended for its pre-determined security stance.
.TP
+.B \-\-modes
+Lists all of the libcap modes supported by
+.BR \-\-mode= <mode>.
+.TP
.BR \-\-inmode= <mode>
Confirm that the prevailing mode is that specified in
.IR <mode> ,
@@ -171,7 +187,7 @@ the current process. In all cases,
is deactivated when an
.BR exec ()
is performed. See
-.B \-\-secbits
+.BR \-\-secbits and \-\-mode
for ways to disable this feature.
.TP
.BI \-\-secbits= N
@@ -179,16 +195,17 @@ Set the security-bits for the program.
This is done using the
.BR prctl (2)
.B PR_SET_SECUREBITS
-operation.
-The list of supported bits and their meaning can be found in
-the
+operation. The list of supported bits and their meaning can be found
+in the
.B <sys/secbits.h>
header file. The program will list these bits via the
.B \-\-print
-command.
-The argument is expressed as a numeric bitmask,
-in any of the formats permitted by
+command. The argument is expressed as a numeric bitmask, in any of
+the formats permitted by
.BR strtoul (3).
+An alternative to this bit-twiddling is embedded in the
+.B \-\-mode*
+commandline arguments.
.TP
.BI \-\-chroot= /some/path
Execute the
@@ -223,6 +240,18 @@ 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 \-\-shell =/full/path
+This option changes the shell that is invoked when the argument
+\fB==\fP is encountered.
+.TP
+.BI \-\-strict
+This option toggles the suppression of subsequent attempts to fixup
+\fB\-\-caps=\fP and \fB\-\-inh=\fP arguments. That is, when the
+prevailing Effective flag does not contain \fBCAP_SETPCAP\fB the to be
+raised Inheritable Flag values (in strict mode) are limited to those
+in the Permitted set. The strict mode defaults to off. Supplying this
+argument an even number of times restores this default behavior.
+.TP
.BI \-\-suggest= phrase
Scan each of the textual descriptions of capabilities, known to
\fBcapsh\fP, and display all descriptions that include \fIphrase\fP.
@@ -279,6 +308,13 @@ vector has capability
.B xxx
raised.
.TP
+.BI \-\-has\-b= xxx
+Exit with status 1 unless the
+.I bounding
+vector has capability
+.B xxx
+in its (default) non-blocked state.
+.TP
.BI \-\-iab= xxx
Attempts to set the IAB tuple of inheritable capability vectors.
The text conventions used for \fIxxx\fP are those of
@@ -292,6 +328,18 @@ Removes the specified ambient capability from the running process.
.TP
.B \-\-noamb
Drops all ambient capabilities from the running process.
+.TP
+.B \-\-noenv
+Suppresses overriding of the HOME and USER environment variables when
+a subsequent
+.B \-\-user
+argument is processed.
+.TP
+.B \-\-quiet
+This argument is ignored unless it is the first one. If present, it
+suppresses the capsh runtime check to confirm the running libcap is
+recent enough that it can name all of the kernel supported capability
+values.
.SH "EXIT STATUS"
Following successful execution,
.B capsh
@@ -304,12 +352,14 @@ Written by Andrew G. Morgan <morgan@kernel.org>.
.SH "REPORTING BUGS"
Please report 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 getcap (8),
-.BR setcap (8),
.BR cap_from_text (3),
.BR cap_iab (3)
+.BR capabilities (7),
+.BR captree (8),
+.BR getcap (8),
+.BR getpcaps (8),
and
-.BR capabilities (7).
+.BR setcap (8).
diff --git a/doc/captree.8 b/doc/captree.8
new file mode 100644
index 0000000..86a7de3
--- /dev/null
+++ b/doc/captree.8
@@ -0,0 +1,75 @@
+.\" Hey, EMACS: -*- nroff -*-
+.TH CAPTREE 8 "2022-04-11"
+.\" Please adjust this date whenever revising the manpage.
+.SH NAME
+captree \- display tree of process capabilities
+.SH SYNOPSIS
+.BR captree " [OPTIONS] "
+.RI [( pid | glob-name ") ...]"
+.SH DESCRIPTION
+.B captree
+displays the capabilities on the mentioned processes indicated by
+.IR pid " or " glob-name
+value(s) given on the command line. If no
+.I pid
+etc values are supplied,
+.IR pid =1
+is implied. A
+.I pid
+value of 0 displays all the processes known to the kernel.
+.PP
+The POSIX.1e capabilities are displayed in double quotes in the
+.BR cap_from_text (3)
+format. The IAB tuple of capabilities is displayed between square
+brackets in the text format described in
+.BR cap_iab (3).
+Note, the IAB tuple text is omitted if it contains empty A and B
+components. This is because the regular POSIX.1e text contains
+information about the Inheritable flag already. This behavior can be
+overridden with the
+.B --verbose
+command line argument.
+.PP
+Optional arguments (which must precede the list of pid|glob-name
+values):
+.TP
+.B \-\-help
+Displays usage information and exits. Note, modern Go runtimes exit
+with status 0 in this case, but older runtimes exit with status 2.
+.TP
+.BR \-\-verbose
+Displays capability sets and IAB tuples even when they are empty, or
+redundant.
+.TP
+.BI \-\-depth =n
+Displays the process tree to a depth of
+.IR n .
+Note, the default value for this parameter is 0, which implies
+infinite depth.
+.TP
+.BI \-\-colo[u]r =false
+Colo[u]rs the targeted PIDs, if stdout is a TTY, in red. This option
+defaults to true when running via a TTY. The \fB--color\fI=false\fR
+argument will suppress this color. Piping the output into some other
+program will also suppress the use of colo[u]r.
+.SH EXIT STATUS
+If the supplied target cannot be found the exit status is 1. Should an
+unrecognized option be provided, the exit status is 2. Otherwise,
+.B captree
+exits with status 0.
+.SH REPORTING BUGS
+Please report bugs via:
+.TP
+https://bugzilla.kernel.org/buglist.cgi?component=libcap&list_id=1090757
+.SH SEE ALSO
+.BR cap_from_text(3),
+.BR capabilities (7),
+and
+.BR cap_iab (3).
+
+There is a longer article about \fBcaptree\fP, which includes some
+examples, here:
+
+ https://sites.google.com/site/fullycapable/captree
+.SH AUTHOR
+Andrew G. Morgan <morgan@kernel.org>
diff --git a/doc/crosslink.sh b/doc/crosslink.sh
new file mode 100755
index 0000000..d701522
--- /dev/null
+++ b/doc/crosslink.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+#
+# So many cross links to maintain. Here is a script that I've used to
+# validate things at least conform to some structure:
+#
+for x in *.? ; do
+ y=$(grep -F '.so m' ${x} | awk '{print $2}' | sed -e 's/man..//')
+ if [ -z "${y}" ]; then
+ continue
+ fi
+ echo
+ echo "###########"
+ echo "${x} => ${y}"
+ grep -F "${x%.*}" "${y}"
+done
diff --git a/doc/getcap.8 b/doc/getcap.8
index 04b601c..8b6d201 100644
--- a/doc/getcap.8
+++ b/doc/getcap.8
@@ -1,5 +1,5 @@
-.\" written by Andrew Main <zefram@dcs.warwick.ac.uk>
-.TH GETCAP 8 "2020-01-07"
+.\" originally written by Andrew Main <zefram@dcs.warwick.ac.uk>
+.TH GETCAP 8 "2021-08-29"
.SH NAME
getcap \- examine file capabilities
.SH SYNOPSIS
@@ -28,10 +28,14 @@ One file per line.
.SH "REPORTING BUGS"
Please report 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 capsh (1),
.BR cap_get_file (3),
.BR cap_to_text (3),
.BR capabilities (7),
.BR user_namespaces (7),
-.BR setcap (8)
+.BR captree (8),
+.BR getpcaps (8)
+and
+.BR setcap (8).
diff --git a/doc/getpcaps.8 b/doc/getpcaps.8
index d519357..1c59ddc 100644
--- a/doc/getpcaps.8
+++ b/doc/getpcaps.8
@@ -1,5 +1,5 @@
.\" Hey, EMACS: -*- nroff -*-
-.TH GETPCAPS 8 "2020-01-04"
+.TH GETPCAPS 8 "2020-08-29"
.\" Please adjust this date whenever revising the manpage.
.SH NAME
getpcaps \- display process capabilities
@@ -33,11 +33,25 @@ Displays output in a somewhat ugly legacy format.
.B \-\-verbose
Displays usage in a legacy-like format but not quite so ugly in modern
default terminal fonts.
+.TP
+.B \-\-iab
+Displays IAB tuple capabilities from the process. The output format
+here is the text format described in \fBcap_iab\fR(3). Double
+quotes encase the regular process capabilities and square brackets
+encase the IAB tuple. This format is also used by \fBcaptree\fR(8).
+.SH "REPORTING BUGS"
+Please report bugs via:
+.TP
+https://bugzilla.kernel.org/buglist.cgi?component=libcap&list_id=1090757
.SH SEE ALSO
.BR capsh (1),
+.BR cap_from_text (3),
+.BR cap_iab (3),
.BR capabilities (7),
+.BR captree (8),
.BR getcap (8),
-.BR setcap (8)
+and
+.BR setcap (8).
.SH AUTHOR
This manual page was originally written by Robert Bihlmeyer
<robbe@debian.org>, for the Debian GNU/Linux system (but may be used
diff --git a/doc/libcap.3 b/doc/libcap.3
index b8c8520..a91cf7e 100644
--- a/doc/libcap.3
+++ b/doc/libcap.3
@@ -1,15 +1,21 @@
-.TH LIBCAP 3 "2021-03-06" "" "Linux Programmer's Manual"
+.TH LIBCAP 3 "2022-10-16" "" "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, \
-cap_get_flag, cap_get_pid, cap_get_proc, cap_set_fd, cap_set_file, \
-cap_set_flag, cap_set_proc, cap_size, cap_to_name, cap_to_text, \
-cap_get_pid, cap_dup \- capability data object manipulation
+cap_drop_bound, cap_dup, cap_fill, cap_fill_flag, cap_free, cap_from_name, \
+cap_from_text, cap_get_ambient, cap_get_bound, cap_get_fd, \
+cap_get_file, cap_get_flag, cap_get_mode, cap_get_nsowner, cap_get_pid, \
+cap_get_pid, cap_get_proc, cap_get_secbits, cap_init, cap_max_bits, \
+cap_prctl, cap_prctlw, cap_proc_root, cap_reset_ambient, \
+cap_set_ambient, cap_set_fd, cap_set_file, cap_set_flag, cap_setgroups, \
+cap_set_mode, cap_set_nsowner, cap_set_proc, cap_set_secbits, \
+cap_setuid, cap_size, cap_to_name, cap_to_text \- capability data object manipulation
.SH SYNOPSIS
.nf
#include <sys/capability.h>
int cap_clear(cap_t cap_p);
+int cap_fill(cap_t cap_p, cap_flag_t to, cap_flag_t from);
+int cap_fill_flag(cap_t cap_p, cap_flag_t to, const cap_t ref, cap_flag_t from);
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);
@@ -21,6 +27,7 @@ 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);
+cap_value_t cap_max_bits();
#include <sys/types.h>
@@ -35,18 +42,42 @@ 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_init();
cap_t cap_dup(cap_t cap_p);
+
+char *cap_proc_root(const char *root);
+int cap_get_nsowner(cap_t cap_p);
+int cap_set_nsowner(cap_t cap_p, uid_t rootuid);
+int cap_get_bound(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);
+int cap_set_mode(cap_mode_t flavor);
+cap_mode_t cap_get_mode(void);
+const char *cap_mode_name(cap_mode_t flavor);
+unsigned cap_get_secbits();
+int cap_set_secbits(unsigned bits);
+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_setuid(uid_t uid);
+int cap_setgroups(gid_t gid, size_t ngroups, const gid_t groups[]);
.fi
.sp
Link with \fI\-lcap\fP.
.fi
.SH DESCRIPTION
-These functions work on a capability state held in working storage.
+These primary functions work on a capability state held in working
+storage and attempt to complete the POSIX.1e (draft) user space API
+for Capability based privilege.
+.PP
A
.I cap_t
holds information about the capabilities in each of the three sets,
-Permitted, Inheritable, and Effective.
-Each capability in a set may be clear (disabled, 0) or set (enabled, 1).
+Permitted, Inheritable, and Effective. Each capability in a set may
+be clear (disabled, 0) or set (enabled, 1).
.PP
These functions work with the following data types:
.TP 18
@@ -79,10 +110,65 @@ is set appropriately.
These functions are as per the withdrawn POSIX.1e draft specification.
The following functions are Linux extensions:
.BR cap_clear_flag (),
+.BR cap_drop_bound (),
+.BR cap_fill (),
+.BR cap_fill_flag (),
.BR cap_from_name (),
-.BR cap_to_name (),
+.BR cap_get_ambient (),
+.BR cap_get_bound (),
+.BR cap_get_mode (),
+.BR cap_get_nsowner (),
+.BR cap_get_secbits (),
+.BR cap_mode_name (),
+.BR cap_proc_root (),
+.BR cap_prctl (),
+.BR cap_prctlw (),
+.BR cap_reset_ambient (),
+.BR cap_setgroups (),
+.BR cap_setuid (),
+.BR cap_set_ambient (),
+.BR cap_set_mode (),
+.BR cap_set_nsowner (),
+.BR cap_set_secbits (),
+.BR cap_to_name ()
and
.BR cap_compare ().
+.PP
+A Linux, \fIIAB\fP, extension of Inheritable, Bounding and Ambient
+tuple capability vectors are also supported by \fBlibcap\fP. Those
+functions are described in a companion man page:
+.BR cap_iab (3).
+Further, for managing the complexity of launching a sub-process,
+\fBlibcap\fP supports the abstraction:
+.BR cap_launch (3).
+.PP
+In addition to the \fBcap_\fP prefixed \fBlibcap\fP API, the library
+also provides prototypes for the Linux system calls that provide the
+native API for process capabilities. These prototypes are:
+.sp
+.nf
+int capget(cap_user_header_t header, cap_user_data_t data);
+int capset(cap_user_header_t header, const cap_user_data_t data);
+.fi
+.sp
+Further, \fBlibcap\fP provides a set-up function,
+.sp
+.nf
+void cap_set_syscall(
+ long int (*new_syscall)(long int, long int, long int, long int),
+ long int (*new_syscall6)(long int,
+ long int, long int, long int,
+ long int, long int, long int));
+.fi
+.sp
+which can be used to redirect its use of the
+.BR capset ()
+and other system calls that write kernel managed state. This is
+especially useful when supporting POSIX semantics for security
+state. When a program is linked against
+.BR libpsx (3)
+as described in that man page, this function is used to connect
+\fBlibcap\fP to POSIX semantics system calls.
.SH "REPORTING BUGS"
The
.B libcap
@@ -98,9 +184,15 @@ https://bugzilla.kernel.org/buglist.cgi?component=libcap&list_id=1090757
.BR cap_from_text (3),
.BR cap_get_file (3),
.BR cap_get_proc (3),
+.BR cap_iab (3),
.BR cap_init (3),
+.BR cap_launch (3),
.BR capabilities (7),
.BR getpid (2),
-.BR capsh (1)
+.BR capsh (1),
+.BR captree (8),
+.BR getcap (8),
+.BR getpcaps (8),
+.BR setcap (8)
and
.BR libpsx (3).
diff --git a/doc/libpsx.3 b/doc/libpsx.3
index 4ba306b..4a0b5b6 100644
--- a/doc/libpsx.3
+++ b/doc/libpsx.3
@@ -1,12 +1,21 @@
-.TH LIBPSX 3 "2021-03-06" "" "Linux Programmer's Manual"
+.TH LIBPSX 3 "2021-12-12" "" "Linux Programmer's Manual"
.SH NAME
-psx_syscall3, psx_syscall6 \- POSIX semantics for system calls
+psx_syscall3, psx_syscall6, psx_set_sensitivity \- POSIX semantics for system calls
.SH SYNOPSIS
.nf
#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);
+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);
+int psx_set_sensitivity(psx_sensitivity_t sensitivity);
+void psx_load_syscalls(long int (**syscall_fn)(long int,
+ long int, long int, long int),
+ long int (**syscall6_fn)(long int,
+ long int, long int, long int,
+ long int, long int, long int));
.fi
.sp
Link with one of these:
@@ -21,7 +30,7 @@ library attempts to fill a gap left by the
.BR pthreads (7)
implementation on Linux. To be compliant POSIX threads, via the
.BR nptl "(7) " setxid
-mechanism glibc maintains consistent UID and GID credentials amongst
+mechanism, glibc maintains consistent UID and GID credentials amongst
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,
@@ -34,10 +43,12 @@ 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
-inserts itself in the SIGSYS handler stack. It goes to great length to
-be the first such handler but acts as a pass-through for other SIGSYS
+.BR SIGRTMIN "), " libpsx
+inserts itself in the
+.B SIGSYS
+handler stack. It goes to great length to be the first such handler
+but acts as a pass-through for other
+.B SIGSYS
uses.
.PP
A linker trick of
@@ -51,7 +62,9 @@ can keep track of all pthreads.
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
+of the caller. The macrology (which ultimately invokes
+.BR __psx_syscall ())
+pads out the call to actually use
.BR psx_syscall3 ()
or
.BR psx_syscall6 ()
@@ -62,6 +75,32 @@ larger. You are encouraged to use the more explicit
and
.BR psx_syscall6 ()
functions as needed.
+.PP
+.BR psx_set_sensitivity ()
+changes the behavior of the mirrored system calls:
+.B PSX_IGNORE
+ensures that differences are ignored (the default behavior);
+.B PSX_WARNING
+prints a stderr notification about how the results differ; and
+.B PSX_ERROR
+prints the error details and generates a
+.B SIGSYS
+signal.
+.PP
+.BR psx_load_syscalls ()
+can be used to set caller defined function pointers for invoking 3 and
+6 argument syscalls. This function can be used to configure a library,
+or program to change behavior when linked against
+.BR libpsx .
+Indeed,
+.B libcap
+uses this function from
+.B libpsx
+to override its thread scoped default system call based API. When
+linked with
+.BR libpsx ", " libcap
+can operate on all the threads of a multithreaded program to operate
+with POSIX semantics.
.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
diff --git a/doc/mkmd.sh b/doc/mkmd.sh
index af835d5..ce8baa2 100755
--- a/doc/mkmd.sh
+++ b/doc/mkmd.sh
@@ -32,6 +32,8 @@ function do_page () {
sect="${m#*.}"
output="${base}-${sect}.md"
+ echo "converting ${m}" 1>&2
+
redir="$(grep '^.so man' "${m}")"
if [[ $? -eq 0 ]]; then
r="${redir#*/}"
@@ -48,6 +50,14 @@ function do_page () {
cat > "${index}" <<EOF
# Manpages for libcap and libpsx
+EOF
+
+if [[ -f "local-md.preamble" ]]; then
+ cat "local-md.preamble" >> "${index}"
+fi
+
+cat >> "${index}" <<EOF
+
## Individual reference pages
EOF
@@ -67,6 +77,14 @@ cat >> "${index}" <<EOF
## More information
+EOF
+
+if [[ -f "local-md.postscript" ]]; then
+ cat "local-md.postscript" >> "${index}"
+fi
+
+cat >> "${index}" <<EOF
+
For further information, see the
[FullyCapable](https://sites.google.com/site/fullycapable/) homepage
for libcap.
diff --git a/doc/psx_load_syscalls.3 b/doc/psx_load_syscalls.3
new file mode 100644
index 0000000..663420c
--- /dev/null
+++ b/doc/psx_load_syscalls.3
@@ -0,0 +1 @@
+.so man3/libpsx.3
diff --git a/doc/psx_set_sensitivity.3 b/doc/psx_set_sensitivity.3
new file mode 100644
index 0000000..663420c
--- /dev/null
+++ b/doc/psx_set_sensitivity.3
@@ -0,0 +1 @@
+.so man3/libpsx.3
diff --git a/doc/setcap.8 b/doc/setcap.8
index 463752d..d652076 100644
--- a/doc/setcap.8
+++ b/doc/setcap.8
@@ -1,4 +1,4 @@
-.TH SETCAP 8 "2020-01-07"
+.TH SETCAP 8 "2020-08-29"
.SH NAME
setcap \- set file capabilities
.SH SYNOPSIS
@@ -54,10 +54,14 @@ exit code is 1.
.SH "REPORTING BUGS"
Please report 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 capsh (1),
.BR cap_from_text (3),
.BR cap_get_file (3),
.BR capabilities (7),
.BR user_namespaces (7),
+.BR captree (8),
.BR getcap (8)
+and
+.BR getpcaps (8).
diff --git a/doc/values/3.txt b/doc/values/3.txt
index 8a605c2..2d68efd 100644
--- a/doc/values/3.txt
+++ b/doc/values/3.txt
@@ -2,3 +2,7 @@ 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.
+
+This capability permits the deletion of a file owned
+by another UID in a directory protected by the sticky
+(t) bit.
diff --git a/doc/values/8.txt b/doc/values/8.txt
index d7654f0..de0b47c 100644
--- a/doc/values/8.txt
+++ b/doc/values/8.txt
@@ -1,19 +1,24 @@
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.
+capabilities.
-[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.]
+Linux supports the POSIX.1e Inheritable set, the POXIX.1e (X
+vector) known in Linux as the Bounding vector, as well as
+the Linux extension Ambient vector.
+
+This capability permits dropping bits from the Bounding
+vector (ie. raising B bits in the libcap IAB
+representation). 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, Effective bits beyond those
+already present in the process' permitted set, or
+Inheritable bits beyond those present in the Bounding
+vector.
+
+[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.]
diff --git a/go/.gitignore b/go/.gitignore
index 7b811c9..96fe4c0 100644
--- a/go/.gitignore
+++ b/go/.gitignore
@@ -2,13 +2,21 @@ good-names.go
compare-cap
try-launching
try-launching-cgo
+psx-fd
+psx-fd-cgo
psx-signals
psx-signals-cgo
b210613
+b215283
+b215283-cgo
+mismatch
+mismatch-cgo
mknames
web
setid
gowns
+captree
+captrace
ok
vendor
go.sum
diff --git a/go/Makefile b/go/Makefile
index 6b69cbe..38c1cf3 100644
--- a/go/Makefile
+++ b/go/Makefile
@@ -14,17 +14,20 @@ IMPORTDIR=kernel.org/pub/linux/libs/security/libcap
PKGDIR=pkg/$(GOOSARCH)/$(IMPORTDIR)
DEPS=../libcap/libcap.a ../libcap/libpsx.a
+TESTS=compare-cap try-launching psx-signals mismatch
-all: PSXGOPACKAGE CAPGOPACKAGE web setid gowns compare-cap try-launching psx-signals
+all: PSXGOPACKAGE CAPGOPACKAGE web setid gowns captree captrace
$(DEPS):
- make -C ../libcap all
+ $(MAKE) -C ../libcap all
../progs/tcapsh-static:
- make -C ../progs tcapsh-static
+ $(MAKE) -C ../progs tcapsh-static
-vendor/$(IMPORTDIR) vendor/modules.txt:
+vendor/$(IMPORTDIR):
mkdir -p "vendor/$(IMPORTDIR)"
+
+vendor/modules.txt: 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
@@ -39,7 +42,7 @@ vendor/$(IMPORTDIR)/cap: vendor/modules.txt
touch ../cap
$(topdir)/libcap/cap_names.h:
- make -C $(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 $@
@@ -54,81 +57,137 @@ CAPGOPACKAGE: vendor/$(IMPORTDIR)/cap ../cap/*.go good-names.go $(PSXGOPACKAGE)
# 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
- CC="$(CC)" $(CGO_LDFLAGS_ALLOW) CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -mod=vendor $<
+ CC="$(CC)" CGO_ENABLED="1" $(CGO_LDFLAGS_ALLOW) CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build $(GO_BUILD_FLAGS) -mod=vendor $<
web: ../goapps/web/web.go CAPGOPACKAGE
- CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) build -mod=vendor -o $@ $<
+ CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) build $(GO_BUILD_FLAGS) -mod=vendor -o $@ $<
ifeq ($(RAISE_GO_FILECAP),yes)
- make -C ../progs setcap
- sudo ../progs/setcap cap_setpcap,cap_net_bind_service=p web
+ $(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
- CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) build -mod=vendor -o $@ $<
+ CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) build $(GO_BUILD_FLAGS) -mod=vendor -o $@ $<
gowns: ../goapps/gowns/gowns.go CAPGOPACKAGE
- CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) build -mod=vendor -o $@ $<
+ CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) build $(GO_BUILD_FLAGS) -mod=vendor -o $@ $<
+
+captree: ../goapps/captree/captree.go CAPGOPACKAGE
+ CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) build $(GO_BUILD_FLAGS) -mod=vendor -o $@ $<
+
+captrace: ../goapps/captrace/captrace.go CAPGOPACKAGE
+ CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) build $(GO_BUILD_FLAGS) -mod=vendor -o $@ $<
-ok: ok.go
- CC="$(CC)" CGO_ENABLED=0 $(GO) build -mod=vendor $<
+ok: ok.go vendor/modules.txt
+ CC="$(CC)" CGO_ENABLED="0" $(GO) build $(GO_BUILD_FLAGS) -mod=vendor $<
try-launching: try-launching.go CAPGOPACKAGE ok
- CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) build -mod=vendor $<
+ CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) build $(GO_BUILD_FLAGS) -mod=vendor $<
+ifeq ($(CGO_REQUIRED),0)
+ CC="$(CC)" CGO_ENABLED="1" $(CGO_LDFLAGS_ALLOW) $(GO) build $(GO_BUILD_FLAGS) -mod=vendor -o $@-cgo $<
+endif
+
+# This is a test case developed from the deadlock investigation,
+# https://github.com/golang/go/issues/50113 . Note the psx-fd.go code
+# works when compiled CGO_ENABLED=1, but deadlocks when compiled
+# CGO_ENABLED=0. This is true for go1.16 and go1.17. The go1.18
+# release fixed this by rewriting the AllThreadsSyscall support, but
+# the large change was not backported. (See noted bug for a much
+# smaller patch for this issue on those older releases.)
+psx-fd: psx-fd.go PSXGOPACKAGE
+ CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) build $(GO_BUILD_FLAGS) -mod=vendor -o $@ $<
+
ifeq ($(CGO_REQUIRED),0)
- CC="$(CC)" CGO_ENABLED="1" $(CGO_LDFLAGS_ALLOW) $(GO) build -mod=vendor -o $@-cgo $<
+psx-fd-cgo: psx-fd.go PSXGOPACKAGE
+ CC="$(CC)" CGO_ENABLED="1" $(CGO_LDFLAGS_ALLOW) $(GO) build $(GO_BUILD_FLAGS) -mod=vendor -o $@ $<
endif
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 $<
+ CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build $(GO_BUILD_FLAGS) -mod=vendor $<
ifeq ($(CGO_REQUIRED),0)
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 $@ $<
+ CC="$(CC)" CGO_ENABLED="1" $(CGO_LDFLAGS_ALLOW) CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build $(GO_BUILD_FLAGS) -mod=vendor -o $@ $<
endif
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 $<
+ CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build $(GO_BUILD_FLAGS) -mod=vendor $<
+
+b215283: b215283.go CAPGOPACKAGE
+ CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build $(GO_BUILD_FLAGS) -mod=vendor $<
+
+ifeq ($(CGO_REQUIRED),0)
+b215283-cgo: b215283.go CAPGOPACKAGE
+ CC="$(CC)" CGO_ENABLED="1" $(CGO_LDFLAGS_ALLOW) CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build $(GO_BUILD_FLAGS) -mod=vendor -o $@ $<
+endif
+
+mismatch: mismatch.go PSXGOPACKAGE
+ CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build $(GO_BUILD_FLAGS) -mod=vendor $<
+
+ifeq ($(CGO_REQUIRED),0)
+mismatch-cgo: mismatch.go CAPGOPACKAGE
+ CC="$(CC)" CGO_ENABLED="1" $(CGO_LDFLAGS_ALLOW) CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build $(GO_BUILD_FLAGS) -mod=vendor -o $@ $<
+endif
-test: all
+test: setid gowns captree psx-fd $(TESTS)
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
+ ./mismatch || exit 0 ; exit 1
+ timeout 5 ./psx-fd || echo "this is a known Go bug"
ifeq ($(CGO_REQUIRED),0)
- $(MAKE) psx-signals-cgo
+ $(MAKE) psx-signals-cgo mismatch-cgo psx-fd-cgo
./psx-signals-cgo
+ ./mismatch-cgo || exit 0 ; exit 1
+ ./psx-fd-cgo
endif
./setid --caps=false
./gowns -- -c "echo gowns runs"
+ ./captree 0
# Note, the user namespace doesn't require sudo, but I wanted to avoid
# requiring that the hosting kernel supports user namespaces for the
# regular test case.
-sudotest: test ../progs/tcapsh-static b210613
- ./gowns --ns -- -c "echo gowns runs with user namespace"
+sudotest: test ../progs/tcapsh-static b210613 b215283
+ ../progs/tcapsh-static --has-b=cap_sys_admin || exit 0 && ./gowns --ns -- -c "echo gowns runs with user namespace"
./try-launching
ifeq ($(CGO_REQUIRED),0)
./try-launching-cgo
endif
- sudo ./try-launching
+ $(SUDO) ./try-launching
ifeq ($(CGO_REQUIRED),0)
- sudo ./try-launching-cgo
+ $(SUDO) ./try-launching-cgo
endif
- sudo ../progs/tcapsh-static --cap-uid=$$(id -u) --caps="cap_setpcap=ep" --iab="^cap_setpcap" -- -c ./b210613
+ $(SUDO) ../progs/tcapsh-static --cap-uid=$$(id -u) --caps="cap_setpcap=ep" --iab="^cap_setpcap" -- -c ./b210613
+ $(SUDO) ./b215283
+ifeq ($(CGO_REQUIRED),0)
+ $(MAKE) b215283-cgo
+ $(SUDO) ./b215283-cgo
+endif
+
+# As of libcap-2.55 We stopped installing the cap and psx packages as
+# part of the install. Most distribution's packagers skip the Go
+# builds, so it was not well used any way. The new hotness is to just
+# use Go modules and download the packages from a tagged release in
+# the git repository. For an example of how to do this from scratch:
+#
+# https://sites.google.com/site/fullycapable/getting-started-with-go/building-go-programs-that-manipulate-capabilities
+#
+# For those brave souls that do include the Go build (testing) as part
+# of their packaging, we reward them with a copy of the captree
+# utility!
install: all
- rm -rf $(FAKEROOT)$(GOPKGDIR)/$(IMPORTDIR)/psx
- mkdir -p $(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 vendor/$(IMPORTDIR)/cap/* $(FAKEROOT)$(GOPKGDIR)/$(IMPORTDIR)/cap
+ mkdir -p -m 0755 $(FAKEROOT)$(SBINDIR)
+ install -m 0755 captree $(FAKEROOT)$(SBINDIR)
clean:
rm -f *.o *.so *~ mknames ok good-names.go
- rm -f web setid gowns
+ rm -f web setid gowns captree captrace
rm -f compare-cap try-launching try-launching-cgo
rm -f $(topdir)/cap/*~ $(topdir)/psx/*~
- rm -f b210613 psx-signals psx-signals-cgo
+ rm -f b210613 b215283 b215283-cgo psx-signals psx-signals-cgo
+ rm -f mismatch mismatch-cgo psx-fd psx-fd-cgo
rm -fr vendor CAPGOPACKAGE PSXGOPACKAGE go.sum
diff --git a/go/b215283.go b/go/b215283.go
new file mode 100644
index 0000000..26596b6
--- /dev/null
+++ b/go/b215283.go
@@ -0,0 +1,47 @@
+// Program b215283 requires privilege to execute and is a minimally adapted
+// version of a test case provided by Lorenz Bauer as a reproducer for a
+// problem he found and reported in:
+//
+// https://bugzilla.kernel.org/show_bug.cgi?id=215283
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "kernel.org/pub/linux/libs/security/libcap/cap"
+)
+
+func main() {
+ const secbits = cap.SecbitNoRoot | cap.SecbitNoSetUIDFixup
+
+ if v, err := cap.GetProc().GetFlag(cap.Permitted, cap.SETPCAP); err != nil {
+ panic(fmt.Sprintf("failed to get flag value: %v", err))
+ os.Exit(1)
+ } else if !v {
+ fmt.Printf("test requires cap_setpcap: found %q\n", cap.GetProc())
+ os.Exit(1)
+ }
+ if bits := cap.GetSecbits(); bits != 0 {
+ fmt.Printf("test expects secbits=0 to run; found: 0%o\n", bits)
+ os.Exit(1)
+ }
+
+ fmt.Println("secbits:", cap.GetSecbits(), " caps:", cap.GetProc())
+
+ l := cap.FuncLauncher(func(interface{}) error {
+ return cap.NewSet().SetProc()
+ })
+
+ if _, err := l.Launch(nil); err != nil {
+ fmt.Printf("launch failed: %v\n", err)
+ os.Exit(1)
+ }
+
+ fmt.Println("secbits:", cap.GetSecbits(), " caps:", cap.GetProc())
+
+ if err := secbits.Set(); err != nil {
+ fmt.Printf("set securebits: %v", err.Error())
+ os.Exit(1)
+ }
+}
diff --git a/go/compare-cap.go b/go/compare-cap.go
index f2a7d6b..064d5fa 100644
--- a/go/compare-cap.go
+++ b/go/compare-cap.go
@@ -116,16 +116,18 @@ func tryFileCaps() {
if err := want.SetFd(f); err != nil {
log.Fatalf("failed to fset file capability: %v", err)
}
- if err := saved.SetProc(); err != nil {
- log.Fatalf("failed to lower effective capability: %v", err)
- }
- // End of critical section.
-
if got, err := cap.GetFd(f); err != nil {
log.Fatalf("failed to fread caps: %v", err)
} else if is, was := got.String(), want.String(); is != was {
log.Fatalf("fread file caps do not match desired: got=%q want=%q", is, was)
}
+ if err := empty.SetFd(f); err != nil && err != syscall.ENODATA {
+ log.Fatalf("blocked from cleanup fremoving filecaps: %v", err)
+ }
+ if err := saved.SetProc(); err != nil {
+ log.Fatalf("failed to lower effective capability: %v", err)
+ }
+ // End of critical section.
}
// tryProcCaps performs a set of convenience functions and compares
@@ -158,8 +160,8 @@ func tryProcCaps() {
log.Fatalf("wrong of groups: got=%v want=[100 l01]", gs)
}
- if mode := cap.GetMode(); mode != cap.ModeUncertain {
- log.Fatalf("initial mode should be 0 (UNCERTAIN), got: %d (%v)", mode, mode)
+ if mode := cap.GetMode(); mode != cap.ModeHybrid {
+ log.Fatalf("initial mode should be 4 (HYBRID), got: %d (%v)", mode, mode)
}
// To distinguish PURE1E and PURE1E_INIT we need an inheritable capability set.
diff --git a/go/go.mod b/go/go.mod
index 4c49252..378b218 100644
--- a/go/go.mod
+++ b/go/go.mod
@@ -3,6 +3,6 @@ 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
+ kernel.org/pub/linux/libs/security/libcap/cap v1.2.69
+ kernel.org/pub/linux/libs/security/libcap/psx v1.2.69
)
diff --git a/go/mismatch.go b/go/mismatch.go
new file mode 100644
index 0000000..bbcf6eb
--- /dev/null
+++ b/go/mismatch.go
@@ -0,0 +1,15 @@
+// Program mismatch should panic because the syscall being requested
+// never returns consistent results.
+package main
+
+import (
+ "fmt"
+ "syscall"
+
+ "kernel.org/pub/linux/libs/security/libcap/psx"
+)
+
+func main() {
+ tid, _, err := psx.Syscall3(syscall.SYS_GETTID, 0, 0, 0)
+ fmt.Printf("gettid() -> %d: %v\n", tid, err)
+}
diff --git a/go/mknames.go b/go/mknames.go
index ff07218..ef348ae 100644
--- a/go/mknames.go
+++ b/go/mknames.go
@@ -52,8 +52,8 @@ func main() {
/* ** DO NOT EDIT THIS FILE. IT WAS AUTO-GENERATED BY LIBCAP'S GO BUILDER (mknames.go) ** */
-// NamedCount holds the number of capability values with official
-// names known at the time this libcap/cap version, was released. The
+// NamedCount holds the number of capability values, with official
+// names, known at the time this libcap/cap version was released. The
// "../libcap/cap" package is fully able to manipulate higher numbered
// capability values by numerical value. However, if you find
// cap.NamedCount < cap.MaxBits(), it is probably time to upgrade this
diff --git a/go/psx-fd.go b/go/psx-fd.go
new file mode 100644
index 0000000..7aa3a76
--- /dev/null
+++ b/go/psx-fd.go
@@ -0,0 +1,25 @@
+package main
+
+import (
+ "log"
+ "os"
+ "syscall"
+ "time"
+
+ "kernel.org/pub/linux/libs/security/libcap/psx"
+)
+
+const prSetKeepCaps = 8
+
+func main() {
+ r, w, err := os.Pipe()
+ if err != nil {
+ log.Fatalf("failed to obtain pipe: %v", err)
+ }
+ data := make([]byte, 2+r.Fd())
+ go r.Read(data)
+ time.Sleep(500 * time.Millisecond)
+ psx.Syscall3(syscall.SYS_PRCTL, prSetKeepCaps, 1, 0)
+ w.Close()
+ r.Close()
+}
diff --git a/go/try-launching.go b/go/try-launching.go
index 9f20e6b..b09b254 100644
--- a/go/try-launching.go
+++ b/go/try-launching.go
@@ -20,6 +20,8 @@ func tryLaunching() {
}
root := cwd[:strings.LastIndex(cwd, "/")]
+ hasSysAdmin, _ := cap.GetBound(cap.SYS_ADMIN)
+
vs := []struct {
args []string
fail bool
@@ -38,7 +40,7 @@ func tryLaunching() {
uid: 123,
gid: 456,
groups: []int{1, 2, 3},
- fail: syscall.Getuid() != 0,
+ fail: syscall.Getuid() != 0 || !hasSysAdmin,
},
{
args: []string{"/ok"},
diff --git a/goapps/captrace/captrace.go b/goapps/captrace/captrace.go
new file mode 100644
index 0000000..1ef1ace
--- /dev/null
+++ b/goapps/captrace/captrace.go
@@ -0,0 +1,230 @@
+// Program captrace traces processes and notices when they attempt
+// kernel actions that require Effective capabilities.
+//
+// The reference material for developing this tool was the the book
+// "Linux Observabililty with BPF" by David Calavera and Lorenzo
+// Fontana.
+package main
+
+import (
+ "bufio"
+ "flag"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "os/exec"
+ "strconv"
+ "strings"
+ "sync"
+ "syscall"
+ "time"
+
+ "kernel.org/pub/linux/libs/security/libcap/cap"
+)
+
+var (
+ bpftrace = flag.String("bpftrace", "bpftrace", "command to launch bpftrace")
+ debug = flag.Bool("debug", false, "more output")
+ pid = flag.Int("pid", -1, "PID of target process to trace (-1 = trace all)")
+)
+
+type thread struct {
+ PPID, Datum int
+ Value cap.Value
+ Token string
+}
+
+// mu protects these two maps.
+var mu sync.Mutex
+
+// tids tracks which PIDs we are following.
+var tids = make(map[int]int)
+
+// cache tracks in-flight cap_capable invocations.
+var cache = make(map[int]*thread)
+
+// event adds or resolves a capability event.
+func event(add bool, tid int, th *thread) {
+ mu.Lock()
+ defer mu.Unlock()
+
+ if len(tids) != 0 {
+ if _, ok := tids[th.PPID]; !ok {
+ if *debug {
+ log.Printf("dropped %d %d %v event", th.PPID, tid, *th)
+ }
+ return
+ }
+ tids[tid] = th.PPID
+ tids[th.PPID] = th.PPID
+ }
+
+ if add {
+ cache[tid] = th
+ } else {
+ if b, ok := cache[tid]; ok {
+ detail := ""
+ if th.Datum < 0 {
+ detail = fmt.Sprintf(" (%v)", syscall.Errno(-th.Datum))
+ }
+ task := ""
+ if th.PPID != tid {
+ task = fmt.Sprintf("+{%d}", tid)
+ }
+ log.Printf("%-16s %d%s opt=%d %q -> %d%s", b.Token, b.PPID, task, b.Datum, b.Value, th.Datum, detail)
+ }
+ delete(cache, tid)
+ }
+}
+
+// tailTrace tails the bpftrace command output recognizing lines of
+// interest.
+func tailTrace(cmd *exec.Cmd, out io.Reader) {
+ launched := false
+ sc := bufio.NewScanner(out)
+ for sc.Scan() {
+ fields := strings.Split(sc.Text(), " ")
+ if len(fields) < 4 {
+ continue // ignore
+ }
+ if !launched {
+ launched = true
+ mu.Unlock()
+ }
+ switch fields[0] {
+ case "CB":
+ if len(fields) < 6 {
+ continue
+ }
+ pid, err := strconv.Atoi(fields[1])
+ if err != nil {
+ continue
+ }
+ th := &thread{
+ PPID: pid,
+ }
+ tid, err := strconv.Atoi(fields[2])
+ if err != nil {
+ continue
+ }
+ c, err := strconv.Atoi(fields[3])
+ if err != nil {
+ continue
+ }
+ th.Value = cap.Value(c)
+ aud, err := strconv.Atoi(fields[4])
+ if err != nil {
+ continue
+ }
+ th.Datum = aud
+ th.Token = strings.Join(fields[5:], " ")
+ event(true, tid, th)
+ case "CE":
+ if len(fields) < 4 {
+ continue
+ }
+ pid, err := strconv.Atoi(fields[1])
+ if err != nil {
+ continue
+ }
+ th := &thread{
+ PPID: pid,
+ }
+ tid, err := strconv.Atoi(fields[2])
+ if err != nil {
+ continue
+ }
+ aud, err := strconv.Atoi(fields[3])
+ if err != nil {
+ continue
+ }
+ th.Datum = aud
+ event(false, tid, th)
+ default:
+ if *debug {
+ fmt.Println("unparsable:", fields)
+ }
+ }
+ }
+ if err := sc.Err(); err != nil {
+ log.Fatalf("scanning failed: %v", err)
+ }
+}
+
+// tracer invokes bpftool it returns an error if the invocation fails.
+func tracer() (*exec.Cmd, error) {
+ cmd := exec.Command(*bpftrace, "-e", `kprobe:cap_capable {
+ printf("CB %d %d %d %d %s\n", pid, tid, arg2, arg3, comm);
+}
+kretprobe:cap_capable {
+ printf("CE %d %d %d\n", pid, tid, retval);
+}`)
+ out, err := cmd.StdoutPipe()
+ cmd.Stderr = os.Stderr
+ if err != nil {
+ return nil, fmt.Errorf("unable to create stdout for %q: %v", *bpftrace, err)
+ }
+ mu.Lock() // Unlocked on first ouput from tracer.
+ if err := cmd.Start(); err != nil {
+ return nil, fmt.Errorf("failed to start %q: %v", *bpftrace, err)
+ }
+ go tailTrace(cmd, out)
+ return cmd, nil
+}
+
+func main() {
+ flag.Usage = func() {
+ fmt.Fprintf(flag.CommandLine.Output(), `Usage: %s [options] [command ...]
+
+This tool monitors cap_capable() kernel execution to summarize when
+Effective Flag capabilities are checked in a running process{thread}.
+The monitoring is performed indirectly using the bpftrace tool.
+
+Each line logged has a timestamp at which the tracing program is able to
+summarize the return value of the check. A return value of " -> 0" implies
+the check succeeded and confirms the process{thread} does have the
+specified Effective capability.
+
+The listed "opt=" value indicates some auditing context for why the
+kernel needed to check the capability was Effective.
+
+Options:
+`, os.Args[0])
+ flag.PrintDefaults()
+ }
+ flag.Parse()
+
+ tr, err := tracer()
+ if err != nil {
+ log.Fatalf("failed to start tracer: %v", err)
+ }
+
+ mu.Lock()
+
+ if *pid != -1 {
+ tids[*pid] = *pid
+ } else if len(flag.Args()) != 0 {
+ args := flag.Args()
+ cmd := exec.Command(args[0], args[1:]...)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Start(); err != nil {
+ log.Fatalf("failed to start %v: %v", flag.Args(), err)
+ }
+ tids[cmd.Process.Pid] = cmd.Process.Pid
+
+ // waiting for the trace to complete is racy, so we sleep
+ // to obtain the last events then kill the tracer and wait
+ // for it to exit. Defers are in reverse order.
+ defer tr.Wait()
+ defer tr.Process.Kill()
+ defer time.Sleep(1 * time.Second)
+
+ tr = cmd
+ }
+
+ mu.Unlock()
+ tr.Wait()
+}
diff --git a/goapps/captrace/go.mod b/goapps/captrace/go.mod
new file mode 100644
index 0000000..9817252
--- /dev/null
+++ b/goapps/captrace/go.mod
@@ -0,0 +1,5 @@
+module captrace
+
+go 1.16
+
+require kernel.org/pub/linux/libs/security/libcap/cap v1.2.69
diff --git a/goapps/captree/captree.go b/goapps/captree/captree.go
new file mode 100644
index 0000000..7768b11
--- /dev/null
+++ b/goapps/captree/captree.go
@@ -0,0 +1,468 @@
+// Program captree explores a process tree rooted in the supplied
+// argument(s) and displays a process tree indicating the capabilities
+// of all the dependent PID values.
+//
+// This was inspired by the pstree utility. The key idea here, however,
+// is to explore a process tree for capability state.
+//
+// Each line of output is intended to capture a brief representation
+// of the capability state of a process (both *Set and *IAB) and
+// for its related threads.
+//
+// Ex:
+//
+// $ bash -c 'exec captree $$'
+// --captree(9758+{9759,9760,9761,9762})
+//
+// In the normal case, such as the above, where the targeted process
+// is not privileged, no distracting capability strings are displayed.
+// Where a process is thread group leader to a set of other thread
+// ids, they are listed as `+{...}`.
+//
+// For privileged binaries, we have:
+//
+// $ captree 551
+// --polkitd(551) "=ep"
+// :>-gmain{552} "=ep"
+// :>-gdbus{555} "=ep"
+//
+// That is, the text representation of the process capability state is
+// displayed in double quotes "..." as a suffix to the process/thread.
+// If the name of any thread of this process, or its own capability
+// state, is in some way different from the primary process then it is
+// displayed on a subsequent line prefixed with ":>-" and threads
+// sharing name and capability state are listed on that line. Here we
+// have two sub-threads with the same capability state, but unique
+// names.
+//
+// Sometimes members of a process group have different capabilities:
+//
+// $ captree 1368
+// --dnsmasq(1368) "cap_net_bind_service,cap_net_admin,cap_net_raw=ep"
+// +-dnsmasq(1369) "=ep"
+//
+// Where the A and B components of the IAB tuple are non-default, the
+// output also includes these:
+//
+// $ captree 925
+// --dbus-broker-lau(925) [!cap_sys_rawio,!cap_mknod]
+// +-dbus-broker(965) "cap_audit_write=eip" [!cap_sys_rawio,!cap_mknod,cap_audit_write]
+//
+// That is, the `[...]` appendage captures the IAB text representation
+// of that tuple. Note, if only the I part of that tuple is
+// non-default, it is already captured in the quoted process
+// capability state, so the IAB tuple is omitted.
+//
+// To view the complete system process map, rooted at the kernel, try
+// this:
+//
+// $ captree 0
+//
+// To view a specific binary (as named in /proc/<PID>/status as 'Name:
+// ...'), matched by a glob, try this:
+//
+// $ captree 'cap*ree'
+//
+// The quotes might be needed to avoid the '*' confusing your shell.
+package main
+
+import (
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "sort"
+ "strconv"
+ "strings"
+ "sync"
+
+ "kernel.org/pub/linux/libs/security/libcap/cap"
+)
+
+var (
+ proc = flag.String("proc", "/proc", "root of proc filesystem")
+ depth = flag.Int("depth", 0, "how many processes deep (0=all)")
+ verbose = flag.Bool("verbose", false, "display empty capabilities")
+ color = flag.Bool("color", true, "color targeted PIDs on tty in red")
+ colour = flag.Bool("colour", true, "colour targeted PIDs on tty in red")
+)
+
+type task struct {
+ mu sync.Mutex
+ viewed bool
+ depth int
+ pid string
+ cmd string
+ cap *cap.Set
+ iab *cap.IAB
+ parent string
+ threads []*task
+ children []string
+}
+
+func (ts *task) String() string {
+ return fmt.Sprintf("%s %q [%v] %s %v %v", ts.cmd, ts.cap, ts.iab, ts.parent, ts.threads, ts.children)
+}
+
+var (
+ wg sync.WaitGroup
+ mu sync.Mutex
+ colored bool
+)
+
+func isATTY() bool {
+ s, err := os.Stdout.Stat()
+ if err == nil && (s.Mode()&os.ModeCharDevice) != 0 {
+ return true
+ }
+ return false
+}
+
+func highlight(text string) string {
+ if colored {
+ return fmt.Sprint("\033[31m", text, "\033[0m")
+ }
+ return text
+}
+
+func (ts *task) fill(pid string, n int, thread bool) {
+ defer wg.Done()
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ c, _ := cap.GetPID(n)
+ iab, _ := cap.IABGetPID(n)
+ ts.mu.Lock()
+ defer ts.mu.Unlock()
+ ts.pid = pid
+ ts.cap = c
+ ts.iab = iab
+ }()
+
+ d, err := ioutil.ReadFile(fmt.Sprintf("%s/%s/status", *proc, pid))
+ if err != nil {
+ ts.mu.Lock()
+ defer ts.mu.Unlock()
+ ts.cmd = "<zombie>"
+ ts.parent = "1"
+ return
+ }
+ for _, line := range strings.Split(string(d), "\n") {
+ if strings.HasPrefix(line, "Name:\t") {
+ ts.mu.Lock()
+ ts.cmd = line[6:]
+ ts.mu.Unlock()
+ continue
+ }
+ if strings.HasPrefix(line, "PPid:\t") {
+ ppid := line[6:]
+ if ppid == pid {
+ continue
+ }
+ ts.mu.Lock()
+ ts.parent = ppid
+ ts.mu.Unlock()
+ }
+ }
+ if thread {
+ return
+ }
+
+ threads, err := ioutil.ReadDir(fmt.Sprintf("%s/%s/task", *proc, pid))
+ if err != nil {
+ return
+ }
+ var ths []*task
+ for _, t := range threads {
+ tid := t.Name()
+ if tid == pid {
+ continue
+ }
+ n, err := strconv.ParseInt(pid, 10, 64)
+ if err != nil {
+ continue
+ }
+ thread := &task{}
+ wg.Add(1)
+ go thread.fill(tid, int(n), true)
+ ths = append(ths, thread)
+ }
+ ts.mu.Lock()
+ defer ts.mu.Unlock()
+ ts.threads = ths
+}
+
+var empty = cap.NewSet()
+var noiab = cap.IABInit()
+
+// rDump prints out the tree of processes rooted at pid.
+func rDump(pids map[string]*task, requested map[string]bool, pid, stub, lstub, estub string, depth int) {
+ info, ok := pids[pid]
+ if !ok {
+ panic("programming error")
+ return
+ }
+ if info.viewed {
+ // This process (tree) has already been viewed so skip
+ // repeating it.
+ return
+ }
+ info.viewed = true
+
+ c := ""
+ set := info.cap
+ if set != nil {
+ if val, _ := set.Cf(empty); val != 0 || *verbose {
+ c = fmt.Sprintf(" %q", set)
+ }
+ }
+ iab := ""
+ tup := info.iab
+ if tup != nil {
+ if val, _ := tup.Cf(noiab); val.Has(cap.Bound) || val.Has(cap.Amb) || *verbose {
+ iab = fmt.Sprintf(" [%s]", tup)
+ }
+ }
+ var misc []*task
+ var same []string
+ for _, t := range info.threads {
+ if val, _ := t.cap.Cf(set); val != 0 {
+ misc = append(misc, t)
+ continue
+ }
+ if val, _ := t.iab.Cf(tup); val != 0 {
+ misc = append(misc, t)
+ continue
+ }
+ if t.cmd != info.cmd {
+ misc = append(misc, t)
+ continue
+ }
+ same = append(same, t.pid)
+ }
+ tids := ""
+ if len(same) != 0 {
+ tids = fmt.Sprintf("+{%s}", strings.Join(same, ","))
+ }
+ hPID := pid
+ if requested[pid] {
+ hPID = highlight(pid)
+ requested[pid] = false
+ }
+ fmt.Printf("%s%s%s(%s%s)%s%s\n", stub, lstub, info.cmd, hPID, tids, c, iab)
+ // loop over any threads that differ in capability state.
+ for len(misc) != 0 {
+ this := misc[0]
+ var nmisc []*task
+ var hPID = this.pid
+ if requested[this.pid] {
+ hPID = highlight(this.pid)
+ requested[this.pid] = false
+ }
+ same := []string{hPID}
+ for _, t := range misc[1:] {
+ if val, _ := this.cap.Cf(t.cap); val != 0 {
+ nmisc = append(nmisc, t)
+ continue
+ }
+ if val, _ := this.iab.Cf(t.iab); val != 0 {
+ nmisc = append(nmisc, t)
+ continue
+ }
+ if this.cmd != t.cmd {
+ nmisc = append(nmisc, t)
+ continue
+ }
+ hPID = t.pid
+ if requested[t.pid] {
+ hPID = highlight(t.pid)
+ requested[t.pid] = false
+ }
+ same = append(same, hPID)
+ }
+ c := ""
+ set := this.cap
+ if set != nil {
+ if val, _ := set.Cf(empty); val != 0 || *verbose {
+ c = fmt.Sprintf(" %q", set)
+ }
+ }
+ iab := ""
+ tup := this.iab
+ if tup != nil {
+ if val, _ := tup.Cf(noiab); val.Has(cap.Bound) || val.Has(cap.Amb) || *verbose {
+ iab = fmt.Sprintf(" [%s]", tup)
+ }
+ }
+ fmt.Printf("%s%s:>-%s{%s}%s%s\n", stub, estub, this.cmd, strings.Join(same, ","), c, iab)
+ misc = nmisc
+ }
+ if depth == 1 {
+ return
+ }
+ if depth > 1 {
+ depth--
+ }
+ x := info.children
+ sort.Slice(x, func(i, j int) bool {
+ a, _ := strconv.Atoi(x[i])
+ b, _ := strconv.Atoi(x[j])
+ return a < b
+ })
+ stub = fmt.Sprintf("%s%s", stub, estub)
+ lstub = "+-"
+ for i, cid := range x {
+ estub := "| "
+ if i+1 == len(x) {
+ estub = " "
+ }
+ rDump(pids, requested, cid, stub, lstub, estub, depth)
+ }
+}
+
+func findPIDs(list []string, pids map[string]*task, glob string) <-chan string {
+ finds := make(chan string)
+ go func() {
+ defer close(finds)
+ found := false
+ // search for PIDs, if found exit.
+ for _, pid := range list {
+ match, _ := filepath.Match(glob, pids[pid].cmd)
+ if !match {
+ continue
+ }
+ found = true
+ finds <- pid
+ }
+ if found {
+ return
+ }
+ fmt.Printf("no process matched %q\n", glob)
+ os.Exit(1)
+ }()
+ return finds
+}
+
+func setDepth(pids map[string]*task, pid string) int {
+ if pid == "0" {
+ return 0
+ }
+ x := pids[pid]
+ if x.depth == 0 {
+ x.depth = setDepth(pids, x.parent) + 1
+ }
+ return x.depth
+}
+
+func main() {
+ flag.Usage = func() {
+ fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] [pid|glob] ...\nOptions:\n", os.Args[0])
+ flag.PrintDefaults()
+ }
+ flag.Parse()
+
+ // Honor the command line request if possible.
+ colored = *color && *colour && isATTY()
+
+ // Just in case the user wants to override this, we set the
+ // cap package up to find it.
+ cap.ProcRoot(*proc)
+
+ pids := make(map[string]*task)
+ pids["0"] = &task{
+ cmd: "<kernel>",
+ }
+
+ // Ingest the entire process tree
+ fs, err := ioutil.ReadDir(*proc)
+ if err != nil {
+ log.Fatalf("unable to open %q: %v", *proc, err)
+ }
+ for _, f := range fs {
+ pid := f.Name()
+ n, err := strconv.ParseInt(pid, 10, 64)
+ if err != nil {
+ continue
+ }
+ ts := &task{}
+ mu.Lock()
+ pids[pid] = ts
+ mu.Unlock()
+ wg.Add(1)
+ go ts.fill(pid, int(n), false)
+ }
+ wg.Wait()
+
+ var list []string
+ for pid, ts := range pids {
+ setDepth(pids, pid)
+ list = append(list, pid)
+ if pid == "0" {
+ continue
+ }
+ if pts, ok := pids[ts.parent]; ok {
+ pts.children = append(pts.children, pid)
+ }
+ }
+
+ // Sort the process tree by tree depth - shallowest first,
+ // with numerical order breaking ties.
+ sort.Slice(list, func(i, j int) bool {
+ x, y := pids[list[i]], pids[list[j]]
+ if x.depth == y.depth {
+ a, _ := strconv.Atoi(x.pid)
+ b, _ := strconv.Atoi(y.pid)
+ return a < b
+ }
+ return x.depth < y.depth
+ })
+
+ args := flag.Args()
+ if len(args) == 0 {
+ args = []string{"1"}
+ }
+
+ wanted := make(map[string]int)
+ requested := make(map[string]bool)
+ for _, pid := range args {
+ if _, err := strconv.ParseUint(pid, 10, 64); err == nil {
+ requested[pid] = true
+ if info, ok := pids[pid]; ok {
+ wanted[pid] = info.depth
+ continue
+ }
+ if requested[pid] {
+ continue
+ }
+ requested[pid] = true
+ continue
+ }
+ for pid := range findPIDs(list, pids, pid) {
+ requested[pid] = true
+ if info, ok := pids[pid]; ok {
+ wanted[pid] = info.depth
+ }
+ }
+ }
+
+ var noted []string
+ for pid := range wanted {
+ noted = append(noted, pid)
+ }
+ sort.Slice(noted, func(i, j int) bool {
+ return wanted[noted[i]] < wanted[noted[j]]
+ })
+
+ // We've boiled down the processes to a unique set of targets.
+ for _, pid := range noted {
+ rDump(pids, requested, pid, "", "--", " ", *depth)
+ }
+
+ for pid, missed := range requested {
+ if missed {
+ fmt.Println("[PID", pid, "not found]")
+ }
+ }
+}
diff --git a/goapps/captree/go.mod b/goapps/captree/go.mod
new file mode 100644
index 0000000..09e579c
--- /dev/null
+++ b/goapps/captree/go.mod
@@ -0,0 +1,5 @@
+module captree
+
+go 1.16
+
+require kernel.org/pub/linux/libs/security/libcap/cap v1.2.69
diff --git a/goapps/gowns/go.mod b/goapps/gowns/go.mod
index 92de262..0e867ed 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 v1.2.53
+require kernel.org/pub/linux/libs/security/libcap/cap v1.2.69
diff --git a/goapps/setid/go.mod b/goapps/setid/go.mod
index 02061c2..09595b4 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 v1.2.53
- kernel.org/pub/linux/libs/security/libcap/psx v1.2.53
+ kernel.org/pub/linux/libs/security/libcap/cap v1.2.69
+ kernel.org/pub/linux/libs/security/libcap/psx v1.2.69
)
diff --git a/goapps/web/README b/goapps/web/README
deleted file mode 100644
index cbabd5d..0000000
--- a/goapps/web/README
+++ /dev/null
@@ -1,18 +0,0 @@
-This sample program needs to be built as follows (when built with Go
-prior to 1.15):
-
- CGO_LDFLAGS_ALLOW="-Wl,-?-wrap[=,][^-.@][^,]*" go build web.go
-
-go1.15+ does not require the CGO_LDFLAGS_ALLOW variable and can build
-this code with
-
- go build web.go
-
-A more complete walk through of what this code does is provided here:
-
- 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:
-
- https://bugzilla.kernel.org/buglist.cgi?component=libcap&list_id=1065141&product=Tools&resolution=---
diff --git a/goapps/web/README.md b/goapps/web/README.md
new file mode 100644
index 0000000..970d10e
--- /dev/null
+++ b/goapps/web/README.md
@@ -0,0 +1,28 @@
+# Web serving with/without privilege
+
+## Building
+
+This sample program needs to be built as follows (when built with Go
+prior to 1.15):
+```
+ export CGO_LDFLAGS_ALLOW="-Wl,-?-wrap[=,][^-.@][^,]*"
+ go mod tidy
+ go build web.go
+```
+go1.15+ does not require the `CGO_LDFLAGS_ALLOW` environment variable
+and can build this code with:
+```
+ go mod tidy
+ go build web.go
+```
+
+## Further discussion
+
+A more complete walk through of what this code does is provided on the
+[Fully Capable
+website](https://sites.google.com/site/fullycapable/getting-started-with-go/building-go-programs-that-manipulate-capabilities).
+
+## Reporting bugs
+
+Go compilers prior to go1.11.13 are not expected to work. Report more
+recent issues to the [`libcap` bug tracker](https://bugzilla.kernel.org/buglist.cgi?component=libcap&list_id=1065141&product=Tools&resolution=---).
diff --git a/goapps/web/go.mod b/goapps/web/go.mod
index 4ca6b0e..054357b 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 v1.2.53
+require kernel.org/pub/linux/libs/security/libcap/cap v1.2.69
diff --git a/goapps/web/web.go b/goapps/web/web.go
index c96e745..5f9c5cb 100644
--- a/goapps/web/web.go
+++ b/goapps/web/web.go
@@ -13,6 +13,8 @@
// package - go versions prior to 1.15 need some environment variable
// workarounds):
//
+// go mod init web
+// go mod tidy
// go build web.go
// sudo setcap cap_setpcap,cap_net_bind_service=p web
// ./web --port=80
diff --git a/kdebug/Makefile b/kdebug/Makefile
index 0e8c11f..35a16d0 100644
--- a/kdebug/Makefile
+++ b/kdebug/Makefile
@@ -10,7 +10,7 @@ shell: exit
./test-kernel.sh
exit: exit.c
- $(CC) -O2 $< -o $@ --static
+ $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ --static
all:
@echo cd to kdebug to test a kernel build
diff --git a/kdebug/test-kernel.sh b/kdebug/test-kernel.sh
index 38f5584..391bb8a 100755
--- a/kdebug/test-kernel.sh
+++ b/kdebug/test-kernel.sh
@@ -13,8 +13,9 @@ function die {
}
pushd ..
-make test || die "failed to make test of libcap tree"
-make -C progs tcapsh-static || die "failed to make progs/tcapsh-static"
+make LIBCSTATIC=yes all test || die "failed to make all test of libcap tree"
+make LIBCSTATIC=yes -C progs tcapsh-static || die "failed to make progs/tcapsh-static"
+make -C tests uns_test
popd
# Assumes desired make *config (eg. make defconfig) is already done.
@@ -61,7 +62,7 @@ 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"
+COMMANDS="awk cat chmod cp dmesg grep 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
done
diff --git a/libcap/Makefile b/libcap/Makefile
index b5689d2..f5dde3e 100644
--- a/libcap/Makefile
+++ b/libcap/Makefile
@@ -18,6 +18,9 @@ CAPMAGICOBJ=cap_magic.o
PSXFILES=../psx/psx
PSXMAGICOBJ=psx_magic.o
+# Always build libcap sources this way:
+CFLAGS += -fPIC
+
# The linker magic needed to build a dynamic library as independently
# executable
MAGIC=-Wl,-e,__so_start
@@ -49,13 +52,13 @@ ifeq ($(PTHREADS),yes)
$(MAKE) $(PSXTITLE).pc
endif
-ifeq ($(BUILD_GPERF),yes)
+ifeq ($(USE_GPERF),yes)
USE_GPERF_OUTPUT = $(GPERF_OUTPUT)
INCLUDE_GPERF_OUTPUT = -DINCLUDE_GPERF_OUTPUT='"$(GPERF_OUTPUT)"'
endif
$(LIBTITLE).pc: $(LIBTITLE).pc.in
- sed -e 's,@prefix@,$(prefix),' \
+ $(BUILD_SED) -e 's,@prefix@,$(prefix),' \
-e 's,@exec_prefix@,$(exec_prefix),' \
-e 's,@libdir@,$(LIBDIR),' \
-e 's,@includedir@,$(inc_prefix)/include,' \
@@ -64,7 +67,7 @@ $(LIBTITLE).pc: $(LIBTITLE).pc.in
$< >$@
$(PSXTITLE).pc: $(PSXTITLE).pc.in
- sed -e 's,@prefix@,$(prefix),' \
+ $(BUILD_SED) -e 's,@prefix@,$(prefix),' \
-e 's,@exec_prefix@,$(exec_prefix),' \
-e 's,@libdir@,$(LIBDIR),' \
-e 's,@includedir@,$(inc_prefix)/include,' \
@@ -73,23 +76,27 @@ $(PSXTITLE).pc: $(PSXTITLE).pc.in
$< >$@
_makenames: _makenames.c cap_names.list.h
- $(BUILD_CC) $(BUILD_CFLAGS) $< -o $@
+ $(BUILD_CC) $(BUILD_CFLAGS) $(BUILD_CPPFLAGS) $< -o $@
cap_names.h: _makenames
./_makenames > cap_names.h
-$(GPERF_OUTPUT): cap_names.list.h
- perl -e 'print "struct __cap_token_s { const char *name; int index; };\n%{\nconst struct __cap_token_s *__cap_lookup_name(const char *, size_t);\n%}\n%%\n"; while ($$l = <>) { $$l =~ s/[\{\"]//g; $$l =~ s/\}.*// ; print $$l; }' < $< | gperf --ignore-case --language=ANSI-C --readonly --null-strings --global-table --hash-function-name=__cap_hash_name --lookup-function-name="__cap_lookup_name" -c -t -m20 $(INDENT) > $@
- sed -e 's/unsigned int len/size_t len/' -i $@
+$(GPERF_OUTPUT): cap_names.list.h cap_names.header Makefile
+ (cat cap_names.header ; $(BUILD_SED) -e 's/[\{\}"]//g' -e 's/,$$//' cap_names.list.h) | gperf --ignore-case --language=ANSI-C --readonly --null-strings --global-table --hash-function-name=__cap_hash_name --lookup-function-name="__cap_lookup_name" -c -t -m20 $(INDENT) > $@
+ $(BUILD_SED) -e 's/unsigned int len/size_t len/' -i $@
# Intention is that libcap keeps up with torvalds' tree, as reflected
# by this maintained version of the kernel header. libcap dynamically
# trims the meaning of "all" capabilities down to that of the running
-# kernel as of 2.30.
+# kernel as of 2.30. That is, all production kernels should be equal
+# to or behind libcap.
+#
+# Note "./libcap.so --summary" should explain how the built libcap.so
+# compares to the running kernel.
UAPI_HEADER := $(topdir)/libcap/include/uapi/linux/capability.h
cap_names.list.h: Makefile $(UAPI_HEADER)
@echo "=> making $@ from $(UAPI_HEADER)"
- perl -e 'while ($$l=<>) { if ($$l =~ /^\#define[ \t](CAP[_A-Z]+)[ \t]+([0-9]+)\s+$$/) { $$tok=$$1; $$val=$$2; $$tok =~ tr/A-Z/a-z/; print "{\"$$tok\",$$val},\n"; } }' $(UAPI_HEADER) | fgrep -v 0x > $@
+ $(BUILD_EGREP) '^#define\s+CAP_([^\s]+)\s+[0-9]+\s*$$' include/uapi/linux/capability.h | $(BUILD_SED) -e 's/^#define\s\+/{"/' -e 's/\s*$$/},/' -e 's/\s\+/",/' -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/' > $@
$(STACAPLIBNAME): $(CAPOBJS)
$(AR) rcs $@ $^
@@ -105,18 +112,18 @@ empty: empty.c
$(CC) -o $@ $<
loader.txt: empty
- $(OBJCOPY) --dump-section .interp=$@ $<
+ $(OBJCOPY) --dump-section .interp=$@ $< /dev/null
-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 $@
+cap_magic.o: execable.h execable.c loader.txt libcap.h
+ $(CC) $(CFLAGS) $(CPPFLAGS) -DLIBRARY_VERSION=\"$(LIBTITLE)-$(VERSION).$(MINOR)\" -DSHARED_LOADER=\"$(shell cat loader.txt)\" -include ./libcap.h -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)
-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 $@
+psx_magic.o: execable.h psx_exec.c loader.txt
+ $(CC) $(CFLAGS) $(CPPFLAGS) -DLIBRARY_VERSION=\"$(PSXTITLE)-$(VERSION).$(MINOR)\" -DSHARED_LOADER=\"$(shell cat loader.txt)\" -c psx_exec.c -o $@
$(PSXLIBNAME) $(MAJPSXLIBNAME) $(MINPSXLIBNAME): $(PSXOBJS) include/sys/psx_syscall.h $(PSXMAGICOBJ)
$(LD) $(CFLAGS) $(LDFLAGS) -Wl,-soname,$(MAJPSXLIBNAME) -o $(MINPSXLIBNAME) $(PSXOBJS) $(PSXMAGICOBJ) $(MAGIC) $(PSXLINKFLAGS)
@@ -125,16 +132,19 @@ $(PSXLIBNAME) $(MAJPSXLIBNAME) $(MINPSXLIBNAME): $(PSXOBJS) include/sys/psx_sysc
endif
%.o: %.c $(INCLS)
- $(CC) $(CFLAGS) $(IPATH) -c $< -o $@
+ $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
cap_text.o: cap_text.c $(USE_GPERF_OUTPUT) $(INCLS)
- $(CC) $(CFLAGS) $(IPATH) $(INCLUDE_GPERF_OUTPUT) -c $< -o $@
+ $(CC) $(CFLAGS) $(CPPFLAGS) $(INCLUDE_GPERF_OUTPUT) -c $< -o $@
-cap_test: cap_test.c libcap.h $(CAPOBJS)
- $(CC) $(CFLAGS) $(IPATH) $< $(CAPOBJS) -o $@
+cap_test: cap_test.c $(INCLS) $(CAPOBJS)
+ $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $< $(CAPOBJS) -o $@
libcapsotest: $(CAPLIBNAME)
./$(CAPLIBNAME)
+ ./$(CAPLIBNAME) --usage
+ ./$(CAPLIBNAME) --help
+ ./$(CAPLIBNAME) --summary
libpsxsotest: $(PSXLIBNAME)
./$(PSXLIBNAME)
@@ -148,6 +158,9 @@ ifeq ($(PTHREADS),yes)
endif
endif
+sudotest:
+ @echo no sudotests for libcap
+
install: install-static
ifeq ($(SHARED),yes)
$(MAKE) install-shared
@@ -177,7 +190,7 @@ install-static-cap: install-common-cap $(STACAPLIBNAME)
install -m 0644 $(STACAPLIBNAME) $(FAKEROOT)$(LIBDIR)/$(STACAPLIBNAME)
install-shared-cap: install-common-cap $(MINCAPLIBNAME)
- install -m 0644 $(MINCAPLIBNAME) $(FAKEROOT)$(LIBDIR)/$(MINCAPLIBNAME)
+ install -m 0755 $(MINCAPLIBNAME) $(FAKEROOT)$(LIBDIR)/$(MINCAPLIBNAME)
ln -sf $(MINCAPLIBNAME) $(FAKEROOT)$(LIBDIR)/$(MAJCAPLIBNAME)
ln -sf $(MAJCAPLIBNAME) $(FAKEROOT)$(LIBDIR)/$(CAPLIBNAME)
ifeq ($(FAKEROOT),)
@@ -188,7 +201,7 @@ 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)
+ install -m 0755 $(MINPSXLIBNAME) $(FAKEROOT)$(LIBDIR)/$(MINPSXLIBNAME)
ln -sf $(MINPSXLIBNAME) $(FAKEROOT)$(LIBDIR)/$(MAJPSXLIBNAME)
ln -sf $(MAJPSXLIBNAME) $(FAKEROOT)$(LIBDIR)/$(PSXLIBNAME)
ifeq ($(FAKEROOT),)
diff --git a/libcap/_makenames.c b/libcap/_makenames.c
index 46ab0c9..30eb080 100644
--- a/libcap/_makenames.c
+++ b/libcap/_makenames.c
@@ -26,7 +26,7 @@ struct {
* indicated extended empty space.
*/
static void *recalloc(void *p, int was, int is) {
- void *n = realloc(p, is);
+ char *n = realloc(p, is);
if (!n) {
fputs("out of memory", stderr);
exit(1);
@@ -45,10 +45,14 @@ int main(void)
if (maxcaps <= list[i].index) {
maxcaps = list[i].index + 1;
}
- if (list[i].index >= pointers_avail) {
+ if (pointers == NULL || list[i].index >= pointers_avail) {
int was = pointers_avail * sizeof(char *);
pointers_avail = 2 * list[i].index + 1;
pointers = recalloc(pointers, was, pointers_avail * sizeof(char *));
+ if (pointers == NULL) {
+ perror("unable to continue");
+ exit(1);
+ }
}
pointers[list[i].index] = list[i].name;
int n = strlen(list[i].name);
diff --git a/libcap/cap_alloc.c b/libcap/cap_alloc.c
index 88ba6da..504abd2 100644
--- a/libcap/cap_alloc.c
+++ b/libcap/cap_alloc.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997-8,2019 Andrew G Morgan <morgan@kernel.org>
+ * Copyright (c) 1997-8,2019,2021 Andrew G Morgan <morgan@kernel.org>
*
* This file deals with allocation and deallocation of internal
* capability sets as specified by POSIX.1e (formerlly, POSIX 6).
@@ -8,41 +8,65 @@
#include "libcap.h"
/*
- * This gets set via the pre-main() executed constructor function below it.
+ * Make start up atomic.
+ */
+static __u8 __libcap_mutex;
+
+/*
+ * These get set via the pre-main() executed constructor function below it.
*/
static cap_value_t _cap_max_bits;
-__attribute__((constructor (300))) static void _initialize_libcap(void) {
- if (_cap_max_bits) {
- return;
+__attribute__((visibility ("hidden")))
+__attribute__((constructor (300))) void _libcap_initialize(void)
+{
+ int errno_saved = errno;
+ _cap_mu_lock(&__libcap_mutex);
+ if (!_cap_max_bits) {
+ cap_set_syscall(NULL, NULL);
+ _binary_search(_cap_max_bits, cap_get_bound, 0, __CAP_MAXBITS,
+ __CAP_BITS);
}
- cap_set_syscall(NULL, NULL);
- _binary_search(_cap_max_bits, cap_get_bound, 0, __CAP_MAXBITS, __CAP_BITS);
+ _cap_mu_unlock(&__libcap_mutex);
+ errno = errno_saved;
}
-cap_value_t cap_max_bits(void) {
+cap_value_t cap_max_bits(void)
+{
return _cap_max_bits;
}
/*
- * Obtain a blank set of capabilities
+ * capability allocation is all done in terms of this structure.
*/
+struct _cap_alloc_s {
+ __u32 magic;
+ __u32 size;
+ union {
+ struct _cap_struct set;
+ struct cap_iab_s iab;
+ struct cap_launch_s launcher;
+ } u;
+};
+/*
+ * Obtain a blank set of capabilities
+ */
cap_t cap_init(void)
{
- __u32 *raw_data;
+ struct _cap_alloc_s *raw_data;
cap_t result;
- raw_data = calloc(1, sizeof(__u32) + sizeof(*result));
+ raw_data = calloc(1, sizeof(struct _cap_alloc_s));
if (raw_data == NULL) {
_cap_debug("out of memory");
errno = ENOMEM;
return NULL;
}
+ raw_data->magic = CAP_T_MAGIC;
+ raw_data->size = sizeof(struct _cap_alloc_s);
- *raw_data = CAP_T_MAGIC;
- result = (cap_t) (raw_data + 1);
-
+ result = &raw_data->u.set;
result->head.version = _LIBCAP_CAPABILITY_VERSION;
capget(&result->head, NULL); /* load the kernel-capability version */
@@ -72,34 +96,47 @@ cap_t cap_init(void)
* This is an internal library function to duplicate a string and
* tag the result as something cap_free can handle.
*/
-
-char *_libcap_strdup(const char *old)
+__attribute__((visibility ("hidden"))) char *_libcap_strdup(const char *old)
{
- __u32 *raw_data;
+ struct _cap_alloc_s *header;
+ char *raw_data;
+ size_t len;
if (old == NULL) {
errno = EINVAL;
return NULL;
}
- raw_data = malloc( sizeof(__u32) + strlen(old) + 1 );
+ len = strlen(old);
+ if ((len & 0x3fffffff) != len) {
+ _cap_debug("len is too long for libcap to manage");
+ errno = EINVAL;
+ return NULL;
+ }
+ len += 1 + 2*sizeof(__u32);
+ if (len < sizeof(struct _cap_alloc_s)) {
+ len = sizeof(struct _cap_alloc_s);
+ }
+
+ raw_data = calloc(1, len);
if (raw_data == NULL) {
errno = ENOMEM;
return NULL;
}
+ header = (void *) raw_data;
+ header->magic = CAP_S_MAGIC;
+ header->size = (__u32) len;
- *(raw_data++) = CAP_S_MAGIC;
- strcpy((char *) raw_data, old);
-
- return ((char *) raw_data);
+ raw_data += 2*sizeof(__u32);
+ strcpy(raw_data, old);
+ return raw_data;
}
/*
* This function duplicates an internal capability set with
- * malloc()'d memory. It is the responsibility of the user to call
+ * calloc()'d memory. It is the responsibility of the user to call
* cap_free() to liberate it.
*/
-
cap_t cap_dup(cap_t cap_d)
{
cap_t result;
@@ -116,15 +153,53 @@ cap_t cap_dup(cap_t cap_d)
return NULL;
}
+ _cap_mu_lock(&cap_d->mutex);
memcpy(result, cap_d, sizeof(*cap_d));
+ _cap_mu_unlock(&cap_d->mutex);
+ _cap_mu_unlock(&result->mutex);
return result;
}
-cap_iab_t cap_iab_init(void) {
- __u32 *base = calloc(1, sizeof(__u32) + sizeof(struct cap_iab_s));
- *(base++) = CAP_IAB_MAGIC;
- return (cap_iab_t) base;
+cap_iab_t cap_iab_init(void)
+{
+ struct _cap_alloc_s *base = calloc(1, sizeof(struct _cap_alloc_s));
+ if (base == NULL) {
+ _cap_debug("out of memory");
+ return NULL;
+ }
+ base->magic = CAP_IAB_MAGIC;
+ base->size = sizeof(struct _cap_alloc_s);
+ return &base->u.iab;
+}
+
+/*
+ * This function duplicates an internal iab tuple with calloc()'d
+ * memory. It is the responsibility of the user to call cap_free() to
+ * liberate it.
+ */
+cap_iab_t cap_iab_dup(cap_iab_t iab)
+{
+ cap_iab_t result;
+
+ if (!good_cap_iab_t(iab)) {
+ _cap_debug("bad argument");
+ errno = EINVAL;
+ return NULL;
+ }
+
+ result = cap_iab_init();
+ if (result == NULL) {
+ _cap_debug("out of memory");
+ return NULL;
+ }
+
+ _cap_mu_lock(&iab->mutex);
+ memcpy(result, iab, sizeof(*iab));
+ _cap_mu_unlock(&iab->mutex);
+ _cap_mu_unlock(&result->mutex);
+
+ return result;
}
/*
@@ -137,9 +212,15 @@ cap_iab_t cap_iab_init(void) {
cap_launch_t cap_new_launcher(const char *arg0, const char * const *argv,
const char * const *envp)
{
- __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;
+ struct _cap_alloc_s *data = calloc(1, sizeof(struct _cap_alloc_s));
+ if (data == NULL) {
+ _cap_debug("out of memory");
+ return NULL;
+ }
+ data->magic = CAP_LAUNCH_MAGIC;
+ data->size = sizeof(struct _cap_alloc_s);
+
+ struct cap_launch_s *attr = &data->u.launcher;
attr->arg0 = arg0;
attr->argv = argv;
attr->envp = envp;
@@ -155,65 +236,72 @@ cap_launch_t cap_new_launcher(const char *arg0, const char * const *argv,
*/
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;
+ struct _cap_alloc_s *data = calloc(1, sizeof(struct _cap_alloc_s));
+ if (data == NULL) {
+ _cap_debug("out of memory");
+ return NULL;
+ }
+ data->magic = CAP_LAUNCH_MAGIC;
+ data->size = sizeof(struct _cap_alloc_s);
+
+ struct cap_launch_s *attr = &data->u.launcher;
attr->custom_setup_fn = callback_fn;
return attr;
}
/*
- * Scrub and then liberate an internal capability set.
+ * Scrub and then liberate the recognized allocated object.
*/
-
int cap_free(void *data_p)
{
- if (!data_p)
+ if (!data_p) {
return 0;
-
- if (good_cap_t(data_p)) {
- data_p = -1 + (__u32 *) data_p;
- memset(data_p, 0, sizeof(__u32) + sizeof(struct _cap_struct));
- free(data_p);
- data_p = NULL;
- return 0;
- }
-
- if (good_cap_string(data_p)) {
- size_t length = strlen(data_p) + sizeof(__u32);
- data_p = -1 + (__u32 *) data_p;
- memset(data_p, 0, length);
- free(data_p);
- data_p = NULL;
- return 0;
}
- if (good_cap_iab_t(data_p)) {
- size_t length = sizeof(struct cap_iab_s) + sizeof(__u32);
- data_p = -1 + (__u32 *) data_p;
- memset(data_p, 0, length);
- free(data_p);
- data_p = NULL;
- return 0;
+ /* confirm alignment */
+ if ((sizeof(uintptr_t)-1) & (uintptr_t) data_p) {
+ _cap_debug("whatever we're cap_free()ing it isn't aligned right: %p",
+ data_p);
+ errno = EINVAL;
+ return -1;
}
- if (good_cap_launch_t(data_p)) {
- cap_launch_t launcher = data_p;
- if (launcher->iab) {
- cap_free(launcher->iab);
+ void *base = (void *) (-2 + (__u32 *) data_p);
+ struct _cap_alloc_s *data = base;
+ switch (data->magic) {
+ case CAP_T_MAGIC:
+ _cap_mu_lock(&data->u.set.mutex);
+ break;
+ case CAP_S_MAGIC:
+ case CAP_IAB_MAGIC:
+ break;
+ case CAP_LAUNCH_MAGIC:
+ if (data->u.launcher.iab != NULL) {
+ _cap_mu_unlock(&data->u.launcher.iab->mutex);
+ if (cap_free(data->u.launcher.iab) != 0) {
+ return -1;
+ }
}
- if (launcher->chroot) {
- cap_free(launcher->chroot);
+ data->u.launcher.iab = NULL;
+ if (cap_free(data->u.launcher.chroot) != 0) {
+ return -1;
}
- size_t length = sizeof(struct cap_iab_s) + sizeof(__u32);
- data_p = -1 + (__u32 *) data_p;
- memset(data_p, 0, length);
- free(data_p);
- data_p = NULL;
- return 0;
+ data->u.launcher.chroot = NULL;
+ break;
+ default:
+ _cap_debug("don't recognize what we're supposed to liberate");
+ errno = EINVAL;
+ return -1;
}
- _cap_debug("don't recognize what we're supposed to liberate");
- errno = EINVAL;
- return -1;
+ /*
+ * operate here with respect to base, to avoid tangling with the
+ * automated buffer overflow detection.
+ */
+ memset(base, 0, data->size);
+ free(base);
+ data_p = NULL;
+ data = NULL;
+ base = NULL;
+ return 0;
}
diff --git a/libcap/cap_extint.c b/libcap/cap_extint.c
index bf0967b..462cc65 100644
--- a/libcap/cap_extint.c
+++ b/libcap/cap_extint.c
@@ -36,40 +36,49 @@ struct cap_ext_struct {
*/
static size_t _libcap_min_ext_flag_size = CAP_SET_SIZE < 8 ? CAP_SET_SIZE : 8;
+static ssize_t _cap_size_locked(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 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);
+ size_t used;
+ if (!good_cap_t(cap_d)) {
+ return ssizeof(struct cap_ext_struct);
}
- return ssizeof(struct cap_ext_struct);
+ _cap_mu_lock(&cap_d->mutex);
+ used = _cap_size_locked(cap_d);
+ _cap_mu_unlock(&cap_d->mutex);
+ return used;
}
/*
@@ -90,10 +99,11 @@ ssize_t cap_copy_ext(void *cap_ext, cap_t cap_d, ssize_t length)
return -1;
}
- csz = cap_size(cap_d);
+ _cap_mu_lock(&cap_d->mutex);
+ csz = _cap_size_locked(cap_d);
if (csz > length) {
errno = EINVAL;
- return -1;
+ _cap_mu_unlock_return(&cap_d->mutex, -1);
}
len_set = (csz - (CAP_EXT_MAGIC_SIZE+1))/NUMBER_OF_CAP_SETS;
@@ -122,7 +132,7 @@ ssize_t cap_copy_ext(void *cap_ext, cap_t cap_d, ssize_t length)
}
/* All done: return length of external representation */
- return csz;
+ _cap_mu_unlock_return(&cap_d->mutex, csz);
}
/*
diff --git a/libcap/cap_file.c b/libcap/cap_file.c
index 84ae3e1..0bc07f7 100644
--- a/libcap/cap_file.c
+++ b/libcap/cap_file.c
@@ -1,7 +1,7 @@
/*
* Copyright (c) 1997,2007,2016 Andrew G Morgan <morgan@kernel.org>
*
- * This file deals with setting capabilities on files.
+ * This file deals with getting/setting capabilities from/on files.
*/
#ifndef _DEFAULT_SOURCE
@@ -12,7 +12,6 @@
#include <byteswap.h>
#include <sys/stat.h>
#include <unistd.h>
-#include <linux/xattr.h>
/*
* We hardcode the prototypes for the Linux system calls here since
@@ -127,6 +126,7 @@ static int _fcaps_save(struct vfs_ns_cap_data *rawvfscap, cap_t cap_d,
errno = EINVAL;
return -1;
}
+ _cap_mu_lock(&cap_d->mutex);
switch (cap_d->head.version) {
case _LINUX_CAPABILITY_VERSION_1:
@@ -144,14 +144,14 @@ static int _fcaps_save(struct vfs_ns_cap_data *rawvfscap, cap_t cap_d,
default:
errno = EINVAL;
- return -1;
+ _cap_mu_unlock_return(&cap_d->mutex, -1);
}
if (cap_d->rootid != 0) {
if (cap_d->head.version < _LINUX_CAPABILITY_VERSION_3) {
_cap_debug("namespaces with non-0 rootid unsupported by kernel");
errno = EINVAL;
- return -1;
+ _cap_mu_unlock_return(&cap_d->mutex, -1);
}
magic = VFS_CAP_REVISION_3;
tocopy = VFS_CAP_U32_3;
@@ -172,7 +172,7 @@ static int _fcaps_save(struct vfs_ns_cap_data *rawvfscap, cap_t cap_d,
* System does not support these capabilities
*/
errno = EINVAL;
- return -1;
+ _cap_mu_unlock_return(&cap_d->mutex, -1);
}
i++;
}
@@ -188,7 +188,7 @@ static int _fcaps_save(struct vfs_ns_cap_data *rawvfscap, cap_t cap_d,
& (cap_d->u[i].flat[CAP_PERMITTED]
| cap_d->u[i].flat[CAP_INHERITABLE]))) {
errno = EINVAL;
- return -1;
+ _cap_mu_unlock_return(&cap_d->mutex, -1);
}
}
@@ -198,7 +198,7 @@ static int _fcaps_save(struct vfs_ns_cap_data *rawvfscap, cap_t cap_d,
rawvfscap->magic_etc = FIXUP_32BITS(magic|VFS_CAP_FLAGS_EFFECTIVE);
}
- return 0; /* success */
+ _cap_mu_unlock_return(&cap_d->mutex, 0); /* success */
}
/*
@@ -269,7 +269,15 @@ cap_t cap_get_file(const char *filename)
uid_t cap_get_nsowner(cap_t cap_d)
{
- return cap_d->rootid;
+ uid_t nsowner;
+ if (!good_cap_t(cap_d)) {
+ errno = EINVAL;
+ return -1;
+ }
+ _cap_mu_lock(&cap_d->mutex);
+ nsowner = cap_d->rootid;
+ _cap_mu_unlock(&cap_d->mutex);
+ return nsowner;
}
/*
@@ -337,13 +345,17 @@ int cap_set_file(const char *filename, cap_t cap_d)
}
/*
- * Set rootid for the file capability sets.
+ * Set nsowner for the file capability set.
*/
-
int cap_set_nsowner(cap_t cap_d, uid_t rootuid)
{
- cap_d->rootid = rootuid;
- return 0;
+ if (!good_cap_t(cap_d)) {
+ errno = EINVAL;
+ return -1;
+ }
+ _cap_mu_lock(&cap_d->mutex);
+ cap_d->rootid = rootuid;
+ _cap_mu_unlock_return(&cap_d->mutex, 0);
}
#else /* ie. ndef VFS_CAP_U32 */
@@ -380,8 +392,8 @@ int cap_set_file(const char *filename, cap_t cap_d)
int cap_set_nsowner(cap_t cap_d, uid_t rootuid)
{
- errno = EINVAL;
- return -1;
+ errno = EINVAL;
+ return -1;
}
#endif /* def VFS_CAP_U32 */
diff --git a/libcap/cap_flag.c b/libcap/cap_flag.c
index 51799b0..94afd1e 100644
--- a/libcap/cap_flag.c
+++ b/libcap/cap_flag.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997-8,2008,20 Andrew G. Morgan <morgan@kernel.org>
+ * Copyright (c) 1997-8,2008,20-21 Andrew G. Morgan <morgan@kernel.org>
*
* This file deals with flipping of capabilities on internal
* capability sets as specified by POSIX.1e (formerlly, POSIX 6).
@@ -14,7 +14,6 @@
* returned as the contents of *raised. The capability is from one of
* the sets stored in cap_d as specified by set and value
*/
-
int cap_get_flag(cap_t cap_d, cap_value_t value, cap_flag_t set,
cap_flag_value_t *raised)
{
@@ -25,7 +24,9 @@ int cap_get_flag(cap_t cap_d, cap_value_t value, cap_flag_t set,
if (raised && good_cap_t(cap_d) && value >= 0 && value < __CAP_MAXBITS
&& set >= 0 && set < NUMBER_OF_CAP_SETS) {
+ _cap_mu_lock(&cap_d->mutex);
*raised = isset_cap(cap_d,value,set) ? CAP_SET:CAP_CLEAR;
+ _cap_mu_unlock(&cap_d->mutex);
return 0;
} else {
_cap_debug("invalid arguments");
@@ -51,6 +52,7 @@ int cap_set_flag(cap_t cap_d, cap_flag_t set,
&& (set >= 0) && (set < NUMBER_OF_CAP_SETS)
&& (raise == CAP_SET || raise == CAP_CLEAR) ) {
int i;
+ _cap_mu_lock(&cap_d->mutex);
for (i=0; i<no_values; ++i) {
if (array_values[i] < 0 || array_values[i] >= __CAP_MAXBITS) {
_cap_debug("weird capability (%d) - skipped", array_values[i]);
@@ -64,14 +66,12 @@ int cap_set_flag(cap_t cap_d, cap_flag_t set,
}
}
}
+ _cap_mu_unlock(&cap_d->mutex);
return 0;
-
} else {
-
_cap_debug("invalid arguments");
errno = EINVAL;
return -1;
-
}
}
@@ -82,7 +82,9 @@ int cap_set_flag(cap_t cap_d, cap_flag_t set,
int cap_clear(cap_t cap_d)
{
if (good_cap_t(cap_d)) {
+ _cap_mu_lock(&cap_d->mutex);
memset(&(cap_d->u), 0, sizeof(cap_d->u));
+ _cap_mu_unlock(&cap_d->mutex);
return 0;
} else {
_cap_debug("invalid pointer");
@@ -104,9 +106,11 @@ int cap_clear_flag(cap_t cap_d, cap_flag_t flag)
if (good_cap_t(cap_d)) {
unsigned i;
+ _cap_mu_lock(&cap_d->mutex);
for (i=0; i<_LIBCAP_CAPABILITY_U32S; i++) {
cap_d->u[i].flat[flag] = 0;
}
+ _cap_mu_unlock(&cap_d->mutex);
return 0;
}
/*
@@ -134,6 +138,15 @@ int cap_compare(cap_t a, cap_t b)
return -1;
}
+ /*
+ * To avoid a deadlock corner case, we operate on an unlocked
+ * private copy of b
+ */
+ b = cap_dup(b);
+ if (b == NULL) {
+ return -1;
+ }
+ _cap_mu_lock(&a->mutex);
for (i=0, result=0; i<_LIBCAP_CAPABILITY_U32S; i++) {
result |=
((a->u[i].flat[CAP_EFFECTIVE] != b->u[i].flat[CAP_EFFECTIVE])
@@ -143,16 +156,21 @@ int cap_compare(cap_t a, cap_t b)
| ((a->u[i].flat[CAP_PERMITTED] != b->u[i].flat[CAP_PERMITTED])
? LIBCAP_PER : 0);
}
+ _cap_mu_unlock(&a->mutex);
+ cap_free(b);
return result;
}
/*
- * cap_fill copies a bit-vector of capability state in a cap_t from
- * one flag to another.
+ * cap_fill_flag copies a bit-vector of capability state in one cap_t from one
+ * flag to another flag of another cap_t.
*/
-int cap_fill(cap_t cap_d, cap_flag_t to, cap_flag_t from)
+int cap_fill_flag(cap_t cap_d, cap_flag_t to, cap_t ref, cap_flag_t from)
{
- if (!good_cap_t(cap_d)) {
+ int i;
+ cap_t orig;
+
+ if (!good_cap_t(cap_d) || !good_cap_t(ref)) {
errno = EINVAL;
return -1;
}
@@ -163,15 +181,31 @@ int cap_fill(cap_t cap_d, cap_flag_t to, cap_flag_t from)
return -1;
}
- int i;
+ orig = cap_dup(ref);
+ if (orig == NULL) {
+ return -1;
+ }
+
+ _cap_mu_lock(&cap_d->mutex);
for (i = 0; i < _LIBCAP_CAPABILITY_U32S; i++) {
- cap_d->u[i].flat[to] = cap_d->u[i].flat[from];
+ cap_d->u[i].flat[to] = orig->u[i].flat[from];
}
+ _cap_mu_unlock(&cap_d->mutex);
+ cap_free(orig);
return 0;
}
/*
+ * 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)
+{
+ return cap_fill_flag(cap_d, to, cap_d, from);
+}
+
+/*
* 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,
@@ -183,20 +217,25 @@ cap_flag_value_t cap_iab_get_vector(cap_iab_t iab, cap_iab_vector_t vec,
unsigned o = (bit >> 5);
__u32 mask = 1u << (bit & 31);
+ cap_flag_value_t ret;
+ _cap_mu_lock(&iab->mutex);
switch (vec) {
case CAP_IAB_INH:
- return !!(iab->i[o] & mask);
+ ret = !!(iab->i[o] & mask);
break;
case CAP_IAB_AMB:
- return !!(iab->a[o] & mask);
+ ret = !!(iab->a[o] & mask);
break;
case CAP_IAB_BOUND:
- return !!(iab->nb[o] & mask);
+ ret = !!(iab->nb[o] & mask);
break;
default:
- return 0;
+ ret = 0;
}
+ _cap_mu_unlock(&iab->mutex);
+
+ return ret;
}
/*
@@ -216,6 +255,7 @@ int cap_iab_set_vector(cap_iab_t iab, cap_iab_vector_t vec, cap_value_t bit,
__u32 on = 1u << (bit & 31);
__u32 mask = ~on;
+ _cap_mu_lock(&iab->mutex);
switch (vec) {
case CAP_IAB_INH:
iab->i[o] = (iab->i[o] & mask) | (raised ? on : 0);
@@ -230,9 +270,10 @@ int cap_iab_set_vector(cap_iab_t iab, cap_iab_vector_t vec, cap_value_t bit,
break;
default:
errno = EINVAL;
- return -1;
+ _cap_mu_unlock_return(&iab->mutex, -1);
}
+ _cap_mu_unlock(&iab->mutex);
return 0;
}
@@ -246,6 +287,8 @@ int cap_iab_set_vector(cap_iab_t iab, cap_iab_vector_t vec, cap_value_t bit,
int cap_iab_fill(cap_iab_t iab, cap_iab_vector_t vec,
cap_t cap_d, cap_flag_t flag)
{
+ int i, ret = 0;
+
if (!good_cap_t(cap_d) || !good_cap_iab_t(iab)) {
errno = EINVAL;
return -1;
@@ -261,8 +304,17 @@ int cap_iab_fill(cap_iab_t iab, cap_iab_vector_t vec,
return -1;
}
- int i;
- for (i = 0; i < _LIBCAP_CAPABILITY_U32S; i++) {
+ /*
+ * Make a private copy so we don't need to hold two locks at once
+ * avoiding a recipe for a deadlock.
+ */
+ cap_d = cap_dup(cap_d);
+ if (cap_d == NULL) {
+ return -1;
+ }
+
+ _cap_mu_lock(&iab->mutex);
+ for (i = 0; !ret && i < _LIBCAP_CAPABILITY_U32S; i++) {
switch (vec) {
case CAP_IAB_INH:
iab->i[i] = cap_d->u[i].flat[flag];
@@ -277,9 +329,41 @@ int cap_iab_fill(cap_iab_t iab, cap_iab_vector_t vec,
break;
default:
errno = EINVAL;
- return -1;
+ ret = -1;
+ break;
}
}
+ _cap_mu_unlock(&iab->mutex);
- return 0;
+ cap_free(cap_d);
+ return ret;
+}
+
+/*
+ * cap_iab_compare compares two iab tuples.
+ */
+int cap_iab_compare(cap_iab_t a, cap_iab_t b)
+{
+ int j, result;
+ if (!(good_cap_iab_t(a) && good_cap_iab_t(b))) {
+ _cap_debug("invalid arguments");
+ errno = EINVAL;
+ return -1;
+ }
+ b = cap_iab_dup(b);
+ if (b == NULL) {
+ return -1;
+ }
+
+ _cap_mu_lock(&a->mutex);
+ for (j=0, result=0; j<_LIBCAP_CAPABILITY_U32S; j++) {
+ result |=
+ (a->i[j] == b->i[j] ? 0 : (1 << CAP_IAB_INH)) |
+ (a->a[j] == b->a[j] ? 0 : (1 << CAP_IAB_AMB)) |
+ (a->nb[j] == b->nb[j] ? 0 : (1 << CAP_IAB_BOUND));
+ }
+ _cap_mu_unlock(&a->mutex);
+ cap_free(b);
+
+ return result;
}
diff --git a/libcap/cap_names.header b/libcap/cap_names.header
new file mode 100644
index 0000000..8d64f64
--- /dev/null
+++ b/libcap/cap_names.header
@@ -0,0 +1,5 @@
+struct __cap_token_s { const char *name; int index; };
+%{
+const struct __cap_token_s *__cap_lookup_name(const char *, size_t);
+%}
+%%
diff --git a/libcap/cap_proc.c b/libcap/cap_proc.c
index e12c8e6..24bc274 100644
--- a/libcap/cap_proc.c
+++ b/libcap/cap_proc.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997-8,2007,11,19,20 Andrew G Morgan <morgan@kernel.org>
+ * Copyright (c) 1997-8,2007,11,19-21 Andrew G Morgan <morgan@kernel.org>
*
* This file deals with getting and setting capabilities on processes.
*/
@@ -18,8 +18,6 @@
#include <sys/types.h>
#include <sys/wait.h>
-#include <linux/limits.h>
-
#include "libcap.h"
/*
@@ -137,7 +135,13 @@ static int _libcap_wprctl3(struct syscaller_s *sc,
long int pr_cmd, long int arg1, long int arg2)
{
if (_libcap_overrode_syscalls) {
- return sc->three(SYS_prctl, pr_cmd, arg1, arg2);
+ int result;
+ result = sc->three(SYS_prctl, pr_cmd, arg1, arg2);
+ if (result >= 0) {
+ return result;
+ }
+ errno = -result;
+ return -1;
}
return prctl(pr_cmd, arg1, arg2, 0, 0, 0);
}
@@ -147,7 +151,13 @@ static int _libcap_wprctl6(struct syscaller_s *sc,
long int arg3, long int arg4, long int arg5)
{
if (_libcap_overrode_syscalls) {
- return sc->six(SYS_prctl, pr_cmd, arg1, arg2, arg3, arg4, arg5);
+ int result;
+ result = sc->six(SYS_prctl, pr_cmd, arg1, arg2, arg3, arg4, arg5);
+ if (result >= 0) {
+ return result;
+ }
+ errno = -result;
+ return -1;
}
return prctl(pr_cmd, arg1, arg2, arg3, arg4, arg5);
}
@@ -183,7 +193,9 @@ static int _cap_set_proc(struct syscaller_s *sc, cap_t cap_d) {
}
_cap_debug("setting process capabilities");
+ _cap_mu_lock(&cap_d->mutex);
retval = _libcap_capset(sc, &cap_d->head, &cap_d->u[0].set);
+ _cap_mu_unlock(&cap_d->mutex);
return retval;
}
@@ -208,9 +220,11 @@ int capgetp(pid_t pid, cap_t cap_d)
_cap_debug("getting process capabilities for proc %d", pid);
+ _cap_mu_lock(&cap_d->mutex);
cap_d->head.pid = pid;
error = capget(&cap_d->head, &cap_d->u[0].set);
cap_d->head.pid = 0;
+ _cap_mu_unlock(&cap_d->mutex);
return error;
}
@@ -252,10 +266,12 @@ int capsetp(pid_t pid, cap_t cap_d)
}
_cap_debug("setting process capabilities for proc %d", pid);
+ _cap_mu_lock(&cap_d->mutex);
cap_d->head.pid = pid;
error = capset(&cap_d->head, &cap_d->u[0].set);
cap_d->head.version = _LIBCAP_CAPABILITY_VERSION;
cap_d->head.pid = 0;
+ _cap_mu_unlock(&cap_d->mutex);
return error;
}
@@ -267,26 +283,12 @@ int capsetp(pid_t pid, cap_t cap_d)
int cap_get_bound(cap_value_t cap)
{
- int result;
-
- result = prctl(PR_CAPBSET_READ, pr_arg(cap), pr_arg(0));
- if (result < 0) {
- errno = -result;
- return -1;
- }
- return result;
+ return prctl(PR_CAPBSET_READ, pr_arg(cap), pr_arg(0));
}
static int _cap_drop_bound(struct syscaller_s *sc, cap_value_t cap)
{
- int result;
-
- result = _libcap_wprctl3(sc, PR_CAPBSET_DROP, pr_arg(cap), pr_arg(0));
- if (result < 0) {
- errno = -result;
- return -1;
- }
- return result;
+ return _libcap_wprctl3(sc, PR_CAPBSET_DROP, pr_arg(cap), pr_arg(0));
}
/* drop a capability from the bounding set */
@@ -312,7 +314,7 @@ int cap_get_ambient(cap_value_t cap)
static int _cap_set_ambient(struct syscaller_s *sc,
cap_value_t cap, cap_flag_value_t set)
{
- int result, val;
+ int val;
switch (set) {
case CAP_SET:
val = PR_CAP_AMBIENT_RAISE;
@@ -324,13 +326,8 @@ static int _cap_set_ambient(struct syscaller_s *sc,
errno = EINVAL;
return -1;
}
- result = _libcap_wprctl6(sc, PR_CAP_AMBIENT, pr_arg(val), pr_arg(cap),
- pr_arg(0), pr_arg(0), pr_arg(0));
- if (result < 0) {
- errno = -result;
- return -1;
- }
- return result;
+ return _libcap_wprctl6(sc, PR_CAP_AMBIENT, pr_arg(val), pr_arg(cap),
+ pr_arg(0), pr_arg(0), pr_arg(0));
}
/*
@@ -355,14 +352,9 @@ static int _cap_reset_ambient(struct syscaller_s *sc)
}
}
- result = _libcap_wprctl6(sc, PR_CAP_AMBIENT,
- pr_arg(PR_CAP_AMBIENT_CLEAR_ALL),
- pr_arg(0), pr_arg(0), pr_arg(0), pr_arg(0));
- if (result < 0) {
- errno = -result;
- return -1;
- }
- return result;
+ return _libcap_wprctl6(sc, PR_CAP_AMBIENT,
+ pr_arg(PR_CAP_AMBIENT_CLEAR_ALL),
+ pr_arg(0), pr_arg(0), pr_arg(0), pr_arg(0));
}
/*
@@ -371,7 +363,7 @@ static int _cap_reset_ambient(struct syscaller_s *sc)
* case where the set is empty already but the ambient cap API is
* locked.
*/
-int cap_reset_ambient()
+int cap_reset_ambient(void)
{
return _cap_reset_ambient(&multithread);
}
@@ -443,13 +435,17 @@ static cap_value_t raise_cap_setpcap[] = {CAP_SETPCAP};
static int _cap_set_mode(struct syscaller_s *sc, cap_mode_t flavor)
{
- cap_t working = cap_get_proc();
+ int ret;
unsigned secbits = CAP_SECURED_BITS_AMBIENT;
+ cap_t working = cap_get_proc();
- int ret = cap_set_flag(working, CAP_EFFECTIVE,
- 1, raise_cap_setpcap, CAP_SET);
- ret = ret | _cap_set_proc(sc, working);
+ if (working == NULL) {
+ _cap_debug("getting current process' capabilities failed");
+ return -1;
+ }
+ ret = cap_set_flag(working, CAP_EFFECTIVE, 1, raise_cap_setpcap, CAP_SET) |
+ _cap_set_proc(sc, working);
if (ret == 0) {
cap_flag_t c;
@@ -483,7 +479,9 @@ static int _cap_set_mode(struct syscaller_s *sc, cap_mode_t flavor)
/* for good measure */
_cap_set_no_new_privs(sc);
break;
-
+ case CAP_MODE_HYBRID:
+ ret = _cap_set_secbits(sc, 0);
+ break;
default:
errno = EINVAL;
ret = -1;
@@ -519,13 +517,16 @@ cap_mode_t cap_get_mode(void)
{
unsigned secbits = cap_get_secbits();
+ if (secbits == 0) {
+ return CAP_MODE_HYBRID;
+ }
if ((secbits & CAP_SECURED_BITS_BASIC) != CAP_SECURED_BITS_BASIC) {
return CAP_MODE_UNCERTAIN;
}
/* validate ambient is not set */
int olderrno = errno;
- int ret = 0;
+ int ret = 0, cf;
cap_value_t c;
for (c = 0; !ret; c++) {
ret = cap_get_ambient(c);
@@ -534,6 +535,7 @@ cap_mode_t cap_get_mode(void)
if (c && secbits != CAP_SECURED_BITS_AMBIENT) {
return CAP_MODE_UNCERTAIN;
}
+ ret = 0;
break;
}
if (ret) {
@@ -541,11 +543,22 @@ cap_mode_t cap_get_mode(void)
}
}
+ /*
+ * Explore how capabilities differ from empty.
+ */
cap_t working = cap_get_proc();
cap_t empty = cap_init();
- int cf = cap_compare(empty, working);
+ if (working == NULL || empty == NULL) {
+ _cap_debug("working=%p, empty=%p - need both non-NULL", working, empty);
+ ret = -1;
+ } else {
+ cf = cap_compare(empty, working);
+ }
cap_free(empty);
cap_free(working);
+ if (ret != 0) {
+ return CAP_MODE_UNCERTAIN;
+ }
if (CAP_DIFFERS(cf, CAP_INHERITABLE)) {
return CAP_MODE_PURE1E;
@@ -571,6 +584,10 @@ static int _cap_setuid(struct syscaller_s *sc, uid_t uid)
{
const cap_value_t raise_cap_setuid[] = {CAP_SETUID};
cap_t working = cap_get_proc();
+ if (working == NULL) {
+ return -1;
+ }
+
(void) cap_set_flag(working, CAP_EFFECTIVE,
1, raise_cap_setuid, CAP_SET);
/*
@@ -626,6 +643,10 @@ static int _cap_setgroups(struct syscaller_s *sc,
{
const cap_value_t raise_cap_setgid[] = {CAP_SETGID};
cap_t working = cap_get_proc();
+ if (working == NULL) {
+ return -1;
+ }
+
(void) cap_set_flag(working, CAP_EFFECTIVE,
1, raise_cap_setgid, CAP_SET);
/*
@@ -684,9 +705,25 @@ int cap_setgroups(gid_t gid, size_t ngroups, const gid_t groups[])
*/
cap_iab_t cap_iab_get_proc(void)
{
- cap_iab_t iab = cap_iab_init();
- cap_t current = cap_get_proc();
+ cap_iab_t iab;
+ cap_t current;
+
+ iab = cap_iab_init();
+ if (iab == NULL) {
+ _cap_debug("no memory for IAB tuple");
+ return NULL;
+ }
+
+ current = cap_get_proc();
+ if (current == NULL) {
+ _cap_debug("no memory for cap_t");
+ cap_free(iab);
+ return NULL;
+ }
+
cap_iab_fill(iab, CAP_IAB_INH, current, CAP_INHERITABLE);
+ cap_free(current);
+
cap_value_t c;
for (c = cap_max_bits(); c; ) {
--c;
@@ -704,13 +741,17 @@ cap_iab_t cap_iab_get_proc(void)
/*
* _cap_iab_set_proc sets the iab collection using the requested syscaller.
+ * The iab value is locked by the caller.
*/
static int _cap_iab_set_proc(struct syscaller_s *sc, cap_iab_t iab)
{
- int ret, i;
- cap_t working, temp = cap_get_proc();
+ int ret, i, raising = 0;
cap_value_t c;
- int raising = 0;
+ cap_t working, temp = cap_get_proc();
+
+ if (temp == NULL) {
+ return -1;
+ }
for (i = 0; i < _LIBCAP_CAPABILITY_U32S; i++) {
__u32 newI = iab->i[i];
@@ -722,6 +763,10 @@ static int _cap_iab_set_proc(struct syscaller_s *sc, cap_iab_t iab)
}
working = cap_dup(temp);
+ if (working == NULL) {
+ ret = -1;
+ goto defer;
+ }
if (raising) {
ret = cap_set_flag(working, CAP_EFFECTIVE,
1, raise_cap_setpcap, CAP_SET);
@@ -770,7 +815,15 @@ defer:
*/
int cap_iab_set_proc(cap_iab_t iab)
{
- return _cap_iab_set_proc(&multithread, iab);
+ int retval;
+ if (!good_cap_iab_t(iab)) {
+ errno = EINVAL;
+ return -1;
+ }
+ _cap_mu_lock(&iab->mutex);
+ retval = _cap_iab_set_proc(&multithread, iab);
+ _cap_mu_unlock(&iab->mutex);
+ return retval;
}
/*
@@ -785,51 +838,94 @@ int cap_iab_set_proc(cap_iab_t iab)
* considered to have failed and the launch will be aborted - further,
* errno will be communicated to the parent.
*/
-void cap_launcher_callback(cap_launch_t attr, int (callback_fn)(void *detail))
+int cap_launcher_callback(cap_launch_t attr, int (callback_fn)(void *detail))
{
+ if (!good_cap_launch_t(attr)) {
+ errno = EINVAL;
+ return -1;
+ }
+ _cap_mu_lock(&attr->mutex);
attr->custom_setup_fn = callback_fn;
+ _cap_mu_unlock(&attr->mutex);
+ return 0;
}
/*
* cap_launcher_setuid primes the launcher to attempt a change of uid.
*/
-void cap_launcher_setuid(cap_launch_t attr, uid_t uid)
+int cap_launcher_setuid(cap_launch_t attr, uid_t uid)
{
+ if (!good_cap_launch_t(attr)) {
+ errno = EINVAL;
+ return -1;
+ }
+ _cap_mu_lock(&attr->mutex);
attr->uid = uid;
attr->change_uids = 1;
+ _cap_mu_unlock(&attr->mutex);
+ return 0;
}
/*
* cap_launcher_setgroups primes the launcher to attempt a change of
* gid and groups.
*/
-void cap_launcher_setgroups(cap_launch_t attr, gid_t gid,
- int ngroups, const gid_t *groups)
+int cap_launcher_setgroups(cap_launch_t attr, gid_t gid,
+ int ngroups, const gid_t *groups)
{
+ if (!good_cap_launch_t(attr)) {
+ errno = EINVAL;
+ return -1;
+ }
+ _cap_mu_lock(&attr->mutex);
attr->gid = gid;
attr->ngroups = ngroups;
attr->groups = groups;
attr->change_gids = 1;
+ _cap_mu_unlock(&attr->mutex);
+ return 0;
}
/*
* cap_launcher_set_mode primes the launcher to attempt a change of
* mode.
*/
-void cap_launcher_set_mode(cap_launch_t attr, cap_mode_t flavor)
+int cap_launcher_set_mode(cap_launch_t attr, cap_mode_t flavor)
{
+ if (!good_cap_launch_t(attr)) {
+ errno = EINVAL;
+ return -1;
+ }
+ _cap_mu_lock(&attr->mutex);
attr->mode = flavor;
attr->change_mode = 1;
+ _cap_mu_unlock(&attr->mutex);
+ return 0;
}
/*
- * cap_launcher_set_iab primes the launcher to attempt to change the iab bits of
- * the launched child.
+ * cap_launcher_set_iab primes the launcher to attempt to change the
+ * IAB values of the launched child. The launcher locks iab while it
+ * is owned by the launcher: this prevents the user from
+ * asynchronously changing its value while it is associated with the
+ * launcher.
*/
cap_iab_t cap_launcher_set_iab(cap_launch_t attr, cap_iab_t iab)
{
+ if (!good_cap_launch_t(attr)) {
+ errno = EINVAL;
+ return NULL;
+ }
+ _cap_mu_lock(&attr->mutex);
cap_iab_t old = attr->iab;
attr->iab = iab;
+ if (old != NULL) {
+ _cap_mu_unlock(&old->mutex);
+ }
+ if (iab != NULL) {
+ _cap_mu_lock(&iab->mutex);
+ }
+ _cap_mu_unlock(&attr->mutex);
return old;
}
@@ -837,15 +933,26 @@ cap_iab_t cap_launcher_set_iab(cap_launch_t attr, cap_iab_t iab)
* cap_launcher_set_chroot sets the intended chroot for the launched
* child.
*/
-void cap_launcher_set_chroot(cap_launch_t attr, const char *chroot)
+int cap_launcher_set_chroot(cap_launch_t attr, const char *chroot)
{
+ if (!good_cap_launch_t(attr)) {
+ errno = EINVAL;
+ return -1;
+ }
+ _cap_mu_lock(&attr->mutex);
attr->chroot = _libcap_strdup(chroot);
+ _cap_mu_unlock(&attr->mutex);
+ return 0;
}
static int _cap_chroot(struct syscaller_s *sc, const char *root)
{
const cap_value_t raise_cap_sys_chroot[] = {CAP_SYS_CHROOT};
cap_t working = cap_get_proc();
+ if (working == NULL) {
+ return -1;
+ }
+
(void) cap_set_flag(working, CAP_EFFECTIVE,
1, raise_cap_sys_chroot, CAP_SET);
int ret = _cap_set_proc(sc, working);
@@ -859,6 +966,9 @@ static int _cap_chroot(struct syscaller_s *sc, const char *root)
} else {
ret = chroot(root);
}
+ if (ret == 0) {
+ ret = chdir("/");
+ }
}
int olderrno = errno;
(void) cap_clear_flag(working, CAP_EFFECTIVE);
@@ -952,15 +1062,21 @@ pid_t cap_launch(cap_launch_t attr, void *detail) {
int ps[2];
pid_t child;
+ if (!good_cap_launch_t(attr)) {
+ errno = EINVAL;
+ return -1;
+ }
+ _cap_mu_lock(&attr->mutex);
+
/* The launch must have a purpose */
if (attr->custom_setup_fn == NULL &&
(attr->arg0 == NULL || attr->argv == NULL)) {
errno = EINVAL;
- return -1;
+ _cap_mu_unlock_return(&attr->mutex, -1);
}
if (pipe2(ps, O_CLOEXEC) != 0) {
- return -1;
+ _cap_mu_unlock_return(&attr->mutex, -1);
}
child = fork();
@@ -970,9 +1086,11 @@ pid_t cap_launch(cap_launch_t attr, void *detail) {
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);
+ /* no return from above function */
}
+
+ /* child has its own copy, and parent no longer needs it locked. */
+ _cap_mu_unlock(&attr->mutex);
close(ps[1]);
if (child < 0) {
goto defer;
diff --git a/libcap/cap_test.c b/libcap/cap_test.c
index a717217..68b6a13 100644
--- a/libcap/cap_test.c
+++ b/libcap/cap_test.c
@@ -1,12 +1,17 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+
#include "libcap.h"
static cap_value_t top;
-static int cf(cap_value_t x) {
+static int cf(cap_value_t x)
+{
return top - x - 1;
}
-static int test_cap_bits(void) {
+static int test_cap_bits(void)
+{
static cap_value_t vs[] = {
5, 6, 11, 12, 15, 16, 17, 38, 41, 63, 64, __CAP_MAXBITS+3, 0, -1
};
@@ -15,35 +20,49 @@ static int test_cap_bits(void) {
for (i = 0; vs[i] >= 0; i++) {
cap_value_t ans;
- top = i;
- _binary_search(ans, cf, 0, __CAP_MAXBITS, 0);
+ top = vs[i];
+ _binary_search(ans, cf, 0, __CAP_MAXBITS, -1);
if (ans != top) {
- if (top > __CAP_MAXBITS && ans == __CAP_MAXBITS) {
- } else {
- printf("test_cap_bits miscompared [%d] top=%d - got=%d\n",
- i, top, ans);
- failed = -1;
+ if (top == 0 && ans == -1) {
+ continue;
+ }
+ if (top > __CAP_MAXBITS && ans == -1) {
+ continue;
}
+ printf("test_cap_bits miscompared [%d] top=%d - got=%d\n",
+ i, top, ans);
+ failed = -1;
}
}
return failed;
}
-static int test_cap_flags(void) {
+static int test_cap_flags(void)
+{
cap_t c, d;
cap_flag_t f = CAP_INHERITABLE, t;
cap_value_t v;
+ int retval = 0;
c = cap_init();
if (c == NULL) {
printf("test_flags failed to allocate a set\n");
return -1;
}
+ if (cap_compare(c, NULL) != -1) {
+ printf("compare to NULL should give invalid\n");
+ return -1;
+ }
+ if (cap_compare(NULL, c) != -1) {
+ printf("compare with NULL should give invalid\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;
+ retval = -1;
+ goto drop_c;
}
}
@@ -51,28 +70,225 @@ static int test_cap_flags(void) {
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;
+ retval = -1;
+ goto drop_d;
}
if (cap_clear_flag(c, f)) {
printf("cap_fill unable to clear flag %d\n", f);
- return -1;
+ retval = -1;
+ goto drop_d;
}
f = t;
}
if (cap_compare(c, d)) {
printf("permuted cap_fill()ing failed to perform net no-op\n");
+ retval = -1;
+ }
+ if (cap_fill_flag(NULL, CAP_EFFECTIVE, c, CAP_INHERITABLE) == 0) {
+ printf("filling NULL flag should fail\n");
+ retval = -1;
+ }
+ if (cap_fill_flag(d, CAP_PERMITTED, c, CAP_INHERITABLE) != 0) {
+ perror("filling PERMITEED flag should work");
+ retval = -1;
+ }
+ if (cap_fill_flag(c, CAP_PERMITTED, d, CAP_PERMITTED) != 0) {
+ perror("filling PERMITTED flag from another cap_t should work");
+ retval = -1;
+ }
+ if (cap_compare(c, d)) {
+ printf("permuted cap_fill()ing failed to perform net no-op\n");
+ retval = -1;
+ }
+
+drop_d:
+ if (cap_free(d) != 0) {
+ perror("failed to free d");
+ retval = -1;
+ }
+drop_c:
+ if (cap_free(c) != 0) {
+ perror("failed to free c");
+ retval = -1;
+ }
+ return retval;
+}
+
+static int test_short_bits(void)
+{
+ int result = 0;
+ char *tmp;
+ int n = asprintf(&tmp, "%d", __CAP_MAXBITS);
+ if (n <= 0) {
+ return -1;
+ }
+ if (strlen(tmp) > __CAP_NAME_SIZE) {
+ printf("cap_to_text buffer size reservation needs fixing (%ld > %d)\n",
+ (long int)strlen(tmp), __CAP_NAME_SIZE);
+ result = -1;
+ }
+ free(tmp);
+ return result;
+}
+
+static int noop(void *data)
+{
+ return -1;
+}
+
+static int test_alloc(void)
+{
+ int retval = 0;
+ cap_t c;
+ cap_iab_t iab;
+ cap_launch_t launcher;
+ char *old_root;
+
+ printf("test_alloc\n");
+ fflush(stdout);
+
+ c = cap_init();
+ if (c == NULL) {
+ perror("failed to allocate a cap_t");
+ fflush(stderr);
return -1;
}
- cap_free(d);
- cap_free(c);
- return 0;
+
+ iab = cap_iab_init();
+ if (iab == NULL) {
+ perror("failed to allocate a cap_iab_t");
+ fflush(stderr);
+ retval = -1;
+ goto drop_c;
+ }
+
+ launcher = cap_func_launcher(noop);
+ if (launcher == NULL) {
+ perror("failde to allocate a launcher");
+ fflush(stderr);
+ retval = -1;
+ goto drop_iab;
+ }
+
+ cap_launcher_set_chroot(launcher, "/tmp");
+ if (cap_launcher_set_iab(launcher, iab) != NULL) {
+ printf("unable to replace iab in launcher\n");
+ fflush(stdout);
+ retval = -1;
+ goto drop_iab;
+ }
+
+ iab = cap_launcher_set_iab(launcher, cap_iab_init());
+ if (iab == NULL) {
+ printf("unable to recover iab in launcher\n");
+ fflush(stdout);
+ retval = -1;
+ goto drop_launcher;
+ }
+
+ old_root = cap_proc_root("blah");
+ if (old_root != NULL) {
+ printf("bad initial proc_root [%s]\n", old_root);
+ fflush(stdout);
+ retval = -1;
+ }
+ if (cap_free(old_root)) {
+ perror("unable to free old proc root");
+ fflush(stderr);
+ retval = -1;
+ }
+ if (retval) {
+ goto drop_launcher;
+ }
+ old_root = cap_proc_root("/proc");
+ if (strcmp(old_root, "blah") != 0) {
+ printf("bad proc_root value [%s]\n", old_root);
+ fflush(stdout);
+ retval = -1;
+ }
+ if (cap_free(old_root)) {
+ perror("unable to free replacement proc root");
+ fflush(stderr);
+ retval = -1;
+ }
+ if (retval) {
+ goto drop_launcher;
+ }
+
+drop_launcher:
+ printf("test_alloc: drop_launcher\n");
+ fflush(stdout);
+ if (cap_free(launcher)) {
+ perror("failed to free launcher");
+ fflush(stderr);
+ retval = -1;
+ }
+
+drop_iab:
+ printf("test_alloc: drop_iab\n");
+ fflush(stdout);
+ if (!cap_free(2+(__u32 *) iab)) {
+ printf("unable to recognize bad cap_iab_t pointer\n");
+ fflush(stdout);
+ retval = -1;
+ }
+ if (cap_free(iab)) {
+ perror("failed to free iab");
+ fflush(stderr);
+ retval = -1;
+ }
+
+drop_c:
+ printf("test_alloc: drop_cap\n");
+ fflush(stdout);
+ if (!cap_free(1+(__u32 *) c)) {
+ printf("unable to recognize bad cap_t pointer\n");
+ fflush(stdout);
+ retval = -1;
+ }
+ if (cap_free(c)) {
+ perror("failed to free c");
+ fflush(stderr);
+ retval = -1;
+ }
+ return retval;
+}
+
+static int test_prctl(void)
+{
+ int ret, retval=0;
+ errno = 0;
+ ret = cap_get_bound((cap_value_t) -1);
+ if (ret != -1) {
+ printf("cap_get_bound(-1) did not return error: %d\n", ret);
+ retval = -1;
+ } else if (errno != EINVAL) {
+ perror("cap_get_bound(-1) errno != EINVAL");
+ retval = -1;
+ }
+ return retval;
}
int main(int argc, char **argv) {
int result = 0;
+ printf("test_cap_bits: being called\n");
+ fflush(stdout);
result = test_cap_bits() | result;
+ printf("test_cap_flags: being called\n");
+ fflush(stdout);
result = test_cap_flags() | result;
+ printf("test_short_bits: being called\n");
+ fflush(stdout);
+ result = test_short_bits() | result;
+ printf("test_alloc: being called\n");
+ fflush(stdout);
+ result = test_alloc() | result;
+ printf("test_prctl: being called\n");
+ fflush(stdout);
+ result = test_prctl() | result;
+ printf("tested\n");
+ fflush(stdout);
if (result) {
printf("cap_test FAILED\n");
diff --git a/libcap/cap_text.c b/libcap/cap_text.c
index 87e0838..7566bd8 100644
--- a/libcap/cap_text.c
+++ b/libcap/cap_text.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997-8,2007-8,2019 Andrew G Morgan <morgan@kernel.org>
+ * Copyright (c) 1997-8,2007-8,2019,2021 Andrew G Morgan <morgan@kernel.org>
* Copyright (c) 1997 Andrew Main <zefram@dcs.warwick.ac.uk>
*
* This file deals with exchanging internal and textual
@@ -100,7 +100,7 @@ static int lookupname(char const **strp)
return n;
} else {
int c;
- unsigned len;
+ size_t len;
for (len=0; (c = str.constp[len]); ++len) {
if (!(isalpha(c) || (c == '_'))) {
@@ -160,6 +160,7 @@ cap_t cap_from_text(const char *str)
cap_blks = _LINUX_CAPABILITY_U32S_3;
break;
default:
+ cap_free(res);
errno = EINVAL;
return NULL;
}
@@ -219,7 +220,7 @@ cap_t cap_from_text(const char *str)
/* cycle through list of actions */
do {
- _cap_debug("next char = `%c'", *str);
+ _cap_debug("next char = '%c'", *str);
if (*str && !isspace(*str)) {
switch (*str++) { /* Effective, Inheritable, Permitted */
case 'e':
@@ -309,20 +310,19 @@ int cap_from_name(const char *name, cap_value_t *value_p)
*/
char *cap_to_name(cap_value_t cap)
{
- if ((cap < 0) || (cap >= __CAP_BITS)) {
-#if UINT_MAX != 4294967295U
-# error Recompile with correctly sized numeric array
-#endif
- char *tmp, *result;
-
- (void) asprintf(&tmp, "%u", cap);
- result = _libcap_strdup(tmp);
- free(tmp);
+ char *tmp, *result;
- return result;
- } else {
+ if ((cap >= 0) && (cap < __CAP_BITS)) {
return _libcap_strdup(_cap_names[cap]);
}
+ if (asprintf(&tmp, "%u", cap) <= 0) {
+ _cap_debug("asprintf filed");
+ return NULL;
+ }
+
+ result = _libcap_strdup(tmp);
+ free(tmp);
+ return result;
}
/*
@@ -348,6 +348,12 @@ static int getstateflags(cap_t caps, int capno)
return f;
}
+/*
+ * This code assumes that the longest named capability is longer than
+ * the decimal text representation of __CAP_MAXBITS. This is very true
+ * at the time of writing and likely to remain so. However, we have
+ * a test in cap_text to validate it at build time.
+ */
#define CAP_TEXT_BUFFER_ZONE 100
char *cap_to_text(cap_t caps, ssize_t *length_p)
@@ -398,6 +404,9 @@ char *cap_to_text(cap_t caps, ssize_t *length_p)
for (n = 0; n < cmb; n++) {
if (getstateflags(caps, n) == t) {
char *this_cap_name = cap_to_name(n);
+ if (this_cap_name == NULL) {
+ return NULL;
+ }
if ((strlen(this_cap_name) + (p - buf)) > CAP_TEXT_SIZE) {
cap_free(this_cap_name);
errno = ERANGE;
@@ -450,6 +459,9 @@ char *cap_to_text(cap_t caps, ssize_t *length_p)
for (n = cmb; n < __CAP_MAXBITS; n++) {
if (getstateflags(caps, n) == t) {
char *this_cap_name = cap_to_name(n);
+ if (this_cap_name == NULL) {
+ return NULL;
+ }
if ((strlen(this_cap_name) + (p - buf)) > CAP_TEXT_SIZE) {
cap_free(this_cap_name);
errno = ERANGE;
@@ -491,6 +503,8 @@ const char *cap_mode_name(cap_mode_t flavor) {
return "PURE1E";
case CAP_MODE_UNCERTAIN:
return "UNCERTAIN";
+ case CAP_MODE_HYBRID:
+ return "HYBRID";
default:
return "UNKNOWN";
}
@@ -508,6 +522,7 @@ char *cap_iab_to_text(cap_iab_t iab)
int first = 1;
if (good_cap_iab_t(iab)) {
+ _cap_mu_lock(&iab->mutex);
for (c = 0; c < cmb; c++) {
int keep = 0;
int o = c >> 5;
@@ -541,6 +556,7 @@ char *cap_iab_to_text(cap_iab_t iab)
first = 0;
}
}
+ _cap_mu_unlock(&iab->mutex);
}
*p = '\0';
return _libcap_strdup(buf);
@@ -549,6 +565,9 @@ char *cap_iab_to_text(cap_iab_t iab)
cap_iab_t cap_iab_from_text(const char *text)
{
cap_iab_t iab = cap_iab_init();
+ if (iab == NULL) {
+ return iab;
+ }
if (text != NULL) {
unsigned flags;
for (flags = 0; *text; text++) {
@@ -604,3 +623,148 @@ cleanup:
errno = EINVAL;
return NULL;
}
+
+static __u32 _parse_hex32(const char *c)
+{
+ int i;
+ __u32 v = 0;
+ for (i=0; i < 8; i++, c++) {
+ v <<= 4;
+ if (*c == 0 || *c < '0') {
+ return 0;
+ } else if (*c <= '9') {
+ v += *c - '0';
+ } else if (*c > 'f') {
+ return 0;
+ } else if (*c >= 'a') {
+ v += *c + 10 - 'a';
+ } else if (*c < 'A') {
+ return 0;
+ } else if (*c <= 'F') {
+ v += *c + 10 - 'A';
+ } else {
+ return 0;
+ }
+ }
+ return v;
+}
+
+/*
+ * _parse_vec_string converts the hex dumps in /proc/<pid>/current into
+ * an array of u32s - masked as per the forceall() mask.
+ */
+static __u32 _parse_vec_string(__u32 *vals, const char *c, int invert)
+{
+ int i;
+ int words = strlen(c)/8;
+ if (words > _LIBCAP_CAPABILITY_U32S) {
+ return 0;
+ }
+ forceall(vals, ~0, words);
+ for (i = 0; i < words; i++) {
+ __u32 val = _parse_hex32(c+8*(words-1-i));
+ if (invert) {
+ val = ~val;
+ }
+ vals[i] &= val;
+ }
+ return ~0;
+}
+
+/*
+ * libcap believes this is the root of the mounted "/proc"
+ * filesystem. (NULL == "/proc".)
+ */
+static char *_cap_proc_dir;
+
+/*
+ * If the constructor is called (see cap_alloc.c) then we'll need the
+ * corresponding destructor.
+ */
+__attribute__((destructor (300))) static void _cleanup_libcap(void)
+{
+ if (_cap_proc_dir == NULL) {
+ return;
+ }
+ cap_free(_cap_proc_dir);
+ _cap_proc_dir = NULL;
+}
+
+/*
+ * cap_proc_root reads and (optionally: when root != NULL) changes
+ * libcap's notion of where the "/proc" filesystem is mounted. It
+ * defaults to the value "/proc". Note, this is a global value and not
+ * considered thread safe to write - so the client should take
+ * suitable care when changing it. Further, libcap will allocate
+ * memory for storing the replacement root, and it is this memory that
+ * is returned. So, when changing the value, the caller should
+ * cap_free(the-return-value) when done with it.
+ *
+ * A return value of NULL implies the default is in effect "/proc".
+ */
+char *cap_proc_root(const char *root)
+{
+ char *old = _cap_proc_dir;
+ if (root != NULL) {
+ _cap_proc_dir = _libcap_strdup(root);
+ }
+ return old;
+}
+
+#define PROC_LINE_MAX (8 + 8*_LIBCAP_CAPABILITY_U32S + 100)
+/*
+ * cap_iab_get_pid fills an IAB tuple from the content of
+ * /proc/<pid>/status. Linux doesn't support syscall access to the
+ * needed information, so we parse it out of that file.
+ */
+cap_iab_t cap_iab_get_pid(pid_t pid)
+{
+ cap_iab_t iab;
+ char *path;
+ FILE *file;
+ char line[PROC_LINE_MAX];
+ const char *proc_root = _cap_proc_dir;
+
+ if (proc_root == NULL) {
+ proc_root = "/proc";
+ }
+ if (asprintf(&path, "%s/%d/status", proc_root, pid) <= 0) {
+ return NULL;
+ }
+ file = fopen(path, "r");
+ free(path);
+ if (file == NULL) {
+ return NULL;
+ }
+
+ iab = cap_iab_init();
+ uint ok = 0;
+ if (iab != NULL) {
+ while (fgets(line, PROC_LINE_MAX-1, file) != NULL) {
+ if (strncmp("Cap", line, 3) != 0) {
+ continue;
+ }
+ if (strncmp("Inh:\t", line+3, 5) == 0) {
+ ok = (_parse_vec_string(iab->i, line+8, 0) &
+ LIBCAP_IAB_I_FLAG) | ok;
+ continue;
+ }
+ if (strncmp("Bnd:\t", line+3, 5) == 0) {
+ ok = (_parse_vec_string(iab->nb, line+8, 1) &
+ LIBCAP_IAB_NB_FLAG) | ok;
+ continue;
+ }
+ if (strncmp("Amb:\t", line+3, 5) == 0) {
+ ok = (_parse_vec_string(iab->a, line+8, 0) &
+ LIBCAP_IAB_A_FLAG) | ok;
+ continue;
+ }
+ }
+ }
+ if (ok != (LIBCAP_IAB_IA_FLAG | LIBCAP_IAB_NB_FLAG)) {
+ cap_free(iab);
+ iab = NULL;
+ }
+ fclose(file);
+ return iab;
+}
diff --git a/libcap/execable.c b/libcap/execable.c
index be18914..9f7062e 100644
--- a/libcap/execable.c
+++ b/libcap/execable.c
@@ -1,10 +1,48 @@
#include <stdio.h>
+#include <stdlib.h>
+#include <sys/capability.h>
+
#include "execable.h"
+static void usage(int status)
+{
+ printf("\nusage: libcap.so [--help|--usage|--summary]\n");
+ exit(status);
+}
+
+static void summary(void)
+{
+ cap_value_t bits = cap_max_bits(), c;
+ cap_mode_t mode = cap_get_mode();
+
+ printf("\nCurrent mode: %s\n", cap_mode_name(mode));
+ printf("Number of cap values known to: this libcap=%d, running kernel=%d\n",
+ CAP_LAST_CAP+1, bits);
+
+ if (bits > CAP_LAST_CAP+1) {
+ printf("=> Consider upgrading libcap to name:");
+ for (c = CAP_LAST_CAP+1; c < bits; c++) {
+ printf(" %d", c);
+ }
+ } else if (bits < CAP_LAST_CAP+1) {
+ printf("=> Newer kernels also provide support for:");
+ for (c = bits; c <= CAP_LAST_CAP; c++) {
+ char *name = cap_to_name(c);
+ printf(" %s", name);
+ cap_free(name);
+ }
+ } else {
+ return;
+ }
+ printf("\n");
+}
+
SO_MAIN(int argc, char **argv)
{
+ int i;
const char *cmd = "This library";
- if (argv != NULL) {
+
+ if (argv != NULL && argv[0] != NULL) {
cmd = argv[0];
}
printf("%s is the shared library version: " LIBRARY_VERSION ".\n"
@@ -12,4 +50,15 @@ SO_MAIN(int argc, char **argv)
"More information on this library is available from:\n"
"\n"
" https://sites.google.com/site/fullycapable/\n", cmd);
+
+ for (i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "--usage") || !strcmp(argv[i], "--help")) {
+ usage(0);
+ }
+ if (!strcmp(argv[i], "--summary")) {
+ summary();
+ continue;
+ }
+ usage(1);
+ }
}
diff --git a/libcap/execable.h b/libcap/execable.h
index 0bcc5d4..7a2d247 100644
--- a/libcap/execable.h
+++ b/libcap/execable.h
@@ -71,23 +71,42 @@ static void __execable_parse_args(int *argc_p, char ***argv_p)
}
/*
+ * Linux x86 ABI requires the stack be 16 byte aligned. Keep things
+ * simple and just force it.
+ */
+#if defined(__i386__) || defined(__x86_64__)
+#define __SO_FORCE_ARG_ALIGNMENT __attribute__((force_align_arg_pointer))
+#else
+#define __SO_FORCE_ARG_ALIGNMENT
+#endif /* def some x86 */
+
+/*
+ * Permit the compiler to override this one.
+ */
+#ifndef EXECABLE_INITIALIZE
+#define EXECABLE_INITIALIZE do { } while(0)
+#endif /* ndef EXECABLE_INITIALIZE */
+
+/*
* 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); \
+#define SO_MAIN \
+static void __execable_main(int, char**); \
+__attribute__((visibility ("hidden"))) \
+void __so_start(void); \
+__SO_FORCE_ARG_ALIGNMENT \
+void __so_start(void) \
+{ \
+ int argc; \
+ char **argv; \
+ __execable_parse_args(&argc, &argv); \
+ EXECABLE_INITIALIZE; \
__execable_main(argc, argv); \
- if (argc != 0) { \
- free(argv[0]); \
- free(argv); \
- } \
- exit(0); \
-} \
+ 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 d172ddc..2db9972 100644
--- a/libcap/include/sys/capability.h
+++ b/libcap/include/sys/capability.h
@@ -2,7 +2,7 @@
* <sys/capability.h>
*
* Copyright (C) 1997 Aleph One
- * Copyright (C) 1997,8, 2008,19,20 Andrew G. Morgan <morgan@kernel.org>
+ * Copyright (C) 1997,8, 2008,19-22 Andrew G. Morgan <morgan@kernel.org>
*
* defunct POSIX.1e Standard: 25.2 Capabilities <sys/capability.h>
*/
@@ -15,13 +15,18 @@ extern "C" {
#endif
/*
+ * Provide a programmatic way to #ifdef around features.
+ */
+#define LIBCAP_MAJOR 2
+#define LIBCAP_MINOR 69
+
+/*
* This file complements the kernel file by providing prototype
* information for the user library.
*/
#include <sys/types.h>
#include <stdint.h>
-#include <linux/types.h>
#ifndef __user
#define __user
@@ -54,6 +59,26 @@ typedef int cap_value_t;
extern cap_value_t cap_max_bits(void);
/*
+ * cap_proc_root reads and (optionally: when root != NULL) changes
+ * libcap's notion of where the "/proc" filesystem is mounted. When
+ * the return value is NULL, it should be interpreted as the
+ * value "/proc".
+ *
+ * Note, this is a global value and not considered thread safe to
+ * write - so the client should take suitable care when changing
+ * it.
+ *
+ * Further, libcap will allocate a memory copy for storing the
+ * replacement root, and it is this kind of memory that is returned.
+ * So, when changing the value, the caller should
+ * cap_free(the-return-value) else cause a memory leak.
+ *
+ * Note, the library uses a destructor to clean up the live allocated
+ * value of the working setting.
+ */
+extern char *cap_proc_root(const char *root);
+
+/*
* Set identifiers
*/
typedef enum {
@@ -102,11 +127,13 @@ typedef unsigned cap_mode_t;
#define CAP_MODE_NOPRIV ((cap_mode_t) 1)
#define CAP_MODE_PURE1E_INIT ((cap_mode_t) 2)
#define CAP_MODE_PURE1E ((cap_mode_t) 3)
+#define CAP_MODE_HYBRID ((cap_mode_t) 4)
/* libcap/cap_alloc.c */
extern cap_t cap_dup(cap_t);
extern int cap_free(void *);
extern cap_t cap_init(void);
+extern cap_iab_t cap_iab_dup(cap_iab_t);
extern cap_iab_t cap_iab_init(void);
/* libcap/cap_flag.c */
@@ -115,10 +142,14 @@ 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_flag(cap_t cap_d, cap_flag_t to,
+ cap_t ref, cap_flag_t from);
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);
+#define CAP_IAB_DIFFERS(result, vector) (((result) & (1 << (vector))) != 0)
+extern int cap_iab_compare(cap_iab_t, cap_iab_t);
extern cap_flag_value_t cap_iab_get_vector(cap_iab_t, cap_iab_vector_t,
cap_value_t);
@@ -185,6 +216,7 @@ extern int cap_setuid(uid_t uid);
extern int cap_setgroups(gid_t gid, size_t ngroups, const gid_t groups[]);
extern cap_iab_t cap_iab_get_proc(void);
+extern cap_iab_t cap_iab_get_pid(pid_t);
extern int cap_iab_set_proc(cap_iab_t iab);
typedef struct cap_launch_s *cap_launch_t;
@@ -192,14 +224,14 @@ 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);
-extern void cap_launcher_setgroups(cap_launch_t attr, gid_t gid,
- int ngroups, const gid_t *groups);
-extern void cap_launcher_set_mode(cap_launch_t attr, cap_mode_t flavor);
+extern int cap_launcher_callback(cap_launch_t attr,
+ int (callback_fn)(void *detail));
+extern int cap_launcher_setuid(cap_launch_t attr, uid_t uid);
+extern int cap_launcher_setgroups(cap_launch_t attr, gid_t gid,
+ int ngroups, const gid_t *groups);
+extern int 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 int cap_launcher_set_chroot(cap_launch_t attr, const char *chroot);
extern pid_t cap_launch(cap_launch_t attr, void *detail);
/*
diff --git a/libcap/include/uapi/linux/capability.h b/libcap/include/uapi/linux/capability.h
index 09b5563..56c9180 100644
--- a/libcap/include/uapi/linux/capability.h
+++ b/libcap/include/uapi/linux/capability.h
@@ -14,7 +14,9 @@
#ifndef _UAPI_LINUX_CAPABILITY_H
#define _UAPI_LINUX_CAPABILITY_H
-#include <linux/types.h>
+#include <stdint.h>
+#define __u32 uint32_t
+#define __le32 __u32
/* User-level do most of the mapping between kernel and user
capabilities based on the version tag given by the kernel. The
@@ -422,5 +424,4 @@ struct vfs_ns_cap_data {
#define CAP_TO_INDEX(x) ((x) >> 5) /* 1 << 5 == bits in __u32 */
#define CAP_TO_MASK(x) (1u << ((x) & 31)) /* mask for indexed __u32 */
-
#endif /* _UAPI_LINUX_CAPABILITY_H */
diff --git a/libcap/libcap.h b/libcap/libcap.h
index 67fa0d0..f4a72fe 100644
--- a/libcap/libcap.h
+++ b/libcap/libcap.h
@@ -9,6 +9,7 @@
#define LIBCAP_H
#include <errno.h>
+#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -113,6 +114,7 @@ struct _cap_vfs_cap_data {
#define CAP_T_MAGIC 0xCA90D0
struct _cap_struct {
+ __u8 mutex;
struct __user_cap_header_struct head;
union {
struct __user_cap_data_struct set;
@@ -121,6 +123,24 @@ struct _cap_struct {
uid_t rootid;
};
+/*
+ * Elementary exclusive locking primatives for situations where
+ * linking with pthreads needs it, but such linking is not common.
+ *
+ * _cap_mu_blocked(x) attempts to lock x but if already locked, returns true
+ * _cap_mu_lock(x) attempts to lock and waits until the lock is granted
+ * _cap_mu_unlock(x) unconditionally unlocks the lock
+ * _cap_mu_unlock_return(x, y) unlock lock x and return value y
+ */
+#define _cap_mu_blocked(x) \
+ __atomic_test_and_set((void *)(x), __ATOMIC_SEQ_CST)
+#define _cap_mu_lock(x) \
+ while (_cap_mu_blocked(x)) sched_yield()
+#define _cap_mu_unlock(x) \
+ __atomic_clear((void *) (x), __ATOMIC_SEQ_CST)
+#define _cap_mu_unlock_return(x, y) \
+ do { _cap_mu_unlock(x); return (y); } while (0)
+
/* the maximum bits supportable */
#define __CAP_MAXBITS (__CAP_BLKS * 32)
@@ -128,10 +148,15 @@ struct _cap_struct {
#define CAP_S_MAGIC 0xCA95D0
/* iab set magic for cap_free */
-#define CAP_IAB_MAGIC 0xCA9AB
+#define CAP_IAB_MAGIC 0xCA91AB
/* launcher magic for cap_free */
-#define CAP_LAUNCH_MAGIC 0xCA91A
+#define CAP_LAUNCH_MAGIC 0xCA91AC
+
+#define magic_of(x) ((x) ? *(-2 + (const __u32 *) x) : 0)
+#define good_cap_t(x) (CAP_T_MAGIC == magic_of(x))
+#define good_cap_iab_t(x) (CAP_IAB_MAGIC == magic_of(x))
+#define good_cap_launch_t(x) (CAP_LAUNCH_MAGIC == magic_of(x))
/*
* kernel API cap set abstraction
@@ -142,16 +167,6 @@ struct _cap_struct {
#define isset_cap(y, x, set) ((y)->u[(x) >> 5].flat[set] & (1u << ((x)&31)))
/*
- * Private definitions for internal use by the library.
- */
-
-#define __libcap_check_magic(c,magic) ((c) && *(-1+(__u32 *)(c)) == (magic))
-#define good_cap_t(c) __libcap_check_magic(c, CAP_T_MAGIC)
-#define good_cap_string(c) __libcap_check_magic(c, CAP_S_MAGIC)
-#define good_cap_iab_t(c) __libcap_check_magic(c, CAP_IAB_MAGIC)
-#define good_cap_launch_t(c) __libcap_check_magic(c, CAP_LAUNCH_MAGIC)
-
-/*
* These match CAP_DIFFERS() expectations
*/
#define LIBCAP_EFF (1 << CAP_EFFECTIVE)
@@ -187,6 +202,9 @@ struct _cap_struct {
#endif /* DEBUG */
extern char *_libcap_strdup(const char *text);
+extern void _libcap_initialize(void);
+
+#define EXECABLE_INITIALIZE _libcap_initialize()
/*
* These are semi-public prototypes, they will only be defined in
@@ -227,7 +245,7 @@ extern int capsetp(pid_t pid, cap_t cap_d);
min = mid + 1; \
} \
} \
- val = min ? min : fallback; \
+ val = min ? (min <= high ? min : fallback) : fallback; \
} while(0)
/*
@@ -238,6 +256,7 @@ extern int capsetp(pid_t pid, cap_t cap_d);
* applied.
*/
struct cap_iab_s {
+ __u8 mutex;
__u32 i[_LIBCAP_CAPABILITY_U32S];
__u32 a[_LIBCAP_CAPABILITY_U32S];
__u32 nb[_LIBCAP_CAPABILITY_U32S];
@@ -254,6 +273,7 @@ struct cap_iab_s {
* multithreaded applications.
*/
struct cap_launch_s {
+ __u8 mutex;
/*
* Once forked but before active privilege is changed, this
* function (if non-NULL) is called.
diff --git a/libcap/psx_exec.c b/libcap/psx_exec.c
new file mode 100644
index 0000000..5e7a88f
--- /dev/null
+++ b/libcap/psx_exec.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 && argv[0] != 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/pam_cap/.gitignore b/pam_cap/.gitignore
index ef9e57f..dac617b 100644
--- a/pam_cap/.gitignore
+++ b/pam_cap/.gitignore
@@ -3,3 +3,5 @@ testlink
test_pam_cap
lazylink.so
pam_cap_linkopts
+LIBCAP
+incapable.conf
diff --git a/pam_cap/License b/pam_cap/License
index e88aa3f..6c20dc0 100644
--- a/pam_cap/License
+++ b/pam_cap/License
@@ -1,3 +1,5 @@
+/* SPDX-License-Identifier: BSD-3-Clause OR LGPL-2.0-or-later */
+
Unless otherwise *explicitly* stated the following text describes the
licensed conditions under which the contents of this module release
may be distributed:
diff --git a/pam_cap/Makefile b/pam_cap/Makefile
index 689239e..258e519 100644
--- a/pam_cap/Makefile
+++ b/pam_cap/Makefile
@@ -3,6 +3,9 @@
topdir=$(shell pwd)/..
include ../Make.Rules
+# Always build pam_cap sources this way:
+CFLAGS += -fPIC
+
all: pam_cap.so
$(MAKE) testlink
@@ -14,10 +17,14 @@ install: all
$(MAKE) -C ../libcap loader.txt
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 $@
+ $(CC) $(CFLAGS) $(CPPFLAGS) -DLIBCAP_VERSION=\"libcap-$(VERSION).$(MINOR)\" -DSHARED_LOADER=\"$(shell cat ../libcap/loader.txt)\" -c execable.c -o $@
+
+LIBCAP:
+ $(MAKE) -C ../libcap all
+ touch $@
-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)
+pam_cap.so: pam_cap.o execable.o pam_cap_linkopts LIBCAP
+ cat pam_cap_linkopts | xargs -e $(LD) $(LDFLAGS) -o $@ pam_cap.o execable.o $(LIBCAPLIB)
# Some distributions force link everything at compile time, and don't
# take advantage of libpam's dlopen runtime options to resolve ill
@@ -28,43 +35,59 @@ pam_cap.so: pam_cap.o execable.o pam_cap_linkopts
#
# 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...
+# If the current build environment is one of those, or we can't
+# reliably prove it isn't, extend the link options for pam_cap.so to
+# force linkage against libpam and the gazillion other things libpam
+# is linked against...
+#
+# If you want to force this behavior one way or the other, use the
+# make FORCELINKPAM=yes or FORCELINKPAM=no override.
+ifeq ($(FORCELINKPAM),yes)
+pam_cap_linkopts: Makefile
+ echo "-Wl,-e,__so_start -lpam" > $@
+else
+ifeq ($(FORCELINKPAM),no)
+pam_cap_linkopts: Makefile
+ echo "-Wl,-e,__so_start" > $@
+else
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 $@
+ $(LD) -o $@ $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) lazylink.c -DSHARED_LOADER=\"$(shell cat ../libcap/loader.txt)\" -Wl,-e,__so_start
+endif
+endif
../libcap/libcap.a:
$(MAKE) -C ../libcap libcap.a
+# Avoid $(LDFLAGS) here to avoid conflicts with --static for a in-tree
+# test binary.
test_pam_cap: test_pam_cap.c pam_cap.c ../libcap/libcap.a
- $(CC) $(CFLAGS) $(IPATH) -o $@ test_pam_cap.c $(LIBCAPLIB) $(LDFLAGS) --static
+ $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ test_pam_cap.c $(LIBCAPLIB) --static
-testlink: test.c pam_cap.o
- $(CC) $(CFLAGS) -o $@ $+ -lpam -ldl $(LIBCAPLIB) $(LDFLAGS)
+testlink: test.o pam_cap.o
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $+ -lpam -ldl $(LIBCAPLIB)
-test: testlink test_pam_cap pam_cap.so
- $(MAKE) testlink
+incapable.conf:
+ echo "^cap_setuid alpha" > $@ && chmod o+w $@
+
+test: testlink test_pam_cap pam_cap.so incapable.conf
./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
- sudo ./test_pam_cap root 0x0 0x0 0x0 config=./sudotest.conf
- sudo ./test_pam_cap alpha 0x0 0x0 0x0 config=./capability.conf
- sudo ./test_pam_cap alpha 0x0 0x1 0x80 config=./sudotest.conf
- sudo ./test_pam_cap beta 0x0 0x1 0x0 config=./sudotest.conf
- sudo ./test_pam_cap gamma 0x0 0x0 0x81 config=./sudotest.conf
- sudo ./test_pam_cap delta 0x41 0x80 0x41 config=./sudotest.conf
+sudotest: test_pam_cap incapable.conf
+ $(SUDO) ./test_pam_cap root 0x0 0x0 0x0 config=./capability.conf
+ $(SUDO) ./test_pam_cap root 0x0 0x0 0x0 config=./sudotest.conf
+ $(SUDO) ./test_pam_cap alpha 0x0 0x0 0x0 config=./capability.conf
+ $(SUDO) ./test_pam_cap alpha 0x0 0x1 0x80 config=./sudotest.conf
+ $(SUDO) ./test_pam_cap beta 0x0 0x1 0x0 config=./sudotest.conf
+ $(SUDO) ./test_pam_cap gamma 0x0 0x0 0x81 config=./sudotest.conf
+ $(SUDO) ./test_pam_cap delta 0x41 0x80 0x41 config=./sudotest.conf
clean:
rm -f *.o *.so testlink lazylink.so test_pam_cap pam_cap_linkopts *~
+ rm -f LIBCAP incapable.conf
diff --git a/pam_cap/execable.c b/pam_cap/execable.c
index 0bf42d3..17276b4 100644
--- a/pam_cap/execable.c
+++ b/pam_cap/execable.c
@@ -26,7 +26,7 @@ SO_MAIN(int argc, char **argv)
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"
+ "abstraction. See the pam_cap License file for licensing information.\n"
"\n"
"Release notes and feature documentation for libcap and pam_cap.so\n"
"can be found at:\n"
@@ -36,7 +36,7 @@ SO_MAIN(int argc, char **argv)
return;
}
- if (argc > 2 || strcmp(argv[1], "--help")) {
+ if (argc > 2 || argv[1] == NULL || strcmp(argv[1], "--help")) {
printf("\n%s only supports the optional argument --help\n", cmd);
exit(1);
}
@@ -48,6 +48,7 @@ SO_MAIN(int argc, char **argv)
"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",
+ "default=<iab> - fallback IAB value if there is no '*' rule\n"
+ "defer - apply IAB value at pam_exit (not via setcred)\n",
cmd);
}
diff --git a/pam_cap/pam_cap.c b/pam_cap/pam_cap.c
index 162e1f5..b9419cb 100644
--- a/pam_cap/pam_cap.c
+++ b/pam_cap/pam_cap.c
@@ -12,6 +12,7 @@
#endif
#include <errno.h>
+#include <fcntl.h>
#include <grp.h>
#include <limits.h>
#include <pwd.h>
@@ -22,6 +23,7 @@
#include <syslog.h>
#include <sys/capability.h>
#include <sys/prctl.h>
+#include <sys/stat.h>
#include <sys/types.h>
#include <linux/limits.h>
@@ -39,9 +41,11 @@ struct pam_cap_s {
int debug;
int keepcaps;
int autoauth;
+ int defer;
const char *user;
const char *conf_filename;
const char *fallback;
+ pam_handle_t *pamh;
};
/*
@@ -67,6 +71,9 @@ static int load_groups(const char *user, char ***groups, int *groups_n) {
}
*groups = calloc(ngrps, sizeof(char *));
+ if (*groups == NULL) {
+ return -1;
+ }
int g_n = 0, i;
for (i = 0; i < ngrps; i++) {
const struct group *g = getgrgid(grps[i]);
@@ -101,6 +108,27 @@ static char *read_capabilities_for_user(const char *user, const char *source)
D(("failed to open capability file"));
goto defer;
}
+ /*
+ * In all cases other than "/dev/null", the config file should not
+ * be world writable. We do not check for ownership limitations or
+ * group write restrictions as these represent legitimate local
+ * administration choices. Especially in a system operating in
+ * CAP_MODE_PURE1E.
+ */
+ if (strcmp(source, "/dev/null") != 0) {
+ struct stat sb;
+ D(("validate filehandle [for opened %s] does not point to a world"
+ " writable file", source));
+ if (fstat(fileno(cap_file), &sb) != 0) {
+ D(("unable to fstat config file: %d", errno));
+ goto close_out_file;
+ }
+ if ((sb.st_mode & S_IWOTH) != 0) {
+ D(("open failed [%s] is world writable test: security hole",
+ source));
+ goto close_out_file;
+ }
+ }
int found_one = 0;
while (!found_one &&
@@ -162,6 +190,7 @@ static char *read_capabilities_for_user(const char *user, const char *source)
line = NULL;
}
+close_out_file:
fclose(cap_file);
defer:
@@ -182,6 +211,51 @@ defer:
}
/*
+ * This is the "defer" cleanup function that actually applies the IAB
+ * tuple. This happens really late in the PAM session, hopefully after
+ * the application has performed its setuid() function.
+ */
+static void iab_apply(pam_handle_t *pamh, void *data, int error_status)
+{
+ cap_iab_t iab = data;
+ int retval = error_status & ~(PAM_DATA_REPLACE|PAM_DATA_SILENT);
+
+#ifdef PAM_DEBUG
+ {
+ cap_t c = cap_get_proc();
+ cap_iab_t tu = cap_iab_get_proc();
+ char *tc, *ttu;
+ tc = cap_to_text(c, NULL);
+ ttu = cap_iab_to_text(tu);
+
+ D(("iab_apply with uid=%d,euid=%d and error_status=0x%08x \"%s\", [%s]",
+ getuid(), geteuid(), error_status, tc, ttu));
+
+ cap_free(ttu);
+ cap_free(tc);
+ cap_free(tu);
+ cap_free(c);
+ }
+#endif
+
+ data = NULL;
+ if (error_status & PAM_DATA_REPLACE) {
+ goto done;
+ }
+
+ if (retval != PAM_SUCCESS || !(error_status & PAM_DATA_SILENT)) {
+ goto done;
+ }
+
+ if (cap_iab_set_proc(iab) != 0) {
+ D(("IAB setting failed"));
+ }
+
+done:
+ cap_free(iab);
+}
+
+/*
* Set capabilities for current process to match the current
* permitted+executable sets combined with the configured inheritable
* set.
@@ -238,7 +312,16 @@ static int set_capabilities(struct pam_cap_s *cs)
goto cleanup_conf;
}
- if (!cap_iab_set_proc(iab)) {
+ if (cs->defer) {
+ D(("configured to delay applying IAB"));
+ int ret = pam_set_data(cs->pamh, "pam_cap_iab", iab, iab_apply);
+ if (ret != PAM_SUCCESS) {
+ D(("unable to cache capabilities for delayed setting: %d", ret));
+ /* since ok=0, the module will return PAM_IGNORE */
+ cap_free(iab);
+ }
+ iab = NULL;
+ } else if (!cap_iab_set_proc(iab)) {
D(("able to set the IAB [%s] value", conf_caps));
ok = 1;
}
@@ -249,6 +332,10 @@ static int set_capabilities(struct pam_cap_s *cs)
* Best effort to set keep caps - this may help work around
* situations where applications are using a capabilities
* unaware setuid() call.
+ *
+ * It isn't needed unless you want to support Ambient vector
+ * values in the IAB. In this case, it will likely also
+ * require you use the "defer" module argument.
*/
D(("setting keepcaps"));
(void) cap_prctlw(PR_SET_KEEPCAPS, 1, 0, 0, 0, 0);
@@ -296,6 +383,8 @@ static void parse_args(int argc, const char **argv, struct pam_cap_s *pcs)
pcs->autoauth = 1;
} else if (!strncmp(*argv, "default=", 8)) {
pcs->fallback = 8 + *argv;
+ } else if (!strcmp(*argv, "defer")) {
+ pcs->defer = 1;
} else {
_pam_log(LOG_ERR, "unknown option; %s", *argv);
}
@@ -330,7 +419,7 @@ int pam_sm_authenticate(pam_handle_t *pamh, int flags,
}
if (retval != PAM_SUCCESS) {
- D(("pam_get_user failed: %s", pam_strerror(pamh, retval)));
+ D(("pam_get_user failed: pam error=%d", retval));
memset(&pcs, 0, sizeof(pcs));
return PAM_AUTH_ERR;
}
@@ -355,23 +444,22 @@ int pam_sm_authenticate(pam_handle_t *pamh, int flags,
_pam_drop(conf_caps);
return PAM_SUCCESS;
-
- } else {
-
- D(("there are no capabilities restrictions on this user"));
- return PAM_IGNORE;
-
}
+
+ D(("there are no capabilities restrictions on this user"));
+ return PAM_IGNORE;
}
/*
- * pam_sm_setcred applies inheritable capabilities loaded by the
- * pam_sm_authenticate pass for the user.
+ * pam_sm_setcred optionally applies inheritable capabilities loaded
+ * by the pam_sm_authenticate pass for the user. If it doesn't apply
+ * them directly (because of the "defer" module argument), it caches
+ * the cap_iab_t value for later use during the pam_end() call.
*/
int pam_sm_setcred(pam_handle_t *pamh, int flags,
int argc, const char **argv)
{
- int retval;
+ int retval = 0;
struct pam_cap_s pcs;
if (!(flags & (PAM_ESTABLISH_CRED | PAM_REINITIALIZE_CRED))) {
@@ -387,6 +475,7 @@ int pam_sm_setcred(pam_handle_t *pamh, int flags,
return PAM_AUTH_ERR;
}
+ pcs.pamh = pamh;
retval = set_capabilities(&pcs);
memset(&pcs, 0, sizeof(pcs));
diff --git a/pam_cap/test_pam_cap.c b/pam_cap/test_pam_cap.c
index 4c09a5d..4bcf236 100644
--- a/pam_cap/test_pam_cap.c
+++ b/pam_cap/test_pam_cap.c
@@ -51,6 +51,17 @@ int pam_get_item(const pam_handle_t *pamh, int item_type, const void **item) {
return 0;
}
+int pam_set_data(pam_handle_t *pamh, const char *module_data_name, void *data,
+ void (*cleanup)(pam_handle_t *pamh, void *data,
+ int error_status)) {
+ if (cleanup != iab_apply) {
+ errno = EINVAL;
+ return -1;
+ }
+ cap_free(data);
+ return -1;
+}
+
int getgrouplist(const char *user, gid_t group, gid_t *groups, int *ngroups) {
int i,j;
for (i = 0; i < n_users; i++) {
@@ -134,31 +145,35 @@ struct vargs {
static int test_arg_parsing(void) {
static struct vargs vs[] = {
{
- { 1, 0, 0, NULL, NULL, NULL },
+ { 1, 0, 0, 0, NULL, NULL, NULL },
{ "debug", NULL }
},
{
- { 0, 1, 0, NULL, NULL, NULL },
+ { 0, 1, 0, 0, NULL, NULL, NULL },
{ "keepcaps", NULL }
},
{
- { 0, 0, 1, NULL, NULL, NULL },
+ { 0, 0, 1, 0, NULL, NULL, NULL },
{ "autoauth", NULL }
},
{
- { 1, 0, 1, NULL, NULL, NULL },
+ { 1, 0, 1, 0, NULL, NULL, NULL },
{ "autoauth", "debug", NULL }
},
{
- { 0, 0, 0, NULL, "/over/there", NULL },
+ { 0, 0, 0, 0, NULL, "/over/there", NULL },
{ "config=/over/there", NULL }
},
{
- { 0, 0, 0, NULL, NULL, "^cap_setfcap" },
+ { 0, 0, 0, 0, NULL, NULL, "^cap_setfcap" },
{ "default=^cap_setfcap", NULL }
},
{
- { 0, 0, 0, NULL, NULL, NULL },
+ { 0, 0, 0, 1, NULL, NULL, NULL },
+ { "defer", NULL }
+ },
+ {
+ { 0, 0, 0, 0, NULL, NULL, NULL },
{ NULL }
}
};
@@ -222,8 +237,20 @@ int main(int argc, char *argv[]) {
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");
+ if (read_capabilities_for_user("alpha", "/dev/null") != NULL) {
+ printf("/dev/null should return no capabilities\n");
+ exit(1);
+ }
+ if (read_capabilities_for_user("unknown", "capability.conf") != NULL) {
+ printf("capability.conf should return no capabilities for unknown\n");
+ exit(1);
+ }
+ char *iab_text = read_capabilities_for_user("alpha", "./incapable.conf");
+ if (iab_text != NULL) {
+ printf("./incapable.conf should grant no capabilities: got=%s\n",
+ iab_text);
+ free(iab_text);
+ exit(1);
}
/*
@@ -238,6 +265,10 @@ int main(int argc, char *argv[]) {
printf("test_pam_cap: OK! (Skipping privileged tests (uid!=0))\n");
exit(0);
}
+ if (argc == 1) {
+ printf("test_pam_cap: OK (kick the tires test)\n");
+ exit(0);
+ }
change[A] = strtoul(argv[2], NULL, 0);
change[B] = strtoul(argv[3], NULL, 0);
diff --git a/pgp.keys.asc b/pgp.keys.asc
index b39f76a..b03643f 100644
--- a/pgp.keys.asc
+++ b/pgp.keys.asc
@@ -3,7 +3,6 @@ morgan@kernel.org upload/signature key.
pub 4096R/E2CCF3F4 2011-10-07 Andrew G. Morgan (Work Address) <agm@google.com>
uid Andrew G. Morgan <morgan@kernel.org>
-----BEGIN PGP PUBLIC KEY BLOCK-----
-Version: GnuPG v1.4.15 (GNU/Linux)
mQINBE6OiBIBEADpdtUxC8Fmhn5UK6UCZdU7mFgZwN8U9cabFUPfUIkMqXULhCD0
hG2/amuiiUoLollPjOopNqk4cc8LcZfszOdBFAYj7MeWzNySVw4KkWrVCEH/bZ0Q
@@ -16,97 +15,144 @@ VPGAa4K+dnI2oy4wukzl/unAKrlMCBRsRoW2qjy3TDSXqwJhd34ilHzrdAdchrh/
acBfbBtRzVlcDTnGltDNMuRTXzujaY9C3B0L2E+Jfrds8WcM8ASO4mHwJUTMrBwM
b5sFSG+/X9Ufg/c2G086HQ7xMERUA5oz66P5ReHCph8WHQN2L5vtZwL7//hZB9hn
G0K1210YEDXpFPijpis/54MKUSkWEFOLjUbiSPbwEfb79A00CcHojQQinwARAQAB
-tDBBbmRyZXcgRy4gTW9yZ2FuIChXb3JrIEFkZHJlc3MpIDxhZ21AZ29vZ2xlLmNv
-bT6JAjgEEwECACIFAk6VD4ICGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJ
-ECnuhIrizPP0zNoQAMDjx3iovvf0rpAYFvvAoPbzhEXcJ41/T+paxWOJm8SEg7fX
-nUHgXeTwW3RJPIp7PguctPogvKQV+7GcU5Dcg13DZO4nMrSsvInsLQkfeDVU/zl2
-MuHFOtBMpDp6iGcUwjS0bYbvl03fPj7ZXIML+I7OSyNeoZ/n2ztI9UiIBHovsHqZ
-qYm4d7VOi4nVj1Y/Gak99sw3cLvUwq9f3i8ioNzynqBT7jA+GWFaeVJuGrOCBBBg
-uIu0Ekg42NAZ2AR32wQP5eEtlSAq8Il9RZzewa1v74loDNJOl+kW5/jQK6tGj2A9
-vlTqVzHUDmPZ9n6Ds7h3wo2g3gzYX1cuM3spW9UsA8XUDNY2yNFYDC9IsAI09u18
-N7f89isG/yYh5MZpJz2fx7cecHtwSVukTGHDsaoHTXMlfjQmVU5efORZJa6Bx0Tk
-aSCwecem3q+3OcdgW8XwPWik/5Wv8B3dJopMH1Mw3pRhirtTd6/88xNyLkJStptB
-DZvbqvB2nMmSiqgh0mPeslnwubxJ5/4FbP9zlLN7zp49RZHKDl/8EMSXGCjmG6UT
-xW6I3YpKdc4+yEd19/UUtxqQOfbgFvlcbesQ5ILvLOzZidkS7y0v4i9rZBe/HEy3
-eG8z4s5dloBrpSBvKySwqWuuSDn3tMqw4Bz2Be3FgtYA4TnNy7shcFR2BMFotCRB
-bmRyZXcgRy4gTW9yZ2FuIDxtb3JnYW5Aa2VybmVsLm9yZz6JAjsEEwECACUCGwMG
-CwkIBwMCBhUIAgkKCwQWAgMBAh4BAheABQJOmRGPAhkBAAoJECnuhIrizPP0wK0P
-/RMvjmzeXbgoa36cBDvDKReAiC56Au4qGXkNah3984tNPT1hVUKCiwiUmULoNJbE
-I4qFJTtwsMi5QzE+daCA7t+ALJiC+PKiKFG1LDz7mxfhmBeS3XcYuqZdjyKrATUF
-r0SHbsJxtRCslawGD2gKczLknFeBXL0997TfJS9ipLibqCtmvyryHn4EbZfoJqcp
-j/RBN/izVGHNYI8BsZpO5F6z7vXoncDL0dKh65ndGaIbhVDUPsDBvzg3i+EzhB51
-hYTTNKK0QpWbmsXfJBnvztinfLUsnO9HV8aRaygOI/DAKAtT7YPXORA1oFYtx69b
-zulqC+TXUmeV8YW8bETH4xHM9mQb0oNLPibR2nK2FSDiLp0/eEM5vgzfPVUX7WzB
-JUPsf0ah/e1yrXqudGUUZ0R+3VMOdxMryZBKLymkzyvu6a5DcLarqAt8y9ciRH67
-HKNnE1gvHf5K2Q37gwSecwmXCjpMlbVJnIarLKBcVRcYKtxgPxCv6483I8heSKF7
-PB/IFBmzT1cX7lhln9+62Ks/0Gs0pA0iNLaD+POPiqWrAwZsFvKjD9PDaCBDFRWj
-FqZLyJMsMi1qmP8jWsdQqPdUskQC0ftvw3Z6SiyyrriSAzglCjmmAcfdt+w4b/EO
-4SzSZUnd/ApkHkZx1Lbta15WKxGi7S8/5zNdaK721nUdiEYEEBECAAYFAk6Oi/kA
-CgkQQheEq9QabfJhdwCdEhWd2WbjrypMC2jEqWUswmf7fsQAn3LwZyeVJK5LApOF
-7NimHkCQV9z7iQIcBBABAgAGBQJOl+CHAAoJEO2/8mhZLMbY4ywP/2qX0+QrilRC
-eqk8cOmljLB+sxiA2Jc5YINAXipg6PSQzF7IlMnSNSW69ARLPW5iyDTljXTtD85W
-/yWhm3vsouWldBa1Wb6xVb8iA8H8fUUKCY7ngCSjHJxPa1KRsTrMKCkLHR2MP7Qi
-ar0dvquomtlx5chkhXmY+0cxcA/cMB/A/fbfDvvbYD5HYiB90AylPmLbM9XiLF0F
-RSJt7iokGidS1W80ZCg5p1R02dQV5H7/111Xx1QIggPcNPWGwCK61Q3tPV0xc0oQ
-dZpQk2hnPVHF7BMmCyB/iNRofF9mpC/QZGFRQkb3XgdIdK/O23VQntSGctrtnL1M
-rcrgQUIrMaU3LKFbIE7DBwMUzUaTO/t14ZQQUZJTAKLSVCfvGvgh6/dqaXpssQxL
-D2S5J1sWs1ZVInOhjo2OZnVl3SEmQT9h6NB93QRoGfbfy+AJgReRcfCep5zDMrud
-5HPym9itvMLVVzw267Yn0ATBhrESAY8LqBBRbigM/TL+jNPfsQzhEzHXFsQL/dKh
-V4N8IURnpCqHzY2BSnTX1K8ipl+iRGpMVfkYQnM660AIJhAReT2rwzuhGRKHbOXz
-UrzoEg1PEw/+69ZmcGUZH1VtSrOw0r6eub+rg7Q0R4r6c8kF2vS2XSQn/MZ2Wqjk
-hW4fWCqqogIvCkqk1Jt3OCRIWbVC0bKKiQIcBBABAgAGBQJOmJxYAAoJECDQTlpx
-NmCnTvEP/38M2bsQGnKVhNsAcr7sDO4YmDrc8V/bUrGjADWmLcW/K2MDOWLZIwmg
-Z1qMifHXuy/NhyX3/xp8VacNAlpuQ8o/T77P1QCLwuPu+fuXLOmFkCISFeTW5g/d
-pShZ4tsTXAaJs7bQdQnsY3prZl0CMJtItOhwW34PDZL95Vp2ZRx84Dn355KHUeeq
-yQjqu+cEz2T5sfVj/O2w1tgeWcMxrOI3ARD/Ks+CeWoFZPezq2K4ctka7Q+muH9/
-1WCatdpryf5SJoBMDaC7GXzGegesKQr35sfNM9XRP1TphmCqQz4VOb+stIEJv1Dq
-c9Lc4EScOwmESt5mzPwrZ3OJ+stFKW1QJgErUb55TNQ4C957rodxCerNa9ptpdUk
-U9Pb2vpSurNRgETA/urZkBO/vPQ8MEgdJSbVgh0Rj/zPFnj3akQFc98U5Km0TIHJ
-7r6S+qj73itUM79jMVKJgewPEA8cys0ACLoM5uRNYq35mY4OeP/Edm6NLiKfD0us
-MfEQ+02B8RqXuHBAJAa/+f+U3zGkw268f3/16kZv/PTMfdOEy1cjKlQ3LFwIHfny
-Brb/3vHAVTAyEbBPWmULEjopdevEPKmKyW2EXFphBmjOHSghmIRDxO2WmSuI8bIU
-sH4oq6MwqAJpE5rzreBNLNh5ZY4yzw3nAJb6Bb59m0kt2fHKIq+AiQI4BBMBAgAi
-BQJOjogSAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRAp7oSK4szz9HRi
-D/4sMdw5WrUtmagrXWVyj83YLJW2GBxH6s5UR1/fyl5uDYjBAccf3jDuIwVZzpCJ
-ZpQ8RwvRV699Pag5L5uwDEvkiIMROPNescaXGROuNoCFfqIOTVZfGya2w06dB0Kh
-C0l++iO6YVy1eJkyc/XooiOOtEfv5UpBZSWn9hMYaNSc9tiQcyPxzEnEQYUmIoXG
-kHXUNRDBQfJLRZP4e9YjN/hH0ZW7/rHXXMxeBREfbCekKy0qDgJ/Sf3Eh6dwUkOR
-/vCrdZM2Q8TTX0LJdflJdqMEuYHqm1j9RrnoXIjhx0wFopEOHPSr2qxOu2gOkyxB
-JE7Ur3IKpMRaoCR0xHMb5MOgnMmwRW2G6KcZTCdr2jmxp2hK3BxRcUt3qh74jhZL
-Dbv5dxTqVn/VK1CGhHbrcW2adkyi2sK7vVARdlSmHYWIWhLqv77p7tkSAX76Qig8
-X75WGF+W3YSAS4f3I6QXRnXxzG8TbMIa4CfeN5IZ2Z5TisC2YyuG8VdM/m6i6W18
-cLa7ZNGE3w04eVQvtigG+9p9gCs5Kg6PVVxwJsjGDDqHkCslfFF8Wl1ZdqXqtUB2
-RKTWb4XNU5XxO0xIGFtLUNnCKcJAOUCu/oRJ/WWHW+BKDdG1VbgYVFTXHc6YZpet
-2D+sAs7cWV8GDJ9nChHWcQ5C/bPV1PVnheZhwGvHLsWrILkCDQROjogSARAAtLny
-8nlyr8fyYGAocQz0S47a99n/X0Vmgwo1trJsCXWbOrpztznY8IFRK/dRnRHiMwBx
-WQ4CvdUk2p0MweUiOjpEN7bUm92jeFXMr0hpQKf+O4DMExHS4hxLwArnKFuAk2ej
-RQGXBcEoMv11LiUwuzFbWdXqMsA1TbuA+WvEBnFUYM/6xNiJeRIUIiGydhG1yaw8
-HrNWLHnhhcOfT6z5AO69hZZiJacp9pU/+jnep/M42p4J17x81+ESpJeladwR0Qxc
-0qxOyWidN7oO5hSiBEwU6lYQjdQ23pa7tN1o90P9jyN2nFBEdBu2D/mi4DV/+VXU
-YHNEy3uNhmmLGwMoPVWiZveRmG74+ne7MVyxwb9EIF3IenS4T65ee1dlZvaoMxUl
-Ue8htEK0ChrQZOfITs9MyjUwoTiLUVo3kQeMli9HJEQXPRjHqkkZ7W65LhkEVnHS
-PHWtttRSDkuZYtze+he142GzDSQA3dF2zy/tLpBb5CA29ITcQTspgV7AuV8YQqDZ
-4XWHsR9Am5334N83EXk2oouqxl7mKUB0Vg6tujNCBSRn6A3CUaA29w/MyTg4z6Yw
-6HD3il1J8PcWEoOzqlUoPd8tA5pcZCcKngkXndpXgsZCgoCgvx9WNU+LUrHBfhC3
-TLLsI7iGO1JvLghkesKTARF3O2hS3xAhfGZxn8MAEQEAAYkCHwQYAQIACQUCTo6I
-EgIbDAAKCRAp7oSK4szz9HSYD/9hmEsJuSgAGwx/OPweYuDGkA25ajDAu59LpzTb
-jB/yOU1rDVUu3cMH+UEyaEGlhbneGvHF2DsEC9il/8fVL4eaE9EWpopIonYndBE9
-1+YiGHPToiyKcdp0KuQMwm2ENAiEf/qErrB2NLna4wfZUx5lzvEOEk3cNPmNz2ER
-yMPXIeeiQ9VKp3MzopWhvBItAyIzzuydKKvJAKzDoTOEL4w60slAphj8rVCsW45k
-2AurWUH7VFM8ezXunieLeygCGb+YJZAet6yVXD3UwnNcWCGQ+xKSPuyKrn4xKG0N
-5gzxnGIh/S/7IOjRaNR5X+pfWd6YzN9qURUfiXmuLSPRHK4Flfam4gMMHul9wL6X
-BayFo2NUPBaxg4U9ACAgSJxgCTNPCKwnovecOsRmIESKtT1F3hbZRRgRGj/TDepJ
-QNfHSyk/ZQfuoJggBMQLJKzGII42rb0W90QLMk0SyCzeb3LO3yyNiKpluNpJsl2I
-qdBJE5t1LxhKDnju6JlFyPcGJnP/doTuDTjjL0V+guPAGVbuq0g2hku+ZlJwjMSt
-NwHPWxeifuDJbQVIp0xZbI5djdHC8hVJX+d09J5eq0PlgMEidc4F+Vv+mmGJl0Gi
-NfhmTaACSRzbI25/bhvj2xhx8A2LEOuU/+nzYgQzPcFpawiUP1wBnTqi+maxKx5/
-9ifyrw==
-=Ibs8
+tCRBbmRyZXcgRy4gTW9yZ2FuIDxtb3JnYW5Aa2VybmVsLm9yZz6JAjsEEwECACUC
+GwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheABQJOmRGPAhkBAAoJECnuhIrizPP0
+wK0P/RMvjmzeXbgoa36cBDvDKReAiC56Au4qGXkNah3984tNPT1hVUKCiwiUmULo
+NJbEI4qFJTtwsMi5QzE+daCA7t+ALJiC+PKiKFG1LDz7mxfhmBeS3XcYuqZdjyKr
+ATUFr0SHbsJxtRCslawGD2gKczLknFeBXL0997TfJS9ipLibqCtmvyryHn4EbZfo
+Jqcpj/RBN/izVGHNYI8BsZpO5F6z7vXoncDL0dKh65ndGaIbhVDUPsDBvzg3i+Ez
+hB51hYTTNKK0QpWbmsXfJBnvztinfLUsnO9HV8aRaygOI/DAKAtT7YPXORA1oFYt
+x69bzulqC+TXUmeV8YW8bETH4xHM9mQb0oNLPibR2nK2FSDiLp0/eEM5vgzfPVUX
+7WzBJUPsf0ah/e1yrXqudGUUZ0R+3VMOdxMryZBKLymkzyvu6a5DcLarqAt8y9ci
+RH67HKNnE1gvHf5K2Q37gwSecwmXCjpMlbVJnIarLKBcVRcYKtxgPxCv6483I8he
+SKF7PB/IFBmzT1cX7lhln9+62Ks/0Gs0pA0iNLaD+POPiqWrAwZsFvKjD9PDaCBD
+FRWjFqZLyJMsMi1qmP8jWsdQqPdUskQC0ftvw3Z6SiyyrriSAzglCjmmAcfdt+w4
+b/EO4SzSZUnd/ApkHkZx1Lbta15WKxGi7S8/5zNdaK721nUdiEYEEBECAAYFAk6O
+i/kACgkQQheEq9QabfJhdwCdEhWd2WbjrypMC2jEqWUswmf7fsQAn3LwZyeVJK5L
+ApOF7NimHkCQV9z7iQIcBBABAgAGBQJOl+CHAAoJEO2/8mhZLMbY4ywP/2qX0+Qr
+ilRCeqk8cOmljLB+sxiA2Jc5YINAXipg6PSQzF7IlMnSNSW69ARLPW5iyDTljXTt
+D85W/yWhm3vsouWldBa1Wb6xVb8iA8H8fUUKCY7ngCSjHJxPa1KRsTrMKCkLHR2M
+P7Qiar0dvquomtlx5chkhXmY+0cxcA/cMB/A/fbfDvvbYD5HYiB90AylPmLbM9Xi
+LF0FRSJt7iokGidS1W80ZCg5p1R02dQV5H7/111Xx1QIggPcNPWGwCK61Q3tPV0x
+c0oQdZpQk2hnPVHF7BMmCyB/iNRofF9mpC/QZGFRQkb3XgdIdK/O23VQntSGctrt
+nL1MrcrgQUIrMaU3LKFbIE7DBwMUzUaTO/t14ZQQUZJTAKLSVCfvGvgh6/dqaXps
+sQxLD2S5J1sWs1ZVInOhjo2OZnVl3SEmQT9h6NB93QRoGfbfy+AJgReRcfCep5zD
+Mrud5HPym9itvMLVVzw267Yn0ATBhrESAY8LqBBRbigM/TL+jNPfsQzhEzHXFsQL
+/dKhV4N8IURnpCqHzY2BSnTX1K8ipl+iRGpMVfkYQnM660AIJhAReT2rwzuhGRKH
+bOXzUrzoEg1PEw/+69ZmcGUZH1VtSrOw0r6eub+rg7Q0R4r6c8kF2vS2XSQn/MZ2
+WqjkhW4fWCqqogIvCkqk1Jt3OCRIWbVC0bKKiQIcBBABAgAGBQJOmJxYAAoJECDQ
+TlpxNmCnTvEP/38M2bsQGnKVhNsAcr7sDO4YmDrc8V/bUrGjADWmLcW/K2MDOWLZ
+IwmgZ1qMifHXuy/NhyX3/xp8VacNAlpuQ8o/T77P1QCLwuPu+fuXLOmFkCISFeTW
+5g/dpShZ4tsTXAaJs7bQdQnsY3prZl0CMJtItOhwW34PDZL95Vp2ZRx84Dn355KH
+UeeqyQjqu+cEz2T5sfVj/O2w1tgeWcMxrOI3ARD/Ks+CeWoFZPezq2K4ctka7Q+m
+uH9/1WCatdpryf5SJoBMDaC7GXzGegesKQr35sfNM9XRP1TphmCqQz4VOb+stIEJ
+v1Dqc9Lc4EScOwmESt5mzPwrZ3OJ+stFKW1QJgErUb55TNQ4C957rodxCerNa9pt
+pdUkU9Pb2vpSurNRgETA/urZkBO/vPQ8MEgdJSbVgh0Rj/zPFnj3akQFc98U5Km0
+TIHJ7r6S+qj73itUM79jMVKJgewPEA8cys0ACLoM5uRNYq35mY4OeP/Edm6NLiKf
+D0usMfEQ+02B8RqXuHBAJAa/+f+U3zGkw268f3/16kZv/PTMfdOEy1cjKlQ3LFwI
+HfnyBrb/3vHAVTAyEbBPWmULEjopdevEPKmKyW2EXFphBmjOHSghmIRDxO2WmSuI
+8bIUsH4oq6MwqAJpE5rzreBNLNh5ZY4yzw3nAJb6Bb59m0kt2fHKIq+AiQI4BBMB
+AgAiBQJOjogSAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRAp7oSK4szz
+9HRiD/4sMdw5WrUtmagrXWVyj83YLJW2GBxH6s5UR1/fyl5uDYjBAccf3jDuIwVZ
+zpCJZpQ8RwvRV699Pag5L5uwDEvkiIMROPNescaXGROuNoCFfqIOTVZfGya2w06d
+B0KhC0l++iO6YVy1eJkyc/XooiOOtEfv5UpBZSWn9hMYaNSc9tiQcyPxzEnEQYUm
+IoXGkHXUNRDBQfJLRZP4e9YjN/hH0ZW7/rHXXMxeBREfbCekKy0qDgJ/Sf3Eh6dw
+UkOR/vCrdZM2Q8TTX0LJdflJdqMEuYHqm1j9RrnoXIjhx0wFopEOHPSr2qxOu2gO
+kyxBJE7Ur3IKpMRaoCR0xHMb5MOgnMmwRW2G6KcZTCdr2jmxp2hK3BxRcUt3qh74
+jhZLDbv5dxTqVn/VK1CGhHbrcW2adkyi2sK7vVARdlSmHYWIWhLqv77p7tkSAX76
+Qig8X75WGF+W3YSAS4f3I6QXRnXxzG8TbMIa4CfeN5IZ2Z5TisC2YyuG8VdM/m6i
+6W18cLa7ZNGE3w04eVQvtigG+9p9gCs5Kg6PVVxwJsjGDDqHkCslfFF8Wl1ZdqXq
+tUB2RKTWb4XNU5XxO0xIGFtLUNnCKcJAOUCu/oRJ/WWHW+BKDdG1VbgYVFTXHc6Y
+Zpet2D+sAs7cWV8GDJ9nChHWcQ5C/bPV1PVnheZhwGvHLsWrIIkCMwQQAQgAHRYh
+BCB5yApF/r2boJUa8ssjEuHs9z9XBQJdDwwPAAoJEMsjEuHs9z9Xh14P/3HmG8NE
+Rr75KQZ+Nbdg9jhA56MMgKoHawchaLm+Jmpt5cYymaG+hZyFXKdGmOk0zltD3qNG
+TIoC4dg5BKRdCWa7U7uTptrWaZ/tendnPU4UThGrExEaiAJ1BzJqV7qUWQ0nRyU0
+g/h3ZQUrzvVXtr7SL/xfQFvKSEKmW8t6yXhcLGkJfRlLsE+rXsgRuRmVRSw7zTNq
++uJqExr8bGXThM/9ikmTTDjRCBVmCT2wbWimY5TSXm4fomMDnWumKhsJsgabnbTL
+ch1E+YFwSGmjHyluNAscU3rDrle2FoOdmZCXvLbFEZyFIuecAmi5zTPm7Ko0SA2e
+bQLwqkwo/k+YYOriW5ljoQh1uFTKdzyhIJmozEmVQH83Foq8gE/8aJed9asHq/ty
+0UUttaoWt5XwCTWg8uq0xwang18qO6HgUMKBSCRQOcPSGg1UX1l4ipx47JmtAhIN
+SV15y9Xm9gc4fKiJ1EBIJvdgf8NrSYDQwzTyA0A5W1oEMNuFs8eUBjn6+AiTN1jI
+UnVBJ/I8shY/hpgnwgLUUxD4RiH+KKeq2Xx+UPDtFCfOuYiL7ITTS6FrZFcdjE1O
+I7l7M+uulavhxc8iMNsT3mm1SClcxdufr2000UCIaIblPRVd+iKlzhhsGAJZ7vP9
+jRQ1m1CIWgmgdDdgHlR/JEHDvS9DhBwdDCcviQIzBBABCgAdFiEEiRirsLX+whtp
+fs5a+9SYGk9gV3wFAmLHkdcACgkQ+9SYGk9gV3yNew/7BlNc4Gv+FYIg/37+SmDf
+b/WVvLvJ1Rw1x7rnRhivQFMPwFw6R0dahK7taxFgC94b7nlGxRfgf3D63P6IXTtR
+YirLeICun7WrGr4vPbjb+qXLMZLvq4MnVRmxnor+z+wCTn3csZgEvRAdw4kppG6F
+o4wBB5mphgAxxF7FTFKU7Y1az/YZdGt/TvG5CLLCZYKmCC2DplDI2hABcIlIN+XB
+z8sZzZBI/6YIuRuABeXgo35hlU9RrKAYU3qxp09YAIbD1eR56l7spcdbTJnE4qs1
+o4XsUujv+JGoUc1UI7eF0TtA5T2NSs42fNwfsXcr2f37fx6rDWyypD2bDgfQL20+
+jtgud7Jzf41M/o7Mfhy6iOAeCBhs/+JbAgNmvJb+zOl+DlaCwQnvZFouunLkO09B
+QwgOG37TaS9WuZik1c1Wi9qzlB1/tZJABf28LgBXrQaF/8EBSmfMESByR3NLMuQE
+2C53l+WoFE0y6VNplpGDcU+hoZpgU2lNJyA7J4MODjVcQzhSBIFTMw3hcqp+c+QY
+a+hxYqcjEP56BymKrVDk6pHjCGagA+GrrKd3J50p4WjbNmIj6NThgYqFEWOKg/eL
+cADnWsDoam69PsxIZIttY3MtkdY+xMVpXZCLu6Kl8hTFkALHrpbCD+vrFt3wJ7Iy
+EZnKqqLTws9GwuQnD4l8FGm0MEFuZHJldyBHLiBNb3JnYW4gKFdvcmsgQWRkcmVz
+cykgPGFnbUBnb29nbGUuY29tPokCOAQTAQIAIgUCTpUPggIbAwYLCQgHAwIGFQgC
+CQoLBBYCAwECHgECF4AACgkQKe6EiuLM8/TM2hAAwOPHeKi+9/SukBgW+8Cg9vOE
+RdwnjX9P6lrFY4mbxISDt9edQeBd5PBbdEk8ins+C5y0+iC8pBX7sZxTkNyDXcNk
+7icytKy8iewtCR94NVT/OXYy4cU60EykOnqIZxTCNLRthu+XTd8+Ptlcgwv4js5L
+I16hn+fbO0j1SIgEei+wepmpibh3tU6LidWPVj8ZqT32zDdwu9TCr1/eLyKg3PKe
+oFPuMD4ZYVp5Um4as4IEEGC4i7QSSDjY0BnYBHfbBA/l4S2VICrwiX1FnN7BrW/v
+iWgM0k6X6Rbn+NArq0aPYD2+VOpXMdQOY9n2foOzuHfCjaDeDNhfVy4zeylb1SwD
+xdQM1jbI0VgML0iwAjT27Xw3t/z2Kwb/JiHkxmknPZ/Htx5we3BJW6RMYcOxqgdN
+cyV+NCZVTl585FklroHHRORpILB5x6ber7c5x2BbxfA9aKT/la/wHd0mikwfUzDe
+lGGKu1N3r/zzE3IuQlK2m0ENm9uq8HacyZKKqCHSY96yWfC5vEnn/gVs/3OUs3vO
+nj1FkcoOX/wQxJcYKOYbpRPFbojdikp1zj7IR3X39RS3GpA59uAW+Vxt6xDkgu8s
+7NmJ2RLvLS/iL2tkF78cTLd4bzPizl2WgGulIG8rJLCpa65IOfe0yrDgHPYF7cWC
+1gDhOc3LuyFwVHYEwWiIRgQQEQIABgUCW52DcgAKCRBCF4Sr1Bpt8tboAJ4uDyZQ
+PHCoV04tDbBKHIynok1dfACeMNckl8DppIgV3kgFJsHG2vVHKjmJAjMEEAEIAB0W
+IQQgecgKRf69m6CVGvLLIxLh7Pc/VwUCXQ8MGwAKCRDLIxLh7Pc/V0F9EACHKNqF
+l5xXDHe/0nlZ+J/OFRNIE8ObZAxQLaPfK3gRkFn/SbKQzkzB84X2il7A/W221Lzi
+me5eTFPhTX3RxUcoSQdrtCCov5gCeuiUbhuJ28zuJxslxLE8bhnmNfpLmFFGtbMI
+kXq+y0uqc08Yj8frPXKgx7KvOoovpm0X/igiAkiuKLhbq8xIwaIN0NL4slFlx+ZP
+Ed0KA6qOvlLr0T/lLVptAeMrzfi2gqY1utSqE5IVrbtU6Kptw3zfURsGFFIaKjIr
+hzu25Cdpg/NxYGqo2GqD0lZ+OeWSy0WI5sxCSDqr0to9lvsJGv2Nc06ixIjH7vG2
+Hc/cC0QyHdBM6GwaLmUH9hrcSCLR5kxTzAW0Cf6lrAZUL36Ivl5l+zoLdJqSgZLY
+YXqMdQf75Y5TRFzry5pWRef3ba4/sgui89W11Uccdq/pGe4OKo0I/vq3bv35/3cZ
+aMGjj3x6v67kk8GWbKg6CPBnzb1dY7VDA5RWOt2lPZr4omUNFwRpxAfZADUz2Q4S
+tMQVE018SSH1i6G9EB8KVQEBeD4qgaWs1z9sqA7K5wlBzGarTa2RspH0GMmYwxBY
+hXtYpKm/47Dkg8j3N01VVwky0XGPFHCVgFbeXGknL1O3thOGs5XPO05jtBcbYI1u
+vvK+h/CNn1yuTG13BSG4pgRF1Sy6CFLHme0d/okCMwQQAQoAHRYhBIkYq7C1/sIb
+aX7OWvvUmBpPYFd8BQJix5HeAAoJEPvUmBpPYFd8viUP/0p2jAtPGX4rQ22IVBHt
+JkfsXe9Jj0L3qtHUiH3Y9A6pPWhfr17PNEy6oQ57PgmPA7MS7rfJ2Dzr61g5ItgP
+5MMX1DY+6tcKahgzutAV2eLCEwkS3Nfv2z8t+DSQHCU+MzBFr7pP/Z9egr/jCCjv
+FoB3nLZ0luAxV4RXtAaGnXrMJrJjGSt2iUEwaYKM1hYP9DcQ+ur3d5i6GTVnLSRa
+3pNuLnvqfH2emRN2XFugGaa6DEwHvQOJn8NcFwHfohSSOEIQSGMES15/ww+CUmPt
+b9NgfbCAuLdZEzkKoatgo0Lp/yZqMMw8m3uJ/Kt1FoJojm/k7hCy97WaHBhBfKMo
+BdFwT6/7cmnQZJDXiLUSRe8UpmrgvJ2wuFp2LV8xLr8FYweuUSCfqs8EzYf+A/12
+64DLrlmhFa/WA4HIYs8F9a+QOIZ9dMcqy17RqPjTwVHnB/m+uQcbYiOBk/26WVNy
+MT8isZzyIX5eN5xTj4nMARi2MjVcYsGouo1smppygWdHZ5PHICrJ9wcSyY/2wo6e
+JW0foNFKkffmdQ2jW0OxI6iPD/khbaJ+qtAq7L8RpjALjcr6kALr5EvuH7p9le1Y
+eKjN7jXRQ0CcBJ3zAFbWEtWXtsemXz8f7o9Jlji9rzRUCxR3atIjQTkccv8b6FEe
+dVOWefKBtO4jl17UQng/DeVAuQINBE6OiBIBEAC0ufLyeXKvx/JgYChxDPRLjtr3
+2f9fRWaDCjW2smwJdZs6unO3OdjwgVEr91GdEeIzAHFZDgK91STanQzB5SI6OkQ3
+ttSb3aN4VcyvSGlAp/47gMwTEdLiHEvACucoW4CTZ6NFAZcFwSgy/XUuJTC7MVtZ
+1eoywDVNu4D5a8QGcVRgz/rE2Il5EhQiIbJ2EbXJrDwes1YseeGFw59PrPkA7r2F
+lmIlpyn2lT/6Od6n8zjangnXvHzX4RKkl6Vp3BHRDFzSrE7JaJ03ug7mFKIETBTq
+VhCN1Dbelru03Wj3Q/2PI3acUER0G7YP+aLgNX/5VdRgc0TLe42GaYsbAyg9VaJm
+95GYbvj6d7sxXLHBv0QgXch6dLhPrl57V2Vm9qgzFSVR7yG0QrQKGtBk58hOz0zK
+NTChOItRWjeRB4yWL0ckRBc9GMeqSRntbrkuGQRWcdI8da221FIOS5li3N76F7Xj
+YbMNJADd0XbPL+0ukFvkIDb0hNxBOymBXsC5XxhCoNnhdYexH0Cbnffg3zcReTai
+i6rGXuYpQHRWDq26M0IFJGfoDcJRoDb3D8zJODjPpjDocPeKXUnw9xYSg7OqVSg9
+3y0DmlxkJwqeCRed2leCxkKCgKC/H1Y1T4tSscF+ELdMsuwjuIY7Um8uCGR6wpMB
+EXc7aFLfECF8ZnGfwwARAQABiQIfBBgBAgAJBQJOjogSAhsMAAoJECnuhIrizPP0
+dJgP/2GYSwm5KAAbDH84/B5i4MaQDblqMMC7n0unNNuMH/I5TWsNVS7dwwf5QTJo
+QaWFud4a8cXYOwQL2KX/x9Uvh5oT0Ramikiidid0ET3X5iIYc9OiLIpx2nQq5AzC
+bYQ0CIR/+oSusHY0udrjB9lTHmXO8Q4STdw0+Y3PYRHIw9ch56JD1UqnczOilaG8
+Ei0DIjPO7J0oq8kArMOhM4QvjDrSyUCmGPytUKxbjmTYC6tZQftUUzx7Ne6eJ4t7
+KAIZv5glkB63rJVcPdTCc1xYIZD7EpI+7IqufjEobQ3mDPGcYiH9L/sg6NFo1Hlf
+6l9Z3pjM32pRFR+Jea4tI9EcrgWV9qbiAwwe6X3AvpcFrIWjY1Q8FrGDhT0AICBI
+nGAJM08IrCei95w6xGYgRIq1PUXeFtlFGBEaP9MN6klA18dLKT9lB+6gmCAExAsk
+rMYgjjatvRb3RAsyTRLILN5vcs7fLI2IqmW42kmyXYip0EkTm3UvGEoOeO7omUXI
+9wYmc/92hO4NOOMvRX6C48AZVu6rSDaGS75mUnCMxK03Ac9bF6J+4MltBUinTFls
+jl2N0cLyFUlf53T0nl6rQ+WAwSJ1zgX5W/6aYYmXQaI1+GZNoAJJHNsjbn9uG+Pb
+GHHwDYsQ65T/6fNiBDM9wWlrCJQ/XAGdOqL6ZrErHn/2J/Kv
+=0HgQ
-----END PGP PUBLIC KEY BLOCK-----
pub 1024D/D41A6DF2 2002-09-23 Andrew G. Morgan <morgan@kernel.org>
-----BEGIN PGP PUBLIC KEY BLOCK-----
-Version: GnuPG v1.4.15 (GNU/Linux)
mQGiBD2PVCcRBADmR2dfKJIaGj120v0EjrGbnYic8nKCrDLUHmtiZyIlMeTNqnw/
/Q2m057SIyFC5K5W7XV8LIsOcpEBAdIS5QLClwec/wqVj1FU5TLHNifR9fBq+DaI
@@ -140,8 +186,33 @@ PskEkzaX+mzf3Tfn6k7+d/dPYRHPrX0STdsNMqrZkqjt5tiozuLYJUE/PDKafzdI
Q7ya5ps2AdIKNixiSRPC+6cNB85NBorLXs9yg+JkQlPeUI7/DZb2iz1iZXTnyCZv
SvKA7JMYpCOzZ6fWshanZ/91hxQvsYHjYC+zQVTErSYQlBqz8fDLPfyYQiqQFn4c
T/i1WsxYMEaZtexvLuQe9LeaDqyY19DyBaJIIiN6EcGZ4sXRa7M6QUD1HKjEWt62
-U4shHkPGIMgQWLcRZDepovlpGVXLmXEf
-=oXom
+U4shHkPGIMgQWLcRZDepovlpGVXLmXEfiQIcBBABAgAGBQJbnYNFAAoJECnuhIri
+zPP0M+4QAI9UJZJL7X7wPkLr3srTZBDbMhJkEQVHQolirrnb3Ojkp1NAQ8vsujqm
+tyPM9OPvQC3gylat/tQoMPO9C0/2ztfqMY0ZlxPuiys582wn23/Iqrwn3a3UicU4
+iiCwOS0iBVPeNFr5QuZRZJ58S9af1w53PoPr7ZdS0+bg4hcuO7sq/3VMzqmvkL5O
+ptXNd7LEiHzxZDMNrdsgnLwC+umOvzV4/wSY0N5BCwCGYpoTipH9X08KF19uSKGV
+xG80eEErYKo1o20OMAoDEUHlc6wIPArqdsXg9tt4qclyx0Y83LFWTyc4f6lesG3c
+dZKFZXFSjIFEn5NVgsX/NhzOkUag+rRqpJR6hxz2MunHGNGmmT+O6B/z84PEHxqp
+VbMFOIhQJO6MMwJ1sv2RTk4yNeZS7IcW4q/ApKTWRaNoDl3s6csIKGD1myHTGs8e
+/yAodElwIHXQL3TroY7kP0dtsDj3Am1+UTbM6O8ro4vjhOVm9Ce+f5nek+es9JiX
+4xCWC3ngjnqrPMGKMEWark9mJZRk+QOqBb1H9ZzQJnvbiq6MQJot83hMBxsirlZp
+AdI1p1Qzmai7TToQnzyrZqKc1fl9dJrbxxIbPbJNClU19LTcXraPh0GLVpgdkt1q
+27PKbyeWnKK7FBKnnFJeOXgO4HSP7XkpMiUSGYZgu/QpJ5SkipW8iFcEExECABcF
+Aj2PVCcFCwcKAwQDFQMCAxYCAQIXgAAKCRBCF4Sr1Bpt8u+VAJ9BWCevOxPoNaWm
+O81Sz1HIPuII+QCeNaquwRuYR19cjrT6C0HWwmd4v8eJAjMEEAEKAB0WIQSJGKuw
+tf7CG2l+zlr71JgaT2BXfAUCYsdB1QAKCRD71JgaT2BXfIiFEACqo6nZhMVjldEF
+Pat89Hka/OTSMtG/m4U5Sf5JgZD+DvqbVojk06+Ca8viLQgz0EJHthyLwP7T7L0y
+o0ZBTcaJAjTgjtEb9ZeAFuq+lgK9XCAf0+rElVqJ93cAhS6+hucsLjLhhakDr60K
+TUwJzdWMnzlizOQjEKm8TdYbukG3xYE/DVCYLHWW39aYGFcOo1N9+UszN4YKSjTa
+zJ1+yR9/VncIIH8KWN1EMW8bOAJ5SYjkdA5cQc1oP0aNqMM3mgTtCnCS0EFtoBxl
+HRc+tg9oJoCAnmj3sPvZfaFhLoqz8+Xp1QHRFSkxaunXr33L1Y/KxNWWCQBpe7J9
+/FSReMmKiqZaUoNRrkh7RfSGXtSIXpAB/cr5iTdty5TYLYCuGY7ITe0DzGjhn3GJ
+87i2uJgvVKETJV0lO6lcugqXN/FGXGlJNtgsWCmRlrkqix+S91ZCI6FNTfY9d78y
+fxNpWxElT9rs35CvggIDigQ8YJKVb9JSciZhXFSE1U/2/xW3DTUXrHQDmvB1Ch+I
+L5N75Q/n56eQKN5Mb2oUchXluJBg7Bu0oR63/vEDA5ALaK/6OX7u0pbTDhugFjym
+ssYorVQQgFarAZU13JmzNX3PWDf2VPTf/rxETHmBiVYnCh9Ois9ZYEpxTrWy0AY7
+RF7BYQDbZJbxCppcYIIDh1S6EYFvrw==
+=w+Ya
-----END PGP PUBLIC KEY BLOCK-----
The following is my DSA key 'D41A6DF2' signed with my old '2A398175'
diff --git a/progs/Makefile b/progs/Makefile
index 2c3c993..80f890a 100644
--- a/progs/Makefile
+++ b/progs/Makefile
@@ -14,21 +14,31 @@ ifeq ($(DYNAMIC),yes)
LDPATH = LD_LIBRARY_PATH=../libcap
DEPS = ../libcap/libcap.so
else
-LDSTATIC = --static
+ifeq ($(LIBCSTATIC),yes)
+LDFLAGS = --static
DEPS = ../libcap/libcap.a
+else
+# For this build variant override the LDFLAGS to link statically from
+# libraries within the build tree. If you never want this, use make
+# DYNAMIC=yes . Note, we can't reliably link statically against glibc
+# becasuse of https://sourceware.org/bugzilla/show_bug.cgi?id=12491 .
+LDFLAGS = -Wl,-Bstatic
+LDFLAGS_SUFFIX = -Wl,-Bdynamic
+DEPS = ../libcap/libcap.a
+endif
endif
../libcap/libcap.a:
- make -C ../libcap libcap.a
+ $(MAKE) -C ../libcap libcap.a
../libcap/libcap.so:
- make -C ../libcap libcap.so
+ $(MAKE) -C ../libcap libcap.so
$(BUILD): %: %.o $(DEPS)
- $(CC) $(CFLAGS) -o $@ $< $(LIBCAPLIB) $(LDSTATIC)
+ $(CC) $(CFLAGS) $(LDFLAGS) $< $(LIBCAPLIB) $(LDFLAGS_SUFFIX) -o $@
%.o: %.c $(INCS)
- $(CC) $(IPATH) $(CFLAGS) -c $< -o $@
+ $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
install: all
mkdir -p -m 0755 $(FAKEROOT)$(SBINDIR)
@@ -39,26 +49,29 @@ ifeq ($(RAISE_SETFCAP),yes)
$(FAKEROOT)$(SBINDIR)/setcap cap_setfcap=i $(FAKEROOT)$(SBINDIR)/setcap
endif
-test: $(PROGS) capsh
+test:
+ @echo "no program tests without privilege, try 'make sudotest'"
-capshdoc.h.cf: capshdoc.h ./mkcapshdoc.sh
+capshdoc.c.cf: capshdoc.c ./mkcapshdoc.sh
./mkcapshdoc.sh > $@
- diff -u capshdoc.h $@ || (rm $@ ; exit 1)
+ diff -u capshdoc.c $@ || (rm $@ ; exit 1)
-capsh: capsh.c capshdoc.h.cf $(DEPS)
- $(CC) $(IPATH) $(CAPSH_SHELL) $(CFLAGS) -o $@ $< $(LIBCAPLIB) $(LDSTATIC)
+capsh: capsh.c capshdoc.c.cf capshdoc.h $(DEPS)
+ $(CC) $(CFLAGS) $(CPPFLAGS) $(CAPSH_SHELL) $(LDFLAGS) $< capshdoc.c $(LIBCAPLIB) $(LDFLAGS_SUFFIX) -o $@
-tcapsh-static: capsh.c capshdoc.h.cf $(DEPS)
- $(CC) $(IPATH) $(CAPSH_SHELL) $(CFLAGS) -o $@ $< $(LIBCAPLIB) --static
+# Statically linked with minimal linkage flags to enable running in a
+# chroot and in other in-tree testing contexts.
+tcapsh-static: capsh.c capshdoc.c.cf capshdoc.h $(DEPS)
+ $(CC) $(CFLAGS) $(CPPFLAGS) $(CAPSH_SHELL) -o $@ $< capshdoc.c $(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
+sudotest: tcapsh-static uns_test capsh setcap getcap getpcaps tcapsh-static
+ $(SUDO) $(LDPATH) ./quicktest.sh
clean:
$(LOCALCLEAN)
rm -f *.o $(BUILD) privileged ping hack.sh compare-cap uns_test
- rm -f capsh tcapsh* capshdoc.h.cf
+ rm -f capsh tcapsh* capshdoc.*.cf
diff --git a/progs/capsh.c b/progs/capsh.c
index 50c2c99..f753291 100644
--- a/progs/capsh.c
+++ b/progs/capsh.c
@@ -40,6 +40,35 @@
#define MAX_GROUPS 100 /* max number of supplementary groups for user */
+/* parse a non-negative integer with some error handling */
+static unsigned long nonneg_uint(const char *text, const char *prefix, int *ok)
+{
+ char *remains;
+ unsigned long value;
+ ssize_t len = strlen(text);
+
+ if (len == 0 || *text == '-') {
+ goto fail;
+ }
+ value = strtoul(text, &remains, 0);
+ if (*remains) {
+ goto fail;
+ }
+ if (ok != NULL) {
+ *ok = 1;
+ }
+ return value;
+
+fail:
+ if (ok == NULL) {
+ fprintf(stderr, "%s: want non-negative integer, got \"%s\"\n",
+ prefix, text);
+ exit(1);
+ }
+ *ok = 0;
+ return 0;
+}
+
static char *binary(unsigned long value)
{
static char string[8*sizeof(unsigned long) + 1];
@@ -89,6 +118,10 @@ static void display_current(void)
char *text;
all = cap_get_proc();
+ if (all == NULL) {
+ perror("failed to get process capabilities");
+ exit(1);
+ }
text = cap_to_text(all, NULL);
printf("Current: %s\n", text);
cap_free(text);
@@ -101,7 +134,16 @@ static void display_current_iab(void)
char *text;
iab = cap_iab_get_proc();
+ if (iab == NULL) {
+ perror("failed to get IAB for process");
+ exit(1);
+ }
text = cap_iab_to_text(iab);
+ if (text == NULL) {
+ perror("failed to obtain text for IAB");
+ cap_free(iab);
+ exit(1);
+ }
printf("Current IAB: %s\n", text);
cap_free(text);
cap_free(iab);
@@ -178,29 +220,42 @@ static void arg_print(void)
static const cap_value_t raise_setpcap[1] = { CAP_SETPCAP };
static const cap_value_t raise_chroot[1] = { CAP_SYS_CHROOT };
-static void push_pcap(cap_t *orig_p, cap_t *raised_for_setpcap_p)
+static cap_t will_need_setpcap(int strict)
{
- /*
- * We need to do this here because --inh=XXX may have reset
- * orig and it isn't until we are within the --drop code that
- * we know what the prevailing (orig) pI value is.
- */
- *orig_p = cap_get_proc();
- if (NULL == *orig_p) {
+ cap_flag_value_t enabled;
+ cap_t raised = NULL;
+
+ if (strict) {
+ return NULL;
+ }
+
+ raised = cap_get_proc();
+ if (raised == NULL) {
perror("Capabilities not available");
exit(1);
}
-
- *raised_for_setpcap_p = cap_dup(*orig_p);
- if (NULL == *raised_for_setpcap_p) {
- fprintf(stderr, "modification requires CAP_SETPCAP\n");
+ if (cap_get_flag(raised, CAP_SETPCAP, CAP_EFFECTIVE, &enabled) != 0) {
+ perror("Unable to check CAP_EFFECTIVE CAP_SETPCAP value");
exit(1);
}
- if (cap_set_flag(*raised_for_setpcap_p, CAP_EFFECTIVE, 1,
- raise_setpcap, CAP_SET) != 0) {
- perror("unable to select CAP_SETPCAP");
+ if (enabled != CAP_SET) {
+ cap_set_flag(raised, CAP_EFFECTIVE, 1, raise_setpcap, CAP_SET);
+ } else {
+ /* no need to raise - since already raised */
+ cap_free(raised);
+ raised = NULL;
+ }
+ return raised;
+}
+
+static void push_pcap(int strict, cap_t *orig_p, cap_t *raised_for_setpcap_p)
+{
+ *orig_p = cap_get_proc();
+ if (NULL == *orig_p) {
+ perror("Capabilities not available");
exit(1);
}
+ *raised_for_setpcap_p = will_need_setpcap(strict);
}
static void pop_pcap(cap_t orig, cap_t raised_for_setpcap)
@@ -209,23 +264,24 @@ static void pop_pcap(cap_t orig, cap_t raised_for_setpcap)
cap_free(orig);
}
-static void arg_drop(const char *arg_names)
+static void arg_drop(int strict, const char *arg_names)
{
char *ptr;
cap_t orig, raised_for_setpcap;
char *names;
- push_pcap(&orig, &raised_for_setpcap);
+ push_pcap(strict, &orig, &raised_for_setpcap);
if (strcmp("all", arg_names) == 0) {
unsigned j = 0;
while (CAP_IS_SUPPORTED(j)) {
int status;
- if (cap_set_proc(raised_for_setpcap) != 0) {
+ if (raised_for_setpcap != NULL &&
+ cap_set_proc(raised_for_setpcap) != 0) {
perror("unable to raise CAP_SETPCAP for BSET changes");
exit(1);
}
status = cap_drop_bound(j);
- if (cap_set_proc(orig) != 0) {
+ if (raised_for_setpcap != NULL && cap_set_proc(orig) != 0) {
perror("unable to lower CAP_SETPCAP post BSET change");
exit(1);
}
@@ -258,12 +314,13 @@ static void arg_drop(const char *arg_names)
fprintf(stderr, "capability [%s] is unknown to libcap\n", ptr);
exit(1);
}
- if (cap_set_proc(raised_for_setpcap) != 0) {
+ if (raised_for_setpcap != NULL &&
+ cap_set_proc(raised_for_setpcap) != 0) {
perror("unable to raise CAP_SETPCAP for BSET changes");
exit(1);
}
status = cap_drop_bound(cap);
- if (cap_set_proc(orig) != 0) {
+ if (raised_for_setpcap != NULL && cap_set_proc(orig) != 0) {
perror("unable to lower CAP_SETPCAP post BSET change");
exit(1);
}
@@ -279,23 +336,15 @@ static void arg_drop(const char *arg_names)
static void arg_change_amb(const char *arg_names, cap_flag_value_t set)
{
char *ptr;
- cap_t orig, raised_for_setpcap;
+ cap_t orig;
char *names;
- push_pcap(&orig, &raised_for_setpcap);
+ orig = cap_get_proc();
if (strcmp("all", arg_names) == 0) {
unsigned j = 0;
while (CAP_IS_SUPPORTED(j)) {
int status;
- if (cap_set_proc(raised_for_setpcap) != 0) {
- perror("unable to raise CAP_SETPCAP for AMBIENT changes");
- exit(1);
- }
status = cap_set_ambient(j, set);
- if (cap_set_proc(orig) != 0) {
- perror("unable to lower CAP_SETPCAP post AMBIENT change");
- exit(1);
- }
if (status != 0) {
char *name_ptr;
@@ -307,7 +356,7 @@ static void arg_change_amb(const char *arg_names, cap_flag_value_t set)
}
j++;
}
- pop_pcap(orig, raised_for_setpcap);
+ cap_free(orig);
return;
}
@@ -325,22 +374,14 @@ static void arg_change_amb(const char *arg_names, cap_flag_value_t set)
fprintf(stderr, "capability [%s] is unknown to libcap\n", ptr);
exit(1);
}
- if (cap_set_proc(raised_for_setpcap) != 0) {
- perror("unable to raise CAP_SETPCAP for AMBIENT changes");
- exit(1);
- }
status = cap_set_ambient(cap, set);
- if (cap_set_proc(orig) != 0) {
- perror("unable to lower CAP_SETPCAP post AMBIENT change");
- exit(1);
- }
if (status != 0) {
fprintf(stderr, "failed to %s ambient [%s=%u]\n",
set == CAP_CLEAR ? "clear":"raise", ptr, cap);
exit(1);
}
}
- pop_pcap(orig, raised_for_setpcap);
+ cap_free(orig);
free(names);
}
@@ -427,24 +468,70 @@ static void describe(cap_value_t cap) {
}
}
-int main(int argc, char *argv[], char *envp[])
+__attribute__ ((noreturn))
+static void do_launch(char *args[], char *envp[])
{
+ cap_launch_t lau;
pid_t child;
- unsigned i;
- const char *shell = SHELL;
+ int ret, result;
- child = 0;
-
- char *temp_name = cap_to_name(cap_max_bits() - 1);
- if (temp_name[0] != 'c') {
- printf("WARNING: libcap needs an update (cap=%d should have a name).\n",
- cap_max_bits() - 1);
+ lau = cap_new_launcher(args[0], (void *) args, (void *) envp);
+ if (lau == NULL) {
+ perror("failed to create launcher");
+ exit(1);
+ }
+ child = cap_launch(lau, NULL);
+ if (child <= 0) {
+ perror("child failed to start");
+ exit(1);
}
- cap_free(temp_name);
+ cap_free(lau);
+ ret = waitpid(child, &result, 0);
+ if (ret != child) {
+ fprintf(stderr, "failed to wait for PID=%d, result=%x: ",
+ child, result);
+ perror("");
+ exit(1);
+ }
+ if (WIFEXITED(result)) {
+ exit(WEXITSTATUS(result));
+ }
+ if (WIFSIGNALED(result)) {
+ fprintf(stderr, "child PID=%d terminated by signo=%d\n",
+ child, WTERMSIG(result));
+ exit(1);
+ }
+ fprintf(stderr, "child PID=%d generated result=%0x\n", child, result);
+ exit(1);
+}
+
+int main(int argc, char *argv[], char *envp[])
+{
+ pid_t child = 0;
+ unsigned i;
+ int strict = 0, quiet_start = 0, dont_set_env = 0;
+ const char *shell = SHELL;
for (i=1; i<argc; ++i) {
+ if (!strcmp("--quiet", argv[i])) {
+ quiet_start = 1;
+ continue;
+ }
+ if (i == 1) {
+ char *temp_name = cap_to_name(cap_max_bits() - 1);
+ if (temp_name == NULL) {
+ perror("obtaining highest capability name");
+ exit(1);
+ }
+ if (temp_name[0] != 'c') {
+ printf("WARNING: libcap needs an update"
+ " (cap=%d should have a name).\n",
+ cap_max_bits() - 1);
+ }
+ cap_free(temp_name);
+ }
if (!strncmp("--drop=", argv[i], 7)) {
- arg_drop(argv[i]+7);
+ arg_drop(strict, argv[i]+7);
} else if (!strncmp("--dropped=", argv[i], 10)) {
cap_value_t cap;
if (cap_from_name(argv[i]+10, &cap) < 0) {
@@ -459,7 +546,7 @@ int main(int argc, char *argv[], char *envp[])
}
} else if (!strcmp("--has-ambient", argv[i])) {
if (!CAP_AMBIENT_SUPPORTED()) {
- fprintf(stderr, "ambient set not supported\n");
+ perror("ambient set not supported");
exit(1);
}
} else if (!strncmp("--addamb=", argv[i], 9)) {
@@ -468,32 +555,21 @@ int main(int argc, char *argv[], char *envp[])
arg_change_amb(argv[i]+9, CAP_CLEAR);
} else if (!strncmp("--noamb", argv[i], 7)) {
if (cap_reset_ambient() != 0) {
- fprintf(stderr, "failed to reset ambient set\n");
+ perror("failed to reset ambient set");
exit(1);
}
+ } else if (!strcmp("--noenv", argv[i])) {
+ dont_set_env = 1;
} else if (!strncmp("--inh=", argv[i], 6)) {
cap_t all, raised_for_setpcap;
char *text;
char *ptr;
- all = cap_get_proc();
- if (all == NULL) {
- perror("Capabilities not available");
- exit(1);
- }
+ push_pcap(strict, &all, &raised_for_setpcap);
if (cap_clear_flag(all, CAP_INHERITABLE) != 0) {
perror("libcap:cap_clear_flag() internal error");
exit(1);
}
-
- raised_for_setpcap = cap_dup(all);
- if ((raised_for_setpcap != NULL)
- && (cap_set_flag(raised_for_setpcap, CAP_EFFECTIVE, 1,
- raise_setpcap, CAP_SET) != 0)) {
- cap_free(raised_for_setpcap);
- raised_for_setpcap = NULL;
- }
-
text = cap_to_text(all, NULL);
cap_free(all);
if (text == NULL) {
@@ -510,13 +586,13 @@ int main(int argc, char *argv[], char *envp[])
} else {
strcpy(ptr, text);
}
+ cap_free(text);
all = cap_from_text(ptr);
if (all == NULL) {
perror("Fatal error internalizing capabilities");
exit(1);
}
- cap_free(text);
free(ptr);
if (raised_for_setpcap != NULL) {
@@ -534,45 +610,29 @@ int main(int argc, char *argv[], char *envp[])
perror("Unable to set inheritable capabilities");
exit(1);
}
- /*
- * Since status is based on orig, we don't want to restore
- * the previous value of 'all' again here!
- */
-
cap_free(all);
+ } else if (!strcmp("--strict", argv[i])) {
+ strict = !strict;
} else if (!strncmp("--caps=", argv[i], 7)) {
cap_t all, raised_for_setpcap;
- raised_for_setpcap = cap_get_proc();
- if (raised_for_setpcap == NULL) {
- perror("Capabilities not available");
- exit(1);
- }
-
- if ((raised_for_setpcap != NULL)
- && (cap_set_flag(raised_for_setpcap, CAP_EFFECTIVE, 1,
- raise_setpcap, CAP_SET) != 0)) {
- cap_free(raised_for_setpcap);
- raised_for_setpcap = NULL;
- }
-
+ raised_for_setpcap = will_need_setpcap(strict);
all = cap_from_text(argv[i]+7);
if (all == NULL) {
fprintf(stderr, "unable to interpret [%s]\n", argv[i]);
exit(1);
}
-
if (raised_for_setpcap != NULL) {
/*
- * This is only for the case that pP does not contain
- * the requested change to pI.. Failing here is not
- * indicative of the cap_set_proc(all) failing (always).
+ * This is actually only for the case that pP does not
+ * contain the requested change to pI.. Failing here
+ * is not always indicative of the cap_set_proc(all)
+ * failing.
*/
(void) cap_set_proc(raised_for_setpcap);
cap_free(raised_for_setpcap);
raised_for_setpcap = NULL;
}
-
if (cap_set_proc(all) != 0) {
fprintf(stderr, "Unable to set capabilities [%s]\n", argv[i]);
exit(1);
@@ -581,7 +641,6 @@ int main(int argc, char *argv[], char *envp[])
* Since status is based on orig, we don't want to restore
* the previous value of 'all' again here!
*/
-
cap_free(all);
} else if (!strcmp("--modes", argv[i])) {
cap_mode_t c;
@@ -594,30 +653,38 @@ int main(int argc, char *argv[], char *envp[])
printf(" %s", m);
}
printf("\n");
- } else if (!strncmp("--mode=", argv[i], 7)) {
- const char *target = argv[i]+7;
- cap_mode_t c;
- int found = 0;
- for (c = 1; ; c++) {
- const char *m = cap_mode_name(c);
- if (!strcmp("UNKNOWN", m)) {
- found = 0;
- break;
+ } else if (!strncmp("--mode", argv[i], 6)) {
+ if (argv[i][6] == '=') {
+ const char *target = argv[i]+7;
+ cap_mode_t c;
+ int found = 0;
+ for (c = 1; ; c++) {
+ const char *m = cap_mode_name(c);
+ if (!strcmp("UNKNOWN", m)) {
+ found = 0;
+ break;
+ }
+ if (!strcmp(m, target)) {
+ found = 1;
+ break;
+ }
}
- if (!strcmp(m, target)) {
- found = 1;
- break;
+ if (!found) {
+ printf("unsupported mode: %s\n", target);
+ exit(1);
}
- }
- if (!found) {
- printf("unsupported mode: %s\n", target);
- exit(1);
- }
- int ret = cap_set_mode(c);
- if (ret != 0) {
- printf("failed to set mode [%s]: %s\n",
- target, strerror(errno));
- exit(1);
+ int ret = cap_set_mode(c);
+ if (ret != 0) {
+ printf("failed to set mode [%s]: %s\n",
+ target, strerror(errno));
+ exit(1);
+ }
+ } else if (argv[i][6]) {
+ printf("unrecognized command [%s]\n", argv[i]);
+ goto usage;
+ } else {
+ cap_mode_t m = cap_get_mode();
+ printf("Mode: %s\n", cap_mode_name(m));
}
} else if (!strncmp("--inmode=", argv[i], 9)) {
const char *target = argv[i]+9;
@@ -631,7 +698,7 @@ int main(int argc, char *argv[], char *envp[])
unsigned value;
int set;
- value = strtoul(argv[i]+7, NULL, 0);
+ value = nonneg_uint(argv[i]+7, "invalid --keep value", NULL);
set = prctl(PR_SET_KEEPCAPS, value);
if (set < 0) {
fprintf(stderr, "prctl(PR_SET_KEEPCAPS, %u) failed: %s\n",
@@ -688,7 +755,7 @@ int main(int argc, char *argv[], char *envp[])
} else if (!strncmp("--secbits=", argv[i], 10)) {
unsigned value;
int status;
- value = strtoul(argv[i]+10, NULL, 0);
+ value = nonneg_uint(argv[i]+10, "invalid --secbits value", NULL);
status = cap_set_secbits(value);
if (status < 0) {
fprintf(stderr, "failed to set securebits to 0%o/0x%x\n",
@@ -701,8 +768,9 @@ int main(int argc, char *argv[], char *envp[])
fprintf(stderr, "already forked\n");
exit(1);
}
- value = strtoul(argv[i]+10, NULL, 0);
+ value = nonneg_uint(argv[i]+10, "invalid --forkfor value", NULL);
if (value == 0) {
+ fprintf(stderr, "require non-zero --forkfor value\n");
goto usage;
}
child = fork();
@@ -717,7 +785,8 @@ int main(int argc, char *argv[], char *envp[])
pid_t result;
unsigned value;
- value = strtoul(argv[i]+9, NULL, 0);
+ value = nonneg_uint(argv[i]+9, "invalid --killit signo value",
+ NULL);
if (!child) {
fprintf(stderr, "no forked process to kill\n");
exit(1);
@@ -743,7 +812,7 @@ int main(int argc, char *argv[], char *envp[])
unsigned value;
int status;
- value = strtoul(argv[i]+6, NULL, 0);
+ value = nonneg_uint(argv[i]+6, "invalid --uid value", NULL);
status = setuid(value);
if (status < 0) {
fprintf(stderr, "Failed to set uid=%u: %s\n",
@@ -754,7 +823,7 @@ int main(int argc, char *argv[], char *envp[])
unsigned value;
int status;
- value = strtoul(argv[i]+10, NULL, 0);
+ value = nonneg_uint(argv[i]+10, "invalid --cap-uid value", NULL);
status = cap_setuid(value);
if (status < 0) {
fprintf(stderr, "Failed to cap_setuid(%u): %s\n",
@@ -765,7 +834,7 @@ int main(int argc, char *argv[], char *envp[])
unsigned value;
int status;
- value = strtoul(argv[i]+6, NULL, 0);
+ value = nonneg_uint(argv[i]+6, "invalid --gid value", NULL);
status = setgid(value);
if (status < 0) {
fprintf(stderr, "Failed to set gid=%u: %s\n",
@@ -845,6 +914,20 @@ int main(int argc, char *argv[], char *envp[])
pwd->pw_uid, user, strerror(errno));
exit(1);
}
+ if (!dont_set_env) {
+ /*
+ * not setting this confuses bash at start up, but use
+ * --noenv to preserve the HOME etc values instead.
+ */
+ if (setenv("HOME", pwd->pw_dir, 1) != 0) {
+ perror("unable to set HOME");
+ exit(1);
+ }
+ if (setenv("USER", user, 1) != 0) {
+ perror("unable to set USER");
+ exit(1);
+ }
+ }
} else if (!strncmp("--decode=", argv[i], 9)) {
unsigned long long value;
unsigned cap;
@@ -885,13 +968,22 @@ int main(int argc, char *argv[], char *envp[])
}
} else if (!strcmp("--print", argv[i])) {
arg_print();
- } else if ((!strcmp("--", argv[i])) || (!strcmp("==", argv[i]))) {
+ } else if ((!strcmp("--", argv[i])) || (!strcmp("==", argv[i]))
+ || (!strcmp("-+", argv[i])) || (!strcmp("=+", argv[i]))) {
+ int launch = argv[i][1] == '+';
if (argv[i][0] == '=') {
+ if (quiet_start) {
+ argv[i--] = strdup("--quiet");
+ }
argv[i] = find_self(argv[0]);
} else {
argv[i] = strdup(shell);
}
argv[argc] = NULL;
+ /* Two ways to chain load - use cap_launch() or execve() */
+ if (launch) {
+ do_launch(argv+i, envp);
+ }
execve(argv[i], argv+i, envp);
fprintf(stderr, "execve '%s' failed!\n", argv[i]);
free(argv[i]);
@@ -909,6 +1001,10 @@ int main(int argc, char *argv[], char *envp[])
exit(1);
}
orig = cap_get_proc();
+ if (orig == NULL) {
+ perror("failed to get process capabilities");
+ exit(1);
+ }
if (cap_get_flag(orig, cap, CAP_PERMITTED, &enabled) || !enabled) {
fprintf(stderr, "cap[%s] not permitted\n", argv[i]+8);
exit(1);
@@ -925,6 +1021,10 @@ int main(int argc, char *argv[], char *envp[])
exit(1);
}
orig = cap_get_proc();
+ if (orig == NULL) {
+ perror("failed to get process capabilities");
+ exit(1);
+ }
if (cap_get_flag(orig, cap, CAP_INHERITABLE, &enabled)
|| !enabled) {
fprintf(stderr, "cap[%s] not inheritable\n", argv[i]+8);
@@ -942,10 +1042,21 @@ int main(int argc, char *argv[], char *envp[])
fprintf(stderr, "cap[%s] not in ambient vector\n", argv[i]+8);
exit(1);
}
+ } else if (!strncmp("--has-b=", argv[i], 8)) {
+ cap_value_t cap;
+ if (cap_from_name(argv[i]+8, &cap) < 0) {
+ fprintf(stderr, "cap[%s] not recognized by library\n",
+ argv[i] + 8);
+ exit(1);
+ }
+ if (!cap_get_bound(cap)) {
+ fprintf(stderr, "cap[%s] not in bounding vector\n", argv[i]+8);
+ exit(1);
+ }
} else if (!strncmp("--is-uid=", argv[i], 9)) {
unsigned value;
uid_t uid;
- value = strtoul(argv[i]+9, NULL, 0);
+ value = nonneg_uint(argv[i]+9, "invalid --is-uid value", NULL);
uid = getuid();
if (uid != value) {
fprintf(stderr, "uid: got=%d, want=%d\n", uid, value);
@@ -954,7 +1065,7 @@ int main(int argc, char *argv[], char *envp[])
} else if (!strncmp("--is-gid=", argv[i], 9)) {
unsigned value;
gid_t gid;
- value = strtoul(argv[i]+9, NULL, 0);
+ value = nonneg_uint(argv[i]+9, "invalid --is-gid value", NULL);
gid = getgid();
if (gid != value) {
fprintf(stderr, "gid: got=%d, want=%d\n", gid, value);
@@ -967,7 +1078,7 @@ int main(int argc, char *argv[], char *envp[])
exit(1);
}
if (cap_iab_set_proc(iab)) {
- perror("unable to set IAP vectors");
+ perror("unable to set IAB tuple");
exit(1);
}
cap_free(iab);
@@ -983,7 +1094,7 @@ int main(int argc, char *argv[], char *envp[])
}
} else if (!strcmp("--license", argv[i])) {
printf(
- "%s see LICENSE file for details.\n"
+ "%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);
@@ -997,7 +1108,7 @@ int main(int argc, char *argv[], char *envp[])
fprintf(stderr, "negative capability (%d) invalid\n", cap);
exit(1);
}
- if (cap < CAPSH_DOC_LIMIT) {
+ if (cap < capsh_doc_limit) {
describe(cap);
continue;
}
@@ -1010,10 +1121,14 @@ int main(int argc, char *argv[], char *envp[])
} else if (!strncmp("--suggest=", argv[i], 10)) {
cap_value_t cap;
int hits = 0;
- for (cap=0; cap < CAPSH_DOC_LIMIT; cap++) {
+ for (cap=0; cap < capsh_doc_limit; cap++) {
const char **lines = explanations[cap];
int j;
char *name = cap_to_name(cap);
+ if (name == NULL) {
+ perror("invalid named cap");
+ exit(1);
+ }
char *match = strcasestr(name, argv[i]+10);
cap_free(name);
if (match != NULL) {
@@ -1046,11 +1161,13 @@ int main(int argc, char *argv[], char *envp[])
" --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"
+ " --drop=xxx drop xxx,... caps from bounding set\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-a=xxx exit 1 if capability xxx not ambient\n"
+ " --has-b=xxx exit 1 if capability xxx not dropped\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"
@@ -1064,19 +1181,25 @@ int main(int argc, char *argv[], char *envp[])
" --keep=<n> set keep-capability bit to <n>\n"
" --killit=<n> send signal(n) to child\n"
" --license display license info\n"
- " --modes list libcap named capability modes\n"
- " --mode=<xxx> set capability mode to <xxx>\n"
+ " --mode display current libcap mode\n"
+ " --mode=<xxx> set libcap mode to <xxx>\n"
+ " --modes list libcap named modes\n"
" --no-new-privs set sticky process privilege limiter\n"
" --noamb reset (drop) all ambient capabilities\n"
+ " --noenv no fixup of env vars (for --user)\n"
" --print display capability relevant state\n"
+ " --quiet if first argument skip max cap check\n"
" --secbits=<n> write a new value for securebits\n"
" --shell=/xx/yy use /xx/yy instead of " SHELL " for --\n"
+ " --strict toggle --caps, --drop and --inh fixups\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"
" == re-exec(capsh) with args as for --\n"
+ " =+ cap_launch capsh with args as for -+\n"
" -- remaining arguments are for " SHELL "\n"
+ " -+ cap_launch " SHELL " with remaining args\n"
" (without -- [%s] will simply exit(0))\n",
argv[0], argv[0]);
if (strcmp("--help", argv[1]) && strcmp("-h", argv[1])) {
diff --git a/progs/capshdoc.c b/progs/capshdoc.c
new file mode 100644
index 0000000..5560ef9
--- /dev/null
+++ b/progs/capshdoc.c
@@ -0,0 +1,422 @@
+#include <stdio.h>
+
+#include "./capshdoc.h"
+
+/*
+ * 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.",
+ "",
+ "This capability permits the deletion of a file owned",
+ "by another UID in a directory protected by the sticky",
+ "(t) bit.",
+ 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, the POXIX.1e (X",
+ "vector) known in Linux as the Bounding vector, as well as",
+ "the Linux extension Ambient vector.",
+ "",
+ "This capability permits dropping bits from the Bounding",
+ "vector (ie. raising B bits in the libcap IAB",
+ "representation). 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, Effective bits beyond those",
+ "already present in the process' permitted set, or",
+ "Inheritable bits beyond those present in the Bounding",
+ "vector.",
+ "",
+ "[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
+};
+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,
+};
+
+const int capsh_doc_limit = 41;
diff --git a/progs/capshdoc.h b/progs/capshdoc.h
index c182144..d9cbab9 100644
--- a/progs/capshdoc.h
+++ b/progs/capshdoc.h
@@ -3,411 +3,5 @@
#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
+extern const char **explanations[];
+extern const int capsh_doc_limit;
diff --git a/progs/getcap.c b/progs/getcap.c
index eec733b..780943d 100644
--- a/progs/getcap.c
+++ b/progs/getcap.c
@@ -49,7 +49,7 @@ static int do_getcap(const char *fname, const struct stat *stbuf,
cap_d = cap_get_file(fname);
if (cap_d == NULL) {
- if (errno != ENODATA) {
+ if (errno != ENODATA && errno != ENOTSUP) {
fprintf(stderr, "Failed to get capabilities of file '%s' (%s)\n",
fname, strerror(errno));
} else if (verbose) {
@@ -110,11 +110,11 @@ int main(int argc, char **argv)
for (i=optind; argv[i] != NULL; i++) {
struct stat stbuf;
-
- if (lstat(argv[i], &stbuf) != 0) {
- fprintf(stderr, "%s (%s)\n", argv[i], strerror(errno));
+ char *arg = argv[i];
+ if (lstat(arg, &stbuf) != 0) {
+ fprintf(stderr, "%s (%s)\n", arg, strerror(errno));
} else if (recursive) {
- nftw(argv[i], do_getcap, 20, FTW_PHYS);
+ nftw(arg, do_getcap, 20, FTW_PHYS);
} else {
int tflag = S_ISREG(stbuf.st_mode) ? FTW_F :
(S_ISLNK(stbuf.st_mode) ? FTW_SL : FTW_NS);
diff --git a/progs/getpcaps.c b/progs/getpcaps.c
index 5e78487..7e14c36 100644
--- a/progs/getpcaps.c
+++ b/progs/getpcaps.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997,2008 Andrew G. Morgan <morgan@kernel.org>
+ * Copyright (c) 1997-8,2007-8,19,21-22 Andrew G. Morgan <morgan@kernel.org>
*
* This displays the capabilities of given target process(es).
*/
@@ -14,7 +14,7 @@
static void usage(int code)
{
fprintf(stderr,
-"usage: getcaps <pid> [<pid> ...]\n\n"
+"usage: getcaps [opts] <pid> [<pid> ...]\n\n"
" This program displays the capabilities on the queried process(es).\n"
" The capabilities are displayed in the cap_from_text(3) format.\n"
"\n"
@@ -22,6 +22,7 @@ static void usage(int code)
" --help, -h or --usage display this message.\n"
" --verbose use a more verbose output format.\n"
" --ugly or --legacy use the archaic legacy output format.\n"
+ " --iab show IAB of process too.\n"
" --license display license info\n");
exit(code);
}
@@ -30,34 +31,55 @@ int main(int argc, char **argv)
{
int retval = 0;
int verbose = 0;
+ int iab = 0;
+ cap_iab_t noiab = cap_iab_init();
if (argc < 2) {
usage(1);
}
- for ( ++argv; --argc > 0; ++argv ) {
- ssize_t length;
+ for (++argv; --argc > 0; ++argv) {
+ long lpid;
int pid;
+ char *endarg;
cap_t cap_d;
+ const char *arg = *argv;
- if (!strcmp(argv[0], "--help") || !strcmp(argv[0], "--usage") ||
- !strcmp(argv[0], "-h")) {
+ if (!strcmp(arg, "--help") || !strcmp(arg, "--usage") ||
+ !strcmp(arg, "-h")) {
usage(0);
- } else if (!strcmp(argv[0], "--license")) {
+ } else if (!strcmp(arg, "--license")) {
printf("%s see LICENSE file for details.\n"
- "[Copyright (c) 1997-8,2007,19,21"
+ "[Copyright (c) 1997-8,2007-8,19,21-22"
" Andrew G. Morgan <morgan@kernel.org>]\n",
- argv[0]);
+ arg);
exit(0);
- } else if (!strcmp(argv[0], "--verbose")) {
+ } else if (!strcmp(arg, "--verbose")) {
verbose = 1;
continue;
- } else if (!strcmp(argv[0], "--ugly") || !strcmp(argv[0], "--legacy")) {
+ } else if (!strcmp(arg, "--ugly") || !strcmp(arg, "--legacy")) {
verbose = 2;
continue;
+ } else if (!strcmp(arg, "--iab")) {
+ iab = 1;
+ continue;
}
- pid = atoi(argv[0]);
+ errno = 0;
+ lpid = strtol(arg, &endarg, 10);
+ if (errno == 0) {
+ if (*endarg != '\0') {
+ errno = EINVAL;
+ } else if (lpid < 0 || lpid != (pid_t) lpid) {
+ errno = EOVERFLOW;
+ }
+ }
+ if (errno != 0) {
+ fprintf(stderr, "Cannot parse pid %s: (%s)\n", arg, strerror(errno));
+ retval = 1;
+ continue;
+ }
+ pid = lpid;
cap_d = cap_get_pid(pid);
if (cap_d == NULL) {
@@ -65,19 +87,44 @@ int main(int argc, char **argv)
" (%s)\n", pid, strerror(errno));
retval = 1;
continue;
- } else {
- char *result = cap_to_text(cap_d, &length);
- if (verbose == 1) {
- printf("Capabilities for '%s': %s\n", *argv, result);
- } else if (verbose == 2) {
- fprintf(stderr, "Capabilities for `%s': %s\n", *argv, result);
- } else {
- printf("%s: %s\n", *argv, result);
+ }
+
+ char *result = cap_to_text(cap_d, NULL);
+ if (iab) {
+ printf("%s:", arg);
+ if (verbose || strcmp("=", result) != 0) {
+ printf(" \"%s\"", result);
+ }
+ cap_iab_t iab_val = cap_iab_get_pid(pid);
+ if (iab_val == NULL) {
+ fprintf(stderr, " no IAB value for %d\n", pid);
+ exit(1);
}
- cap_free(result);
- result = NULL;
- cap_free(cap_d);
+ int cf = cap_iab_compare(noiab, iab_val);
+ if (verbose ||
+ CAP_IAB_DIFFERS(cf, CAP_IAB_AMB) ||
+ CAP_IAB_DIFFERS(cf, CAP_IAB_BOUND)) {
+ char *iab_text = cap_iab_to_text(iab_val);
+ if (iab_text == NULL) {
+ perror(" no text for IAB");
+ exit(1);
+ }
+ printf(" [%s]", iab_text);
+ cap_free(iab_text);
+ }
+ cap_free(iab_val);
+ printf("\n");
+ } else if (verbose == 1) {
+ printf("Capabilities for '%s': %s\n", arg, result);
+ } else if (verbose == 2) {
+ fprintf(stderr, "Capabilities for `%s': %s\n", arg, result);
+ } else {
+ printf("%s: %s\n", arg, result);
}
+
+ cap_free(result);
+ result = NULL;
+ cap_free(cap_d);
}
return retval;
diff --git a/progs/mkcapshdoc.sh b/progs/mkcapshdoc.sh
index 705d526..d2ee4bd 100755
--- a/progs/mkcapshdoc.sh
+++ b/progs/mkcapshdoc.sh
@@ -1,13 +1,12 @@
#!/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
+# The Makefile generally only generates the .c 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
+#include <stdio.h>
+
+#include "./capshdoc.h"
/*
* A line by line explanation of each named capability value
@@ -16,7 +15,7 @@ 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/},//')
+ name=$(grep -F ",${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}
@@ -25,7 +24,7 @@ while [ -f "../doc/values/${x}.txt" ]; do
done
cat<<EOF
-static const char **explanations[] = {
+const char **explanations[] = {
EOF
let y=0
while [ "${y}" -lt "${x}" ]; do
@@ -34,5 +33,6 @@ while [ "${y}" -lt "${x}" ]; do
done
cat<<EOF
};
-#define CAPSH_DOC_LIMIT ${x}
+
+const int capsh_doc_limit = ${x};
EOF
diff --git a/progs/quicktest.sh b/progs/quicktest.sh
index ba64ab5..59e16b0 100755
--- a/progs/quicktest.sh
+++ b/progs/quicktest.sh
@@ -79,7 +79,7 @@ fail_capsh --mode=NOPRIV --print --mode=PURE1E
fail_capsh --user=nobody --mode=NOPRIV --print -- ./privileged
# simple IAB setting (no ambient) in pure1e mode.
-pass_capsh --mode=PURE1E --iab='!%cap_chown,cap_sys_admin'
+pass_capsh --mode=PURE1E --iab='!%cap_chown,cap_setuid'
# Explore keep_caps support
pass_capsh --keep=0 --keep=1 --keep=0 --keep=1 --print
@@ -94,14 +94,14 @@ pass_capsh --keep=0 --keep=1 --keep=0 --keep=1 --print
# 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.
-pass_capsh --uid=1 -- -c "./tcapsh --keep=1 --caps=\"cap_net_raw,cap_net_admin=ip\" --print --uid=1 --print --caps=\"cap_net_raw,cap_net_admin=pie\" --print"
+pass_capsh --uid=1 -- -c "./tcapsh --keep=1 --caps=\"cap_net_raw,cap_net_bind_service=ip\" --print --uid=1 --print --caps=\"cap_net_raw,cap_net_bind_service=pie\" --print"
# this test is a change of user to a new user, note we need to raise
# the cap_setuid capability (libcap has a function for that) in this case.
-pass_capsh --uid=1 -- -c "./tcapsh --caps=\"cap_net_raw,cap_net_admin=ip cap_setuid=p\" --print --cap-uid=2 --print --caps=\"cap_net_raw,cap_net_admin=pie\" --print"
+pass_capsh --uid=1 -- -c "./tcapsh --caps=\"cap_net_raw,cap_net_bind_service=ip cap_setuid=p\" --print --cap-uid=2 --print --caps=\"cap_net_raw,cap_net_bind_service=pie\" --print"
# This fails, on 2.6.24, but shouldn't
-pass_capsh --uid=1 -- -c "./tcapsh --keep=1 --caps=\"cap_net_raw,cap_net_admin=ip\" --uid=1 --forkfor=10 --caps= --print --killit=9 --print"
+pass_capsh --uid=1 -- -c "./tcapsh --keep=1 --caps=\"cap_net_raw,cap_net_bind_service=ip\" --uid=1 --forkfor=10 --caps= --print --killit=9 --print"
# only continue with these if --secbits is supported
./capsh --secbits=0x2f > /dev/null 2>&1
@@ -130,7 +130,22 @@ fail_capsh --secbits=47 --print -- -c "./capsh --uid=$nouid"
pass_capsh --secbits=0x2f --print -- -c "./privileged --uid=$nouid"
# observe that the bounding set can be used to suppress this forced capability
-fail_capsh --drop=cap_setuid --secbits=0x2f --print -- -c "./privileged --uid=$nouid"
+fail_capsh --drop=cap_setuid --secbits=0x2f --print -- \
+ -c "./privileged --uid=$nouid"
+
+# observe that effective cap_setpcap is required to drop bset
+fail_capsh --caps="=ep cap_setpcap-ep" --drop=cap_setuid --current
+pass_capsh --strict --caps="cap_setpcap=ep" --drop=cap_setuid --current
+fail_capsh --strict --caps="cap_setpcap=p" --drop=cap_setuid --current
+fail_capsh --strict --caps="=ep cap_setpcap-e" --drop=cap_setuid --current
+
+# observe that effective cap_setpcap is required to raise non-p bits
+fail_capsh --strict --caps="cap_setpcap=p" --inh=cap_chown --current
+# non-strict mode and capsh figures it out
+pass_capsh --caps="cap_setpcap=p" --inh=cap_chown --current
+
+# permitted bits can be raised in inheritable flag without being effective.
+pass_capsh --strict --caps="cap_chown=p" --inh=cap_chown --current
# change the way the capability is obtained (make it inheritable)
./setcap cap_setuid,cap_setgid=ei ./privileged
@@ -199,7 +214,8 @@ echo "no capabilities [\$caps] for this shell script"
exit 1
EOF
/bin/chmod +x hack.sh
- pass_capsh --keep=1 --uid=$nouid --inh=cap_setuid --addamb=cap_setuid -- ./hack.sh
+ pass_capsh --keep=1 --uid=$nouid --inh=cap_setuid --addamb=cap_setuid -- \
+ ./hack.sh
/bin/rm -f hack.sh
@@ -207,15 +223,28 @@ EOF
# This is sort of the opposite of privileged - it should ensure that
# 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"
+ fail_capsh --keep=1 --uid=$nouid --inh=cap_setuid --addamb=cap_setuid -- \
+ -c "./privileged --print --uid=1"
+
+ pass_capsh --keep=1 --uid=$nouid --strict \
+ --caps="cap_setuid=p cap_setpcap=ep" \
+ --inh=cap_setuid --addamb=cap_setuid --current
+
+ # No effective capabilities are needed to raise or lower ambient values.
+ pass_capsh --keep=1 --uid=$nouid --strict --caps="cap_setuid=p" \
+ --inh=cap_setuid --addamb=cap_setuid --current
+ pass_capsh --keep=1 --uid=$nouid --strict --iab="!^cap_setuid" \
+ --caps="cap_setuid=pi" --current --delamb=cap_setuid --current
+
# finally remove the capability from the privileged binary and try again.
./setcap -r ./privileged
- pass_capsh --keep=1 --uid=$nouid --inh=cap_setuid --addamb=cap_setuid -- -c "./privileged --print --uid=1"
+ pass_capsh --keep=1 --uid=$nouid --inh=cap_setuid --addamb=cap_setuid -- \
+ -c "./privileged --print --uid=1"
# validate IAB setting with an ambient capability
- pass_capsh --iab='!%cap_chown,^cap_setpcap,cap_sys_admin'
- fail_capsh --mode=PURE1E --iab='!%cap_chown,^cap_sys_admin'
+ pass_capsh --iab='!%cap_chown,^cap_setpcap,cap_setuid'
+ fail_capsh --mode=PURE1E --iab='!%cap_chown,^cap_setuid'
fi
/bin/rm -f ./privileged
@@ -227,7 +256,7 @@ rm -f 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]"
+ ./getcap -n ./nsprivileged | grep -F "[rootid=1]"
if [ $? -ne 0 ]; then
echo "FAILED setting ns rootid on file"
exit 1
@@ -250,9 +279,11 @@ if [ -f ../go/compare-cap ]; then
echo "FAILED to execute go binary"
exit 1
fi
- LD_LIBRARY_PATH=../libcap ./compare-cap 2>&1 | grep "skipping file cap tests"
+ LD_LIBRARY_PATH=../libcap ./compare-cap 2>&1 | \
+ grep "skipping file cap tests"
if [ $? -eq 0 ]; then
echo "FAILED not engaging file cap tests"
+ exit 1
fi
echo "PASSED"
else
diff --git a/progs/setcap.c b/progs/setcap.c
index e28b1c7..737efcc 100644
--- a/progs/setcap.c
+++ b/progs/setcap.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997,2007-8,2020 Andrew G. Morgan <morgan@kernel.org>
+ * Copyright (c) 1997,2007-8,2020,21 Andrew G. Morgan <morgan@kernel.org>
*
* This sets/verifies the capabilities of a given file.
*/
@@ -21,17 +21,47 @@ static void usage(int status)
" -r remove capability from file\n"
" - read capability text from stdin\n"
" <capsN> cap_from_text(3) formatted file capability\n"
+ " [ Note: capsh --suggest=\"something...\" might help you pick. ]"
"\n"
" -h this message and exit status 0\n"
" -q quietly\n"
" -v validate supplied capability matches file\n"
- " -n <rootid> write a user namespace limited capability\n"
+ " -n <rootid> write a user namespace (!= 0) limited capability\n"
" --license display the license info\n"
);
exit(status);
}
-#define MAXCAP 2048
+/* parse a positive integer with some error handling */
+static unsigned long pos_uint(const char *text, const char *prefix, int *ok)
+{
+ char *remains;
+ unsigned long value;
+ ssize_t len = strlen(text);
+
+ if (len == 0 || *text == '-') {
+ goto fail;
+ }
+ value = strtoul(text, &remains, 0);
+ if (*remains || value == 0) {
+ goto fail;
+ }
+ if (ok != NULL) {
+ *ok = 1;
+ }
+ return value;
+
+fail:
+ if (ok == NULL) {
+ fprintf(stderr, "%s: want positive integer, got \"%s\"\n",
+ prefix, text);
+ exit(1);
+ }
+ *ok = 0;
+ return 0;
+}
+
+#define MAXCAP 2048
static int read_caps(int quiet, const char *filename, char *buffer)
{
@@ -84,9 +114,12 @@ int main(int argc, char **argv)
" (old libcap?)\n");
}
+ cap_t cap_d = NULL;
while (--argc > 0) {
const char *text;
- cap_t cap_d;
+
+ cap_free(cap_d);
+ cap_d = NULL;
if (!strcmp(*++argv, "-q")) {
quiet = 1;
@@ -95,7 +128,7 @@ int main(int argc, char **argv)
if (!strcmp("--license", *argv)) {
printf(
"%s see LICENSE file for details.\n"
- "Copyright (c) 1997,2007-8,2020 Andrew G. Morgan"
+ "Copyright (c) 1997,2007-8,2020-21 Andrew G. Morgan"
" <morgan@kernel.org>\n", argv[0]);
exit(0);
}
@@ -108,19 +141,17 @@ int main(int argc, char **argv)
}
if (!strcmp(*argv, "-n")) {
if (argc < 2) {
- fprintf(stderr, "usage: .. -n <rootid> .. - rootid!=0 file caps");
+ fprintf(stderr,
+ "usage: .. -n <rootid> .. - rootid!=0 file caps");
exit(1);
}
--argc;
- rootid = (uid_t) atoi(*++argv);
- if (rootid+1 < 2) {
- fprintf(stderr, "invalid rootid!=0 of '%s'", *argv);
- exit(1);
- }
+ rootid = (uid_t) pos_uint(*++argv, "bad ns rootid", NULL);
continue;
}
if (!strcmp(*argv, "-r")) {
+ cap_free(cap_d);
cap_d = NULL;
} else {
if (!strcmp(*argv,"-")) {
@@ -143,11 +174,9 @@ int main(int argc, char **argv)
}
#ifdef DEBUG
{
- ssize_t length;
- const char *result;
-
- result = cap_to_text(cap_d, &length);
+ char *result = cap_to_text(cap_d, NULL);
fprintf(stderr, "caps set to: [%s]\n", result);
+ cap_free(result);
}
#endif
}
@@ -162,13 +191,20 @@ int main(int argc, char **argv)
int cmp;
if (cap_d == NULL) {
- cap_d = cap_from_text("=");
+ cap_d = cap_init();
+ if (cap_d == NULL) {
+ perror("unable to obtain empty capability");
+ exit(1);
+ }
}
cap_on_file = cap_get_file(*++argv);
-
if (cap_on_file == NULL) {
- cap_on_file = cap_from_text("=");
+ cap_on_file = cap_init();
+ if (cap_on_file == NULL) {
+ perror("unable to use missing capability");
+ exit(1);
+ }
}
cmp = cap_compare(cap_on_file, cap_d);
@@ -238,17 +274,31 @@ int main(int argc, char **argv)
}
#endif /* def linux */
- fprintf(stderr,
- "Failed to set capabilities on file `%s' (%s)\n",
- argv[0], strerror(oerrno));
- if (!explained) {
- usage(1);
+ switch (oerrno) {
+ case EINVAL:
+ fprintf(stderr,
+ "Invalid file '%s' for capability operation\n",
+ argv[0]);
+ exit(1);
+ case ENODATA:
+ if (cap_d == NULL) {
+ fprintf(stderr,
+ "File '%s' has no capablity to remove\n",
+ argv[0]);
+ exit(1);
+ }
+ /* FALLTHROUGH */
+ default:
+ fprintf(stderr,
+ "Failed to set capabilities on file '%s': %s\n",
+ argv[0], strerror(oerrno));
+ exit(1);
}
}
}
- if (cap_d) {
- cap_free(cap_d);
- }
+ }
+ if (cap_d) {
+ cap_free(cap_d);
}
exit(0);
diff --git a/psx/License b/psx/License
index 2645a87..39108c2 100644
--- a/psx/License
+++ b/psx/License
@@ -1,3 +1,5 @@
+/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-only */
+
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.
diff --git a/psx/psx.c b/psx/psx.c
index 90dcc50..65eb2aa 100644
--- a/psx/psx.c
+++ b/psx/psx.c
@@ -29,6 +29,26 @@
#include "psx_syscall.h"
+#ifdef _PSX_DEBUG_MEMORY
+
+static void *_psx_calloc(const char *file, const int line,
+ size_t nmemb, size_t size) {
+ void *ptr = calloc(nmemb, size);
+ fprintf(stderr, "psx:%d:%s:%d: calloc(%ld, %ld) -> %p\n", gettid(),
+ file, line, (long int)nmemb, (long int)size, ptr);
+ return ptr;
+}
+
+static void _psx_free(const char *file, const int line, void *ptr) {
+ fprintf(stderr, "psx:%d:%s:%d: free(%p)\n", gettid(), file, line, ptr);
+ return free(ptr);
+}
+
+#define calloc(a, b) _psx_calloc(__FILE__, __LINE__, a, b)
+#define free(a) _psx_free(__FILE__, __LINE__, a)
+
+#endif /* def _PSX_DEBUG_MEMORY */
+
/*
* psx_load_syscalls() can be weakly defined in dependent libraries to
* provide a mechanism for a library to optionally leverage this psx
@@ -56,6 +76,8 @@ typedef struct registered_thread_s {
pthread_mutex_t mu;
int pending;
int gone;
+ long int retval;
+ pid_t tid;
} registered_thread_t;
static pthread_once_t psx_tracker_initialized = PTHREAD_ONCE_INIT;
@@ -81,6 +103,7 @@ static struct psx_tracker_s {
psx_tracker_state_t state;
int initialized;
int psx_sig;
+ psx_sensitivity_t sensitivity;
struct {
long syscall_nr;
@@ -107,6 +130,10 @@ pthread_key_t psx_action_key;
*/
static void *psx_do_registration(void) {
registered_thread_t *node = calloc(1, sizeof(registered_thread_t));
+ if (node == NULL) {
+ perror("unable to register psx handler");
+ _exit(1);
+ }
pthread_mutex_init(&node->mu, NULL);
node->thread = pthread_self();
pthread_setspecific(psx_action_key, node);
@@ -132,19 +159,20 @@ static void psx_posix_syscall_actor(int signum, siginfo_t *info, void *ignore) {
return;
}
+ long int retval;
if (!psx_tracker.cmd.six) {
- (void) syscall(psx_tracker.cmd.syscall_nr,
- psx_tracker.cmd.arg1,
- psx_tracker.cmd.arg2,
- psx_tracker.cmd.arg3);
+ retval = syscall(psx_tracker.cmd.syscall_nr,
+ psx_tracker.cmd.arg1,
+ psx_tracker.cmd.arg2,
+ psx_tracker.cmd.arg3);
} else {
- (void) syscall(psx_tracker.cmd.syscall_nr,
- psx_tracker.cmd.arg1,
- psx_tracker.cmd.arg2,
- psx_tracker.cmd.arg3,
- psx_tracker.cmd.arg4,
- psx_tracker.cmd.arg5,
- psx_tracker.cmd.arg6);
+ retval = syscall(psx_tracker.cmd.syscall_nr,
+ psx_tracker.cmd.arg1,
+ psx_tracker.cmd.arg2,
+ psx_tracker.cmd.arg3,
+ psx_tracker.cmd.arg4,
+ psx_tracker.cmd.arg5,
+ psx_tracker.cmd.arg6);
}
/*
@@ -156,6 +184,8 @@ static void psx_posix_syscall_actor(int signum, siginfo_t *info, void *ignore) {
if (ref) {
pthread_mutex_lock(&ref->mu);
ref->pending = 0;
+ ref->retval = retval;
+ ref->tid = syscall(SYS_gettid);
pthread_mutex_unlock(&ref->mu);
} /*
* else thread must be dying and its psx_action_key has already
@@ -167,6 +197,7 @@ static void psx_posix_syscall_actor(int signum, siginfo_t *info, void *ignore) {
* Some forward declarations for the initialization
* psx_syscall_start() routine.
*/
+static void _psx_cleanup(void);
static void _psx_prepare_fork(void);
static void _psx_fork_completed(void);
static void _psx_forked_child(void);
@@ -230,6 +261,7 @@ static void psx_syscall_start(void) {
psx_confirm_sigaction();
psx_do_registration(); /* register the main thread. */
+ atexit(_psx_cleanup);
psx_tracker.initialized = 1;
}
@@ -255,7 +287,9 @@ static void psx_unlock(void)
}
/*
- * under lock perform a state transition.
+ * under lock perform a state transition. Changing state is generally
+ * done via this function. However, there is a single exception in
+ * _psx_cleanup().
*/
static void psx_new_state(psx_tracker_state_t was, psx_tracker_state_t is)
{
@@ -319,7 +353,7 @@ static void _psx_forked_child(void) {
*
* We do this because the glibc man page for fork() suggests that
* only a subset of things will work post fork(). Specifically,
- * only a "async-signal-safe functions (see signal- safety(7))
+ * only a "async-signal-safe functions (see signal-safety(7))
* until such time as it calls execve(2)" can be relied upon. That
* man page suggests that you can't expect mutexes to work: "not
* async-signal-safe because it uses pthread_mutex_lock(3)
@@ -410,7 +444,7 @@ static void _psx_exiting(void *node) {
pthread_sigmask(SIG_SETMASK, &orig_sigbits, NULL);
/*
- * Allow the rest of the psx system carry on as per normal.
+ * Allow the rest of the psx system to carry on as per normal.
*/
psx_new_state(_PSX_EXITING, _PSX_IDLE);
}
@@ -482,7 +516,7 @@ int __wrap_pthread_create(pthread_t *thread, const pthread_attr_t *attr,
pthread_sigmask(SIG_BLOCK, &sigbit, NULL);
int ret = __real_pthread_create(thread, attr, _psx_start_fn, starter);
- if (ret == -1) {
+ if (ret > 0) {
psx_new_state(_PSX_CREATE, _PSX_IDLE);
memset(starter, 0, sizeof(*starter));
free(starter);
@@ -603,6 +637,7 @@ long int __psx_syscall(long int syscall_nr, ...) {
}
psx_unlock();
+ int mismatch = 0;
for (;;) {
int waiting = 0;
psx_lock();
@@ -615,8 +650,12 @@ long int __psx_syscall(long int syscall_nr, ...) {
pthread_mutex_lock(&ref->mu);
int pending = ref->pending;
int gone = ref->gone;
- if (pending && !gone) {
- gone = (pthread_kill(ref->thread, 0) != 0);
+ if (!gone) {
+ if (pending) {
+ gone = (pthread_kill(ref->thread, 0) != 0);
+ } else {
+ mismatch |= (ref->retval != ret);
+ }
}
pthread_mutex_unlock(&ref->mu);
if (!gone) {
@@ -635,10 +674,92 @@ long int __psx_syscall(long int syscall_nr, ...) {
sched_yield();
}
- errno = restore_errno;
psx_tracker.cmd.active = 0;
+ if (mismatch) {
+ psx_lock();
+ switch (psx_tracker.sensitivity) {
+ case PSX_IGNORE:
+ break;
+ default:
+ fprintf(stderr, "psx_syscall result differs.\n");
+ if (psx_tracker.cmd.six) {
+ fprintf(stderr, "trap:%ld a123456=[%ld,%ld,%ld,%ld,%ld,%ld]\n",
+ psx_tracker.cmd.syscall_nr,
+ psx_tracker.cmd.arg1,
+ psx_tracker.cmd.arg2,
+ psx_tracker.cmd.arg3,
+ psx_tracker.cmd.arg4,
+ psx_tracker.cmd.arg5,
+ psx_tracker.cmd.arg6);
+ } else {
+ fprintf(stderr, "trap:%ld a123=[%ld,%ld,%ld]\n",
+ psx_tracker.cmd.syscall_nr,
+ psx_tracker.cmd.arg1,
+ psx_tracker.cmd.arg2,
+ psx_tracker.cmd.arg3);
+ }
+ fprintf(stderr, "results:");
+ for (ref = psx_tracker.root; ref; ref = next) {
+ next = ref->next;
+ if (ref->thread == self) {
+ continue;
+ }
+ if (ret != ref->retval) {
+ fprintf(stderr, " %d={%ld}", ref->tid, ref->retval);
+ }
+ }
+ fprintf(stderr, " wanted={%ld}\n", ret);
+ if (psx_tracker.sensitivity == PSX_WARNING) {
+ break;
+ }
+ pthread_kill(self, SIGSYS);
+ }
+ psx_unlock();
+ }
+ errno = restore_errno;
psx_new_state(_PSX_SYSCALL, _PSX_IDLE);
defer:
return ret;
}
+
+/*
+ * _psx_cleanup its called when the program exits. It is used to free
+ * any memory used by the thread tracker.
+ */
+static void _psx_cleanup(void) {
+ registered_thread_t *ref, *next;
+
+ /*
+ * We enter the exiting state. Unlike exiting a single thread we
+ * never leave this state since this cleanup is only done at
+ * program exit.
+ */
+ psx_lock();
+ while (psx_tracker.state != _PSX_IDLE && psx_tracker.state != _PSX_INFORK) {
+ pthread_cond_wait(&psx_tracker.cond, &psx_tracker.state_mu);
+ }
+ psx_tracker.state = _PSX_EXITING;
+ psx_unlock();
+
+ for (ref = psx_tracker.root; ref; ref = next) {
+ next = ref->next;
+ psx_do_unregister(ref);
+ }
+}
+
+/*
+ * Change the PSX sensitivity level. If the threads appear to have
+ * diverged in behavior, this can cause the library to notify the
+ * user.
+ */
+int psx_set_sensitivity(psx_sensitivity_t level) {
+ if (level < PSX_IGNORE || level > PSX_ERROR) {
+ errno = EINVAL;
+ return -1;
+ }
+ psx_lock();
+ psx_tracker.sensitivity = level;
+ psx_unlock();
+ return 0;
+}
diff --git a/psx/psx.go b/psx/psx.go
index 77648e2..130f0cb 100644
--- a/psx/psx.go
+++ b/psx/psx.go
@@ -9,11 +9,27 @@ import "syscall"
// file.
//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) {
return syscall.AllThreadsSyscall(syscallnr, arg1, arg2, arg3)
}
//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) {
return syscall.AllThreadsSyscall6(syscallnr, arg1, arg2, arg3, arg4, arg5, arg6)
}
diff --git a/psx/psx_cgo.go b/psx/psx_cgo.go
index 26aa15a..1f75137 100644
--- a/psx/psx_cgo.go
+++ b/psx/psx_cgo.go
@@ -4,6 +4,7 @@ package psx // import "kernel.org/pub/linux/libs/security/libcap/psx"
import (
"runtime"
+ "sync"
"syscall"
)
@@ -32,6 +33,15 @@ func setErrno(v int) int {
return int(C.__errno_too(C.long(v)))
}
+var makeFatal sync.Once
+
+// forceFatal configures the psx_syscall mechanism to PSX_ERROR.
+func forceFatal() {
+ makeFatal.Do(func() {
+ C.psx_set_sensitivity(C.PSX_ERROR)
+ })
+}
+
//go:uintptrescapes
// Syscall3 performs a 3 argument syscall. Syscall3 differs from
@@ -45,6 +55,7 @@ func setErrno(v int) int {
// If CGO_ENABLED=0 it redirects to the go1.16+
// syscall.AllThreadsSyscall() function.
func Syscall3(syscallnr, arg1, arg2, arg3 uintptr) (uintptr, uintptr, syscall.Errno) {
+ forceFatal()
// We lock to the OSThread here because we may need errno to
// be the one for this thread.
runtime.LockOSThread()
@@ -65,6 +76,7 @@ func Syscall3(syscallnr, arg1, arg2, arg3 uintptr) (uintptr, uintptr, syscall.Er
// 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) {
+ forceFatal()
// We lock to the OSThread here because we may need errno to
// be the one for this thread.
runtime.LockOSThread()
diff --git a/psx/psx_syscall.h b/psx/psx_syscall.h
index 3987d59..7a8c9a1 100644
--- a/psx/psx_syscall.h
+++ b/psx/psx_syscall.h
@@ -67,6 +67,27 @@ void psx_load_syscalls(long int (**syscall_fn)(long int,
long int, long int, long int,
long int, long int, long int));
+/*
+ * psx_sensitivity_t holds the level of paranoia for non-POSIX syscall
+ * behavior. The default is PSX_IGNORE: which is best effort - no
+ * enforcement; PSX_WARNING will dump to stderr a warning when a
+ * syscall's results differ; PSX_ERROR will dump info as per
+ * PSX_WARNING and generate a SIGSYS. The current mode can be set with
+ * psx_set_sensitivity().
+ */
+typedef enum {
+ PSX_IGNORE = 0,
+ PSX_WARNING = 1,
+ PSX_ERROR = 2,
+} psx_sensitivity_t;
+
+/*
+ * psx_set_sensitivity sets the current sensitivity of the PSX
+ * mechanism. The function returns 0 on success and -1 if the
+ * requested level is invalid.
+ */
+int psx_set_sensitivity(psx_sensitivity_t level);
+
#ifdef __cplusplus
}
#endif
diff --git a/psx/psx_test.go b/psx/psx_test.go
index 4b90f63..40a543f 100644
--- a/psx/psx_test.go
+++ b/psx/psx_test.go
@@ -2,6 +2,7 @@ package psx
import (
"runtime"
+ "sync"
"syscall"
"testing"
)
@@ -41,9 +42,65 @@ func killAThread(c <-chan struct{}) {
<-c
}
+// Test state is mirrored as expected.
+func TestShared(t *testing.T) {
+ const prGetKeepCaps = 7
+ const prSetKeepCaps = 8
+
+ var wg sync.WaitGroup
+
+ newTracker := func() chan<- uintptr {
+ ch := make(chan uintptr)
+ go func() {
+ runtime.LockOSThread()
+ defer wg.Done()
+ tid := syscall.Gettid()
+ for {
+ if _, ok := <-ch; !ok {
+ break
+ }
+ val, ok := <-ch
+ if !ok {
+ break
+ }
+ got, _, e := Syscall3(syscall.SYS_PRCTL, prGetKeepCaps, 0, 0)
+ if e != 0 {
+ t.Fatalf("[%d] psx:prctl(GET_KEEPCAPS) ?= %d failed: %v", tid, val, syscall.Errno(e))
+ }
+ if got != val {
+ t.Errorf("[%d] bad keepcaps value: got=%d, want=%d", tid, got, val)
+ }
+ if _, ok := <-ch; !ok {
+ break
+ }
+ }
+ }()
+ return ch
+ }
+
+ var tracked []chan<- uintptr
+ for i := 0; i <= 10; i++ {
+ val := uintptr(i & 1)
+ if _, _, e := Syscall3(syscall.SYS_PRCTL, prSetKeepCaps, val, 0); e != 0 {
+ t.Fatalf("[%d] psx:prctl(SET_KEEPCAPS, %d) failed: %v", i, i&1, syscall.Errno(e))
+ }
+ wg.Add(1)
+ tracked = append(tracked, newTracker())
+ for _, ch := range tracked {
+ ch <- 2 // start serialization.
+ ch <- val // definitely written after change.
+ ch <- 3 // end serialization.
+ }
+ }
+ for _, ch := range tracked {
+ close(ch)
+ }
+ wg.Wait()
+}
+
// Test to confirm no regression against:
//
-// https://github.com/golang/go/issues/42494
+// https://github.com/golang/go/issues/42494
func TestThreadChurn(t *testing.T) {
const prSetKeepCaps = 8
diff --git a/tests/Makefile b/tests/Makefile
index 7ce8776..ecb7d1b 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -8,12 +8,10 @@ include ../Make.Rules
#
all:
- $(MAKE) libcap_launch_test uns_test
-ifeq ($(PTHREADS),yes)
- $(MAKE) psx_test libcap_psx_test libcap_psx_launch_test
-endif
+ @echo leave test building to test target
-install: all
+install:
+ @echo nothing to install from tests
ifeq ($(DYNAMIC),yes)
LINKEXTRA=-Wl,-rpath,../libcap
@@ -22,7 +20,10 @@ ifeq ($(PTHREADS),yes)
DEPS += ../libcap/libpsx.so
endif
else
-LDSTATIC = --static
+# For this build variant override the LDFLAGS to link statically from
+# libraries within the build tree. If you never want this, use
+# make DYNAMIC=yes ...
+LDFLAGS = --static
DEPS=../libcap/libcap.a
ifeq ($(PTHREADS),yes)
DEPS += ../libcap/libpsx.a
@@ -63,59 +64,56 @@ run_psx_test: psx_test
./psx_test
psx_test: psx_test.c $(DEPS)
- $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LDSTATIC)
+ $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB)
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) $(LDSTATIC)
+ $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB)
# privileged
uns_test: uns_test.c $(DEPS)
- $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LDSTATIC)
+ $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB)
run_uns_test: uns_test
- echo exit | sudo ./uns_test
+ echo exit | $(SUDO) ./uns_test
run_libcap_launch_test: libcap_launch_test noop ../progs/tcapsh-static
- sudo ./libcap_launch_test
+ $(SUDO) ./libcap_launch_test
run_libcap_psx_launch_test: libcap_psx_launch_test ../progs/tcapsh-static
- sudo ./libcap_psx_launch_test
+ $(SUDO) ./libcap_psx_launch_test
libcap_launch_test: libcap_launch_test.c $(DEPS)
- $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LDSTATIC)
+ $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB)
# 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) $(LDSTATIC)
+ $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -DWITH_PTHREADS $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB)
# 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
+ $(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 $<
+ $(SUDO) ./noexploit ; if [ $$? -eq 0 ]; then exit 0; else exit 1 ; fi
exploit: exploit.o $(DEPS)
- $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) -lpthread $(LDSTATIC)
+ $(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) -lpthread
# 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) $(LDSTATIC)
+ $(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LIBCAPLIB)
# This one runs in a chroot with no shared library files.
noop: noop.c
- $(CC) $(CFLAGS) $< -o $@ --static
+ $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ --static
clean:
rm -f psx_test libcap_psx_test libcap_launch_test uns_test *~
diff --git a/tests/libcap_launch_test.c b/tests/libcap_launch_test.c
index f45b2b7..b982573 100644
--- a/tests/libcap_launch_test.c
+++ b/tests/libcap_launch_test.c
@@ -1,3 +1,4 @@
+#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -40,7 +41,9 @@ struct test_case_s {
static int clean_out(void *data) {
cap_t empty;
empty = cap_init();
- cap_set_proc(empty);
+ if (cap_set_proc(empty) != 0) {
+ _exit(1);
+ }
cap_free(empty);
return 0;
}
@@ -117,16 +120,33 @@ int main(int argc, char **argv) {
},
};
+ if (errno != 0) {
+ perror("unexpected initial value for errno");
+ exit(1);
+ }
+
cap_t orig = cap_get_proc();
+ if (orig == NULL) {
+ perror("failed to get process capabilities");
+ exit(1);
+ }
int success = 1, i;
for (i=0; vs[i].pass_on != NO_MORE; i++) {
+ cap_launch_t attr = NULL;
const struct test_case_s *v = &vs[i];
+ if (cap_launch(attr, NULL) != -1) {
+ perror("NULL launch didn't fail");
+ exit(1);
+ }
printf("[%d] test should %s\n", i,
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 (attr == NULL) {
+ perror("failed to obtain launcher");
+ exit(1);
+ }
if (v->callback_fn != NULL) {
cap_launcher_callback(attr, v->callback_fn);
}
@@ -195,6 +215,10 @@ int main(int argc, char **argv) {
}
cap_t final = cap_get_proc();
+ if (final == NULL) {
+ perror("unable to get final capabilities");
+ exit(1);
+ }
if (cap_compare(orig, final)) {
char *was = cap_to_text(orig, NULL);
char *is = cap_to_text(final, NULL);
diff --git a/tests/libcap_psx_test.c b/tests/libcap_psx_test.c
index 9f53f06..9ef8cac 100644
--- a/tests/libcap_psx_test.c
+++ b/tests/libcap_psx_test.c
@@ -16,8 +16,15 @@ static void *thread_fork_exit(void *data) {
usleep(1234);
pid_t pid = fork();
cap_t start = cap_get_proc();
+ if (start == NULL) {
+ perror("FAILED: unable to start");
+ exit(1);
+ }
if (pid == 0) {
- cap_set_proc(start);
+ if (cap_set_proc(start)) {
+ perror("setting empty caps failed");
+ exit(1);
+ }
exit(0);
}
int res;
@@ -27,6 +34,7 @@ static void *thread_fork_exit(void *data) {
exit(1);
}
cap_set_proc(start);
+ cap_free(start);
return NULL;
}
@@ -35,6 +43,10 @@ int main(int argc, char **argv) {
printf("hello libcap and libpsx ");
fflush(stdout);
cap_t start = cap_get_proc();
+ if (start == NULL) {
+ perror("FAILED: to actually start");
+ exit(1);
+ }
pthread_t ignored[10];
for (i = 0; i < 10; i++) {
pthread_create(&ignored[i], NULL, thread_fork_exit, NULL);
@@ -42,7 +54,10 @@ int main(int argc, char **argv) {
for (i = 0; i < 10; i++) {
printf("."); /* because of fork, this may print double */
fflush(stdout); /* try to limit the above effect */
- cap_set_proc(start);
+ if (cap_set_proc(start)) {
+ perror("failed to set proc");
+ exit(1);
+ }
usleep(1000);
}
printf(" PASSED\n");
diff --git a/tests/uns_test.c b/tests/uns_test.c
index d8f5415..3fe73af 100644
--- a/tests/uns_test.c
+++ b/tests/uns_test.c
@@ -62,6 +62,17 @@ int main(int argc, char **argv)
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();
+ cap_flag_value_t present;
+
+ if (cap_get_flag(orig, CAP_SYS_ADMIN, CAP_EFFECTIVE, &present) != 0) {
+ perror("failed to read a capability flag");
+ exit(1);
+ }
+ if (present != CAP_SET) {
+ fprintf(stderr,
+ "environment missing cap_sys_admin - exploit not testable\n");
+ exit(0);
+ }
/* Run with this one lowered */
cap_set_flag(orig, CAP_EFFECTIVE, 1, &fscap, CAP_CLEAR);
@@ -153,6 +164,8 @@ int main(int argc, char **argv)
bailok:
fprintf(stderr, "exploit attempt failed\n");
- (void) write(fds.to[1], "!", 1);
+ if (write(fds.to[1], "!", 1) != 1) {
+ perror("failed to inform child [ignored]");
+ }
exit(0);
}