diff options
author | dec05eba <dec05eba@protonmail.com> | 2024-02-10 19:05:02 +0100 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2024-02-11 02:11:41 +0100 |
commit | 667a6b3b1bc20516d1bf7d8a4611cd3a4d3f3bc6 (patch) | |
tree | 2770b29323939cec51670dd0d1aebd39a8041914 /depends/libcap/libcap/cap_text.c |
Initial commit, done
Diffstat (limited to 'depends/libcap/libcap/cap_text.c')
-rw-r--r-- | depends/libcap/libcap/cap_text.c | 770 |
1 files changed, 770 insertions, 0 deletions
diff --git a/depends/libcap/libcap/cap_text.c b/depends/libcap/libcap/cap_text.c new file mode 100644 index 0000000..7566bd8 --- /dev/null +++ b/depends/libcap/libcap/cap_text.c @@ -0,0 +1,770 @@ +/* + * Copyright (c) 1997-8,2007-8,2019,2021 Andrew G Morgan <morgan@kernel.org> + * Copyright (c) 1997 Andrew Main <zefram@dcs.warwick.ac.uk> + * + * This file deals with exchanging internal and textual + * representations of capability sets. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <stdio.h> + +#define LIBCAP_PLEASE_INCLUDE_ARRAY +#include "libcap.h" + +static char const *_cap_names[__CAP_BITS] = LIBCAP_CAP_NAMES; + +#include <ctype.h> +#include <limits.h> + +#ifdef INCLUDE_GPERF_OUTPUT +/* we need to include it after #define _GNU_SOURCE is set */ +#include INCLUDE_GPERF_OUTPUT +#endif + +/* Maximum output text length */ +#define CAP_TEXT_SIZE (__CAP_NAME_SIZE * __CAP_MAXBITS) + +/* + * Parse a textual representation of capabilities, returning an internal + * representation. + */ + +#define raise_cap_mask(flat, c) (flat)[CAP_TO_INDEX(c)] |= CAP_TO_MASK(c) + +static void setbits(cap_t a, const __u32 *b, cap_flag_t set, unsigned blks) +{ + int n; + for (n = blks; n--; ) { + a->u[n].flat[set] |= b[n]; + } +} + +static void clrbits(cap_t a, const __u32 *b, cap_flag_t set, unsigned blks) +{ + int n; + for (n = blks; n--; ) + a->u[n].flat[set] &= ~b[n]; +} + +static char const *namcmp(char const *str, char const *nam) +{ + while (*nam && tolower((unsigned char)*str) == *nam) { + str++; + nam++; + } + if (*nam || isalnum((unsigned char)*str) || *str == '_') + return NULL; + return str; +} + +/* + * forceall forces all of the kernel named capabilities to be assigned + * the masked value, and zeroed otherwise. Note, if the kernel is ahead + * of libcap, the upper bits will be referred to by number. + */ +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 (cmb >= base + 32) { + mask = ~0; + } else if (cmb > base) { + mask = (unsigned) ((1ULL << (cmb % 32)) - 1); + } + flat[n] = value & mask; + } + + return; +} + +static int lookupname(char const **strp) +{ + union { + char const *constp; + char *p; + } str; + + str.constp = *strp; + if (isdigit(*str.constp)) { + unsigned long n = strtoul(str.constp, &str.p, 0); + if (n >= __CAP_MAXBITS) + return -1; + *strp = str.constp; + return n; + } else { + int c; + size_t len; + + for (len=0; (c = str.constp[len]); ++len) { + if (!(isalpha(c) || (c == '_'))) { + break; + } + } + +#ifdef GPERF_DOWNCASE + const struct __cap_token_s *token_info; + + token_info = __cap_lookup_name(str.constp, len); + if (token_info != NULL) { + *strp = str.constp + len; + return token_info->index; + } +#else /* ie., ndef GPERF_DOWNCASE */ + char const *s; + unsigned n = cap_max_bits(); + if (n > __CAP_BITS) { + n = __CAP_BITS; + } + while (n--) { + if (_cap_names[n] && (s = namcmp(str.constp, _cap_names[n]))) { + *strp = s; + return n; + } + } +#endif /* def GPERF_DOWNCASE */ + + return -1; /* No definition available */ + } +} + +cap_t cap_from_text(const char *str) +{ + cap_t res; + int n; + unsigned cap_blks; + + if (str == NULL) { + _cap_debug("bad argument"); + errno = EINVAL; + return NULL; + } + + if (!(res = cap_init())) + return NULL; + + switch (res->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: + cap_free(res); + errno = EINVAL; + return NULL; + } + + _cap_debug("%s", str); + + for (;;) { + __u32 list[__CAP_BLKS]; + char op; + int flags = 0, listed=0; + + memset(list, 0, sizeof(__u32)*__CAP_BLKS); + + /* skip leading spaces */ + while (isspace((unsigned char)*str)) + str++; + if (!*str) { + _cap_debugcap("e = ", *res, CAP_EFFECTIVE); + _cap_debugcap("i = ", *res, CAP_INHERITABLE); + _cap_debugcap("p = ", *res, CAP_PERMITTED); + + return res; + } + + /* identify caps specified by this clause */ + if (isalnum((unsigned char)*str) || *str == '_') { + for (;;) { + if (namcmp(str, "all")) { + str += 3; + forceall(list, ~0, cap_blks); + } else { + n = lookupname(&str); + if (n == -1) + goto bad; + raise_cap_mask(list, n); + } + if (*str != ',') + break; + if (!isalnum((unsigned char)*++str) && *str != '_') + goto bad; + } + listed = 1; + } else if (*str == '+' || *str == '-') { + goto bad; /* require a list of capabilities */ + } else { + forceall(list, ~0, cap_blks); + } + + /* identify first operation on list of capabilities */ + op = *str++; + if (op == '=' && (*str == '+' || *str == '-')) { + if (!listed) + goto bad; + op = (*str++ == '+' ? 'P':'M'); /* skip '=' and take next op */ + } else if (op != '+' && op != '-' && op != '=') + goto bad; + + /* cycle through list of actions */ + do { + _cap_debug("next char = '%c'", *str); + if (*str && !isspace(*str)) { + switch (*str++) { /* Effective, Inheritable, Permitted */ + case 'e': + flags |= LIBCAP_EFF; + break; + case 'i': + flags |= LIBCAP_INH; + break; + case 'p': + flags |= LIBCAP_PER; + break; + default: + goto bad; + } + } else if (op != '=') { + _cap_debug("only '=' can be followed by space"); + goto bad; + } + + _cap_debug("how to read?"); + switch (op) { /* how do we interpret the caps? */ + case '=': + case 'P': /* =+ */ + case 'M': /* =- */ + clrbits(res, list, CAP_EFFECTIVE, cap_blks); + clrbits(res, list, CAP_PERMITTED, cap_blks); + clrbits(res, list, CAP_INHERITABLE, cap_blks); + if (op == 'M') + goto minus; + /* fall through */ + case '+': + if (flags & LIBCAP_EFF) + setbits(res, list, CAP_EFFECTIVE, cap_blks); + if (flags & LIBCAP_PER) + setbits(res, list, CAP_PERMITTED, cap_blks); + if (flags & LIBCAP_INH) + setbits(res, list, CAP_INHERITABLE, cap_blks); + break; + case '-': + minus: + if (flags & LIBCAP_EFF) + clrbits(res, list, CAP_EFFECTIVE, cap_blks); + if (flags & LIBCAP_PER) + clrbits(res, list, CAP_PERMITTED, cap_blks); + if (flags & LIBCAP_INH) + clrbits(res, list, CAP_INHERITABLE, cap_blks); + break; + } + + /* new directive? */ + if (*str == '+' || *str == '-') { + if (!listed) { + _cap_debug("for + & - must list capabilities"); + goto bad; + } + flags = 0; /* reset the flags */ + op = *str++; + if (!isalpha(*str)) + goto bad; + } + } while (*str && !isspace(*str)); + _cap_debug("next clause"); + } + +bad: + cap_free(res); + res = NULL; + errno = EINVAL; + return res; +} + +/* + * lookup a capability name and return its numerical value + */ +int cap_from_name(const char *name, cap_value_t *value_p) +{ + int n; + + if (((n = lookupname(&name)) >= 0) && (value_p != NULL)) { + *value_p = (unsigned) n; + } + return -(n < 0); +} + +/* + * Convert a single capability index number into a string representation + */ +char *cap_to_name(cap_value_t cap) +{ + char *tmp, *result; + + 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; +} + +/* + * Convert an internal representation to a textual one. The textual + * representation is stored in static memory. It will be overwritten + * on the next occasion that this function is called. + */ + +static int getstateflags(cap_t caps, int capno) +{ + int f = 0; + + if (isset_cap(caps, capno, CAP_EFFECTIVE)) { + f |= LIBCAP_EFF; + } + if (isset_cap(caps, capno, CAP_PERMITTED)) { + f |= LIBCAP_PER; + } + if (isset_cap(caps, capno, CAP_INHERITABLE)) { + f |= LIBCAP_INH; + } + + 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) +{ + char buf[CAP_TEXT_SIZE+CAP_TEXT_BUFFER_ZONE]; + char *p, *base; + int histo[8]; + int m, t; + unsigned n; + + /* Check arguments */ + if (!good_cap_t(caps)) { + errno = EINVAL; + return NULL; + } + + _cap_debugcap("e = ", *caps, CAP_EFFECTIVE); + _cap_debugcap("i = ", *caps, CAP_INHERITABLE); + _cap_debugcap("p = ", *caps, CAP_PERMITTED); + + memset(histo, 0, sizeof(histo)); + + /* default prevailing state to the named bits */ + 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 + we bias to preferring non-set (m=0) with the >= 0 test. Failing + to do this causes strange things to happen with older systems + that don't know about bits 32+. */ + for (m=t=7; t--; ) + if (histo[t] >= histo[m]) + m = t; + + /* blank is not a valid capability set */ + base = buf; + p = sprintf(buf, "=%s%s%s", + (m & LIBCAP_EFF) ? "e" : "", + (m & LIBCAP_INH) ? "i" : "", + (m & LIBCAP_PER) ? "p" : "" ) + buf; + + for (t = 8; t--; ) { + if (t == m || !histo[t]) { + continue; + } + *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; + return NULL; + } + p += sprintf(p, "%s,", this_cap_name); + cap_free(this_cap_name); + } + } + p--; + n = t & ~m; + if (n) { + char op = '+'; + if (base[0] == '=' && base[1] == ' ') { + /* + * Special case all lowered default "= foo,...+eip + * ..." as "foo,...=eip ...". (Equivalent but shorter.) + */ + base += 2; + op = '='; + } + p += sprintf(p, "%c%s%s%s", op, + (n & LIBCAP_EFF) ? "e" : "", + (n & LIBCAP_INH) ? "i" : "", + (n & LIBCAP_PER) ? "p" : ""); + } + n = ~t & m; + if (n) { + p += sprintf(p, "-%s%s%s", + (n & LIBCAP_EFF) ? "e" : "", + (n & LIBCAP_INH) ? "i" : "", + (n & LIBCAP_PER) ? "p" : ""); + } + if (p - buf > CAP_TEXT_SIZE) { + errno = ERANGE; + return NULL; + } + } + + /* capture remaining unnamed bits - which must all be +. */ + memset(histo, 0, sizeof(histo)); + for (n = cmb; n < __CAP_MAXBITS; n++) + histo[getstateflags(caps, n)]++; + + for (t = 8; t-- > 1; ) { + if (!histo[t]) { + continue; + } + *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; + return NULL; + } + p += sprintf(p, "%s,", this_cap_name); + cap_free(this_cap_name); + } + } + p--; + p += sprintf(p, "+%s%s%s", + (t & LIBCAP_EFF) ? "e" : "", + (t & LIBCAP_INH) ? "i" : "", + (t & LIBCAP_PER) ? "p" : ""); + if (p - buf > CAP_TEXT_SIZE) { + errno = ERANGE; + return NULL; + } + } + + _cap_debug("%s", base); + if (length_p) { + *length_p = p - base; + } + + return (_libcap_strdup(base)); +} + +/* + * cap_mode_name returns a text token naming the specified mode. + */ +const char *cap_mode_name(cap_mode_t flavor) { + switch (flavor) { + case CAP_MODE_NOPRIV: + return "NOPRIV"; + case CAP_MODE_PURE1E_INIT: + return "PURE1E_INIT"; + case CAP_MODE_PURE1E: + return "PURE1E"; + case CAP_MODE_UNCERTAIN: + return "UNCERTAIN"; + case CAP_MODE_HYBRID: + return "HYBRID"; + default: + return "UNKNOWN"; + } +} + +/* + * cap_iab_to_text serializes an iab into a canonical text + * representation. + */ +char *cap_iab_to_text(cap_iab_t iab) +{ + char buf[CAP_TEXT_SIZE+CAP_TEXT_BUFFER_ZONE]; + char *p = buf; + cap_value_t c, cmb = cap_max_bits(); + 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; + __u32 bit = 1U << (c & 31); + __u32 ib = iab->i[o] & bit; + __u32 ab = iab->a[o] & bit; + __u32 nbb = iab->nb[o] & bit; + if (!(nbb | ab | ib)) { + continue; + } + if (!first) { + *p++ = ','; + } + if (nbb) { + *p++ = '!'; + keep = 1; + } + if (ab) { + *p++ = '^'; + keep = 1; + } else if (nbb && ib) { + *p++ = '%'; + } + if (keep || ib) { + if (c < __CAP_BITS) { + strcpy(p, _cap_names[c]); + } else { + sprintf(p, "%u", c); + } + p += strlen(p); + first = 0; + } + } + _cap_mu_unlock(&iab->mutex); + } + *p = '\0'; + return _libcap_strdup(buf); +} + +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++) { + /* consume prefixes */ + switch (*text) { + case '!': + flags |= LIBCAP_IAB_NB_FLAG; + continue; + case '^': + flags |= LIBCAP_IAB_IA_FLAG; + continue; + case '%': + flags |= LIBCAP_IAB_I_FLAG; + continue; + default: + break; + } + if (!flags) { + flags = LIBCAP_IAB_I_FLAG; + } + + /* consume cap name */ + cap_value_t c = lookupname(&text); + if (c == -1) { + goto cleanup; + } + unsigned o = c >> 5; + __u32 mask = 1U << (c & 31); + if (flags & LIBCAP_IAB_I_FLAG) { + iab->i[o] |= mask; + } + if (flags & LIBCAP_IAB_A_FLAG) { + iab->a[o] |= mask; + } + if (flags & LIBCAP_IAB_NB_FLAG) { + iab->nb[o] |= mask; + } + + /* rest should be end or comma */ + if (*text == '\0') { + break; + } + if (*text != ',') { + goto cleanup; + } + flags = 0; + } + } + return iab; + +cleanup: + cap_free(iab); + errno = EINVAL; + return NULL; +} + +static __u32 _parse_hex32(const char *c) +{ + int i; + __u32 v = 0; + for (i=0; i < 8; i++, c++) { + v <<= 4; + if (*c == 0 || *c < '0') { + return 0; + } else if (*c <= '9') { + v += *c - '0'; + } else if (*c > 'f') { + return 0; + } else if (*c >= 'a') { + v += *c + 10 - 'a'; + } else if (*c < 'A') { + return 0; + } else if (*c <= 'F') { + v += *c + 10 - 'A'; + } else { + return 0; + } + } + return v; +} + +/* + * _parse_vec_string converts the hex dumps in /proc/<pid>/current into + * an array of u32s - masked as per the forceall() mask. + */ +static __u32 _parse_vec_string(__u32 *vals, const char *c, int invert) +{ + int i; + int words = strlen(c)/8; + if (words > _LIBCAP_CAPABILITY_U32S) { + return 0; + } + forceall(vals, ~0, words); + for (i = 0; i < words; i++) { + __u32 val = _parse_hex32(c+8*(words-1-i)); + if (invert) { + val = ~val; + } + vals[i] &= val; + } + return ~0; +} + +/* + * libcap believes this is the root of the mounted "/proc" + * filesystem. (NULL == "/proc".) + */ +static char *_cap_proc_dir; + +/* + * If the constructor is called (see cap_alloc.c) then we'll need the + * corresponding destructor. + */ +__attribute__((destructor (300))) static void _cleanup_libcap(void) +{ + if (_cap_proc_dir == NULL) { + return; + } + cap_free(_cap_proc_dir); + _cap_proc_dir = NULL; +} + +/* + * cap_proc_root reads and (optionally: when root != NULL) changes + * libcap's notion of where the "/proc" filesystem is mounted. It + * defaults to the value "/proc". Note, this is a global value and not + * considered thread safe to write - so the client should take + * suitable care when changing it. Further, libcap will allocate + * memory for storing the replacement root, and it is this memory that + * is returned. So, when changing the value, the caller should + * cap_free(the-return-value) when done with it. + * + * A return value of NULL implies the default is in effect "/proc". + */ +char *cap_proc_root(const char *root) +{ + char *old = _cap_proc_dir; + if (root != NULL) { + _cap_proc_dir = _libcap_strdup(root); + } + return old; +} + +#define PROC_LINE_MAX (8 + 8*_LIBCAP_CAPABILITY_U32S + 100) +/* + * cap_iab_get_pid fills an IAB tuple from the content of + * /proc/<pid>/status. Linux doesn't support syscall access to the + * needed information, so we parse it out of that file. + */ +cap_iab_t cap_iab_get_pid(pid_t pid) +{ + cap_iab_t iab; + char *path; + FILE *file; + char line[PROC_LINE_MAX]; + const char *proc_root = _cap_proc_dir; + + if (proc_root == NULL) { + proc_root = "/proc"; + } + if (asprintf(&path, "%s/%d/status", proc_root, pid) <= 0) { + return NULL; + } + file = fopen(path, "r"); + free(path); + if (file == NULL) { + return NULL; + } + + iab = cap_iab_init(); + uint ok = 0; + if (iab != NULL) { + while (fgets(line, PROC_LINE_MAX-1, file) != NULL) { + if (strncmp("Cap", line, 3) != 0) { + continue; + } + if (strncmp("Inh:\t", line+3, 5) == 0) { + ok = (_parse_vec_string(iab->i, line+8, 0) & + LIBCAP_IAB_I_FLAG) | ok; + continue; + } + if (strncmp("Bnd:\t", line+3, 5) == 0) { + ok = (_parse_vec_string(iab->nb, line+8, 1) & + LIBCAP_IAB_NB_FLAG) | ok; + continue; + } + if (strncmp("Amb:\t", line+3, 5) == 0) { + ok = (_parse_vec_string(iab->a, line+8, 0) & + LIBCAP_IAB_A_FLAG) | ok; + continue; + } + } + } + if (ok != (LIBCAP_IAB_IA_FLAG | LIBCAP_IAB_NB_FLAG)) { + cap_free(iab); + iab = NULL; + } + fclose(file); + return iab; +} |