summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew G. Morgan <morgan@kernel.org>2020-01-03 14:00:22 -0800
committerAndrew G. Morgan <morgan@kernel.org>2020-01-03 14:00:22 -0800
commitf1f62a748d7c67361e91e32d26abafbfb03eeee4 (patch)
tree6006486eb4380a4fff648fd4232a4eabeb14edc7
parent872d2ee59e29644d73b7530a27404a3d5c8ee42d (diff)
downloadlibcap2-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--Makefile1
-rw-r--r--cap/cap.go7
-rw-r--r--cap/text.go3
-rw-r--r--libcap/.gitignore1
-rw-r--r--libcap/Makefile8
-rw-r--r--libcap/_makenames.c16
-rw-r--r--libcap/cap_alloc.c19
-rw-r--r--libcap/cap_flag.c6
-rw-r--r--libcap/cap_proc.c64
-rw-r--r--libcap/cap_test.c39
-rw-r--r--libcap/cap_text.c40
-rw-r--r--libcap/include/sys/capability.h7
-rw-r--r--libcap/include/sys/psx_syscall.h21
-rw-r--r--libcap/libcap.h16
-rw-r--r--libcap/psx.c18
-rw-r--r--pam_cap/pam_cap.c4
-rw-r--r--progs/capsh.c7
17 files changed, 194 insertions, 83 deletions
diff --git a/Makefile b/Makefile
index 14e29ef..d8cf49c 100644
--- a/Makefile
+++ b/Makefile
@@ -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 $@
diff --git a/cap/cap.go b/cap/cap.go
index e165014..469b3cd 100644
--- a/cap/cap.go
+++ b/cap/cap.go
@@ -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);