From 25b8964e178b4c134a94b3a1b92f73111736c358 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Thu, 19 Aug 2021 20:00:23 -0700 Subject: Make lib*.so files executable. Since 2.52 these libraries have supported being run as binaries so install them as such. Signed-off-by: Andrew G. Morgan --- libcap/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libcap/Makefile b/libcap/Makefile index b5689d2..2cfbd98 100644 --- a/libcap/Makefile +++ b/libcap/Makefile @@ -177,7 +177,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 +188,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),) -- cgit v1.2.3 From d21a561ddb80cdc039f32eb5225f5d398d221291 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Thu, 19 Aug 2021 20:49:39 -0700 Subject: Drop non-standard IPATH in favor of CPPFLAGS. This also required locally augmenting CFLAGS with -fPIC in the Makefile's that required it. Signed-off-by: Andrew G. Morgan --- Make.Rules | 6 ++++-- libcap/Makefile | 15 +++++++++------ pam_cap/Makefile | 11 +++++++---- progs/Makefile | 6 +++--- tests/Makefile | 16 ++++++++-------- 5 files changed, 31 insertions(+), 23 deletions(-) diff --git a/Make.Rules b/Make.Rules index 125f2aa..d669275 100644 --- a/Make.Rules +++ b/Make.Rules @@ -52,15 +52,17 @@ 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 CC := $(CROSS_COMPILE)gcc DEFINES := -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 COPTS ?= -O2 CFLAGS ?= $(COPTS) $(DEFINES) +CPPFLAGS += $(LIBCAP_INCLUDES) BUILD_CC ?= $(CC) BUILD_COPTS ?= -O2 -BUILD_CFLAGS ?= $(BUILD_COPTS) $(DEFINES) $(IPATH) +BUILD_CFLAGS ?= $(BUILD_COPTS) $(DEFINES) +BUILD_CPPFLAGS += $(LIBCAP_INCLUDES) AR := $(CROSS_COMPILE)ar RANLIB := $(CROSS_COMPILE)ranlib OBJCOPY := $(CROSS_COMPILE)objcopy diff --git a/libcap/Makefile b/libcap/Makefile index 2cfbd98..56fb0af 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 @@ -73,7 +76,7 @@ $(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 @@ -108,7 +111,7 @@ loader.txt: empty $(OBJCOPY) --dump-section .interp=$@ $< cap_magic.o: execable.h execable.c loader.txt - $(CC) $(CFLAGS) $(IPATH) -DLIBRARY_VERSION=\"$(LIBTITLE)-$(VERSION).$(MINOR)\" -DSHARED_LOADER=\"$(shell cat loader.txt)\" -c execable.c -o $@ + $(CC) $(CFLAGS) $(CPPFLAGS) -DLIBRARY_VERSION=\"$(LIBTITLE)-$(VERSION).$(MINOR)\" -DSHARED_LOADER=\"$(shell cat loader.txt)\" -c execable.c -o $@ $(CAPLIBNAME) $(MAJCAPLIBNAME) $(MINCAPLIBNAME): $(CAPOBJS) $(CAPMAGICOBJ) $(LD) $(CFLAGS) $(LDFLAGS) -Wl,-soname,$(MAJCAPLIBNAME) -o $(MINCAPLIBNAME) $^ $(MAGIC) @@ -116,7 +119,7 @@ $(CAPLIBNAME) $(MAJCAPLIBNAME) $(MINCAPLIBNAME): $(CAPOBJS) $(CAPMAGICOBJ) 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 $@ + $(CC) $(CFLAGS) $(CPPFLAGS) -DLIBRARY_VERSION=\"$(PSXTITLE)-$(VERSION).$(MINOR)\" -DSHARED_LOADER=\"$(shell cat loader.txt)\" -c execable.c -o $@ $(PSXLIBNAME) $(MAJPSXLIBNAME) $(MINPSXLIBNAME): $(PSXOBJS) include/sys/psx_syscall.h $(PSXMAGICOBJ) $(LD) $(CFLAGS) $(LDFLAGS) -Wl,-soname,$(MAJPSXLIBNAME) -o $(MINPSXLIBNAME) $(PSXOBJS) $(PSXMAGICOBJ) $(MAGIC) $(PSXLINKFLAGS) @@ -125,13 +128,13 @@ $(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 $@ + $(CC) $(CFLAGS) $(CPPFLAGS) $< $(CAPOBJS) -o $@ libcapsotest: $(CAPLIBNAME) ./$(CAPLIBNAME) diff --git a/pam_cap/Makefile b/pam_cap/Makefile index 689239e..758d51b 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,7 +17,7 @@ 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 $@ 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) @@ -36,16 +39,16 @@ pam_cap_linkopts: lazylink.so ./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 + $(LD) -o $@ $(CFLAGS) $(CPPFLAGS) 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 $@ + $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@ ../libcap/libcap.a: $(MAKE) -C ../libcap libcap.a test_pam_cap: test_pam_cap.c pam_cap.c ../libcap/libcap.a - $(CC) $(CFLAGS) $(IPATH) -o $@ test_pam_cap.c $(LIBCAPLIB) $(LDFLAGS) --static + $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ test_pam_cap.c $(LIBCAPLIB) $(LDFLAGS) --static testlink: test.c pam_cap.o $(CC) $(CFLAGS) -o $@ $+ -lpam -ldl $(LIBCAPLIB) $(LDFLAGS) diff --git a/progs/Makefile b/progs/Makefile index 2c3c993..172ad37 100644 --- a/progs/Makefile +++ b/progs/Makefile @@ -28,7 +28,7 @@ $(BUILD): %: %.o $(DEPS) $(CC) $(CFLAGS) -o $@ $< $(LIBCAPLIB) $(LDSTATIC) %.o: %.c $(INCS) - $(CC) $(IPATH) $(CFLAGS) -c $< -o $@ + $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@ install: all mkdir -p -m 0755 $(FAKEROOT)$(SBINDIR) @@ -46,10 +46,10 @@ capshdoc.h.cf: capshdoc.h ./mkcapshdoc.sh diff -u capshdoc.h $@ || (rm $@ ; exit 1) capsh: capsh.c capshdoc.h.cf $(DEPS) - $(CC) $(IPATH) $(CAPSH_SHELL) $(CFLAGS) -o $@ $< $(LIBCAPLIB) $(LDSTATIC) + $(CC) $(CFLAGS) $(CPPFLAGS) $(CAPSH_SHELL) -o $@ $< $(LIBCAPLIB) $(LDSTATIC) tcapsh-static: capsh.c capshdoc.h.cf $(DEPS) - $(CC) $(IPATH) $(CAPSH_SHELL) $(CFLAGS) -o $@ $< $(LIBCAPLIB) --static + $(CC) $(CFLAGS) $(CPPFLAGS) $(CAPSH_SHELL) -o $@ $< $(LIBCAPLIB) --static uns_test: ../tests/uns_test.c $(MAKE) -C ../tests uns_test diff --git a/tests/Makefile b/tests/Makefile index 7ce8776..4e1a14d 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -63,17 +63,17 @@ run_psx_test: psx_test ./psx_test psx_test: psx_test.c $(DEPS) - $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LDSTATIC) + $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LDSTATIC) run_libcap_psx_test: libcap_psx_test ./libcap_psx_test libcap_psx_test: libcap_psx_test.c $(DEPS) - $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) $(LDSTATIC) + $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) $(LDSTATIC) # privileged uns_test: uns_test.c $(DEPS) - $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LDSTATIC) + $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LDSTATIC) run_uns_test: uns_test echo exit | sudo ./uns_test @@ -85,13 +85,13 @@ run_libcap_psx_launch_test: libcap_psx_launch_test ../progs/tcapsh-static sudo ./libcap_psx_launch_test libcap_launch_test: libcap_launch_test.c $(DEPS) - $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LDSTATIC) + $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LDSTATIC) # This varies only slightly from the above insofar as it currently # only links in the pthreads fork support. TODO() we need to change # the source to do something interesting with pthreads. libcap_psx_launch_test: libcap_launch_test.c $(DEPS) - $(CC) $(CFLAGS) $(IPATH) -DWITH_PTHREADS $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) $(LDSTATIC) + $(CC) $(CFLAGS) $(CPPFLAGS) -DWITH_PTHREADS $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) $(LDSTATIC) # This test demonstrates that libpsx is needed to secure multithreaded @@ -103,15 +103,15 @@ run_exploit_test: exploit noexploit sudo ./noexploit ; if [ $$? -eq 0 ]; then exit 0; else exit 1 ; fi exploit.o: exploit.c - $(CC) $(CFLAGS) $(IPATH) -c $< + $(CC) $(CFLAGS) $(CPPFLAGS) -c $< exploit: exploit.o $(DEPS) - $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) -lpthread $(LDSTATIC) + $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) -lpthread $(LDSTATIC) # Note, for some reason, the order of libraries is important to avoid # the exploit working for dynamic linking. noexploit: exploit.o $(DEPS) - $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LIBCAPLIB) $(LDSTATIC) + $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LIBCAPLIB) $(LDSTATIC) # This one runs in a chroot with no shared library files. noop: noop.c -- cgit v1.2.3 From 34186d026bad09e8e2bd9839bce138616c5d2557 Mon Sep 17 00:00:00 2001 From: Samanta Navarro Date: Sat, 21 Aug 2021 12:11:00 +0000 Subject: Fix endless loop with very long strings If a string with more than UINT_MAX characters is passed into cap_from_text, then an endless loop occurs in lookupname. This is clearly an edge case but the fix is very simple as well: Use size_t instead of unsigned. Signed-off-by: Samanta Navarro Signed-off-by: Andrew G. Morgan --- libcap/cap_text.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcap/cap_text.c b/libcap/cap_text.c index 87e0838..ff3699d 100644 --- a/libcap/cap_text.c +++ b/libcap/cap_text.c @@ -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 == '_'))) { -- cgit v1.2.3 From 86c85c07c83f7ddc722b872ea0ff9e9b0f70bbc8 Mon Sep 17 00:00:00 2001 From: Samanta Navarro Date: Sat, 21 Aug 2021 12:11:45 +0000 Subject: Check return values of allocating functions The calloc and asprintf functions can return NULL if not enough memory is available. The majority of the code base checks for this condition already. Signed-off-by: Samanta Navarro Signed-off-by: Andrew G. Morgan --- libcap/cap_alloc.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/libcap/cap_alloc.c b/libcap/cap_alloc.c index 88ba6da..91813db 100644 --- a/libcap/cap_alloc.c +++ b/libcap/cap_alloc.c @@ -123,6 +123,10 @@ cap_t cap_dup(cap_t cap_d) cap_iab_t cap_iab_init(void) { __u32 *base = calloc(1, sizeof(__u32) + sizeof(struct cap_iab_s)); + if (base == NULL) { + _cap_debug("out of memory"); + return NULL; + } *(base++) = CAP_IAB_MAGIC; return (cap_iab_t) base; } @@ -138,6 +142,10 @@ 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)); + if (data == NULL) { + _cap_debug("out of memory"); + return NULL; + } *(data++) = CAP_LAUNCH_MAGIC; struct cap_launch_s *attr = (struct cap_launch_s *) data; attr->arg0 = arg0; @@ -156,6 +164,10 @@ 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)); + if (data == NULL) { + _cap_debug("out of memory"); + return NULL; + } *(data++) = CAP_LAUNCH_MAGIC; struct cap_launch_s *attr = (struct cap_launch_s *) data; attr->custom_setup_fn = callback_fn; -- cgit v1.2.3 From f81144578ff24a70356faafb82e55de8f3e1292f Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 21 Aug 2021 10:29:16 -0700 Subject: Handle libcap allocation failures more explicitly and fix a memory leak. This started out as a refactoring of a patch provided by Samanta Navarro. Reworked, I noticed a latent memory leak in cap_iab_get_proc(), so I've fixed that too. Also, migrated a compile failure check to a more useful cap_test for a highly unlikely corner case (future proofing). While there, noticed and fixed the binary search test and code (not sure what it was testing before). Signed-off-by: Andrew G. Morgan --- libcap/cap_proc.c | 20 ++++++++++++++++++-- libcap/cap_test.c | 37 ++++++++++++++++++++++++++++++------- libcap/cap_text.c | 29 +++++++++++++++++------------ libcap/libcap.h | 2 +- 4 files changed, 66 insertions(+), 22 deletions(-) diff --git a/libcap/cap_proc.c b/libcap/cap_proc.c index e12c8e6..5e3f9f9 100644 --- a/libcap/cap_proc.c +++ b/libcap/cap_proc.c @@ -684,9 +684,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; diff --git a/libcap/cap_test.c b/libcap/cap_test.c index a717217..e8eb647 100644 --- a/libcap/cap_test.c +++ b/libcap/cap_test.c @@ -1,3 +1,6 @@ +#define _GNU_SOURCE +#include + #include "libcap.h" static cap_value_t top; @@ -15,15 +18,18 @@ 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; @@ -68,11 +74,28 @@ static int test_cap_flags(void) { return 0; } +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", + strlen(tmp), __CAP_NAME_SIZE); + result = -1; + } + free(tmp); + return result; +} + int main(int argc, char **argv) { int result = 0; result = test_cap_bits() | result; result = test_cap_flags() | result; + result = test_short_bits() | result; if (result) { printf("cap_test FAILED\n"); diff --git a/libcap/cap_text.c b/libcap/cap_text.c index ff3699d..25c5ef5 100644 --- a/libcap/cap_text.c +++ b/libcap/cap_text.c @@ -219,7 +219,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 +309,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 +347,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) diff --git a/libcap/libcap.h b/libcap/libcap.h index 67fa0d0..97a47ae 100644 --- a/libcap/libcap.h +++ b/libcap/libcap.h @@ -227,7 +227,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) /* -- cgit v1.2.3 From 25cdfaf7f813a94612279712a2d6d1e1bb39e08b Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 22 Aug 2021 14:25:11 -0700 Subject: Fix error code handling for failed cap_reset_ambient() Looks like the system call wrapper wasn't migrated properly when I added support to get fakeroot ( https://bugzilla.kernel.org/show_bug.cgi?id=206539 ) working again. That is, all builds in the inclusive range libcap-[2.28, 2.53] have this issue. Signed-off-by: Andrew G. Morgan --- libcap/cap_proc.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/libcap/cap_proc.c b/libcap/cap_proc.c index 5e3f9f9..fdb8cbe 100644 --- a/libcap/cap_proc.c +++ b/libcap/cap_proc.c @@ -355,14 +355,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)); } /* -- cgit v1.2.3 From ac297b51c6730c22a06b759bd4235b47c52053bc Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 22 Aug 2021 14:32:12 -0700 Subject: Revamp the comparison API of *Set and *IAB tuples; add IABGetPID(). Older APIs remain but are documented as deprecated. If we ever need to release a golang version "2" version of the library, I'll drop support for deprecated functions, but I have no intention of needing to do that. In the mean time, the deprecated functions are wrappers around the new functions. New API: *Set and *IAB have .Cf() functions now. That return a [IAB]Diff value. This value, if 0, means the compared pointers match one another. Non-zero values can be interogated with the ([IAB]Diff).Has() functions. Also, add an IABGetPID() function. Since the kernel provides no syscall support for this, we have to resort to parsing the /proc/ files. Implemented mostly for parity with the syscall backed GetPID() *Set returning API. Signed-off-by: Andrew G. Morgan --- cap/cap.go | 9 ++++ cap/cap_test.go | 2 +- cap/convenience.go | 6 +-- cap/flags.go | 29 +++++++++++-- cap/iab.go | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 157 insertions(+), 13 deletions(-) diff --git a/cap/cap.go b/cap/cap.go index 908e2bb..d4b241e 100644 --- a/cap/cap.go +++ b/cap/cap.go @@ -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 { diff --git a/cap/cap_test.go b/cap/cap_test.go index d7c7970..54f1e3f 100644 --- a/cap/cap_test.go +++ b/cap/cap_test.go @@ -199,7 +199,7 @@ func TestIAB(t *testing.T) { if err != nil { t.Fatalf("failed to get init's capabilities: %v", err) } - iab := IABInit() + iab := NewIAB() iab.Fill(Amb, one, Permitted) for i := 0; i < words; i++ { if iab.i[i] != iab.a[i] { diff --git a/cap/convenience.go b/cap/convenience.go index d604ad1..c40b63d 100644 --- a/cap/convenience.go +++ b/cap/convenience.go @@ -101,12 +101,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 } diff --git a/cap/flags.go b/cap/flags.go index 83a871a..d46634d 100644 --- a/cap/flags.go +++ b/cap/flags.go @@ -153,20 +153,31 @@ func (c *Set) ClearFlag(vec Flag) error { // 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. +// +// This function is deprecated in favor of (*Set).Cf(). func (c *Set) Compare(d *Set) (uint, error) { + u, err := c.Cf(d) + return uint(u), err +} + +// 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 c == nil || len(c.flat) == 0 || d == nil || len(d.flat) == 0 { return 0, ErrBadSet } - var cf uint + 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 @@ -174,6 +185,16 @@ func (c *Set) Compare(d *Set) (uint, error) { // Differs processes the result of Compare and determines if the // Flag's components were different. +// +// Use of this function is deprecated in favor of the (Diff).Has() +// function, where Diff is returned as a result of the (*Set).Cf() +// function. func Differs(cf uint, vec Flag) bool { return cf&(1< 0; { @@ -78,7 +106,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,6 +147,9 @@ func IABFromText(text string) (*IAB, error) { // String serializes an IAB to a string format. func (iab *IAB) String() string { + if iab == nil { + return "" + } var vs []string for c := Value(0); c < Value(maxValues); c++ { offset, mask := omask(c) @@ -285,3 +316,86 @@ 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 iab == alt { + return 0, nil + } + if iab == nil || alt == nil || len(iab.i) != words || len(alt.i) != words || len(iab.a) != words || len(alt.a) != words || len(iab.nb) != words || len(alt.nb) != words { + return 0, ErrBadValue + } + + var cf IABDiff + for i := 0; i < words; i++ { + if iab.i[i] != alt.i[i] { + cf |= iBits + } + if iab.a[i] != alt.a[i] { + cf |= aBits + } + if iab.nb[i] != alt.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 +} + +// 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//status file content. +func IABGetPID(pid int) (*IAB, error) { + tf := fmt.Sprintf("/proc/%d/status", 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 +} -- cgit v1.2.3 From 596850bf55899c0217aa53fcff99491fbecdc2b2 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 22 Aug 2021 15:47:16 -0700 Subject: Add the captree example. This is a small command line utility for doing something like pstree but focused on revealing the full capability state of the processes and threads shown. This requires support provided in the cap.IABGetPID() function which will debut in libcap-2.54. For now, the binary is only buildable from HEAD in the git repository. Signed-off-by: Andrew G. Morgan --- go/.gitignore | 1 + go/Makefile | 8 +- goapps/captree/captree.go | 319 ++++++++++++++++++++++++++++++++++++++++++++++ goapps/captree/go.mod | 5 + 4 files changed, 331 insertions(+), 2 deletions(-) create mode 100644 goapps/captree/captree.go create mode 100644 goapps/captree/go.mod diff --git a/go/.gitignore b/go/.gitignore index 7b811c9..ac6056f 100644 --- a/go/.gitignore +++ b/go/.gitignore @@ -9,6 +9,7 @@ mknames web setid gowns +captree ok vendor go.sum diff --git a/go/Makefile b/go/Makefile index 6b69cbe..52e1f3e 100644 --- a/go/Makefile +++ b/go/Makefile @@ -15,7 +15,7 @@ PKGDIR=pkg/$(GOOSARCH)/$(IMPORTDIR) DEPS=../libcap/libcap.a ../libcap/libpsx.a -all: PSXGOPACKAGE CAPGOPACKAGE web setid gowns compare-cap try-launching psx-signals +all: PSXGOPACKAGE CAPGOPACKAGE web setid gowns captree compare-cap try-launching psx-signals $(DEPS): make -C ../libcap all @@ -70,6 +70,9 @@ setid: ../goapps/setid/setid.go CAPGOPACKAGE PSXGOPACKAGE gowns: ../goapps/gowns/gowns.go CAPGOPACKAGE CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) build -mod=vendor -o $@ $< +captree: ../goapps/captree/captree.go CAPGOPACKAGE + CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) build -mod=vendor -o $@ $< + ok: ok.go CC="$(CC)" CGO_ENABLED=0 $(GO) build -mod=vendor $< @@ -101,6 +104,7 @@ ifeq ($(CGO_REQUIRED),0) 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 @@ -127,7 +131,7 @@ install: all clean: rm -f *.o *.so *~ mknames ok good-names.go - rm -f web setid gowns + rm -f web setid gowns captree rm -f compare-cap try-launching try-launching-cgo rm -f $(topdir)/cap/*~ $(topdir)/psx/*~ rm -f b210613 psx-signals psx-signals-cgo diff --git a/goapps/captree/captree.go b/goapps/captree/captree.go new file mode 100644 index 0000000..527150c --- /dev/null +++ b/goapps/captree/captree.go @@ -0,0 +1,319 @@ +// 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 +// +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "log" + "sort" + "strconv" + "strings" + "sync" + + "kernel.org/pub/linux/libs/security/libcap/cap" +) + +var ( + proc = flag.String("proc", "/proc", "root of proc filesystem") +) + +type task struct { + mu sync.Mutex + 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 +) + +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 = "" + 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, pid, stub, lstub, estub string) { + info, ok := pids[pid] + if !ok { + fmt.Println("[PID:", pid, "not found]") + return + } + c := "" + set := info.cap + if set != nil { + if val, _ := set.Cf(empty); val != 0 { + 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) { + 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, ",")) + } + fmt.Printf("%s%s%s(%s%s)%s%s\n", stub, lstub, info.cmd, pid, tids, c, iab) + // loop over any threads that differ in capability state. + for len(misc) != 0 { + this := misc[0] + var nmisc []*task + same := []string{this.pid} + 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 + } + same = append(same, t.pid) + } + c := "" + set := this.cap + if set != nil { + if val, _ := set.Cf(empty); val != 0 { + 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) { + 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 + } + 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, cid, stub, lstub, estub) + } +} + +func main() { + flag.Parse() + + pids := make(map[string]*task) + pids["0"] = &task{ + cmd: "", + } + + 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 { + list = append(list, pid) + if pid == "0" { + continue + } + if pts, ok := pids[ts.parent]; ok { + pts.children = append(pts.children, pid) + } + } + sort.Slice(list, func(i, j int) bool { + a, _ := strconv.Atoi(list[i]) + b, _ := strconv.Atoi(list[j]) + return a < b + }) + + args := flag.Args() + if len(args) == 0 { + args = []string{"1"} + } + + for _, pid := range args { + rDump(pids, pid, "", "--", " ") + } +} diff --git a/goapps/captree/go.mod b/goapps/captree/go.mod new file mode 100644 index 0000000..ff1ad6c --- /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.53 -- cgit v1.2.3 From 4f45bcc83545efdb4ffc5b9c05e1dbabe196339d Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 22 Aug 2021 20:58:04 -0700 Subject: Add cap_iab_{compare,get_pid} functions to libcap; --iab to getpcaps. This brings libcap back to parity with the Go 'cap' package. We provide a CAP_IAB_DIFFERS(result, vector) macro to evaluate the result of cap_iab_compare(). Extend the getpcaps arguments to include --iab. This causes the utility to explore the IAB tuple for the specified process. When used, this outputs a text representation in a similar format to that of the 'captree' (Go) utility. Signed-off-by: Andrew G. Morgan --- doc/Makefile | 3 +- doc/cap_iab.3 | 18 +++++++ doc/cap_iab_compare.3 | 1 + doc/cap_iab_get_pid.3 | 1 + doc/getpcaps.8 | 7 +++ libcap/cap_file.c | 2 +- libcap/cap_flag.c | 23 +++++++-- libcap/cap_text.c | 103 +++++++++++++++++++++++++++++++++++++++- libcap/include/sys/capability.h | 3 ++ progs/getpcaps.c | 22 ++++++++- 10 files changed, 175 insertions(+), 8 deletions(-) create mode 100644 doc/cap_iab_compare.3 create mode 100644 doc/cap_iab_get_pid.3 diff --git a/doc/Makefile b/doc/Makefile index a34cee0..943dbfa 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -21,7 +21,8 @@ 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_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 diff --git a/doc/cap_iab.3 b/doc/cap_iab.3 index a453428..7e87a0f 100644 --- a/doc/cap_iab.3 +++ b/doc/cap_iab.3 @@ -7,6 +7,8 @@ cap_iab_t cap_iab_init(void); 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); @@ -16,6 +18,8 @@ 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); @@ -75,6 +79,11 @@ 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). +.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 @@ -107,6 +116,15 @@ 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 +.B CAP_IAB_DIFFERS +.RI ( status ", " vector ) +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. .BR cap_iab_fill () 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_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/getpcaps.8 b/doc/getpcaps.8 index d519357..3926a8c 100644 --- a/doc/getpcaps.8 +++ b/doc/getpcaps.8 @@ -33,11 +33,18 @@ 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 unique. Double quotes encase the regular process capabilities +and square brackets encase the IAB tuple. .SH SEE ALSO .BR capsh (1), .BR capabilities (7), .BR getcap (8), .BR setcap (8) +and +.BR cap_iab (3). .SH AUTHOR This manual page was originally written by Robert Bihlmeyer , for the Debian GNU/Linux system (but may be used diff --git a/libcap/cap_file.c b/libcap/cap_file.c index 84ae3e1..95c0572 100644 --- a/libcap/cap_file.c +++ b/libcap/cap_file.c @@ -1,7 +1,7 @@ /* * Copyright (c) 1997,2007,2016 Andrew G Morgan * - * This file deals with setting capabilities on files. + * This file deals with getting/setting capabilities from/on files. */ #ifndef _DEFAULT_SOURCE diff --git a/libcap/cap_flag.c b/libcap/cap_flag.c index 51799b0..1f561f7 100644 --- a/libcap/cap_flag.c +++ b/libcap/cap_flag.c @@ -65,13 +65,10 @@ int cap_set_flag(cap_t cap_d, cap_flag_t set, } } return 0; - } else { - _cap_debug("invalid arguments"); errno = EINVAL; return -1; - } } @@ -283,3 +280,23 @@ int cap_iab_fill(cap_iab_t iab, cap_iab_vector_t vec, return 0; } + +/* + * 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; + } + 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)); + } + return result; +} diff --git a/libcap/cap_text.c b/libcap/cap_text.c index 25c5ef5..17072f7 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 + * Copyright (c) 1997-8,2007-8,2019,2021 Andrew G Morgan * Copyright (c) 1997 Andrew Main * * This file deals with exchanging internal and textual @@ -609,3 +609,104 @@ 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//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; +} + +#define PROC_LINE_MAX (8 + 8*_LIBCAP_CAPABILITY_U32S + 100) +/* + * cap_iab_get_pid fills an IAB tuple from the content of + * /proc//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]; + + if (asprintf(&path, "/proc/%d/status", 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/include/sys/capability.h b/libcap/include/sys/capability.h index d172ddc..f98da5a 100644 --- a/libcap/include/sys/capability.h +++ b/libcap/include/sys/capability.h @@ -119,6 +119,8 @@ 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 +187,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; diff --git a/progs/getpcaps.c b/progs/getpcaps.c index 5e78487..3cd3fa0 100644 --- a/progs/getpcaps.c +++ b/progs/getpcaps.c @@ -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,13 +31,13 @@ int main(int argc, char **argv) { int retval = 0; int verbose = 0; + int iab = 0; if (argc < 2) { usage(1); } for ( ++argv; --argc > 0; ++argv ) { - ssize_t length; int pid; cap_t cap_d; @@ -55,6 +56,9 @@ int main(int argc, char **argv) } else if (!strcmp(argv[0], "--ugly") || !strcmp(argv[0], "--legacy")) { verbose = 2; continue; + } else if (!strcmp(argv[0], "--iab")) { + iab = 1; + continue; } pid = atoi(argv[0]); @@ -66,11 +70,25 @@ int main(int argc, char **argv) retval = 1; continue; } else { - char *result = cap_to_text(cap_d, &length); + char *result = cap_to_text(cap_d, NULL); 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 if (iab) { + 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); + } + char *iab_text = cap_iab_to_text(iab_val); + if (iab_text == NULL) { + perror("no text for IAB"); + exit(1); + } + printf("%s: \"%s\" [%s]\n", *argv, result, iab_text); + cap_free(iab_text); + cap_free(iab_val); } else { printf("%s: %s\n", *argv, result); } -- cgit v1.2.3 From afbc554d16a32ed352191e49b3673f2f72d0002f Mon Sep 17 00:00:00 2001 From: "Arnout Vandecappelle (Essensium/Mind)" Date: Tue, 24 Aug 2021 01:22:57 +0200 Subject: libcap/Makefile: don't overwrite 'empty' when generating loader.txt objcopy takes an input file and an output file as arguments. If the output file is left out, the input file will be overwritten. Since the objcopy command used to generate loader.txt only does a dump-section and no filtering, in practice there is no change to empty. However, as a side-effect, its timestamp is updated. The timestamp of empty and of loader.txt will be more or less the same; however, loader.txt is closed just before the output file is closed, so it's possible that the timestamp of loader.txt is just a little bit earlier. If this happens, it causes loader.txt to be rebuilt later, which in turn causes a number of other object files to be rebuilt. Usually that's harmless, but it sometimes causes the rebuild to happen during 'make install'. This is particularly annoying if 'make install' is done as root, since loader.txt becomes owned by root in that case. Fix this by specifying a harmless output file: /dev/null. Fixes: ee3b25c0a877fa74d1aec88f325ac45b09963c82 Signed-off-by: Arnout Vandecappelle (Essensium/Mind) Signed-off-by: Andrew G. Morgan --- libcap/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcap/Makefile b/libcap/Makefile index 56fb0af..399aa66 100644 --- a/libcap/Makefile +++ b/libcap/Makefile @@ -108,7 +108,7 @@ 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) $(CPPFLAGS) -DLIBRARY_VERSION=\"$(LIBTITLE)-$(VERSION).$(MINOR)\" -DSHARED_LOADER=\"$(shell cat loader.txt)\" -c execable.c -o $@ -- cgit v1.2.3 From 2e28d3f3e8a1eab3cae561465a4e8076b984be2f Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Mon, 23 Aug 2021 19:17:42 -0700 Subject: Allow the builder to force -lpam linkage. I've yet to understand why this is needed. But, apparently, folk feel strongly that there is a reason one might want to force it one way or another. If you don't care one way or the other, let the Makefiles figure out something that works. Signed-off-by: Andrew G. Morgan --- Make.Rules | 12 ++++++++---- pam_cap/Makefile | 20 +++++++++++++++++--- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/Make.Rules b/Make.Rules index d669275..8adaaa5 100644 --- a/Make.Rules +++ b/Make.Rules @@ -53,16 +53,22 @@ GOMAJOR=1 KERNEL_HEADERS := $(topdir)/libcap/include/uapi LIBCAP_INCLUDES = -I$(KERNEL_HEADERS) -I$(topdir)/libcap/include +DEFINES := -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 CC := $(CROSS_COMPILE)gcc -DEFINES := -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 +LD=$(CC) -Wl,-x -shared COPTS ?= -O2 CFLAGS ?= $(COPTS) $(DEFINES) +LDFLAGS ?= #-g CPPFLAGS += $(LIBCAP_INCLUDES) + BUILD_CC ?= $(CC) -BUILD_COPTS ?= -O2 +BUILD_LD ?= $(BUILD_CC) -Wl,-x -shared +BUILD_COPTS ?= $(COPTS) BUILD_CFLAGS ?= $(BUILD_COPTS) $(DEFINES) +BUILD_LDFLAGS ?= $(LDFLAGS) BUILD_CPPFLAGS += $(LIBCAP_INCLUDES) + AR := $(CROSS_COMPILE)ar RANLIB := $(CROSS_COMPILE)ranlib OBJCOPY := $(CROSS_COMPILE)objcopy @@ -71,8 +77,6 @@ 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 LIBCAPLIB := -L$(topdir)/libcap -lcap PSXLINKFLAGS := -lpthread -Wl,-wrap,pthread_create LIBPSXLIB := -L$(topdir)/libcap -lpsx $(PSXLINKFLAGS) diff --git a/pam_cap/Makefile b/pam_cap/Makefile index 758d51b..d35bdb4 100644 --- a/pam_cap/Makefile +++ b/pam_cap/Makefile @@ -31,15 +31,29 @@ 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) $(CPPFLAGS) lazylink.c -DSHARED_LOADER=\"$(shell cat ../libcap/loader.txt)\" $(LDFLAGS) -Wl,-e,__so_start +endif +endif pam_cap.o: pam_cap.c $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@ -- cgit v1.2.3 From 5647374b3319796957edfb25400bf4164efca6c2 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Mon, 23 Aug 2021 19:47:38 -0700 Subject: Revert most of the LDFLAGS -> LDSTATIC change. The intention was to force --static linking in only one corner case, so be more explicit about that one, and revert the build behavior in the others. Reason for doing this was feedback from Arnout Vandecappelle in: https://bugzilla.kernel.org/show_bug.cgi?id=214023#c16 Signed-off-by: Andrew G. Morgan --- progs/Makefile | 9 ++++++--- tests/Makefile | 19 +++++++++++-------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/progs/Makefile b/progs/Makefile index 172ad37..c36ba9c 100644 --- a/progs/Makefile +++ b/progs/Makefile @@ -14,7 +14,10 @@ ifeq ($(DYNAMIC),yes) LDPATH = LD_LIBRARY_PATH=../libcap DEPS = ../libcap/libcap.so 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 endif @@ -25,7 +28,7 @@ endif make -C ../libcap libcap.so $(BUILD): %: %.o $(DEPS) - $(CC) $(CFLAGS) -o $@ $< $(LIBCAPLIB) $(LDSTATIC) + $(CC) $(CFLAGS) -o $@ $< $(LIBCAPLIB) $(LDFLAGS) %.o: %.c $(INCS) $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@ @@ -46,7 +49,7 @@ capshdoc.h.cf: capshdoc.h ./mkcapshdoc.sh diff -u capshdoc.h $@ || (rm $@ ; exit 1) capsh: capsh.c capshdoc.h.cf $(DEPS) - $(CC) $(CFLAGS) $(CPPFLAGS) $(CAPSH_SHELL) -o $@ $< $(LIBCAPLIB) $(LDSTATIC) + $(CC) $(CFLAGS) $(CPPFLAGS) $(CAPSH_SHELL) -o $@ $< $(LIBCAPLIB) $(LDFLAGS) tcapsh-static: capsh.c capshdoc.h.cf $(DEPS) $(CC) $(CFLAGS) $(CPPFLAGS) $(CAPSH_SHELL) -o $@ $< $(LIBCAPLIB) --static diff --git a/tests/Makefile b/tests/Makefile index 4e1a14d..de890d0 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -22,7 +22,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,17 +66,17 @@ run_psx_test: psx_test ./psx_test psx_test: psx_test.c $(DEPS) - $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LDSTATIC) + $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LDFLAGS) run_libcap_psx_test: libcap_psx_test ./libcap_psx_test libcap_psx_test: libcap_psx_test.c $(DEPS) - $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) $(LDSTATIC) + $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) $(LDFLAGS) # privileged uns_test: uns_test.c $(DEPS) - $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LDSTATIC) + $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LDFLAGS) run_uns_test: uns_test echo exit | sudo ./uns_test @@ -85,13 +88,13 @@ run_libcap_psx_launch_test: libcap_psx_launch_test ../progs/tcapsh-static sudo ./libcap_psx_launch_test libcap_launch_test: libcap_launch_test.c $(DEPS) - $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LDSTATIC) + $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LDFLAGS) # 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) $(CPPFLAGS) -DWITH_PTHREADS $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) $(LDSTATIC) + $(CC) $(CFLAGS) $(CPPFLAGS) -DWITH_PTHREADS $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) $(LDFLAGS) # This test demonstrates that libpsx is needed to secure multithreaded @@ -106,12 +109,12 @@ exploit.o: exploit.c $(CC) $(CFLAGS) $(CPPFLAGS) -c $< exploit: exploit.o $(DEPS) - $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) -lpthread $(LDSTATIC) + $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) -lpthread $(LDFLAGS) # Note, for some reason, the order of libraries is important to avoid # the exploit working for dynamic linking. noexploit: exploit.o $(DEPS) - $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LIBCAPLIB) $(LDSTATIC) + $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LIBCAPLIB) $(LDFLAGS) # This one runs in a chroot with no shared library files. noop: noop.c -- cgit v1.2.3 From be65c32dedbea96760fa7226881fed64f63d9a52 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Mon, 23 Aug 2021 20:09:53 -0700 Subject: Make 'progs/getpcaps --iab' act like 'go/captree' in output format The combined options 'getpcaps --iab --verbose' will show everything in detail (even the boring stuff). Also used this exercise to test the libcap changes for iab comparisons. Signed-off-by: Andrew G. Morgan --- progs/getpcaps.c | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/progs/getpcaps.c b/progs/getpcaps.c index 3cd3fa0..8fce0a3 100644 --- a/progs/getpcaps.c +++ b/progs/getpcaps.c @@ -32,6 +32,7 @@ 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); @@ -71,24 +72,34 @@ int main(int argc, char **argv) continue; } else { char *result = cap_to_text(cap_d, NULL); - 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 if (iab) { + if (iab) { + printf("%s:", *argv); + 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); + fprintf(stderr, " no IAB value for %d\n", pid); exit(1); } - char *iab_text = cap_iab_to_text(iab_val); - if (iab_text == NULL) { - perror("no text for IAB"); - exit(1); + 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); } - printf("%s: \"%s\" [%s]\n", *argv, result, iab_text); - cap_free(iab_text); cap_free(iab_val); + printf("\n"); + } else 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); } -- cgit v1.2.3 From 6715a509015d2143dad0df92f2b12d3317b2cdcf Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Wed, 25 Aug 2021 18:48:01 -0700 Subject: Address a compiler warning with the tests/uns_test. We're comfortable ignoring a write return code, but not all compilers are so display a comment when the write in the uns_test fails. This addresses: https://bugzilla.kernel.org/show_bug.cgi?id=214143 Signed-off-by: Andrew G. Morgan --- tests/uns_test.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/uns_test.c b/tests/uns_test.c index d8f5415..a1dbde0 100644 --- a/tests/uns_test.c +++ b/tests/uns_test.c @@ -153,6 +153,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); } -- cgit v1.2.3 From 7a75dbc2bb0cc46666572845704b1764ab38a4bc Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Wed, 25 Aug 2021 19:11:22 -0700 Subject: Absorb some of archlinux's Make.Rule customizations. These allow overriding of the sbin target directory with make sbindir=xxx or make sbin=xxx We've recently made some CPPFLAGS changes, so I'm not going to disturb those further this iteration. Signed-off-by: Andrew G. Morgan --- Make.Rules | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Make.Rules b/Make.Rules index 8adaaa5..dbf70ec 100644 --- a/Make.Rules +++ b/Make.Rules @@ -21,6 +21,14 @@ ifndef lib lib=$(shell ldd /usr/bin/ld|egrep "ld-linux|ld.so"|cut -d/ -f2) endif +ifndef sbin +sbin=sbin +endif + +ifdef sbindir +sbin=$(sbindir) +endif + ifdef prefix exec_prefix=$(prefix) lib_prefix=$(exec_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 -- cgit v1.2.3 From 04f903f9155b23a6a9f0dd972b448ada5bfc5f82 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Wed, 25 Aug 2021 19:37:15 -0700 Subject: Add some more information to help, and contextual error messages. This was inspired by a feature Debian has been patching orginally credited to Zhi Li. Signed-off-by: Andrew G. Morgan --- progs/setcap.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/progs/setcap.c b/progs/setcap.c index e28b1c7..54260be 100644 --- a/progs/setcap.c +++ b/progs/setcap.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997,2007-8,2020 Andrew G. Morgan + * Copyright (c) 1997,2007-8,2020,21 Andrew G. Morgan * * This sets/verifies the capabilities of a given file. */ @@ -21,6 +21,7 @@ static void usage(int status) " -r remove capability from file\n" " - read capability text from stdin\n" " 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" @@ -95,7 +96,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" " \n", argv[0]); exit(0); } @@ -238,11 +239,24 @@ 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); + } + default: + fprintf(stderr, + "Failed to set capabilities on file '%s': %s\n", + argv[0], strerror(oerrno)); + exit(1); } } } -- cgit v1.2.3 From 935ab8f7cd332f958738c5c90d88dc2111187594 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Wed, 25 Aug 2021 19:48:12 -0700 Subject: Support overriding choice of 'sudo'. Use something like: make SUDO=my_sudo sudotest Signed-off-by: Andrew G. Morgan --- Make.Rules | 1 + go/Makefile | 8 ++++---- pam_cap/Makefile | 14 +++++++------- progs/Makefile | 2 +- tests/Makefile | 10 +++++----- 5 files changed, 18 insertions(+), 17 deletions(-) diff --git a/Make.Rules b/Make.Rules index dbf70ec..197a3df 100644 --- a/Make.Rules +++ b/Make.Rules @@ -65,6 +65,7 @@ DEFINES := -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 CC := $(CROSS_COMPILE)gcc LD=$(CC) -Wl,-x -shared +SUDO := sudo COPTS ?= -O2 CFLAGS ?= $(COPTS) $(DEFINES) LDFLAGS ?= #-g diff --git a/go/Makefile b/go/Makefile index 52e1f3e..2100eed 100644 --- a/go/Makefile +++ b/go/Makefile @@ -60,7 +60,7 @@ web: ../goapps/web/web.go CAPGOPACKAGE CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) build -mod=vendor -o $@ $< ifeq ($(RAISE_GO_FILECAP),yes) make -C ../progs setcap - sudo ../progs/setcap cap_setpcap,cap_net_bind_service=p web + $(SUDO) ../progs/setcap cap_setpcap,cap_net_bind_service=p web @echo "NOTE: RAISED cap_setpcap,cap_net_bind_service ON web binary" endif @@ -115,11 +115,11 @@ sudotest: test ../progs/tcapsh-static b210613 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 install: all rm -rf $(FAKEROOT)$(GOPKGDIR)/$(IMPORTDIR)/psx diff --git a/pam_cap/Makefile b/pam_cap/Makefile index d35bdb4..d5da6be 100644 --- a/pam_cap/Makefile +++ b/pam_cap/Makefile @@ -75,13 +75,13 @@ test: testlink test_pam_cap pam_cap.so @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 + $(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 *~ diff --git a/progs/Makefile b/progs/Makefile index c36ba9c..51e9a63 100644 --- a/progs/Makefile +++ b/progs/Makefile @@ -59,7 +59,7 @@ uns_test: ../tests/uns_test.c cp ../tests/uns_test . sudotest: test tcapsh-static uns_test - sudo $(LDPATH) ./quicktest.sh + $(SUDO) $(LDPATH) ./quicktest.sh clean: $(LOCALCLEAN) diff --git a/tests/Makefile b/tests/Makefile index de890d0..d9ed248 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -79,13 +79,13 @@ uns_test: uns_test.c $(DEPS) $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LDFLAGS) 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) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LDFLAGS) @@ -101,9 +101,9 @@ libcap_psx_launch_test: libcap_launch_test.c $(DEPS) # 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 + $(SUDO) ./noexploit ; if [ $$? -eq 0 ]; then exit 0; else exit 1 ; fi exploit.o: exploit.c $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -- cgit v1.2.3 From a0aaea6e27df499c4d3c548af16e3727b99ca918 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Wed, 25 Aug 2021 19:50:46 -0700 Subject: Add a comment about overriding DYNAMIC. Signed-off-by: Andrew G. Morgan --- Make.Rules | 1 + 1 file changed, 1 insertion(+) diff --git a/Make.Rules b/Make.Rules index 197a3df..b2bb1b8 100644 --- a/Make.Rules +++ b/Make.Rules @@ -102,6 +102,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) -- cgit v1.2.3 From 07cdff9ac969c35f1b2e4c0ccb5e3cc5fdceb2b2 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Wed, 25 Aug 2021 21:09:19 -0700 Subject: Up the release version to 2.54 Signed-off-by: Andrew G. Morgan --- Make.Rules | 2 +- cap/go.mod | 2 +- contrib/seccomp/go.mod | 2 +- go/go.mod | 4 ++-- goapps/captree/go.mod | 2 +- goapps/gowns/go.mod | 2 +- goapps/setid/go.mod | 4 ++-- goapps/web/go.mod | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Make.Rules b/Make.Rules index b2bb1b8..c2e1523 100644 --- a/Make.Rules +++ b/Make.Rules @@ -1,7 +1,7 @@ # Common version number defines for libcap LIBTITLE=libcap VERSION=2 -MINOR=53 +MINOR=54 # ## Optional prefixes: diff --git a/cap/go.mod b/cap/go.mod index a299f4c..cf37bcb 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.54 diff --git a/contrib/seccomp/go.mod b/contrib/seccomp/go.mod index 8f72409..0e08db1 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.54 diff --git a/go/go.mod b/go/go.mod index 4c49252..95be67b 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.54 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.54 ) diff --git a/goapps/captree/go.mod b/goapps/captree/go.mod index ff1ad6c..52aada9 100644 --- a/goapps/captree/go.mod +++ b/goapps/captree/go.mod @@ -2,4 +2,4 @@ module captree go 1.16 -require kernel.org/pub/linux/libs/security/libcap/cap v1.2.53 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.54 diff --git a/goapps/gowns/go.mod b/goapps/gowns/go.mod index 92de262..d845031 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.54 diff --git a/goapps/setid/go.mod b/goapps/setid/go.mod index 02061c2..cad32f2 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.54 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.54 ) diff --git a/goapps/web/go.mod b/goapps/web/go.mod index 4ca6b0e..2c8326c 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.54 -- cgit v1.2.3 From c90b5debdf28acc010d5ee50ff5ff0c97ab0e367 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Thu, 26 Aug 2021 20:24:47 -0700 Subject: Fix some static analysis results. This series of issues was found by Zoltan Fridrich. Signed-off-by: Andrew G. Morgan --- libcap/_makenames.c | 4 ++++ libcap/cap_proc.c | 4 ++++ libcap/cap_text.c | 10 ++++++++++ libcap/execable.c | 2 +- pam_cap/pam_cap.c | 3 +++ progs/capsh.c | 17 +++++++++++++++++ progs/getcap.c | 8 ++++---- progs/setcap.c | 6 +++++- psx/psx.c | 4 ++++ tests/libcap_launch_test.c | 10 ++++++++-- tests/libcap_psx_test.c | 9 +++++++++ 11 files changed, 69 insertions(+), 8 deletions(-) diff --git a/libcap/_makenames.c b/libcap/_makenames.c index 46ab0c9..b09cf69 100644 --- a/libcap/_makenames.c +++ b/libcap/_makenames.c @@ -49,6 +49,10 @@ int main(void) 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_proc.c b/libcap/cap_proc.c index fdb8cbe..1494f8d 100644 --- a/libcap/cap_proc.c +++ b/libcap/cap_proc.c @@ -723,6 +723,10 @@ static int _cap_iab_set_proc(struct syscaller_s *sc, cap_iab_t iab) cap_value_t c; int raising = 0; + if (temp == NULL) { + return -1; + } + for (i = 0; i < _LIBCAP_CAPABILITY_U32S; i++) { __u32 newI = iab->i[i]; __u32 oldIP = temp->u[i].flat[CAP_INHERITABLE] | diff --git a/libcap/cap_text.c b/libcap/cap_text.c index 17072f7..a0857bc 100644 --- a/libcap/cap_text.c +++ b/libcap/cap_text.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; } @@ -403,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; @@ -455,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; @@ -554,6 +561,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++) { diff --git a/libcap/execable.c b/libcap/execable.c index be18914..5e7a88f 100644 --- a/libcap/execable.c +++ b/libcap/execable.c @@ -4,7 +4,7 @@ SO_MAIN(int argc, char **argv) { 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" diff --git a/pam_cap/pam_cap.c b/pam_cap/pam_cap.c index 162e1f5..17ad83e 100644 --- a/pam_cap/pam_cap.c +++ b/pam_cap/pam_cap.c @@ -67,6 +67,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]); diff --git a/progs/capsh.c b/progs/capsh.c index 50c2c99..42d9064 100644 --- a/progs/capsh.c +++ b/progs/capsh.c @@ -101,7 +101,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); @@ -436,6 +445,10 @@ int main(int argc, char *argv[], char *envp[]) child = 0; 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); @@ -1014,6 +1027,10 @@ int main(int argc, char *argv[], char *envp[]) 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) { diff --git a/progs/getcap.c b/progs/getcap.c index eec733b..7df7f0e 100644 --- a/progs/getcap.c +++ b/progs/getcap.c @@ -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/setcap.c b/progs/setcap.c index 54260be..066e47f 100644 --- a/progs/setcap.c +++ b/progs/setcap.c @@ -167,9 +167,12 @@ int main(int argc, char **argv) } cap_on_file = cap_get_file(*++argv); - if (cap_on_file == NULL) { cap_on_file = cap_from_text("="); + if (cap_on_file == NULL) { + perror("unable to use missing capability"); + exit(1); + } } cmp = cap_compare(cap_on_file, cap_d); @@ -252,6 +255,7 @@ int main(int argc, char **argv) argv[0]); exit(1); } + /* FALLTHROUGH */ default: fprintf(stderr, "Failed to set capabilities on file '%s': %s\n", diff --git a/psx/psx.c b/psx/psx.c index 90dcc50..c317063 100644 --- a/psx/psx.c +++ b/psx/psx.c @@ -107,6 +107,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); diff --git a/tests/libcap_launch_test.c b/tests/libcap_launch_test.c index f45b2b7..d1b3d40 100644 --- a/tests/libcap_launch_test.c +++ b/tests/libcap_launch_test.c @@ -40,7 +40,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; } @@ -121,12 +123,16 @@ int main(int argc, char **argv) { int success = 1, i; for (i=0; vs[i].pass_on != NO_MORE; i++) { + cap_launch_t attr; const struct test_case_s *v = &vs[i]; 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); } diff --git a/tests/libcap_psx_test.c b/tests/libcap_psx_test.c index 9f53f06..e473126 100644 --- a/tests/libcap_psx_test.c +++ b/tests/libcap_psx_test.c @@ -16,6 +16,10 @@ 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); exit(0); @@ -27,6 +31,7 @@ static void *thread_fork_exit(void *data) { exit(1); } cap_set_proc(start); + cap_free(start); return NULL; } @@ -35,6 +40,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); -- cgit v1.2.3 From a56162c6900d203c5ac63a2b41b46cb0c45c645f Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Thu, 26 Aug 2021 21:31:15 -0700 Subject: Eliminate an alignment issue found by clang. Clang helpfully noticed that libcap allocated things should be 64-bit aligned on 64-bit platforms. Restructure the memory allocation to ensure this. Fixes: https://bugzilla.kernel.org/show_bug.cgi?id=214183 Signed-off-by: Andrew G. Morgan --- libcap/cap_alloc.c | 146 ++++++++++++++++++++++++++++------------------------- libcap/libcap.h | 14 ++--- 2 files changed, 81 insertions(+), 79 deletions(-) diff --git a/libcap/cap_alloc.c b/libcap/cap_alloc.c index 91813db..72317d4 100644 --- a/libcap/cap_alloc.c +++ b/libcap/cap_alloc.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997-8,2019 Andrew G Morgan + * Copyright (c) 1997-8,2019,2021 Andrew G Morgan * * This file deals with allocation and deallocation of internal * capability sets as specified by POSIX.1e (formerlly, POSIX 6). @@ -24,25 +24,39 @@ cap_value_t cap_max_bits(void) { return _cap_max_bits; } +/* + * capability allocation is all done in terms of this structure. + */ +struct _cap_alloc_s { + __u32 magic; + __u32 size; + union { + char string_start; /* enough memory is allocated for string */ + 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,26 +86,30 @@ 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) { - __u32 *raw_data; + struct _cap_alloc_s *raw_data; + size_t len; if (old == NULL) { errno = EINVAL; return NULL; } - - raw_data = malloc( sizeof(__u32) + strlen(old) + 1 ); + len = strlen(old) + 1 + 2*sizeof(__u32); + if ((len & 0xffffffff) != len) { + errno = EINVAL; + return NULL; + } + raw_data = calloc(1, len); if (raw_data == NULL) { errno = ENOMEM; return NULL; } + raw_data->magic = CAP_S_MAGIC; + raw_data->size = (__u32) len; + strcpy(&raw_data->u.string_start, old); - *(raw_data++) = CAP_S_MAGIC; - strcpy((char *) raw_data, old); - - return ((char *) raw_data); + return &raw_data->u.string_start; } /* @@ -99,12 +117,12 @@ char *_libcap_strdup(const char *old) * malloc()'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; - if (!good_cap_t(cap_d)) { + __u32 *magic_p = -2 + (__u32 *) cap_d; + if (*magic_p != CAP_T_MAGIC) { _cap_debug("bad argument"); errno = EINVAL; return NULL; @@ -121,14 +139,16 @@ cap_t cap_dup(cap_t cap_d) return result; } -cap_iab_t cap_iab_init(void) { - __u32 *base = calloc(1, sizeof(__u32) + sizeof(struct cap_iab_s)); +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++) = CAP_IAB_MAGIC; - return (cap_iab_t) base; + base->magic = CAP_IAB_MAGIC; + base->size = sizeof(struct _cap_alloc_s); + return &base->u.iab; } /* @@ -141,13 +161,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)); + struct _cap_alloc_s *data = calloc(1, sizeof(struct _cap_alloc_s)); if (data == NULL) { _cap_debug("out of memory"); return NULL; } - *(data++) = CAP_LAUNCH_MAGIC; - struct cap_launch_s *attr = (struct cap_launch_s *) data; + 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; @@ -163,69 +185,55 @@ 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)); + struct _cap_alloc_s *data = calloc(1, sizeof(struct _cap_alloc_s)); if (data == NULL) { _cap_debug("out of memory"); return NULL; } - *(data++) = CAP_LAUNCH_MAGIC; - struct cap_launch_s *attr = (struct cap_launch_s *) data; + 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) - 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; + if (!data_p) { 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); - } - if (launcher->chroot) { - cap_free(launcher->chroot); - } - 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; + struct _cap_alloc_s *data = (void *) (-2 + (__u32 *) data_p); + switch (data->magic) { + case CAP_T_MAGIC: + case CAP_IAB_MAGIC: + case CAP_S_MAGIC: + break; + case CAP_LAUNCH_MAGIC: + (void) cap_free(&data->u.launcher.iab); + (void) cap_free(&data->u.launcher.chroot); + 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; + memset(data, 0, data->size); + free(data); + data_p = NULL; + data = NULL; + return 0; } diff --git a/libcap/libcap.h b/libcap/libcap.h index 97a47ae..ffc8c8a 100644 --- a/libcap/libcap.h +++ b/libcap/libcap.h @@ -133,6 +133,10 @@ struct _cap_struct { /* launcher magic for cap_free */ #define CAP_LAUNCH_MAGIC 0xCA91A +#define magic_of(x) (*(-2 + (const __u32 *) x)) +#define good_cap_t(x) (CAP_T_MAGIC == magic_of(x)) +#define good_cap_iab_t(x) (CAP_IAB_MAGIC == magic_of(x)) + /* * kernel API cap set abstraction */ @@ -141,16 +145,6 @@ struct _cap_struct { #define lower_cap(x, set) u[(x) >> 5].flat[set] &= ~(1u << ((x)&31)) #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 */ -- cgit v1.2.3 From 386af0edbc9eec3b382451da782a08ba4632db06 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Thu, 26 Aug 2021 22:26:56 -0700 Subject: Stop installing the cap and psx packages, but do install captree. We also add the cap.ProcRoot() API to let the user redirect to their local /proc/ directory - in case anyone runs with an unusual setup like that. I've been studying the downstream package definitions and no one it doesn't seem popular to build the Go packages. Indeed, Go folk themselves prefer to install via modules anyway, so we're getting with the program. However, if folk want to build test the Go stuff as part of a package build and run an install as well, we reward them with the 'captree' binary. Signed-off-by: Andrew G. Morgan --- cap/iab.go | 17 ++++++++++++++++- go/Makefile | 19 +++++++++++++------ goapps/captree/captree.go | 4 ++++ 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/cap/iab.go b/cap/iab.go index 816118f..1be921c 100644 --- a/cap/iab.go +++ b/cap/iab.go @@ -366,11 +366,26 @@ func parseHex(hex string, invert bool) []uint32 { 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//status file content. func IABGetPID(pid int) (*IAB, error) { - tf := fmt.Sprintf("/proc/%d/status", pid) + tf := fmt.Sprintf("%s/%d/status", procRoot, pid) d, err := ioutil.ReadFile(tf) if err != nil { return nil, err diff --git a/go/Makefile b/go/Makefile index 2100eed..ce464d1 100644 --- a/go/Makefile +++ b/go/Makefile @@ -121,13 +121,20 @@ ifeq ($(CGO_REQUIRED),0) endif $(SUDO) ../progs/tcapsh-static --cap-uid=$$(id -u) --caps="cap_setpcap=ep" --iab="^cap_setpcap" -- -c ./b210613 +# 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 diff --git a/goapps/captree/captree.go b/goapps/captree/captree.go index 527150c..aa94cd3 100644 --- a/goapps/captree/captree.go +++ b/goapps/captree/captree.go @@ -267,6 +267,10 @@ func rDump(pids map[string]*task, pid, stub, lstub, estub string) { func main() { flag.Parse() + // 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: "", -- cgit v1.2.3 From 552db8f4116df3fad4e4ebf90a9a05a77b9486fd Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Fri, 27 Aug 2021 09:45:46 -0700 Subject: More fixes for static analysis issues. Further observations from Zoltan Fridrich's static analysis of libcap. This commit also includes a fix for something I broke with the last round of "fixing", and a test to make sure I don't make that mistake again. Signed-off-by: Andrew G. Morgan --- libcap/_makenames.c | 2 +- libcap/cap_alloc.c | 11 +++-- libcap/cap_proc.c | 49 +++++++++++++++---- libcap/cap_test.c | 114 ++++++++++++++++++++++++++++++++++++++++----- pam_cap/execable.c | 2 +- progs/capsh.c | 12 +++++ progs/setcap.c | 29 +++++++----- tests/libcap_launch_test.c | 8 ++++ tests/libcap_psx_test.c | 10 +++- 9 files changed, 199 insertions(+), 38 deletions(-) diff --git a/libcap/_makenames.c b/libcap/_makenames.c index b09cf69..c0d6db4 100644 --- a/libcap/_makenames.c +++ b/libcap/_makenames.c @@ -45,7 +45,7 @@ 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 *)); diff --git a/libcap/cap_alloc.c b/libcap/cap_alloc.c index 72317d4..df1a275 100644 --- a/libcap/cap_alloc.c +++ b/libcap/cap_alloc.c @@ -41,7 +41,6 @@ struct _cap_alloc_s { /* * Obtain a blank set of capabilities */ - cap_t cap_init(void) { struct _cap_alloc_s *raw_data; @@ -222,8 +221,14 @@ int cap_free(void *data_p) case CAP_S_MAGIC: break; case CAP_LAUNCH_MAGIC: - (void) cap_free(&data->u.launcher.iab); - (void) cap_free(&data->u.launcher.chroot); + if (cap_free(data->u.launcher.iab) != 0) { + return -1; + } + data->u.launcher.iab = NULL; + if (cap_free(data->u.launcher.chroot) != 0) { + return -1; + } + data->u.launcher.chroot = NULL; break; default: _cap_debug("don't recognize what we're supposed to liberate"); diff --git a/libcap/cap_proc.c b/libcap/cap_proc.c index 1494f8d..eac86cb 100644 --- a/libcap/cap_proc.c +++ b/libcap/cap_proc.c @@ -438,13 +438,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; @@ -520,7 +524,7 @@ cap_mode_t cap_get_mode(void) /* 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); @@ -529,6 +533,7 @@ cap_mode_t cap_get_mode(void) if (c && secbits != CAP_SECURED_BITS_AMBIENT) { return CAP_MODE_UNCERTAIN; } + ret = 0; break; } if (ret) { @@ -536,11 +541,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; @@ -566,6 +582,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); /* @@ -621,6 +641,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); /* @@ -718,10 +742,9 @@ cap_iab_t cap_iab_get_proc(void) */ 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; @@ -737,6 +760,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); @@ -861,6 +888,10 @@ 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); diff --git a/libcap/cap_test.c b/libcap/cap_test.c index e8eb647..41192b7 100644 --- a/libcap/cap_test.c +++ b/libcap/cap_test.c @@ -5,11 +5,13 @@ 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 }; @@ -35,10 +37,12 @@ static int test_cap_bits(void) { 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) { @@ -49,7 +53,8 @@ static int test_cap_flags(void) { 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; } } @@ -57,24 +62,36 @@ 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"); - return -1; + 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; } - cap_free(d); - cap_free(c); - return 0; + return retval; } -static int test_short_bits(void) { +static int test_short_bits(void) +{ int result = 0; char *tmp; int n = asprintf(&tmp, "%d", __CAP_MAXBITS); @@ -90,12 +107,87 @@ static int test_short_bits(void) { 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; + + c = cap_init(); + if (c == NULL) { + perror("failed to allocate a cap_t"); + return -1; + } + + iab = cap_iab_init(); + if (iab == NULL) { + perror("failed to allocate a cap_iab_t"); + retval = -1; + goto drop_c; + } + + launcher = cap_func_launcher(noop); + if (launcher == NULL) { + perror("failde to allocate a launcher"); + 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"); + 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"); + retval = -1; + goto drop_launcher; + } + +drop_launcher: + if (cap_free(launcher)) { + perror("failed to free launcher"); + retval = -1; + } + +drop_iab: + if (!cap_free(2+(__u32 *) iab)) { + printf("unable to recognize bad cap_iab_t pointer\n"); + retval = -1; + } + if (cap_free(iab)) { + perror("failed to free iab"); + retval = -1; + } + +drop_c: + if (!cap_free(1+(__u32 *)c)) { + printf("unable to recognize bad cap_t pointer\n"); + retval = -1; + } + if (cap_free(c)) { + perror("failed to free c"); + retval = -1; + } + return retval; +} + int main(int argc, char **argv) { int result = 0; result = test_cap_bits() | result; result = test_cap_flags() | result; result = test_short_bits() | result; + result = test_alloc() | result; if (result) { printf("cap_test FAILED\n"); diff --git a/pam_cap/execable.c b/pam_cap/execable.c index 0bf42d3..05f18d8 100644 --- a/pam_cap/execable.c +++ b/pam_cap/execable.c @@ -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); } diff --git a/progs/capsh.c b/progs/capsh.c index 42d9064..763c08d 100644 --- a/progs/capsh.c +++ b/progs/capsh.c @@ -89,6 +89,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); @@ -922,6 +926,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); @@ -938,6 +946,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); diff --git a/progs/setcap.c b/progs/setcap.c index 066e47f..f8be53a 100644 --- a/progs/setcap.c +++ b/progs/setcap.c @@ -85,9 +85,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; @@ -109,7 +112,8 @@ int main(int argc, char **argv) } if (!strcmp(*argv, "-n")) { if (argc < 2) { - fprintf(stderr, "usage: .. -n .. - rootid!=0 file caps"); + fprintf(stderr, + "usage: .. -n .. - rootid!=0 file caps"); exit(1); } --argc; @@ -122,6 +126,7 @@ int main(int argc, char **argv) } if (!strcmp(*argv, "-r")) { + cap_free(cap_d); cap_d = NULL; } else { if (!strcmp(*argv,"-")) { @@ -144,11 +149,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 } @@ -163,12 +166,16 @@ 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); @@ -264,9 +271,9 @@ int main(int argc, char **argv) } } } - if (cap_d) { - cap_free(cap_d); - } + } + if (cap_d) { + cap_free(cap_d); } exit(0); diff --git a/tests/libcap_launch_test.c b/tests/libcap_launch_test.c index d1b3d40..343e389 100644 --- a/tests/libcap_launch_test.c +++ b/tests/libcap_launch_test.c @@ -120,6 +120,10 @@ int main(int argc, char **argv) { }; 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++) { @@ -201,6 +205,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 e473126..9ef8cac 100644 --- a/tests/libcap_psx_test.c +++ b/tests/libcap_psx_test.c @@ -21,7 +21,10 @@ static void *thread_fork_exit(void *data) { 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; @@ -51,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"); -- cgit v1.2.3 From b56400f81ddd42e0e57372c957e668e6d5a72834 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Fri, 27 Aug 2021 10:20:21 -0700 Subject: Implement libcap:cap_proc_root() function. This is needed to locally configure libcap to find the pid data if the proc filesystem is not mounted at "/proc" (rare). Currently libcap only uses this info to implement cap_iab_get_pid(). This brings libcap back to parity with the Go "cap" package. Signed-off-by: Andrew G. Morgan --- libcap/cap_alloc.c | 9 ++++++--- libcap/cap_test.c | 26 ++++++++++++++++++++++++++ libcap/cap_text.c | 27 ++++++++++++++++++++++++++- libcap/include/sys/capability.h | 16 ++++++++++++++++ 4 files changed, 74 insertions(+), 4 deletions(-) diff --git a/libcap/cap_alloc.c b/libcap/cap_alloc.c index df1a275..5733e2f 100644 --- a/libcap/cap_alloc.c +++ b/libcap/cap_alloc.c @@ -8,19 +8,22 @@ #include "libcap.h" /* - * This gets set via the pre-main() executed constructor function below it. + * 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) { +__attribute__((constructor (300))) static void _initialize_libcap(void) +{ if (_cap_max_bits) { return; } cap_set_syscall(NULL, NULL); _binary_search(_cap_max_bits, cap_get_bound, 0, __CAP_MAXBITS, __CAP_BITS); + cap_proc_root("/proc"); } -cap_value_t cap_max_bits(void) { +cap_value_t cap_max_bits(void) +{ return _cap_max_bits; } diff --git a/libcap/cap_test.c b/libcap/cap_test.c index 41192b7..9fa7300 100644 --- a/libcap/cap_test.c +++ b/libcap/cap_test.c @@ -118,6 +118,7 @@ static int test_alloc(void) cap_t c; cap_iab_t iab; cap_launch_t launcher; + char *old_root; c = cap_init(); if (c == NULL) { @@ -153,6 +154,31 @@ static int test_alloc(void) goto drop_launcher; } + old_root = cap_proc_root("blah"); + if (old_root == NULL || strcmp(old_root, "/proc") != 0) { + printf("bad initial proc_root [%s]\n", old_root); + retval = -1; + } + if (cap_free(old_root)) { + perror("unable to free old proc root"); + 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); + retval = -1; + } + if (cap_free(old_root)) { + perror("unable to free replacement proc root"); + retval = -1; + } + if (retval) { + goto drop_launcher; + } + drop_launcher: if (cap_free(launcher)) { perror("failed to free launcher"); diff --git a/libcap/cap_text.c b/libcap/cap_text.c index a0857bc..013eb1e 100644 --- a/libcap/cap_text.c +++ b/libcap/cap_text.c @@ -667,6 +667,31 @@ static __u32 _parse_vec_string(__u32 *vals, const char *c, int invert) return ~0; } +/* + * libcap believes this is the root of the mounted "/proc" + * filesystem + */ +static char *_cap_proc_dir; + +/* + * 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. + */ +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 @@ -680,7 +705,7 @@ cap_iab_t cap_iab_get_pid(pid_t pid) FILE *file; char line[PROC_LINE_MAX]; - if (asprintf(&path, "/proc/%d/status", pid) <= 0) { + if (asprintf(&path, "%s/%d/status", _cap_proc_dir, pid) <= 0) { return NULL; } file = fopen(path, "r"); diff --git a/libcap/include/sys/capability.h b/libcap/include/sys/capability.h index f98da5a..59f9377 100644 --- a/libcap/include/sys/capability.h +++ b/libcap/include/sys/capability.h @@ -53,6 +53,22 @@ 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. 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 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) when done with it. + */ +extern char *cap_proc_root(const char *root); + /* * Set identifiers */ -- cgit v1.2.3 From de1130dbfe6d4ce99422b11cac147d39448bcd40 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Fri, 27 Aug 2021 13:55:11 -0700 Subject: Speculative fix for build failure. Not sure exactly what is causing the build server to fail (can't reproduce yet), but add some extra padding to a calloc and also some test debugging printf()s. Signed-off-by: Andrew G. Morgan --- Make.Rules | 11 ++++++----- libcap/cap_alloc.c | 4 ++++ libcap/cap_test.c | 7 ++++++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Make.Rules b/Make.Rules index c2e1523..ae9d42d 100644 --- a/Make.Rules +++ b/Make.Rules @@ -54,7 +54,8 @@ 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 @@ -152,11 +153,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/libcap/cap_alloc.c b/libcap/cap_alloc.c index 5733e2f..ba2dd11 100644 --- a/libcap/cap_alloc.c +++ b/libcap/cap_alloc.c @@ -98,7 +98,11 @@ char *_libcap_strdup(const char *old) return NULL; } len = strlen(old) + 1 + 2*sizeof(__u32); + if (len < sizeof(struct _cap_alloc_s)) { + len = sizeof(struct _cap_alloc_s); + } if ((len & 0xffffffff) != len) { + _cap_debug("len is too long for libcap to manage"); errno = EINVAL; return NULL; } diff --git a/libcap/cap_test.c b/libcap/cap_test.c index 9fa7300..0beb45d 100644 --- a/libcap/cap_test.c +++ b/libcap/cap_test.c @@ -120,6 +120,8 @@ static int test_alloc(void) cap_launch_t launcher; char *old_root; + printf("test_alloc\n"); + c = cap_init(); if (c == NULL) { perror("failed to allocate a cap_t"); @@ -180,12 +182,14 @@ static int test_alloc(void) } drop_launcher: + printf("test_alloc: drop_launcher\n"); if (cap_free(launcher)) { perror("failed to free launcher"); retval = -1; } drop_iab: + printf("test_alloc: drop_iab\n"); if (!cap_free(2+(__u32 *) iab)) { printf("unable to recognize bad cap_iab_t pointer\n"); retval = -1; @@ -196,7 +200,8 @@ drop_iab: } drop_c: - if (!cap_free(1+(__u32 *)c)) { + printf("test_alloc: drop_cap\n"); + if (!cap_free(1+(__u32 *) c)) { printf("unable to recognize bad cap_t pointer\n"); retval = -1; } -- cgit v1.2.3 From 43365cf01c64b530e7a3d62214247e1aa042414d Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Fri, 27 Aug 2021 21:01:46 -0700 Subject: Still unclear where cap_test is crashing. Add more debug logging. Signed-off-by: Andrew G. Morgan --- libcap/cap_test.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/libcap/cap_test.c b/libcap/cap_test.c index 0beb45d..729efed 100644 --- a/libcap/cap_test.c +++ b/libcap/cap_test.c @@ -121,16 +121,19 @@ static int test_alloc(void) 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; } iab = cap_iab_init(); if (iab == NULL) { perror("failed to allocate a cap_iab_t"); + fflush(stderr); retval = -1; goto drop_c; } @@ -138,6 +141,7 @@ static int test_alloc(void) launcher = cap_func_launcher(noop); if (launcher == NULL) { perror("failde to allocate a launcher"); + fflush(stderr); retval = -1; goto drop_iab; } @@ -145,6 +149,7 @@ static int test_alloc(void) 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; } @@ -152,6 +157,7 @@ static int test_alloc(void) 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; } @@ -159,10 +165,12 @@ static int test_alloc(void) old_root = cap_proc_root("blah"); if (old_root == NULL || strcmp(old_root, "/proc") != 0) { 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) { @@ -171,10 +179,12 @@ static int test_alloc(void) 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) { @@ -183,30 +193,38 @@ static int test_alloc(void) 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; @@ -215,10 +233,20 @@ drop_c: 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("tested\n"); + fflush(stdout); if (result) { printf("cap_test FAILED\n"); -- cgit v1.2.3 From 6c38eb78d96a60a9503dc5c89ade67b65778fed9 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 28 Aug 2021 09:43:51 -0700 Subject: Avoid the build server failure. I figured out that the key ingredient to reproducing this issue was: make COPTS="-D_FORTIFY_SOURCE=2 -O1 -g" clean test Signed-off-by: Andrew G. Morgan --- Makefile | 1 + libcap/cap_alloc.c | 27 ++++++++++++++++++--------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index d26af01..1c195dd 100644 --- a/Makefile +++ b/Makefile @@ -67,6 +67,7 @@ endif distcheck: ./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 diff --git a/libcap/cap_alloc.c b/libcap/cap_alloc.c index ba2dd11..4dabe27 100644 --- a/libcap/cap_alloc.c +++ b/libcap/cap_alloc.c @@ -34,7 +34,6 @@ struct _cap_alloc_s { __u32 magic; __u32 size; union { - char string_start; /* enough memory is allocated for string */ struct _cap_struct set; struct cap_iab_s iab; struct cap_launch_s launcher; @@ -90,7 +89,8 @@ cap_t cap_init(void) */ char *_libcap_strdup(const char *old) { - struct _cap_alloc_s *raw_data; + struct _cap_alloc_s *header; + char *raw_data; size_t len; if (old == NULL) { @@ -106,16 +106,19 @@ char *_libcap_strdup(const char *old) errno = EINVAL; return NULL; } + raw_data = calloc(1, len); if (raw_data == NULL) { errno = ENOMEM; return NULL; } - raw_data->magic = CAP_S_MAGIC; - raw_data->size = (__u32) len; - strcpy(&raw_data->u.string_start, old); + header = (void *) raw_data; + header->magic = CAP_S_MAGIC; + header->size = (__u32) len; - return &raw_data->u.string_start; + raw_data += 2*sizeof(__u32); + strcpy(raw_data, old); + return raw_data; } /* @@ -221,7 +224,8 @@ int cap_free(void *data_p) return -1; } - struct _cap_alloc_s *data = (void *) (-2 + (__u32 *) data_p); + void *base = (void *) (-2 + (__u32 *) data_p); + struct _cap_alloc_s *data = base; switch (data->magic) { case CAP_T_MAGIC: case CAP_IAB_MAGIC: @@ -243,9 +247,14 @@ int cap_free(void *data_p) return -1; } - memset(data, 0, data->size); - free(data); + /* + * 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; } -- cgit v1.2.3 From 61b2fcc4510641ffd691d8e5a82e968b458f0cb9 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 28 Aug 2021 15:58:16 -0700 Subject: Make sudotest more robust against untestable environments I'm setting up some testing environments and they are not all created equal. Signed-off-by: Andrew G. Morgan --- doc/capsh.1 | 7 +++++++ go/Makefile | 2 +- go/try-launching.go | 4 +++- libcap/cap_flag.c | 1 - progs/capsh.c | 13 +++++++++++++ progs/quicktest.sh | 12 ++++++------ tests/uns_test.c | 11 +++++++++++ 7 files changed, 41 insertions(+), 9 deletions(-) diff --git a/doc/capsh.1 b/doc/capsh.1 index e309438..9bed928 100644 --- a/doc/capsh.1 +++ b/doc/capsh.1 @@ -279,6 +279,13 @@ vector has capability .B xxx raised. .TP +.BI \-\-has\-b= xxx +Exit with status 1 unless the +.I bounding +set vector has capability +.B xxx +enabled. +.TP .BI \-\-iab= xxx Attempts to set the IAB tuple of inheritable capability vectors. The text conventions used for \fIxxx\fP are those of diff --git a/go/Makefile b/go/Makefile index ce464d1..854f0e3 100644 --- a/go/Makefile +++ b/go/Makefile @@ -110,7 +110,7 @@ endif # 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" + ../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 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/libcap/cap_flag.c b/libcap/cap_flag.c index 1f561f7..9df1842 100644 --- a/libcap/cap_flag.c +++ b/libcap/cap_flag.c @@ -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) { diff --git a/progs/capsh.c b/progs/capsh.c index 763c08d..0cf8b1e 100644 --- a/progs/capsh.c +++ b/progs/capsh.c @@ -967,6 +967,17 @@ 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; @@ -1075,11 +1086,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= fork and make child sleep for sec\n" " --gid= set gid to (hint: id )\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" diff --git a/progs/quicktest.sh b/progs/quicktest.sh index ba64ab5..ebb7567 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 @@ -214,8 +214,8 @@ EOF 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 diff --git a/tests/uns_test.c b/tests/uns_test.c index a1dbde0..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); -- cgit v1.2.3 From b972c50c0989a81da308886e5d602c272e90f8cb Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 29 Aug 2021 15:09:59 -0700 Subject: Add captree command line options and support process by name. Add some features to captree. I plan to post a companion article here: https://sites.google.com/site/fullycapable/captree Signed-off-by: Andrew G. Morgan --- doc/Makefile | 2 +- doc/captree.8 | 63 +++++++++++++++++++++++++++++++++++++++++++++++ goapps/captree/captree.go | 61 +++++++++++++++++++++++++++++++++++++++------ 3 files changed, 117 insertions(+), 9 deletions(-) create mode 100644 doc/captree.8 diff --git a/doc/Makefile b/doc/Makefile index 943dbfa..e2802dc 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -26,7 +26,7 @@ MAN3S = cap_init.3 cap_free.3 cap_dup.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 +MAN8S = getcap.8 setcap.8 getpcaps.8 captree.8 MANS = $(MAN1S) $(MAN3S) $(MAN8S) diff --git a/doc/captree.8 b/doc/captree.8 new file mode 100644 index 0000000..700610f --- /dev/null +++ b/doc/captree.8 @@ -0,0 +1,63 @@ +.\" Hey, EMACS: -*- nroff -*- +.TH CAPTREE 8 "2021-08-29" +.\" Please adjust this date whenever revising the manpage. +.SH NAME +captree \- display process tree capabilities +.SH SYNOPSIS +.BR captree " [optional args] " +.IR [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. +.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. +.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 diff --git a/goapps/captree/captree.go b/goapps/captree/captree.go index aa94cd3..4c7a586 100644 --- a/goapps/captree/captree.go +++ b/goapps/captree/captree.go @@ -58,6 +58,12 @@ // // $ captree 0 // +// To view a specific binary (as named in /proc//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 ( @@ -65,6 +71,7 @@ import ( "fmt" "io/ioutil" "log" + "path/filepath" "sort" "strconv" "strings" @@ -74,7 +81,9 @@ import ( ) var ( - proc = flag.String("proc", "/proc", "root of proc filesystem") + 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") ) type task struct { @@ -168,7 +177,7 @@ var empty = cap.NewSet() var noiab = cap.IABInit() // rDump prints out the tree of processes rooted at pid. -func rDump(pids map[string]*task, pid, stub, lstub, estub string) { +func rDump(pids map[string]*task, pid, stub, lstub, estub string, depth int) { info, ok := pids[pid] if !ok { fmt.Println("[PID:", pid, "not found]") @@ -177,14 +186,14 @@ func rDump(pids map[string]*task, pid, stub, lstub, estub string) { c := "" set := info.cap if set != nil { - if val, _ := set.Cf(empty); val != 0 { + 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) { + if val, _ := tup.Cf(noiab); val.Has(cap.Bound) || val.Has(cap.Amb) || *verbose { iab = fmt.Sprintf(" [%s]", tup) } } @@ -233,20 +242,26 @@ func rDump(pids map[string]*task, pid, stub, lstub, estub string) { c := "" set := this.cap if set != nil { - if val, _ := set.Cf(empty); val != 0 { + 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) { + 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]) @@ -260,10 +275,34 @@ func rDump(pids map[string]*task, pid, stub, lstub, estub string) { if i+1 == len(x) { estub = " " } - rDump(pids, cid, stub, lstub, estub) + rDump(pids, 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 + } + // TODO if no processes found, should we search the + // threads? + fmt.Printf("no process matched %q\n", glob) + }() + return finds +} + func main() { flag.Parse() @@ -318,6 +357,12 @@ func main() { } for _, pid := range args { - rDump(pids, pid, "", "--", " ") + if _, err := strconv.ParseUint(pid, 10, 64); err == nil { + rDump(pids, pid, "", "--", " ", *depth) + continue + } + for pid := range findPIDs(list, pids, pid) { + rDump(pids, pid, "", "--", " ", *depth) + } } } -- cgit v1.2.3 From b9d56654dee6c8998fa477ffb20e8a5d01044f96 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 29 Aug 2021 15:36:06 -0700 Subject: Update man pages. Some fixes, some more efficient URLs, some more coherrent cross-references. Signed-off-by: Andrew G. Morgan --- doc/cap_iab.3 | 27 +++++++++++++++------------ doc/capsh.1 | 16 +++++++++------- doc/getcap.8 | 12 ++++++++---- doc/getpcaps.8 | 17 ++++++++++++----- doc/setcap.8 | 8 ++++++-- 5 files changed, 50 insertions(+), 30 deletions(-) diff --git a/doc/cap_iab.3 b/doc/cap_iab.3 index 7e87a0f..ebcc87f 100644 --- a/doc/cap_iab.3 +++ b/doc/cap_iab.3 @@ -1,4 +1,4 @@ -.TH CAP_IAB 3 "2021-03-10" "" "Linux Programmer's Manual" +.TH CAP_IAB 3 "2021-08-29" "" "Linux Programmer's Manual" .SH NAME .nf #include @@ -60,8 +60,8 @@ from the handshake inheritance between pre-exec* process and file-capability bestowed executable of the traditional capability mechanism. .PP -The convolution rules for IAB style inheritance are: I'=I; A'= A & ~B; -P'=A & ~B. Where P etc are the pre-exec values and P' etc are the +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 @@ -87,8 +87,8 @@ cap_iab_t should be freed with .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 @@ -103,12 +103,12 @@ previous function). The returned IAB set should be freed with 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 @@ -172,7 +172,10 @@ work. Instead the \fIpam_cap.so\fP config syntax was generalized into a whole set of libcap functions for bundling together all three naively inheritable capabilities: the IAB set. The support for this debuted in libcap-2.33. - +.SH "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/capsh.1 b/doc/capsh.1 index 9bed928..87ce06e 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-08-29" "libcap" "User Commands" .SH NAME capsh \- capability shell wrapper .SH SYNOPSIS @@ -282,9 +282,9 @@ raised. .BI \-\-has\-b= xxx Exit with status 1 unless the .I bounding -set vector has capability +vector has capability .B xxx -enabled. +in its (default) non-blocked state. .TP .BI \-\-iab= xxx Attempts to set the IAB tuple of inheritable capability vectors. @@ -311,12 +311,14 @@ Written by Andrew G. Morgan . .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/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 -.TH GETCAP 8 "2020-01-07" +.\" originally written by Andrew Main +.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 3926a8c..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 @@ -36,15 +36,22 @@ default terminal fonts. .TP .B \-\-iab Displays IAB tuple capabilities from the process. The output format -here is unique. Double quotes encase the regular process capabilities -and square brackets encase the IAB tuple. +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 cap_iab (3). +.BR setcap (8). .SH AUTHOR This manual page was originally written by Robert Bihlmeyer , for the Debian GNU/Linux system (but may be used 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). -- cgit v1.2.3 From 2bfe36c9999c852c79c0bba1c31b25285326b91d Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 29 Aug 2021 18:54:03 -0700 Subject: Up the release version to 2.55 Signed-off-by: Andrew G. Morgan --- Make.Rules | 2 +- cap/go.mod | 2 +- contrib/seccomp/go.mod | 2 +- go/go.mod | 4 ++-- goapps/captree/go.mod | 2 +- goapps/gowns/go.mod | 2 +- goapps/setid/go.mod | 4 ++-- goapps/web/go.mod | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Make.Rules b/Make.Rules index ae9d42d..66207b4 100644 --- a/Make.Rules +++ b/Make.Rules @@ -1,7 +1,7 @@ # Common version number defines for libcap LIBTITLE=libcap VERSION=2 -MINOR=54 +MINOR=55 # ## Optional prefixes: diff --git a/cap/go.mod b/cap/go.mod index cf37bcb..4fc795e 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.54 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.55 diff --git a/contrib/seccomp/go.mod b/contrib/seccomp/go.mod index 0e08db1..bd900ea 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.54 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.55 diff --git a/go/go.mod b/go/go.mod index 95be67b..58ac777 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.54 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.54 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.55 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.55 ) diff --git a/goapps/captree/go.mod b/goapps/captree/go.mod index 52aada9..e93b5f2 100644 --- a/goapps/captree/go.mod +++ b/goapps/captree/go.mod @@ -2,4 +2,4 @@ module captree go 1.16 -require kernel.org/pub/linux/libs/security/libcap/cap v1.2.54 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.55 diff --git a/goapps/gowns/go.mod b/goapps/gowns/go.mod index d845031..3cd6596 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.54 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.55 diff --git a/goapps/setid/go.mod b/goapps/setid/go.mod index cad32f2..bf24c47 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.54 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.54 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.55 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.55 ) diff --git a/goapps/web/go.mod b/goapps/web/go.mod index 2c8326c..58c008e 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.54 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.55 -- cgit v1.2.3 From f5ae31ebd3d49a064cb85ccd62a75198883cec3b Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Mon, 30 Aug 2021 20:01:13 -0700 Subject: Be more consistent with make variables. Noticed that we weren't applying the same amount of flag discipline to local BUILD_* tool rules. Fixing that, I see we've been carrying a source code issue in libcap/_makenames.c for a while. (FIXED). Signed-off-by: Andrew G. Morgan --- Make.Rules | 38 ++++++++++++++++++++------------------ go/Makefile | 8 ++++---- libcap/_makenames.c | 2 +- progs/Makefile | 4 ++-- 4 files changed, 27 insertions(+), 25 deletions(-) diff --git a/Make.Rules b/Make.Rules index 66207b4..4ce2285 100644 --- a/Make.Rules +++ b/Make.Rules @@ -63,39 +63,41 @@ GOMAJOR=1 KERNEL_HEADERS := $(topdir)/libcap/include/uapi LIBCAP_INCLUDES = -I$(KERNEL_HEADERS) -I$(topdir)/libcap/include DEFINES := -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 +SYSTEM_HEADERS = /usr/include -CC := $(CROSS_COMPILE)gcc -LD=$(CC) -Wl,-x -shared SUDO := sudo +CC := $(CROSS_COMPILE)gcc +LD := $(CC) -Wl,-x -shared +AR := $(CROSS_COMPILE)ar +RANLIB := $(CROSS_COMPILE)ranlib +OBJCOPY := $(CROSS_COMPILE)objcopy + +# Reference: +# CPPFLAGS used for building .o files from .c & .h files +# CFLAGS used when building libraries from .o, .c and .h files + COPTS ?= -O2 -CFLAGS ?= $(COPTS) $(DEFINES) +CFLAGS ?= $(COPTS) +CPPFLAGS += -Dlinux $(WARNINGS) $(DEBUG) $(DEFINES) $(LIBCAP_INCLUDES) LDFLAGS ?= #-g -CPPFLAGS += $(LIBCAP_INCLUDES) +DEBUG = -g #-DDEBUG +WARNINGS=-Wall -Wwrite-strings -Wpointer-arith -Wcast-qual -Wcast-align \ + -Wstrict-prototypes -Wmissing-prototypes -Wnested-externs \ + -Winline -Wshadow BUILD_CC ?= $(CC) BUILD_LD ?= $(BUILD_CC) -Wl,-x -shared BUILD_COPTS ?= $(COPTS) -BUILD_CFLAGS ?= $(BUILD_COPTS) $(DEFINES) +BUILD_CFLAGS ?= $(BUILD_COPTS) +BUILD_CPPFLAGS += -Dlinux $(WARNINGS) $(DEBUG) $(DEFINES) $(LIBCAP_INCLUDES) BUILD_LDFLAGS ?= $(LDFLAGS) -BUILD_CPPFLAGS += $(LIBCAP_INCLUDES) +BUILD_GPERF := $(shell which gperf >/dev/null 2>/dev/null && echo yes) -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 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, diff --git a/go/Makefile b/go/Makefile index 854f0e3..5af5321 100644 --- a/go/Makefile +++ b/go/Makefile @@ -18,10 +18,10 @@ DEPS=../libcap/libcap.a ../libcap/libpsx.a all: PSXGOPACKAGE CAPGOPACKAGE web setid gowns captree compare-cap try-launching psx-signals $(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: mkdir -p "vendor/$(IMPORTDIR)" @@ -39,7 +39,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 $@ @@ -59,7 +59,7 @@ compare-cap: compare-cap.go CAPGOPACKAGE web: ../goapps/web/web.go CAPGOPACKAGE CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) build -mod=vendor -o $@ $< ifeq ($(RAISE_GO_FILECAP),yes) - make -C ../progs setcap + $(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 diff --git a/libcap/_makenames.c b/libcap/_makenames.c index c0d6db4..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); diff --git a/progs/Makefile b/progs/Makefile index 51e9a63..0917dd3 100644 --- a/progs/Makefile +++ b/progs/Makefile @@ -22,10 +22,10 @@ DEPS = ../libcap/libcap.a 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) $(LDFLAGS) -- cgit v1.2.3 From 264b784089bfb56b0039c1ebfc5e92912f0284ce Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Tue, 31 Aug 2021 06:57:54 -0700 Subject: Resurrect the $(WARNINGS) for the target build Signed-off-by: Andrew G. Morgan --- Make.Rules | 10 +++++----- contrib/Makefile | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Make.Rules b/Make.Rules index 4ce2285..9c33d75 100644 --- a/Make.Rules +++ b/Make.Rules @@ -76,14 +76,14 @@ OBJCOPY := $(CROSS_COMPILE)objcopy # CPPFLAGS used for building .o files from .c & .h files # CFLAGS used when building libraries from .o, .c and .h files -COPTS ?= -O2 -CFLAGS ?= $(COPTS) -CPPFLAGS += -Dlinux $(WARNINGS) $(DEBUG) $(DEFINES) $(LIBCAP_INCLUDES) -LDFLAGS ?= #-g -DEBUG = -g #-DDEBUG +DEBUG = # -g -DDEBUG WARNINGS=-Wall -Wwrite-strings -Wpointer-arith -Wcast-qual -Wcast-align \ -Wstrict-prototypes -Wmissing-prototypes -Wnested-externs \ -Winline -Wshadow +COPTS ?= -O2 +CFLAGS ?= $(COPTS) $(WARNINGS) $(DEBUG) +CPPFLAGS += -Dlinux $(DEFINES) $(LIBCAP_INCLUDES) +LDFLAGS ?= # -g BUILD_CC ?= $(CC) BUILD_LD ?= $(BUILD_CC) -Wl,-x -shared 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 -- cgit v1.2.3 From 15a3d49bf12b24c0a353525acb93e188f67e7581 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Tue, 31 Aug 2021 19:05:59 -0700 Subject: Move $(LDFLAGS) earlier in build command lines. As explained (thanks David Seifert) there are some LDFLAGS that need to precede actual linked libraries. For example, -Wl,--as-needed. Given this, I've tried it and it appears to work for the default build cases as captured in 'make distcheck'. Signed-off-by: Andrew G. Morgan --- pam_cap/Makefile | 8 ++++---- progs/Makefile | 6 ++++-- tests/Makefile | 14 +++++++------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/pam_cap/Makefile b/pam_cap/Makefile index d5da6be..09083ea 100644 --- a/pam_cap/Makefile +++ b/pam_cap/Makefile @@ -20,7 +20,7 @@ execable.o: execable.c ../libcap/execable.h ../libcap/loader.txt $(CC) $(CFLAGS) $(CPPFLAGS) -DLIBCAP_VERSION=\"libcap-$(VERSION).$(MINOR)\" -DSHARED_LOADER=\"$(shell cat ../libcap/loader.txt)\" -c execable.c -o $@ pam_cap.so: pam_cap.o execable.o pam_cap_linkopts - cat pam_cap_linkopts | xargs -e $(LD) -o $@ pam_cap.o execable.o $(LIBCAPLIB) $(LDFLAGS) + 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 @@ -51,7 +51,7 @@ pam_cap_linkopts: lazylink.so ./lazylink.so || echo "-lpam" >> $@ lazylink.so: lazylink.c ../libcap/execable.h ../libcap/loader.txt - $(LD) -o $@ $(CFLAGS) $(CPPFLAGS) lazylink.c -DSHARED_LOADER=\"$(shell cat ../libcap/loader.txt)\" $(LDFLAGS) -Wl,-e,__so_start + $(LD) -o $@ $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) lazylink.c -DSHARED_LOADER=\"$(shell cat ../libcap/loader.txt)\" -Wl,-e,__so_start endif endif @@ -62,10 +62,10 @@ pam_cap.o: pam_cap.c $(MAKE) -C ../libcap libcap.a test_pam_cap: test_pam_cap.c pam_cap.c ../libcap/libcap.a - $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ test_pam_cap.c $(LIBCAPLIB) $(LDFLAGS) --static + $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ test_pam_cap.c $(LIBCAPLIB) --static testlink: test.c pam_cap.o - $(CC) $(CFLAGS) -o $@ $+ -lpam -ldl $(LIBCAPLIB) $(LDFLAGS) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $+ -lpam -ldl $(LIBCAPLIB) test: testlink test_pam_cap pam_cap.so $(MAKE) testlink diff --git a/progs/Makefile b/progs/Makefile index 0917dd3..2f887c8 100644 --- a/progs/Makefile +++ b/progs/Makefile @@ -28,7 +28,7 @@ endif $(MAKE) -C ../libcap libcap.so $(BUILD): %: %.o $(DEPS) - $(CC) $(CFLAGS) -o $@ $< $(LIBCAPLIB) $(LDFLAGS) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIBCAPLIB) %.o: %.c $(INCS) $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@ @@ -49,8 +49,10 @@ capshdoc.h.cf: capshdoc.h ./mkcapshdoc.sh diff -u capshdoc.h $@ || (rm $@ ; exit 1) capsh: capsh.c capshdoc.h.cf $(DEPS) - $(CC) $(CFLAGS) $(CPPFLAGS) $(CAPSH_SHELL) -o $@ $< $(LIBCAPLIB) $(LDFLAGS) + $(CC) $(CFLAGS) $(CPPFLAGS) $(CAPSH_SHELL) $(LDFLAGS) -o $@ $< $(LIBCAPLIB) +# Statically linked with minimal linkage flags to enable running in a +# chroot and in other in-tree testing contexts. tcapsh-static: capsh.c capshdoc.h.cf $(DEPS) $(CC) $(CFLAGS) $(CPPFLAGS) $(CAPSH_SHELL) -o $@ $< $(LIBCAPLIB) --static diff --git a/tests/Makefile b/tests/Makefile index d9ed248..770cffa 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -66,17 +66,17 @@ run_psx_test: psx_test ./psx_test psx_test: psx_test.c $(DEPS) - $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LDFLAGS) + $(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) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) $(LDFLAGS) + $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) # privileged uns_test: uns_test.c $(DEPS) - $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LDFLAGS) + $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) run_uns_test: uns_test echo exit | $(SUDO) ./uns_test @@ -88,13 +88,13 @@ run_libcap_psx_launch_test: libcap_psx_launch_test ../progs/tcapsh-static $(SUDO) ./libcap_psx_launch_test libcap_launch_test: libcap_launch_test.c $(DEPS) - $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LDFLAGS) + $(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) $(CPPFLAGS) -DWITH_PTHREADS $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) $(LDFLAGS) + $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -DWITH_PTHREADS $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) # This test demonstrates that libpsx is needed to secure multithreaded @@ -109,12 +109,12 @@ exploit.o: exploit.c $(CC) $(CFLAGS) $(CPPFLAGS) -c $< exploit: exploit.o $(DEPS) - $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) -lpthread $(LDFLAGS) + $(CC) $(CFLAGS) $(CPPFLAGS) $(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) $(CPPFLAGS) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LIBCAPLIB) $(LDFLAGS) + $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LIBCAPLIB) # This one runs in a chroot with no shared library files. noop: noop.c -- cgit v1.2.3 From 2d776b10dc9f4b33ec3778f6d4fddc51f9b9dcde Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Wed, 1 Sep 2021 06:52:31 -0700 Subject: Permit root to run test_pam_cap without arguments. This fixes a bug preventing 'make test' from working when invoked by root. Bug reported by David Seifert: https://bugzilla.kernel.org/show_bug.cgi?id=214257 Signed-off-by: Andrew G. Morgan --- pam_cap/test_pam_cap.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pam_cap/test_pam_cap.c b/pam_cap/test_pam_cap.c index 4c09a5d..4c67cad 100644 --- a/pam_cap/test_pam_cap.c +++ b/pam_cap/test_pam_cap.c @@ -223,7 +223,8 @@ int main(int argc, char *argv[]) { exit(1); } if (read_capabilities_for_user("morgan", "/dev/null") != NULL) { - printf("/dev/null is not a valid config file\n"); + printf("/dev/null should return no capabilities\n"); + exit(1); } /* @@ -238,6 +239,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); -- cgit v1.2.3 From 41f065cdc95f8bbe79ccba94cff20cd5434f7d2a Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Wed, 1 Sep 2021 07:25:18 -0700 Subject: cap_iab.3 doc fixes and cleanup Signed-off-by: Andrew G. Morgan --- doc/cap_iab.3 | 65 ++++++++++++++++++++++++++++------------------------------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/doc/cap_iab.3 b/doc/cap_iab.3 index ebcc87f..a0ec988 100644 --- a/doc/cap_iab.3 +++ b/doc/cap_iab.3 @@ -1,4 +1,4 @@ -.TH CAP_IAB 3 "2021-08-29" "" "Linux Programmer's Manual" +.TH CAP_IAB 3 "2021-09-01" "" "Linux Programmer's Manual" .SH NAME .nf #include @@ -36,29 +36,26 @@ 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 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&I; P'=A&I&P. Where P etc are the pre-exec values and P' etc are the @@ -66,12 +63,13 @@ 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_get_proc () @@ -91,13 +89,13 @@ 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 @@ -118,19 +116,18 @@ 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 -.B CAP_IAB_DIFFERS -.RI ( status ", " vector ) +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. .SH "ERRORS" @@ -143,10 +140,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 @@ -160,17 +157,17 @@ 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: -- cgit v1.2.3 From 2762c2c1a8c98d9012fcd40f20d133493a0b3219 Mon Sep 17 00:00:00 2001 From: David Seifert Date: Wed, 1 Sep 2021 16:53:17 +0200 Subject: Canonicalize build system * Respect user's CFLAGS/CPPFLAGS/LDFLAGS * Respect $(MAKE) * Remove CPPFLAGS from link rules Note: for in-tree built test binaries, where we build --static, we do not apply LDFLAGS: we want to limit external dependencies in general; and users' LDFLAGS have a strong tendency to conflict with --static for linking. Work in collaboration with David Seifert (ie, he wrote most of it). Signed-off-by: Andrew G. Morgan --- contrib/bug400591/Makefile | 4 ++-- contrib/sucap/Makefile | 2 +- kdebug/Makefile | 2 +- libcap/Makefile | 5 +---- pam_cap/Makefile | 10 ++++------ tests/Makefile | 9 +++------ 6 files changed, 12 insertions(+), 20 deletions(-) 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/sucap/Makefile b/contrib/sucap/Makefile index 91947af..8cd4cef 100644 --- a/contrib/sucap/Makefile +++ b/contrib/sucap/Makefile @@ -1,7 +1,7 @@ all: su su: su.c - $(CC) -DPAM_APP_NAME=\"sucap\" -o $@ $< -lpam -lpam_misc -lcap + $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -DPAM_APP_NAME=\"sucap\" -o $@ $< -lpam -lpam_misc -lcap # to permit all ambient capabilities, this needs all permitted. sudo setcap =p ./su 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/libcap/Makefile b/libcap/Makefile index 399aa66..4b96a0c 100644 --- a/libcap/Makefile +++ b/libcap/Makefile @@ -104,9 +104,6 @@ $(STAPSXLIBNAME): $(PSXOBJS) include/sys/psx_syscall.h ifeq ($(SHARED),yes) -empty: empty.c - $(CC) -o $@ $< - loader.txt: empty $(OBJCOPY) --dump-section .interp=$@ $< /dev/null @@ -134,7 +131,7 @@ cap_text.o: cap_text.c $(USE_GPERF_OUTPUT) $(INCLS) $(CC) $(CFLAGS) $(CPPFLAGS) $(INCLUDE_GPERF_OUTPUT) -c $< -o $@ cap_test: cap_test.c libcap.h $(CAPOBJS) - $(CC) $(CFLAGS) $(CPPFLAGS) $< $(CAPOBJS) -o $@ + $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $< $(CAPOBJS) -o $@ libcapsotest: $(CAPLIBNAME) ./$(CAPLIBNAME) diff --git a/pam_cap/Makefile b/pam_cap/Makefile index 09083ea..b245307 100644 --- a/pam_cap/Makefile +++ b/pam_cap/Makefile @@ -55,20 +55,18 @@ lazylink.so: lazylink.c ../libcap/execable.h ../libcap/loader.txt endif endif -pam_cap.o: pam_cap.c - $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@ - ../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) $(CPPFLAGS) $(LDFLAGS) -o $@ test_pam_cap.c $(LIBCAPLIB) --static + $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ test_pam_cap.c $(LIBCAPLIB) --static -testlink: test.c pam_cap.o +testlink: test.o pam_cap.o $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $+ -lpam -ldl $(LIBCAPLIB) test: testlink test_pam_cap pam_cap.so - $(MAKE) testlink ./test_pam_cap LD_LIBRARY_PATH=../libcap ./pam_cap.so LD_LIBRARY_PATH=../libcap ./pam_cap.so --help diff --git a/tests/Makefile b/tests/Makefile index 770cffa..dd78432 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -105,20 +105,17 @@ run_exploit_test: exploit noexploit @echo exploit should fail $(SUDO) ./noexploit ; if [ $$? -eq 0 ]; then exit 0; else exit 1 ; fi -exploit.o: exploit.c - $(CC) $(CFLAGS) $(CPPFLAGS) -c $< - exploit: exploit.o $(DEPS) - $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) -lpthread + $(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) $(CPPFLAGS) $(LDFLAGS) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LIBCAPLIB) + $(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 *~ -- cgit v1.2.3 From 0cba26fca376c34fa715b31b915ea0adee5d77ce Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Wed, 1 Sep 2021 20:08:14 -0700 Subject: sucap/su should start with an empty INHERITABLE flag. Signed-off-by: Andrew G. Morgan --- contrib/sucap/su.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/contrib/sucap/su.c b/contrib/sucap/su.c index 5c98e5f..d72a69a 100644 --- a/contrib/sucap/su.c +++ b/contrib/sucap/su.c @@ -1352,7 +1352,7 @@ static int perform_launch_and_cleanup(cap_t all, int is_login, 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 +1387,16 @@ 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; + } + if (cap_set_proc(all) != 0) { + D(("failed to restore process capabilities")); + return PAM_SYSTEM_ERR; + } + launcher = cap_new_launcher(shell_args[0], (const char * const *) &shell_args[1], (const char * const *) shell_env); @@ -1394,7 +1404,7 @@ 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_set_iab(launcher, iab); cap_launcher_callback(launcher, launch_callback_fn); child = cap_launch(launcher, pamh); @@ -1446,6 +1456,7 @@ int main(int argc, char *argv[]) all = cap_get_proc(); cap_fill(all, CAP_EFFECTIVE, CAP_PERMITTED); + cap_clear_flag(all, CAP_INHERITABLE); checkfds(); @@ -1553,7 +1564,7 @@ int main(int argc, char *argv[]) goto utmp_closer; } - status = perform_launch_and_cleanup(t_caps, is_login, shell, command); + status = perform_launch_and_cleanup(all, is_login, shell, command); close_session(pamh, all); utmp_closer: -- cgit v1.2.3 From 142ad758ffaaec1b4c4e5e7c61287a943f21ff7b Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Wed, 1 Sep 2021 20:22:45 -0700 Subject: Don't display duplicate subtrees with captree. This addresses issue (1) of: https://bugzilla.kernel.org/show_bug.cgi?id=214269 Signed-off-by: Andrew G. Morgan --- goapps/captree/captree.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/goapps/captree/captree.go b/goapps/captree/captree.go index 4c7a586..f6bdaa4 100644 --- a/goapps/captree/captree.go +++ b/goapps/captree/captree.go @@ -88,6 +88,7 @@ var ( type task struct { mu sync.Mutex + viewed bool pid string cmd string cap *cap.Set @@ -183,6 +184,13 @@ func rDump(pids map[string]*task, pid, stub, lstub, estub string, depth int) { fmt.Println("[PID:", pid, "not found]") 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 { -- cgit v1.2.3 From 2a3984fe0fc12880e15760b35733db6031b0a652 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Wed, 1 Sep 2021 22:34:47 -0700 Subject: Avoid disecting sub-trees of processes in captree output. Added --color as an argument to make it easier to spot what you are looking for in the output. This addresses item (2) of: https://bugzilla.kernel.org/show_bug.cgi?id=214269 Signed-off-by: Andrew G. Morgan --- goapps/captree/captree.go | 96 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 84 insertions(+), 12 deletions(-) diff --git a/goapps/captree/captree.go b/goapps/captree/captree.go index f6bdaa4..189becc 100644 --- a/goapps/captree/captree.go +++ b/goapps/captree/captree.go @@ -84,11 +84,13 @@ 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", false, "color the targeted process(es) red") ) type task struct { mu sync.Mutex viewed bool + depth int pid string cmd string cap *cap.Set @@ -107,6 +109,13 @@ var ( mu sync.Mutex ) +func highlight(text string) string { + if !*color { + return text + } + return fmt.Sprint("\033[31m", text, "\033[0m") +} + func (ts *task) fill(pid string, n int, thread bool) { defer wg.Done() wg.Add(1) @@ -178,10 +187,10 @@ var empty = cap.NewSet() var noiab = cap.IABInit() // rDump prints out the tree of processes rooted at pid. -func rDump(pids map[string]*task, pid, stub, lstub, estub string, depth int) { +func rDump(pids map[string]*task, requested map[string]bool, pid, stub, lstub, estub string, depth int) { info, ok := pids[pid] if !ok { - fmt.Println("[PID:", pid, "not found]") + panic("programming error") return } if info.viewed { @@ -226,12 +235,22 @@ func rDump(pids map[string]*task, pid, stub, lstub, estub string, depth int) { if len(same) != 0 { tids = fmt.Sprintf("+{%s}", strings.Join(same, ",")) } - fmt.Printf("%s%s%s(%s%s)%s%s\n", stub, lstub, info.cmd, pid, tids, c, iab) + 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 - same := []string{this.pid} + 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) @@ -245,7 +264,12 @@ func rDump(pids map[string]*task, pid, stub, lstub, estub string, depth int) { nmisc = append(nmisc, t) continue } - same = append(same, t.pid) + hPID = t.pid + if requested[t.pid] { + hPID = highlight(t.pid) + requested[t.pid] = false + } + same = append(same, hPID) } c := "" set := this.cap @@ -283,7 +307,7 @@ func rDump(pids map[string]*task, pid, stub, lstub, estub string, depth int) { if i+1 == len(x) { estub = " " } - rDump(pids, cid, stub, lstub, estub, depth) + rDump(pids, requested, cid, stub, lstub, estub, depth) } } @@ -304,13 +328,22 @@ func findPIDs(list []string, pids map[string]*task, glob string) <-chan string { if found { return } - // TODO if no processes found, should we search the - // threads? fmt.Printf("no process matched %q\n", glob) }() 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.Parse() @@ -345,6 +378,7 @@ func main() { var list []string for pid, ts := range pids { + setDepth(pids, pid) list = append(list, pid) if pid == "0" { continue @@ -353,9 +387,15 @@ func main() { pts.children = append(pts.children, pid) } } + sort.Slice(list, func(i, j int) bool { - a, _ := strconv.Atoi(list[i]) - b, _ := strconv.Atoi(list[j]) + x, y := pids[list[i]], pids[list[j]] + if x.depth > y.depth { + return false + } + // Break tie with numerical order + a, _ := strconv.Atoi(x.pid) + b, _ := strconv.Atoi(y.pid) return a < b }) @@ -364,13 +404,45 @@ func main() { 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 { - rDump(pids, pid, "", "--", " ", *depth) + 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) { - rDump(pids, pid, "", "--", " ", *depth) + 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]") } } } -- cgit v1.2.3 From f245f03f430482d972b5934d66bd4a31616f95ad Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Thu, 2 Sep 2021 18:25:55 -0700 Subject: Make captree use --color on any terminal by default. Disable with --colo[u]r=false or pipe into something else. Ex. 'captree | cat' Signed-off-by: Andrew G. Morgan --- goapps/captree/captree.go | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/goapps/captree/captree.go b/goapps/captree/captree.go index 189becc..4fb023d 100644 --- a/goapps/captree/captree.go +++ b/goapps/captree/captree.go @@ -71,6 +71,7 @@ import ( "fmt" "io/ioutil" "log" + "os" "path/filepath" "sort" "strconv" @@ -84,7 +85,8 @@ 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", false, "color the targeted process(es) red") + 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 { @@ -105,15 +107,24 @@ func (ts *task) String() string { } var ( - wg sync.WaitGroup - mu sync.Mutex + 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 !*color { - return text + if colored { + return fmt.Sprint("\033[31m", text, "\033[0m") } - return fmt.Sprint("\033[31m", text, "\033[0m") + return text } func (ts *task) fill(pid string, n int, thread bool) { @@ -347,6 +358,9 @@ func setDepth(pids map[string]*task, pid string) int { func main() { 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) @@ -356,11 +370,11 @@ func main() { cmd: "", } + // 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) @@ -388,15 +402,16 @@ func main() { } } + // Sort the proccess 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 { - return false + if x.depth == y.depth { + a, _ := strconv.Atoi(x.pid) + b, _ := strconv.Atoi(y.pid) + return a < b } - // Break tie with numerical order - a, _ := strconv.Atoi(x.pid) - b, _ := strconv.Atoi(y.pid) - return a < b + return x.depth < y.depth }) args := flag.Args() -- cgit v1.2.3 From c54a087429f96d0ab4561d1aa3bb55a802c226a2 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Thu, 2 Sep 2021 20:02:29 -0700 Subject: Document latest option for captree in its man page. Signed-off-by: Andrew G. Morgan --- doc/captree.8 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/captree.8 b/doc/captree.8 index 700610f..d2c0003 100644 --- a/doc/captree.8 +++ b/doc/captree.8 @@ -1,5 +1,5 @@ .\" Hey, EMACS: -*- nroff -*- -.TH CAPTREE 8 "2021-08-29" +.TH CAPTREE 8 "2021-09-02" .\" Please adjust this date whenever revising the manpage. .SH NAME captree \- display process tree capabilities @@ -45,6 +45,13 @@ 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 REPORTING BUGS Please report bugs via: .TP -- cgit v1.2.3 From 0f286b584ebe7019ba33cc48ad9e4ad8efb5b23c Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Thu, 2 Sep 2021 20:03:32 -0700 Subject: Up the release version to 2.56 Signed-off-by: Andrew G. Morgan --- Make.Rules | 2 +- cap/go.mod | 2 +- contrib/seccomp/go.mod | 2 +- go/go.mod | 4 ++-- goapps/captree/go.mod | 2 +- goapps/gowns/go.mod | 2 +- goapps/setid/go.mod | 4 ++-- goapps/web/go.mod | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Make.Rules b/Make.Rules index 9c33d75..3eeb098 100644 --- a/Make.Rules +++ b/Make.Rules @@ -1,7 +1,7 @@ # Common version number defines for libcap LIBTITLE=libcap VERSION=2 -MINOR=55 +MINOR=56 # ## Optional prefixes: diff --git a/cap/go.mod b/cap/go.mod index 4fc795e..8c0b39a 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.55 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.56 diff --git a/contrib/seccomp/go.mod b/contrib/seccomp/go.mod index bd900ea..fd51170 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.55 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.56 diff --git a/go/go.mod b/go/go.mod index 58ac777..9bfa615 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.55 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.55 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.56 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.56 ) diff --git a/goapps/captree/go.mod b/goapps/captree/go.mod index e93b5f2..3671013 100644 --- a/goapps/captree/go.mod +++ b/goapps/captree/go.mod @@ -2,4 +2,4 @@ module captree go 1.16 -require kernel.org/pub/linux/libs/security/libcap/cap v1.2.55 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.56 diff --git a/goapps/gowns/go.mod b/goapps/gowns/go.mod index 3cd6596..8c31384 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.55 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.56 diff --git a/goapps/setid/go.mod b/goapps/setid/go.mod index bf24c47..ca368f4 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.55 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.55 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.56 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.56 ) diff --git a/goapps/web/go.mod b/goapps/web/go.mod index 58c008e..bd08d25 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.55 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.56 -- cgit v1.2.3 From 3249c2696468bc82ec45f4fcabddc390473a45da Mon Sep 17 00:00:00 2001 From: David Seifert Date: Sat, 4 Sep 2021 10:39:34 +0200 Subject: Build system fixes Summary: - Always keep $(WARNINGS) when overriding CFLAGS Signed-off-by: Andrew G. Morgan --- Make.Rules | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Make.Rules b/Make.Rules index 3eeb098..00f2a03 100644 --- a/Make.Rules +++ b/Make.Rules @@ -78,10 +78,11 @@ OBJCOPY := $(CROSS_COMPILE)objcopy DEBUG = # -g -DDEBUG WARNINGS=-Wall -Wwrite-strings -Wpointer-arith -Wcast-qual -Wcast-align \ - -Wstrict-prototypes -Wmissing-prototypes -Wnested-externs \ + -Wstrict-prototypes -Wmissing-prototypes -Wnested-externs \ -Winline -Wshadow COPTS ?= -O2 -CFLAGS ?= $(COPTS) $(WARNINGS) $(DEBUG) +CFLAGS ?= $(COPTS) $(DEBUG) +CFLAGS += $(WARNINGS) CPPFLAGS += -Dlinux $(DEFINES) $(LIBCAP_INCLUDES) LDFLAGS ?= # -g -- cgit v1.2.3 From 99799844ad9272d43892881d1090369e6032aec2 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 4 Sep 2021 11:42:24 -0700 Subject: Don't build the tests/binaries until we want to run them Signed-off-by: Andrew G. Morgan --- tests/Makefile | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/Makefile b/tests/Makefile index dd78432..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 -- cgit v1.2.3 From 5ef14d07420c299ecd8de96928a5d1ce1e232df3 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 4 Sep 2021 14:05:11 -0700 Subject: Refactor top level Makefile to reduce redundant building Make build a bit quicker for folk that don't want to run tests. Signed-off-by: Andrew G. Morgan --- Makefile | 28 +++++----------------------- doc/Makefile | 6 ++++++ go/Makefile | 5 +++-- libcap/Makefile | 3 +++ pam_cap/Makefile | 2 +- progs/Makefile | 5 +++-- 6 files changed, 21 insertions(+), 28 deletions(-) diff --git a/Makefile b/Makefile index 1c195dd..9ee11c8 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: @@ -41,30 +44,9 @@ 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.sh $(MAKE) DYNAMIC=no COPTS="-D_FORTIFY_SOURCE=2 -O1 -g" clean test diff --git a/doc/Makefile b/doc/Makefile index e2802dc..9614180 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -32,6 +32,12 @@ MANS = $(MAN1S) $(MAN3S) $(MAN8S) all: $(MANS) +test: + @echo no doc tests available + +sudotest: + @echo no doc sudotests available + .PHONY: html html: mkdir -p html diff --git a/go/Makefile b/go/Makefile index 5af5321..2b2061b 100644 --- a/go/Makefile +++ b/go/Makefile @@ -14,8 +14,9 @@ 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 -all: PSXGOPACKAGE CAPGOPACKAGE web setid gowns captree compare-cap try-launching psx-signals +all: PSXGOPACKAGE CAPGOPACKAGE web setid gowns captree $(DEPS): $(MAKE) -C ../libcap all @@ -93,7 +94,7 @@ 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 $< -test: all +test: all $(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 diff --git a/libcap/Makefile b/libcap/Makefile index 4b96a0c..84340f2 100644 --- a/libcap/Makefile +++ b/libcap/Makefile @@ -148,6 +148,9 @@ ifeq ($(PTHREADS),yes) endif endif +sudotest: + @echo no sudotests for libcap + install: install-static ifeq ($(SHARED),yes) $(MAKE) install-shared diff --git a/pam_cap/Makefile b/pam_cap/Makefile index b245307..a000978 100644 --- a/pam_cap/Makefile +++ b/pam_cap/Makefile @@ -72,7 +72,7 @@ test: testlink test_pam_cap pam_cap.so LD_LIBRARY_PATH=../libcap ./pam_cap.so --help @echo "module can be run as an executable!" -sudotest: test test_pam_cap +sudotest: 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 diff --git a/progs/Makefile b/progs/Makefile index 2f887c8..e2bd7fe 100644 --- a/progs/Makefile +++ b/progs/Makefile @@ -42,7 +42,8 @@ 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 ./mkcapshdoc.sh > $@ @@ -60,7 +61,7 @@ uns_test: ../tests/uns_test.c $(MAKE) -C ../tests uns_test cp ../tests/uns_test . -sudotest: test tcapsh-static uns_test +sudotest: tcapsh-static uns_test $(SUDO) $(LDPATH) ./quicktest.sh clean: -- cgit v1.2.3 From 967b3a0a95223036478e7d991ee96fa42912803a Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 5 Sep 2021 11:42:28 -0700 Subject: Add --mode query support to capsh This addresses the feature request: https://bugzilla.kernel.org/show_bug.cgi?id=214319 Signed-off-by: Andrew G. Morgan --- doc/capsh.1 | 11 ++++++++--- progs/capsh.c | 57 +++++++++++++++++++++++++++++++++------------------------ 2 files changed, 41 insertions(+), 27 deletions(-) diff --git a/doc/capsh.1 b/doc/capsh.1 index 87ce06e..42637ab 100644 --- a/doc/capsh.1 +++ b/doc/capsh.1 @@ -91,9 +91,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= Force the program into a @@ -101,6 +102,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= . +.TP .BR \-\-inmode= Confirm that the prevailing mode is that specified in .IR , diff --git a/progs/capsh.c b/progs/capsh.c index 0cf8b1e..be86cd7 100644 --- a/progs/capsh.c +++ b/progs/capsh.c @@ -611,30 +611,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; @@ -1106,8 +1114,9 @@ int main(int argc, char *argv[], char *envp[]) " --keep= set keep-capability bit to \n" " --killit= send signal(n) to child\n" " --license display license info\n" - " --modes list libcap named capability modes\n" - " --mode= set capability mode to \n" + " --mode display current libcap mode\n" + " --mode= set libcap mode to \n" + " --modes list libcap named modes\n" " --no-new-privs set sticky process privilege limiter\n" " --noamb reset (drop) all ambient capabilities\n" " --print display capability relevant state\n" -- cgit v1.2.3 From f223de46154a3d2644914ea358d5c69b36770459 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 5 Sep 2021 19:45:05 -0700 Subject: Treat ENOTSUP as an expected error for getcap. Things like /proc/* files don't support capabilities on them and if getcap looks at them it generates a lot of errors. Treat it as equivalent to there being no capability on the file. This addresses https://bugzilla.kernel.org/show_bug.cgi?id=214317 Signed-off-by: Andrew G. Morgan --- progs/getcap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/progs/getcap.c b/progs/getcap.c index 7df7f0e..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) { -- cgit v1.2.3 From 8b3ffc23b6cbe42d2eac5a3c0d970fd26472a246 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 5 Sep 2021 19:50:07 -0700 Subject: Fixed parallel make issues Tried make -j12 and these fixes were needed. Signed-off-by: Andrew G. Morgan --- go/Makefile | 2 +- libcap/Makefile | 2 +- progs/Makefile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go/Makefile b/go/Makefile index 2b2061b..3f192cd 100644 --- a/go/Makefile +++ b/go/Makefile @@ -94,7 +94,7 @@ 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 $< -test: all $(TESTS) +test: setid gowns captree $(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 diff --git a/libcap/Makefile b/libcap/Makefile index 84340f2..7706063 100644 --- a/libcap/Makefile +++ b/libcap/Makefile @@ -130,7 +130,7 @@ endif cap_text.o: cap_text.c $(USE_GPERF_OUTPUT) $(INCLS) $(CC) $(CFLAGS) $(CPPFLAGS) $(INCLUDE_GPERF_OUTPUT) -c $< -o $@ -cap_test: cap_test.c libcap.h $(CAPOBJS) +cap_test: cap_test.c $(INCLS) $(CAPOBJS) $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $< $(CAPOBJS) -o $@ libcapsotest: $(CAPLIBNAME) diff --git a/progs/Makefile b/progs/Makefile index e2bd7fe..846bd76 100644 --- a/progs/Makefile +++ b/progs/Makefile @@ -61,7 +61,7 @@ uns_test: ../tests/uns_test.c $(MAKE) -C ../tests uns_test cp ../tests/uns_test . -sudotest: tcapsh-static uns_test +sudotest: tcapsh-static uns_test capsh setcap getcap tcapsh-static $(SUDO) $(LDPATH) ./quicktest.sh clean: -- cgit v1.2.3 From 8434c10a690f3352ff5d8cb011859502718a60b7 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Tue, 7 Sep 2021 10:47:45 -0700 Subject: Be more systematic about POSIX.1e value group names cap.Set's have Flag component Values cap.IAB's have Vector component Values Signed-off-by: Andrew G. Morgan --- cap/convenience.go | 10 +++++----- cap/file.go | 28 +++++++++++++++------------- cap/flags.go | 17 ++++++++--------- cap/names.go | 39 ++++++++++++++++++++++----------------- doc/values/8.txt | 39 ++++++++++++++++++++++----------------- progs/capshdoc.h | 39 ++++++++++++++++++++++----------------- 6 files changed, 94 insertions(+), 78 deletions(-) diff --git a/cap/convenience.go b/cap/convenience.go index c40b63d..e832981 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) @@ -234,11 +234,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..a309bd0 100644 --- a/cap/file.go +++ b/cap/file.go @@ -229,20 +229,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). @@ -272,7 +274,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 { diff --git a/cap/flags.go b/cap/flags.go index d46634d..310ac5d 100644 --- a/cap/flags.go +++ b/cap/flags.go @@ -2,8 +2,8 @@ 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 { // Checked this first, because otherwise we are sure @@ -96,10 +96,10 @@ func (c *Set) Fill(to, from Flag) error { // 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,7 +126,7 @@ 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 { return ErrBadSet @@ -143,8 +143,7 @@ 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) } diff --git a/cap/names.go b/cap/names.go index 8ee96d1..356da9e 100644 --- a/cap/names.go +++ b/cap/names.go @@ -70,24 +70,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. // - // [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.] SETPCAP // LINUX_IMMUTABLE allows a process to modify the S_IMMUTABLE and 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/progs/capshdoc.h b/progs/capshdoc.h index c182144..2ac6ecb 100644 --- a/progs/capshdoc.h +++ b/progs/capshdoc.h @@ -64,24 +64,29 @@ static const char *explanation7[] = { /* cap_setuid = 7 */ }; 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.", + "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.]", NULL }; static const char *explanation9[] = { /* cap_linux_immutable = 9 */ -- cgit v1.2.3 From b35370f7f65387c02c0542d6c36144ca0a0e5efd Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Tue, 7 Sep 2021 13:14:54 -0700 Subject: Implement --strict capsh argument. Up to this point, capsh hides some complexity concerning raising the CAP_SETPCAP in order to raise inheritable and drop bounding set values. This made it harder to explain some aspects of inheritance, and I ran into that detail writing this: https://sites.google.com/site/fullycapable/why-didnt-that-work#h.z7rwbcazhr4r Refactored capsh.c to clean up some buggy code, and also fix some documentation, including reference to the --strict argument. Signed-off-by: Andrew G. Morgan --- doc/capsh.1 | 27 ++++++++--- progs/capsh.c | 137 +++++++++++++++++++++-------------------------------- progs/quicktest.sh | 40 ++++++++++++++-- 3 files changed, 109 insertions(+), 95 deletions(-) diff --git a/doc/capsh.1 b/doc/capsh.1 index 42637ab..60d3ad2 100644 --- a/doc/capsh.1 +++ b/doc/capsh.1 @@ -176,7 +176,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 @@ -184,16 +184,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 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 @@ -228,6 +229,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. diff --git a/progs/capsh.c b/progs/capsh.c index be86cd7..efd1f56 100644 --- a/progs/capsh.c +++ b/progs/capsh.c @@ -191,29 +191,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) @@ -222,23 +235,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); } @@ -271,12 +285,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); } @@ -292,23 +307,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; @@ -320,7 +327,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; } @@ -338,22 +345,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); } @@ -444,6 +443,7 @@ int main(int argc, char *argv[], char *envp[]) { pid_t child; unsigned i; + int strict = 0; const char *shell = SHELL; child = 0; @@ -461,7 +461,7 @@ int main(int argc, char *argv[], char *envp[]) for (i=1; i 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= set uid to (hint: id )\n" diff --git a/progs/quicktest.sh b/progs/quicktest.sh index ebb7567..776b175 100755 --- a/progs/quicktest.sh +++ b/progs/quicktest.sh @@ -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,11 +223,24 @@ 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_setuid' @@ -250,7 +279,8 @@ 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" fi -- cgit v1.2.3 From 39067301976057bc8915e4025f6715432a5b0c74 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Thu, 9 Sep 2021 13:57:36 -0700 Subject: Up the release version to 2.57 Signed-off-by: Andrew G. Morgan --- Make.Rules | 2 +- cap/go.mod | 2 +- contrib/seccomp/go.mod | 2 +- go/go.mod | 4 ++-- goapps/captree/go.mod | 2 +- goapps/gowns/go.mod | 2 +- goapps/setid/go.mod | 4 ++-- goapps/web/go.mod | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Make.Rules b/Make.Rules index 00f2a03..cde6f16 100644 --- a/Make.Rules +++ b/Make.Rules @@ -1,7 +1,7 @@ # Common version number defines for libcap LIBTITLE=libcap VERSION=2 -MINOR=56 +MINOR=57 # ## Optional prefixes: diff --git a/cap/go.mod b/cap/go.mod index 8c0b39a..68e70a4 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.56 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.57 diff --git a/contrib/seccomp/go.mod b/contrib/seccomp/go.mod index fd51170..85b4da2 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.56 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.57 diff --git a/go/go.mod b/go/go.mod index 9bfa615..8178343 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.56 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.56 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.57 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.57 ) diff --git a/goapps/captree/go.mod b/goapps/captree/go.mod index 3671013..ea5aaff 100644 --- a/goapps/captree/go.mod +++ b/goapps/captree/go.mod @@ -2,4 +2,4 @@ module captree go 1.16 -require kernel.org/pub/linux/libs/security/libcap/cap v1.2.56 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.57 diff --git a/goapps/gowns/go.mod b/goapps/gowns/go.mod index 8c31384..fa32381 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.56 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.57 diff --git a/goapps/setid/go.mod b/goapps/setid/go.mod index ca368f4..7fa1773 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.56 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.56 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.57 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.57 ) diff --git a/goapps/web/go.mod b/goapps/web/go.mod index bd08d25..c76ecf4 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.56 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.57 -- cgit v1.2.3 From e7297c1925d827d3932dc7ed96554a1d94c17dd7 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Fri, 10 Sep 2021 16:02:28 -0700 Subject: More standard deprecation comment for cap.Compare and cap.IABInit Based on what I see on go.dev, there seems to be some preferred comment style for deprecating a function. Use it to help spread the word. Signed-off-by: Andrew G. Morgan --- cap/flags.go | 33 ++++++++++++++++++++++++++++----- cap/iab.go | 12 +++++++++++- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/cap/flags.go b/cap/flags.go index 310ac5d..88ad355 100644 --- a/cap/flags.go +++ b/cap/flags.go @@ -148,12 +148,35 @@ 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. +// Compare returns 0 if c and d are identical in content. // -// This function is deprecated in favor of (*Set).Cf(). +// 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 diff --git a/cap/iab.go b/cap/iab.go index 1be921c..90fc436 100644 --- a/cap/iab.go +++ b/cap/iab.go @@ -79,7 +79,17 @@ func NewIAB() *IAB { } } -// IABInit is a deprecated alias for the NewIAB function. +// IABInit allocates a new IAB tuple. +// +// Deprecated: Replace with NewIAB. +// +// Example, replace this: +// +// iab := IABInit() +// +// with this: +// +// iab := NewIAB() func IABInit() *IAB { return NewIAB() } -- cgit v1.2.3 From 893c134ca5cd6fc33ac19d8dbb9d985067d2e66b Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Fri, 10 Sep 2021 16:11:57 -0700 Subject: Another missing dependency for make -j13 One more missing dependency for pam_cap.so building. Signed-off-by: Andrew G. Morgan --- pam_cap/Makefile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pam_cap/Makefile b/pam_cap/Makefile index a000978..d852101 100644 --- a/pam_cap/Makefile +++ b/pam_cap/Makefile @@ -19,7 +19,11 @@ install: all execable.o: execable.c ../libcap/execable.h ../libcap/loader.txt $(CC) $(CFLAGS) $(CPPFLAGS) -DLIBCAP_VERSION=\"libcap-$(VERSION).$(MINOR)\" -DSHARED_LOADER=\"$(shell cat ../libcap/loader.txt)\" -c execable.c -o $@ -pam_cap.so: pam_cap.o execable.o pam_cap_linkopts +LIBCAP: + $(MAKE) -C ../libcap all + touch $@ + +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 @@ -83,3 +87,4 @@ sudotest: test_pam_cap clean: rm -f *.o *.so testlink lazylink.so test_pam_cap pam_cap_linkopts *~ + rm -f LIBCAP -- cgit v1.2.3 From fb9b1d69c47af4edd278ab8b08d1f9065afb5e35 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Fri, 10 Sep 2021 22:56:09 -0700 Subject: Update pam_cap .gitignore file Signed-off-by: Andrew G. Morgan --- pam_cap/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/pam_cap/.gitignore b/pam_cap/.gitignore index ef9e57f..87f4186 100644 --- a/pam_cap/.gitignore +++ b/pam_cap/.gitignore @@ -3,3 +3,4 @@ testlink test_pam_cap lazylink.so pam_cap_linkopts +LIBCAP -- cgit v1.2.3 From 1dd3fb43123ccf257491c85bb336407f39eaff9d Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 11 Sep 2021 16:13:56 -0700 Subject: Add in something the builder can override to augment the GO builds I've been looking at reasons packagers are not building the Go binaries and found this with respect to RPMs: https://github.com/rpm-software-management/rpm/issues/367 There has been no easy way to inject the otherwise unneeded workaround: -ldflags=-linkmode=external for building (which, strangely, generates some sort of warning and gratuitously links glibc to an otherwise static build), but seems to work. Until RPM supports Go's native '.note.go.buildid', and RPM requires '.note.gnu.build-id' on binaries, I guess this can work around it: GO_BUILD_FLAGS='-ldflags=-linkmode=external' Signed-off-by: Andrew G. Morgan --- Make.Rules | 3 ++- go/Makefile | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Make.Rules b/Make.Rules index cde6f16..1d34144 100644 --- a/Make.Rules +++ b/Make.Rules @@ -129,7 +129,7 @@ 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 @@ -138,6 +138,7 @@ CGO_LDFLAGS_ALLOW := CGO_LDFLAGS_ALLOW="-Wl,-?-wrap[=,][^-.@][^,]*" endif CGO_CFLAGS := -I$(topdir)/libcap/include CGO_LDFLAGS := -L$(topdir)/libcap +GO_BUILD_FLAGS := endif endif diff --git a/go/Makefile b/go/Makefile index 3f192cd..67ded78 100644 --- a/go/Makefile +++ b/go/Makefile @@ -55,10 +55,10 @@ 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_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 @@ -66,33 +66,33 @@ ifeq ($(RAISE_GO_FILECAP),yes) 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 -mod=vendor -o $@ $< + 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 $< + 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 -mod=vendor -o $@-cgo $< + CC="$(CC)" CGO_ENABLED="1" $(CGO_LDFLAGS_ALLOW) $(GO) build $(GO_BUILD_FLAGS) -mod=vendor -o $@-cgo $< 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 $< test: setid gowns captree $(TESTS) CC="$(CC)" CGO_ENABLED="$(CGO_REQUIRED)" $(CGO_LDFLAGS_ALLOW) $(GO) test -mod=vendor $(IMPORTDIR)/psx -- cgit v1.2.3 From 280110a9caf8510af9775bb75942d050134c12d9 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 11 Sep 2021 19:07:40 -0700 Subject: Recover the kdebug make rules. These were broken as a result of delaying building the test and sudotest binaries until they were actually needed. Signed-off-by: Andrew G. Morgan --- kdebug/test-kernel.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kdebug/test-kernel.sh b/kdebug/test-kernel.sh index 38f5584..c5c2183 100755 --- a/kdebug/test-kernel.sh +++ b/kdebug/test-kernel.sh @@ -13,7 +13,7 @@ function die { } pushd .. -make test || die "failed to make test of libcap tree" +make all test sudotest || die "failed to make all test sudotest of libcap tree" make -C progs tcapsh-static || die "failed to make progs/tcapsh-static" popd -- cgit v1.2.3 From c9f6bdda4c714ceeeaa17d473dd649fd41cc245d Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 12 Sep 2021 18:50:55 -0700 Subject: kdebug shouldn't require sudotest to build uns_test binary. Part of the reason for the QEMU kernel test is to fully test the library against kernels without requiring sudo. Signed-off-by: Andrew G. Morgan --- kdebug/test-kernel.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kdebug/test-kernel.sh b/kdebug/test-kernel.sh index c5c2183..0962ce5 100755 --- a/kdebug/test-kernel.sh +++ b/kdebug/test-kernel.sh @@ -13,8 +13,9 @@ function die { } pushd .. -make all test sudotest || die "failed to make all test sudotest of libcap tree" +make all test || die "failed to make all test of libcap tree" make -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. -- cgit v1.2.3 From a3446b5c6e0879b289287c9a87a57cbdc95e99da Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 12 Sep 2021 19:27:21 -0700 Subject: Free _cap_proc_dir on exit. Credit to yan12125 for finding this bug: https://bugzilla.kernel.org/show_bug.cgi?id=214373 Signed-off-by: Andrew G. Morgan --- libcap/cap_text.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/libcap/cap_text.c b/libcap/cap_text.c index 013eb1e..1004aae 100644 --- a/libcap/cap_text.c +++ b/libcap/cap_text.c @@ -673,6 +673,19 @@ static __u32 _parse_vec_string(__u32 *vals, const char *c, int invert) */ 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 -- cgit v1.2.3 From fc6253b9de68dafae1927b2bcbfcef9e9ec6e05a Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Mon, 13 Sep 2021 21:08:42 -0700 Subject: Add PAM "session" support to pam_cap.so. This is an attempt to address: https://bugzilla.kernel.org/show_bug.cgi?id=214377 The basic structure is you configure PAM with a config like this: #%PAM-1.0 auth required pam_cap.so use_session keepcaps auth required pam_unix.so account required pam_unix.so password required pam_unix.so session required pam_unix.so session optional pam_cap.so Here the "auth" part prepares the application with "keepcaps", and the "use_session" instructs the module to apply any IAB tuple for the user at session open time and not during the setcred (auth) flow. This has been tested against the contrib/sucap implementation of su. The "use_session" support should work with more standard PAM enabled apps too, but I'll wait for some positive feedback (see the bug) before declaring it stable. FWIW the contrib/sucap/su app also supports this config for Ambient vector setting (without a "session" invocation of pam_cap.so): #%PAM-1.0 auth required pam_cap.so auth required pam_unix.so account required pam_unix.so password required pam_unix.so session required pam_unix.so but that is because the sucap/su app is more tightly integrated with libcap than the standard PAM apps. Signed-off-by: Andrew G. Morgan --- pam_cap/execable.c | 3 +- pam_cap/pam_cap.c | 77 ++++++++++++++++++++++++++++++++++++++++++-------- pam_cap/test_pam_cap.c | 18 +++++++----- 3 files changed, 79 insertions(+), 19 deletions(-) diff --git a/pam_cap/execable.c b/pam_cap/execable.c index 05f18d8..8e5028d 100644 --- a/pam_cap/execable.c +++ b/pam_cap/execable.c @@ -48,6 +48,7 @@ SO_MAIN(int argc, char **argv) "config= - 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= - fallback IAB value if there is no '*' rule\n", + "default= - fallback IAB value if there is no '*' rule\n" + "use_session - apply IAB value during session open, not setcred\n", cmd); } diff --git a/pam_cap/pam_cap.c b/pam_cap/pam_cap.c index 17ad83e..80eb40b 100644 --- a/pam_cap/pam_cap.c +++ b/pam_cap/pam_cap.c @@ -39,11 +39,16 @@ struct pam_cap_s { int debug; int keepcaps; int autoauth; + int session; const char *user; const char *conf_filename; const char *fallback; }; +/* + * pam_cap_iab_s is used to capture any cap_iab_t value if it needs to be applied during pam_sm_ + */ + /* * load_groups obtains the list all of the groups associated with the * requested user: gid & supplemental groups. @@ -252,6 +257,12 @@ 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 "use_session" module argument and + * include a "session" line in your PAM config that points to + * pam_cap.so. */ D(("setting keepcaps")); (void) cap_prctlw(PR_SET_KEEPCAPS, 1, 0, 0, 0, 0); @@ -299,6 +310,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, "use_session")) { + pcs->session = 1; } else { _pam_log(LOG_ERR, "unknown option; %s", *argv); } @@ -358,23 +371,23 @@ 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 "use_session" module argument), it + * caches the cap_iab_t value for later use during the + * pam_sm_open_session() 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))) { @@ -390,8 +403,50 @@ int pam_sm_setcred(pam_handle_t *pamh, int flags, return PAM_AUTH_ERR; } - retval = set_capabilities(&pcs); - memset(&pcs, 0, sizeof(pcs)); + if (!pcs.session) { + retval = set_capabilities(&pcs); + memset(&pcs, 0, sizeof(pcs)); + } return (retval ? PAM_SUCCESS:PAM_IGNORE); } + +/* + * pam_sm_open_session supports applying the configured cap_iab_t + * tuple after the user credentials have been fully applied. This is + * for programs where pam_cap.so's auth configuration includes the + * "use_session" and "keepcaps" module arguments. Typically, the + * latter is needed to coax the application into persisting the + * ability to apply the IAB value after a setuid() call by the + * application. + * + * Note, this is only needed in cases where the local system needs to + * support adding Ambient capability vector values. For Inheritable + * capabilities which survive setuid() in all modes, the existing + * module works fine. + */ +int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, + const char **argv) +{ + int retval; + struct pam_cap_s pcs; + + parse_args(argc, argv, &pcs); + + retval = pam_get_item(pamh, PAM_USER, (const void **)&pcs.user); + if ((retval != PAM_SUCCESS) || (pcs.user == NULL) || !(pcs.user[0])) { + D(("user's name is not set")); + return PAM_USER_UNKNOWN; + } + + set_capabilities(&pcs); + memset(&pcs, 0, sizeof(pcs)); + return PAM_SUCCESS; +} + +/* pam_sm_close_session is mostly a no-op; it always returns PAM_SUCCESS. */ +int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, + const char **argv) +{ + return PAM_SUCCESS; +} diff --git a/pam_cap/test_pam_cap.c b/pam_cap/test_pam_cap.c index 4c67cad..0a58da6 100644 --- a/pam_cap/test_pam_cap.c +++ b/pam_cap/test_pam_cap.c @@ -134,31 +134,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 }, + { "use_session", NULL } + }, + { + { 0, 0, 0, 0, NULL, NULL, NULL }, { NULL } } }; -- cgit v1.2.3 From dbd9481fa0e977fdba5b4d9bb8a912e28497fb28 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Mon, 13 Sep 2021 21:10:53 -0700 Subject: Simplify the contric/sucap/su structure. Also include the aggressive default CFLAGS, and fix the many many issues it uncovered. (Honestly, it was a wonder it worked at all before.) Signed-off-by: Andrew G. Morgan --- contrib/sucap/Makefile | 3 + contrib/sucap/su.c | 154 ++++++++++++++++++++++++------------------------- 2 files changed, 80 insertions(+), 77 deletions(-) diff --git a/contrib/sucap/Makefile b/contrib/sucap/Makefile index 8cd4cef..407e5ec 100644 --- a/contrib/sucap/Makefile +++ b/contrib/sucap/Makefile @@ -1,3 +1,6 @@ +topdir=$(shell pwd)/.. +include ../../Make.Rules + all: su su: su.c diff --git a/contrib/sucap/su.c b/contrib/sucap/su.c index d72a69a..c8cc05f 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; @@ -347,7 +346,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 +367,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 +384,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 +464,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 +585,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 +714,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 +738,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 +765,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 +806,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 +826,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 +1076,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 +1095,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 +1114,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 +1136,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 +1157,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 +1173,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 +1196,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 +1210,8 @@ static int set_credentials(pam_handle_t *pamh, cap_t all, int login, } uid = pw->pw_uid; + *uid_p = uid; + shell = x_strdup(pw->pw_shell); if (shell == NULL) { D(("user %s has no shell", user)); @@ -1300,8 +1282,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 +1303,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,18 +1319,30 @@ 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_. */ -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; @@ -1392,10 +1386,6 @@ static int perform_launch_and_cleanup(cap_t all, int is_login, D(("failed to read IAB value of process")); return PAM_SYSTEM_ERR; } - if (cap_set_proc(all) != 0) { - D(("failed to restore process capabilities")); - return PAM_SYSTEM_ERR; - } launcher = cap_new_launcher(shell_args[0], (const char * const *) &shell_args[1], @@ -1404,12 +1394,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, iab); 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); @@ -1425,7 +1419,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; @@ -1449,10 +1443,10 @@ 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); @@ -1468,10 +1462,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 */ } /* @@ -1492,7 +1486,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; } @@ -1500,12 +1494,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, ... */ @@ -1518,12 +1518,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 @@ -1550,8 +1544,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 */ @@ -1560,17 +1553,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(all, 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); } @@ -1587,7 +1588,6 @@ delete_cred: pam_strerror(pamh, retcode)); } -old_owner: D(("return terminal to local control")); restore_terminal_owner(); -- cgit v1.2.3 From 783d9b5c5f5038cbbe166c0cdf6d356edb1c9f7c Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Tue, 14 Sep 2021 17:26:33 -0700 Subject: Not sure why I didn't include this line before! Signed-off-by: Andrew G. Morgan --- contrib/sucap/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/sucap/README.md b/contrib/sucap/README.md index 586f017..0808912 100644 --- a/contrib/sucap/README.md +++ b/contrib/sucap/README.md @@ -10,6 +10,7 @@ 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: +https://github.com/linux-pam/linux-pam/pull/373/commits/bf9b1d8ad909634000a7356af2d865a79d3f86f3 The original sources were found here: -- cgit v1.2.3 From 2c3b8949f4374db5285865ad8ce1bdf49d6f24c6 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Tue, 14 Sep 2021 19:36:56 -0700 Subject: Another attempt at supporting Ambient vector setting from pam_cap.so. While the session idea worked with contrib/sucap/su.c, it failed on more traditional PAM apps. For a second (likely last) attempt to find a path, I've deleted the session support and now attempt to do the setting via a PAM data item cleanup() callback. In the contrib/sucap/su.c code, evolved from the original SimplePAMApps 'su', there is a pam_end(pamh, PAM_SUCCESS | PAM_DATA_SILENT) from within the fork()d launcher code, so I hope this convention is standard for all the PAM apps that came after. The suggested config for this module for an app, that wants to support the Ambient vector, is thus now: #%PAM-1.0 auth required pam_cap.so keepcaps defer auth required pam_unix.so account required pam_unix.so password required pam_unix.so session required pam_unix.so This is all part of an effort to address: https://bugzilla.kernel.org/show_bug.cgi?id=214377 Signed-off-by: Andrew G. Morgan --- pam_cap/execable.c | 2 +- pam_cap/pam_cap.c | 100 +++++++++++++++++++++---------------------------- pam_cap/test_pam_cap.c | 13 ++++++- 3 files changed, 55 insertions(+), 60 deletions(-) diff --git a/pam_cap/execable.c b/pam_cap/execable.c index 8e5028d..f826a57 100644 --- a/pam_cap/execable.c +++ b/pam_cap/execable.c @@ -49,6 +49,6 @@ SO_MAIN(int argc, char **argv) "keepcaps - workaround for apps that setuid without this\n" "autoauth - pam_cap.so to always succeed for the 'auth' phase\n" "default= - fallback IAB value if there is no '*' rule\n" - "use_session - apply IAB value during session open, not setcred\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 80eb40b..2538b35 100644 --- a/pam_cap/pam_cap.c +++ b/pam_cap/pam_cap.c @@ -39,16 +39,13 @@ struct pam_cap_s { int debug; int keepcaps; int autoauth; - int session; + int defer; const char *user; const char *conf_filename; const char *fallback; + pam_handle_t *pamh; }; -/* - * pam_cap_iab_s is used to capture any cap_iab_t value if it needs to be applied during pam_sm_ - */ - /* * load_groups obtains the list all of the groups associated with the * requested user: gid & supplemental groups. @@ -189,6 +186,33 @@ defer: return cap_string; } +/* + * 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); + + 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 @@ -246,7 +270,11 @@ 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")); + pam_set_data(cs->pamh, "pam_cap_iab", iab, iab_apply); + iab = NULL; + } else if (!cap_iab_set_proc(iab)) { D(("able to set the IAB [%s] value", conf_caps)); ok = 1; } @@ -260,9 +288,7 @@ static int set_capabilities(struct pam_cap_s *cs) * * 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 "use_session" module argument and - * include a "session" line in your PAM config that points to - * pam_cap.so. + * require you use the "defer" module argument. */ D(("setting keepcaps")); (void) cap_prctlw(PR_SET_KEEPCAPS, 1, 0, 0, 0, 0); @@ -310,8 +336,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, "use_session")) { - pcs->session = 1; + } else if (!strcmp(*argv, "defer")) { + pcs->defer = 1; } else { _pam_log(LOG_ERR, "unknown option; %s", *argv); } @@ -380,9 +406,8 @@ int pam_sm_authenticate(pam_handle_t *pamh, int flags, /* * 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 "use_session" module argument), it - * caches the cap_iab_t value for later use during the - * pam_sm_open_session() call. + * 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) @@ -403,50 +428,9 @@ int pam_sm_setcred(pam_handle_t *pamh, int flags, return PAM_AUTH_ERR; } - if (!pcs.session) { - retval = set_capabilities(&pcs); - memset(&pcs, 0, sizeof(pcs)); - } - - return (retval ? PAM_SUCCESS:PAM_IGNORE); -} - -/* - * pam_sm_open_session supports applying the configured cap_iab_t - * tuple after the user credentials have been fully applied. This is - * for programs where pam_cap.so's auth configuration includes the - * "use_session" and "keepcaps" module arguments. Typically, the - * latter is needed to coax the application into persisting the - * ability to apply the IAB value after a setuid() call by the - * application. - * - * Note, this is only needed in cases where the local system needs to - * support adding Ambient capability vector values. For Inheritable - * capabilities which survive setuid() in all modes, the existing - * module works fine. - */ -int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, - const char **argv) -{ - int retval; - struct pam_cap_s pcs; - - parse_args(argc, argv, &pcs); - - retval = pam_get_item(pamh, PAM_USER, (const void **)&pcs.user); - if ((retval != PAM_SUCCESS) || (pcs.user == NULL) || !(pcs.user[0])) { - D(("user's name is not set")); - return PAM_USER_UNKNOWN; - } - - set_capabilities(&pcs); + pcs.pamh = pamh; + retval = set_capabilities(&pcs); memset(&pcs, 0, sizeof(pcs)); - return PAM_SUCCESS; -} -/* pam_sm_close_session is mostly a no-op; it always returns PAM_SUCCESS. */ -int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, - const char **argv) -{ - return PAM_SUCCESS; + return (retval ? PAM_SUCCESS:PAM_IGNORE); } diff --git a/pam_cap/test_pam_cap.c b/pam_cap/test_pam_cap.c index 0a58da6..886888e 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++) { @@ -159,7 +170,7 @@ static int test_arg_parsing(void) { }, { { 0, 0, 0, 1, NULL, NULL, NULL }, - { "use_session", NULL } + { "defer", NULL } }, { { 0, 0, 0, 0, NULL, NULL, NULL }, -- cgit v1.2.3 From 9f9602215ccf205cca1b0a495db9eae18d204265 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Tue, 14 Sep 2021 20:54:00 -0700 Subject: Add some debugging info for the pam_cap.so deferred callback. As with the other D(()) entries in the pam_cap.so module, this is enabled if the /* #define PAM_DEBUG */ comment is uncommented at the top of the pam_cap.so file. I tried this on a sample app and it didn't actually follow the documentation: http://www.linux-pam.org/Linux-PAM-html/adg-interface-by-app-expected.html#adg-pam_end where no pam_end() call was made to terminate the fork()ed copy of the pamh value. That app needs to be fixed. Signed-off-by: Andrew G. Morgan --- pam_cap/pam_cap.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pam_cap/pam_cap.c b/pam_cap/pam_cap.c index 2538b35..7e8cade 100644 --- a/pam_cap/pam_cap.c +++ b/pam_cap/pam_cap.c @@ -196,6 +196,24 @@ 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; -- cgit v1.2.3 From 0efe94c6ec601a5d1e84819e87618c5837395709 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Tue, 14 Sep 2021 21:57:05 -0700 Subject: Fix typo in capsh. Signed-off-by: Andrew G. Morgan --- progs/capsh.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/progs/capsh.c b/progs/capsh.c index efd1f56..ed2dd9b 100644 --- a/progs/capsh.c +++ b/progs/capsh.c @@ -981,7 +981,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); -- cgit v1.2.3 From 01627eae86cc299de459067614e6964b63bb6bcb Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Fri, 17 Sep 2021 19:35:29 -0700 Subject: Up the release version to 2.58 Signed-off-by: Andrew G. Morgan --- Make.Rules | 2 +- cap/go.mod | 2 +- contrib/seccomp/go.mod | 2 +- go/go.mod | 4 ++-- goapps/captree/go.mod | 2 +- goapps/gowns/go.mod | 2 +- goapps/setid/go.mod | 4 ++-- goapps/web/go.mod | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Make.Rules b/Make.Rules index 1d34144..b86eee5 100644 --- a/Make.Rules +++ b/Make.Rules @@ -1,7 +1,7 @@ # Common version number defines for libcap LIBTITLE=libcap VERSION=2 -MINOR=57 +MINOR=58 # ## Optional prefixes: diff --git a/cap/go.mod b/cap/go.mod index 68e70a4..1c6bf57 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.57 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.58 diff --git a/contrib/seccomp/go.mod b/contrib/seccomp/go.mod index 85b4da2..df24554 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.57 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.58 diff --git a/go/go.mod b/go/go.mod index 8178343..1627607 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.57 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.57 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.58 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.58 ) diff --git a/goapps/captree/go.mod b/goapps/captree/go.mod index ea5aaff..508b8d6 100644 --- a/goapps/captree/go.mod +++ b/goapps/captree/go.mod @@ -2,4 +2,4 @@ module captree go 1.16 -require kernel.org/pub/linux/libs/security/libcap/cap v1.2.57 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.58 diff --git a/goapps/gowns/go.mod b/goapps/gowns/go.mod index fa32381..092fc66 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.57 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.58 diff --git a/goapps/setid/go.mod b/goapps/setid/go.mod index 7fa1773..d0223e0 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.57 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.57 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.58 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.58 ) diff --git a/goapps/web/go.mod b/goapps/web/go.mod index c76ecf4..aced675 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.57 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.58 -- cgit v1.2.3 From 33a6686e2bc126916145f01246ee6be80669dcdb Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Fri, 17 Sep 2021 21:07:19 -0700 Subject: More compliant cap.Differs documentation. Deprecation has a stylized comment format as per go.dev. Signed-off-by: Andrew G. Morgan --- cap/flags.go | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/cap/flags.go b/cap/flags.go index 88ad355..df7a9a5 100644 --- a/cap/flags.go +++ b/cap/flags.go @@ -208,9 +208,23 @@ func (c *Set) Cf(d *Set) (Diff, error) { // Differs processes the result of Compare and determines if the // Flag's components were different. // -// Use of this function is deprecated in favor of the (Diff).Has() -// function, where Diff is returned as a result of the (*Set).Cf() -// function. +// 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< Date: Wed, 22 Sep 2021 18:50:53 -0700 Subject: Spelling fix. Signed-off-by: Andrew G. Morgan --- goapps/captree/captree.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/goapps/captree/captree.go b/goapps/captree/captree.go index 4fb023d..fc89b51 100644 --- a/goapps/captree/captree.go +++ b/goapps/captree/captree.go @@ -402,7 +402,7 @@ func main() { } } - // Sort the proccess tree by tree depth - shallowest first, + // 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]] -- cgit v1.2.3 From f8b754967348052ca92c6d2c95551cbbb1e1d387 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Thu, 23 Sep 2021 20:46:31 -0700 Subject: Update example to avoid reference to deprecated Compare function. In 2.54 (*Set).Compare() was deprecated in favor of (*Set).Cf(), so update the top level comment to reflect the preferred API. Signed-off-by: Andrew G. Morgan --- cap/cap.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cap/cap.go b/cap/cap.go index d4b241e..5bbfea3 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) // } // -- cgit v1.2.3 From 6643c636e8ab44add497f97e479ad8a931d43adf Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Fri, 24 Sep 2021 10:46:24 -0700 Subject: Recognize that NULL is an invalid cap_t and cap_iab_t. This was a regresssion introduced in libcap-2.55. Fixed in libcap-2.59. Added a cap_launch NULL test too. Comparing against NULL would cause a SIGSEGV against these library revisions. Signed-off-by: Andrew G. Morgan --- doc/cap_clear.3 | 20 +++++++++++--------- libcap/cap_proc.c | 5 +++++ libcap/cap_test.c | 8 ++++++++ libcap/libcap.h | 7 ++++--- tests/libcap_launch_test.c | 6 +++++- 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/doc/cap_clear.3 b/doc/cap_clear.3 index 6d06049..eb8f4ec 100644 --- a/doc/cap_clear.3 +++ b/doc/cap_clear.3 @@ -88,16 +88,18 @@ 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. .SH "RETURN VALUE" diff --git a/libcap/cap_proc.c b/libcap/cap_proc.c index eac86cb..8633824 100644 --- a/libcap/cap_proc.c +++ b/libcap/cap_proc.c @@ -998,6 +998,11 @@ 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; + } + /* The launch must have a purpose */ if (attr->custom_setup_fn == NULL && (attr->arg0 == NULL || attr->argv == NULL)) { diff --git a/libcap/cap_test.c b/libcap/cap_test.c index 729efed..2c068aa 100644 --- a/libcap/cap_test.c +++ b/libcap/cap_test.c @@ -49,6 +49,14 @@ static int test_cap_flags(void) 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)) { diff --git a/libcap/libcap.h b/libcap/libcap.h index ffc8c8a..bd80342 100644 --- a/libcap/libcap.h +++ b/libcap/libcap.h @@ -133,9 +133,10 @@ struct _cap_struct { /* launcher magic for cap_free */ #define CAP_LAUNCH_MAGIC 0xCA91A -#define magic_of(x) (*(-2 + (const __u32 *) x)) -#define good_cap_t(x) (CAP_T_MAGIC == magic_of(x)) -#define good_cap_iab_t(x) (CAP_IAB_MAGIC == magic_of(x)) +#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 diff --git a/tests/libcap_launch_test.c b/tests/libcap_launch_test.c index 343e389..12d123c 100644 --- a/tests/libcap_launch_test.c +++ b/tests/libcap_launch_test.c @@ -127,8 +127,12 @@ int main(int argc, char **argv) { int success = 1, i; for (i=0; vs[i].pass_on != NO_MORE; i++) { - cap_launch_t attr; + 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"); if (v->args[0] != NULL) { -- cgit v1.2.3 From 22569c7cfb428edc5ae05cf631a25227087e74d0 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Fri, 24 Sep 2021 10:58:05 -0700 Subject: Fix to 'make clean sudotest' reliably Signed-off-by: Andrew G. Morgan --- progs/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/progs/Makefile b/progs/Makefile index 846bd76..8cd9c97 100644 --- a/progs/Makefile +++ b/progs/Makefile @@ -61,7 +61,7 @@ uns_test: ../tests/uns_test.c $(MAKE) -C ../tests uns_test cp ../tests/uns_test . -sudotest: tcapsh-static uns_test capsh setcap getcap tcapsh-static +sudotest: tcapsh-static uns_test capsh setcap getcap getpcaps tcapsh-static $(SUDO) $(LDPATH) ./quicktest.sh clean: -- cgit v1.2.3 From 9eb56596eef5e55a596aa97ecaf8466ea559d05c Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 26 Sep 2021 18:20:33 -0700 Subject: Up the release version to 2.59 Signed-off-by: Andrew G. Morgan --- Make.Rules | 2 +- cap/go.mod | 2 +- contrib/seccomp/go.mod | 2 +- go/go.mod | 4 ++-- goapps/captree/go.mod | 2 +- goapps/gowns/go.mod | 2 +- goapps/setid/go.mod | 4 ++-- goapps/web/go.mod | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Make.Rules b/Make.Rules index b86eee5..a0f044f 100644 --- a/Make.Rules +++ b/Make.Rules @@ -1,7 +1,7 @@ # Common version number defines for libcap LIBTITLE=libcap VERSION=2 -MINOR=58 +MINOR=59 # ## Optional prefixes: diff --git a/cap/go.mod b/cap/go.mod index 1c6bf57..e9bc6f1 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.58 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.59 diff --git a/contrib/seccomp/go.mod b/contrib/seccomp/go.mod index df24554..af85501 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.58 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.59 diff --git a/go/go.mod b/go/go.mod index 1627607..0752c98 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.58 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.58 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.59 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.59 ) diff --git a/goapps/captree/go.mod b/goapps/captree/go.mod index 508b8d6..1e8ae5d 100644 --- a/goapps/captree/go.mod +++ b/goapps/captree/go.mod @@ -2,4 +2,4 @@ module captree go 1.16 -require kernel.org/pub/linux/libs/security/libcap/cap v1.2.58 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.59 diff --git a/goapps/gowns/go.mod b/goapps/gowns/go.mod index 092fc66..3fa4244 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.58 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.59 diff --git a/goapps/setid/go.mod b/goapps/setid/go.mod index d0223e0..9f55c17 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.58 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.58 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.59 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.59 ) diff --git a/goapps/web/go.mod b/goapps/web/go.mod index aced675..98143bf 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.58 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.59 -- cgit v1.2.3 From 87219b72cefefcf28c69c2d33a62e8c67ea0efce Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Tue, 28 Sep 2021 22:30:28 -0700 Subject: Make capshdoc.h stand alone compile friendly. Signed-off-by: Andrew G. Morgan Signed-off-by: Andrew G. Morgan --- progs/capshdoc.h | 4 +++- progs/mkcapshdoc.sh | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/progs/capshdoc.h b/progs/capshdoc.h index 2ac6ecb..6e893a7 100644 --- a/progs/capshdoc.h +++ b/progs/capshdoc.h @@ -1,3 +1,5 @@ +#include + #ifdef CAPSHDOC #error "don't include this twice" #endif @@ -372,7 +374,7 @@ static const char *explanation40[] = { /* cap_checkpoint_restore = 40 */ "also writing to ns_last_pid.", NULL }; -static const char **explanations[] = { +const char **explanations[] = { explanation0, explanation1, explanation2, diff --git a/progs/mkcapshdoc.sh b/progs/mkcapshdoc.sh index 705d526..84d5033 100755 --- a/progs/mkcapshdoc.sh +++ b/progs/mkcapshdoc.sh @@ -4,6 +4,8 @@ # with the checked in code in the progs directory. cat< + #ifdef CAPSHDOC #error "don't include this twice" #endif @@ -25,7 +27,7 @@ while [ -f "../doc/values/${x}.txt" ]; do done cat< Date: Wed, 29 Sep 2021 21:38:36 -0700 Subject: Fix miscellaneous build and lint warnings. Addresses the issues listed here: https://bugzilla.kernel.org/show_bug.cgi?id=214579 Signed-off-by: Andrew G. Morgan --- Make.Rules | 2 +- cap/file.go | 10 +- doc/cap_launch.3 | 2 +- doc/mkmd.sh | 2 + libcap/cap_proc.c | 3 +- progs/Makefile | 14 +- progs/capsh.c | 4 +- progs/capshdoc.c | 418 ++++++++++++++++++++++++++++++++++++++++++++++++++++ progs/capshdoc.h | 417 +-------------------------------------------------- progs/mkcapshdoc.sh | 10 +- psx/psx.go | 16 ++ 11 files changed, 459 insertions(+), 439 deletions(-) create mode 100644 progs/capshdoc.c diff --git a/Make.Rules b/Make.Rules index a0f044f..d9147fc 100644 --- a/Make.Rules +++ b/Make.Rules @@ -79,7 +79,7 @@ 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 + -Winline -Wshadow -Wunreachable-code COPTS ?= -O2 CFLAGS ?= $(COPTS) $(DEBUG) CFLAGS += $(WARNINGS) diff --git a/cap/file.go b/cap/file.go index a309bd0..cfc171d 100644 --- a/cap/file.go +++ b/cap/file.go @@ -350,11 +350,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 diff --git a/doc/cap_launch.3 b/doc/cap_launch.3 index 6d9b8f7..03d50f4 100644 --- a/doc/cap_launch.3 +++ b/doc/cap_launch.3 @@ -86,7 +86,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 diff --git a/doc/mkmd.sh b/doc/mkmd.sh index af835d5..39beac9 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#*/}" diff --git a/libcap/cap_proc.c b/libcap/cap_proc.c index 8633824..7514305 100644 --- a/libcap/cap_proc.c +++ b/libcap/cap_proc.c @@ -1021,8 +1021,7 @@ 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 */ } close(ps[1]); if (child < 0) { diff --git a/progs/Makefile b/progs/Makefile index 8cd9c97..2cb7520 100644 --- a/progs/Makefile +++ b/progs/Makefile @@ -45,17 +45,17 @@ endif 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) $(CFLAGS) $(CPPFLAGS) $(CAPSH_SHELL) $(LDFLAGS) -o $@ $< $(LIBCAPLIB) +capsh: capsh.c capshdoc.c.cf capshdoc.h $(DEPS) + $(CC) $(CFLAGS) $(CPPFLAGS) $(CAPSH_SHELL) $(LDFLAGS) -o $@ $< capshdoc.c $(LIBCAPLIB) # Statically linked with minimal linkage flags to enable running in a # chroot and in other in-tree testing contexts. -tcapsh-static: capsh.c capshdoc.h.cf $(DEPS) - $(CC) $(CFLAGS) $(CPPFLAGS) $(CAPSH_SHELL) -o $@ $< $(LIBCAPLIB) --static +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 @@ -67,4 +67,4 @@ sudotest: tcapsh-static uns_test capsh setcap getcap getpcaps tcapsh-static 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 ed2dd9b..a5b7b08 100644 --- a/progs/capsh.c +++ b/progs/capsh.c @@ -1011,7 +1011,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; } @@ -1024,7 +1024,7 @@ 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); diff --git a/progs/capshdoc.c b/progs/capshdoc.c new file mode 100644 index 0000000..ee7e974 --- /dev/null +++ b/progs/capshdoc.c @@ -0,0 +1,418 @@ +#include + +#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.", + 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 6e893a7..d9cbab9 100644 --- a/progs/capshdoc.h +++ b/progs/capshdoc.h @@ -1,420 +1,7 @@ -#include - #ifdef CAPSHDOC #error "don't include this twice" #endif #define CAPSHDOC -/* - * A line by line explanation of each named capability value - */ -static const char *explanation0[] = { /* cap_chown = 0 */ - "Allows a process to arbitrarily change the user and", - "group ownership of a file.", - NULL -}; -static const char *explanation1[] = { /* cap_dac_override = 1 */ - "Allows a process to override of all Discretionary", - "Access Control (DAC) access, including ACL execute", - "access. That is read, write or execute files that the", - "process would otherwise not have access to. This", - "excludes DAC access covered by CAP_LINUX_IMMUTABLE.", - NULL -}; -static const char *explanation2[] = { /* cap_dac_read_search = 2 */ - "Allows a process to override all DAC restrictions", - "limiting the read and search of files and", - "directories. This excludes DAC access covered by", - "CAP_LINUX_IMMUTABLE.", - NULL -}; -static const char *explanation3[] = { /* cap_fowner = 3 */ - "Allows a process to perform operations on files, even", - "where file owner ID should otherwise need be equal to", - "the UID, except where CAP_FSETID is applicable. It", - "doesn't override MAC and DAC restrictions.", - NULL -}; -static const char *explanation4[] = { /* cap_fsetid = 4 */ - "Allows a process to set the S_ISUID and S_ISUID bits of", - "the file permissions, even when the process' effective", - "UID or GID/supplementary GIDs do not match that of the", - "file.", - NULL -}; -static const char *explanation5[] = { /* cap_kill = 5 */ - "Allows a process to send a kill(2) signal to any other", - "process - overriding the limitation that there be a", - "[E]UID match between source and target process.", - NULL -}; -static const char *explanation6[] = { /* cap_setgid = 6 */ - "Allows a process to freely manipulate its own GIDs:", - " - arbitrarily set the GID, EGID, REGID, RESGID values", - " - arbitrarily set the supplementary GIDs", - " - allows the forging of GID credentials passed over a", - " socket", - NULL -}; -static const char *explanation7[] = { /* cap_setuid = 7 */ - "Allows a process to freely manipulate its own UIDs:", - " - arbitrarily set the UID, EUID, REUID and RESUID", - " values", - " - allows the forging of UID credentials passed over a", - " socket", - NULL -}; -static const char *explanation8[] = { /* cap_setpcap = 8 */ - "Allows a process to freely manipulate its inheritable", - "capabilities.", - "", - "Linux supports the POSIX.1e Inheritable set, 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, -}; -#define CAPSH_DOC_LIMIT 41 +extern const char **explanations[]; +extern const int capsh_doc_limit; diff --git a/progs/mkcapshdoc.sh b/progs/mkcapshdoc.sh index 84d5033..8421685 100755 --- a/progs/mkcapshdoc.sh +++ b/progs/mkcapshdoc.sh @@ -1,15 +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< -#ifdef CAPSHDOC -#error "don't include this twice" -#endif -#define CAPSHDOC +#include "./capshdoc.h" /* * A line by line explanation of each named capability value @@ -36,5 +33,6 @@ while [ "${y}" -lt "${x}" ]; do done cat< Date: Fri, 1 Oct 2021 18:16:57 +0000 Subject: Lower the start up memory allocation overhead. In the vast majority of cases, code will not need to override the "/proc" root directory, so treat NULL as equivalent to "/proc". Signed-off-by: Andrew G. Morgan Signed-off-by: Andrew G. Morgan --- libcap/cap_alloc.c | 1 - libcap/cap_test.c | 2 +- libcap/cap_text.c | 10 ++++++++-- libcap/include/sys/capability.h | 10 +++++++--- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/libcap/cap_alloc.c b/libcap/cap_alloc.c index 4dabe27..f77dec3 100644 --- a/libcap/cap_alloc.c +++ b/libcap/cap_alloc.c @@ -19,7 +19,6 @@ __attribute__((constructor (300))) static void _initialize_libcap(void) } cap_set_syscall(NULL, NULL); _binary_search(_cap_max_bits, cap_get_bound, 0, __CAP_MAXBITS, __CAP_BITS); - cap_proc_root("/proc"); } cap_value_t cap_max_bits(void) diff --git a/libcap/cap_test.c b/libcap/cap_test.c index 2c068aa..b523091 100644 --- a/libcap/cap_test.c +++ b/libcap/cap_test.c @@ -171,7 +171,7 @@ static int test_alloc(void) } old_root = cap_proc_root("blah"); - if (old_root == NULL || strcmp(old_root, "/proc") != 0) { + if (old_root != NULL) { printf("bad initial proc_root [%s]\n", old_root); fflush(stdout); retval = -1; diff --git a/libcap/cap_text.c b/libcap/cap_text.c index 1004aae..40bd59a 100644 --- a/libcap/cap_text.c +++ b/libcap/cap_text.c @@ -669,7 +669,7 @@ static __u32 _parse_vec_string(__u32 *vals, const char *c, int invert) /* * libcap believes this is the root of the mounted "/proc" - * filesystem + * filesystem. (NULL == "/proc".) */ static char *_cap_proc_dir; @@ -695,6 +695,8 @@ __attribute__((destructor (300))) static void _cleanup_libcap(void) * 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) { @@ -717,8 +719,12 @@ cap_iab_t cap_iab_get_pid(pid_t pid) char *path; FILE *file; char line[PROC_LINE_MAX]; + const char *proc_root = _cap_proc_dir; - if (asprintf(&path, "%s/%d/status", _cap_proc_dir, pid) <= 0) { + if (proc_root == NULL) { + proc_root = "/proc"; + } + if (asprintf(&path, "%s/%d/status", proc_root, pid) <= 0) { return NULL; } file = fopen(path, "r"); diff --git a/libcap/include/sys/capability.h b/libcap/include/sys/capability.h index 59f9377..80171b0 100644 --- a/libcap/include/sys/capability.h +++ b/libcap/include/sys/capability.h @@ -55,8 +55,9 @@ 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. It - * defaults to the value "/proc". + * 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 @@ -65,7 +66,10 @@ extern cap_value_t cap_max_bits(void); * 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) when done with it. + * 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); -- cgit v1.2.3 From 2ffbc9d2488e044cbb4851f3218f19171bafa46c Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Fri, 1 Oct 2021 21:45:23 +0000 Subject: Implement cap_fill_flag() and cap.FillFlag() APIs. This API avoids a complex use case that requires substantially more code outside of libcap. Signed-off-by: Andrew G. Morgan Signed-off-by: Andrew G. Morgan --- cap/flags.go | 29 +++++++++++++++++++++++------ doc/Makefile | 1 + doc/cap_clear.3 | 14 ++++++++++---- doc/cap_fill.3 | 1 + doc/cap_fill_flag.3 | 1 + libcap/cap_flag.c | 19 ++++++++++++++----- libcap/cap_test.c | 16 ++++++++++++++++ libcap/include/sys/capability.h | 2 ++ 8 files changed, 68 insertions(+), 15 deletions(-) create mode 100644 doc/cap_fill.3 create mode 100644 doc/cap_fill_flag.3 diff --git a/cap/flags.go b/cap/flags.go index df7a9a5..57c0443 100644 --- a/cap/flags.go +++ b/cap/flags.go @@ -75,24 +75,41 @@ 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 { +// 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 c == nil || len(c.flat) == 0 || ref == nil || len(ref.flat) == 0 { return ErrBadSet } if to > Inheritable || from > Inheritable { return ErrBadValue } + + // Avoid deadlock by copying to intermediate memory. + a := make([]uint32, len(ref.flat)) + ref.mu.Lock() + for i := range ref.flat { + a[i] = ref.flat[i][from] + } + ref.mu.Unlock() + c.mu.Lock() defer c.mu.Unlock() for i := range c.flat { - c.flat[i][to] = c.flat[i][from] + c.flat[i][to] = a[i] } 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") diff --git a/doc/Makefile b/doc/Makefile index 9614180..4fc9ea5 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -8,6 +8,7 @@ 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_compare.3 cap_get_proc.3 cap_get_pid.3 cap_set_proc.3 \ cap_get_file.3 cap_get_fd.3 cap_set_file.3 cap_set_fd.3 \ cap_copy_ext.3 cap_size.3 cap_copy_int.3 cap_mode.3 \ diff --git a/doc/cap_clear.3 b/doc/cap_clear.3 index eb8f4ec..a1c40d5 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 "2021-10-01" "" "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 \- capability data object manipulation .SH SYNOPSIS .nf #include @@ -11,6 +11,8 @@ 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); .fi @@ -20,7 +22,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 +34,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 +83,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 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/libcap/cap_flag.c b/libcap/cap_flag.c index 9df1842..d94232b 100644 --- a/libcap/cap_flag.c +++ b/libcap/cap_flag.c @@ -143,12 +143,12 @@ int cap_compare(cap_t a, cap_t b) } /* - * cap_fill copies a bit-vector of capability state in a cap_t from - * one flag to another. + * 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, const cap_t ref, cap_flag_t from) { - if (!good_cap_t(cap_d)) { + if (!good_cap_t(cap_d) || !good_cap_t(ref)) { errno = EINVAL; return -1; } @@ -161,12 +161,21 @@ int cap_fill(cap_t cap_d, cap_flag_t to, cap_flag_t from) int i; 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] = ref->u[i].flat[from]; } 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. */ diff --git a/libcap/cap_test.c b/libcap/cap_test.c index b523091..b7fb2c5 100644 --- a/libcap/cap_test.c +++ b/libcap/cap_test.c @@ -84,6 +84,22 @@ static int test_cap_flags(void) 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) { diff --git a/libcap/include/sys/capability.h b/libcap/include/sys/capability.h index 80171b0..429d8ad 100644 --- a/libcap/include/sys/capability.h +++ b/libcap/include/sys/capability.h @@ -135,6 +135,8 @@ 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, + const 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) -- cgit v1.2.3 From aca076443591ba18438b60e41294b59a324daf04 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Wed, 13 Oct 2021 21:59:14 -0700 Subject: Make cap_t operations thread safe. If two threads operate on the same cap_t value, ensure that the operations occur atomically. (Not, however, reentrantly.) Also added some sanity checking to cap_set_nsowner() and cap_get_nsowner(). Signed-off-by: Andrew G. Morgan --- libcap/cap_alloc.c | 9 ++++-- libcap/cap_extint.c | 72 +++++++++++++++++++++++------------------ libcap/cap_file.c | 37 ++++++++++++++------- libcap/cap_flag.c | 57 +++++++++++++++++++++++++++----- libcap/cap_proc.c | 8 ++++- libcap/include/sys/capability.h | 2 +- libcap/libcap.h | 24 ++++++++++++-- 7 files changed, 152 insertions(+), 57 deletions(-) diff --git a/libcap/cap_alloc.c b/libcap/cap_alloc.c index f77dec3..6a6ca2c 100644 --- a/libcap/cap_alloc.c +++ b/libcap/cap_alloc.c @@ -122,7 +122,7 @@ char *_libcap_strdup(const char *old) /* * 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) @@ -142,7 +142,10 @@ 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; } @@ -227,8 +230,10 @@ int cap_free(void *data_p) struct _cap_alloc_s *data = base; switch (data->magic) { case CAP_T_MAGIC: - case CAP_IAB_MAGIC: + _cap_mu_lock(&data->u.set.mutex); + break; case CAP_S_MAGIC: + case CAP_IAB_MAGIC: break; case CAP_LAUNCH_MAGIC: if (cap_free(data->u.launcher.iab) != 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; ju[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; ju[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 95c0572..4178705 100644 --- a/libcap/cap_file.c +++ b/libcap/cap_file.c @@ -127,6 +127,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 +145,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 +173,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 +189,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 +199,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 +270,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 +346,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 +393,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 d94232b..39d8646 100644 --- a/libcap/cap_flag.c +++ b/libcap/cap_flag.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997-8,2008,20 Andrew G. Morgan + * Copyright (c) 1997-8,2008,20-21 Andrew G. Morgan * * This file deals with flipping of capabilities on internal * capability sets as specified by POSIX.1e (formerlly, POSIX 6). @@ -24,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"); @@ -50,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= __CAP_MAXBITS) { _cap_debug("weird capability (%d) - skipped", array_values[i]); @@ -63,6 +66,7 @@ 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"); @@ -78,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"); @@ -100,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; } /* @@ -130,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]) @@ -139,6 +156,8 @@ 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; } @@ -146,8 +165,11 @@ int cap_compare(cap_t a, cap_t b) * 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_flag(cap_t cap_d, cap_flag_t to, const cap_t ref, cap_flag_t from) +int cap_fill_flag(cap_t cap_d, cap_flag_t to, cap_t ref, cap_flag_t from) { + int i; + cap_t orig; + if (!good_cap_t(cap_d) || !good_cap_t(ref)) { errno = EINVAL; return -1; @@ -159,11 +181,18 @@ int cap_fill_flag(cap_t cap_d, cap_flag_t to, const cap_t ref, 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] = ref->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; } @@ -251,6 +280,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; @@ -266,8 +297,16 @@ 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; + } + + for (i = 0; !ret && i < _LIBCAP_CAPABILITY_U32S; i++) { switch (vec) { case CAP_IAB_INH: iab->i[i] = cap_d->u[i].flat[flag]; @@ -282,11 +321,13 @@ int cap_iab_fill(cap_iab_t iab, cap_iab_vector_t vec, break; default: errno = EINVAL; - return -1; + ret = -1; + break; } } - return 0; + cap_free(cap_d); + return ret; } /* diff --git a/libcap/cap_proc.c b/libcap/cap_proc.c index 7514305..a26fe5a 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 + * Copyright (c) 1997-8,2007,11,19-21 Andrew G Morgan * * This file deals with getting and setting capabilities on processes. */ @@ -183,7 +183,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 +210,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 +256,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; } diff --git a/libcap/include/sys/capability.h b/libcap/include/sys/capability.h index 429d8ad..20209d5 100644 --- a/libcap/include/sys/capability.h +++ b/libcap/include/sys/capability.h @@ -136,7 +136,7 @@ extern int cap_set_flag(cap_t, cap_flag_t, int, const cap_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, - const cap_t ref, cap_flag_t from); + 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) diff --git a/libcap/libcap.h b/libcap/libcap.h index bd80342..0b57a53 100644 --- a/libcap/libcap.h +++ b/libcap/libcap.h @@ -9,6 +9,7 @@ #define LIBCAP_H #include +#include #include #include #include @@ -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,10 @@ 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)) -- cgit v1.2.3 From 5b16d336d009bc927a0545b9ecbb91942f1742f5 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Fri, 22 Oct 2021 11:33:13 -0700 Subject: Add a cap_iab_dup() function and make IAB access atomic. Embed mutex locked operation into the IAB API. The idea being that while libcap operates on an IAB tuple, it cannot be operated on by a thread running in parallel. This makes IAB access thread safe (but not reentrant). The only potential API behavioral change is that the IAB tuple associated with a cap_launcher_t is now locked for the duration of its association with that launcher. This prevents a race condition with launching and another thread changing that IAB tuple. Signed-off-by: Andrew G. Morgan --- doc/Makefile | 2 +- doc/cap_iab.3 | 7 +++++++ doc/cap_iab_dup.3 | 1 + doc/cap_launch.3 | 8 +++++--- libcap/cap_alloc.c | 39 +++++++++++++++++++++++++++++++++++---- libcap/cap_flag.c | 28 +++++++++++++++++++++++----- libcap/cap_proc.c | 24 +++++++++++++++++++++--- libcap/cap_text.c | 2 ++ libcap/include/sys/capability.h | 1 + libcap/libcap.h | 1 + 10 files changed, 97 insertions(+), 16 deletions(-) create mode 100644 doc/cap_iab_dup.3 diff --git a/doc/Makefile b/doc/Makefile index 4fc9ea5..f014b28 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -22,7 +22,7 @@ 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_compare.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 \ diff --git a/doc/cap_iab.3 b/doc/cap_iab.3 index a0ec988..4a55d0b 100644 --- a/doc/cap_iab.3 +++ b/doc/cap_iab.3 @@ -5,6 +5,8 @@ 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); @@ -72,6 +74,11 @@ 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 () returns a copy of the IAB value for the current process. The returned cap_iab_t should be freed with 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_launch.3 b/doc/cap_launch.3 index 03d50f4..786bca3 100644 --- a/doc/cap_launch.3 +++ b/doc/cap_launch.3 @@ -124,9 +124,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 diff --git a/libcap/cap_alloc.c b/libcap/cap_alloc.c index 6a6ca2c..6a674f4 100644 --- a/libcap/cap_alloc.c +++ b/libcap/cap_alloc.c @@ -129,8 +129,7 @@ cap_t cap_dup(cap_t cap_d) { cap_t result; - __u32 *magic_p = -2 + (__u32 *) cap_d; - if (*magic_p != CAP_T_MAGIC) { + if (!good_cap_t(cap_d)) { _cap_debug("bad argument"); errno = EINVAL; return NULL; @@ -162,6 +161,35 @@ cap_iab_t cap_iab_init(void) 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; +} + /* * cap_new_launcher allocates some memory for a launcher and * initializes it. To actually launch a program with this launcher, @@ -236,8 +264,11 @@ int cap_free(void *data_p) case CAP_IAB_MAGIC: break; case CAP_LAUNCH_MAGIC: - if (cap_free(data->u.launcher.iab) != 0) { - return -1; + if (data->u.launcher.iab != NULL) { + _cap_mu_unlock(&data->u.launcher.iab->mutex); + if (cap_free(data->u.launcher.iab) != 0) { + return -1; + } } data->u.launcher.iab = NULL; if (cap_free(data->u.launcher.chroot) != 0) { diff --git a/libcap/cap_flag.c b/libcap/cap_flag.c index 39d8646..94afd1e 100644 --- a/libcap/cap_flag.c +++ b/libcap/cap_flag.c @@ -217,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; } /* @@ -250,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); @@ -264,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; } @@ -306,6 +313,7 @@ int cap_iab_fill(cap_iab_t iab, cap_iab_vector_t vec, return -1; } + _cap_mu_lock(&iab->mutex); for (i = 0; !ret && i < _LIBCAP_CAPABILITY_U32S; i++) { switch (vec) { case CAP_IAB_INH: @@ -325,6 +333,7 @@ int cap_iab_fill(cap_iab_t iab, cap_iab_vector_t vec, break; } } + _cap_mu_unlock(&iab->mutex); cap_free(cap_d); return ret; @@ -341,11 +350,20 @@ int cap_iab_compare(cap_iab_t a, cap_iab_t b) 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_proc.c b/libcap/cap_proc.c index a26fe5a..8a10f75 100644 --- a/libcap/cap_proc.c +++ b/libcap/cap_proc.c @@ -745,6 +745,7 @@ 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) { @@ -818,7 +819,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; } /* @@ -871,13 +880,22 @@ void cap_launcher_set_mode(cap_launch_t attr, cap_mode_t flavor) } /* - * 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) { 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); + } return old; } diff --git a/libcap/cap_text.c b/libcap/cap_text.c index 40bd59a..8dfe9f8 100644 --- a/libcap/cap_text.c +++ b/libcap/cap_text.c @@ -520,6 +520,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; @@ -553,6 +554,7 @@ char *cap_iab_to_text(cap_iab_t iab) first = 0; } } + _cap_mu_unlock(&iab->mutex); } *p = '\0'; return _libcap_strdup(buf); diff --git a/libcap/include/sys/capability.h b/libcap/include/sys/capability.h index 20209d5..4f499dc 100644 --- a/libcap/include/sys/capability.h +++ b/libcap/include/sys/capability.h @@ -127,6 +127,7 @@ typedef unsigned cap_mode_t; 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 */ diff --git a/libcap/libcap.h b/libcap/libcap.h index 0b57a53..374ee7c 100644 --- a/libcap/libcap.h +++ b/libcap/libcap.h @@ -253,6 +253,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]; -- cgit v1.2.3 From 73194f5369286e10c9e19cbd71de59c3b45b5789 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Fri, 22 Oct 2021 12:10:26 -0700 Subject: Make cap_launcher_t operations atomic. Modify the cap_launch() behavior when chroot is set. Now, the launcher code will force the post chroot() environment to chdir("/"). Modify the API for many of the cap_launch_*() functions that previously were void, to returning int (0=OK, -1=see errno). I'm confident that this should be code backwardly compatible, since the return values are new and prior code would have been assuming success. Signed-off-by: Andrew G. Morgan --- doc/cap_launch.3 | 14 +++++---- libcap/cap_proc.c | 64 +++++++++++++++++++++++++++++++++++------ libcap/include/sys/capability.h | 14 ++++----- libcap/libcap.h | 1 + 4 files changed, 72 insertions(+), 21 deletions(-) diff --git a/doc/cap_launch.3 b/doc/cap_launch.3 index 786bca3..95313ec 100644 --- a/doc/cap_launch.3 +++ b/doc/cap_launch.3 @@ -8,17 +8,18 @@ cap_launch_t cap_new_launcher(const char *arg0, const char * const *argv, 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 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. @@ -155,7 +156,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/libcap/cap_proc.c b/libcap/cap_proc.c index 8a10f75..22a307e 100644 --- a/libcap/cap_proc.c +++ b/libcap/cap_proc.c @@ -842,41 +842,69 @@ 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; } /* @@ -888,6 +916,11 @@ void cap_launcher_set_mode(cap_launch_t attr, cap_mode_t flavor) */ cap_iab_t cap_launcher_set_iab(cap_launch_t attr, cap_iab_t iab) { + 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) { @@ -896,6 +929,7 @@ cap_iab_t cap_launcher_set_iab(cap_launch_t attr, cap_iab_t iab) if (iab != NULL) { _cap_mu_lock(&iab->mutex); } + _cap_mu_unlock(&attr->mutex); return old; } @@ -903,9 +937,16 @@ 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) @@ -929,6 +970,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); @@ -1026,16 +1070,17 @@ pid_t cap_launch(cap_launch_t attr, void *detail) { 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(); @@ -1047,6 +1092,9 @@ pid_t cap_launch(cap_launch_t attr, void *detail) { _cap_launch(ps[1], attr, detail); /* 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/include/sys/capability.h b/libcap/include/sys/capability.h index 4f499dc..8719f61 100644 --- a/libcap/include/sys/capability.h +++ b/libcap/include/sys/capability.h @@ -218,14 +218,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/libcap.h b/libcap/libcap.h index 374ee7c..a22f69a 100644 --- a/libcap/libcap.h +++ b/libcap/libcap.h @@ -270,6 +270,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. -- cgit v1.2.3 From 140fa8438bde4e6affe8aefa6fbf077eae968686 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Fri, 22 Oct 2021 15:21:58 -0700 Subject: Bugfix for (*IAB).Fill() and improve atomicity of API. Improve atomicity of Launcher and IAB use within the cap package. Signed-off-by: Andrew G. Morgan --- cap/cap_test.go | 8 +++++--- cap/iab.go | 64 +++++++++++++++++++++++++++++++++++++++++++++++++-------- cap/launch.go | 48 ++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 105 insertions(+), 15 deletions(-) diff --git a/cap/cap_test.go b/cap/cap_test.go index 54f1e3f..52afd43 100644 --- a/cap/cap_test.go +++ b/cap/cap_test.go @@ -200,17 +200,19 @@ func TestIAB(t *testing.T) { t.Fatalf("failed to get init's capabilities: %v", err) } iab := NewIAB() - iab.Fill(Amb, one, Permitted) + 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/iab.go b/cap/iab.go index 90fc436..e10687c 100644 --- a/cap/iab.go +++ b/cap/iab.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "strconv" "strings" + "sync" ) // omask returns the offset and mask for a specific capability. @@ -20,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 } @@ -79,6 +81,20 @@ func NewIAB() *IAB { } } +// Dup returns a duplicate copy of the IAB. +func (iab *IAB) Dup() (*IAB, error) { + if iab == nil { + return nil, ErrBadValue + } + 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. @@ -161,6 +177,8 @@ func (iab *IAB) String() string { return "" } 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 @@ -182,6 +200,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 @@ -234,17 +254,24 @@ func (sc *syscaller) iabSetProc(iab *IAB) (err error) { // other bits, so this function carefully performs the the combined // operation in the most flexible manner. func (iab *IAB) SetProc() error { + if iab == nil { + return ErrBadValue + } 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 val >= MaxBits() { + if val >= MaxBits() || iab == nil { return false, ErrBadValue } + iab.mu.RLock() + defer iab.mu.RUnlock() offset, mask := omask(val) switch vec { case Inh: @@ -266,6 +293,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 iab == nil { + return ErrBadValue + } + iab.mu.Lock() + defer iab.mu.Unlock() for _, val := range vals { if val >= Value(maxValues) { return ErrBadValue @@ -306,18 +338,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 iab == nil { + return ErrBadValue + } + // 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: @@ -337,16 +376,23 @@ func (iab *IAB) Cf(alt *IAB) (IABDiff, error) { if iab == nil || alt == nil || len(iab.i) != words || len(alt.i) != words || len(iab.a) != words || len(alt.a) != words || len(iab.nb) != words || len(alt.nb) != words { return 0, ErrBadValue } + // Avoid holding two locks at once. + ref, err := alt.Dup() + if err != nil { + return 0, ErrBadValue + } + iab.mu.RLock() + defer iab.mu.RUnlock() var cf IABDiff for i := 0; i < words; i++ { - if iab.i[i] != alt.i[i] { + if iab.i[i] != ref.i[i] { cf |= iBits } - if iab.a[i] != alt.a[i] { + if iab.a[i] != ref.a[i] { cf |= aBits } - if iab.nb[i] != alt.nb[i] { + if iab.nb[i] != ref.nb[i] { cf |= bBits } } diff --git a/cap/launch.go b/cap/launch.go index 6145f3e..63959b4 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,20 +145,37 @@ 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 } @@ -283,6 +318,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 } @@ -343,6 +380,11 @@ 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 } -- cgit v1.2.3 From 3d60128581126f69e31ee963d47cef456ee9e371 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Fri, 22 Oct 2021 15:58:53 -0700 Subject: Concurrency fixes for *cap.Set atomicity. Previously, the atomicity was not uniformly enforced. Signed-off-by: Andrew G. Morgan --- cap/cap.go | 4 ++++ cap/file.go | 17 +++++++++----- cap/flags.go | 72 +++++++++++++++++++++++++++++++++++------------------------- cap/text.go | 1 + 4 files changed, 58 insertions(+), 36 deletions(-) diff --git a/cap/cap.go b/cap/cap.go index 5bbfea3..10c24a2 100644 --- a/cap/cap.go +++ b/cap/cap.go @@ -352,6 +352,8 @@ 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 @@ -371,6 +373,8 @@ func (sc *syscaller) setProc(c *Set) error { func (c *Set) SetProc() error { state, sc := scwStateSC() defer scwSetState(launchBlocked, state, -1) + c.mu.RLock() + defer c.mu.RUnlock() return sc.setProc(c) } diff --git a/cap/file.go b/cap/file.go index cfc171d..b433c9b 100644 --- a/cap/file.go +++ b/cap/file.go @@ -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: @@ -255,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 @@ -297,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 @@ -372,8 +377,8 @@ func (c *Set) Export() ([]byte, error) { } 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 57c0443..0463484 100644 --- a/cap/flags.go +++ b/cap/flags.go @@ -87,18 +87,19 @@ func (c *Set) FillFlag(to Flag, ref *Set, from Flag) error { return ErrBadValue } - // Avoid deadlock by copying to intermediate memory. - a := make([]uint32, len(ref.flat)) - ref.mu.Lock() - for i := range ref.flat { - a[i] = ref.flat[i][from] + // Avoid deadlock by using a copy. + if c != ref { + var err error + ref, err = ref.Dup() + if err != nil { + return err + } } - ref.mu.Unlock() c.mu.Lock() defer c.mu.Unlock() for i := range c.flat { - c.flat[i][to] = a[i] + c.flat[i][to] = ref.flat[i][from] } return nil } @@ -165,6 +166,40 @@ func (c *Set) ClearFlag(vec Flag) error { return c.forceFlag(vec, false) } +// 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 c == nil || len(c.flat) == 0 || d == nil || len(d.flat) == 0 { + return 0, ErrBadSet + } + 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 |= effectiveDiff + } + if c.flat[i][Permitted]^d.flat[i][Permitted] != 0 { + cf |= permittedDiff + } + if c.flat[i][Inheritable]^d.flat[i][Inheritable] != 0 { + cf |= inheritableDiff + } + } + return cf, nil +} + // Compare returns 0 if c and d are identical in content. // // Deprecated: Replace with (*Set).Cf(). @@ -199,29 +234,6 @@ func (c *Set) Compare(d *Set) (uint, error) { return uint(u), err } -// 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 c == nil || len(c.flat) == 0 || d == nil || len(d.flat) == 0 { - return 0, ErrBadSet - } - var cf Diff - for i := 0; i < words; i++ { - if c.flat[i][Effective]^d.flat[i][Effective] != 0 { - cf |= effectiveDiff - } - if c.flat[i][Permitted]^d.flat[i][Permitted] != 0 { - cf |= permittedDiff - } - if c.flat[i][Inheritable]^d.flat[i][Inheritable] != 0 { - cf |= inheritableDiff - } - } - return cf, nil -} - // Differs processes the result of Compare and determines if the // Flag's components were different. // diff --git a/cap/text.go b/cap/text.go index cf11a2d..7c3f540 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) -- cgit v1.2.3 From 687dc0b8fea42dd58a1b6ca8f50b28fe049bb2a7 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Fri, 22 Oct 2021 16:32:51 -0700 Subject: Unify the cap package tests for good *Set or *IAB types. We had somewhat inconsistent checks before, so this should cut down on corner cases to worry about. Signed-off-by: Andrew G. Morgan --- cap/cap.go | 18 +++++++++++++----- cap/file.go | 4 ++-- cap/flags.go | 28 +++++++++++++++++----------- cap/iab.go | 39 +++++++++++++++++++++++++-------------- cap/text.go | 2 +- 5 files changed, 58 insertions(+), 33 deletions(-) diff --git a/cap/cap.go b/cap/cap.go index 10c24a2..784bf65 100644 --- a/cap/cap.go +++ b/cap/cap.go @@ -318,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() @@ -355,9 +363,6 @@ func GetProc() *Set { // 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) } @@ -371,6 +376,9 @@ 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() diff --git a/cap/file.go b/cap/file.go index b433c9b..9a5b10c 100644 --- a/cap/file.go +++ b/cap/file.go @@ -369,8 +369,8 @@ 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 diff --git a/cap/flags.go b/cap/flags.go index 0463484..c4c2ed8 100644 --- a/cap/flags.go +++ b/cap/flags.go @@ -5,10 +5,10 @@ import "errors" // 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 @@ -80,8 +80,11 @@ func (c *Set) Clear() error { // 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 c == nil || len(c.flat) == 0 || ref == nil || len(ref.flat) == 0 { - return ErrBadSet + if err := c.good(); err != nil { + return err + } + if err := ref.good(); err != nil { + return err } if to > Inheritable || from > Inheritable { return ErrBadValue @@ -146,7 +149,10 @@ func allMask(index uint) (mask uint32) { // forceFlag sets 'all' capability values (supported by the kernel) of // 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) @@ -171,8 +177,8 @@ func (c *Set) ClearFlag(vec Flag) error { // (Diff).Has() function can be used to determine how the two // capability sets differ. func (c *Set) Cf(d *Set) (Diff, error) { - if c == nil || len(c.flat) == 0 || d == nil || len(d.flat) == 0 { - return 0, ErrBadSet + if err := c.good(); err != nil { + return 0, err } if c == d { return 0, nil diff --git a/cap/iab.go b/cap/iab.go index e10687c..9199e57 100644 --- a/cap/iab.go +++ b/cap/iab.go @@ -81,10 +81,18 @@ func NewIAB() *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 iab == nil { - return nil, ErrBadValue + if err := iab.good(); err != nil { + return nil, err } v := NewIAB() iab.mu.RLock() @@ -173,7 +181,7 @@ func IABFromText(text string) (*IAB, error) { // String serializes an IAB to a string format. func (iab *IAB) String() string { - if iab == nil { + if err := iab.good(); err != nil { return "" } var vs []string @@ -254,8 +262,8 @@ func (sc *syscaller) iabSetProc(iab *IAB) (err error) { // other bits, so this function carefully performs the the combined // operation in the most flexible manner. func (iab *IAB) SetProc() error { - if iab == nil { - return ErrBadValue + if err := iab.good(); err != nil { + return err } state, sc := scwStateSC() defer scwSetState(launchBlocked, state, -1) @@ -267,7 +275,10 @@ func (iab *IAB) SetProc() error { // 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 val >= MaxBits() || iab == nil { + if err := iab.good(); err != nil { + return false, err + } + if val >= MaxBits() { return false, ErrBadValue } iab.mu.RLock() @@ -293,8 +304,8 @@ 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 iab == nil { - return ErrBadValue + if err := iab.good(); err != nil { + return err } iab.mu.Lock() defer iab.mu.Unlock() @@ -338,8 +349,8 @@ 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 iab == nil { - return ErrBadValue + if err := iab.good(); err != nil { + return err } // work with a copy to avoid potential deadlock. s, err := c.Dup() @@ -370,16 +381,16 @@ func (iab *IAB) Fill(vec Vector, c *Set, flag Flag) error { // 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 } - if iab == nil || alt == nil || len(iab.i) != words || len(alt.i) != words || len(iab.a) != words || len(alt.a) != words || len(iab.nb) != words || len(alt.nb) != words { - return 0, ErrBadValue - } // Avoid holding two locks at once. ref, err := alt.Dup() if err != nil { - return 0, ErrBadValue + return 0, err } iab.mu.RLock() defer iab.mu.RUnlock() diff --git a/cap/text.go b/cap/text.go index 7c3f540..42cb7fc 100644 --- a/cap/text.go +++ b/cap/text.go @@ -85,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 "" } bins := make([]int, 8) -- cgit v1.2.3 From e4725c1a9e1bc7b72e56bd8251ae4058d15a5e28 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Fri, 22 Oct 2021 20:50:43 -0700 Subject: Add --quiet and cap_launch() support to capsh. The flag --quiet causes capsh to suppress its startup check that the linked libcap has support for all of the named capabilities of the hosting kernel. The cap_launch() support is via "-+" and "=+" arguments. These use cap_launch() to fork() before exec*()ing the corresponding command but are otherwise equivalent to "--" and "==" respectively. Signed-off-by: Andrew G. Morgan --- doc/capsh.1 | 21 +++++++++++++-- progs/capsh.c | 83 ++++++++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 87 insertions(+), 17 deletions(-) diff --git a/doc/capsh.1 b/doc/capsh.1 index 60d3ad2..aafe3fb 100644 --- a/doc/capsh.1 +++ b/doc/capsh.1 @@ -1,4 +1,4 @@ -.TH CAPSH 1 "2021-08-29" "libcap" "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 . @@ -317,6 +328,12 @@ Removes the specified ambient capability from the running process. .TP .B \-\-noamb Drops all ambient capabilities from the running process. +.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 diff --git a/progs/capsh.c b/progs/capsh.c index a5b7b08..2295359 100644 --- a/progs/capsh.c +++ b/progs/capsh.c @@ -439,27 +439,68 @@ 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; - int strict = 0; - const char *shell = SHELL; + int ret, result; - child = 0; - - char *temp_name = cap_to_name(cap_max_bits() - 1); - if (temp_name == NULL) { - perror("obtaining highest capability name"); + 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); } - if (temp_name[0] != 'c') { - printf("WARNING: libcap needs an update (cap=%d should have a name).\n", - cap_max_bits() - 1); + 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); } - cap_free(temp_name); + 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; + const char *shell = SHELL; for (i=1; i\n", argv[0]); exit(0); @@ -1090,6 +1140,7 @@ int main(int argc, char *argv[], char *envp[]) " --no-new-privs set sticky process privilege limiter\n" " --noamb reset (drop) all ambient capabilities\n" " --print display capability relevant state\n" + " --quiet if first argument skip max cap check\n" " --secbits= 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" @@ -1098,7 +1149,9 @@ int main(int argc, char *argv[], char *envp[]) " --uid= set uid to (hint: id )\n" " --user= 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])) { -- cgit v1.2.3 From 5306fa23ff92832be949b28d86eec39b54fbee26 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Fri, 22 Oct 2021 21:29:14 -0700 Subject: Up the release version to 2.60 Signed-off-by: Andrew G. Morgan --- Make.Rules | 2 +- cap/go.mod | 2 +- contrib/seccomp/go.mod | 2 +- go/go.mod | 4 ++-- goapps/captree/go.mod | 2 +- goapps/gowns/go.mod | 2 +- goapps/setid/go.mod | 4 ++-- goapps/web/go.mod | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Make.Rules b/Make.Rules index d9147fc..e654504 100644 --- a/Make.Rules +++ b/Make.Rules @@ -1,7 +1,7 @@ # Common version number defines for libcap LIBTITLE=libcap VERSION=2 -MINOR=59 +MINOR=60 # ## Optional prefixes: diff --git a/cap/go.mod b/cap/go.mod index e9bc6f1..755e9d2 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.59 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.60 diff --git a/contrib/seccomp/go.mod b/contrib/seccomp/go.mod index af85501..cf1bc0e 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.59 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.60 diff --git a/go/go.mod b/go/go.mod index 0752c98..0a5a2d4 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.59 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.59 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.60 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.60 ) diff --git a/goapps/captree/go.mod b/goapps/captree/go.mod index 1e8ae5d..cccfd5e 100644 --- a/goapps/captree/go.mod +++ b/goapps/captree/go.mod @@ -2,4 +2,4 @@ module captree go 1.16 -require kernel.org/pub/linux/libs/security/libcap/cap v1.2.59 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.60 diff --git a/goapps/gowns/go.mod b/goapps/gowns/go.mod index 3fa4244..879ea6d 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.59 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.60 diff --git a/goapps/setid/go.mod b/goapps/setid/go.mod index 9f55c17..e785d4f 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.59 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.59 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.60 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.60 ) diff --git a/goapps/web/go.mod b/goapps/web/go.mod index 98143bf..a214f27 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.59 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.60 -- cgit v1.2.3 From 8e1e967bc8d99a3233d51f67f6b88620cdff78dc Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 6 Nov 2021 08:02:20 -0700 Subject: setcap: clean up error handling of the ns rootid argument. Bug reported by Artem S. Tashkinov: https://bugzilla.kernel.org/show_bug.cgi?id=214909 Signed-off-by: Andrew G. Morgan --- progs/setcap.c | 41 +++++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/progs/setcap.c b/progs/setcap.c index f8be53a..02a8a5d 100644 --- a/progs/setcap.c +++ b/progs/setcap.c @@ -26,13 +26,42 @@ static void usage(int status) " -h this message and exit status 0\n" " -q quietly\n" " -v validate supplied capability matches file\n" - " -n write a user namespace limited capability\n" + " -n 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) { @@ -99,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,21 Andrew G. Morgan" + "Copyright (c) 1997,2007-8,2020-21 Andrew G. Morgan" " \n", argv[0]); exit(0); } @@ -117,11 +146,7 @@ int main(int argc, char **argv) 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; } -- cgit v1.2.3 From 9c4997d6592e5daf046a6968ac83cf615c51fbe1 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 6 Nov 2021 08:45:06 -0700 Subject: capsh: better error handling for integer parsing. Bug reported by meitingli: https://bugzilla.kernel.org/show_bug.cgi?id=214911 Signed-off-by: Andrew G. Morgan --- progs/capsh.c | 49 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/progs/capsh.c b/progs/capsh.c index 2295359..4f568c3 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]; @@ -667,7 +696,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", @@ -724,7 +753,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", @@ -737,8 +766,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(); @@ -753,7 +783,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); @@ -779,7 +810,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", @@ -790,7 +821,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", @@ -801,7 +832,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", @@ -1009,7 +1040,7 @@ int main(int argc, char *argv[], char *envp[]) } 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); @@ -1018,7 +1049,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); -- cgit v1.2.3 From 2b763ab706891255e8cef9f36ca023e32f5d5991 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 7 Nov 2021 09:38:47 -0800 Subject: An example of a shared library object with its own file capability. I've been exploring the idea of how to create limited use privileged binaries that can be linked into otherwise unprivileged binaries. This is a worked example of the bootstrapping process for a webserver. I intend to provide a more complete writeup of what is going on with this example here: https://sites.google.com/site/fullycapable/capable-shared-objects For this present example to work you have to be using a libcap that includes cap_launch support (ie., libcap 2.33+, but this code will be included with libcap-2.61 and might inadvertently actually require something that new to work robustly). This code appears to be very fragile at present. It works on my Chromebook's linux container, but not under Fedora 34 - segfaulting. Signed-off-by: Andrew G. Morgan --- contrib/capso/Makefile | 23 +++++ contrib/capso/README.md | 20 ++++ contrib/capso/bind.c | 29 ++++++ contrib/capso/capso.c | 265 ++++++++++++++++++++++++++++++++++++++++++++++++ contrib/capso/capso.h | 16 +++ 5 files changed, 353 insertions(+) create mode 100644 contrib/capso/Makefile create mode 100644 contrib/capso/README.md create mode 100644 contrib/capso/bind.c create mode 100644 contrib/capso/capso.c create mode 100644 contrib/capso/capso.h diff --git a/contrib/capso/Makefile b/contrib/capso/Makefile new file mode 100644 index 0000000..87ed34b --- /dev/null +++ b/contrib/capso/Makefile @@ -0,0 +1,23 @@ +topdir=$(shell pwd)/../.. +include ../../Make.Rules + +# Always build sources this way: +CFLAGS += -fPIC + +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..64e9b43 --- /dev/null +++ b/contrib/capso/README.md @@ -0,0 +1,20 @@ +# 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" ./capso.so file to bind to the privileged +port 80. + +A writeup of how to explore this example is provided here: + +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 +#include +#include +#include +#include + +#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..13710e2 --- /dev/null +++ b/contrib/capso/capso.c @@ -0,0 +1,265 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "capso.h" + +/* + * 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; + + memset(&conf, 0, sizeof(conf)); + 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); + } + + 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) +{ + char const *args[3]; + char *path; + int fd, ignored; + int sp[2]; + struct iovec iov[1]; + char junk[1]; + const int rec_buf_len = CMSG_SPACE(sizeof(int)); + char *rec_buf[CMSG_SPACE(sizeof(int))]; + struct msghdr msg; + cap_launch_t helper; + pid_t child; + + fd = try_bind80(hostname); + if (fd >= 0) { + return fd; + } + + /* + * 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"); + return -1; + } + + args[0] = "bind80-helper"; + args[1] = hostname; + args[2] = NULL; + + helper = cap_new_launcher(path, args, NULL); + 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); + + return fd; +} + +#include "../../libcap/execable.h" +//#define SO_MAIN int main + +SO_MAIN(int argc, char **argv) +{ + const char *cmd = ""; + 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]; + + if (argv != NULL) { + cmd = argv[0]; + } + + if (argc != 2 || argv[1] == NULL || !strcmp(argv[1], "--help")) { + fprintf(stderr, "usage: %s \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("cap_set_proc problem"); + 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"); + } + + 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 */ -- cgit v1.2.3 From 95085598764ca6c874dc1ee48364dbd2921fc000 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 7 Nov 2021 15:09:13 -0800 Subject: Use calloc'd memory for unix domain socket exchange. This seems more stable for passing file descriptor from privileged child to unprivileged parent. Signed-off-by: Andrew G. Morgan --- contrib/capso/capso.c | 67 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/contrib/capso/capso.c b/contrib/capso/capso.c index 13710e2..3f2f3ba 100644 --- a/contrib/capso/capso.c +++ b/contrib/capso/capso.c @@ -47,16 +47,20 @@ static char *where_am_i(void) */ static int try_bind80(const char *hostname) { - struct addrinfo conf, *detail = NULL; + struct addrinfo *conf, *detail = NULL; int err, ret = -1, one = 1; - memset(&conf, 0, sizeof(conf)); - conf.ai_family = PF_UNSPEC; - conf.ai_socktype = SOCK_STREAM; - conf.ai_protocol = 0; - conf.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; + 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); + err = getaddrinfo(hostname, "80", conf, &detail); if (err != 0) { goto done; } @@ -78,10 +82,11 @@ static int try_bind80(const char *hostname) goto done; } -done: + done: if (detail != NULL) { freeaddrinfo(detail); } + free(conf); return ret; } @@ -116,23 +121,33 @@ static int set_fd3(void *detail) */ int bind80(const char *hostname) { + cap_launch_t helper; + pid_t child; char const *args[3]; char *path; int fd, ignored; int sp[2]; - struct iovec iov[1]; char junk[1]; const int rec_buf_len = CMSG_SPACE(sizeof(int)); char *rec_buf[CMSG_SPACE(sizeof(int))]; - struct msghdr msg; - cap_launch_t helper; - pid_t child; + struct iovec *iov; + struct msghdr *msg; fd = try_bind80(hostname); if (fd >= 0) { return fd; } + 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 @@ -141,7 +156,7 @@ int bind80(const char *hostname) path = where_am_i(); if (path == NULL) { perror("unable to find self"); - return -1; + goto drop_alloc; } args[0] = "bind80-helper"; @@ -168,27 +183,31 @@ int bind80(const char *hostname) 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; + 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)); + if (recvmsg(sp[0], msg, 0) != -1) { + fd = * (int *) CMSG_DATA(CMSG_FIRSTHDR(msg)); } waitpid(child, &ignored, 0); -drop_sp: + drop_sp: close(sp[0]); -drop_helper: + drop_helper: cap_free(helper); -drop_path: + drop_path: free(path); + drop_alloc: + free(msg); + free(iov); + return fd; } -- cgit v1.2.3 From 2e4335523e0a705b4ce6b49b1bfccfe519f559ce Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Tue, 9 Nov 2021 07:28:59 -0800 Subject: Include some helpful debugging for capso.so This makes explaining how the program works more straightforward. That is: make CAPSO_DEBUG=-DCAPSO_DEBUG clean all builds a version that prints out some helpful info and pauses so the user can observe the capability state of the process tree at different stages of execution. Signed-off-by: Andrew G. Morgan --- contrib/capso/Makefile | 2 +- contrib/capso/capso.c | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/contrib/capso/Makefile b/contrib/capso/Makefile index 87ed34b..70af7f9 100644 --- a/contrib/capso/Makefile +++ b/contrib/capso/Makefile @@ -2,7 +2,7 @@ topdir=$(shell pwd)/../.. include ../../Make.Rules # Always build sources this way: -CFLAGS += -fPIC +CFLAGS += -fPIC $(CAPSO_DEBUG) all: bind diff --git a/contrib/capso/capso.c b/contrib/capso/capso.c index 3f2f3ba..2a357c0 100644 --- a/contrib/capso/capso.c +++ b/contrib/capso/capso.c @@ -138,6 +138,11 @@ int bind80(const char *hostname) 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; @@ -226,6 +231,11 @@ SO_MAIN(int argc, char **argv) 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]; } @@ -280,5 +290,10 @@ SO_MAIN(int argc, char **argv) perror("failed to write fd"); } +#ifdef CAPSO_DEBUG + printf("exiting standalone %s\n", argv[0]); + sleep(30); +#endif + exit(0); } -- cgit v1.2.3 From 0c463bf38d48dd9b725a5fa491e0728e04c37b94 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Wed, 10 Nov 2021 23:28:41 -0800 Subject: Pick the correct topdir in our sucap example. Signed-off-by: Andrew G. Morgan --- contrib/sucap/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/sucap/Makefile b/contrib/sucap/Makefile index 407e5ec..c58d5c4 100644 --- a/contrib/sucap/Makefile +++ b/contrib/sucap/Makefile @@ -1,4 +1,4 @@ -topdir=$(shell pwd)/.. +topdir=$(shell pwd)/../.. include ../../Make.Rules all: su -- cgit v1.2.3 From c234bf90839f19e0332b586335411cb626a25a18 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 13 Nov 2021 20:38:18 -0800 Subject: Work around a __i386__ compilation issue for runnable .so files. This was reported by Sam James and debugged with respect to: https://bugs.gentoo.org/show_bug.cgi?id=820071 Modern versions of glibc employ SSE instructions that require the stack to be aligned to 16 bytes in order to execute movaps and friends to stack stored memory. The ABI for x86_64 requires this alignment so we'd not seen this issue before being cc:d into the bug. Signed-off-by: Andrew G. Morgan --- libcap/execable.h | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/libcap/execable.h b/libcap/execable.h index 0bcc5d4..229dd2f 100644 --- a/libcap/execable.h +++ b/libcap/execable.h @@ -74,20 +74,26 @@ static void __execable_parse_args(int *argc_p, char ***argv_p) * Note, to avoid any runtime confusion, SO_MAIN is a void static * function. */ +#if defined(__i386__) +#define __SO_FORCE_ARG_ALIGNMENT __attribute__((force_align_arg_pointer)) +#else +#define __SO_FORCE_ARG_ALIGNMENT +#endif /* def __i386 */ -#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**); \ +extern void __so_start(void); \ +__SO_FORCE_ARG_ALIGNMENT \ +void __so_start(void) \ +{ \ + int argc; \ + char **argv; \ + __execable_parse_args(&argc, &argv); \ __execable_main(argc, argv); \ - if (argc != 0) { \ - free(argv[0]); \ - free(argv); \ - } \ - exit(0); \ -} \ + if (argc != 0) { \ + free(argv[0]); \ + free(argv); \ + } \ + exit(0); \ +} \ static void __execable_main -- cgit v1.2.3 From e9414f540a82b5348a12cfaddff229241564e1f3 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 14 Nov 2021 20:38:30 -0800 Subject: Work around musl not hard-coding the ABI for Linux x86_64. There seems to be a subtle difference between glibc and musl over whether or not a runnable *.so needs to start out with its stack aligned to 16 bytes or not. Since Linux ABIs for x86 (both 32 and 64 bit varieties) require 16 byte alignment, just force it on both these architectures. This addresses: https://bugzilla.kernel.org/show_bug.cgi?id=215009 Signed-off-by: Andrew G. Morgan --- libcap/execable.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/libcap/execable.h b/libcap/execable.h index 229dd2f..7cf9b34 100644 --- a/libcap/execable.h +++ b/libcap/execable.h @@ -71,15 +71,19 @@ static void __execable_parse_args(int *argc_p, char ***argv_p) } /* - * Note, to avoid any runtime confusion, SO_MAIN is a void static - * function. + * Linux x86 ABI requires the stack be 16 byte aligned. Keep things + * simple and just force it. */ -#if defined(__i386__) +#if defined(__i386__) || defined(__x86_64__) #define __SO_FORCE_ARG_ALIGNMENT __attribute__((force_align_arg_pointer)) #else #define __SO_FORCE_ARG_ALIGNMENT -#endif /* def __i386 */ +#endif /* def some x86 */ +/* + * 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); \ -- cgit v1.2.3 From 9fa2fe739c2eb4960f0417a1e5f8dcccbfd3d49b Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 14 Nov 2021 21:11:23 -0800 Subject: Force libcap.so to initialize itself when run directly. Not sure where this will go, but libcap.so uses _libcap_initialize() to set itself up at start up. So, run it when invoking libcap.so directly as a binary. Signed-off-by: Andrew G. Morgan --- libcap/Makefile | 4 ++-- libcap/cap_alloc.c | 10 +++++++++- libcap/execable.h | 8 ++++++++ libcap/libcap.h | 3 +++ 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/libcap/Makefile b/libcap/Makefile index 7706063..967820e 100644 --- a/libcap/Makefile +++ b/libcap/Makefile @@ -107,8 +107,8 @@ ifeq ($(SHARED),yes) loader.txt: empty $(OBJCOPY) --dump-section .interp=$@ $< /dev/null -cap_magic.o: execable.h execable.c loader.txt - $(CC) $(CFLAGS) $(CPPFLAGS) -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) diff --git a/libcap/cap_alloc.c b/libcap/cap_alloc.c index 6a674f4..600b7cd 100644 --- a/libcap/cap_alloc.c +++ b/libcap/cap_alloc.c @@ -7,18 +7,26 @@ #include "libcap.h" +/* + * 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) +__attribute__((constructor (300))) void _libcap_initialize() { + _cap_mu_lock(&__libcap_mutex); if (_cap_max_bits) { + _cap_mu_unlock(&__libcap_mutex); return; } cap_set_syscall(NULL, NULL); _binary_search(_cap_max_bits, cap_get_bound, 0, __CAP_MAXBITS, __CAP_BITS); + _cap_mu_unlock(&__libcap_mutex); } cap_value_t cap_max_bits(void) diff --git a/libcap/execable.h b/libcap/execable.h index 7cf9b34..fee17b4 100644 --- a/libcap/execable.h +++ b/libcap/execable.h @@ -80,6 +80,13 @@ static void __execable_parse_args(int *argc_p, char ***argv_p) #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. @@ -93,6 +100,7 @@ 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]); \ diff --git a/libcap/libcap.h b/libcap/libcap.h index a22f69a..f4a72fe 100644 --- a/libcap/libcap.h +++ b/libcap/libcap.h @@ -202,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 -- cgit v1.2.3 From e0582d0aee30032190222c110d72d7be22c715af Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Wed, 17 Nov 2021 09:21:31 -0800 Subject: Prune the the. Signed-off-by: Andrew G. Morgan --- cap/file.go | 2 +- cap/iab.go | 2 +- doc/cap_iab.3 | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cap/file.go b/cap/file.go index 9a5b10c..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") diff --git a/cap/iab.go b/cap/iab.go index 9199e57..da189be 100644 --- a/cap/iab.go +++ b/cap/iab.go @@ -259,7 +259,7 @@ 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 { diff --git a/doc/cap_iab.3 b/doc/cap_iab.3 index 4a55d0b..8bec5fd 100644 --- a/doc/cap_iab.3 +++ b/doc/cap_iab.3 @@ -1,4 +1,4 @@ -.TH CAP_IAB 3 "2021-09-01" "" "Linux Programmer's Manual" +.TH CAP_IAB 3 "2021-11-17" "" "Linux Programmer's Manual" .SH NAME .nf #include @@ -45,7 +45,7 @@ set of rules when the executed file has no configured There are some constraints enforced by the kernel with respect to the 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; and the Bound (or \fIblocked\fP) vector is the twos-complement of the process bounding vector. -- cgit v1.2.3 From f7deb5ad451458eeec20f9e7546d731e932bec2c Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 20 Nov 2021 16:36:44 -0800 Subject: Add some information about cap_max_bits(). This function has been defined for a while (since libcap-2.30), but I just found it wasn't documented. Signed-off-by: Andrew G. Morgan --- doc/cap_clear.3 | 16 +++++++++++++++- doc/cap_max_bits.3 | 1 + doc/libcap.3 | 1 + 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 doc/cap_max_bits.3 diff --git a/doc/cap_clear.3 b/doc/cap_clear.3 index a1c40d5..0321515 100644 --- a/doc/cap_clear.3 +++ b/doc/cap_clear.3 @@ -15,6 +15,7 @@ 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. @@ -108,6 +109,14 @@ evaluates to non-zero if the returned 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 (), @@ -117,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 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/libcap.3 b/doc/libcap.3 index b8c8520..c1ae4ba 100644 --- a/doc/libcap.3 +++ b/doc/libcap.3 @@ -21,6 +21,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 -- cgit v1.2.3 From 4c7dde9f519095f74d19982fb3c1d05fb1ff46be Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 20 Nov 2021 16:55:58 -0800 Subject: Add some more info to the libcap.so as an executable. Mostly cause we can, but this gives a little more diagnostic value to the libcap.so executable mode of operation. usage: libcap.so [--help|--usage|--summary] Signed-off-by: Andrew G. Morgan --- libcap/Makefile | 3 +++ libcap/execable.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/libcap/Makefile b/libcap/Makefile index 967820e..05fa90d 100644 --- a/libcap/Makefile +++ b/libcap/Makefile @@ -135,6 +135,9 @@ cap_test: cap_test.c $(INCLS) $(CAPOBJS) libcapsotest: $(CAPLIBNAME) ./$(CAPLIBNAME) + ./$(CAPLIBNAME) --usage + ./$(CAPLIBNAME) --help + ./$(CAPLIBNAME) --summary libpsxsotest: $(PSXLIBNAME) ./$(PSXLIBNAME) diff --git a/libcap/execable.c b/libcap/execable.c index 5e7a88f..9d3ae7f 100644 --- a/libcap/execable.c +++ b/libcap/execable.c @@ -1,9 +1,44 @@ #include +#include +#include + #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); + } + } + printf("\n"); +} + SO_MAIN(int argc, char **argv) { + int i; const char *cmd = "This library"; + if (argv != NULL && argv[0] != NULL) { cmd = argv[0]; } @@ -12,4 +47,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); + } } -- cgit v1.2.3 From 1e9320e48e74ec9a36a77b609463c867e271f8b9 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 20 Nov 2021 22:16:08 -0800 Subject: Clean up libpsx.so execable mode. The previous commit crossed the beams on libpsx.so and libcap.so executable build. This commit decouples them. Signed-off-by: Andrew G. Morgan --- libcap/Makefile | 4 ++-- libcap/psx_exec.c | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 libcap/psx_exec.c diff --git a/libcap/Makefile b/libcap/Makefile index 05fa90d..df1267a 100644 --- a/libcap/Makefile +++ b/libcap/Makefile @@ -115,8 +115,8 @@ $(CAPLIBNAME) $(MAJCAPLIBNAME) $(MINCAPLIBNAME): $(CAPOBJS) $(CAPMAGICOBJ) ln -sf $(MINCAPLIBNAME) $(MAJCAPLIBNAME) ln -sf $(MAJCAPLIBNAME) $(CAPLIBNAME) -psx_magic.o: execable.h execable.c loader.txt - $(CC) $(CFLAGS) $(CPPFLAGS) -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) 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 +#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); +} -- cgit v1.2.3 From d63835d4913f2f2501f749ab08697c1c3c27474c Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 20 Nov 2021 22:20:04 -0800 Subject: Up the release version to 2.61 Signed-off-by: Andrew G. Morgan --- Make.Rules | 2 +- cap/go.mod | 2 +- contrib/seccomp/go.mod | 2 +- go/go.mod | 4 ++-- goapps/captree/go.mod | 2 +- goapps/gowns/go.mod | 2 +- goapps/setid/go.mod | 4 ++-- goapps/web/go.mod | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Make.Rules b/Make.Rules index e654504..70d5829 100644 --- a/Make.Rules +++ b/Make.Rules @@ -1,7 +1,7 @@ # Common version number defines for libcap LIBTITLE=libcap VERSION=2 -MINOR=60 +MINOR=61 # ## Optional prefixes: diff --git a/cap/go.mod b/cap/go.mod index 755e9d2..3f84806 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.60 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.61 diff --git a/contrib/seccomp/go.mod b/contrib/seccomp/go.mod index cf1bc0e..53c27d4 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.60 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.61 diff --git a/go/go.mod b/go/go.mod index 0a5a2d4..98e2df8 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.60 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.60 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.61 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.61 ) diff --git a/goapps/captree/go.mod b/goapps/captree/go.mod index cccfd5e..9ea7efe 100644 --- a/goapps/captree/go.mod +++ b/goapps/captree/go.mod @@ -2,4 +2,4 @@ module captree go 1.16 -require kernel.org/pub/linux/libs/security/libcap/cap v1.2.60 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.61 diff --git a/goapps/gowns/go.mod b/goapps/gowns/go.mod index 879ea6d..df15100 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.60 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.61 diff --git a/goapps/setid/go.mod b/goapps/setid/go.mod index e785d4f..6c09a41 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.60 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.60 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.61 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.61 ) diff --git a/goapps/web/go.mod b/goapps/web/go.mod index a214f27..fa4ff4c 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.60 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.61 -- cgit v1.2.3 From 42555598bf70dde98d1aaf5e3967bd9dbc2e5112 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 20 Nov 2021 23:51:43 -0800 Subject: Recognize default secbits of 0 as "HYBRID" mode. Signed-off-by: Andrew G. Morgan --- cap/convenience.go | 10 ++++++++++ go/compare-cap.go | 4 ++-- libcap/cap_proc.c | 7 ++++++- libcap/cap_text.c | 2 ++ libcap/include/sys/capability.h | 1 + 5 files changed, 21 insertions(+), 3 deletions(-) diff --git a/cap/convenience.go b/cap/convenience.go index e832981..a31ac09 100644 --- a/cap/convenience.go +++ b/cap/convenience.go @@ -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 } @@ -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" } diff --git a/go/compare-cap.go b/go/compare-cap.go index f2a7d6b..5e489e5 100644 --- a/go/compare-cap.go +++ b/go/compare-cap.go @@ -158,8 +158,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/libcap/cap_proc.c b/libcap/cap_proc.c index 22a307e..db947f4 100644 --- a/libcap/cap_proc.c +++ b/libcap/cap_proc.c @@ -488,7 +488,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; @@ -524,6 +526,9 @@ 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; } diff --git a/libcap/cap_text.c b/libcap/cap_text.c index 8dfe9f8..7566bd8 100644 --- a/libcap/cap_text.c +++ b/libcap/cap_text.c @@ -503,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"; } diff --git a/libcap/include/sys/capability.h b/libcap/include/sys/capability.h index 8719f61..cb96d82 100644 --- a/libcap/include/sys/capability.h +++ b/libcap/include/sys/capability.h @@ -122,6 +122,7 @@ 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); -- cgit v1.2.3 From 2ec5fdcdd5280b36e72957febac142b961fe63b2 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 21 Nov 2021 10:10:44 -0800 Subject: Include link for cap_max_bits.3 man page. Signed-off-by: Andrew G. Morgan --- doc/Makefile | 2 +- doc/cap_clear.3 | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/doc/Makefile b/doc/Makefile index f014b28..11b9370 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -8,7 +8,7 @@ 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_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_copy_ext.3 cap_size.3 cap_copy_int.3 cap_mode.3 \ diff --git a/doc/cap_clear.3 b/doc/cap_clear.3 index 0321515..19a736a 100644 --- a/doc/cap_clear.3 +++ b/doc/cap_clear.3 @@ -139,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), -- cgit v1.2.3 From 9d3c75818196802bb5bc3c43bb4a52fb89a83e8c Mon Sep 17 00:00:00 2001 From: David Seifert Date: Sun, 28 Nov 2021 14:14:42 +0100 Subject: Fix `-Wformat` on 32-bit platforms Signed-off-by: Andrew G. Morgan --- libcap/cap_test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcap/cap_test.c b/libcap/cap_test.c index b7fb2c5..39df261 100644 --- a/libcap/cap_test.c +++ b/libcap/cap_test.c @@ -124,7 +124,7 @@ static int test_short_bits(void) } if (strlen(tmp) > __CAP_NAME_SIZE) { printf("cap_to_text buffer size reservation needs fixing (%ld > %d)\n", - strlen(tmp), __CAP_NAME_SIZE); + (long int)strlen(tmp), __CAP_NAME_SIZE); result = -1; } free(tmp); -- cgit v1.2.3 From 1fe7dbe984adef5cf9d0f58b02acd6f2b72c6993 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 4 Dec 2021 09:51:07 -0800 Subject: Drop perl from the build requirements. David Seifert at Gentoo made a request to not require perl for the libcap build since their distribution wants to build it prior to building perl and so requiring it requires they maintain some extra patches. We previously introduced the need for perl in response to some apparent incompatibilities between various versions of sed: https://git.kernel.org/pub/scm/libs/libcap/libcap.git/commit/?id=9494a1fab59ac0b6e4f0bfc536fa482c6d6490b6 However, it has been 13 years since that time so we're optimistic those problems are no longer present for anyone and we've also added a make variable abstraction in case some builder wants to override their system default 'sed' as make BUILD_SED=... etc. We've also done something similar with make uses of grep, egrep and fgrep. Finally, for make variable naming consistency, we've replaced use of BUILD_GPERF with USE_GPERF. Since folk may be using BUILD_GPERF in their package building scripts, we error out if it is set. The expectation is that people will update their package defs. (Eventually, we plan to reuse BUILD_GPERF as an alias for 'gperf'.) Signed-off-by: Andrew G. Morgan --- Make.Rules | 14 +++++++++++++- Makefile | 2 +- doc/Makefile | 2 +- libcap/Makefile | 20 ++++++++++++-------- libcap/cap_names.header | 5 +++++ 5 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 libcap/cap_names.header diff --git a/Make.Rules b/Make.Rules index 70d5829..eaaf3d6 100644 --- a/Make.Rules +++ b/Make.Rules @@ -92,7 +92,19 @@ BUILD_COPTS ?= $(COPTS) BUILD_CFLAGS ?= $(BUILD_COPTS) BUILD_CPPFLAGS += -Dlinux $(WARNINGS) $(DEBUG) $(DEFINES) $(LIBCAP_INCLUDES) BUILD_LDFLAGS ?= $(LDFLAGS) -BUILD_GPERF := $(shell which gperf >/dev/null 2>/dev/null && echo yes) +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 diff --git a/Makefile b/Makefile index 9ee11c8..256c339 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ clean-here: distclean: clean $(DISTCLEAN) @echo "CONFIRM Go package cap has right version dependency on cap/psx:" - for x in $$(find . -name go.mod); do grep -F -v "module" $$x | fgrep "kernel.org/pub/linux/libs/security/libcap" > /dev/null || continue ; grep -F "v$(GOMAJOR).$(VERSION).$(MINOR)" $$x > /dev/null && continue ; echo "$$x is not updated. 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 "Now validate that everything is checked in to a clean tree.." test -z "$$(git status --ignored -s)" diff --git a/doc/Makefile b/doc/Makefile index 11b9370..e78eb6e 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -44,7 +44,7 @@ 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/libcap/Makefile b/libcap/Makefile index df1267a..7a38fe9 100644 --- a/libcap/Makefile +++ b/libcap/Makefile @@ -52,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,' \ @@ -67,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,' \ @@ -81,18 +81,22 @@ _makenames: _makenames.c cap_names.list.h 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 $@ $^ 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); +%} +%% -- cgit v1.2.3 From 806b53d13a792d834622b2e546cfdceecc5af699 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Fri, 10 Dec 2021 20:59:19 -0800 Subject: Take more care post launch Lorenz Bauer found a race condition in the cap.Launcher teardown process and reported it here: https://bugzilla.kernel.org/show_bug.cgi?id=215283 This seems to significantly improve the situation. I'm going to study the test case some more, but this is definitely part of the solution. Signed-off-by: Andrew G. Morgan --- cap/launch.go | 11 +++++++---- go/.gitignore | 2 ++ go/Makefile | 18 ++++++++++++++++-- go/b215283.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 go/b215283.go diff --git a/cap/launch.go b/cap/launch.go index 63959b4..b67a856 100644 --- a/cap/launch.go +++ b/cap/launch.go @@ -257,6 +257,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. @@ -266,10 +272,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 @@ -394,6 +396,7 @@ func (attr *Launcher) Launch(data interface{}) (int, error) { for { select { case v, ok := <-result: + <-result // blocks until the launch() goroutine exits if !ok { return -1, ErrLaunchFailed } diff --git a/go/.gitignore b/go/.gitignore index ac6056f..bdb47b5 100644 --- a/go/.gitignore +++ b/go/.gitignore @@ -5,6 +5,8 @@ try-launching-cgo psx-signals psx-signals-cgo b210613 +b215283 +b215283-cgo mknames web setid diff --git a/go/Makefile b/go/Makefile index 67ded78..ea1db7f 100644 --- a/go/Makefile +++ b/go/Makefile @@ -94,6 +94,14 @@ endif b210613: b210613.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 $< +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 + test: setid gowns captree $(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 @@ -110,7 +118,7 @@ endif # 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 +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) @@ -121,6 +129,12 @@ ifeq ($(CGO_REQUIRED),0) $(SUDO) ./try-launching-cgo endif $(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 @@ -142,5 +156,5 @@ clean: rm -f web setid gowns captree 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 -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) + } +} -- cgit v1.2.3 From e458889fbda4052919b61fd9f727bb1ac906d436 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 11 Dec 2021 14:56:07 -0800 Subject: Complete launch thread exit sequence. This should complete the fix for: https://bugzilla.kernel.org/show_bug.cgi?id=215283 Simplify the code, and add a test that the kernel has confirmed that the thread is no longer running. Signed-off-by: Andrew G. Morgan --- cap/launch.go | 49 +++++++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/cap/launch.go b/cap/launch.go index b67a856..cc3be50 100644 --- a/cap/launch.go +++ b/cap/launch.go @@ -181,6 +181,10 @@ func (attr *Launcher) SetChroot(root string) { // 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 @@ -232,13 +236,14 @@ func launch(result chan<- lResult, attr *Launcher, data interface{}, quit chan<- defer close(quit) } - pid := syscall.Getpid() + 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 @@ -291,12 +296,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 } } @@ -343,12 +348,24 @@ 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. +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 @@ -393,19 +410,11 @@ func (attr *Launcher) Launch(data interface{}) (int, error) { result := make(chan lResult) go launch(result, attr, data, nil) - for { - select { - case v, ok := <-result: - <-result // blocks until the launch() goroutine exits - 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 } -- cgit v1.2.3 From 89b4b8021d23a7043ad832a7f643fcd2e70a6560 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 11 Dec 2021 18:01:14 -0800 Subject: Clean up cap package documentation for Launch. Signed-off-by: Andrew G. Morgan --- cap/launch.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/cap/launch.go b/cap/launch.go index cc3be50..de7fd90 100644 --- a/cap/launch.go +++ b/cap/launch.go @@ -236,6 +236,7 @@ func launch(result chan<- lResult, attr *Launcher, data interface{}, quit chan<- defer close(quit) } + // Thread group ID is the process ID. tgid := syscall.Getpid() // This code waits until we are not scheduled on the parent @@ -355,7 +356,9 @@ abort: } } -// pollForThreadExit waits for a thread to terminate. +// 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 @@ -369,7 +372,7 @@ func (v lResult) pollForThreadExit() { // 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 @@ -381,15 +384,15 @@ func (v lResult) pollForThreadExit() { // 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 -- cgit v1.2.3 From cc91f55960ce81e7cc24ef0bf729bdf02e2f60e1 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 11 Dec 2021 18:06:34 -0800 Subject: Up the release version to 2.62 Signed-off-by: Andrew G. Morgan --- Make.Rules | 2 +- cap/go.mod | 2 +- contrib/seccomp/go.mod | 2 +- go/go.mod | 4 ++-- goapps/captree/go.mod | 2 +- goapps/gowns/go.mod | 2 +- goapps/setid/go.mod | 4 ++-- goapps/web/go.mod | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Make.Rules b/Make.Rules index eaaf3d6..cd4dd01 100644 --- a/Make.Rules +++ b/Make.Rules @@ -1,7 +1,7 @@ # Common version number defines for libcap LIBTITLE=libcap VERSION=2 -MINOR=61 +MINOR=62 # ## Optional prefixes: diff --git a/cap/go.mod b/cap/go.mod index 3f84806..e01afee 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.61 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.62 diff --git a/contrib/seccomp/go.mod b/contrib/seccomp/go.mod index 53c27d4..93515d6 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.61 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.62 diff --git a/go/go.mod b/go/go.mod index 98e2df8..b4eba06 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.61 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.61 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.62 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.62 ) diff --git a/goapps/captree/go.mod b/goapps/captree/go.mod index 9ea7efe..3448014 100644 --- a/goapps/captree/go.mod +++ b/goapps/captree/go.mod @@ -2,4 +2,4 @@ module captree go 1.16 -require kernel.org/pub/linux/libs/security/libcap/cap v1.2.61 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.62 diff --git a/goapps/gowns/go.mod b/goapps/gowns/go.mod index df15100..bbd148c 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.61 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.62 diff --git a/goapps/setid/go.mod b/goapps/setid/go.mod index 6c09a41..f737744 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.61 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.61 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.62 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.62 ) diff --git a/goapps/web/go.mod b/goapps/web/go.mod index fa4ff4c..dd297fc 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.61 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.62 -- cgit v1.2.3 From af2bf057ed03a0c8b29898b721aef66b021124d0 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 12 Dec 2021 11:52:16 -0800 Subject: Make cgo psx_syscall variant crash like runtime.AllThreadsSyscall When a syscall that yields different return values is called from the Go psx.Syscall*() API, we want to mirror the behavior of the native golang runtime.AllThreadsSyscall() function. The previous inconsistency was pointed out by Lorenz Bauer in: https://bugzilla.kernel.org/show_bug.cgi?id=215283#c8 [I decided to defer this change until 2.63, and not include this in the bug-fix for 215283, on the grounds it is a slight incompatibility in runtime behavior, and wanted to give folk an opportunity to plan for it. This new behavior enforcement will crash an unprepared go program.] Signed-off-by: Andrew G. Morgan --- doc/Makefile | 3 +- doc/libpsx.3 | 16 +++++++- doc/psx_set_sensitivity.3 | 1 + go/.gitignore | 2 + go/Makefile | 15 +++++++- go/mismatch.go | 15 ++++++++ psx/psx.c | 96 ++++++++++++++++++++++++++++++++++++++++------- psx/psx_cgo.go | 12 ++++++ psx/psx_syscall.h | 21 +++++++++++ 9 files changed, 162 insertions(+), 19 deletions(-) create mode 100644 doc/psx_set_sensitivity.3 create mode 100644 go/mismatch.go diff --git a/doc/Makefile b/doc/Makefile index e78eb6e..c7d50e0 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -26,7 +26,8 @@ MAN3S = cap_init.3 cap_free.3 cap_dup.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 + psx_syscall.3 psx_syscall3.3 psx_syscall6.3 psx_set_sensitivity.3 \ + libpsx.3 MAN8S = getcap.8 setcap.8 getpcaps.8 captree.8 MANS = $(MAN1S) $(MAN3S) $(MAN8S) diff --git a/doc/libpsx.3 b/doc/libpsx.3 index 4ba306b..d117ae8 100644 --- a/doc/libpsx.3 +++ b/doc/libpsx.3 @@ -1,12 +1,13 @@ -.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 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); .fi .sp Link with one of these: @@ -62,6 +63,17 @@ 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. .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/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/go/.gitignore b/go/.gitignore index bdb47b5..9a88b0d 100644 --- a/go/.gitignore +++ b/go/.gitignore @@ -7,6 +7,8 @@ psx-signals-cgo b210613 b215283 b215283-cgo +mismatch +mismatch-cgo mknames web setid diff --git a/go/Makefile b/go/Makefile index ea1db7f..fb7d6ec 100644 --- a/go/Makefile +++ b/go/Makefile @@ -14,7 +14,7 @@ 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 +TESTS=compare-cap try-launching psx-signals mismatch all: PSXGOPACKAGE CAPGOPACKAGE web setid gowns captree @@ -102,14 +102,24 @@ 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: setid gowns captree $(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 ifeq ($(CGO_REQUIRED),0) - $(MAKE) psx-signals-cgo + $(MAKE) psx-signals-cgo mismatch-cgo ./psx-signals-cgo + ./mismatch-cgo || exit 0 ; exit 1 endif ./setid --caps=false ./gowns -- -c "echo gowns runs" @@ -157,4 +167,5 @@ clean: rm -f compare-cap try-launching try-launching-cgo rm -f $(topdir)/cap/*~ $(topdir)/psx/*~ rm -f b210613 b215283 b215283-cgo psx-signals psx-signals-cgo + rm -f mismatch mismatch-cgo rm -fr vendor CAPGOPACKAGE PSXGOPACKAGE go.sum 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/psx/psx.c b/psx/psx.c index c317063..0a0817c 100644 --- a/psx/psx.c +++ b/psx/psx.c @@ -56,6 +56,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 +83,7 @@ static struct psx_tracker_s { psx_tracker_state_t state; int initialized; int psx_sig; + psx_sensitivity_t sensitivity; struct { long syscall_nr; @@ -136,19 +139,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); } /* @@ -160,6 +164,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 @@ -607,6 +613,7 @@ long int __psx_syscall(long int syscall_nr, ...) { } psx_unlock(); + int mismatch = 0; for (;;) { int waiting = 0; psx_lock(); @@ -619,8 +626,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) { @@ -639,10 +650,67 @@ 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 a123=[%ld,%ld,%ld]\n", + psx_tracker.cmd.syscall_nr, + psx_tracker.cmd.arg1, + psx_tracker.cmd.arg2, + psx_tracker.cmd.arg3); + } else { + 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); + } + 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; } + +/* + * 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_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 -- cgit v1.2.3 From cbdd2b14e0b6e48ac5139c9d4327020cd6996d40 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Mon, 13 Dec 2021 07:30:09 -0800 Subject: Fix argument crash dump order in psx text dumper. Signed-off-by: Andrew G. Morgan --- psx/psx.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/psx/psx.c b/psx/psx.c index 0a0817c..6b669ae 100644 --- a/psx/psx.c +++ b/psx/psx.c @@ -659,12 +659,6 @@ long int __psx_syscall(long int syscall_nr, ...) { default: fprintf(stderr, "psx_syscall result differs.\n"); if (psx_tracker.cmd.six) { - 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); - } else { fprintf(stderr, "trap:%ld a123456=[%ld,%ld,%ld,%ld,%ld,%ld]\n", psx_tracker.cmd.syscall_nr, psx_tracker.cmd.arg1, @@ -673,6 +667,12 @@ long int __psx_syscall(long int syscall_nr, ...) { 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) { -- cgit v1.2.3 From f25a1b7e69f7b33e6afb58b3e38f3450b7d2d9a0 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Wed, 19 Jan 2022 06:44:58 -0800 Subject: Reset the value of errno for main() Since libcap does some error testing with a pre-main() constructor, reset errno to zero as that constructor returns. Problem reported by Yang Xu. Signed-off-by: Andrew G. Morgan --- libcap/cap_alloc.c | 11 ++++++----- tests/libcap_launch_test.c | 6 ++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/libcap/cap_alloc.c b/libcap/cap_alloc.c index 600b7cd..c826e7a 100644 --- a/libcap/cap_alloc.c +++ b/libcap/cap_alloc.c @@ -19,14 +19,15 @@ static cap_value_t _cap_max_bits; __attribute__((constructor (300))) void _libcap_initialize() { + int errno_saved = errno; _cap_mu_lock(&__libcap_mutex); - if (_cap_max_bits) { - _cap_mu_unlock(&__libcap_mutex); - return; + 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) diff --git a/tests/libcap_launch_test.c b/tests/libcap_launch_test.c index 12d123c..b982573 100644 --- a/tests/libcap_launch_test.c +++ b/tests/libcap_launch_test.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -119,6 +120,11 @@ 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"); -- cgit v1.2.3 From bbabfb4cf4280825b5aeb96bf5354d654efa246e Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 23 Jan 2022 16:35:23 -0800 Subject: Add a test case for a deadlock. The CGO_ENABLED=0 failure mode is discussed in: https://github.com/golang/go/issues/50113 At the present time, this only passes when the psx package is compiled CGO_ENABLED=1. The problem being that a blocking read cannot be interrupted by the CGO_ENABLED=0 build of package "psx". It does not deadlock when compiled CGO_ENABLED=1 because the psx signal wakes the reading thread up back into user space. Signed-off-by: Andrew G. Morgan --- go/.gitignore | 2 ++ go/Makefile | 20 +++++++++++++++++--- go/psx-fd.go | 25 +++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 go/psx-fd.go diff --git a/go/.gitignore b/go/.gitignore index 9a88b0d..eca62ba 100644 --- a/go/.gitignore +++ b/go/.gitignore @@ -2,6 +2,8 @@ good-names.go compare-cap try-launching try-launching-cgo +psx-fd +psx-fd-cgo psx-signals psx-signals-cgo b210613 diff --git a/go/Makefile b/go/Makefile index fb7d6ec..4aface4 100644 --- a/go/Makefile +++ b/go/Makefile @@ -83,6 +83,18 @@ 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. At the time of writing, this is true for go1.16+. +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) +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 $(GO_BUILD_FLAGS) -mod=vendor $< @@ -110,16 +122,18 @@ 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: setid gowns captree $(TESTS) +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 mismatch-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" @@ -167,5 +181,5 @@ clean: rm -f compare-cap try-launching try-launching-cgo rm -f $(topdir)/cap/*~ $(topdir)/psx/*~ rm -f b210613 b215283 b215283-cgo psx-signals psx-signals-cgo - rm -f mismatch mismatch-cgo + rm -f mismatch mismatch-cgo psx-fd psx-fd-cgo rm -fr vendor CAPGOPACKAGE PSXGOPACKAGE go.sum 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() +} -- cgit v1.2.3 From e1bd9ac089cc7d6535d28296e0730f7d54df7097 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 23 Jan 2022 16:36:06 -0800 Subject: Trim includes. I've upgraded one of my systems to Fedora 35 and I found trimming the headers in this way made the three compilations of libcap, used by `make distcheck`, work with standard Fedora 35 compiler packages. Signed-off-by: Andrew G. Morgan --- Make.Rules | 2 +- Makefile | 2 +- libcap/cap_file.c | 1 - libcap/cap_proc.c | 2 -- libcap/include/sys/capability.h | 3 +-- libcap/include/uapi/linux/capability.h | 5 +++-- 6 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Make.Rules b/Make.Rules index cd4dd01..750232b 100644 --- a/Make.Rules +++ b/Make.Rules @@ -148,7 +148,7 @@ ifeq ($(CGO_REQUIRED),1) # 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 diff --git a/Makefile b/Makefile index 256c339..84c90c8 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,7 @@ distcheck: $(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/libcap/cap_file.c b/libcap/cap_file.c index 4178705..0bc07f7 100644 --- a/libcap/cap_file.c +++ b/libcap/cap_file.c @@ -12,7 +12,6 @@ #include #include #include -#include /* * We hardcode the prototypes for the Linux system calls here since diff --git a/libcap/cap_proc.c b/libcap/cap_proc.c index db947f4..65204bf 100644 --- a/libcap/cap_proc.c +++ b/libcap/cap_proc.c @@ -18,8 +18,6 @@ #include #include -#include - #include "libcap.h" /* diff --git a/libcap/include/sys/capability.h b/libcap/include/sys/capability.h index cb96d82..c85413b 100644 --- a/libcap/include/sys/capability.h +++ b/libcap/include/sys/capability.h @@ -2,7 +2,7 @@ * * * Copyright (C) 1997 Aleph One - * Copyright (C) 1997,8, 2008,19,20 Andrew G. Morgan + * Copyright (C) 1997,8, 2008,19-22 Andrew G. Morgan * * defunct POSIX.1e Standard: 25.2 Capabilities */ @@ -21,7 +21,6 @@ extern "C" { #include #include -#include #ifndef __user #define __user 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 +#include +#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 */ -- cgit v1.2.3 From 1d88048c314c2bc239459ed10e0685c4b1950747 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 23 Jan 2022 16:36:22 -0800 Subject: Up the release version to 2.63 Signed-off-by: Andrew G. Morgan --- Make.Rules | 2 +- cap/go.mod | 2 +- contrib/seccomp/go.mod | 2 +- go/go.mod | 4 ++-- goapps/captree/go.mod | 2 +- goapps/gowns/go.mod | 2 +- goapps/setid/go.mod | 4 ++-- goapps/web/go.mod | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Make.Rules b/Make.Rules index 750232b..bd4c0b9 100644 --- a/Make.Rules +++ b/Make.Rules @@ -1,7 +1,7 @@ # Common version number defines for libcap LIBTITLE=libcap VERSION=2 -MINOR=62 +MINOR=63 # ## Optional prefixes: diff --git a/cap/go.mod b/cap/go.mod index e01afee..4e696bf 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.62 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.63 diff --git a/contrib/seccomp/go.mod b/contrib/seccomp/go.mod index 93515d6..eb6a83a 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.62 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.63 diff --git a/go/go.mod b/go/go.mod index b4eba06..f5d54b9 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.62 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.62 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.63 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.63 ) diff --git a/goapps/captree/go.mod b/goapps/captree/go.mod index 3448014..dc27736 100644 --- a/goapps/captree/go.mod +++ b/goapps/captree/go.mod @@ -2,4 +2,4 @@ module captree go 1.16 -require kernel.org/pub/linux/libs/security/libcap/cap v1.2.62 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.63 diff --git a/goapps/gowns/go.mod b/goapps/gowns/go.mod index bbd148c..75b84e0 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.62 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.63 diff --git a/goapps/setid/go.mod b/goapps/setid/go.mod index f737744..81a5abc 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.62 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.62 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.63 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.63 ) diff --git a/goapps/web/go.mod b/goapps/web/go.mod index dd297fc..92c4f70 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.62 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.63 -- cgit v1.2.3 From 66a8a1421e4520e9dda0a46704e25bafb989b1ae Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 5 Feb 2022 17:26:05 -0800 Subject: psx: free allocated memory at exit. Kalen Hall reported that Valgrind detected a memory leak associated with a multi-threaded program linked against libcap and libpsx. https://bugzilla.kernel.org/show_bug.cgi?id=215551 I've been unable to validate this myself with valgrind (likely holding it wrong), but did explore psx for allocated memory and via fprintf's convinced myself that this change should pair all calloc()s with a corresponding free(). Signed-off-by: Andrew G. Morgan --- psx/psx.c | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/psx/psx.c b/psx/psx.c index 6b669ae..1876978 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 @@ -177,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); @@ -240,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; } @@ -420,7 +442,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); } @@ -699,6 +721,26 @@ 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_new_state(_PSX_IDLE, _PSX_EXITING); + + 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 -- cgit v1.2.3 From aae937481ae28c0cfe502309e9acb5c34ba0b2cd Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 13 Feb 2022 19:58:05 -0800 Subject: Be explicit about CGO_ENABLED=1 for compare-cap build. It looks like go1.18 is going to default to CGO_ENABLED=0, so force CGO_ENABLED=1 when building this cap-libcap comparison program. Fixes: https://bugzilla.kernel.org/show_bug.cgi?id=215603 Signed-off-by: Andrew G. Morgan --- go/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go/Makefile b/go/Makefile index 4aface4..109581b 100644 --- a/go/Makefile +++ b/go/Makefile @@ -55,7 +55,7 @@ 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 $(GO_BUILD_FLAGS) -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 $(GO_BUILD_FLAGS) -mod=vendor -o $@ $< @@ -75,7 +75,7 @@ captree: ../goapps/captree/captree.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 $(GO_BUILD_FLAGS) -mod=vendor $< + 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 $(GO_BUILD_FLAGS) -mod=vendor $< -- cgit v1.2.3 From 15cacf20709c6917c798e298a1e087c0663e5c13 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Tue, 29 Mar 2022 18:06:18 -0700 Subject: Fix prctl return code/errno handling in libcap. Bug reported by Anderson Toshiyuki Sasaki: https://bugzilla.kernel.org/show_bug.cgi?id=215772 Signed-off-by: Andrew G. Morgan --- libcap/cap_proc.c | 45 +++++++++++++++++++-------------------------- libcap/cap_test.c | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/libcap/cap_proc.c b/libcap/cap_proc.c index 65204bf..0ce5db7 100644 --- a/libcap/cap_proc.c +++ b/libcap/cap_proc.c @@ -135,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); } @@ -145,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); } @@ -271,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 */ @@ -316,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; @@ -328,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)); } /* diff --git a/libcap/cap_test.c b/libcap/cap_test.c index 39df261..68b6a13 100644 --- a/libcap/cap_test.c +++ b/libcap/cap_test.c @@ -254,6 +254,21 @@ drop_c: 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; @@ -269,6 +284,9 @@ int main(int argc, char **argv) { 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); -- cgit v1.2.3 From ceaa591b012610d3fe402d33e7d7ca14716cf965 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 9 Apr 2022 17:02:13 -0700 Subject: Clarify how the cap_get_pid() argument is interpreted. Addresses: https://bugzilla.kernel.org/show_bug.cgi?id=215812 Signed-off-by: Andrew G. Morgan --- doc/cap_get_proc.3 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/doc/cap_get_proc.3 b/doc/cap_get_proc.3 index 496c06e..7c3bc81 100644 --- a/doc/cap_get_proc.3 +++ b/doc/cap_get_proc.3 @@ -76,7 +76,11 @@ with the process capabilities of the process indicated by is 0, then the calling process's capabilities are returned.) This information can also be obtained from the .I /proc//status -file. +file. Note, when the caller is operating within a +.RB ( CLONE_NEWPID ) +namespace, the numerical +.I pid +argument is interpreted in the range of that namespace. .PP .BR cap_get_bound () with a @@ -392,5 +396,6 @@ displays the resulting privilege state. .BR cap_from_text (3), .BR cap_get_file (3), .BR cap_init (3), +.BR namespaces (7), .BR psx_syscall (3), .BR capabilities (7). -- cgit v1.2.3 From fc029cb5170361981b6d971fe56ff60f1ab10b45 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 10 Apr 2022 14:49:26 -0700 Subject: Include LIBCAP_{MAJOR,MINOR} #define's in sys/capability.h It looks like various distributions are fairly far behind HEAD for their version of libcap. This way folk can work around a lack of features in their code. Signed-off-by: Andrew G. Morgan --- Makefile | 3 +++ libcap/include/sys/capability.h | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/Makefile b/Makefile index 84c90c8..a3337a6 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,9 @@ distclean: clean @echo "CONFIRM Go package cap has right version dependency on cap/psx:" 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!" diff --git a/libcap/include/sys/capability.h b/libcap/include/sys/capability.h index c85413b..730af37 100644 --- a/libcap/include/sys/capability.h +++ b/libcap/include/sys/capability.h @@ -14,6 +14,12 @@ extern "C" { #endif +/* + * Provide a programmatic way to #ifdef around features. + */ +#define LIBCAP_MAJOR 2 +#define LIBCAP_MINOR 63 + /* * This file complements the kernel file by providing prototype * information for the user library. -- cgit v1.2.3 From 7617af6b0754da00c1094215ee7828d6592f8ade Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 10 Apr 2022 15:39:14 -0700 Subject: Avoid a deadlock in forked psx thread exit. go/captree was seeing lots of libcap_psx_test processes hanging around. It turns out that the newly added _psx_cleanup() function was deadlocking because inside a forked processes the psx_tracker.state was _PSX_INFORK and never _PSX_IDLE. This completes the fix for: https://bugzilla.kernel.org/show_bug.cgi?id=215551 Signed-off-by: Andrew G. Morgan --- psx/psx.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/psx/psx.c b/psx/psx.c index 1876978..d9c0485 100644 --- a/psx/psx.c +++ b/psx/psx.c @@ -287,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) { @@ -351,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) @@ -733,7 +735,12 @@ static void _psx_cleanup(void) { * never leave this state since this cleanup is only done at * program exit. */ - psx_new_state(_PSX_IDLE, _PSX_EXITING); + 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; -- cgit v1.2.3 From 38cfa2e9582794108772631bbd800de7652b05d0 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 10 Apr 2022 15:39:39 -0700 Subject: Up the release version to 2.64 Signed-off-by: Andrew G. Morgan --- Make.Rules | 2 +- cap/go.mod | 2 +- contrib/seccomp/go.mod | 2 +- go/go.mod | 4 ++-- goapps/captree/go.mod | 2 +- goapps/gowns/go.mod | 2 +- goapps/setid/go.mod | 4 ++-- goapps/web/go.mod | 2 +- libcap/include/sys/capability.h | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Make.Rules b/Make.Rules index bd4c0b9..dee4953 100644 --- a/Make.Rules +++ b/Make.Rules @@ -1,7 +1,7 @@ # Common version number defines for libcap LIBTITLE=libcap VERSION=2 -MINOR=63 +MINOR=64 # ## Optional prefixes: diff --git a/cap/go.mod b/cap/go.mod index 4e696bf..71af5de 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.63 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.64 diff --git a/contrib/seccomp/go.mod b/contrib/seccomp/go.mod index eb6a83a..3462138 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.63 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.64 diff --git a/go/go.mod b/go/go.mod index f5d54b9..e5b59e1 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.63 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.63 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.64 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.64 ) diff --git a/goapps/captree/go.mod b/goapps/captree/go.mod index dc27736..d0c3bc6 100644 --- a/goapps/captree/go.mod +++ b/goapps/captree/go.mod @@ -2,4 +2,4 @@ module captree go 1.16 -require kernel.org/pub/linux/libs/security/libcap/cap v1.2.63 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.64 diff --git a/goapps/gowns/go.mod b/goapps/gowns/go.mod index 75b84e0..2c33e44 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.63 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.64 diff --git a/goapps/setid/go.mod b/goapps/setid/go.mod index 81a5abc..adb92b8 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.63 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.63 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.64 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.64 ) diff --git a/goapps/web/go.mod b/goapps/web/go.mod index 92c4f70..1e56b5b 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.63 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.64 diff --git a/libcap/include/sys/capability.h b/libcap/include/sys/capability.h index 730af37..82048d0 100644 --- a/libcap/include/sys/capability.h +++ b/libcap/include/sys/capability.h @@ -18,7 +18,7 @@ extern "C" { * Provide a programmatic way to #ifdef around features. */ #define LIBCAP_MAJOR 2 -#define LIBCAP_MINOR 63 +#define LIBCAP_MINOR 64 /* * This file complements the kernel file by providing prototype -- cgit v1.2.3 From 94250487ed76c64abc6bed9e58930608a168717d Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Mon, 11 Apr 2022 10:22:21 -0700 Subject: More useful captree usage string and man page. Include more detail about command line expectations and exit status values. Signed-off-by: Andrew G. Morgan --- doc/captree.8 | 19 ++++++++++++------- goapps/captree/captree.go | 5 +++++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/doc/captree.8 b/doc/captree.8 index d2c0003..86a7de3 100644 --- a/doc/captree.8 +++ b/doc/captree.8 @@ -1,15 +1,15 @@ .\" Hey, EMACS: -*- nroff -*- -.TH CAPTREE 8 "2021-09-02" +.TH CAPTREE 8 "2022-04-11" .\" Please adjust this date whenever revising the manpage. .SH NAME -captree \- display process tree capabilities +captree \- display tree of process capabilities .SH SYNOPSIS -.BR captree " [optional args] " -.IR [pid|glob-name ... ] +.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 +.IR pid " or " glob-name value(s) given on the command line. If no .I pid etc values are supplied, @@ -34,7 +34,8 @@ Optional arguments (which must precede the list of pid|glob-name values): .TP .B \-\-help -Displays usage information and exits. +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 @@ -51,7 +52,11 @@ 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 diff --git a/goapps/captree/captree.go b/goapps/captree/captree.go index fc89b51..5313cb4 100644 --- a/goapps/captree/captree.go +++ b/goapps/captree/captree.go @@ -340,6 +340,7 @@ func findPIDs(list []string, pids map[string]*task, glob string) <-chan string { return } fmt.Printf("no process matched %q\n", glob) + os.Exit(1) }() return finds } @@ -356,6 +357,10 @@ func setDepth(pids map[string]*task, pid string) int { } 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. -- cgit v1.2.3 From 21d08b03c2a737e4384a07857e0289ad0126b663 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 17 Apr 2022 06:41:23 -0700 Subject: Fix syntax error in DEBUG protected setcap.c code. Bug reported with fix from yixiangzhike. Signed-off-by: Andrew G. Morgan --- progs/setcap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/progs/setcap.c b/progs/setcap.c index 02a8a5d..737efcc 100644 --- a/progs/setcap.c +++ b/progs/setcap.c @@ -176,7 +176,7 @@ int main(int argc, char **argv) { char *result = cap_to_text(cap_d, NULL); fprintf(stderr, "caps set to: [%s]\n", result); - cap_free(result) + cap_free(result); } #endif } -- cgit v1.2.3 From 9a9579181897a62dc107b121f139a319d7e297fa Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Thu, 28 Apr 2022 21:24:44 -0700 Subject: Improve documentation for cap_get_pid and cap_reset_ambient. - cap_get_pid() add detail about the function argument and return value when used across namespaces. Thanks to nemonemo for reporting: https://bugzilla.kernel.org/show_bug.cgi?id=215812 - cap_reset_ambient() had some factually incorrect content. Thanks to Tinker One for reporting: https://bugzilla.kernel.org/show_bug.cgi?id=215910 Signed-off-by: Andrew G. Morgan --- doc/cap_get_proc.3 | 53 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/doc/cap_get_proc.3 b/doc/cap_get_proc.3 index 7c3bc81..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,22 +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//status -file. Note, when the caller is operating within a +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. +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 @@ -124,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 @@ -382,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 @@ -397,5 +412,7 @@ displays the resulting privilege state. .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). -- cgit v1.2.3 From eb0f1df722d5e760137a0dd85fee8e78c95ee68f Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 30 Apr 2022 16:04:47 -0700 Subject: Prevent 'capsh --user=xxx --' from generating a bash error. This change adds support to capsh for the --noenv argument, which will restore pre-libcap-2.65 behavior to capsh. The change we're making here, however, is that capsh will now set the USER and HOME environment variables when the command line contains --user=xxx. The issue this addresses is described here: https://bugzilla.kernel.org/show_bug.cgi?id=215926 This has been annoying me for long enough, and I want to clean up the article: https://sites.google.com/site/fullycapable/inheriting-privilege to not pepper "--norc" in distracting places. Signed-off-by: Andrew G. Morgan --- doc/capsh.1 | 6 ++++++ progs/capsh.c | 19 ++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/doc/capsh.1 b/doc/capsh.1 index aafe3fb..4f3aaae 100644 --- a/doc/capsh.1 +++ b/doc/capsh.1 @@ -329,6 +329,12 @@ Removes the specified ambient capability from the running process. .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 diff --git a/progs/capsh.c b/progs/capsh.c index 4f568c3..f753291 100644 --- a/progs/capsh.c +++ b/progs/capsh.c @@ -509,7 +509,7 @@ int main(int argc, char *argv[], char *envp[]) { pid_t child = 0; unsigned i; - int strict = 0, quiet_start = 0; + int strict = 0, quiet_start = 0, dont_set_env = 0; const char *shell = SHELL; for (i=1; ipw_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; @@ -1170,6 +1186,7 @@ int main(int argc, char *argv[], char *envp[]) " --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= write a new value for securebits\n" -- cgit v1.2.3 From 52288ccc0b341cc4ce2751accca467ee1cc67389 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Tue, 17 May 2022 07:10:19 -0700 Subject: Close out this comment in the go/Makefile The deadlock issue is fixed in go1.18. Signed-off-by: Andrew G. Morgan --- go/Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/go/Makefile b/go/Makefile index 109581b..f6390da 100644 --- a/go/Makefile +++ b/go/Makefile @@ -86,7 +86,10 @@ 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. At the time of writing, this is true for go1.16+. +# 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 $@ $< -- cgit v1.2.3 From fc99e561503f03704f19aced9731096ea6f63d14 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 9 Jul 2022 13:36:48 -0700 Subject: Include more signatures in pgp.keys.asc. These updates should also be available on keyservers. Signed-off-by: Andrew G. Morgan --- pgp.keys.asc | 251 ++++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 161 insertions(+), 90 deletions(-) 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) uid Andrew G. Morgan -----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 -----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' -- cgit v1.2.3 From a47d86dfb9bfb56968b7d5469471ea317a12379c Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 17 Jul 2022 15:33:06 -0700 Subject: Up the release version to 2.65 Signed-off-by: Andrew G. Morgan --- Make.Rules | 2 +- Makefile | 2 +- cap/go.mod | 2 +- contrib/seccomp/go.mod | 2 +- go/go.mod | 4 ++-- goapps/captree/go.mod | 2 +- goapps/gowns/go.mod | 2 +- goapps/setid/go.mod | 4 ++-- goapps/web/go.mod | 2 +- libcap/include/sys/capability.h | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Make.Rules b/Make.Rules index dee4953..8deb29e 100644 --- a/Make.Rules +++ b/Make.Rules @@ -1,7 +1,7 @@ # Common version number defines for libcap LIBTITLE=libcap VERSION=2 -MINOR=64 +MINOR=65 # ## Optional prefixes: diff --git a/Makefile b/Makefile index a3337a6..fca2b58 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,7 @@ release: distclean ktest: all $(MAKE) -C kdebug test -distcheck: +distcheck: distclean ./distcheck.sh $(MAKE) DYNAMIC=no COPTS="-D_FORTIFY_SOURCE=2 -O1 -g" clean test $(MAKE) DYNAMIC=yes clean all test sudotest diff --git a/cap/go.mod b/cap/go.mod index 71af5de..18175bd 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.64 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.65 diff --git a/contrib/seccomp/go.mod b/contrib/seccomp/go.mod index 3462138..30d3cce 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.64 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.65 diff --git a/go/go.mod b/go/go.mod index e5b59e1..aa0bdfa 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.64 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.64 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.65 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.65 ) diff --git a/goapps/captree/go.mod b/goapps/captree/go.mod index d0c3bc6..79f90b7 100644 --- a/goapps/captree/go.mod +++ b/goapps/captree/go.mod @@ -2,4 +2,4 @@ module captree go 1.16 -require kernel.org/pub/linux/libs/security/libcap/cap v1.2.64 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.65 diff --git a/goapps/gowns/go.mod b/goapps/gowns/go.mod index 2c33e44..654b9b0 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.64 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.65 diff --git a/goapps/setid/go.mod b/goapps/setid/go.mod index adb92b8..8a961d5 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.64 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.64 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.65 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.65 ) diff --git a/goapps/web/go.mod b/goapps/web/go.mod index 1e56b5b..088e482 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.64 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.65 diff --git a/libcap/include/sys/capability.h b/libcap/include/sys/capability.h index 82048d0..3002836 100644 --- a/libcap/include/sys/capability.h +++ b/libcap/include/sys/capability.h @@ -18,7 +18,7 @@ extern "C" { * Provide a programmatic way to #ifdef around features. */ #define LIBCAP_MAJOR 2 -#define LIBCAP_MINOR 64 +#define LIBCAP_MINOR 65 /* * This file complements the kernel file by providing prototype -- cgit v1.2.3 From 27e801bcbcffadd8a9e3193f0c520b1f08535574 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Thu, 21 Jul 2022 11:52:47 -0700 Subject: Fix for "make clean ; make -j48 test" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Missed a vendor dependency for the ok.go file. More recent go releases seem more picky about module or vendoring being used, and for the in-tree builds we consistently use vendoring. So make sure the vendoring directory set up has completed before trying to build ok.go. The failure was reported by Tomasz Kłoczko. Signed-off-by: Andrew G. Morgan --- go/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go/Makefile b/go/Makefile index f6390da..342d93f 100644 --- a/go/Makefile +++ b/go/Makefile @@ -74,8 +74,8 @@ gowns: ../goapps/gowns/gowns.go CAPGOPACKAGE captree: ../goapps/captree/captree.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 $(GO_BUILD_FLAGS) -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 $(GO_BUILD_FLAGS) -mod=vendor $< -- cgit v1.2.3 From 7db9589038d88b88111582da7681d33e1177f636 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Fri, 22 Jul 2022 07:22:57 -0700 Subject: Some more simplifications for building I'm not 100% sure this is needed, but I'm not yet convinced 'make distclean && make -j48 test' works reliably, but I find this easier to reason about. Signed-off-by: Andrew G. Morgan --- go/Makefile | 4 +++- libcap/Makefile | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/go/Makefile b/go/Makefile index 342d93f..cd90765 100644 --- a/go/Makefile +++ b/go/Makefile @@ -24,8 +24,10 @@ $(DEPS): ../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 diff --git a/libcap/Makefile b/libcap/Makefile index 7a38fe9..f5dde3e 100644 --- a/libcap/Makefile +++ b/libcap/Makefile @@ -108,6 +108,9 @@ $(STAPSXLIBNAME): $(PSXOBJS) include/sys/psx_syscall.h ifeq ($(SHARED),yes) +empty: empty.c + $(CC) -o $@ $< + loader.txt: empty $(OBJCOPY) --dump-section .interp=$@ $< /dev/null -- cgit v1.2.3 From fc437fd8308b4ee4d8e3053d51471883946f9b04 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Fri, 5 Aug 2022 20:43:05 -0700 Subject: Fix an issue with bash displaying an error. Also down size the default capabilities needed by the 'sucap' su program. This is aimed at addressing: https://bugzilla.kernel.org/show_bug.cgi?id=215926 Signed-off-by: Andrew G. Morgan --- contrib/sucap/Makefile | 12 +++++++++--- contrib/sucap/su.c | 43 ++++++++++++++++++++++++++++++++----------- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/contrib/sucap/Makefile b/contrib/sucap/Makefile index c58d5c4..df61ed5 100644 --- a/contrib/sucap/Makefile +++ b/contrib/sucap/Makefile @@ -1,12 +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) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -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/su.c b/contrib/sucap/su.c index c8cc05f..e3dfe70 100644 --- a/contrib/sucap/su.c +++ b/contrib/sucap/su.c @@ -149,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); } /* @@ -1210,6 +1216,11 @@ static int set_credentials(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); @@ -1226,11 +1237,18 @@ static int set_credentials(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(); @@ -1613,5 +1631,8 @@ auth_exit: } su_exit: + if (status != 0) { + perror(PAM_APP_NAME " failed"); + } exit(status); /* transparent exit */ } -- cgit v1.2.3 From fc804acc078ef03e2c5b3a233f118a537f260ccd Mon Sep 17 00:00:00 2001 From: Jakub Wilk Date: Thu, 1 Sep 2022 22:23:19 +0200 Subject: getpcaps: catch PID parsing errors. Signed-off-by: Jakub Wilk Signed-off-by: Andrew G. Morgan --- progs/getpcaps.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/progs/getpcaps.c b/progs/getpcaps.c index 8fce0a3..1e914b2 100644 --- a/progs/getpcaps.c +++ b/progs/getpcaps.c @@ -39,7 +39,9 @@ int main(int argc, char **argv) } for ( ++argv; --argc > 0; ++argv ) { + long lpid; int pid; + char *endarg; cap_t cap_d; if (!strcmp(argv[0], "--help") || !strcmp(argv[0], "--usage") || @@ -62,7 +64,22 @@ int main(int argc, char **argv) continue; } - pid = atoi(argv[0]); + errno = 0; + lpid = strtol(argv[0], &endarg, 10); + if (*endarg != '\0') { + errno = EINVAL; + } + if (errno == 0) { + if (lpid < 0 || pid != (pid_t) pid) + errno = EOVERFLOW; + } + if (errno != 0) { + fprintf(stderr, "Cannot parse pid %s (%s)\n", + argv[0], strerror(errno)); + retval = 1; + continue; + } + pid = lpid; cap_d = cap_get_pid(pid); if (cap_d == NULL) { -- cgit v1.2.3 From 26e3a096a4eb4edd8bbcaab57ac8df38e6594a1d Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 4 Sep 2022 14:36:52 -0700 Subject: Clean up getpcaps code. Address some corner cases and trim down the size of the code a bit. Signed-off-by: Andrew G. Morgan --- progs/getpcaps.c | 103 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 52 insertions(+), 51 deletions(-) diff --git a/progs/getpcaps.c b/progs/getpcaps.c index 1e914b2..7e14c36 100644 --- a/progs/getpcaps.c +++ b/progs/getpcaps.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997,2008 Andrew G. Morgan + * Copyright (c) 1997-8,2007-8,19,21-22 Andrew G. Morgan * * This displays the capabilities of given target process(es). */ @@ -14,7 +14,7 @@ static void usage(int code) { fprintf(stderr, -"usage: getcaps [ ...]\n\n" +"usage: getcaps [opts] [ ...]\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" @@ -38,44 +38,44 @@ int main(int argc, char **argv) usage(1); } - for ( ++argv; --argc > 0; ++argv ) { + 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 ]\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(argv[0], "--iab")) { + } else if (!strcmp(arg, "--iab")) { iab = 1; continue; } errno = 0; - lpid = strtol(argv[0], &endarg, 10); - if (*endarg != '\0') { - errno = EINVAL; - } + lpid = strtol(arg, &endarg, 10); if (errno == 0) { - if (lpid < 0 || pid != (pid_t) pid) + 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", - argv[0], strerror(errno)); + fprintf(stderr, "Cannot parse pid %s: (%s)\n", arg, strerror(errno)); retval = 1; continue; } @@ -87,43 +87,44 @@ int main(int argc, char **argv) " (%s)\n", pid, strerror(errno)); retval = 1; continue; - } else { - char *result = cap_to_text(cap_d, NULL); - if (iab) { - printf("%s:", *argv); - 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); + } + + 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); + } + 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); } - 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", *argv, result); - } else if (verbose == 2) { - fprintf(stderr, "Capabilities for `%s': %s\n", *argv, result); - } else { - printf("%s: %s\n", *argv, result); + printf(" [%s]", iab_text); + cap_free(iab_text); } - cap_free(result); - result = NULL; - cap_free(cap_d); + 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; -- cgit v1.2.3 From 09a2c1dbb88b8e8f21e83a002a4dbe62975029a9 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 18 Sep 2022 16:56:40 -0700 Subject: Add an example of using BPF kprobing to trace capability use. $ make $ sudo go/captrace your-program will attempt to explore what capabilities are needed to run your program by observing when cap_capable() inside the kernel is associated with your-program. Other ways to invoke this are $ sudo go/captrace --pid= $ sudo go/captrace The last of these traces everything running on a system. Signed-off-by: Andrew G. Morgan --- go/Makefile | 7 +- goapps/captrace/captrace.go | 230 ++++++++++++++++++++++++++++++++++++++++++++ goapps/captrace/go.mod | 5 + 3 files changed, 240 insertions(+), 2 deletions(-) create mode 100644 goapps/captrace/captrace.go create mode 100644 goapps/captrace/go.mod diff --git a/go/Makefile b/go/Makefile index cd90765..38c1cf3 100644 --- a/go/Makefile +++ b/go/Makefile @@ -16,7 +16,7 @@ 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 captree +all: PSXGOPACKAGE CAPGOPACKAGE web setid gowns captree captrace $(DEPS): $(MAKE) -C ../libcap all @@ -76,6 +76,9 @@ gowns: ../goapps/gowns/gowns.go CAPGOPACKAGE 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 vendor/modules.txt CC="$(CC)" CGO_ENABLED="0" $(GO) build $(GO_BUILD_FLAGS) -mod=vendor $< @@ -182,7 +185,7 @@ install: all clean: rm -f *.o *.so *~ mknames ok good-names.go - rm -f web setid gowns captree + 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 b215283 b215283-cgo psx-signals psx-signals-cgo 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..e2c3cbd --- /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.65 -- cgit v1.2.3 From 281b6e47e1a0d0235cc97d302548bdacdd53a019 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Thu, 22 Sep 2022 06:45:32 -0700 Subject: Add captrace to .gitignore file Signed-off-by: Andrew G. Morgan --- go/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/go/.gitignore b/go/.gitignore index eca62ba..96fe4c0 100644 --- a/go/.gitignore +++ b/go/.gitignore @@ -16,6 +16,7 @@ web setid gowns captree +captrace ok vendor go.sum -- cgit v1.2.3 From 60ff008d95584cc18701e98ed3cc4fa3d6cef9cb Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Thu, 22 Sep 2022 06:54:37 -0700 Subject: Fix typos in the cap_from_text.3 man page. This addresses this bug reported by Paulo Andrade (thanks!): https://bugzilla.kernel.org/show_bug.cgi?id=216514 Signed-off-by: Andrew G. Morgan --- doc/cap_from_text.3 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/cap_from_text.3 b/doc/cap_from_text.3 index 9370e26..a0c9282 100644 --- a/doc/cap_from_text.3 +++ b/doc/cap_from_text.3 @@ -1,7 +1,7 @@ .\" .\" written by Andrew Main .\" -.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 @@ -10,7 +10,7 @@ state textual representation translation #include cap_t cap_from_text(const char* buf_p ); -char *cap_to_text(cap_t caps, ssize_t * length_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 @@ -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 -- cgit v1.2.3 From 4f96e6788d535da5f57a3452a54b8d92bd41cd8e Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 24 Sep 2022 13:37:39 -0700 Subject: Up the release version to 2.66 Signed-off-by: Andrew G. Morgan --- Make.Rules | 2 +- cap/go.mod | 2 +- contrib/seccomp/go.mod | 2 +- go/go.mod | 4 ++-- goapps/captrace/go.mod | 2 +- goapps/captree/go.mod | 2 +- goapps/gowns/go.mod | 2 +- goapps/setid/go.mod | 4 ++-- goapps/web/go.mod | 2 +- libcap/include/sys/capability.h | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Make.Rules b/Make.Rules index 8deb29e..d2768e1 100644 --- a/Make.Rules +++ b/Make.Rules @@ -1,7 +1,7 @@ # Common version number defines for libcap LIBTITLE=libcap VERSION=2 -MINOR=65 +MINOR=66 # ## Optional prefixes: diff --git a/cap/go.mod b/cap/go.mod index 18175bd..599269e 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.65 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.66 diff --git a/contrib/seccomp/go.mod b/contrib/seccomp/go.mod index 30d3cce..250ce41 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.65 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.66 diff --git a/go/go.mod b/go/go.mod index aa0bdfa..ac949c5 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.65 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.65 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.66 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.66 ) diff --git a/goapps/captrace/go.mod b/goapps/captrace/go.mod index e2c3cbd..51a3b74 100644 --- a/goapps/captrace/go.mod +++ b/goapps/captrace/go.mod @@ -2,4 +2,4 @@ module captrace go 1.16 -require kernel.org/pub/linux/libs/security/libcap/cap v1.2.65 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.66 diff --git a/goapps/captree/go.mod b/goapps/captree/go.mod index 79f90b7..327826e 100644 --- a/goapps/captree/go.mod +++ b/goapps/captree/go.mod @@ -2,4 +2,4 @@ module captree go 1.16 -require kernel.org/pub/linux/libs/security/libcap/cap v1.2.65 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.66 diff --git a/goapps/gowns/go.mod b/goapps/gowns/go.mod index 654b9b0..455bbee 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.65 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.66 diff --git a/goapps/setid/go.mod b/goapps/setid/go.mod index 8a961d5..6741535 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.65 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.65 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.66 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.66 ) diff --git a/goapps/web/go.mod b/goapps/web/go.mod index 088e482..0318099 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.65 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.66 diff --git a/libcap/include/sys/capability.h b/libcap/include/sys/capability.h index 3002836..6205598 100644 --- a/libcap/include/sys/capability.h +++ b/libcap/include/sys/capability.h @@ -18,7 +18,7 @@ extern "C" { * Provide a programmatic way to #ifdef around features. */ #define LIBCAP_MAJOR 2 -#define LIBCAP_MINOR 65 +#define LIBCAP_MINOR 66 /* * This file complements the kernel file by providing prototype -- cgit v1.2.3 From 45bf9259a04fefc017024825c27153a650669577 Mon Sep 17 00:00:00 2001 From: David Seifert Date: Thu, 29 Sep 2022 10:57:37 +0200 Subject: Use POSIX `grep` * GNU grep 3.8 considers `egrep` and `fgrep` obsolescent and throws warnings: ./mkcapshdoc.sh > capshdoc.c.cf fgrep: warning: fgrep is obsolescent; using /bin/grep -F fgrep: warning: fgrep is obsolescent; using /bin/grep -F fgrep: warning: fgrep is obsolescent; using /bin/grep -F fgrep: warning: fgrep is obsolescent; using /bin/grep -F [...] https://lists.gnu.org/archive/html/info-gnu/2022-09/msg00001.html Signed-off-by: Andrew G. Morgan --- Make.Rules | 2 +- kdebug/test-kernel.sh | 2 +- progs/mkcapshdoc.sh | 2 +- progs/quicktest.sh | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Make.Rules b/Make.Rules index d2768e1..26d433a 100644 --- a/Make.Rules +++ b/Make.Rules @@ -18,7 +18,7 @@ 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 diff --git a/kdebug/test-kernel.sh b/kdebug/test-kernel.sh index 0962ce5..5d480c9 100755 --- a/kdebug/test-kernel.sh +++ b/kdebug/test-kernel.sh @@ -62,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/progs/mkcapshdoc.sh b/progs/mkcapshdoc.sh index 8421685..d2ee4bd 100755 --- a/progs/mkcapshdoc.sh +++ b/progs/mkcapshdoc.sh @@ -15,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} diff --git a/progs/quicktest.sh b/progs/quicktest.sh index 776b175..7366a6c 100755 --- a/progs/quicktest.sh +++ b/progs/quicktest.sh @@ -256,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 -- cgit v1.2.3 From b643699aa65388dd248e67e18004cad416bc4a7c Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Tue, 4 Oct 2022 21:48:36 -0700 Subject: Address bad-whatis-entry issue noticed by Debian build tests Details: https://www.mit.edu/afs.new/sipb/project/debathena/lintian/www/tags/manpage-has-bad-whatis-entry.html Signed-off-by: Andrew G. Morgan --- doc/cap_iab.3 | 5 +++++ doc/cap_launch.3 | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/doc/cap_iab.3 b/doc/cap_iab.3 index 8bec5fd..b8bab84 100644 --- a/doc/cap_iab.3 +++ b/doc/cap_iab.3 @@ -1,5 +1,10 @@ .TH CAP_IAB 3 "2021-11-17" "" "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 \- inheritable IAB tuple support functions +.SH SYNOPSIS .nf #include diff --git a/doc/cap_launch.3 b/doc/cap_launch.3 index 95313ec..1656428 100644 --- a/doc/cap_launch.3 +++ b/doc/cap_launch.3 @@ -1,5 +1,10 @@ .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 -- cgit v1.2.3 From 911da84bf4e1613567d4ef57e70c85326161ccad Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 9 Oct 2022 14:41:45 -0700 Subject: Add some exploit demonstration code to capso.so This exploit code requires a make variable to activate, but is used in the companion article discussing this code to compare and contrast setuid-root to file capable privilege. Tl;dr don't use setuid-root for shared libraries in this way! Follow along here: https://sites.google.com/site/fullycapable/capable-shared-objects Signed-off-by: Andrew G. Morgan --- contrib/capso/.gitignore | 2 ++ contrib/capso/capso.c | 83 ++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 contrib/capso/.gitignore 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/capso.c b/contrib/capso/capso.c index 2a357c0..7ca3427 100644 --- a/contrib/capso/capso.c +++ b/contrib/capso/capso.c @@ -26,6 +26,73 @@ #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 @@ -160,7 +227,7 @@ int bind80(const char *hostname) */ path = where_am_i(); if (path == NULL) { - perror("unable to find self"); + perror("Unable to find self"); goto drop_alloc; } @@ -168,7 +235,7 @@ int bind80(const char *hostname) args[1] = hostname; args[2] = NULL; - helper = cap_new_launcher(path, args, NULL); + helper = cap_new_launcher(path, args, (void *) environ); if (helper == NULL) { goto drop_path; } @@ -247,19 +314,19 @@ SO_MAIN(int argc, char **argv) working = cap_get_proc(); if (working == NULL) { - perror("unable to read capabilities"); + 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"); + perror("Unable to raise CAP_NET_BIND_SERVICE"); exit(1); } if (cap_set_proc(working) != 0) { - perror("cap_set_proc problem"); - fprintf(stderr, "try: sudo setcap cap_net_bind_service=p %s\n", + perror("Problem with cap_set_proc"); + fprintf(stderr, "Try: sudo setcap cap_net_bind_service=p %s\n", argv[0]); exit(1); } @@ -287,9 +354,11 @@ SO_MAIN(int argc, char **argv) *((int *) CMSG_DATA(ctrl)) = fd; if (sendmsg(3, &msg, 0) < 0) { - perror("failed to write fd"); + perror("Failed to write fd"); } + fake_exploit(); + #ifdef CAPSO_DEBUG printf("exiting standalone %s\n", argv[0]); sleep(30); -- cgit v1.2.3 From f30f85c637d7d38f87f1a93ef05b64ee45d6a825 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 15 Oct 2022 08:29:29 -0700 Subject: Modify DYNAMIC=no linking to only refer to libcap.a There is a longstanding WONT_FIX bug: https://sourceware.org/bugzilla/show_bug.cgi?id=12491 that has been causing capsh, when linked fully statically, to segfault. So, for non-dynamic linking of capsh etc utilities only link statically to libcap. This way, in tree builds can be guaranteed to get to execute with in tree API changes. For normal installations, DYNAMIC=yes works as before. Signed-off-by: Andrew G. Morgan --- progs/Makefile | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/progs/Makefile b/progs/Makefile index 2cb7520..826834c 100644 --- a/progs/Makefile +++ b/progs/Makefile @@ -15,9 +15,11 @@ LDPATH = LD_LIBRARY_PATH=../libcap DEPS = ../libcap/libcap.so 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 ... -LDFLAGS = --static +# 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 @@ -28,7 +30,7 @@ endif $(MAKE) -C ../libcap libcap.so $(BUILD): %: %.o $(DEPS) - $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIBCAPLIB) + $(CC) $(CFLAGS) $(LDFLAGS) $< $(LIBCAPLIB) $(LDFLAGS_SUFFIX) -o $@ %.o: %.c $(INCS) $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@ @@ -50,7 +52,7 @@ capshdoc.c.cf: capshdoc.c ./mkcapshdoc.sh diff -u capshdoc.c $@ || (rm $@ ; exit 1) capsh: capsh.c capshdoc.c.cf capshdoc.h $(DEPS) - $(CC) $(CFLAGS) $(CPPFLAGS) $(CAPSH_SHELL) $(LDFLAGS) -o $@ $< capshdoc.c $(LIBCAPLIB) + $(CC) $(CFLAGS) $(CPPFLAGS) $(CAPSH_SHELL) $(LDFLAGS) $< capshdoc.c $(LIBCAPLIB) $(LDFLAGS_SUFFIX) -o $@ # Statically linked with minimal linkage flags to enable running in a # chroot and in other in-tree testing contexts. -- cgit v1.2.3 From 10041371f4175ec175e841c48100c7567d42eba5 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 16 Oct 2022 17:09:11 -0700 Subject: Reviewed man pages, adding overlooked references. This started out as addressing this bug: https://bugzilla.kernel.org/show_bug.cgi?id=216585 But I then made crosslink.sh to figure out what I had missed, and fixed those bits too. Signed-off-by: Andrew G. Morgan --- doc/Makefile | 4 +- doc/cap_clear.3 | 4 +- doc/cap_get_file.3 | 6 +-- doc/cap_get_nsowner.3 | 1 + doc/cap_iab.3 | 34 ++++++++++------ doc/cap_prctl.3 | 1 + doc/cap_prctlw.3 | 1 + doc/cap_proc_root.3 | 1 + doc/cap_set_nsowner.3 | 1 + doc/crosslink.sh | 15 +++++++ doc/libcap.3 | 111 +++++++++++++++++++++++++++++++++++++++++++++----- 11 files changed, 150 insertions(+), 29 deletions(-) create mode 100644 doc/cap_get_nsowner.3 create mode 100644 doc/cap_prctl.3 create mode 100644 doc/cap_prctlw.3 create mode 100644 doc/cap_proc_root.3 create mode 100644 doc/cap_set_nsowner.3 create mode 100755 doc/crosslink.sh diff --git a/doc/Makefile b/doc/Makefile index c7d50e0..c096a9f 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -11,6 +11,7 @@ MAN3S = cap_init.3 cap_free.3 cap_dup.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_from_text.3 cap_to_text.3 cap_from_name.3 cap_to_name.3 \ capsetp.3 capgetp.3 libcap.3 \ @@ -25,7 +26,8 @@ MAN3S = cap_init.3 cap_free.3 cap_dup.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 \ + 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 \ libpsx.3 MAN8S = getcap.8 setcap.8 getpcaps.8 captree.8 diff --git a/doc/cap_clear.3 b/doc/cap_clear.3 index 19a736a..b8dbc30 100644 --- a/doc/cap_clear.3 +++ b/doc/cap_clear.3 @@ -1,6 +1,6 @@ -.TH CAP_CLEAR 3 "2021-10-01" "" "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_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 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 .\" -.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 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_iab.3 b/doc/cap_iab.3 index b8bab84..3e6282d 100644 --- a/doc/cap_iab.3 +++ b/doc/cap_iab.3 @@ -1,38 +1,28 @@ -.TH CAP_IAB 3 "2021-11-17" "" "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 \- inheritable IAB tuple support functions +cap_iab_fill, cap_proc_root \- inheritable IAB tuple support functions .SH SYNOPSIS .nf #include 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. @@ -93,6 +83,11 @@ cap_iab_t should be freed with 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/ /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 @@ -142,6 +137,19 @@ 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. 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/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/libcap.3 b/doc/libcap.3 index c1ae4ba..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 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); @@ -36,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 @@ -80,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 @@ -99,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). -- cgit v1.2.3 From 70998415a87587f31063a26a1e52c6f7806b7834 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Wed, 19 Oct 2022 19:04:50 -0700 Subject: Reviewed license information and adde SPDX ids. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Günther Noack reported some issues with automated dependency checking in https://bugzilla.kernel.org/show_bug.cgi?id=216609 Perhaps these additional lines will help assist those things. I did find a typo in pam_cap/execable.c so I've fixed that. Signed-off-by: Andrew G. Morgan --- License | 2 ++ cap/License | 2 ++ pam_cap/License | 2 ++ pam_cap/execable.c | 2 +- psx/License | 2 ++ 5 files changed, 9 insertions(+), 1 deletion(-) 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/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/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/execable.c b/pam_cap/execable.c index f826a57..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" 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. -- cgit v1.2.3 From 08d48b659aa59d2a5acd9cd13f640f6497718796 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 23 Oct 2022 15:15:30 -0700 Subject: Add an example of combining Go, C code and "psx" without cgo. This example was developed while investigating the issues discussed in: https://bugzilla.kernel.org/show_bug.cgi?id=216610 At this time, it is not possible to build CGO_ENABLED=1 and include the "psx" package without using its "cgo"-tagged build variant. This example provides a worked example of doing the opposite: link a CGO_ENABLED=0 binary with "psx", including some compiled C code. Signed-off-by: Andrew G. Morgan --- contrib/bug216610/Makefile | 23 +++++ contrib/bug216610/README.md | 98 ++++++++++++++++++++++ contrib/bug216610/c/fib.c | 20 +++++ contrib/bug216610/go/.gitignore | 3 + contrib/bug216610/go/go.mod | 3 + contrib/bug216610/go/main.go | 28 +++++++ contrib/bug216610/go/vendor/fibber/fib.go | 26 ++++++ .../bug216610/go/vendor/fibber/fibs_linux_amd64.s | 57 +++++++++++++ 8 files changed, 258 insertions(+) create mode 100644 contrib/bug216610/Makefile create mode 100644 contrib/bug216610/README.md create mode 100644 contrib/bug216610/c/fib.c create mode 100644 contrib/bug216610/go/.gitignore create mode 100644 contrib/bug216610/go/go.mod create mode 100644 contrib/bug216610/go/main.go create mode 100644 contrib/bug216610/go/vendor/fibber/fib.go create mode 100644 contrib/bug216610/go/vendor/fibber/fibs_linux_amd64.s diff --git a/contrib/bug216610/Makefile b/contrib/bug216610/Makefile new file mode 100644 index 0000000..c83284c --- /dev/null +++ b/contrib/bug216610/Makefile @@ -0,0 +1,23 @@ +topdir=$(shell pwd)/../.. +include ../../Make.Rules + +GOTARGET=$(shell eval $$(go env) ; echo $${GOHOSTOS}_$${GOARCH}) + +all: go/fib + +go/fib: go/main.go go/vendor/fibber/fib.syso go/vendor/fibber/fib.go go/vendor/fibber/fibs_linux_amd64.s go/vendor/kernel.org/pub/linux/libs/security/libcap/psx + cd go && CGO_ENABLED=0 go build -o fib main.go + +go/vendor/kernel.org/pub/linux/libs/security/libcap/psx: + mkdir -p go/vendor/kernel.org/pub/linux/libs/security/libcap/ + ln -s $(topdir)/psx $@ + +go/vendor/fibber/fib.syso: c/fib.c + gcc -c -o go/vendor/fibber/fib_$(GOTARGET).syso c/fib.c + +clean: + rm -f *~ + rm -f c/*.o c/*~ + rm -f go/fib go/*~ + rm -f go/vendor/fibber/*.syso go/vendor/fibber/*~ + rm -rf go/vendor/kernel.org diff --git a/contrib/bug216610/README.md b/contrib/bug216610/README.md new file mode 100644 index 0000000..ae59a0d --- /dev/null +++ b/contrib/bug216610/README.md @@ -0,0 +1,98 @@ +# 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 needing the `cgo` variant of `psx` build. However, the go +`"runtime"` package will +[`panic()`](https://cs.opensource.google/go/go/+/refs/tags/go1.19.2:src/runtime/os_linux.go;l=717-720) +if you try this. + +However, in researching that bug, 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_. + +*Caveat Emptor*: this example is potentially very fragile. The Go team +only supports `cgo` linking against C. + +## Content + +In this example we have: +- Some C code, `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/vendor/fibber` that uses this +C compiled compute kernel. +- A top level `Makefile` to build it all. + +This build uses vendored Go packages so I could experiment with +modifications of the `"psx"` package to explore potential changes (of +which there have been none). + +## 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= +fib: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ... +$ +``` +Where `` 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 build a `nocgo` that links +to compute engine style C code. + +## Future thoughts + +At present, this example only works on Linux with `x86_64` (in +go-speak that is `linux_amd64`). This is because I have only provided +some bridging assembly for Go to C calling conventions on that +architecture target (`.../go/vendor/fibber/fibs_linux_amd64.s`). + +Perhaps a later version will have bridging code for all the Go +supported Linux architectures, but it will also have to provide some +mechanism to build the `.../c/fib.c` code to make +`fib_linux_.syso` files. The [cited +bug](https://bugzilla.kernel.org/show_bug.cgi?id=216610) includes some +pointers for how to use Docker to support this. + +The compilation optimization level for `.../c/fib.c` seems to be +important for this example. Depending on which version of the compiler +is being used, the optimization process can make more or less use of +link-time optimizations, which don't seem to work in this example. For +this reason, we don't include `-O` gcc options when compiling that +C file. + +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/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 + +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/go/.gitignore b/contrib/bug216610/go/.gitignore new file mode 100644 index 0000000..68b7ed0 --- /dev/null +++ b/contrib/bug216610/go/.gitignore @@ -0,0 +1,3 @@ +fib +*.syso +vendor/kernel.org diff --git a/contrib/bug216610/go/go.mod b/contrib/bug216610/go/go.mod new file mode 100644 index 0000000..819081e --- /dev/null +++ b/contrib/bug216610/go/go.mod @@ -0,0 +1,3 @@ +module fib + +go 1.18 diff --git a/contrib/bug216610/go/main.go b/contrib/bug216610/go/main.go new file mode 100644 index 0000000..bb5a346 --- /dev/null +++ b/contrib/bug216610/go/main.go @@ -0,0 +1,28 @@ +// Program fib uses the psx package once, and then prints the first +// ten Fibonacci numbers. +package main + +import ( + "fibber" + "fmt" + "log" + "syscall" + + "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/go/vendor/fibber/fib.go b/contrib/bug216610/go/vendor/fibber/fib.go new file mode 100644 index 0000000..e69a309 --- /dev/null +++ b/contrib/bug216610/go/vendor/fibber/fib.go @@ -0,0 +1,26 @@ +package fibber + +import ( + "unsafe" +) + +type State struct { + B, A uint32 +} + +func fibInit(ptr unsafe.Pointer) +func fibNext(ptr unsafe.Pointer) + +// 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{} + fibInit(unsafe.Pointer(&s.B)) + 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() { + fibNext(unsafe.Pointer(&s.B)) +} diff --git a/contrib/bug216610/go/vendor/fibber/fibs_linux_amd64.s b/contrib/bug216610/go/vendor/fibber/fibs_linux_amd64.s new file mode 100644 index 0000000..4e0d800 --- /dev/null +++ b/contrib/bug216610/go/vendor/fibber/fibs_linux_amd64.s @@ -0,0 +1,57 @@ +// 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) + +// Push all of the registers the C callee isn't expected to preserve. +#define PUSHALL() \ + ADJSP $(RINDEX(9)) \ + MOVQ AX, RINDEX(0)(SP) \ + MOVQ CX, RINDEX(1)(SP) \ + MOVQ DX, RINDEX(2)(SP) \ + MOVQ SI, RINDEX(3)(SP) \ + MOVQ DI, RINDEX(4)(SP) \ + MOVQ R8, RINDEX(5)(SP) \ + MOVQ R9, RINDEX(6)(SP) \ + MOVQ R10, RINDEX(7)(SP) \ + MOVQ R11, RINDEX(8)(SP) + +// Pop all of the registers the C callee isn't expected to preserve. +#define POPALL() \ + MOVQ RINDEX(0)(SP), AX \ + MOVQ RINDEX(1)(SP), CX \ + MOVQ RINDEX(2)(SP), DX \ + MOVQ RINDEX(3)(SP), SI \ + MOVQ RINDEX(4)(SP), DI \ + MOVQ RINDEX(5)(SP), R8 \ + MOVQ RINDEX(6)(SP), R9 \ + MOVQ RINDEX(7)(SP), R10 \ + MOVQ RINDEX(8)(SP), R11 \ + ADJSP $-(RINDEX(9)) + +// Header to this function wrapper is the last time we can voluntarily +// yield to some other goroutine. +TEXT ·fibInit(SB),$0-8 + PUSHALL() + MOVQ ptr+RINDEX(0)(FP), DI + CALL fib_init(SB) + POPALL() + RET + +// Header to this function wrapper is the last time we can voluntarily +// yield to some other goroutine. +TEXT ·fibNext(SB),$0-8 + PUSHALL() + MOVQ ptr+RINDEX(0)(FP), DI + CALL fib_next(SB) + POPALL() + RET -- cgit v1.2.3 From 0d528688fe40e9703463b27f27c4dbe485e229a0 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 29 Oct 2022 20:24:17 -0700 Subject: Add support for optimized C compilation to .syso objects. It took me a while to figure out why optimized C compilation seemed to generate miscomputation of the Fibonacci number sequence. It appears to be an unresolved issue with Go's internal linking which is discussed here: https://github.com/golang/go/issues/24321 For a compute kernel, it seems important to be able to accommodate compiler optimization. This adds some refinement for the strategy I'm exploring to address: https://bugzilla.kernel.org/show_bug.cgi?id=216610 Signed-off-by: Andrew G. Morgan --- contrib/bug216610/Makefile | 6 ++-- contrib/bug216610/README.md | 68 ++++++++++++++++++++++++------------ contrib/bug216610/gcc_linux_amd64.sh | 56 +++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 26 deletions(-) create mode 100755 contrib/bug216610/gcc_linux_amd64.sh diff --git a/contrib/bug216610/Makefile b/contrib/bug216610/Makefile index c83284c..aca7ab9 100644 --- a/contrib/bug216610/Makefile +++ b/contrib/bug216610/Makefile @@ -5,15 +5,15 @@ GOTARGET=$(shell eval $$(go env) ; echo $${GOHOSTOS}_$${GOARCH}) all: go/fib -go/fib: go/main.go go/vendor/fibber/fib.syso go/vendor/fibber/fib.go go/vendor/fibber/fibs_linux_amd64.s go/vendor/kernel.org/pub/linux/libs/security/libcap/psx +go/fib: go/main.go go/vendor/fibber/fib.go go/vendor/fibber/fibs_$(GOTARGET).s go/vendor/fibber/fib_$(GOTARGET).syso go/vendor/kernel.org/pub/linux/libs/security/libcap/psx cd go && CGO_ENABLED=0 go build -o fib main.go go/vendor/kernel.org/pub/linux/libs/security/libcap/psx: mkdir -p go/vendor/kernel.org/pub/linux/libs/security/libcap/ ln -s $(topdir)/psx $@ -go/vendor/fibber/fib.syso: c/fib.c - gcc -c -o go/vendor/fibber/fib_$(GOTARGET).syso c/fib.c +go/vendor/fibber/fib_$(GOTARGET).syso: c/fib.c ./gcc_$(GOTARGET).sh + ./gcc_$(GOTARGET).sh -O3 c/fib.c -c -o go/vendor/fibber/fib_$(GOTARGET).syso clean: rm -f *~ diff --git a/contrib/bug216610/README.md b/contrib/bug216610/README.md index ae59a0d..a05a828 100644 --- a/contrib/bug216610/README.md +++ b/contrib/bug216610/README.md @@ -12,34 +12,50 @@ 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 needing the `cgo` variant of `psx` build. However, the go -`"runtime"` package will +`"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. +if you try this because it needs `libpthread` and `[g]libc` to work. -However, in researching that bug, 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 +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 +This preset 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_. +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. So, a Go-native version of `"psx"` is thus +achievable. This is what the example in this present directory does. -*Caveat Emptor*: this example is potentially very fragile. The Go team -only supports `cgo` linking against C. +*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, `fib_init()` and `fib_next()` that combine to implement -a _compute engine_ to determine [Fibonacci + +- 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/vendor/fibber` that uses this +for this is in the sub directory `./c/fib.c`. + +- Some Go code, in the directory `./go/vendor/fibber` that uses this C compiled compute kernel. + +- `gcc_linux_amd64.sh` which is a wrapper for `gcc` that adjusts the +compilation to be digestible by Go's (internal) linker. Using `gcc` +directly instead of this wrapper generates an incomplete binary - +which miscomputes the expected answers. See the discussion below for +what might be going on. + - A top level `Makefile` to build it all. -This build uses vendored Go packages so I could experiment with +This build uses vendored Go packages so one can experiment with modifications of the `"psx"` package to explore potential changes (of which there have been none). @@ -53,7 +69,7 @@ $ make all $ cd contrib/bug216610 $ make clean all ``` -When you run `.../go/fib` it should generate the following output: +When you run `./go/fib` it should generate the following output: ``` $ ./go/fib psx syscall result: PID= @@ -72,26 +88,32 @@ the reported [Bug this example offers an alternative path to build a `nocgo` that links to compute engine style C code. +The reason we have added the `./gcc_linux_amd64.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, if a `R_X86_64_PC32` +relocation entry made in a `.text` section is intended to map into a +`.rodata.cst8` section in a generated `.syso` file, the Go linker +seems to replace this reference with a `0` offset to `(%rip)`. 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. + ## Future thoughts At present, this example only works on Linux with `x86_64` (in go-speak that is `linux_amd64`). This is because I have only provided some bridging assembly for Go to C calling conventions on that -architecture target (`.../go/vendor/fibber/fibs_linux_amd64.s`). +architecture target (`./go/vendor/fibber/fibs_linux_amd64.s`). Perhaps a later version will have bridging code for all the Go supported Linux architectures, but it will also have to provide some -mechanism to build the `.../c/fib.c` code to make +mechanism to build the `./c/fib.c` code to make `fib_linux_.syso` files. The [cited bug](https://bugzilla.kernel.org/show_bug.cgi?id=216610) includes some pointers for how to use Docker to support this. -The compilation optimization level for `.../c/fib.c` seems to be -important for this example. Depending on which version of the compiler -is being used, the optimization process can make more or less use of -link-time optimizations, which don't seem to work in this example. For -this reason, we don't include `-O` gcc options when compiling that -C file. +## Reporting bugs Please report issues or offer improvements to this example via the [Fully Capable `libcap`](https://sites.google.com/site/fullycapable/) diff --git a/contrib/bug216610/gcc_linux_amd64.sh b/contrib/bug216610/gcc_linux_amd64.sh new file mode 100755 index 0000000..0f7fe83 --- /dev/null +++ b/contrib/bug216610/gcc_linux_amd64.sh @@ -0,0 +1,56 @@ +#!/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' -> '.o' +# 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. +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[@]}" -- cgit v1.2.3 From 6521defb406ba8e7217342852a046998332d356c Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 29 Oct 2022 21:30:08 -0700 Subject: Minor clean ups of the contrib/bug216610 code/docs I generated mirror on github to conveniently see the .md docs and found a few typos. Signed-off-by: Andrew G. Morgan --- contrib/bug216610/README.md | 5 +++-- contrib/bug216610/gcc_linux_amd64.sh | 8 +++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/contrib/bug216610/README.md b/contrib/bug216610/README.md index a05a828..c158161 100644 --- a/contrib/bug216610/README.md +++ b/contrib/bug216610/README.md @@ -22,7 +22,7 @@ learned about it from a brief reference in the [Go Programming Language Wiki](https://zchee.github.io/golang-wiki/GcToolchainTricks/). -This preset directory evolved from my attempt to understand and +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 @@ -94,7 +94,8 @@ cross-sectional `%rip` based data addressing that various optimization modes of gcc like to use. Specifically, if a `R_X86_64_PC32` relocation entry made in a `.text` section is intended to map into a `.rodata.cst8` section in a generated `.syso` file, the Go linker -seems to replace this reference with a `0` offset to `(%rip)`. What +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. diff --git a/contrib/bug216610/gcc_linux_amd64.sh b/contrib/bug216610/gcc_linux_amd64.sh index 0f7fe83..a228c53 100755 --- a/contrib/bug216610/gcc_linux_amd64.sh +++ b/contrib/bug216610/gcc_linux_amd64.sh @@ -45,9 +45,11 @@ for arg in "$@"; do final+=("${arg}") fi done -echo final: "${final[@]}" -echo args: "${args[@]}" -echo ses: "${ses[@]}" + +#echo final: "${final[@]}" +#echo args: "${args[@]}" +#echo ses: "${ses[@]}" + "gcc" "${final[@]}" if [[ $? -ne 0 ]]; then echo "failed to compile" -- cgit v1.2.3 From 9bdfc8609add40594fc2537b22722d16ccd56227 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 30 Oct 2022 13:52:38 -0700 Subject: Clean up some of the markdown text. Signed-off-by: Andrew G. Morgan --- contrib/capso/README.md | 23 ++++++++++++----------- contrib/sucap/README.md | 21 ++++++++++----------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/contrib/capso/README.md b/contrib/capso/README.md index 64e9b43..df2e878 100644 --- a/contrib/capso/README.md +++ b/contrib/capso/README.md @@ -1,20 +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 +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 +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" ./capso.so file to bind to the privileged -port 80. +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 explore this example is provided here: +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/sucap/README.md b/contrib/sucap/README.md index 0808912..5cc0dcc 100644 --- a/contrib/sucap/README.md +++ b/contrib/sucap/README.md @@ -1,22 +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: @@ -33,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 -- cgit v1.2.3 From 3f483219d51b9b71822543e0806a19f91f1ed2f1 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Thu, 2 Feb 2023 19:48:35 -0800 Subject: Resolve a couple of compiler warnings. Explicitly add (void) as argument lists for two function definitions: cap_reset_ambient(void) _libcap_initialize(void) Signed-off-by: Andrew G. Morgan --- libcap/cap_alloc.c | 2 +- libcap/cap_proc.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libcap/cap_alloc.c b/libcap/cap_alloc.c index c826e7a..2403354 100644 --- a/libcap/cap_alloc.c +++ b/libcap/cap_alloc.c @@ -17,7 +17,7 @@ static __u8 __libcap_mutex; */ static cap_value_t _cap_max_bits; -__attribute__((constructor (300))) void _libcap_initialize() +__attribute__((constructor (300))) void _libcap_initialize(void) { int errno_saved = errno; _cap_mu_lock(&__libcap_mutex); diff --git a/libcap/cap_proc.c b/libcap/cap_proc.c index 0ce5db7..24bc274 100644 --- a/libcap/cap_proc.c +++ b/libcap/cap_proc.c @@ -363,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); } -- cgit v1.2.3 From 34b0329dc740d9164765bee8cc9787c4242f82bb Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Thu, 2 Feb 2023 20:10:27 -0800 Subject: Up the release version to 2.67 Signed-off-by: Andrew G. Morgan --- Make.Rules | 2 +- cap/go.mod | 2 +- contrib/seccomp/go.mod | 2 +- go/go.mod | 4 ++-- goapps/captrace/go.mod | 2 +- goapps/captree/go.mod | 2 +- goapps/gowns/go.mod | 2 +- goapps/setid/go.mod | 4 ++-- goapps/web/go.mod | 2 +- libcap/include/sys/capability.h | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Make.Rules b/Make.Rules index 26d433a..3175390 100644 --- a/Make.Rules +++ b/Make.Rules @@ -1,7 +1,7 @@ # Common version number defines for libcap LIBTITLE=libcap VERSION=2 -MINOR=66 +MINOR=67 # ## Optional prefixes: diff --git a/cap/go.mod b/cap/go.mod index 599269e..594daae 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.66 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.67 diff --git a/contrib/seccomp/go.mod b/contrib/seccomp/go.mod index 250ce41..96de9a1 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.66 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.67 diff --git a/go/go.mod b/go/go.mod index ac949c5..902f290 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.66 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.66 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.67 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.67 ) diff --git a/goapps/captrace/go.mod b/goapps/captrace/go.mod index 51a3b74..83e31e3 100644 --- a/goapps/captrace/go.mod +++ b/goapps/captrace/go.mod @@ -2,4 +2,4 @@ module captrace go 1.16 -require kernel.org/pub/linux/libs/security/libcap/cap v1.2.66 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.67 diff --git a/goapps/captree/go.mod b/goapps/captree/go.mod index 327826e..b4debb4 100644 --- a/goapps/captree/go.mod +++ b/goapps/captree/go.mod @@ -2,4 +2,4 @@ module captree go 1.16 -require kernel.org/pub/linux/libs/security/libcap/cap v1.2.66 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.67 diff --git a/goapps/gowns/go.mod b/goapps/gowns/go.mod index 455bbee..b8a82d2 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.66 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.67 diff --git a/goapps/setid/go.mod b/goapps/setid/go.mod index 6741535..e7d80e2 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.66 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.66 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.67 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.67 ) diff --git a/goapps/web/go.mod b/goapps/web/go.mod index 0318099..2dce8ce 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.66 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.67 diff --git a/libcap/include/sys/capability.h b/libcap/include/sys/capability.h index 6205598..4c7366c 100644 --- a/libcap/include/sys/capability.h +++ b/libcap/include/sys/capability.h @@ -18,7 +18,7 @@ extern "C" { * Provide a programmatic way to #ifdef around features. */ #define LIBCAP_MAJOR 2 -#define LIBCAP_MINOR 66 +#define LIBCAP_MINOR 67 /* * This file complements the kernel file by providing prototype -- cgit v1.2.3 From cf91d35d62358ffe3d093356d640f7bbfe10e76f Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Mon, 6 Feb 2023 07:50:02 -0800 Subject: Drop an unnecessary use of ", _" from captree.go Signed-off-by: Andrew G. Morgan --- goapps/captree/captree.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/goapps/captree/captree.go b/goapps/captree/captree.go index 5313cb4..7768b11 100644 --- a/goapps/captree/captree.go +++ b/goapps/captree/captree.go @@ -448,7 +448,7 @@ func main() { } var noted []string - for pid, _ := range wanted { + for pid := range wanted { noted = append(noted, pid) } sort.Slice(noted, func(i, j int) bool { -- cgit v1.2.3 From 27954dde34fcb8c306fa4d80fdbb5950f0949e03 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Mon, 6 Feb 2023 17:55:54 -0800 Subject: Provide a method to import preamble and postscript for md man pages If you have local files: .../libcap/doc/local-md.preamble .../libcap/doc/local-md.postscript when you run .../libcap/doc/mkmd.sh these two files will be inlined into the generated index.md file. This addresses: https://bugzilla.kernel.org/show_bug.cgi?id=217007 Signed-off-by: Andrew G. Morgan --- doc/mkmd.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/mkmd.sh b/doc/mkmd.sh index 39beac9..ce8baa2 100755 --- a/doc/mkmd.sh +++ b/doc/mkmd.sh @@ -50,6 +50,14 @@ function do_page () { cat > "${index}" <> "${index}" +fi + +cat >> "${index}" <> "${index}" <> "${index}" +fi + +cat >> "${index}" < Date: Mon, 6 Feb 2023 18:55:40 -0800 Subject: Add some more explicit testing to the psx_test.go code. While we test this in many other places, we didn't test this explicitly in the psx.go local testing before. Now we do. Signed-off-by: Andrew G. Morgan --- psx/psx_test.go | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/psx/psx_test.go b/psx/psx_test.go index 4b90f63..922e7e4 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, val, 0) + if e != 0 { + t.Fatalf("[%d] psx:prctl(SET_KEEPCAPS, %d) failed: %v", tid, val, syscall.Errno(e)) + } + if got != val { + t.Errorf("bad keepcaps value [%d]: 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 -- cgit v1.2.3 From dbb9617e6f78784678776b9df197ac24228ac30d Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Mon, 6 Feb 2023 19:06:27 -0800 Subject: Sigh. Fix some copy-pasta errors with psx_test.go changes. Signed-off-by: Andrew G. Morgan --- psx/psx_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/psx/psx_test.go b/psx/psx_test.go index 922e7e4..40a543f 100644 --- a/psx/psx_test.go +++ b/psx/psx_test.go @@ -63,12 +63,12 @@ func TestShared(t *testing.T) { if !ok { break } - got, _, e := Syscall3(syscall.SYS_PRCTL, prGetKeepCaps, val, 0) + got, _, e := Syscall3(syscall.SYS_PRCTL, prGetKeepCaps, 0, 0) if e != 0 { - t.Fatalf("[%d] psx:prctl(SET_KEEPCAPS, %d) failed: %v", tid, val, syscall.Errno(e)) + t.Fatalf("[%d] psx:prctl(GET_KEEPCAPS) ?= %d failed: %v", tid, val, syscall.Errno(e)) } if got != val { - t.Errorf("bad keepcaps value [%d]: got=%d, want=%d", tid, got, val) + t.Errorf("[%d] bad keepcaps value: got=%d, want=%d", tid, got, val) } if _, ok := <-ch; !ok { break -- cgit v1.2.3 From 329b69ea640960d7afffc1c780ed4dde13549292 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Wed, 8 Feb 2023 19:13:37 -0800 Subject: Be more strict about what symbols are externally visible. Increase the enforcement of the documented libcap API by marking internal library utility functions as "hidden". This also goes for the .so executable entry points. This addresses this bug: https://bugzilla.kernel.org/show_bug.cgi?id=217014 Signed-off-by: Andrew G. Morgan --- libcap/cap_alloc.c | 3 ++- libcap/execable.c | 3 +++ libcap/execable.h | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/libcap/cap_alloc.c b/libcap/cap_alloc.c index 2403354..59fe503 100644 --- a/libcap/cap_alloc.c +++ b/libcap/cap_alloc.c @@ -17,6 +17,7 @@ static __u8 __libcap_mutex; */ static cap_value_t _cap_max_bits; +__attribute__((visibility ("hidden"))) __attribute__((constructor (300))) void _libcap_initialize(void) { int errno_saved = errno; @@ -95,7 +96,7 @@ 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) { struct _cap_alloc_s *header; char *raw_data; diff --git a/libcap/execable.c b/libcap/execable.c index 9d3ae7f..9f7062e 100644 --- a/libcap/execable.c +++ b/libcap/execable.c @@ -18,6 +18,7 @@ static void summary(void) 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++) { @@ -30,6 +31,8 @@ static void summary(void) printf(" %s", name); cap_free(name); } + } else { + return; } printf("\n"); } diff --git a/libcap/execable.h b/libcap/execable.h index fee17b4..7a2d247 100644 --- a/libcap/execable.h +++ b/libcap/execable.h @@ -93,7 +93,8 @@ static void __execable_parse_args(int *argc_p, char ***argv_p) */ #define SO_MAIN \ static void __execable_main(int, char**); \ -extern void __so_start(void); \ +__attribute__((visibility ("hidden"))) \ +void __so_start(void); \ __SO_FORCE_ARG_ALIGNMENT \ void __so_start(void) \ { \ -- cgit v1.2.3 From ddbaa98412398a6766552285c8e3c0dcdf632dbb Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Thu, 9 Feb 2023 20:13:25 -0800 Subject: Make the compare-cap binary clean up after itself. When run via sudo, compare-cap exits with some file capabilities left on its binary file. This is a test binary, so that's not a big problem, however, it does mean that a 2nd run of the program is started with, potentially, a different initial state. This commit fixes that exit condition and addresses: https://bugzilla.kernel.org/show_bug.cgi?id=217018 Signed-off-by: Andrew G. Morgan --- go/compare-cap.go | 12 +++++++----- progs/quicktest.sh | 1 + 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/go/compare-cap.go b/go/compare-cap.go index 5e489e5..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 diff --git a/progs/quicktest.sh b/progs/quicktest.sh index 7366a6c..59e16b0 100755 --- a/progs/quicktest.sh +++ b/progs/quicktest.sh @@ -283,6 +283,7 @@ if [ -f ../go/compare-cap ]; then grep "skipping file cap tests" if [ $? -eq 0 ]; then echo "FAILED not engaging file cap tests" + exit 1 fi echo "PASSED" else -- cgit v1.2.3 From 7e41da10505189b8dbee93b25dea1dfb07a89d9b Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 11 Feb 2023 19:02:11 -0800 Subject: Simplify and refactor the bug215510 code. This code is investigating the issue: https://bugzilla.kernel.org/show_bug.cgi?id=216610 This present commit extends x86_64 (aka amd64) support to 32-bit arm build support. It is now possible cross compile the program for the Raspberry Pi. To do this, the code needs 'docker' to work. Signed-off-by: Andrew G. Morgan --- contrib/bug216610/.gitignore | 3 + contrib/bug216610/Dockerfile | 13 ++++ contrib/bug216610/Makefile | 27 +++++--- contrib/bug216610/README.md | 94 +++++++++++++++----------- contrib/bug216610/c/build.sh | 10 +++ contrib/bug216610/c/gcc.sh | 61 +++++++++++++++++ contrib/bug216610/go/.gitignore | 4 +- contrib/bug216610/go/fibber/fib.go | 32 +++++++++ contrib/bug216610/go/fibber/fibs_linux_amd64.s | 21 ++++++ contrib/bug216610/go/fibber/fibs_linux_arm.s | 23 +++++++ contrib/bug216610/go/go.mod | 2 + contrib/bug216610/go/main.go | 5 +- contrib/bug216610/mkdocker.sh | 18 +++++ contrib/bug216610/package_fns.sh | 47 +++++++++++++ 14 files changed, 309 insertions(+), 51 deletions(-) create mode 100644 contrib/bug216610/.gitignore create mode 100644 contrib/bug216610/Dockerfile create mode 100755 contrib/bug216610/c/build.sh create mode 100755 contrib/bug216610/c/gcc.sh create mode 100644 contrib/bug216610/go/fibber/fib.go create mode 100644 contrib/bug216610/go/fibber/fibs_linux_amd64.s create mode 100644 contrib/bug216610/go/fibber/fibs_linux_arm.s create mode 100755 contrib/bug216610/mkdocker.sh create mode 100755 contrib/bug216610/package_fns.sh 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 index aca7ab9..ce96fb3 100644 --- a/contrib/bug216610/Makefile +++ b/contrib/bug216610/Makefile @@ -5,19 +5,26 @@ GOTARGET=$(shell eval $$(go env) ; echo $${GOHOSTOS}_$${GOARCH}) all: go/fib -go/fib: go/main.go go/vendor/fibber/fib.go go/vendor/fibber/fibs_$(GOTARGET).s go/vendor/fibber/fib_$(GOTARGET).syso go/vendor/kernel.org/pub/linux/libs/security/libcap/psx - cd go && CGO_ENABLED=0 go build -o fib main.go +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 -go/vendor/kernel.org/pub/linux/libs/security/libcap/psx: - mkdir -p go/vendor/kernel.org/pub/linux/libs/security/libcap/ - ln -s $(topdir)/psx $@ +# 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 -go/vendor/fibber/fib_$(GOTARGET).syso: c/fib.c ./gcc_$(GOTARGET).sh - ./gcc_$(GOTARGET).sh -O3 c/fib.c -c -o go/vendor/fibber/fib_$(GOTARGET).syso +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 *~ + rm -f *~ arms rm -f c/*.o c/*~ rm -f go/fib go/*~ - rm -f go/vendor/fibber/*.syso go/vendor/fibber/*~ - rm -rf go/vendor/kernel.org + rm -f go/fibber/*.syso go/fibber/*~ go/fibber/linkage.go diff --git a/contrib/bug216610/README.md b/contrib/bug216610/README.md index c158161..4425715 100644 --- a/contrib/bug216610/README.md +++ b/contrib/bug216610/README.md @@ -7,12 +7,12 @@ 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 +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 needing the `cgo` variant of `psx` build. However, the go -`"runtime"` package will always +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. @@ -25,10 +25,11 @@ 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 +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. So, a Go-native version of `"psx"` is thus -achievable. This is what the example in this present directory does. +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 @@ -42,23 +43,20 @@ 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`. +for this is in the sub directory `c/fib.c`. -- Some Go code, in the directory `./go/vendor/fibber` that uses this -C compiled compute kernel. +- Some Go code, in the directory `go/fibber` that uses this C compiled +compute kernel. -- `gcc_linux_amd64.sh` which is a wrapper for `gcc` that adjusts the -compilation to be digestible by Go's (internal) linker. Using `gcc` -directly instead of this wrapper generates an incomplete binary - -which miscomputes the expected answers. See the discussion below for -what might be going on. +- `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. -This build uses vendored Go packages so one can experiment with -modifications of the `"psx"` package to explore potential changes (of -which there have been none). - ## Building and running the built binary Set things up with: @@ -85,34 +83,54 @@ 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 build a `nocgo` that links -to compute engine style C code. +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 `./gcc_linux_amd64.sh` wrapper for `gcc` -is that we've found the Go linker has a hard time digesting the +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, if a `R_X86_64_PC32` -relocation entry made in a `.text` section is intended to map into a -`.rodata.cst8` section in a generated `.syso` file, the Go linker -seems to [replace this reference with a `0` offset to +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. +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` (in -go-speak that is `linux_amd64`). This is because I have only provided -some bridging assembly for Go to C calling conventions on that -architecture target (`./go/vendor/fibber/fibs_linux_amd64.s`). - -Perhaps a later version will have bridging code for all the Go -supported Linux architectures, but it will also have to provide some -mechanism to build the `./c/fib.c` code to make -`fib_linux_.syso` files. The [cited -bug](https://bugzilla.kernel.org/show_bug.cgi?id=216610) includes some -pointers for how to use Docker to support this. +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 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/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 index 68b7ed0..ae14305 100644 --- a/contrib/bug216610/go/.gitignore +++ b/contrib/bug216610/go/.gitignore @@ -1,3 +1,5 @@ fib *.syso -vendor/kernel.org +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 index 819081e..28379e8 100644 --- a/contrib/bug216610/go/go.mod +++ b/contrib/bug216610/go/go.mod @@ -1,3 +1,5 @@ module fib go 1.18 + +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.67 diff --git a/contrib/bug216610/go/main.go b/contrib/bug216610/go/main.go index bb5a346..65121f6 100644 --- a/contrib/bug216610/go/main.go +++ b/contrib/bug216610/go/main.go @@ -3,11 +3,12 @@ package main import ( - "fibber" "fmt" "log" "syscall" + "fib/fibber" + "kernel.org/pub/linux/libs/security/libcap/psx" ) @@ -20,7 +21,7 @@ func main() { fmt.Println(pid) s := fibber.NewState() fmt.Print("fib: ", s.A, ", ", s.B) - for i:=0; i<8; i++ { + for i := 0; i < 8; i++ { s.Next() fmt.Print(", ", s.B) } 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 <> /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 <.....syso>" >&2 + exit 1 +fi + +if [[ "${syso%.syso}" == "${syso}" ]]; then + echo "2nd argument should be a .syso file" >&2 + exit 1 +fi + +cat< Date: Sat, 11 Feb 2023 21:15:03 -0800 Subject: Drop vendor directory and clean up extra gcc...sh file These three files were left over, they should have been removed in the last commit. Signed-off-by: Andrew G. Morgan --- contrib/bug216610/gcc_linux_amd64.sh | 58 ---------------------- contrib/bug216610/go/vendor/fibber/fib.go | 26 ---------- .../bug216610/go/vendor/fibber/fibs_linux_amd64.s | 57 --------------------- 3 files changed, 141 deletions(-) delete mode 100755 contrib/bug216610/gcc_linux_amd64.sh delete mode 100644 contrib/bug216610/go/vendor/fibber/fib.go delete mode 100644 contrib/bug216610/go/vendor/fibber/fibs_linux_amd64.s diff --git a/contrib/bug216610/gcc_linux_amd64.sh b/contrib/bug216610/gcc_linux_amd64.sh deleted file mode 100755 index a228c53..0000000 --- a/contrib/bug216610/gcc_linux_amd64.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/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' -> '.o' -# 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. -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/vendor/fibber/fib.go b/contrib/bug216610/go/vendor/fibber/fib.go deleted file mode 100644 index e69a309..0000000 --- a/contrib/bug216610/go/vendor/fibber/fib.go +++ /dev/null @@ -1,26 +0,0 @@ -package fibber - -import ( - "unsafe" -) - -type State struct { - B, A uint32 -} - -func fibInit(ptr unsafe.Pointer) -func fibNext(ptr unsafe.Pointer) - -// 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{} - fibInit(unsafe.Pointer(&s.B)) - 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() { - fibNext(unsafe.Pointer(&s.B)) -} diff --git a/contrib/bug216610/go/vendor/fibber/fibs_linux_amd64.s b/contrib/bug216610/go/vendor/fibber/fibs_linux_amd64.s deleted file mode 100644 index 4e0d800..0000000 --- a/contrib/bug216610/go/vendor/fibber/fibs_linux_amd64.s +++ /dev/null @@ -1,57 +0,0 @@ -// 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) - -// Push all of the registers the C callee isn't expected to preserve. -#define PUSHALL() \ - ADJSP $(RINDEX(9)) \ - MOVQ AX, RINDEX(0)(SP) \ - MOVQ CX, RINDEX(1)(SP) \ - MOVQ DX, RINDEX(2)(SP) \ - MOVQ SI, RINDEX(3)(SP) \ - MOVQ DI, RINDEX(4)(SP) \ - MOVQ R8, RINDEX(5)(SP) \ - MOVQ R9, RINDEX(6)(SP) \ - MOVQ R10, RINDEX(7)(SP) \ - MOVQ R11, RINDEX(8)(SP) - -// Pop all of the registers the C callee isn't expected to preserve. -#define POPALL() \ - MOVQ RINDEX(0)(SP), AX \ - MOVQ RINDEX(1)(SP), CX \ - MOVQ RINDEX(2)(SP), DX \ - MOVQ RINDEX(3)(SP), SI \ - MOVQ RINDEX(4)(SP), DI \ - MOVQ RINDEX(5)(SP), R8 \ - MOVQ RINDEX(6)(SP), R9 \ - MOVQ RINDEX(7)(SP), R10 \ - MOVQ RINDEX(8)(SP), R11 \ - ADJSP $-(RINDEX(9)) - -// Header to this function wrapper is the last time we can voluntarily -// yield to some other goroutine. -TEXT ·fibInit(SB),$0-8 - PUSHALL() - MOVQ ptr+RINDEX(0)(FP), DI - CALL fib_init(SB) - POPALL() - RET - -// Header to this function wrapper is the last time we can voluntarily -// yield to some other goroutine. -TEXT ·fibNext(SB),$0-8 - PUSHALL() - MOVQ ptr+RINDEX(0)(FP), DI - CALL fib_next(SB) - POPALL() - RET -- cgit v1.2.3 From 44ab72a6f93481acf012c2b1f47746ca080841cb Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 19 Feb 2023 19:43:13 -0800 Subject: Update documentation for all API functions. There were a few straggler API functions in libcap and libpsx. Also some functions that should be hidden from references outside the library. Signed-off-by: Andrew G. Morgan --- cap/names.go | 4 +++ doc/__psx_syscall.3 | 1 + doc/cap_copy_ext.3 | 19 +++++++++++---- doc/cap_copy_int_check.3 | 1 + doc/cap_set_syscall.3 | 1 + doc/capability.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ doc/capability.notes | 58 -------------------------------------------- doc/libpsx.3 | 25 +++++++++++++++++-- doc/psx_load_syscalls.3 | 1 + doc/values/3.txt | 4 +++ progs/capshdoc.c | 4 +++ psx/psx.c | 1 + 12 files changed, 117 insertions(+), 65 deletions(-) create mode 100644 doc/__psx_syscall.3 create mode 100644 doc/cap_copy_int_check.3 create mode 100644 doc/cap_set_syscall.3 create mode 100644 doc/capability.md delete mode 100644 doc/capability.notes create mode 100644 doc/psx_load_syscalls.3 diff --git a/cap/names.go b/cap/names.go index 356da9e..226aa2b 100644 --- a/cap/names.go +++ b/cap/names.go @@ -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 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_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_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.md b/doc/capability.md new file mode 100644 index 0000000..cfad4c0 --- /dev/null +++ b/doc/capability.md @@ -0,0 +1,63 @@ +# 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 +capabilities are know as privileges). + +The contents of the libcap package are a library and a number of +simple programs that are intended to show how an application/daemon +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 + +### Adopting a role approach to system security + +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_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 to ensure that the daemon is not +running with the `CAP_FOWNER` capability... + +### 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. + +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. + +### 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. + +[Patch is available for 2.2.1 - I just wrote it!] diff --git a/doc/capability.notes b/doc/capability.notes deleted file mode 100644 index 4087c80..0000000 --- a/doc/capability.notes +++ /dev/null @@ -1,58 +0,0 @@ -Overview --------- - -As of Linux 2.2.0, the power of the superuser has been partitioned -into a set of discrete capabilities (in other places, these -capabilities are know as privileges). - -The contents of the libcap package are a library and a number of -simple programs that are intended to show how an application/daemon -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 ------------------------------ - -Adopting a role approach to system security: - -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 -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... - - -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. - -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. - - -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. - -[Patch is available for 2.2.1 - I just wrote it!] diff --git a/doc/libpsx.3 b/doc/libpsx.3 index d117ae8..ef80fee 100644 --- a/doc/libpsx.3 +++ b/doc/libpsx.3 @@ -6,8 +6,14 @@ psx_syscall3, psx_syscall6, psx_set_sensitivity \- POSIX semantics for system ca #include 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_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: @@ -52,7 +58,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 () @@ -74,6 +82,19 @@ prints a stderr notification about how the results differ; and 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/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/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/progs/capshdoc.c b/progs/capshdoc.c index ee7e974..5560ef9 100644 --- a/progs/capshdoc.c +++ b/progs/capshdoc.c @@ -30,6 +30,10 @@ static const char *explanation3[] = { /* cap_fowner = 3 */ "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 */ diff --git a/psx/psx.c b/psx/psx.c index d9c0485..ecd2274 100644 --- a/psx/psx.c +++ b/psx/psx.c @@ -485,6 +485,7 @@ static void *_psx_start_fn(void *data) { * __wrap_pthread_create is the wrapped destination of all regular * pthread_create calls. */ +__attribute__((visibility ("hidden"))) int __wrap_pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) { psx_starter_t *starter = calloc(1, sizeof(psx_starter_t)); -- cgit v1.2.3 From e32563557ba85f0cbdf0baf62f9a4aec392e4158 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 19 Feb 2023 19:57:41 -0800 Subject: Recognize the new man page links. Signed-off-by: Andrew G. Morgan --- doc/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/Makefile b/doc/Makefile index c096a9f..6919488 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -13,6 +13,7 @@ MAN3S = cap_init.3 cap_free.3 cap_dup.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 \ @@ -29,6 +30,7 @@ MAN3S = cap_init.3 cap_free.3 cap_dup.3 \ 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 -- cgit v1.2.3 From 9c084eceb21250d3ffd4cf1c368dd39f99ef4df1 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 11 Mar 2023 18:11:47 -0800 Subject: Replace the README with a README.md Also include the `go mod tidy` detail. Signed-off-by: Andrew G. Morgan --- goapps/web/README | 18 ------------------ goapps/web/README.md | 28 ++++++++++++++++++++++++++++ goapps/web/web.go | 2 ++ 3 files changed, 30 insertions(+), 18 deletions(-) delete mode 100644 goapps/web/README create mode 100644 goapps/web/README.md 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/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 -- cgit v1.2.3 From 8c435a6fecece9e1c0e1625c2add7eaa546bb93c Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 18 Mar 2023 18:02:44 -0700 Subject: Some formatting fixes for the libpsx man page. Signed-off-by: Andrew G. Morgan --- doc/libpsx.3 | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/doc/libpsx.3 b/doc/libpsx.3 index ef80fee..4a0b5b6 100644 --- a/doc/libpsx.3 +++ b/doc/libpsx.3 @@ -5,15 +5,17 @@ psx_syscall3, psx_syscall6, psx_set_sensitivity \- POSIX semantics for system ca .nf #include -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)); + 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: @@ -28,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, @@ -41,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 @@ -85,13 +89,15 @@ 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 +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 +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. -- cgit v1.2.3 From 5496a0e3854dba9374823e9b561ee8c5fd9c59f4 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 19 Mar 2023 20:18:44 -0700 Subject: Tidy up some text explaining cap.NamedCaps. Signed-off-by: Andrew G. Morgan --- cap/names.go | 4 ++-- go/mknames.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cap/names.go b/cap/names.go index 226aa2b..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 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 -- cgit v1.2.3 From 3a93d8edcf7ca6879f7a2298dfafec8ac01d37a7 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 25 Mar 2023 16:55:20 -0700 Subject: Undo hiding the wrapped function call in libpsx. Signed-off-by: Andrew G. Morgan --- psx/psx.c | 1 - 1 file changed, 1 deletion(-) diff --git a/psx/psx.c b/psx/psx.c index ecd2274..d9c0485 100644 --- a/psx/psx.c +++ b/psx/psx.c @@ -485,7 +485,6 @@ static void *_psx_start_fn(void *data) { * __wrap_pthread_create is the wrapped destination of all regular * pthread_create calls. */ -__attribute__((visibility ("hidden"))) int __wrap_pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) { psx_starter_t *starter = calloc(1, sizeof(psx_starter_t)); -- cgit v1.2.3 From 3c7dda330bd9a154bb5b878d31fd591e4951fe17 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 25 Mar 2023 17:03:17 -0700 Subject: Up the release version to 2.68 Signed-off-by: Andrew G. Morgan --- Make.Rules | 2 +- cap/go.mod | 2 +- contrib/bug216610/go/go.mod | 2 +- contrib/seccomp/go.mod | 2 +- go/go.mod | 4 ++-- goapps/captrace/go.mod | 2 +- goapps/captree/go.mod | 2 +- goapps/gowns/go.mod | 2 +- goapps/setid/go.mod | 4 ++-- goapps/web/go.mod | 2 +- libcap/include/sys/capability.h | 2 +- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Make.Rules b/Make.Rules index 3175390..b92de36 100644 --- a/Make.Rules +++ b/Make.Rules @@ -1,7 +1,7 @@ # Common version number defines for libcap LIBTITLE=libcap VERSION=2 -MINOR=67 +MINOR=68 # ## Optional prefixes: diff --git a/cap/go.mod b/cap/go.mod index 594daae..f8e3b50 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.67 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.68 diff --git a/contrib/bug216610/go/go.mod b/contrib/bug216610/go/go.mod index 28379e8..642d136 100644 --- a/contrib/bug216610/go/go.mod +++ b/contrib/bug216610/go/go.mod @@ -2,4 +2,4 @@ module fib go 1.18 -require kernel.org/pub/linux/libs/security/libcap/psx v1.2.67 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.68 diff --git a/contrib/seccomp/go.mod b/contrib/seccomp/go.mod index 96de9a1..1940c88 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.67 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.68 diff --git a/go/go.mod b/go/go.mod index 902f290..ce4559c 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.67 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.67 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.68 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.68 ) diff --git a/goapps/captrace/go.mod b/goapps/captrace/go.mod index 83e31e3..d946e4a 100644 --- a/goapps/captrace/go.mod +++ b/goapps/captrace/go.mod @@ -2,4 +2,4 @@ module captrace go 1.16 -require kernel.org/pub/linux/libs/security/libcap/cap v1.2.67 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.68 diff --git a/goapps/captree/go.mod b/goapps/captree/go.mod index b4debb4..c66106f 100644 --- a/goapps/captree/go.mod +++ b/goapps/captree/go.mod @@ -2,4 +2,4 @@ module captree go 1.16 -require kernel.org/pub/linux/libs/security/libcap/cap v1.2.67 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.68 diff --git a/goapps/gowns/go.mod b/goapps/gowns/go.mod index b8a82d2..e481d89 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.67 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.68 diff --git a/goapps/setid/go.mod b/goapps/setid/go.mod index e7d80e2..312e07a 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.67 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.67 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.68 + kernel.org/pub/linux/libs/security/libcap/psx v1.2.68 ) diff --git a/goapps/web/go.mod b/goapps/web/go.mod index 2dce8ce..1fdf1a9 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.67 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.68 diff --git a/libcap/include/sys/capability.h b/libcap/include/sys/capability.h index 4c7366c..64e64fb 100644 --- a/libcap/include/sys/capability.h +++ b/libcap/include/sys/capability.h @@ -18,7 +18,7 @@ extern "C" { * Provide a programmatic way to #ifdef around features. */ #define LIBCAP_MAJOR 2 -#define LIBCAP_MINOR 67 +#define LIBCAP_MINOR 68 /* * This file complements the kernel file by providing prototype -- cgit v1.2.3 From a4089305d9fb1aac74966727be2244ef831cb598 Mon Sep 17 00:00:00 2001 From: Emanuele Torre Date: Tue, 11 Apr 2023 00:25:46 +0200 Subject: Improve style in man page function prototypes Use type *id everywhere instead of using type * id and type* id in some places. Also remove superflous spaces after commas, and closing parentheses. While doing this, I also fixed a C syntax mistake in an example in cap_launch.3 Signed-off-by: Andrew G. Morgan --- doc/cap_from_text.3 | 6 +++--- doc/cap_launch.3 | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/cap_from_text.3 b/doc/cap_from_text.3 index a0c9282..1a01c7c 100644 --- a/doc/cap_from_text.3 +++ b/doc/cap_from_text.3 @@ -9,9 +9,9 @@ state textual representation translation .nf #include -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); +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 diff --git a/doc/cap_launch.3 b/doc/cap_launch.3 index 1656428..2d186eb 100644 --- a/doc/cap_launch.3 +++ b/doc/cap_launch.3 @@ -8,8 +8,8 @@ cap_launch, cap_launcher_setuid, cap_launcher_setgroups \ .nf #include -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)); @@ -82,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; -- cgit v1.2.3 From 819f941bceb40a96cd3b06654782111e1efd6c6c Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 22 Apr 2023 15:38:29 -0700 Subject: Partially revive fully static binaries. It looks like I broke the kdebug target build when I dropped fully static building of capsh and friends. Discovered this, looking at answering: https://unix.stackexchange.com/questions/741532/launch-process-with-limited-capabilities-on-minimal-busybox-based-system Signed-off-by: Andrew G. Morgan --- kdebug/test-kernel.sh | 4 ++-- progs/Makefile | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/kdebug/test-kernel.sh b/kdebug/test-kernel.sh index 5d480c9..391bb8a 100755 --- a/kdebug/test-kernel.sh +++ b/kdebug/test-kernel.sh @@ -13,8 +13,8 @@ function die { } pushd .. -make all test || die "failed to make all 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 diff --git a/progs/Makefile b/progs/Makefile index 826834c..80f890a 100644 --- a/progs/Makefile +++ b/progs/Makefile @@ -14,6 +14,10 @@ ifeq ($(DYNAMIC),yes) LDPATH = LD_LIBRARY_PATH=../libcap DEPS = ../libcap/libcap.so else +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 @@ -22,6 +26,7 @@ LDFLAGS = -Wl,-Bstatic LDFLAGS_SUFFIX = -Wl,-Bdynamic DEPS = ../libcap/libcap.a endif +endif ../libcap/libcap.a: $(MAKE) -C ../libcap libcap.a -- cgit v1.2.3 From bc6b36682f188020ee4770fae1d41bde5b2c97bb Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Wed, 3 May 2023 19:18:36 -0700 Subject: Correct the check of pthread_create()'s return value. This function returns a positive number (errno) on error, so the code wasn't previously freeing some memory in this situation. Discussion: https://stackoverflow.com/a/3581020/14760867 Credit for finding this bug in libpsx goes to David Gstir of X41 D-Sec GmbH (https://x41-dsec.de/) who performed a security audit of the libcap source code in April of 2023. The audit was sponsored by the Open Source Technology Improvement Fund (https://ostif.org/). Audit ref: LCAP-CR-23-01 (CVE-2023-2602) Signed-off-by: Andrew G. Morgan --- psx/psx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psx/psx.c b/psx/psx.c index d9c0485..65eb2aa 100644 --- a/psx/psx.c +++ b/psx/psx.c @@ -516,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); -- cgit v1.2.3 From 422bec25ae4a1ab03fd4d6f728695ed279173b18 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Wed, 3 May 2023 19:44:22 -0700 Subject: Large strings can confuse libcap's internal strdup code. Avoid something subtle with really long strings: 1073741823 should be enough for anybody. This is an improved fix over something attempted in libcap-2.55 to address some static analysis findings. Reviewing the library, cap_proc_root() and cap_launcher_set_chroot() are the only two calls where the library is potentially exposed to a user controlled string input. Credit for finding this bug in libcap goes to Richard Weinberger of X41 D-Sec GmbH (https://x41-dsec.de/) who performed a security audit of the libcap source code in April of 2023. The audit was sponsored by the Open Source Technology Improvement Fund (https://ostif.org/). Audit ref: LCAP-CR-23-02 (CVE-2023-2603) Signed-off-by: Andrew G. Morgan --- libcap/cap_alloc.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/libcap/cap_alloc.c b/libcap/cap_alloc.c index 59fe503..504abd2 100644 --- a/libcap/cap_alloc.c +++ b/libcap/cap_alloc.c @@ -106,15 +106,17 @@ __attribute__((visibility ("hidden"))) char *_libcap_strdup(const char *old) errno = EINVAL; return NULL; } - len = strlen(old) + 1 + 2*sizeof(__u32); - if (len < sizeof(struct _cap_alloc_s)) { - len = sizeof(struct _cap_alloc_s); - } - if ((len & 0xffffffff) != len) { + + 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) { -- cgit v1.2.3 From 917c8b5d3450870b4f25fd4a5a5198faa9de9aeb Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Wed, 3 May 2023 20:12:52 -0700 Subject: There was a small memory leak in pam_cap.so when libpam returned an error. The function pam_set_data() takes ownership of a memory pointer if the call succeeds, but does not take that ownership if the function fails. Previously, the failure caused no deferred capability setting and a return code PAM_IGNORE. It continues to do that in this case, but no longer leaks the allocated iab memory. This bug was introduced with deferred IAB capability setting support in libcap-2.58. Credit for finding this bug in pam_cap.so goes to X41 D-Sec GmbH (https://x41-dsec.de/) who performed a security audit of the libcap source code in April of 2023. The audit was sponsored by the Open Source Technology Improvement Fund (https://ostif.org/). Audit ref: LCAP-CR-23-100 Signed-off-by: Andrew G. Morgan --- pam_cap/pam_cap.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pam_cap/pam_cap.c b/pam_cap/pam_cap.c index 7e8cade..91278dc 100644 --- a/pam_cap/pam_cap.c +++ b/pam_cap/pam_cap.c @@ -290,7 +290,12 @@ static int set_capabilities(struct pam_cap_s *cs) if (cs->defer) { D(("configured to delay applying IAB")); - pam_set_data(cs->pamh, "pam_cap_iab", iab, iab_apply); + 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)); -- cgit v1.2.3 From 6baf268986bc8791d069a25a0514241b5467e379 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 6 May 2023 22:24:39 -0700 Subject: Ignore the content of a capability.conf file if it is world-writable. Other than the case of /dev/null, there is no situation in which pam_cap.so should act on world writable config files. There are legitimate local administration choices for the file being owned by non-root users, and similarly writable by a group of trusted users. So, we do not require any specific ownership for the file and do not check for writable access based on owner of group membership. Credit for finding this bug in pam_cap.so goes to X41 D-Sec GmbH (https://x41-dsec.de/) who performed a security audit of the libcap source code in April of 2023. The audit was sponsored by the Open Source Technology Improvement Fund (https://ostif.org/). Audit ref: LCAP-CR-23-101 Signed-off-by: Andrew G. Morgan --- pam_cap/.gitignore | 1 + pam_cap/Makefile | 9 ++++++--- pam_cap/pam_cap.c | 26 +++++++++++++++++++++++++- pam_cap/test_pam_cap.c | 13 ++++++++++++- 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/pam_cap/.gitignore b/pam_cap/.gitignore index 87f4186..dac617b 100644 --- a/pam_cap/.gitignore +++ b/pam_cap/.gitignore @@ -4,3 +4,4 @@ test_pam_cap lazylink.so pam_cap_linkopts LIBCAP +incapable.conf diff --git a/pam_cap/Makefile b/pam_cap/Makefile index d852101..258e519 100644 --- a/pam_cap/Makefile +++ b/pam_cap/Makefile @@ -70,13 +70,16 @@ test_pam_cap: test_pam_cap.c pam_cap.c ../libcap/libcap.a testlink: test.o pam_cap.o $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $+ -lpam -ldl $(LIBCAPLIB) -test: testlink test_pam_cap pam_cap.so +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_pam_cap +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 @@ -87,4 +90,4 @@ sudotest: test_pam_cap clean: rm -f *.o *.so testlink lazylink.so test_pam_cap pam_cap_linkopts *~ - rm -f LIBCAP + rm -f LIBCAP incapable.conf diff --git a/pam_cap/pam_cap.c b/pam_cap/pam_cap.c index 91278dc..b9419cb 100644 --- a/pam_cap/pam_cap.c +++ b/pam_cap/pam_cap.c @@ -12,6 +12,7 @@ #endif #include +#include #include #include #include @@ -22,6 +23,7 @@ #include #include #include +#include #include #include @@ -106,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 && @@ -167,6 +190,7 @@ static char *read_capabilities_for_user(const char *user, const char *source) line = NULL; } +close_out_file: fclose(cap_file); defer: @@ -395,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; } diff --git a/pam_cap/test_pam_cap.c b/pam_cap/test_pam_cap.c index 886888e..4bcf236 100644 --- a/pam_cap/test_pam_cap.c +++ b/pam_cap/test_pam_cap.c @@ -237,10 +237,21 @@ int main(int argc, char *argv[]) { printf("failed to parse arguments\n"); exit(1); } - if (read_capabilities_for_user("morgan", "/dev/null") != NULL) { + 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); + } /* * Start out with a cleared inheritable set. -- cgit v1.2.3 From 8bed80f9b54b7bd2a1ee1fdc2124a094733c9356 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sun, 14 May 2023 19:10:04 -0700 Subject: Up the release version to 2.69 Signed-off-by: Andrew G. Morgan --- Make.Rules | 2 +- cap/go.mod | 2 +- contrib/bug216610/go/go.mod | 2 +- contrib/seccomp/go.mod | 2 +- go/go.mod | 4 ++-- goapps/captrace/go.mod | 2 +- goapps/captree/go.mod | 2 +- goapps/gowns/go.mod | 2 +- goapps/setid/go.mod | 4 ++-- goapps/web/go.mod | 2 +- libcap/include/sys/capability.h | 2 +- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Make.Rules b/Make.Rules index b92de36..721fc7a 100644 --- a/Make.Rules +++ b/Make.Rules @@ -1,7 +1,7 @@ # Common version number defines for libcap LIBTITLE=libcap VERSION=2 -MINOR=68 +MINOR=69 # ## Optional prefixes: diff --git a/cap/go.mod b/cap/go.mod index f8e3b50..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.68 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.69 diff --git a/contrib/bug216610/go/go.mod b/contrib/bug216610/go/go.mod index 642d136..8531994 100644 --- a/contrib/bug216610/go/go.mod +++ b/contrib/bug216610/go/go.mod @@ -2,4 +2,4 @@ module fib go 1.18 -require kernel.org/pub/linux/libs/security/libcap/psx v1.2.68 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.69 diff --git a/contrib/seccomp/go.mod b/contrib/seccomp/go.mod index 1940c88..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.68 +require kernel.org/pub/linux/libs/security/libcap/psx v1.2.69 diff --git a/go/go.mod b/go/go.mod index ce4559c..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.68 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.68 + 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/captrace/go.mod b/goapps/captrace/go.mod index d946e4a..9817252 100644 --- a/goapps/captrace/go.mod +++ b/goapps/captrace/go.mod @@ -2,4 +2,4 @@ module captrace go 1.16 -require kernel.org/pub/linux/libs/security/libcap/cap v1.2.68 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.69 diff --git a/goapps/captree/go.mod b/goapps/captree/go.mod index c66106f..09e579c 100644 --- a/goapps/captree/go.mod +++ b/goapps/captree/go.mod @@ -2,4 +2,4 @@ module captree go 1.16 -require kernel.org/pub/linux/libs/security/libcap/cap v1.2.68 +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 e481d89..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.68 +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 312e07a..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.68 - kernel.org/pub/linux/libs/security/libcap/psx v1.2.68 + 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/go.mod b/goapps/web/go.mod index 1fdf1a9..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.68 +require kernel.org/pub/linux/libs/security/libcap/cap v1.2.69 diff --git a/libcap/include/sys/capability.h b/libcap/include/sys/capability.h index 64e64fb..2db9972 100644 --- a/libcap/include/sys/capability.h +++ b/libcap/include/sys/capability.h @@ -18,7 +18,7 @@ extern "C" { * Provide a programmatic way to #ifdef around features. */ #define LIBCAP_MAJOR 2 -#define LIBCAP_MINOR 68 +#define LIBCAP_MINOR 69 /* * This file complements the kernel file by providing prototype -- cgit v1.2.3