diff options
author | Andrew G. Morgan <morgan@kernel.org> | 2020-01-03 14:00:22 -0800 |
---|---|---|
committer | Andrew G. Morgan <morgan@kernel.org> | 2020-01-03 14:00:22 -0800 |
commit | f1f62a748d7c67361e91e32d26abafbfb03eeee4 (patch) | |
tree | 6006486eb4380a4fff648fd4232a4eabeb14edc7 | |
parent | 872d2ee59e29644d73b7530a27404a3d5c8ee42d (diff) | |
download | libcap2-f1f62a748d7c67361e91e32d26abafbfb03eeee4.tar.gz |
Refactor the way we do the psx linkage in libcap.
Since we now have a serialized (linker trick) to initialize libcap
we can reliably compute the number of capabilities of the running
kernel in a race free way. Export the found number of capabilities
with the cap_max_bits() function. This is also what we now use in
both C and Go to define [all]=[eip]. In Go the equivalent function
is cap.MaxBits().
Signed-off-by: Andrew G. Morgan <morgan@kernel.org>
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | cap/cap.go | 7 | ||||
-rw-r--r-- | cap/text.go | 3 | ||||
-rw-r--r-- | libcap/.gitignore | 1 | ||||
-rw-r--r-- | libcap/Makefile | 8 | ||||
-rw-r--r-- | libcap/_makenames.c | 16 | ||||
-rw-r--r-- | libcap/cap_alloc.c | 19 | ||||
-rw-r--r-- | libcap/cap_flag.c | 6 | ||||
-rw-r--r-- | libcap/cap_proc.c | 64 | ||||
-rw-r--r-- | libcap/cap_test.c | 39 | ||||
-rw-r--r-- | libcap/cap_text.c | 40 | ||||
-rw-r--r-- | libcap/include/sys/capability.h | 7 | ||||
-rw-r--r-- | libcap/include/sys/psx_syscall.h | 21 | ||||
-rw-r--r-- | libcap/libcap.h | 16 | ||||
-rw-r--r-- | libcap/psx.c | 18 | ||||
-rw-r--r-- | pam_cap/pam_cap.c | 4 | ||||
-rw-r--r-- | progs/capsh.c | 7 |
17 files changed, 194 insertions, 83 deletions
@@ -35,6 +35,7 @@ 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 $@ @@ -189,6 +189,13 @@ func cInit() { } } +// MaxBits returns the number of kernel-named capabilities discovered +// at runtime in the current system. +func MaxBits() Value { + startUp.Do(cInit) + return Value(maxValues) +} + // NewSet returns an empty capability set. func NewSet() *Set { startUp.Do(cInit) diff --git a/cap/text.go b/cap/text.go index 117bedf..01b45b4 100644 --- a/cap/text.go +++ b/cap/text.go @@ -110,8 +110,7 @@ func (c *Set) String() string { uBins := make([]int, 8) uPatterns := make([]uint, 32*words) c.histo(0, uBins, uPatterns, Value(maxValues), 32*Value(words)) - for i := uint(8); i > 1; { - i-- + for i := uint(7); i > 0; i-- { if uBins[i] == 0 { continue } diff --git a/libcap/.gitignore b/libcap/.gitignore index 0686d52..000c694 100644 --- a/libcap/.gitignore +++ b/libcap/.gitignore @@ -5,5 +5,6 @@ libcap.a libcap.so* libpsx.a _makenames +cap_test libcap.pc libpsx.pc diff --git a/libcap/Makefile b/libcap/Makefile index 0c0b00a..c2cb0d1 100644 --- a/libcap/Makefile +++ b/libcap/Makefile @@ -80,6 +80,12 @@ $(MINLIBNAME): $(CAPOBJS) cap_text.o: cap_text.c $(USE_GPERF_OUTPUT) $(INCLS) $(CC) $(CFLAGS) $(IPATH) $(INCLUDE_GPERF_OUTPUT) -c $< -o $@ +cap_test: cap_test.c libcap.h + $(CC) $(CFLAGS) $(IPATH) $< -o $@ + +test: cap_test + ./cap_test + install: all mkdir -p -m 0755 $(FAKEROOT)$(INCDIR)/sys install -m 0644 include/sys/capability.h $(FAKEROOT)$(INCDIR)/sys @@ -101,5 +107,5 @@ clean: $(LOCALCLEAN) rm -f $(CAPOBJS) $(LIBNAME)* $(STACAPLIBNAME) libcap.pc libpsx.pc rm -f $(PSXOBJS) $(STAPSXLIBNAME) - rm -f cap_names.h cap_names.list.h _makenames $(GPERF_OUTPUT) + rm -f cap_names.h cap_names.list.h _makenames $(GPERF_OUTPUT) cap_test cd include/sys && $(LOCALCLEAN) diff --git a/libcap/_makenames.c b/libcap/_makenames.c index 8cc819b..14dec75 100644 --- a/libcap/_makenames.c +++ b/libcap/_makenames.c @@ -7,6 +7,7 @@ #include <stdio.h> #include <stdlib.h> +#include <string.h> #include <sys/capability.h> /* @@ -26,24 +27,29 @@ const char *pointers[8*sizeof(struct __user_cap_data_struct)]; int main(void) { - int i, maxcaps=0; + int i, maxcaps=0, maxlength=0; for ( i=0; list[i].index >= 0 && list[i].name; ++i ) { if (maxcaps <= list[i].index) { maxcaps = list[i].index + 1; } pointers[list[i].index] = list[i].name; + int n = strlen(list[i].name); + if (n > maxlength) { + maxlength = n; + } } printf("/*\n" " * DO NOT EDIT: this file is generated automatically from\n" " *\n" - " * <linux/capability.h>\n" - " */\n" - "#define __CAP_BITS %d\n" + " * <uapi/linux/capability.h>\n" + " */\n\n" + "#define __CAP_BITS %d\n" + "#define __CAP_NAME_SIZE %d\n" "\n" "#ifdef LIBCAP_PLEASE_INCLUDE_ARRAY\n" - " char const *_cap_names[__CAP_BITS] = {\n", maxcaps); + " char const *_cap_names[__CAP_BITS] = {\n", maxcaps, maxlength+1); for (i=0; i<maxcaps; ++i) { if (pointers[i]) diff --git a/libcap/cap_alloc.c b/libcap/cap_alloc.c index cad0c0a..57991a5 100644 --- a/libcap/cap_alloc.c +++ b/libcap/cap_alloc.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997-8 Andrew G Morgan <morgan@kernel.org> + * Copyright (c) 1997-8,2019 Andrew G Morgan <morgan@kernel.org> * * This file deals with allocation and deallocation of internal * capability sets as specified by POSIX.1e (formerlly, POSIX 6). @@ -8,6 +8,23 @@ #include "libcap.h" /* + * This gets set via the pre-main() executed constructor function below it. + */ +static cap_value_t _cap_max_bits; + +__attribute__((constructor (300))) static void _initialize_libcap(void) { + if (_cap_max_bits) { + return; + } + cap_set_syscall(NULL, NULL); + _binary_search(_cap_max_bits, cap_get_bound, 0, __CAP_MAXBITS, __CAP_BITS); +} + +cap_value_t cap_max_bits(void) { + return _cap_max_bits; +} + +/* * Obtain a blank set of capabilities */ diff --git a/libcap/cap_flag.c b/libcap/cap_flag.c index 52ec3b3..ff3082f 100644 --- a/libcap/cap_flag.c +++ b/libcap/cap_flag.c @@ -21,7 +21,7 @@ int cap_get_flag(cap_t cap_d, cap_value_t value, cap_flag_t set, * Is it a known capability? */ - if (raised && good_cap_t(cap_d) && value >= 0 && value < __CAP_BITS + if (raised && good_cap_t(cap_d) && value >= 0 && value < __CAP_MAXBITS && set >= 0 && set < NUMBER_OF_CAP_SETS) { *raised = isset_cap(cap_d,value,set) ? CAP_SET:CAP_CLEAR; return 0; @@ -45,12 +45,12 @@ int cap_set_flag(cap_t cap_d, cap_flag_t set, * Is it a known capability? */ - if (good_cap_t(cap_d) && no_values > 0 && no_values <= __CAP_BITS + if (good_cap_t(cap_d) && no_values > 0 && no_values < __CAP_MAXBITS && (set >= 0) && (set < NUMBER_OF_CAP_SETS) && (raise == CAP_SET || raise == CAP_CLEAR) ) { int i; for (i=0; i<no_values; ++i) { - if (array_values[i] < 0 || array_values[i] >= __CAP_BITS) { + if (array_values[i] < 0 || array_values[i] >= __CAP_MAXBITS) { _cap_debug("weird capability (%d) - skipped", array_values[i]); } else { int value = array_values[i]; diff --git a/libcap/cap_proc.c b/libcap/cap_proc.c index 310922c..4006151 100644 --- a/libcap/cap_proc.c +++ b/libcap/cap_proc.c @@ -7,6 +7,7 @@ #define _GNU_SOURCE #include <sys/prctl.h> +#include <sys/psx_syscall.h> #include <sys/securebits.h> #include <sys/syscall.h> #include <unistd.h> @@ -41,38 +42,48 @@ static long int (*_libcap_syscall)(long int, long int, long int, long int) static long int (*_libcap_syscall6)(long int, long int, long int, long int, long int, long int, long int) = _cap_syscall6; -static int _libcap_overrode_syscalls; - -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)) -{ - _libcap_syscall = new_syscall; - _libcap_syscall6 = new_syscall6; - _libcap_overrode_syscalls = 1; -} - /* - * libcap<->libpsx subtle linking trick. If -lpsx is linked, then this - * function will get called when psx is initialized. In so doing, - * libcap will opt to use POSIX compliant syscalls for all state - * changing system calls - via psx_syscall(). + * This gets reset to 0 if we are *not* linked with libpsx. */ -void share_psx_syscall(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)); +static int _libcap_overrode_syscalls = 1; -void share_psx_syscall(long int (*syscall_fn)(long int, +/* + * psx_load_syscalls() is weakly defined so we can have it overriden + * by libpsx if that library is linked. Specifically, when libcap + * calls psx_load_sycalls() it is prepared to override the default + * values for the syscalls that libcap uses to change security state. + * As can be seen here this present function is mostly a + * no-op. However, if libpsx is linked, the one present in that + * library (not being weak) will replace this one and the + * _libcap_overrode_syscalls value isn't forced to zero. + */ +__attribute__((weak)) +void psx_load_syscalls(long int (**syscall_fn)(long int, long int, long int, long int), - long int (*syscall6_fn)(long int, + long int (**syscall6_fn)(long int, long int, long int, long int, long int, long int, long int)) { - cap_set_syscall(syscall_fn, syscall6_fn); + _libcap_overrode_syscalls = 0; +} + +/* + * cap_set_syscall overrides the state setting syscalls that libcap does. + * Generally, you don't need to call this manually: libcap tries hard to + * set things up appropriately. + */ +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)) { + if (new_syscall == NULL) { + psx_load_syscalls(&_libcap_syscall, &_libcap_syscall6); + } else { + _libcap_syscall = new_syscall; + _libcap_syscall6 = new_syscall6; + } } static int _libcap_capset(cap_user_header_t header, const cap_user_data_t data) @@ -91,6 +102,9 @@ static int _libcap_prctl6(long int pr_cmd, long int arg1, long int arg2, return _libcap_syscall6(SYS_prctl, pr_cmd, arg1, arg2, arg3, arg4, arg5); } +/* + * cap_get_proc obtains the capability set for the current process. + */ cap_t cap_get_proc(void) { cap_t result; diff --git a/libcap/cap_test.c b/libcap/cap_test.c new file mode 100644 index 0000000..4ea83c8 --- /dev/null +++ b/libcap/cap_test.c @@ -0,0 +1,39 @@ +#include "libcap.h" + +static cap_value_t top; + +static int cf(cap_value_t x) { + return top - x - 1; +} + +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 + }; + int failed = 0; + cap_value_t i; + for (i = 0; vs[i] >= 0; i++) { + cap_value_t ans; + + top = i; + _binary_search(ans, cf, 0, __CAP_MAXBITS, 0); + 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; + } + } + } + return failed; +} + +int main(int argc, char **argv) { + int result = 0; + result = test_cap_bits() | result; + if (result) { + printf("test FAILED\n"); + exit(1); + } +} diff --git a/libcap/cap_text.c b/libcap/cap_text.c index 3ad9d79..00fbbc6 100644 --- a/libcap/cap_text.c +++ b/libcap/cap_text.c @@ -20,8 +20,8 @@ #include INCLUDE_GPERF_OUTPUT #endif -/* Maximum output text length (16 per cap) */ -#define CAP_TEXT_SIZE (16*__CAP_MAXBITS) +/* Maximum output text length */ +#define CAP_TEXT_SIZE (__CAP_NAME_SIZE * __CAP_MAXBITS) /* * Parse a textual representation of capabilities, returning an internal @@ -63,13 +63,14 @@ static char const *namcmp(char const *str, char const *nam) static void forceall(__u32 *flat, __u32 value, unsigned blks) { unsigned n; + cap_value_t cmb = cap_max_bits(); for (n = blks; n--; ) { unsigned base = 32*n; __u32 mask = 0; - if (__CAP_BITS >= base + 32) { + if (cmb >= base + 32) { mask = ~0; - } else if (__CAP_BITS > base) { - mask = (unsigned) ((1ULL << (__CAP_BITS % 32)) - 1); + } else if (cmb > base) { + mask = (unsigned) ((1ULL << (cmb % 32)) - 1); } flat[n] = value & mask; } @@ -113,7 +114,7 @@ static int lookupname(char const **strp) char const *s; unsigned n; - for (n = __CAP_BITS; n--; ) + for (n = cap_max_bits(); n--; ) if (_cap_names[n] && (s = namcmp(str.constp, _cap_names[n]))) { *strp = s; return n; @@ -347,7 +348,6 @@ char *cap_to_text(cap_t caps, ssize_t *length_p) int histo[8]; int m, t; unsigned n; - unsigned cap_maxbits, cap_blks; /* Check arguments */ if (!good_cap_t(caps)) { @@ -355,23 +355,6 @@ char *cap_to_text(cap_t caps, ssize_t *length_p) return NULL; } - switch (caps->head.version) { - case _LINUX_CAPABILITY_VERSION_1: - cap_blks = _LINUX_CAPABILITY_U32S_1; - break; - case _LINUX_CAPABILITY_VERSION_2: - cap_blks = _LINUX_CAPABILITY_U32S_2; - break; - case _LINUX_CAPABILITY_VERSION_3: - cap_blks = _LINUX_CAPABILITY_U32S_3; - break; - default: - errno = EINVAL; - return NULL; - } - - cap_maxbits = 32 * cap_blks; - _cap_debugcap("e = ", *caps, CAP_EFFECTIVE); _cap_debugcap("i = ", *caps, CAP_INHERITABLE); _cap_debugcap("p = ", *caps, CAP_PERMITTED); @@ -379,7 +362,8 @@ char *cap_to_text(cap_t caps, ssize_t *length_p) memset(histo, 0, sizeof(histo)); /* default prevailing state to the named bits */ - for (n = 0; n < __CAP_BITS; n++) + cap_value_t cmb = cap_max_bits(); + for (n = 0; n < cmb; n++) histo[getstateflags(caps, n)]++; /* find which combination of capability sets shares the most bits @@ -401,7 +385,7 @@ char *cap_to_text(cap_t caps, ssize_t *length_p) continue; } *p++ = ' '; - for (n = 0; n < cap_maxbits; n++) { + for (n = 0; n < cmb; n++) { if (getstateflags(caps, n) == t) { char *this_cap_name = cap_to_name(n); if ((strlen(this_cap_name) + (p - buf)) > CAP_TEXT_SIZE) { @@ -436,7 +420,7 @@ char *cap_to_text(cap_t caps, ssize_t *length_p) /* capture remaining unnamed bits - which must all be +. */ memset(histo, 0, sizeof(histo)); - for (n = cap_maxbits-1; n >= __CAP_BITS; n--) + for (n = cmb; n < __CAP_MAXBITS; n++) histo[getstateflags(caps, n)]++; for (t = 8; t-- > 1; ) { @@ -444,7 +428,7 @@ char *cap_to_text(cap_t caps, ssize_t *length_p) continue; } *p++ = ' '; - for (n = __CAP_BITS; n < cap_maxbits; n++) { + for (n = cmb; n < __CAP_MAXBITS; n++) { if (getstateflags(caps, n) == t) { char *this_cap_name = cap_to_name(n); if ((strlen(this_cap_name) + (p - buf)) > CAP_TEXT_SIZE) { diff --git a/libcap/include/sys/capability.h b/libcap/include/sys/capability.h index d69839b..48301fd 100644 --- a/libcap/include/sys/capability.h +++ b/libcap/include/sys/capability.h @@ -47,6 +47,13 @@ typedef struct _cap_struct *cap_t; typedef int cap_value_t; /* + * libcap initialized first unnamed capability of the running kernel. + * capsh includes a runtime test to flag when this is larger than + * what is known to libcap... Time for a new libcap release! + */ +extern cap_value_t cap_max_bits(void); + +/* * Set identifiers */ typedef enum { diff --git a/libcap/include/sys/psx_syscall.h b/libcap/include/sys/psx_syscall.h index 72c8960..6f8ee03 100644 --- a/libcap/include/sys/psx_syscall.h +++ b/libcap/include/sys/psx_syscall.h @@ -104,16 +104,17 @@ int psx_pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); /* - * This function is weakly defined as a no-op in libpsx. If code - * linking to it provides an alternative implementation, that one is - * called instead. For example, libcap provides one of these functions - * so psx can share its syscall functions with libcap at start up and - * thus pthreads automagically become POSIX semantics compliant. + * This function should be used by systems to obtain pointers to the + * two syscall functions provided by the PSX library. A linkage trick + * is to define this function as weak in a library that can optionally + * use libpsx and then, should the caller link -lpsx, that library can + * implicitly use these POSIX semantics syscalls. See libcap for an + * example of this useage. */ -void share_psx_syscall(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)); +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)); #endif /* _SYS_PSX_SYSCALL_H */ diff --git a/libcap/libcap.h b/libcap/libcap.h index b79159b..8118182 100644 --- a/libcap/libcap.h +++ b/libcap/libcap.h @@ -206,4 +206,20 @@ extern int capsetp(pid_t pid, cap_t cap_d); */ #define ssizeof(x) ((ssize_t) sizeof(x)) +/* + * Put this here as a macro so we can unit test it. + */ +#define _binary_search(val, fn, low, high, fallback) do { \ + cap_value_t min = low, max = high; \ + while (min <= max) { \ + cap_value_t mid = (min+max) / 2; \ + if (fn(mid) < 0) { \ + max = mid - 1; \ + } else { \ + min = mid + 1; \ + } \ + } \ + val = min ? min : fallback; \ + } while(0) + #endif /* LIBCAP_H */ diff --git a/libcap/psx.c b/libcap/psx.c index 7b2400f..c2f892b 100644 --- a/libcap/psx.c +++ b/libcap/psx.c @@ -23,18 +23,21 @@ #include <sys/syscall.h> /* - * share_psx_syscall() is invoked to advertize the two functions - * psx_syscall3() and psx_syscall6(). The linkage is weak here so some - * code external to this library can override it transparently during - * the linkage process. + * psx_load_syscalls() is weakly defined so we can have it overriden + * by libpsx if it is linked. Specifically, when libcap calls + * psx_load_sycalls it will override their defaut values. As can be + * seen here this present function is a no-op. However, if libpsx is + * linked, the one present in that library (not being weak) will + * replace this one. */ -__attribute__((weak)) -void share_psx_syscall(long int (*syscall_fn)(long int, +void psx_load_syscalls(long int (**syscall_fn)(long int, long int, long int, long int), - long int (*syscall6_fn)(long int, + long int (**syscall6_fn)(long int, long int, long int, long int, long int, long int, long int)) { + *syscall_fn = psx_syscall3; + *syscall6_fn = psx_syscall6; } /* @@ -145,7 +148,6 @@ static void psx_signal_start(void) { static void psx_syscall_start(void) { psx_tracker.initialized = 1; psx_signal_start(); - share_psx_syscall(psx_syscall3, psx_syscall6); } static void psx_do_registration(pthread_t thread) { diff --git a/pam_cap/pam_cap.c b/pam_cap/pam_cap.c index d4c1e65..a800f58 100644 --- a/pam_cap/pam_cap.c +++ b/pam_cap/pam_cap.c @@ -242,6 +242,10 @@ static int set_capabilities(struct pam_cap_s *cs) while (cap_get_bound(max_caps) >= 0) { max_caps++; } + if (max_caps != cap_max_bits()) { + D(("this vintage of libcap cannot be trusted; give up")); + goto cleanup_caps; + } has_bound = (max_caps != 0); if (has_bound) { bound = calloc(max_caps, sizeof(int)); diff --git a/progs/capsh.c b/progs/capsh.c index dc265d7..ac3d108 100644 --- a/progs/capsh.c +++ b/progs/capsh.c @@ -318,6 +318,13 @@ int main(int argc, char *argv[], char *envp[]) child = 0; + char *temp_name = cap_to_name(cap_max_bits() - 1); + if (temp_name[0] != 'c') { + printf("WARNING: libcap needs an update (cap=%d should have a name).\n", + cap_max_bits() - 1); + } + cap_free(temp_name); + for (i=1; i<argc; ++i) { if (!strncmp("--drop=", argv[i], 7)) { arg_drop(argv[i]+7); |