diff --git a/libselinux/fuzz/input b/libselinux/fuzz/input new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libselinux/fuzz/selabel_file_compiled-fuzzer.c b/libselinux/fuzz/selabel_file_compiled-fuzzer.c new file mode 100644 index 0000000000..cf0710ad1f --- /dev/null +++ b/libselinux/fuzz/selabel_file_compiled-fuzzer.c @@ -0,0 +1,281 @@ +#include +#include +#include +#include +#include + +#include + +#include "../src/label_file.h" + +extern int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); + +#define MEMFD_FILE_NAME "file_contexts" +#define CTRL_PARTIAL (1U << 0) +#define CTRL_FIND_ALL (1U << 1) +#define CTRL_MODE (1U << 2) + + +__attribute__ ((format(printf, 2, 3))) +static int null_log(int type __attribute__((unused)), const char *fmt __attribute__((unused)), ...) +{ + return 0; +} + +static int validate_context(char **ctxp) +{ + assert(strcmp(*ctxp, "<>") != 0); + + if (*ctxp[0] == '\0') { + errno = EINVAL; + return -1; + } + + return 0; +} + +static int write_full(int fd, const void *data, size_t size) +{ + ssize_t rc; + const unsigned char *p = data; + + while (size > 0) { + rc = write(fd, p, size); + if (rc == -1) { + if (errno == EINTR) + continue; + + return -1; + } + + p += rc; + size -= rc; + } + + return 0; +} + +static FILE* convert_data(const uint8_t *data, size_t size) +{ + FILE* stream; + int fd, rc; + + fd = memfd_create(MEMFD_FILE_NAME, MFD_CLOEXEC); + if (fd == -1) + return NULL; + + rc = write_full(fd, data, size); + if (rc == -1) { + close(fd); + return NULL; + } + + stream = fdopen(fd, "r"); + if (!stream) { + close(fd); + return NULL; + } + + rc = fseek(stream, 0L, SEEK_SET); + if (rc == -1) { + fclose(stream); + return NULL; + } + + return stream; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + struct selabel_handle rec; + struct saved_data sdata = {}; + struct spec_node *root = NULL; + FILE* fp = NULL; + struct lookup_result *result = NULL; + uint8_t control; + uint8_t *fcontext_data1 = NULL, *fcontext_data2 = NULL, *fcontext_data3 = NULL; + char *key = NULL; + size_t fcontext_data1_len, fcontext_data2_len, fcontext_data3_len, key_len; + bool partial, find_all; + mode_t mode; + int rc; + + /* + * Treat first byte as control byte, whether to use partial mode, find all matches or mode to lookup + */ + if (size == 0) + return 0; + + control = data[0]; + data++; + size--; + + if (control & ~(CTRL_PARTIAL | CTRL_FIND_ALL | CTRL_MODE)) + return 0; + + partial = control & CTRL_PARTIAL; + find_all = control & CTRL_FIND_ALL; + /* S_IFSOCK has the highest integer value */ + mode = (control & CTRL_MODE) ? S_IFSOCK : 0; + + + /* + * Split the fuzzer input into up to four pieces: one to three compiled fcontext + * definitions (to mimic file_contexts, file_contexts.homedirs and file_contexts.local, + * and the lookup key + */ + const unsigned char separator[4] = { 0xde, 0xad, 0xbe, 0xef }; + const uint8_t *sep = memmem(data, size, separator, 4); + if (!sep || sep == data) + return 0; + + fcontext_data1_len = sep - data; + fcontext_data1 = malloc(fcontext_data1_len); + if (!fcontext_data1) + goto cleanup; + + memcpy(fcontext_data1, data, fcontext_data1_len); + data += fcontext_data1_len + 4; + size -= fcontext_data1_len + 4; + + sep = memmem(data, size, separator, 4); + if (sep) { + fcontext_data2_len = sep - data; + fcontext_data2 = malloc(fcontext_data2_len); + if (!fcontext_data2) + goto cleanup; + + memcpy(fcontext_data2, data, fcontext_data2_len); + data += fcontext_data2_len + 4; + size -= fcontext_data2_len + 4; + } + + sep = memmem(data, size, separator, 4); + if (sep) { + fcontext_data3_len = sep - data; + fcontext_data3 = malloc(fcontext_data3_len); + if (!fcontext_data3) + goto cleanup; + + memcpy(fcontext_data3, data, fcontext_data3_len); + data += fcontext_data3_len + 4; + size -= fcontext_data3_len + 4; + } + + key_len = size; + key = malloc(key_len + 1); + if (!key) + goto cleanup; + + memcpy(key, data, key_len); + key[key_len] = '\0'; + + + /* + * Mock selabel handle + */ + rec = (struct selabel_handle) { + .backend = SELABEL_CTX_FILE, + .validating = 1, + .data = &sdata, + }; + + selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) { .func_log = &null_log }); + /* validate to pre-compile regular expressions */ + selinux_set_callback(SELINUX_CB_VALIDATE, (union selinux_callback) { .func_validate = &validate_context }); + + root = calloc(1, sizeof(*root)); + if (!root) + goto cleanup; + + sdata.root = root; + + fp = convert_data(fcontext_data1, fcontext_data1_len); + if (!fp) + goto cleanup; + + errno = 0; + rc = load_mmap(fp, fcontext_data1_len, &rec, MEMFD_FILE_NAME); + if (rc) { + assert(errno != 0); + goto cleanup; + } + + fclose(fp); + + fp = convert_data(fcontext_data2, fcontext_data2_len); + if (!fp) + goto cleanup; + + errno = 0; + rc = load_mmap(fp, fcontext_data2_len, &rec, MEMFD_FILE_NAME); + if (rc) { + assert(errno != 0); + goto cleanup; + } + + fclose(fp); + + fp = convert_data(fcontext_data3, fcontext_data3_len); + if (!fp) + goto cleanup; + + errno = 0; + rc = load_mmap(fp, fcontext_data3_len, &rec, MEMFD_FILE_NAME); + if (rc) { + assert(errno != 0); + goto cleanup; + } + + sort_specs(&sdata); + + assert(cmp(&rec, &rec) == SELABEL_EQUAL); + + errno = 0; + result = lookup_all(&rec, key, mode, partial, find_all); + + if (!result) + assert(errno != 0); + + for (const struct lookup_result *res = result; res; res = res->next) { + assert(res->regex_str); + assert(res->regex_str[0] != '\0'); + assert(res->lr->ctx_raw); + assert(res->lr->ctx_raw[0] != '\0'); + assert(strcmp(res->lr->ctx_raw, "<>") != 0); + assert(!res->lr->ctx_trans); + assert(res->lr->validated); + assert(res->prefix_len <= strlen(res->regex_str)); + } + + +cleanup: + free_lookup_result(result); + if (fp) + fclose(fp); + if (sdata.root) { + free_spec_node(sdata.root); + free(sdata.root); + } + + { + struct mmap_area *area, *last_area; + + area = sdata.mmap_areas; + while (area) { + rc = munmap(area->addr, area->len); + assert(rc == 0); + last_area = area; + area = area->next; + free(last_area); + } + } + + free(key); + free(fcontext_data3); + free(fcontext_data2); + free(fcontext_data1); + + /* Non-zero return values are reserved for future use. */ + return 0; +} diff --git a/libselinux/fuzz/selabel_file_text-fuzzer.c b/libselinux/fuzz/selabel_file_text-fuzzer.c new file mode 100644 index 0000000000..5d851de14f --- /dev/null +++ b/libselinux/fuzz/selabel_file_text-fuzzer.c @@ -0,0 +1,225 @@ +#include +#include +#include +#include +#include + +#include + +#include "../src/label_file.h" + +extern int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); + +#define MEMFD_FILE_NAME "file_contexts" +#define CTRL_PARTIAL (1U << 0) +#define CTRL_FIND_ALL (1U << 1) +#define CTRL_MODE (1U << 2) + + +__attribute__ ((format(printf, 2, 3))) +static int null_log(int type __attribute__((unused)), const char *fmt __attribute__((unused)), ...) +{ + return 0; +} + +static int validate_context(char **ctxp) +{ + assert(strcmp(*ctxp, "<>") != 0); + + if (*ctxp[0] == '\0') { + errno = EINVAL; + return -1; + } + + return 0; +} + +static int write_full(int fd, const void *data, size_t size) +{ + ssize_t rc; + const unsigned char *p = data; + + while (size > 0) { + rc = write(fd, p, size); + if (rc == -1) { + if (errno == EINTR) + continue; + + return -1; + } + + p += rc; + size -= rc; + } + + return 0; +} + +static FILE* convert_data(const uint8_t *data, size_t size) +{ + FILE* stream; + int fd, rc; + + fd = memfd_create(MEMFD_FILE_NAME, MFD_CLOEXEC); + if (fd == -1) + return NULL; + + rc = write_full(fd, data, size); + if (rc == -1) { + close(fd); + return NULL; + } + + stream = fdopen(fd, "r"); + if (!stream) { + close(fd); + return NULL; + } + + rc = fseek(stream, 0L, SEEK_SET); + if (rc == -1) { + fclose(stream); + return NULL; + } + + return stream; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + struct selabel_handle rec; + struct saved_data sdata = {}; + struct spec_node *root = NULL; + FILE* fp = NULL; + struct lookup_result *result = NULL; + uint8_t control; + uint8_t *fcontext_data = NULL; + char *key = NULL; + size_t fcontext_data_len, key_len; + bool partial, find_all; + mode_t mode; + int rc; + + /* + * Treat first byte as control byte, whether to use partial mode, find all matches or mode to lookup + */ + if (size == 0) + return 0; + + control = data[0]; + data++; + size--; + + if (control & ~(CTRL_PARTIAL | CTRL_FIND_ALL | CTRL_MODE)) + return 0; + + partial = control & CTRL_PARTIAL; + find_all = control & CTRL_FIND_ALL; + /* S_IFSOCK has the highest integer value */ + mode = (control & CTRL_MODE) ? S_IFSOCK : 0; + + + /* + * Split the fuzzer input into two pieces: the textual fcontext definition and the lookup key + */ + const unsigned char separator[4] = { 0xde, 0xad, 0xbe, 0xef }; + const uint8_t *sep = memmem(data, size, separator, 4); + if (!sep || sep == data) + return 0; + + fcontext_data_len = sep - data; + fcontext_data = malloc(fcontext_data_len); + if (!fcontext_data) + goto cleanup; + + memcpy(fcontext_data, data, fcontext_data_len); + + key_len = size - fcontext_data_len - 4; + key = malloc(key_len + 1); + if (!key) + goto cleanup; + + memcpy(key, sep + 4, key_len); + key[key_len] = '\0'; + + + /* + * Mock selabel handle + */ + rec = (struct selabel_handle) { + .backend = SELABEL_CTX_FILE, + .validating = 1, + .data = &sdata, + }; + + selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) { .func_log = &null_log }); + /* validate to pre-compile regular expressions */ + selinux_set_callback(SELINUX_CB_VALIDATE, (union selinux_callback) { .func_validate = &validate_context }); + + root = calloc(1, sizeof(*root)); + if (!root) + goto cleanup; + + sdata.root = root; + + fp = convert_data(fcontext_data, fcontext_data_len); + if (!fp) + goto cleanup; + + errno = 0; + rc = process_text_file(fp, /*prefix=*/ NULL, &rec, MEMFD_FILE_NAME); + if (rc) { + assert(errno != 0); + goto cleanup; + } + + sort_specs(&sdata); + + assert(cmp(&rec, &rec) == SELABEL_EQUAL); + + errno = 0; + result = lookup_all(&rec, key, mode, partial, find_all); + + if (!result) + assert(errno != 0); + + for (const struct lookup_result *res = result; res; res = res->next) { + assert(res->regex_str); + assert(res->regex_str[0] != '\0'); + assert(res->lr->ctx_raw); + assert(res->lr->ctx_raw[0] != '\0'); + assert(strcmp(res->lr->ctx_raw, "<>") != 0); + assert(!res->lr->ctx_trans); + assert(res->lr->validated); + assert(res->prefix_len <= strlen(res->regex_str)); + } + + +cleanup: + free_lookup_result(result); + if (fp) + fclose(fp); + if (sdata.root) { + free_spec_node(sdata.root); + free(sdata.root); + } + + { + struct mmap_area *area, *last_area; + + area = sdata.mmap_areas; + while (area) { + rc = munmap(area->addr, area->len); + assert(rc == 0); + last_area = area; + area = area->next; + free(last_area); + } + } + + free(key); + free(fcontext_data); + + /* Non-zero return values are reserved for future use. */ + return 0; +} diff --git a/libselinux/include/selinux/avc.h b/libselinux/include/selinux/avc.h index 1f79ba1615..c007b973c5 100644 --- a/libselinux/include/selinux/avc.h +++ b/libselinux/include/selinux/avc.h @@ -20,7 +20,7 @@ extern "C" { */ struct security_id { char * ctx; - unsigned int refcnt; + unsigned int id; }; typedef struct security_id *security_id_t; diff --git a/libselinux/src/avc_sidtab.c b/libselinux/src/avc_sidtab.c index 3303537b10..cb7dfc1d96 100644 --- a/libselinux/src/avc_sidtab.c +++ b/libselinux/src/avc_sidtab.c @@ -4,6 +4,7 @@ * Author : Eamon Walsh, */ #include +#include #include #include #include @@ -43,65 +44,78 @@ int sidtab_init(struct sidtab *s) return rc; } -int sidtab_insert(struct sidtab *s, const char * ctx) +static struct sidtab_node * +sidtab_insert(struct sidtab *s, const char * ctx) { - int hvalue, rc = 0; + unsigned hvalue; struct sidtab_node *newnode; char * newctx; + if (s->nel >= UINT_MAX - 1) + return NULL; + newnode = (struct sidtab_node *)avc_malloc(sizeof(*newnode)); - if (!newnode) { - rc = -1; - goto out; - } + if (!newnode) + return NULL; newctx = strdup(ctx); if (!newctx) { - rc = -1; avc_free(newnode); - goto out; + return NULL; } hvalue = sidtab_hash(newctx); newnode->next = s->htable[hvalue]; newnode->sid_s.ctx = newctx; - newnode->sid_s.refcnt = 1; /* unused */ + newnode->sid_s.id = ++s->nel; s->htable[hvalue] = newnode; - s->nel++; - out: - return rc; + return newnode; } -int -sidtab_context_to_sid(struct sidtab *s, - const char * ctx, security_id_t * sid) +const struct security_id * +sidtab_context_lookup(const struct sidtab *s, const char *ctx) { - int hvalue, rc = 0; - struct sidtab_node *cur; + unsigned hvalue; + const struct sidtab_node *cur; - *sid = NULL; hvalue = sidtab_hash(ctx); - loop: cur = s->htable[hvalue]; while (cur != NULL && strcmp(cur->sid_s.ctx, ctx)) cur = cur->next; - if (cur == NULL) { /* need to make a new entry */ - rc = sidtab_insert(s, ctx); - if (rc) - goto out; - goto loop; /* find the newly inserted node */ + if (cur == NULL) + return NULL; + + return &cur->sid_s; +} + +int +sidtab_context_to_sid(struct sidtab *s, + const char * ctx, security_id_t * sid) +{ + struct sidtab_node *new; + const struct security_id *lookup_sid = sidtab_context_lookup(s, ctx); + + if (lookup_sid) { + /* Dropping const is fine since our sidtab parameter is non-const. */ + *sid = (struct security_id *)lookup_sid; + return 0; } - *sid = &cur->sid_s; - out: - return rc; + new = sidtab_insert(s, ctx); + if (new == NULL) { + *sid = NULL; + return -1; + } + + *sid = &new->sid_s; + return 0; } -void sidtab_sid_stats(struct sidtab *s, char *buf, int buflen) +void sidtab_sid_stats(const struct sidtab *s, char *buf, size_t buflen) { - int i, chain_len, slots_used, max_chain_len; - struct sidtab_node *cur; + size_t i, chain_len, slots_used, max_chain_len; + const struct sidtab_node *cur; slots_used = 0; max_chain_len = 0; @@ -121,8 +135,8 @@ void sidtab_sid_stats(struct sidtab *s, char *buf, int buflen) } snprintf(buf, buflen, - "%s: %u SID entries and %d/%d buckets used, longest " - "chain length %d\n", avc_prefix, s->nel, slots_used, + "%s: %u SID entries and %zu/%d buckets used, longest " + "chain length %zu\n", avc_prefix, s->nel, slots_used, SIDTAB_SIZE, max_chain_len); } @@ -131,7 +145,7 @@ void sidtab_destroy(struct sidtab *s) int i; struct sidtab_node *cur, *temp; - if (!s) + if (!s || !s->htable) return; for (i = 0; i < SIDTAB_SIZE; i++) { @@ -142,7 +156,6 @@ void sidtab_destroy(struct sidtab *s) freecon(temp->sid_s.ctx); avc_free(temp); } - s->htable[i] = NULL; } avc_free(s->htable); s->htable = NULL; diff --git a/libselinux/src/avc_sidtab.h b/libselinux/src/avc_sidtab.h index cc5abe354a..f62fd3535b 100644 --- a/libselinux/src/avc_sidtab.h +++ b/libselinux/src/avc_sidtab.h @@ -24,12 +24,12 @@ struct sidtab { }; int sidtab_init(struct sidtab *s) ; -int sidtab_insert(struct sidtab *s, const char * ctx) ; +const struct security_id * sidtab_context_lookup(const struct sidtab *s, const char *ctx); int sidtab_context_to_sid(struct sidtab *s, const char * ctx, security_id_t * sid) ; -void sidtab_sid_stats(struct sidtab *s, char *buf, int buflen) ; +void sidtab_sid_stats(const struct sidtab *s, char *buf, size_t buflen) ; void sidtab_destroy(struct sidtab *s) ; #endif /* _SELINUX_AVC_SIDTAB_H_ */ diff --git a/libselinux/src/hashtab.c b/libselinux/src/hashtab.c deleted file mode 100644 index 0c6641ed49..0000000000 --- a/libselinux/src/hashtab.c +++ /dev/null @@ -1,234 +0,0 @@ - -/* Author : Stephen Smalley, */ - -/* FLASK */ - -/* - * Implementation of the hash table type. - */ - -#include -#include -#include "hashtab.h" - -hashtab_t selinux_hashtab_create(unsigned int (*hash_value) (hashtab_t h, - const_hashtab_key_t key), - int (*keycmp) (hashtab_t h, - const_hashtab_key_t key1, - const_hashtab_key_t key2), - unsigned int size) -{ - - hashtab_t p; - unsigned int i; - - p = (hashtab_t) malloc(sizeof(hashtab_val_t)); - if (p == NULL) - return p; - - memset(p, 0, sizeof(hashtab_val_t)); - p->size = size; - p->nel = 0; - p->hash_value = hash_value; - p->keycmp = keycmp; - p->htable = (hashtab_ptr_t *) malloc(sizeof(hashtab_ptr_t) * size); - if (p->htable == NULL) { - free(p); - return NULL; - } - for (i = 0; i < size; i++) - p->htable[i] = (hashtab_ptr_t) NULL; - - return p; -} - -int selinux_hashtab_insert(hashtab_t h, hashtab_key_t key, hashtab_datum_t datum) -{ - unsigned int hvalue; - hashtab_ptr_t prev, cur, newnode; - - if (!h) - return HASHTAB_OVERFLOW; - - hvalue = h->hash_value(h, key); - prev = NULL; - cur = h->htable[hvalue]; - while (cur && h->keycmp(h, key, cur->key) > 0) { - prev = cur; - cur = cur->next; - } - - if (cur && (h->keycmp(h, key, cur->key) == 0)) - return HASHTAB_PRESENT; - - newnode = (hashtab_ptr_t) malloc(sizeof(hashtab_node_t)); - if (newnode == NULL) - return HASHTAB_OVERFLOW; - memset(newnode, 0, sizeof(struct hashtab_node)); - newnode->key = key; - newnode->datum = datum; - if (prev) { - newnode->next = prev->next; - prev->next = newnode; - } else { - newnode->next = h->htable[hvalue]; - h->htable[hvalue] = newnode; - } - - h->nel++; - return HASHTAB_SUCCESS; -} - -int selinux_hashtab_remove(hashtab_t h, hashtab_key_t key, - void (*destroy) (hashtab_key_t k, - hashtab_datum_t d, void *args), void *args) -{ - unsigned int hvalue; - hashtab_ptr_t cur, last; - - if (!h) - return HASHTAB_MISSING; - - hvalue = h->hash_value(h, key); - last = NULL; - cur = h->htable[hvalue]; - while (cur != NULL && h->keycmp(h, key, cur->key) > 0) { - last = cur; - cur = cur->next; - } - - if (cur == NULL || (h->keycmp(h, key, cur->key) != 0)) - return HASHTAB_MISSING; - - if (last == NULL) - h->htable[hvalue] = cur->next; - else - last->next = cur->next; - - if (destroy) - destroy(cur->key, cur->datum, args); - free(cur); - h->nel--; - return HASHTAB_SUCCESS; -} - -hashtab_datum_t selinux_hashtab_search(hashtab_t h, const_hashtab_key_t key) -{ - - unsigned int hvalue; - hashtab_ptr_t cur; - - if (!h) - return NULL; - - hvalue = h->hash_value(h, key); - cur = h->htable[hvalue]; - while (cur != NULL && h->keycmp(h, key, cur->key) > 0) - cur = cur->next; - - if (cur == NULL || (h->keycmp(h, key, cur->key) != 0)) - return NULL; - - return cur->datum; -} - -void selinux_hashtab_destroy(hashtab_t h) -{ - unsigned int i; - hashtab_ptr_t cur, temp; - - if (!h) - return; - - for (i = 0; i < h->size; i++) { - cur = h->htable[i]; - while (cur != NULL) { - temp = cur; - cur = cur->next; - free(temp); - } - h->htable[i] = NULL; - } - - free(h->htable); - h->htable = NULL; - - free(h); -} - -void selinux_hashtab_destroy_key(hashtab_t h, - int (*destroy_key) (hashtab_key_t k)) -{ - unsigned int i; - hashtab_ptr_t cur, temp; - - if (!h) - return; - - for (i = 0; i < h->size; i++) { - cur = h->htable[i]; - while (cur != NULL) { - temp = cur; - cur = cur->next; - destroy_key(temp->key); - free(temp); - } - h->htable[i] = NULL; - } - - free(h->htable); - h->htable = NULL; - - free(h); -} - -int selinux_hashtab_map(hashtab_t h, - int (*apply) (hashtab_key_t k, - hashtab_datum_t d, void *args), void *args) -{ - unsigned int i; - hashtab_ptr_t cur; - int ret; - - if (!h) - return HASHTAB_SUCCESS; - - for (i = 0; i < h->size; i++) { - cur = h->htable[i]; - while (cur != NULL) { - ret = apply(cur->key, cur->datum, args); - if (ret) - return ret; - cur = cur->next; - } - } - return HASHTAB_SUCCESS; -} - -void selinux_hashtab_hash_eval(hashtab_t h, char *tag) -{ - unsigned int i; - int chain_len, slots_used, max_chain_len; - hashtab_ptr_t cur; - - slots_used = 0; - max_chain_len = 0; - for (i = 0; i < h->size; i++) { - cur = h->htable[i]; - if (cur) { - slots_used++; - chain_len = 0; - while (cur) { - chain_len++; - cur = cur->next; - } - - if (chain_len > max_chain_len) - max_chain_len = chain_len; - } - } - - printf - ("%s: %d entries and %d/%d buckets used, longest chain length %d\n", - tag, h->nel, slots_used, h->size, max_chain_len); -} diff --git a/libselinux/src/hashtab.h b/libselinux/src/hashtab.h deleted file mode 100644 index 6fbf5fb42c..0000000000 --- a/libselinux/src/hashtab.h +++ /dev/null @@ -1,117 +0,0 @@ - -/* Author : Stephen Smalley, */ - -/* FLASK */ - -/* - * A hash table (hashtab) maintains associations between - * key values and datum values. The type of the key values - * and the type of the datum values is arbitrary. The - * functions for hash computation and key comparison are - * provided by the creator of the table. - */ - -#ifndef _SELINUX_HASHTAB_H_ -#define _SELINUX_HASHTAB_H_ - -#include -#include -#include - -typedef char *hashtab_key_t; /* generic key type */ -typedef const char *const_hashtab_key_t; /* constant generic key type */ -typedef void *hashtab_datum_t; /* generic datum type */ - -typedef struct hashtab_node *hashtab_ptr_t; - -typedef struct hashtab_node { - hashtab_key_t key; - hashtab_datum_t datum; - hashtab_ptr_t next; -} hashtab_node_t; - -typedef struct hashtab_val { - hashtab_ptr_t *htable; /* hash table */ - unsigned int size; /* number of slots in hash table */ - uint32_t nel; /* number of elements in hash table */ - unsigned int (*hash_value) (struct hashtab_val * h, const_hashtab_key_t key); /* hash function */ - int (*keycmp) (struct hashtab_val * h, const_hashtab_key_t key1, const_hashtab_key_t key2); /* key comparison function */ -} hashtab_val_t; - -typedef hashtab_val_t *hashtab_t; - -/* Define status codes for hash table functions */ -#define HASHTAB_SUCCESS 0 -#define HASHTAB_OVERFLOW -ENOMEM -#define HASHTAB_PRESENT -EEXIST -#define HASHTAB_MISSING -ENOENT - -/* - Creates a new hash table with the specified characteristics. - - Returns NULL if insufficient space is available or - the new hash table otherwise. - */ -extern hashtab_t selinux_hashtab_create(unsigned int (*hash_value) (hashtab_t h, - const_hashtab_key_t - key), - int (*keycmp) (hashtab_t h, - const_hashtab_key_t key1, - const_hashtab_key_t key2), - unsigned int size); -/* - Inserts the specified (key, datum) pair into the specified hash table. - - Returns HASHTAB_OVERFLOW if insufficient space is available or - HASHTAB_PRESENT if there is already an entry with the same key or - HASHTAB_SUCCESS otherwise. - */ -extern int selinux_hashtab_insert(hashtab_t h, hashtab_key_t k, hashtab_datum_t d); - -/* - Removes the entry with the specified key from the hash table. - Applies the specified destroy function to (key,datum,args) for - the entry. - - Returns HASHTAB_MISSING if no entry has the specified key or - HASHTAB_SUCCESS otherwise. - */ -extern int selinux_hashtab_remove(hashtab_t h, hashtab_key_t k, - void (*destroy) (hashtab_key_t k, - hashtab_datum_t d, - void *args), void *args); - -/* - Searches for the entry with the specified key in the hash table. - - Returns NULL if no entry has the specified key or - the datum of the entry otherwise. - */ -extern hashtab_datum_t selinux_hashtab_search(hashtab_t h, const_hashtab_key_t k); - -/* - Destroys the specified hash table. - */ -extern void selinux_hashtab_destroy(hashtab_t h); -extern void selinux_hashtab_destroy_key(hashtab_t h, - int (*destroy_key) (hashtab_key_t k)); - -/* - Applies the specified apply function to (key,datum,args) - for each entry in the specified hash table. - - The order in which the function is applied to the entries - is dependent upon the internal structure of the hash table. - - If apply returns a non-zero status, then hashtab_map will cease - iterating through the hash table and will propagate the error - return to its caller. - */ -extern int selinux_hashtab_map(hashtab_t h, - int (*apply) (hashtab_key_t k, - hashtab_datum_t d, - void *args), void *args); - -extern void selinux_hashtab_hash_eval(hashtab_t h, char *tag); - -#endif diff --git a/libselinux/src/label.c b/libselinux/src/label.c index 06d743ec4d..2c51029031 100644 --- a/libselinux/src/label.c +++ b/libselinux/src/label.c @@ -124,18 +124,32 @@ static inline int selabel_is_validate_set(const struct selinux_opt *opts, int selabel_validate(struct selabel_lookup_rec *contexts) { - int rc = 0; + bool validated; + int rc; - if (contexts->validated) - goto out; + validated = __atomic_load_n(&contexts->validated, __ATOMIC_ACQUIRE); + if (validated) + return 0; + + __pthread_mutex_lock(&contexts->lock); + + /* Check if another thread validated the context while we waited on the mutex */ + validated = __atomic_load_n(&contexts->validated, __ATOMIC_ACQUIRE); + if (validated) { + __pthread_mutex_unlock(&contexts->lock); + return 0; + } rc = selinux_validate(&contexts->ctx_raw); + if (rc == 0) + __atomic_store_n(&contexts->validated, true, __ATOMIC_RELEASE); + + __pthread_mutex_unlock(&contexts->lock); + if (rc < 0) - goto out; + return -1; - contexts->validated = true; -out: - return rc; + return 0; } /* Public API helpers */ @@ -143,11 +157,35 @@ static int selabel_fini(const struct selabel_handle *rec, struct selabel_lookup_rec *lr, bool translating) { + char *ctx_trans; + int rc; + if (compat_validate(rec, lr, rec->spec_file, lr->lineno)) return -1; - if (translating && !lr->ctx_trans && - selinux_raw_to_trans_context(lr->ctx_raw, &lr->ctx_trans)) + if (!translating) + return 0; + + ctx_trans = __atomic_load_n(&lr->ctx_trans, __ATOMIC_ACQUIRE); + if (ctx_trans) + return 0; + + __pthread_mutex_lock(&lr->lock); + + /* Check if another thread translated the context while we waited on the mutex */ + ctx_trans = __atomic_load_n(&lr->ctx_trans, __ATOMIC_ACQUIRE); + if (ctx_trans) { + __pthread_mutex_unlock(&lr->lock); + return 0; + } + + rc = selinux_raw_to_trans_context(lr->ctx_raw, &ctx_trans); + if (rc == 0) + __atomic_store_n(&lr->ctx_trans, ctx_trans, __ATOMIC_RELEASE); + + __pthread_mutex_unlock(&lr->lock); + + if (rc) return -1; return 0; diff --git a/libselinux/src/label_backends_android.c b/libselinux/src/label_backends_android.c index 5bad24f20d..cbe932aee2 100644 --- a/libselinux/src/label_backends_android.c +++ b/libselinux/src/label_backends_android.c @@ -91,7 +91,7 @@ static int process_line(struct selabel_handle *rec, unsigned int nspec = data->nspec; const char *errbuf = NULL; - items = read_spec_entries(line_buf, &errbuf, 2, &prop, &context); + items = read_spec_entries(line_buf, strlen(line_buf), &errbuf, 2, &prop, &context); if (items < 0) { if (errbuf) { selinux_log(SELINUX_ERROR, diff --git a/libselinux/src/label_db.c b/libselinux/src/label_db.c index 40d5fc4ad7..eb060ede05 100644 --- a/libselinux/src/label_db.c +++ b/libselinux/src/label_db.c @@ -186,6 +186,7 @@ db_close(struct selabel_handle *rec) free(spec->key); free(spec->lr.ctx_raw); free(spec->lr.ctx_trans); + __pthread_mutex_destroy(&spec->lr.lock); } free(catalog); } @@ -358,6 +359,7 @@ db_init(const struct selinux_opt *opts, unsigned nopts, free(spec->key); free(spec->lr.ctx_raw); free(spec->lr.ctx_trans); + __pthread_mutex_destroy(&spec->lr.lock); } free(catalog); fclose(filp); diff --git a/libselinux/src/label_file.c b/libselinux/src/label_file.c index b9c3be2a8e..267f708ef5 100644 --- a/libselinux/src/label_file.c +++ b/libselinux/src/label_file.c @@ -3,9 +3,11 @@ * * Author : Eamon Walsh * Author : Stephen Smalley + * Author : Christian Göttsche */ #include +#include #include #include #include @@ -19,163 +21,117 @@ #include #include -#include "hashtab.h" #include "callbacks.h" #include "label_internal.h" +#include "selinux_internal.h" #include "label_file.h" -/* controls the shrink multiple of the hashtab length */ -#define SHRINK_MULTIS 1 -struct chkdups_key { - char *regex; - unsigned int mode; -}; +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +# define FUZZ_EXTERN +#else +# define FUZZ_EXTERN static +#endif /* FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */ + /* - * Internals, mostly moved over from matchpathcon.c + * Warn about duplicate specifications. */ - -/* return the length of the text that is the stem of a file name */ -static int get_stem_from_file_name(const char *const buf) +static int nodups_spec_node(const struct spec_node *node, const char *path) { - const char *tmp = strchr(buf + 1, '/'); + int rc = 0; - if (!tmp) - return 0; - return tmp - buf; -} + if (node->literal_specs_num > 1) { + for (uint32_t i = 0; i < node->literal_specs_num - 1; i++) { + const struct literal_spec *node1 = &node->literal_specs[i]; + const struct literal_spec *node2 = &node->literal_specs[i+1]; -/* find the stem of a file name, returns the index into stem_arr (or -1 if - * there is no match - IE for a file in the root directory or a regex that is - * too complex for us). */ -static int find_stem_from_file(struct saved_data *data, const char *key) -{ - int i; - int stem_len = get_stem_from_file_name(key); + if (strcmp(node1->literal_match, node2->literal_match) != 0) + continue; - if (!stem_len) - return -1; - for (i = 0; i < data->num_stems; i++) { - if (stem_len == data->stem_arr[i].len - && !strncmp(key, data->stem_arr[i].buf, stem_len)) { - return i; + if (node1->file_kind != LABEL_FILE_KIND_ALL && node2->file_kind != LABEL_FILE_KIND_ALL && node1->file_kind != node2->file_kind) + continue; + + rc = -1; + errno = EINVAL; + if (strcmp(node1->lr.ctx_raw, node2->lr.ctx_raw) != 0) { + COMPAT_LOG + (SELINUX_ERROR, + "%s: Multiple different specifications for %s %s (%s and %s).\n", + path, + file_kind_to_string(node1->file_kind), + node1->literal_match, + node1->lr.ctx_raw, + node2->lr.ctx_raw); + } else { + COMPAT_LOG + (SELINUX_ERROR, + "%s: Multiple same specifications for %s %s.\n", + path, + file_kind_to_string(node1->file_kind), + node1->literal_match); + } } } - return -1; -} - -/* - * hash calculation and key comparison of hash table - */ -ignore_unsigned_overflow_ -static unsigned int symhash(hashtab_t h, const_hashtab_key_t key) -{ - const struct chkdups_key *k = (const struct chkdups_key *)key; - const char *p = NULL; - size_t size; - unsigned int val = 0; - - size = strlen(k->regex); - for (p = k->regex; ((size_t) (p - k->regex)) < size; p++) - val = - ((val << 4) | (val >> (8 * sizeof(unsigned int) - 4))) ^ (*p); - return val % h->size; -} -static int symcmp(hashtab_t h - __attribute__ ((unused)), const_hashtab_key_t key1, - const_hashtab_key_t key2) -{ - const struct chkdups_key *a = (const struct chkdups_key *)key1; - const struct chkdups_key *b = (const struct chkdups_key *)key2; + if (node->regex_specs_num > 1) { + for (uint32_t i = 0; i < node->regex_specs_num - 1; i++) { + const struct regex_spec *node1 = &node->regex_specs[i]; + const struct regex_spec *node2 = &node->regex_specs[i+1]; - return strcmp(a->regex, b->regex) || (a->mode && b->mode && a->mode != b->mode); -} + if (node1->prefix_len != node2->prefix_len) + continue; -static int destroy_chkdups_key(hashtab_key_t key) -{ - free(key); + if (strcmp(node1->regex_str, node2->regex_str) != 0) + continue; - return 0; -} + if (node1->file_kind != LABEL_FILE_KIND_ALL && node2->file_kind != LABEL_FILE_KIND_ALL && node1->file_kind != node2->file_kind) + continue; -/* - * Warn about duplicate specifications. - */ -static int nodups_specs(struct saved_data *data, const char *path) -{ - int rc = 0, ret = 0; - unsigned int ii; - struct spec *curr_spec, *spec_arr = data->spec_arr; - struct chkdups_key *new = NULL; - unsigned int hashtab_len = (data->nspec / SHRINK_MULTIS) ? data->nspec / SHRINK_MULTIS : 1; - - hashtab_t hash_table = selinux_hashtab_create(symhash, symcmp, hashtab_len); - if (!hash_table) { - rc = -1; - COMPAT_LOG(SELINUX_ERROR, "%s: hashtab create failed.\n", path); - return rc; - } - for (ii = 0; ii < data->nspec; ii++) { - new = (struct chkdups_key *)malloc(sizeof(struct chkdups_key)); - if (!new) { - rc = -1; - selinux_hashtab_destroy_key(hash_table, destroy_chkdups_key); - COMPAT_LOG(SELINUX_ERROR, "%s: hashtab key create failed.\n", path); - return rc; - } - new->regex = spec_arr[ii].regex_str; - new->mode = spec_arr[ii].mode; - ret = selinux_hashtab_insert(hash_table, (hashtab_key_t)new, &spec_arr[ii]); - if (ret == HASHTAB_SUCCESS) - continue; - if (ret == HASHTAB_PRESENT) { - curr_spec = - (struct spec *)selinux_hashtab_search(hash_table, (hashtab_key_t)new); rc = -1; errno = EINVAL; - free(new); - if (strcmp(spec_arr[ii].lr.ctx_raw, curr_spec->lr.ctx_raw)) { + if (strcmp(node1->lr.ctx_raw, node2->lr.ctx_raw) != 0) { COMPAT_LOG (SELINUX_ERROR, - "%s: Multiple different specifications for %s (%s and %s).\n", - path, curr_spec->regex_str, - spec_arr[ii].lr.ctx_raw, - curr_spec->lr.ctx_raw); + "%s: Multiple different specifications for %s %s (%s and %s).\n", + path, + file_kind_to_string(node1->file_kind), + node1->regex_str, + node1->lr.ctx_raw, + node2->lr.ctx_raw); } else { COMPAT_LOG (SELINUX_ERROR, - "%s: Multiple same specifications for %s.\n", - path, curr_spec->regex_str); + "%s: Multiple same specifications for %s %s.\n", + path, + file_kind_to_string(node1->file_kind), + node1->regex_str); } } - if (ret == HASHTAB_OVERFLOW) { - rc = -1; - free(new); - COMPAT_LOG - (SELINUX_ERROR, - "%s: hashtab happen memory error.\n", - path); - break; - } } - selinux_hashtab_destroy_key(hash_table, destroy_chkdups_key); + for (uint32_t i = 0; i < node->children_num; i++) { + int rc2; + + rc2 = nodups_spec_node(&node->children[i], path); + if (rc2) + rc = rc2; + } return rc; } -static int process_text_file(FILE *fp, const char *prefix, - struct selabel_handle *rec, const char *path) +FUZZ_EXTERN int process_text_file(FILE *fp, const char *prefix, + struct selabel_handle *rec, const char *path) { int rc; size_t line_len; + ssize_t nread; unsigned int lineno = 0; char *line_buf = NULL; - while (getline(&line_buf, &line_len, fp) > 0) { - rc = process_line(rec, path, prefix, line_buf, ++lineno); + while ((nread = getline(&line_buf, &line_len, fp)) > 0) { + rc = process_line(rec, path, prefix, line_buf, nread, ++lineno); if (rc) goto out; } @@ -185,300 +141,795 @@ static int process_text_file(FILE *fp, const char *prefix, return rc; } -static int load_mmap(FILE *fp, size_t len, struct selabel_handle *rec, - const char *path) +static int merge_mmap_spec_nodes(struct spec_node *restrict dest, struct spec_node *restrict source) { - struct saved_data *data = (struct saved_data *)rec->data; - int rc; - char *addr, *str_buf; - int *stem_map; - struct mmap_area *mmap_area; - uint32_t i, magic, version; - uint32_t entry_len, stem_map_len, regex_array_len; - const char *reg_version; - const char *reg_arch; - char reg_arch_matches = 0; + /* Nodes should have the same stem */ + assert((dest->stem == NULL && source->stem == NULL) || + (dest->stem && source->stem && dest->stem_len && source->stem_len && strcmp(dest->stem, source->stem) == 0)); + /* Source should be loaded from mmap, so we can assume its data is sorted */ + assert(source->from_mmap); - mmap_area = malloc(sizeof(*mmap_area)); - if (!mmap_area) { - return -1; + + /* + * Merge literal specs + */ + if (source->literal_specs_num > 0) { + if (dest->literal_specs_num > 0) { + struct literal_spec *lspecs; + uint32_t lspecs_num; + + if (__builtin_add_overflow(dest->literal_specs_num, source->literal_specs_num, &lspecs_num)) + return -1; + + lspecs = reallocarray(dest->literal_specs, lspecs_num, sizeof(struct literal_spec)); + if (!lspecs) + return -1; + + memcpy(&lspecs[dest->literal_specs_num], source->literal_specs, source->literal_specs_num * sizeof(struct literal_spec)); + + dest->literal_specs = lspecs; + dest->literal_specs_num = lspecs_num; + dest->literal_specs_alloc = lspecs_num; + + /* Cleanup moved source */ + for (uint32_t i = 0; i < source->literal_specs_num; i++) { + source->literal_specs[i].lr.ctx_raw = NULL; + source->literal_specs[i].lr.ctx_trans = NULL; + } + + } else { + assert(dest->literal_specs == NULL); + dest->literal_specs = source->literal_specs; + dest->literal_specs_num = source->literal_specs_num; + dest->literal_specs_alloc = source->literal_specs_alloc; + source->literal_specs = NULL; + source->literal_specs_num = 0; + source->literal_specs_alloc = 0; + } } - addr = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fileno(fp), 0); - if (addr == MAP_FAILED) { - free(mmap_area); - perror("mmap"); - return -1; + + /* + * Merge regex specs + */ + if (source->regex_specs_num > 0) { + if (dest->regex_specs_num > 0) { + struct regex_spec *rspecs; + uint32_t rspecs_num; + + if (__builtin_add_overflow(dest->regex_specs_num, source->regex_specs_num, &rspecs_num)) + return -1; + + rspecs = reallocarray(dest->regex_specs, rspecs_num, sizeof(struct regex_spec)); + if (!rspecs) + return -1; + + memcpy(&rspecs[dest->regex_specs_num], source->regex_specs, source->regex_specs_num * sizeof(struct regex_spec)); + + dest->regex_specs = rspecs; + dest->regex_specs_num = rspecs_num; + dest->regex_specs_alloc = rspecs_num; + + /* Cleanup moved source */ + for (uint32_t i = 0; i < source->regex_specs_num; i++) { + source->regex_specs[i].lr.ctx_raw = NULL; + source->regex_specs[i].lr.ctx_trans = NULL; + source->regex_specs[i].regex = NULL; + source->regex_specs[i].regex_compiled = false; + } + } else { + assert(dest->regex_specs == NULL); + dest->regex_specs = source->regex_specs; + dest->regex_specs_num = source->regex_specs_num; + dest->regex_specs_alloc = source->regex_specs_alloc; + source->regex_specs = NULL; + source->regex_specs_num = 0; + source->regex_specs_alloc = 0; + } } - /* save where we mmap'd the file to cleanup on close() */ - mmap_area->addr = mmap_area->next_addr = addr; - mmap_area->len = mmap_area->next_len = len; - mmap_area->next = data->mmap_areas; - data->mmap_areas = mmap_area; - /* check if this looks like an fcontext file */ - rc = next_entry(&magic, mmap_area, sizeof(uint32_t)); - if (rc < 0 || magic != SELINUX_MAGIC_COMPILED_FCONTEXT) - return -1; + /* + * Merge child nodes + */ + if (source->children_num > 0) { + if (dest->children_num > 0) { + struct spec_node *new_children; + uint32_t iter_dest, iter_source, new_children_alloc, new_children_num, remaining_dest, remaining_source; - /* check if this version is higher than we understand */ - rc = next_entry(&version, mmap_area, sizeof(uint32_t)); - if (rc < 0 || version > SELINUX_COMPILED_FCONTEXT_MAX_VERS) - return -1; + if (__builtin_add_overflow(dest->children_num, source->children_num, &new_children_alloc)) + return -1; - reg_version = regex_version(); - if (!reg_version) - return -1; + /* Over-allocate in favor of re-allocating multiple times */ + new_children = calloc(new_children_alloc, sizeof(struct spec_node)); + if (!new_children) + return -1; - reg_arch = regex_arch_string(); - if (!reg_arch) + /* Since source is loaded from mmap its child nodes are sorted */ + qsort(dest->children, dest->children_num, sizeof(struct spec_node), compare_spec_node); + + for (iter_dest = 0, iter_source = 0, new_children_num = 0; iter_dest < dest->children_num && iter_source < source->children_num;) { + struct spec_node *child_dest = &dest->children[iter_dest]; + struct spec_node *child_source = &source->children[iter_source]; + int r; + + r = strcmp(child_dest->stem, child_source->stem); + if (r == 0) { + int rc; + + rc = merge_mmap_spec_nodes(child_dest, child_source); + if (rc) { + free(new_children); + return rc; + } + + new_children[new_children_num++] = *child_dest; + free_spec_node(child_source); + iter_dest++; + iter_source++; + } else if (r < 0) { + new_children[new_children_num++] = *child_dest; + iter_dest++; + } else { + new_children[new_children_num++] = *child_source; + iter_source++; + } + } + + remaining_dest = dest->children_num - iter_dest; + remaining_source = source->children_num - iter_source; + assert(!remaining_dest || !remaining_source); + assert(new_children_num + remaining_dest + remaining_source <= new_children_alloc); + + if (remaining_dest > 0) { + memcpy(&new_children[new_children_num], &dest->children[iter_dest], remaining_dest * sizeof(struct spec_node)); + new_children_num += remaining_dest; + } + + if (remaining_source > 0) { + memcpy(&new_children[new_children_num], &source->children[iter_source], remaining_source * sizeof(struct spec_node)); + new_children_num += remaining_source; + } + + free(dest->children); + dest->children = new_children; + dest->children_alloc = new_children_alloc; + dest->children_num = new_children_num; + + free(source->children); + source->children = NULL; + source->children_alloc = 0; + source->children_num = 0; + + } else { + assert(dest->children == NULL); + dest->children = source->children; + dest->children_num = source->children_num; + dest->children_alloc = source->children_alloc; + source->children = NULL; + source->children_num = 0; + source->children_alloc = 0; + } + } + + return 0; +} + +static inline bool entry_size_check(const struct mmap_area *mmap_area, size_t nmenb, size_t size) +{ + size_t required; + + if (__builtin_mul_overflow(nmenb, size, &required)) + return true; + + return required > mmap_area->next_len; +} + +struct context_array { + char **data; + uint32_t size; +}; + +static void free_context_array(struct context_array *ctx_array) +{ + if (!ctx_array->data) + return; + + for (uint32_t i = 0; i < ctx_array->size; i++) + free(ctx_array->data[i]); + + free(ctx_array->data); +} + +static int load_mmap_ctxarray(struct mmap_area *mmap_area, const char *path, struct context_array *ctx_array, bool validating) +{ + uint32_t data_u32, count; + uint16_t data_u16, ctx_len; + char *ctx; + int rc; + + /* + * Read number of context definitions + */ + rc = next_entry(&data_u32, mmap_area, sizeof(uint32_t)); + if (rc < 0) return -1; + count = be32toh(data_u32); - if (version >= SELINUX_COMPILED_FCONTEXT_PCRE_VERS) { + if (entry_size_check(mmap_area, count, 3 * sizeof(char))) + return -1; - len = strlen(reg_version); + (*ctx_array).data = calloc(count, sizeof(char *)); + if (!(*ctx_array).data) { + (*ctx_array).size = 0; + return -1; + } + (*ctx_array).size = count; - rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t)); + for (uint32_t i = 0; i < count; i++) { + /* + * Read raw context + * We need to allocate it on the heap since it might get free'd and replaced in a + * SELINUX_CB_VALIDATE callback. + */ + rc = next_entry(&data_u16, mmap_area, sizeof(uint16_t)); if (rc < 0) return -1; + ctx_len = be16toh(data_u16); + + if (ctx_len == 0 || ctx_len == UINT16_MAX) + return -1; - /* Check version lengths */ - if (len != entry_len) + if (entry_size_check(mmap_area, ctx_len, sizeof(char))) return -1; - /* Check if regex version mismatch */ - str_buf = malloc(entry_len + 1); - if (!str_buf) + ctx = malloc(ctx_len + 1); + if (!ctx) return -1; - rc = next_entry(str_buf, mmap_area, entry_len); + rc = next_entry(ctx, mmap_area, ctx_len); if (rc < 0) { - free(str_buf); + free(ctx); return -1; } + ctx[ctx_len] = '\0'; - str_buf[entry_len] = '\0'; - if ((strcmp(str_buf, reg_version) != 0)) { - COMPAT_LOG(SELINUX_ERROR, - "Regex version mismatch, expected: %s actual: %s\n", - reg_version, str_buf); - free(str_buf); - return -1; + if (validating && strcmp(ctx, "<>") != 0) { + if (selinux_validate(&ctx) < 0) { + selinux_log(SELINUX_ERROR, "%s: context %s is invalid\n", + path, ctx); + free(ctx); + return -1; + } } - free(str_buf); - if (version >= SELINUX_COMPILED_FCONTEXT_REGEX_ARCH) { - len = strlen(reg_arch); + (*ctx_array).data[i] = ctx; + } - rc = next_entry(&entry_len, mmap_area, - sizeof(uint32_t)); - if (rc < 0) - return -1; + return 0; +} - /* Check arch string lengths */ - if (len != entry_len) { - /* - * Skip the entry and conclude that we have - * a mismatch, which is not fatal. - */ - next_entry(NULL, mmap_area, entry_len); - goto end_arch_check; - } +static int load_mmap_literal_spec(struct mmap_area *mmap_area, bool validating, + struct literal_spec *lspec, const struct context_array *ctx_array) +{ + uint32_t data_u32, ctx_id; + uint16_t data_u16, regex_len, lmatch_len; + uint8_t data_u8; + int rc; - /* Check if arch string mismatch */ - str_buf = malloc(entry_len + 1); - if (!str_buf) - return -1; + lspec->from_mmap = true; - rc = next_entry(str_buf, mmap_area, entry_len); - if (rc < 0) { - free(str_buf); - return -1; - } - str_buf[entry_len] = '\0'; - reg_arch_matches = strcmp(str_buf, reg_arch) == 0; - free(str_buf); + /* + * Read raw context id + * We need to allocate it on the heap since it might get free'd and replaced in a + * SELINUX_CB_VALIDATE callback. + */ + rc = next_entry(&data_u32, mmap_area, sizeof(uint32_t)); + if (rc < 0) + return -1; + ctx_id = be32toh(data_u32); + + if (ctx_id == 0 || ctx_id == UINT32_MAX || ctx_id > ctx_array->size) + return -1; + + lspec->lr.ctx_raw = strdup(ctx_array->data[ctx_id - 1]); + if (!lspec->lr.ctx_raw) + return -1; + + if (validating) + /* validated in load_mmap_ctxarray() */ + lspec->lr.validated = true; + + + /* + * Read original regex + */ + rc = next_entry(&data_u16, mmap_area, sizeof(uint16_t)); + if (rc < 0) + return -1; + regex_len = be16toh(data_u16); + + if (regex_len <= 1) + return -1; + + lspec->regex_str = mmap_area->next_addr; + rc = next_entry(NULL, mmap_area, regex_len); + if (rc < 0) + return -1; + + if (lspec->regex_str[0] == '\0' || lspec->regex_str[regex_len - 1] != '\0') + return -1; + + + /* + * Read literal match + */ + rc = next_entry(&data_u16, mmap_area, sizeof(uint16_t)); + if (rc < 0) + return -1; + lmatch_len = be16toh(data_u16); + + if (lmatch_len <= 1) + return -1; + + lspec->literal_match = mmap_area->next_addr; + rc = next_entry(NULL, mmap_area, lmatch_len); + if (rc < 0) + return -1; + + if (lspec->literal_match[0] == '\0' || lspec->literal_match[lmatch_len - 1] != '\0') + return -1; + + lspec->prefix_len = lmatch_len - 1; + + if (lspec->prefix_len > strlen(lspec->regex_str)) + return -1; + + + /* + * Read file kind + */ + rc = next_entry(&data_u8, mmap_area, sizeof(uint8_t)); + if (rc < 0) + return -1; + lspec->file_kind = data_u8; + + + return 0; +} + +static int load_mmap_regex_spec(struct mmap_area *mmap_area, bool validating, bool do_load_precompregex, + struct regex_spec *rspec, const struct context_array *ctx_array) +{ + uint32_t data_u32, ctx_id; + uint16_t data_u16, regex_len; + uint8_t data_u8; + int rc; + + rspec->from_mmap = true; + + + /* + * Read raw context id + * We need to allocate it on the heap since it might get free'd and replaced in a + * SELINUX_CB_VALIDATE callback. + */ + rc = next_entry(&data_u32, mmap_area, sizeof(uint32_t)); + if (rc < 0) + return -1; + ctx_id = be32toh(data_u32); + + if (ctx_id == 0 || ctx_id == UINT32_MAX || ctx_id > ctx_array->size) + return -1; + + rspec->lr.ctx_raw = strdup(ctx_array->data[ctx_id - 1]); + if (!rspec->lr.ctx_raw) + return -1; + + if (validating) + /* validated in load_mmap_ctxarray() */ + rspec->lr.validated = true; + + + /* + * Read original regex + */ + rc = next_entry(&data_u16, mmap_area, sizeof(uint16_t)); + if (rc < 0) + return -1; + regex_len = be16toh(data_u16); + + if (regex_len <= 1) + return -1; + + rspec->regex_str = mmap_area->next_addr; + rc = next_entry(NULL, mmap_area, regex_len); + if (rc < 0) + return -1; + + if (rspec->regex_str[0] == '\0' || rspec->regex_str[regex_len - 1] != '\0') + return -1; + + + /* + * Read prefix length + */ + rc = next_entry(&data_u16, mmap_area, sizeof(uint16_t)); + if (rc < 0) + return -1; + rspec->prefix_len = be16toh(data_u16); + + if (rspec->prefix_len > strlen(rspec->regex_str)) + return -1; + + + /* + * Read file kind + */ + rc = next_entry(&data_u8, mmap_area, sizeof(uint8_t)); + if (rc < 0) + return -1; + rspec->file_kind = data_u8; + + + /* + * Read pcre regex related data + */ + rc = regex_load_mmap(mmap_area, &rspec->regex, do_load_precompregex, + &rspec->regex_compiled); + if (rc < 0) + return -1; + + __pthread_mutex_init(&rspec->regex_lock, NULL); + + + return 0; +} + +static int load_mmap_spec_node(struct mmap_area *mmap_area, const char *path, bool validating, bool do_load_precompregex, + struct spec_node *node, bool is_root, const struct context_array *ctx_array) +{ + uint32_t data_u32, lspec_num, rspec_num, children_num; + uint16_t data_u16, stem_len; + int rc; + + node->from_mmap = true; + + + /* + * Read stem + */ + rc = next_entry(&data_u16, mmap_area, sizeof(uint16_t)); + if (rc < 0) + return -1; + stem_len = be16toh(data_u16); + + if (stem_len == 0) + return -1; + + if ((stem_len == 1) != is_root) + return -1; + + node->stem_len = stem_len - 1; + node->stem = mmap_area->next_addr; + rc = next_entry(NULL, mmap_area, stem_len); + if (rc < 0) + return -1; + + if (is_root) + node->stem = NULL; + else if (node->stem[0] == '\0' || node->stem[stem_len - 1] != '\0') + return -1; + + + /* + * Read literal specs + */ + rc = next_entry(&data_u32, mmap_area, sizeof(uint32_t)); + if (rc < 0) + return -1; + lspec_num = be32toh(data_u32); + + if (lspec_num == UINT32_MAX) + return -1; + + if (lspec_num > 0) { + if (entry_size_check(mmap_area, lspec_num, 3 * sizeof(uint16_t) + sizeof(uint32_t) + 6 * sizeof(char))) + return -1; + + node->literal_specs = calloc(lspec_num, sizeof(struct literal_spec)); + if (!node->literal_specs) + return -1; + + node->literal_specs_num = lspec_num; + node->literal_specs_alloc = lspec_num; + + for (uint32_t i = 0; i < lspec_num; i++) { + rc = load_mmap_literal_spec(mmap_area, validating, &node->literal_specs[i], ctx_array); + if (rc) + return -1; } } -end_arch_check: - /* allocate the stems_data array */ - rc = next_entry(&stem_map_len, mmap_area, sizeof(uint32_t)); + + /* + * Read regex specs + */ + rc = next_entry(&data_u32, mmap_area, sizeof(uint32_t)); if (rc < 0) return -1; + rspec_num = be32toh(data_u32); + + if (rspec_num == UINT32_MAX) + return -1; + + if (rspec_num > 0) { + if (entry_size_check(mmap_area, rspec_num, sizeof(uint32_t) + 3 * sizeof(uint16_t) + 4 * sizeof(char))) + return -1; + + node->regex_specs = calloc(rspec_num, sizeof(struct regex_spec)); + if (!node->regex_specs) + return -1; + + node->regex_specs_num = rspec_num; + node->regex_specs_alloc = rspec_num; + + for (uint32_t i = 0; i < rspec_num; i++) { + rc = load_mmap_regex_spec(mmap_area, validating, do_load_precompregex, &node->regex_specs[i], ctx_array); + if (rc) + return -1; + } + } + /* - * map indexed by the stem # in the mmap file and contains the stem - * number in the data stem_arr + * Read child nodes */ - stem_map = calloc(stem_map_len, sizeof(*stem_map)); - if (!stem_map) + rc = next_entry(&data_u32, mmap_area, sizeof(uint32_t)); + if (rc < 0) + return -1; + children_num = be32toh(data_u32); + + if (children_num == UINT32_MAX) return -1; - for (i = 0; i < stem_map_len; i++) { - char *buf; - uint32_t stem_len; - int newid; + if (children_num > 0) { + const char *prev_stem = NULL; - /* the length does not include the nul */ - rc = next_entry(&stem_len, mmap_area, sizeof(uint32_t)); - if (rc < 0 || !stem_len) { - rc = -1; - goto out; + if (entry_size_check(mmap_area, children_num, 3 * sizeof(uint32_t) + sizeof(uint16_t))) + return -1; + + node->children = calloc(children_num, sizeof(struct spec_node)); + if (!node->children) + return -1; + + node->children_num = children_num; + node->children_alloc = children_num; + + for (uint32_t i = 0; i < children_num; i++) { + rc = load_mmap_spec_node(mmap_area, path, validating, do_load_precompregex, &node->children[i], false, ctx_array); + if (rc) + return -1; + + /* Ensure child nodes are sorted and distinct */ + if (prev_stem && strcmp(prev_stem, node->children[i].stem) >= 0) + return -1; + + prev_stem = node->children[i].stem; } + } + + + if (!is_root && lspec_num == 0 && rspec_num == 0 && children_num == 0) + return -1; + + return 0; +} + +FUZZ_EXTERN int load_mmap(FILE *fp, const size_t len, struct selabel_handle *rec, + const char *path) +{ + struct saved_data *data = rec->data; + struct spec_node *root = NULL; + struct context_array ctx_array = {}; + int rc; + char *addr = NULL, *str_buf = NULL; + struct mmap_area *mmap_area = NULL; + uint64_t data_u64, num_specs; + uint32_t data_u32, pcre_ver_len, pcre_arch_len; + const char *reg_arch, *reg_version; + bool reg_version_matches = false, reg_arch_matches = false; + + mmap_area = malloc(sizeof(*mmap_area)); + if (!mmap_area) + goto err; + + addr = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fileno(fp), 0); + if (addr == MAP_FAILED) + goto err; + + rc = madvise(addr, len, MADV_WILLNEED); + if (rc == -1) + COMPAT_LOG(SELINUX_INFO, "%s: Failed to advise memory mapping: %m\n", + path); + + /* save where we mmap'd the file to cleanup on close() */ + *mmap_area = (struct mmap_area) { + .addr = addr, + .next_addr = addr, + .next = NULL, + .next_len = len, + .len = len, + }; + + /* check if this looks like an fcontext file */ + rc = next_entry(&data_u32, mmap_area, sizeof(uint32_t)); + if (rc < 0 || be32toh(data_u32) != SELINUX_MAGIC_COMPILED_FCONTEXT) + goto err; + + /* check if this version is higher than we understand */ + rc = next_entry(&data_u32, mmap_area, sizeof(uint32_t)); + if (rc < 0 || be32toh(data_u32) != SELINUX_COMPILED_FCONTEXT_TREE_LAYOUT) { + COMPAT_LOG(SELINUX_WARNING, + "%s: Unsupported compiled fcontext version %d, supported is version %d\n", + path, be32toh(data_u32), SELINUX_COMPILED_FCONTEXT_TREE_LAYOUT); + goto err; + } + + reg_version = regex_version(); + if (!reg_version) + goto err; + + reg_arch = regex_arch_string(); + if (!reg_arch) + goto err; + + rc = next_entry(&data_u32, mmap_area, sizeof(uint32_t)); + if (rc < 0) + goto err; + pcre_ver_len = be32toh(data_u32); + + /* Check version lengths */ + if (strlen(reg_version) != pcre_ver_len) { + /* + * Skip the entry and conclude that we have + * a mismatch, which is not fatal. + */ + next_entry(NULL, mmap_area, pcre_ver_len); + goto end_version_check; + } - /* Check for stem_len wrap around. */ - if (stem_len < UINT32_MAX) { - buf = (char *)mmap_area->next_addr; - /* Check if over-run before null check. */ - rc = next_entry(NULL, mmap_area, (stem_len + 1)); - if (rc < 0) - goto out; + if (entry_size_check(mmap_area, pcre_ver_len, sizeof(char))) + goto err; - if (buf[stem_len] != '\0') { - rc = -1; - goto out; - } - } else { - rc = -1; - goto out; - } + str_buf = malloc(pcre_ver_len + 1); + if (!str_buf) + goto err; - /* store the mapping between old and new */ - newid = find_stem(data, buf, stem_len); - if (newid < 0) { - newid = store_stem(data, buf, stem_len); - if (newid < 0) { - rc = newid; - goto out; - } - data->stem_arr[newid].from_mmap = 1; - } - stem_map[i] = newid; - } + rc = next_entry(str_buf, mmap_area, pcre_ver_len); + if (rc < 0) + goto err; - /* allocate the regex array */ - rc = next_entry(®ex_array_len, mmap_area, sizeof(uint32_t)); - if (rc < 0 || !regex_array_len) { - rc = -1; - goto out; - } + str_buf[pcre_ver_len] = '\0'; - for (i = 0; i < regex_array_len; i++) { - struct spec *spec; - int32_t stem_id, meta_chars; - uint32_t mode = 0, prefix_len = 0; + /* Check for regex version mismatch */ + if (strcmp(str_buf, reg_version) != 0) + COMPAT_LOG(SELINUX_WARNING, + "%s: Regex version mismatch, expected: %s actual: %s\n", + path, reg_version, str_buf); + else + reg_version_matches = true; - rc = grow_specs(data); - if (rc < 0) - goto out; + free(str_buf); + str_buf = NULL; - spec = &data->spec_arr[data->nspec]; - spec->from_mmap = 1; +end_version_check: - /* Process context */ - rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t)); - if (rc < 0 || !entry_len) { - rc = -1; - goto out; - } + rc = next_entry(&data_u32, mmap_area, sizeof(uint32_t)); + if (rc < 0) + goto err; + pcre_arch_len = be32toh(data_u32); - str_buf = malloc(entry_len); - if (!str_buf) { - rc = -1; - goto out; - } - rc = next_entry(str_buf, mmap_area, entry_len); - if (rc < 0) { - free(str_buf); - goto out; - } + /* Check arch string lengths */ + if (strlen(reg_arch) != pcre_arch_len) { + /* + * Skip the entry and conclude that we have + * a mismatch, which is not fatal. + */ + next_entry(NULL, mmap_area, pcre_arch_len); + goto end_arch_check; + } - if (str_buf[entry_len - 1] != '\0') { - free(str_buf); - rc = -1; - goto out; - } - spec->lr.ctx_raw = str_buf; + if (entry_size_check(mmap_area, pcre_arch_len, sizeof(char))) + goto err; - if (strcmp(spec->lr.ctx_raw, "<>") && rec->validating) { - if (selabel_validate(&spec->lr) < 0) { - selinux_log(SELINUX_ERROR, - "%s: context %s is invalid\n", - path, spec->lr.ctx_raw); - goto out; - } - } + str_buf = malloc(pcre_arch_len + 1); + if (!str_buf) + goto err; - /* Process regex string */ - rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t)); - if (rc < 0 || !entry_len) { - rc = -1; - goto out; - } + rc = next_entry(str_buf, mmap_area, pcre_arch_len); + if (rc < 0) + goto err; - spec->regex_str = (char *)mmap_area->next_addr; - rc = next_entry(NULL, mmap_area, entry_len); - if (rc < 0) - goto out; + str_buf[pcre_arch_len] = '\0'; - if (spec->regex_str[entry_len - 1] != '\0') { - rc = -1; - goto out; - } + /* Check if arch string mismatch */ + if (strcmp(str_buf, reg_arch) != 0) + COMPAT_LOG(SELINUX_WARNING, + "%s: Regex architecture mismatch, expected: %s actual: %s\n", + path, reg_arch, str_buf); + else + reg_arch_matches = true; - /* Process mode */ - if (version >= SELINUX_COMPILED_FCONTEXT_MODE) - rc = next_entry(&mode, mmap_area, sizeof(uint32_t)); - else - rc = next_entry(&mode, mmap_area, sizeof(mode_t)); - if (rc < 0) - goto out; + free(str_buf); + str_buf = NULL; - spec->mode = mode; +end_arch_check: - /* map the stem id from the mmap file to the data->stem_arr */ - rc = next_entry(&stem_id, mmap_area, sizeof(int32_t)); - if (rc < 0) - goto out; + /* Read number of total specifications */ + rc = next_entry(&data_u64, mmap_area, sizeof(uint64_t)); + if (rc < 0) + goto err; + num_specs = be64toh(data_u64); - if (stem_id < 0 || stem_id >= (int32_t)stem_map_len) - spec->stem_id = -1; - else - spec->stem_id = stem_map[stem_id]; + rc = load_mmap_ctxarray(mmap_area, path, &ctx_array, rec->validating); + if (rc) + goto err; - /* retrieve the hasMetaChars bit */ - rc = next_entry(&meta_chars, mmap_area, sizeof(uint32_t)); - if (rc < 0) - goto out; + root = calloc(1, sizeof(*root)); + if (!root) + goto err; - spec->hasMetaChars = meta_chars; - /* and prefix length for use by selabel_lookup_best_match */ - if (version >= SELINUX_COMPILED_FCONTEXT_PREFIX_LEN) { - rc = next_entry(&prefix_len, mmap_area, - sizeof(uint32_t)); - if (rc < 0) - goto out; + rc = load_mmap_spec_node(mmap_area, path, rec->validating, + reg_version_matches && reg_arch_matches, + root, true, + &ctx_array); + if (rc) + goto err; - spec->prefix_len = prefix_len; - } + /* + * On intermediate failure some data might already have been merged, so always keep the mmap'ed memory. + */ + mmap_area->next = data->mmap_areas; + data->mmap_areas = mmap_area; + mmap_area = NULL; - rc = regex_load_mmap(mmap_area, &spec->regex, reg_arch_matches, - &spec->regex_compiled); - if (rc < 0) - goto out; + if (data->num_specs == 0) { + free_spec_node(data->root); + free(data->root); + data->root = root; + root = NULL; + } else { + rc = merge_mmap_spec_nodes(data->root, root); + if (rc) + goto err; - __pthread_mutex_init(&spec->regex_lock, NULL); - data->nspec++; + free_spec_node(root); + free(root); + root = NULL; } - rc = 0; -out: - free(stem_map); + /* Success */ + data->num_specs += num_specs; - return rc; + free_context_array(&ctx_array); + + return 0; + +err: + free_context_array(&ctx_array); + if (root) { + free_spec_node(root); + free(root); + } + free(str_buf); + free(mmap_area); + if (addr && addr != MAP_FAILED) + munmap(addr, len); + if (errno == 0) + errno = EINVAL; + return -1; } struct file_details { @@ -530,13 +981,26 @@ static int fcontext_is_binary(FILE *fp) if (rc == -1) return -1; - return (len && (magic == SELINUX_MAGIC_COMPILED_FCONTEXT)); + if (!len) + return 0; + + if (be32toh(magic) == SELINUX_MAGIC_COMPILED_FCONTEXT) + return 1; + + /* + * Treat old format magic in little endian as fcontext file as well, + * to avoid it getting parsed as text file. + */ + if (le32toh(magic) == SELINUX_MAGIC_COMPILED_FCONTEXT) + return 2; + + return 0; } #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) static FILE *open_file(const char *path, const char *suffix, - char *save_path, size_t len, struct stat *sb, bool open_oldest) + char *save_path, size_t len, struct stat *sb, bool open_oldest) { unsigned int i; int rc; @@ -553,7 +1017,7 @@ static FILE *open_file(const char *path, const char *suffix, }; rc = snprintf(stack_path, sizeof(stack_path), "%s", path); - if (rc >= (int) sizeof(stack_path)) { + if (rc < 0 || (size_t)rc >= sizeof(stack_path)) { errno = ENAMETOOLONG; return NULL; } @@ -608,8 +1072,9 @@ static FILE *open_file(const char *path, const char *suffix, } static int process_file(const char *path, const char *suffix, - struct selabel_handle *rec, - const char *prefix, struct selabel_digest *digest) + struct selabel_handle *rec, + const char *prefix, + struct selabel_digest *digest) { int rc; unsigned int i; @@ -634,9 +1099,15 @@ static int process_file(const char *path, const char *suffix, return -1; } - rc = rc ? - load_mmap(fp, sb.st_size, rec, found_path) : - process_text_file(fp, prefix, rec, found_path); + if (rc == 2) { + COMPAT_LOG(SELINUX_INFO, "%s: Old compiled fcontext format, skipping\n", found_path); + errno = EINVAL; + } else if (rc == 1) { + rc = load_mmap(fp, sb.st_size, rec, found_path); + } else { + rc = process_text_file(fp, prefix, rec, found_path); + } + if (!rc) rc = digest_add_specfile(digest, fp, NULL, sb.st_size, found_path); @@ -662,10 +1133,10 @@ static void selabel_subs_fini(struct selabel_sub *ptr) } } -static char *selabel_sub(struct selabel_sub *ptr, const char *src) +static char *selabel_sub(const struct selabel_sub *ptr, const char *src) { char *dst = NULL; - int len; + unsigned int len; while (ptr) { if (strncmp(src, ptr->src, ptr->slen) == 0 ) { @@ -688,7 +1159,7 @@ static char *selabel_sub(struct selabel_sub *ptr, const char *src) #if !defined(BUILD_HOST) && !defined(ANDROID) static int selabel_subs_init(const char *path, struct selabel_digest *digest, - struct selabel_sub **out_subs) + struct selabel_sub **out_subs) { char buf[1024]; FILE *cfg = fopen(path, "re"); @@ -709,6 +1180,7 @@ static int selabel_subs_init(const char *path, struct selabel_digest *digest, char *ptr = NULL; char *src = buf; char *dst = NULL; + size_t len; while (*src && isspace((unsigned char)*src)) src++; @@ -729,6 +1201,12 @@ static int selabel_subs_init(const char *path, struct selabel_digest *digest, if (! *dst) continue; + len = strlen(src); + if (len >= UINT32_MAX) { + errno = EINVAL; + goto err; + } + sub = calloc(1, sizeof(*sub)); if (! sub) goto err; @@ -741,7 +1219,7 @@ static int selabel_subs_init(const char *path, struct selabel_digest *digest, if (! sub->dst) goto err; - sub->slen = strlen(src); + sub->slen = len; sub->next = list; list = sub; sub = NULL; @@ -771,7 +1249,7 @@ static int selabel_subs_init(const char *path, struct selabel_digest *digest, } #endif -static char *selabel_sub_key(struct saved_data *data, const char *key) +static char *selabel_sub_key(const struct saved_data *data, const char *key) { char *ptr = NULL; char *dptr = NULL; @@ -786,10 +1264,8 @@ static char *selabel_sub_key(struct saved_data *data, const char *key) } else { ptr = selabel_sub(data->dist_subs, key); } - if (ptr) - return ptr; - return NULL; + return ptr; } static void closef(struct selabel_handle *rec); @@ -797,7 +1273,7 @@ static void closef(struct selabel_handle *rec); static int init(struct selabel_handle *rec, const struct selinux_opt *opts, unsigned n) { - struct saved_data *data = (struct saved_data *)rec->data; + struct saved_data *data = rec->data; const char *path = NULL; const char *prefix = NULL; int status = -1, baseonly = 0; @@ -854,132 +1330,386 @@ static int init(struct selabel_handle *rec, const struct selinux_opt *opts, #endif - if (!path) - goto finish; + if (!path) { + errno = EINVAL; + goto finish; + } + + rec->spec_file = strdup(path); + if (!rec->spec_file) + goto finish; + + /* + * The do detailed validation of the input and fill the spec array + */ + status = process_file(path, NULL, rec, prefix, rec->digest); + if (status) + goto finish; + + if (rec->validating) { + sort_specs(data); + + status = nodups_spec_node(data->root, path); + if (status) + goto finish; + } + + if (!baseonly) { + status = process_file(path, "homedirs", rec, prefix, + rec->digest); + if (status && errno != ENOENT) + goto finish; + + status = process_file(path, "local", rec, prefix, + rec->digest); + if (status && errno != ENOENT) + goto finish; + } + + if (!rec->validating || !baseonly) + sort_specs(data); + + digest_gen_hash(rec->digest); + + status = 0; + +finish: + if (status) + closef(rec); + + return status; +} + +/* + * Backend interface routines + */ +static void closef(struct selabel_handle *rec) +{ + struct saved_data *data = (struct saved_data *)rec->data; + struct mmap_area *area, *last_area; + + if (!data) + return; + + selabel_subs_fini(data->subs); + selabel_subs_fini(data->dist_subs); + + free_spec_node(data->root); + free(data->root); + + area = data->mmap_areas; + while (area) { + munmap(area->addr, area->len); + last_area = area; + area = area->next; + free(last_area); + } + free(data); + rec->data = NULL; +} + +static uint32_t search_literal_spec(const struct literal_spec *array, uint32_t size, const char *key, size_t key_len, bool partial) +{ + uint32_t lower, upper; + + if (size == 0) + return (uint32_t)-1; + + lower = 0; + upper = size - 1; + + while (lower <= upper) { + uint32_t m = lower + (upper - lower) / 2; + int r; + + if (partial) + r = strncmp(array[m].literal_match, key, key_len); + else + r = strcmp(array[m].literal_match, key); + + if (r == 0) { + /* Return the first result, regardless of file kind */ + while (m > 0) { + if (partial) + r = strncmp(array[m - 1].literal_match, key, key_len); + else + r = strcmp(array[m - 1].literal_match, key); + + if (r == 0) + m--; + else + break; + } + return m; + } + + if (r < 0) + lower = m + 1; + else { + if (m == 0) + break; + + upper = m - 1; + } + } + + return (uint32_t)-1; +} + +FUZZ_EXTERN void free_lookup_result(struct lookup_result *result) +{ + struct lookup_result *tmp; + + while (result) { + tmp = result->next; + free(result); + result = tmp; + } +} + +static struct lookup_result *lookup_check_node(struct spec_node *node, const char *key, uint8_t file_kind, bool partial, bool find_all) +{ + struct lookup_result *result = NULL; + struct lookup_result **next = &result; + size_t key_len = strlen(key); + + for (struct spec_node *n = node; n; n = n->parent) { + + uint32_t literal_idx = search_literal_spec(n->literal_specs, n->literal_specs_num, key, key_len, partial); + if (literal_idx != (uint32_t)-1) { + do { + struct literal_spec *lspec = &n->literal_specs[literal_idx]; + + if (file_kind == LABEL_FILE_KIND_ALL || lspec->file_kind == LABEL_FILE_KIND_ALL || lspec->file_kind == file_kind) { + struct lookup_result *r; + +#ifdef __ATOMIC_RELAXED + __atomic_store_n(&lspec->any_matches, true, __ATOMIC_RELAXED); +#else +#error "Please use a compiler that supports __atomic builtins" +#endif + + if (strcmp(lspec->lr.ctx_raw, "<>") == 0) { + free_lookup_result(result); + errno = ENOENT; + return NULL; + } + + r = malloc(sizeof(*r)); + if (!r) { + free_lookup_result(result); + return NULL; + } + + *r = (struct lookup_result) { + .regex_str = lspec->regex_str, + .prefix_len = lspec->prefix_len, + .file_kind = lspec->file_kind, + .lr = &lspec->lr, + .has_meta_chars = false, + .next = NULL, + }; + + *next = r; + next = &r->next; + + if (!find_all) + return result; + } + + literal_idx++; + } while (literal_idx < n->literal_specs_num && + (partial ? (strncmp(n->literal_specs[literal_idx].literal_match, key, key_len) == 0) + : (strcmp(n->literal_specs[literal_idx].literal_match, key) == 0))); + } + + for (uint32_t i = 0; i < n->regex_specs_num; i++) { + struct regex_spec *rspec = &n->regex_specs[i]; + const char *errbuf = NULL; + int rc; + + if (file_kind != LABEL_FILE_KIND_ALL && rspec->file_kind != LABEL_FILE_KIND_ALL && file_kind != rspec->file_kind) + continue; + + if (compile_regex(rspec, &errbuf) < 0) { + COMPAT_LOG(SELINUX_ERROR, "Failed to compile regular expression '%s': %s\n", + rspec->regex_str, errbuf); + free_lookup_result(result); + return NULL; + } + + rc = regex_match(rspec->regex, key, partial); + if (rc == REGEX_MATCH || (partial && rc == REGEX_MATCH_PARTIAL)) { + struct lookup_result *r; + + if (rc == REGEX_MATCH) { +#ifdef __ATOMIC_RELAXED + __atomic_store_n(&rspec->any_matches, true, __ATOMIC_RELAXED); +#else +#error "Please use a compiler that supports __atomic builtins" +#endif + } + + if (strcmp(rspec->lr.ctx_raw, "<>") == 0) { + free_lookup_result(result); + errno = ENOENT; + return NULL; + } + + r = malloc(sizeof(*r)); + if (!r) { + free_lookup_result(result); + return NULL; + } + + *r = (struct lookup_result) { + .regex_str = rspec->regex_str, + .prefix_len = rspec->prefix_len, + .file_kind = rspec->file_kind, + .lr = &rspec->lr, + .has_meta_chars = true, + .next = NULL, + }; + + *next = r; + next = &r->next; + + if (!find_all) + return result; + + continue; + } + + if (rc == REGEX_NO_MATCH) + continue; + + /* else it's an error */ + free_lookup_result(result); + errno = ENOENT; + return NULL; + } + } + + if (!result) + errno = ENOENT; + return result; +} + +static struct spec_node* search_child_node(struct spec_node *array, uint32_t size, const char *key, size_t key_len) +{ + uint32_t lower, upper; + + if (size == 0) + return NULL; + + lower = 0; + upper = size - 1; - rec->spec_file = strdup(path); + while (lower <= upper) { + uint32_t m = lower + (upper - lower) / 2; + int r; - /* - * The do detailed validation of the input and fill the spec array - */ - status = process_file(path, NULL, rec, prefix, rec->digest); - if (status) - goto finish; + r = strncmp(array[m].stem, key, key_len); - if (rec->validating) { - status = nodups_specs(data, path); - if (status) - goto finish; - } + if (r == 0 && array[m].stem[key_len] == '\0') + return &array[m]; - if (!baseonly) { - status = process_file(path, "homedirs", rec, prefix, - rec->digest); - if (status && errno != ENOENT) - goto finish; + if (r < 0) + lower = m + 1; + else { + if (m == 0) + break; - status = process_file(path, "local", rec, prefix, - rec->digest); - if (status && errno != ENOENT) - goto finish; + upper = m - 1; + } } - digest_gen_hash(rec->digest); + return NULL; +} - status = sort_specs(data); +static struct spec_node* lookup_find_deepest_node(struct spec_node *node, const char *key) +{ + /* Find the node matching the deepest stem */ -finish: - if (status) - closef(rec); + struct spec_node *n = node; + const char *p = key; - return status; -} + while (true) { + struct spec_node *child; + size_t length; + const char *q; -/* - * Backend interface routines - */ -static void closef(struct selabel_handle *rec) -{ - struct saved_data *data = (struct saved_data *)rec->data; - struct mmap_area *area, *last_area; - struct spec *spec; - struct stem *stem; - unsigned int i; + if (*p != '/') + break; - if (!data) - return; + q = strchr(p + 1, '/'); + if (q == NULL) + break; - selabel_subs_fini(data->subs); - selabel_subs_fini(data->dist_subs); + length = q - p - 1; + if (length == 0) + break; - for (i = 0; i < data->nspec; i++) { - spec = &data->spec_arr[i]; - free(spec->lr.ctx_trans); - free(spec->lr.ctx_raw); - regex_data_free(spec->regex); - __pthread_mutex_destroy(&spec->regex_lock); - if (spec->from_mmap) - continue; - free(spec->regex_str); - free(spec->type_str); - } + child = search_child_node(n->children, n->children_num, p + 1, length); + if (!child) + break; - for (i = 0; i < (unsigned int)data->num_stems; i++) { - stem = &data->stem_arr[i]; - if (stem->from_mmap) - continue; - free(stem->buf); + n = child; + p = q; } - if (data->spec_arr) - free(data->spec_arr); - if (data->stem_arr) - free(data->stem_arr); + return n; +} - area = data->mmap_areas; - while (area) { - munmap(area->addr, area->len); - last_area = area; - area = area->next; - free(last_area); +static uint8_t mode_to_file_kind(int type) { + type &= S_IFMT; + + switch (type) { + case S_IFBLK: + return LABEL_FILE_KIND_BLK; + case S_IFCHR: + return LABEL_FILE_KIND_CHR; + case S_IFDIR: + return LABEL_FILE_KIND_DIR; + case S_IFIFO: + return LABEL_FILE_KIND_FIFO; + case S_IFLNK: + return LABEL_FILE_KIND_LNK; + case S_IFSOCK: + return LABEL_FILE_KIND_SOCK; + case S_IFREG: + return LABEL_FILE_KIND_REG; + case 0: + default: + return LABEL_FILE_KIND_ALL; } - free(data); - rec->data = NULL; } // Finds all the matches of |key| in the given context. Returns the result in // the allocated array and updates the match count. If match_count is NULL, // stops early once the 1st match is found. -static struct spec **lookup_all(struct selabel_handle *rec, - const char *key, - int type, - bool partial, - size_t *match_count) +FUZZ_EXTERN struct lookup_result *lookup_all(struct selabel_handle *rec, + const char *key, + int type, + bool partial, + bool find_all) { struct saved_data *data = (struct saved_data *)rec->data; - struct spec *spec_arr = data->spec_arr; - int i, rc, file_stem; + struct lookup_result *result = NULL; + struct spec_node *node; size_t len; - mode_t mode = (mode_t)type; + uint8_t file_kind = mode_to_file_kind(type); char *clean_key = NULL; const char *prev_slash, *next_slash; unsigned int sofar = 0; char *sub = NULL; - struct spec **result = NULL; - if (match_count) { - *match_count = 0; - result = calloc(data->nspec, sizeof(struct spec*)); - } else { - result = calloc(1, sizeof(struct spec*)); - } - if (!result) { - selinux_log(SELINUX_ERROR, "Failed to allocate %zu bytes of data\n", - data->nspec * sizeof(struct spec*)); + if (!key) { + errno = EINVAL; goto finish; } - if (!data->nspec) { + if (!data->num_specs) { errno = ENOENT; goto finish; } @@ -1025,84 +1755,26 @@ static struct spec **lookup_all(struct selabel_handle *rec, if (sub) key = sub; - file_stem = find_stem_from_file(data, key); - mode &= S_IFMT; + node = lookup_find_deepest_node(data->root, key); - /* - * Check for matching specifications in reverse order, so that - * the last matching specification is used. - */ - for (i = data->nspec - 1; i >= 0; i--) { - struct spec *spec = &spec_arr[i]; - /* if the spec in question matches no stem or has the same - * stem as the file AND if the spec in question has no mode - * specified or if the mode matches the file mode then we do - * a regex check */ - bool stem_matches = spec->stem_id == -1 || spec->stem_id == file_stem; - // Don't check the stem if we want to find partial matches. - // Otherwise the case "/abc/efg/(/.*)?" will be considered - //a miss for "/abc". - if ((partial || stem_matches) && - (!mode || !spec->mode || mode == spec->mode)) { - if (compile_regex(spec, NULL) < 0) - goto finish; - rc = regex_match(spec->regex, key, partial); - if (rc == REGEX_MATCH || (partial && rc == REGEX_MATCH_PARTIAL)) { - if (rc == REGEX_MATCH) { -#ifdef __ATOMIC_RELAXED - __atomic_store_n(&spec->any_matches, - true, __ATOMIC_RELAXED); -#else -#error "Please use a compiler that supports __atomic builtins" -#endif - } - - if (strcmp(spec_arr[i].lr.ctx_raw, "<>") == 0) { - errno = ENOENT; - goto finish; - } - - if (match_count) { - result[*match_count] = spec; - *match_count += 1; - // Continue to find all the matches. - continue; - } - result[0] = spec; - break; - } - - if (rc == REGEX_NO_MATCH) - continue; - - errno = ENOENT; - /* else it's an error */ - goto finish; - } - } - if (!result[0]) - errno = ENOENT; + result = lookup_check_node(node, key, file_kind, partial, find_all); finish: free(clean_key); free(sub); - if (result && !result[0]) { - free(result); - result = NULL; - } return result; } -static struct spec *lookup_common(struct selabel_handle *rec, - const char *key, - int type, - bool partial) { - struct spec **matches = lookup_all(rec, key, type, partial, NULL); - if (!matches) { +static struct lookup_result *lookup_common(struct selabel_handle *rec, + const char *key, + int type, + bool partial) { + struct lookup_result *result = lookup_all(rec, key, type, partial, false); + if (!result) return NULL; - } - struct spec *result = matches[0]; - free(matches); + + free_lookup_result(result->next); + result->next = NULL; return result; } @@ -1149,7 +1821,7 @@ static bool get_digests_all_partial_matches(struct selabel_handle *rec, } if (status && read_size == SHA1_HASH_SIZE && - memcmp(read_digest, hash_digest, SHA1_HASH_SIZE) == 0) + memcmp(read_digest, hash_digest, SHA1_HASH_SIZE) == 0) return true; return false; @@ -1163,22 +1835,21 @@ static bool hash_all_partial_matches(struct selabel_handle *rec, const char *key { assert(digest); - size_t total_matches; - struct spec **matches = lookup_all(rec, key, 0, true, &total_matches); + struct lookup_result *matches = lookup_all(rec, key, 0, true, true); if (!matches) { return false; } Sha1Context context; Sha1Initialise(&context); - size_t i; - for (i = 0; i < total_matches; i++) { - char* regex_str = matches[i]->regex_str; - mode_t mode = matches[i]->mode; - char* ctx_raw = matches[i]->lr.ctx_raw; + + for (const struct lookup_result *m = matches; m; m = m->next) { + const char* regex_str = m->regex_str; + uint8_t file_kind = m->file_kind; + const char* ctx_raw = m->lr->ctx_raw; Sha1Update(&context, regex_str, strlen(regex_str) + 1); - Sha1Update(&context, &mode, sizeof(mode_t)); + Sha1Update(&context, &file_kind, sizeof(file_kind)); Sha1Update(&context, ctx_raw, strlen(ctx_raw) + 1); } @@ -1186,24 +1857,32 @@ static bool hash_all_partial_matches(struct selabel_handle *rec, const char *key Sha1Finalise(&context, &sha1_hash); memcpy(digest, sha1_hash.bytes, SHA1_HASH_SIZE); - free(matches); + free_lookup_result(matches); return true; } static struct selabel_lookup_rec *lookup(struct selabel_handle *rec, const char *key, int type) { - struct spec *spec; + struct lookup_result *result; + struct selabel_lookup_rec *lookup_result; - spec = lookup_common(rec, key, type, false); - if (spec) - return &spec->lr; - return NULL; + result = lookup_common(rec, key, type, false); + if (!result) + return NULL; + + lookup_result = result->lr; + free_lookup_result(result); + return lookup_result; } static bool partial_match(struct selabel_handle *rec, const char *key) { - return lookup_common(rec, key, 0, true) ? true : false; + struct lookup_result *result = lookup_common(rec, key, 0, true); + bool ret = result; + + free_lookup_result(result); + return ret; } static struct selabel_lookup_rec *lookup_best_match(struct selabel_handle *rec, @@ -1211,10 +1890,9 @@ static struct selabel_lookup_rec *lookup_best_match(struct selabel_handle *rec, const char **aliases, int type) { - size_t n, i; - int best = -1; - struct spec **specs; - size_t prefix_len = 0; + size_t n, i, best = (size_t)-1; + struct lookup_result **results; + uint16_t prefix_len = 0; struct selabel_lookup_rec *lr = NULL; if (!aliases || !aliases[0]) @@ -1223,177 +1901,429 @@ static struct selabel_lookup_rec *lookup_best_match(struct selabel_handle *rec, for (n = 0; aliases[n]; n++) ; - specs = calloc(n+1, sizeof(struct spec *)); - if (!specs) + results = calloc(n+1, sizeof(*results)); + if (!results) return NULL; - specs[0] = lookup_common(rec, key, type, false); - if (specs[0]) { - if (!specs[0]->hasMetaChars) { + results[0] = lookup_common(rec, key, type, false); + if (results[0]) { + if (!results[0]->has_meta_chars) { /* exact match on key */ - lr = &specs[0]->lr; + lr = results[0]->lr; goto out; } best = 0; - prefix_len = specs[0]->prefix_len; + prefix_len = results[0]->prefix_len; } for (i = 1; i <= n; i++) { - specs[i] = lookup_common(rec, aliases[i-1], type, false); - if (specs[i]) { - if (!specs[i]->hasMetaChars) { + results[i] = lookup_common(rec, aliases[i-1], type, false); + if (results[i]) { + if (!results[i]->has_meta_chars) { /* exact match on alias */ - lr = &specs[i]->lr; + lr = results[i]->lr; goto out; } - if (specs[i]->prefix_len > prefix_len) { + if (results[i]->prefix_len > prefix_len) { best = i; - prefix_len = specs[i]->prefix_len; + prefix_len = results[i]->prefix_len; } } } - if (best >= 0) { + if (best != (size_t)-1) { /* longest fixed prefix match on key or alias */ - lr = &specs[best]->lr; + lr = results[best]->lr; } else { errno = ENOENT; } out: - free(specs); + for (i = 0; i <= n; i++) + free_lookup_result(results[i]); + free(results); return lr; } -static enum selabel_cmp_result incomp(const struct spec *spec1, const struct spec *spec2, const char *reason, int i, int j) +static void spec_node_stats(const struct spec_node *node) +{ + bool any_matches; + + for (uint32_t i = 0; i < node->literal_specs_num; i++) { + const struct literal_spec *lspec = &node->literal_specs[i]; + +#ifdef __ATOMIC_RELAXED + any_matches = __atomic_load_n(&node->literal_specs[i].any_matches, __ATOMIC_RELAXED); +#else +#error "Please use a compiler that supports __atomic builtins" +#endif + + if (!any_matches) { + COMPAT_LOG(SELINUX_WARNING, + "Warning! No matches for (%s, %s, %s)\n", + lspec->regex_str, + file_kind_to_string(lspec->file_kind), + lspec->lr.ctx_raw); + } + } + + for (uint32_t i = 0; i < node->regex_specs_num; i++) { + const struct regex_spec *rspec = &node->regex_specs[i]; + +#ifdef __ATOMIC_RELAXED + any_matches = __atomic_load_n(&rspec->any_matches, __ATOMIC_RELAXED); +#else +#error "Please use a compiler that supports __atomic builtins" +#endif + + if (!any_matches) { + COMPAT_LOG(SELINUX_WARNING, + "Warning! No matches for (%s, %s, %s)\n", + rspec->regex_str, + file_kind_to_string(rspec->file_kind), + rspec->lr.ctx_raw); + } + } + + for (uint32_t i = 0; i < node->children_num; i++) + spec_node_stats(&node->children[i]); +} + +static void stats(struct selabel_handle *rec) +{ + const struct saved_data *data = (const struct saved_data *)rec->data; + + spec_node_stats(data->root); +} + +static inline const char* fmt_stem(const char *stem) +{ + return stem ?: "(root)"; +} + +static enum selabel_cmp_result lspec_incomp(const char *stem, const struct literal_spec *lspec1, const struct literal_spec *lspec2, const char *reason, uint32_t iter1, uint32_t iter2) { selinux_log(SELINUX_INFO, - "selabel_cmp: mismatched %s on entry %d: (%s, %x, %s) vs entry %d: (%s, %x, %s)\n", - reason, - i, spec1->regex_str, spec1->mode, spec1->lr.ctx_raw, - j, spec2->regex_str, spec2->mode, spec2->lr.ctx_raw); - return SELABEL_INCOMPARABLE; + "selabel_cmp: mismatched %s in stem %s on literal entry %u: (%s, %s, %s) vs entry %u: (%s, %s, %s)\n", + reason, + fmt_stem(stem), + iter1, lspec1->regex_str, file_kind_to_string(lspec1->file_kind), lspec1->lr.ctx_raw, + iter2, lspec2->regex_str, file_kind_to_string(lspec2->file_kind), lspec2->lr.ctx_raw); + return SELABEL_INCOMPARABLE; } -static enum selabel_cmp_result cmp(const struct selabel_handle *h1, - const struct selabel_handle *h2) +static enum selabel_cmp_result rspec_incomp(const char *stem, const struct regex_spec *rspec1, const struct regex_spec *rspec2, const char *reason, uint32_t iter1, uint32_t iter2) { - const struct saved_data *data1 = (const struct saved_data *)h1->data; - const struct saved_data *data2 = (const struct saved_data *)h2->data; - unsigned int i, nspec1 = data1->nspec, j, nspec2 = data2->nspec; - const struct spec *spec_arr1 = data1->spec_arr, *spec_arr2 = data2->spec_arr; - const struct stem *stem_arr1 = data1->stem_arr, *stem_arr2 = data2->stem_arr; - bool skipped1 = false, skipped2 = false; - - i = 0; - j = 0; - while (i < nspec1 && j < nspec2) { - const struct spec *spec1 = &spec_arr1[i]; - const struct spec *spec2 = &spec_arr2[j]; + selinux_log(SELINUX_INFO, + "selabel_cmp: mismatched %s in stem %s on regex entry %u: (%s, %s, %s) vs entry %u: (%s, %s, %s)\n", + reason, + fmt_stem(stem), + iter1, rspec1->regex_str, file_kind_to_string(rspec1->file_kind), rspec1->lr.ctx_raw, + iter2, rspec2->regex_str, file_kind_to_string(rspec2->file_kind), rspec2->lr.ctx_raw); + return SELABEL_INCOMPARABLE; +} - /* - * Because sort_specs() moves exact pathnames to the - * end, we might need to skip over additional regex - * entries that only exist in one of the configurations. - */ - if (!spec1->hasMetaChars && spec2->hasMetaChars) { - j++; - skipped2 = true; - continue; - } +static enum selabel_cmp_result spec_node_cmp(const struct spec_node *node1, const struct spec_node *node2) +{ + enum selabel_cmp_result result = SELABEL_EQUAL; - if (spec1->hasMetaChars && !spec2->hasMetaChars) { - i++; - skipped1 = true; - continue; - } + if ((node1->stem && node2->stem && strcmp(node1->stem, node2->stem) != 0) || + (node1->stem && node1->stem[0] != '\0' && !node2->stem) || + (!node1->stem && node2->stem && node2->stem[0] != '\0')) { + selinux_log(SELINUX_INFO, "selabel_cmp: incompareable nodes: %s vs %s\n", + fmt_stem(node1->stem), fmt_stem(node2->stem)); + return SELABEL_INCOMPARABLE; + } + + /* Literal specs comparison */ + { + uint32_t iter1 = 0, iter2 = 0; + while (iter1 < node1->literal_specs_num && iter2 < node2->literal_specs_num) { + const struct literal_spec *lspec1 = &node1->literal_specs[iter1]; + const struct literal_spec *lspec2 = &node2->literal_specs[iter2]; + int cmp; + + cmp = strcmp(lspec1->literal_match, lspec2->literal_match); + if (cmp < 0) { + if (result == SELABEL_EQUAL || result == SELABEL_SUPERSET) { + result = SELABEL_SUPERSET; + iter1++; + continue; + } - if (spec1->regex && spec2->regex) { - if (regex_cmp(spec1->regex, spec2->regex) == SELABEL_INCOMPARABLE){ - return incomp(spec1, spec2, "regex", i, j); + return lspec_incomp(node1->stem, lspec1, lspec2, "literal_str", iter1, iter2); } - } else { - if (strcmp(spec1->regex_str, spec2->regex_str)) - return incomp(spec1, spec2, "regex_str", i, j); - } - if (spec1->mode != spec2->mode) - return incomp(spec1, spec2, "mode", i, j); - - if (spec1->stem_id == -1 && spec2->stem_id != -1) - return incomp(spec1, spec2, "stem_id", i, j); - if (spec2->stem_id == -1 && spec1->stem_id != -1) - return incomp(spec1, spec2, "stem_id", i, j); - if (spec1->stem_id != -1 && spec2->stem_id != -1) { - const struct stem *stem1 = &stem_arr1[spec1->stem_id]; - const struct stem *stem2 = &stem_arr2[spec2->stem_id]; - if (stem1->len != stem2->len || - strncmp(stem1->buf, stem2->buf, stem1->len)) - return incomp(spec1, spec2, "stem", i, j); + if (cmp > 0) { + if (result == SELABEL_EQUAL || result == SELABEL_SUBSET) { + result = SELABEL_SUBSET; + iter2++; + continue; + } + + return lspec_incomp(node1->stem, lspec1, lspec2, "literal_str", iter1, iter2); + } + + /* If literal match is equal compare file kind */ + + if (lspec1->file_kind > lspec2->file_kind) { + if (result == SELABEL_EQUAL || result == SELABEL_SUPERSET) { + result = SELABEL_SUPERSET; + iter1++; + continue; + } + + return lspec_incomp(node1->stem, lspec1, lspec2, "file_kind", iter1, iter2); + } + + if (lspec1->file_kind < lspec2->file_kind) { + if (result == SELABEL_EQUAL || result == SELABEL_SUBSET) { + result = SELABEL_SUBSET; + iter2++; + continue; + } + + return lspec_incomp(node1->stem, lspec1, lspec2, "file_kind", iter1, iter2); + } + + iter1++; + iter2++; + } + if (iter1 != node1->literal_specs_num) { + if (result == SELABEL_EQUAL || result == SELABEL_SUPERSET) { + result = SELABEL_SUPERSET; + } else { + selinux_log(SELINUX_INFO, "selabel_cmp: mismatch literal left remnant in stem %s\n", fmt_stem(node1->stem)); + return SELABEL_INCOMPARABLE; + } + } + if (iter2 != node2->literal_specs_num) { + if (result == SELABEL_EQUAL || result == SELABEL_SUBSET) { + result = SELABEL_SUBSET; + } else { + selinux_log(SELINUX_INFO, "selabel_cmp: mismatch literal right remnant in stem %s\n", fmt_stem(node1->stem)); + return SELABEL_INCOMPARABLE; + } } + } + + /* Regex specs comparison */ + { + uint32_t iter1 = 0, iter2 = 0; + while (iter1 < node1->regex_specs_num && iter2 < node2->regex_specs_num) { + const struct regex_spec *rspec1 = &node1->regex_specs[iter1]; + const struct regex_spec *rspec2 = &node2->regex_specs[iter2]; + int cmp; + + if (rspec1->prefix_len > rspec2->prefix_len) { + if (result == SELABEL_EQUAL || result == SELABEL_SUPERSET) { + result = SELABEL_SUPERSET; + iter1++; + continue; + } + + return rspec_incomp(node1->stem, rspec1, rspec2, "regex_prefix_length", iter1, iter2); + } + + if (rspec1->prefix_len < rspec2->prefix_len) { + if (result == SELABEL_EQUAL || result == SELABEL_SUBSET) { + result = SELABEL_SUBSET; + iter2++; + continue; + } + + return rspec_incomp(node1->stem, rspec1, rspec2, "regex_prefix_length", iter1, iter2); + } + + /* If prefix length is equal compare regex string */ + + cmp = strcmp(rspec1->regex_str, rspec2->regex_str); + if (cmp < 0) { + if (result == SELABEL_EQUAL || result == SELABEL_SUPERSET) { + result = SELABEL_SUPERSET; + iter1++; + continue; + } + + return rspec_incomp(node1->stem, rspec1, rspec2, "regex_str", iter1, iter2); + } + + if (cmp > 0) { + if (result == SELABEL_EQUAL || result == SELABEL_SUBSET) { + result = SELABEL_SUBSET; + iter2++; + continue; + } + + return rspec_incomp(node1->stem, rspec1, rspec2, "regex_str", iter1, iter2); + } + + /* If literal match is equal compare file kind */ + + if (rspec1->file_kind > rspec2->file_kind) { + if (result == SELABEL_EQUAL || result == SELABEL_SUPERSET) { + result = SELABEL_SUPERSET; + iter1++; + continue; + } + + return rspec_incomp(node1->stem, rspec1, rspec2, "file_kind", iter1, iter2); + } + + if (rspec1->file_kind < rspec2->file_kind) { + if (result == SELABEL_EQUAL || result == SELABEL_SUBSET) { + result = SELABEL_SUBSET; + iter2++; + continue; + } - if (strcmp(spec1->lr.ctx_raw, spec2->lr.ctx_raw)) - return incomp(spec1, spec2, "ctx_raw", i, j); + return rspec_incomp(node1->stem, rspec1, rspec2, "file_kind", iter1, iter2); + } - i++; - j++; + iter1++; + iter2++; + } + if (iter1 != node1->regex_specs_num) { + if (result == SELABEL_EQUAL || result == SELABEL_SUPERSET) { + result = SELABEL_SUPERSET; + } else { + selinux_log(SELINUX_INFO, "selabel_cmp: mismatch regex_str left remnant in stem %s\n", fmt_stem(node1->stem)); + return SELABEL_INCOMPARABLE; + } + } + if (iter2 != node2->regex_specs_num) { + if (result == SELABEL_EQUAL || result == SELABEL_SUBSET) { + result = SELABEL_SUBSET; + } else { + selinux_log(SELINUX_INFO, "selabel_cmp: mismatch regex_str right remnant in stem %s\n", fmt_stem(node1->stem)); + return SELABEL_INCOMPARABLE; + } + } } - if ((skipped1 || i < nspec1) && !skipped2) - return SELABEL_SUPERSET; - if ((skipped2 || j < nspec2) && !skipped1) - return SELABEL_SUBSET; - if (skipped1 && skipped2) - return SELABEL_INCOMPARABLE; - return SELABEL_EQUAL; -} + /* Child nodes comparison */ + { + uint32_t iter1 = 0, iter2 = 0; + while (iter1 < node1->children_num && iter2 < node2->children_num) { + const struct spec_node *child1 = &node1->children[iter1]; + const struct spec_node *child2 = &node2->children[iter2]; + enum selabel_cmp_result child_result; + int cmp; + + cmp = strcmp(child1->stem, child2->stem); + if (cmp < 0) { + if (result == SELABEL_EQUAL || result == SELABEL_SUPERSET) { + result = SELABEL_SUPERSET; + iter1++; + continue; + } + selinux_log(SELINUX_INFO, "selabel_cmp: mismatch left remnant child node %s stem %s\n", child1->stem, fmt_stem(node1->stem)); + return SELABEL_INCOMPARABLE; + } -static void stats(struct selabel_handle *rec) -{ - struct saved_data *data = (struct saved_data *)rec->data; - unsigned int i, nspec = data->nspec; - struct spec *spec_arr = data->spec_arr; - bool any_matches; + if (cmp > 0) { + if (result == SELABEL_EQUAL || result == SELABEL_SUBSET) { + result = SELABEL_SUBSET; + iter2++; + continue; + } - for (i = 0; i < nspec; i++) { -#ifdef __ATOMIC_RELAXED - any_matches = __atomic_load_n(&spec_arr[i].any_matches, __ATOMIC_RELAXED); -#else -#error "Please use a compiler that supports __atomic builtins" -#endif - if (!any_matches) { - if (spec_arr[i].type_str) { - COMPAT_LOG(SELINUX_WARNING, - "Warning! No matches for (%s, %s, %s)\n", - spec_arr[i].regex_str, - spec_arr[i].type_str, - spec_arr[i].lr.ctx_raw); + selinux_log(SELINUX_INFO, "selabel_cmp: mismatch right remnant child node %s stem %s\n", child1->stem, fmt_stem(node1->stem)); + return SELABEL_INCOMPARABLE; + } + + iter1++; + iter2++; + + /* If stem is equal do a deep comparison */ + + child_result = spec_node_cmp(child1, child2); + switch (child_result) { + case SELABEL_SUBSET: + if (result == SELABEL_EQUAL || result == SELABEL_SUBSET) { + result = SELABEL_SUBSET; + continue; + } + selinux_log(SELINUX_INFO, "selabel_cmp: mismatch child node %s stem %s\n", child1->stem, fmt_stem(node1->stem)); + return SELABEL_INCOMPARABLE; + case SELABEL_EQUAL: + continue; + case SELABEL_SUPERSET: + if (result == SELABEL_EQUAL || result == SELABEL_SUPERSET) { + result = SELABEL_SUPERSET; + continue; + } + selinux_log(SELINUX_INFO, "selabel_cmp: mismatch child node %s stem %s\n", child1->stem, fmt_stem(node1->stem)); + return SELABEL_INCOMPARABLE; + case SELABEL_INCOMPARABLE: + default: + selinux_log(SELINUX_INFO, "selabel_cmp: mismatch child node %s stem %s\n", child1->stem, fmt_stem(node1->stem)); + return SELABEL_INCOMPARABLE; + } + } + if (iter1 != node1->children_num) { + if (result == SELABEL_EQUAL || result == SELABEL_SUPERSET) { + result = SELABEL_SUPERSET; + } else { + selinux_log(SELINUX_INFO, "selabel_cmp: mismatch child left remnant in stem %s\n", fmt_stem(node1->stem)); + return SELABEL_INCOMPARABLE; + } + } + if (iter2 != node2->children_num) { + if (result == SELABEL_EQUAL || result == SELABEL_SUBSET) { + result = SELABEL_SUBSET; } else { - COMPAT_LOG(SELINUX_WARNING, - "Warning! No matches for (%s, %s)\n", - spec_arr[i].regex_str, - spec_arr[i].lr.ctx_raw); + selinux_log(SELINUX_INFO, "selabel_cmp: mismatch child right remnant in stem %s\n", fmt_stem(node1->stem)); + return SELABEL_INCOMPARABLE; } } } + + return result; +} + +FUZZ_EXTERN enum selabel_cmp_result cmp(const struct selabel_handle *h1, const struct selabel_handle *h2) +{ + const struct saved_data *data1, *data2; + + /* Ensured by selabel_cmp() */ + assert(h1->backend == SELABEL_CTX_FILE && h2->backend == SELABEL_CTX_FILE); + + data1 = h1->data; + data2 = h2->data; + + if (data1->num_specs == 0) + return data2->num_specs == 0 ? SELABEL_EQUAL : SELABEL_SUBSET; + if (data2->num_specs == 0) + return SELABEL_SUPERSET; + + return spec_node_cmp(data1->root, data2->root); } int selabel_file_init(struct selabel_handle *rec, - const struct selinux_opt *opts, - unsigned nopts) + const struct selinux_opt *opts, + unsigned nopts) { struct saved_data *data; + struct spec_node *root; - data = (struct saved_data *)calloc(1, sizeof(*data)); + data = calloc(1, sizeof(*data)); if (!data) return -1; + root = calloc(1, sizeof(*root)); + if (!root) { + free(data); + return -1; + } + + data->root = root; + rec->data = data; rec->func_close = &closef; rec->func_stats = &stats; rec->func_lookup = &lookup; rec->func_partial_match = &partial_match; - rec->func_get_digests_all_partial_matches = - &get_digests_all_partial_matches; + rec->func_get_digests_all_partial_matches = &get_digests_all_partial_matches; rec->func_hash_all_partial_matches = &hash_all_partial_matches; rec->func_lookup_best_match = &lookup_best_match; rec->func_cmp = &cmp; diff --git a/libselinux/src/label_file.h b/libselinux/src/label_file.h index c91a6c1895..de8190f9dc 100644 --- a/libselinux/src/label_file.h +++ b/libselinux/src/label_file.h @@ -1,8 +1,12 @@ #ifndef _SELABEL_FILE_H_ #define _SELABEL_FILE_H_ +#include +#include #include #include +#include +#include #include #include @@ -28,46 +32,117 @@ #define SELINUX_COMPILED_FCONTEXT_MODE 3 #define SELINUX_COMPILED_FCONTEXT_PREFIX_LEN 4 #define SELINUX_COMPILED_FCONTEXT_REGEX_ARCH 5 +#define SELINUX_COMPILED_FCONTEXT_TREE_LAYOUT 6 #define SELINUX_COMPILED_FCONTEXT_MAX_VERS \ - SELINUX_COMPILED_FCONTEXT_REGEX_ARCH + SELINUX_COMPILED_FCONTEXT_TREE_LAYOUT /* Required selinux_restorecon and selabel_get_digests_all_partial_matches() */ #define RESTORECON_PARTIAL_MATCH_DIGEST "security.sehash" +#define LABEL_FILE_KIND_INVALID 255 +#define LABEL_FILE_KIND_ALL 0 +#define LABEL_FILE_KIND_DIR 1 +#define LABEL_FILE_KIND_CHR 2 +#define LABEL_FILE_KIND_BLK 3 +#define LABEL_FILE_KIND_SOCK 4 +#define LABEL_FILE_KIND_FIFO 5 +#define LABEL_FILE_KIND_LNK 6 +#define LABEL_FILE_KIND_REG 7 + +/* Only exported for fuzzing */ +struct lookup_result { + const char *regex_str; + struct selabel_lookup_rec *lr; + uint16_t prefix_len; + uint8_t file_kind; + bool has_meta_chars; + struct lookup_result *next; +}; +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +extern int load_mmap(FILE *fp, const size_t len, struct selabel_handle *rec, const char *path); +extern int process_text_file(FILE *fp, const char *prefix, struct selabel_handle *rec, const char *path); +extern void free_lookup_result(struct lookup_result *result); +extern struct lookup_result *lookup_all(struct selabel_handle *rec, const char *key, int type, bool partial, bool find_all); +extern enum selabel_cmp_result cmp(const struct selabel_handle *h1, const struct selabel_handle *h2); +#endif /* FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */ + struct selabel_sub { char *src; - int slen; + unsigned int slen; char *dst; struct selabel_sub *next; }; -/* A file security context specification. */ -struct spec { - struct selabel_lookup_rec lr; /* holds contexts for lookup result */ - char *regex_str; /* regular expression string for diagnostics */ - char *type_str; /* type string for diagnostic messages */ - struct regex_data * regex; /* backend dependent regular expression data */ - bool regex_compiled; /* bool to indicate if the regex is compiled */ - pthread_mutex_t regex_lock; /* lock for lazy compilation of regex */ - mode_t mode; /* mode format value */ - bool any_matches; /* did any pathname match? */ - int stem_id; /* indicates which stem-compression item */ - char hasMetaChars; /* regular expression has meta-chars */ - char from_mmap; /* this spec is from an mmap of the data */ - size_t prefix_len; /* length of fixed path prefix */ +/* A regular expression file security context specification */ +struct regex_spec { + struct selabel_lookup_rec lr; /* contexts for lookup result */ + char *regex_str; /* original regular expression string for diagnostics */ + struct regex_data *regex; /* backend dependent regular expression data */ + pthread_mutex_t regex_lock; /* lock for lazy compilation of regex */ + uint16_t prefix_len; /* length of fixed path prefix */ + uint8_t file_kind; /* file type */ + bool regex_compiled; /* whether the regex is compiled */ + bool any_matches; /* whether any pathname match */ + bool from_mmap; /* whether this spec is from an mmap of the data */ +}; + +/* A literal file security context specification */ +struct literal_spec { + struct selabel_lookup_rec lr; /* contexts for lookup result */ + char *regex_str; /* original regular expression string for diagnostics */ + char *literal_match; /* simplified string from regular expression */ + uint16_t prefix_len; /* length of fixed path prefix, i.e. length of the literal match */ + uint8_t file_kind; /* file type */ + bool any_matches; /* whether any pathname match */ + bool from_mmap; /* whether this spec is from an mmap of the data */ }; -/* A regular expression stem */ -struct stem { - char *buf; - int len; - char from_mmap; +/* + * Max depth of specification nodes + * + * Measure before changing: + * - 2 leads to slower lookup + * - >4 require more memory (and allocations) for no performance gain + */ +#define SPEC_NODE_MAX_DEPTH 3 + +/* A specification node */ +struct spec_node { + /* stem of the node, or NULL for root node */ + char *stem; + + /* parent node */ + struct spec_node *parent; + + /* + * Array of literal specifications (ordered alphabetically) + */ + struct literal_spec *literal_specs; + uint32_t literal_specs_num, literal_specs_alloc; + + /* + * Array of regular expression specifications (ordered from most to least specific) + */ + struct regex_spec *regex_specs; + uint32_t regex_specs_num, regex_specs_alloc; + + /* + * Array of child nodes (ordered alphabetically) + */ + struct spec_node *children; + uint32_t children_num, children_alloc; + + /* length of the stem (reordered to minimize padding) */ + uint16_t stem_len; + + /* whether this node is from an mmap of the data */ + bool from_mmap; }; /* Where we map the file in during selabel_open() */ struct mmap_area { - void *addr; /* Start addr + len used to release memory at close */ + void *addr; /* Start addr + len used to release memory at close */ size_t len; void *next_addr; /* Incremented by next_entry() */ size_t next_len; /* Decremented by next_entry() */ @@ -76,20 +151,12 @@ struct mmap_area { /* Our stored configuration */ struct saved_data { - /* - * The array of specifications, initially in the same order as in - * the specification file. Sorting occurs based on hasMetaChars. - */ - struct spec *spec_arr; - unsigned int nspec; - unsigned int alloc_specs; + /* Root specification node */ + struct spec_node *root; + + /* Number of file specifications */ + uint64_t num_specs; - /* - * The array of regular expression stems. - */ - struct stem *stem_arr; - int num_stems; - int alloc_stems; struct mmap_area *mmap_areas; /* substitution support */ @@ -97,73 +164,126 @@ struct saved_data { struct selabel_sub *subs; }; -static inline mode_t string_to_mode(const char *mode) +static inline mode_t string_to_file_kind(const char *mode) { if (mode[0] != '-' || mode[1] == '\0' || mode[2] != '\0') - return (mode_t)-1; + return LABEL_FILE_KIND_INVALID; switch (mode[1]) { case 'b': - return S_IFBLK; + return LABEL_FILE_KIND_BLK; case 'c': - return S_IFCHR; + return LABEL_FILE_KIND_CHR; case 'd': - return S_IFDIR; + return LABEL_FILE_KIND_DIR; case 'p': - return S_IFIFO; + return LABEL_FILE_KIND_FIFO; case 'l': - return S_IFLNK; + return LABEL_FILE_KIND_LNK; case 's': - return S_IFSOCK; + return LABEL_FILE_KIND_SOCK; case '-': - return S_IFREG; + return LABEL_FILE_KIND_REG; default: - return (mode_t)-1; + return LABEL_FILE_KIND_INVALID; } } -static inline int grow_specs(struct saved_data *data) +static inline const char* file_kind_to_string(uint8_t file_kind) { - struct spec *specs; - size_t new_specs, total_specs; - - if (data->nspec < data->alloc_specs) - return 0; + switch (file_kind) { + case LABEL_FILE_KIND_BLK: + return "block-device"; + case LABEL_FILE_KIND_CHR: + return "character-device"; + case LABEL_FILE_KIND_DIR: + return "directory"; + case LABEL_FILE_KIND_FIFO: + return "fifo-file"; + case LABEL_FILE_KIND_LNK: + return "symlink"; + case LABEL_FILE_KIND_SOCK: + return "sock-file"; + case LABEL_FILE_KIND_REG: + return "regular-file"; + case LABEL_FILE_KIND_ALL: + return "wildcard"; + default: + return "(invalid)"; + } +} - new_specs = data->nspec + 16; - total_specs = data->nspec + new_specs; +/* + * Determine whether the regular expression specification has any meta characters + * or any unsupported escape sequence. + */ +static bool regex_has_meta_chars(const char *regex, size_t *prefix_len, const char *path, unsigned int lineno) +{ + const char *p = regex; + size_t plen = 0; - specs = realloc(data->spec_arr, total_specs * sizeof(*specs)); - if (!specs) { - perror("realloc"); - return -1; + for (;*p != '\0'; p++, plen++) { + switch(*p) { + case '.': + case '^': + case '$': + case '?': + case '*': + case '+': + case '|': + case '[': + case '(': + case '{': + case ']': + case ')': + case '}': + *prefix_len = plen; + return true; + case '\\': + p++; + switch (*p) { + /* curated list of supported characters */ + case '.': + case '^': + case '$': + case '?': + case '*': + case '+': + case '|': + case '[': + case '(': + case '{': + case ']': + case ')': + case '}': + case '-': + case '_': + case ',': + continue; + default: + COMPAT_LOG(SELINUX_INFO, "%s: line %u has unsupported escaped character %c (%#x) for literal matching, continuing using regex\n", + path, lineno, isprint((unsigned char)*p) ? *p : '?', *p); + *prefix_len = plen; + return true; + } + } } - /* blank the new entries */ - memset(&specs[data->nspec], 0, new_specs * sizeof(*specs)); - - data->spec_arr = specs; - data->alloc_specs = total_specs; - return 0; + *prefix_len = plen; + return false; } -/* Determine if the regular expression specification has any meta characters. */ -static inline void spec_hasMetaChars(struct spec *spec) +static int regex_simplify(const char *regex, size_t len, char **out, const char *path, unsigned int lineno) { - char *c; - int len; - char *end; + char *result, *p; + size_t i = 0; - c = spec->regex_str; - len = strlen(spec->regex_str); - end = c + len; - - spec->hasMetaChars = 0; - spec->prefix_len = len; + result = malloc(len + 1); + if (!result) + return -1; - /* Look at each character in the RE specification string for a - * meta character. Return when any meta character reached. */ - while (c < end) { - switch (*c) { + p = result; + while (i < len) { + switch(regex[i]) { case '.': case '^': case '$': @@ -174,172 +294,149 @@ static inline void spec_hasMetaChars(struct spec *spec) case '[': case '(': case '{': - spec->hasMetaChars = 1; - spec->prefix_len = c - spec->regex_str; - return; - case '\\': /* skip the next character */ - c++; + case ']': + case ')': + case '}': + free(result); + return 0; + case '\\': + i++; + if (i >= len) { + COMPAT_LOG(SELINUX_WARNING, "%s: line %u has unsupported final escape character\n", + path, lineno); + free(result); + return 0; + } + switch (regex[i]) { + /* curated list of supported characters */ + case '.': + case '^': + case '$': + case '?': + case '*': + case '+': + case '|': + case '[': + case '(': + case '{': + case ']': + case ')': + case '}': + case '-': + case '_': + case ',': + *p++ = regex[i++]; + break; + default: + /* regex_has_meta_chars() reported already the notable occurrences */ + free(result); + return 0; + } break; default: - break; - + *p++ = regex[i++]; } - c++; } + + *p = '\0'; + *out = result; + return 1; } -/* Move exact pathname specifications to the end. */ -static inline int sort_specs(struct saved_data *data) +static inline int compare_literal_spec(const void *p1, const void *p2) { - struct spec *spec_copy; - struct spec spec; - unsigned int i; - int front, back; - size_t len = sizeof(*spec_copy); - - spec_copy = malloc(len * data->nspec); - if (!spec_copy) - return -1; - - /* first move the exact pathnames to the back */ - front = 0; - back = data->nspec - 1; - for (i = 0; i < data->nspec; i++) { - if (data->spec_arr[i].hasMetaChars) - memcpy(&spec_copy[front++], &data->spec_arr[i], len); - else - memcpy(&spec_copy[back--], &data->spec_arr[i], len); - } + const struct literal_spec *l1 = p1; + const struct literal_spec *l2 = p2; + int ret; - /* - * now the exact pathnames are at the end, but they are in the reverse - * order. Since 'front' is now the first of the 'exact' we can run - * that part of the array switching the front and back element. - */ - back = data->nspec - 1; - while (front < back) { - /* save the front */ - memcpy(&spec, &spec_copy[front], len); - /* move the back to the front */ - memcpy(&spec_copy[front], &spec_copy[back], len); - /* put the old front in the back */ - memcpy(&spec_copy[back], &spec, len); - front++; - back--; - } + ret = strcmp(l1->literal_match, l2->literal_match); + if (ret) + return ret; - free(data->spec_arr); - data->spec_arr = spec_copy; - - return 0; + /* Order wildcard mode (0) last */ + return (l1->file_kind < l2->file_kind) - (l1->file_kind > l2->file_kind); } -/* Return the length of the text that can be considered the stem, returns 0 - * if there is no identifiable stem */ -static inline int get_stem_from_spec(const char *const buf) +static inline int compare_regex_spec(const void *p1, const void *p2) { - const char *tmp = strchr(buf + 1, '/'); - const char *ind; + const struct regex_spec *r1 = p1; + const struct regex_spec *r2 = p2; + size_t regex_len1, regex_len2; + int ret; + + /* Order from high prefix length to low */ + ret = (r1->prefix_len < r2->prefix_len) - (r1->prefix_len > r2->prefix_len); + if (ret) + return ret; + + /* Order from long total regex length to short */ + regex_len1 = strlen(r1->regex_str); + regex_len2 = strlen(r2->regex_str); + ret = (regex_len1 < regex_len2) - (regex_len1 > regex_len2); + if (ret) + return ret; - if (!tmp) - return 0; + /* + * Order for no-duplicates check. + * Use reverse alphabetically order to retain the Fedora ordering of + * `/usr/(.* /)?lib(/.*)?` before `/usr/(.* /)?bin(/.*)?`. + */ + ret = strcmp(r1->regex_str, r2->regex_str); + if (ret) + return -ret; - for (ind = buf; ind < tmp; ind++) { - if (strchr(".^$?*+|[({", (int)*ind)) - return 0; - } - return tmp - buf; + /* Order wildcard mode (0) last */ + return (r1->file_kind < r2->file_kind) - (r1->file_kind > r2->file_kind); } -/* - * return the stemid given a string and a length - */ -static inline int find_stem(struct saved_data *data, const char *buf, - int stem_len) +static inline int compare_spec_node(const void *p1, const void *p2) { - int i; - - for (i = 0; i < data->num_stems; i++) { - if (stem_len == data->stem_arr[i].len && - !strncmp(buf, data->stem_arr[i].buf, stem_len)) - return i; - } + const struct spec_node *n1 = p1; + const struct spec_node *n2 = p2; + int rc; - return -1; + rc = strcmp(n1->stem, n2->stem); + /* There should not be two nodes with the same stem in the same array */ + assert(rc != 0); + return rc; } -/* returns the index of the new stored object */ -static inline int store_stem(struct saved_data *data, char *buf, int stem_len) +static inline void sort_spec_node(struct spec_node *node, struct spec_node *parent) { - int num = data->num_stems; - - if (data->alloc_stems == num) { - struct stem *tmp_arr; - int alloc_stems = data->alloc_stems * 2 + 16; - tmp_arr = realloc(data->stem_arr, - sizeof(*tmp_arr) * alloc_stems); - if (!tmp_arr) { - return -1; - } - data->alloc_stems = alloc_stems; - data->stem_arr = tmp_arr; - } - data->stem_arr[num].len = stem_len; - data->stem_arr[num].buf = buf; - data->stem_arr[num].from_mmap = 0; - data->num_stems++; + /* A node should not be its own parent */ + assert(node != parent); + /* Only root node has NULL stem */ + assert((!parent && !node->stem) || (parent && node->stem && node->stem[0] != '\0')); + /* A non-root node should not be empty */ + assert(!parent || (node->literal_specs_num || node->regex_specs_num || node->children_num)); - return num; -} -/* find the stem of a file spec, returns the index into stem_arr for a new - * or existing stem, (or -1 if there is no possible stem - IE for a file in - * the root directory or a regex that is too complex for us). */ -static inline int find_stem_from_spec(struct saved_data *data, const char *buf) -{ - int stem_len = get_stem_from_spec(buf); - int stemid; - char *stem; - int r; + node->parent = parent; - if (!stem_len) - return -1; + /* Sort for comparison support and binary search lookup */ - stemid = find_stem(data, buf, stem_len); - if (stemid >= 0) - return stemid; + if (node->literal_specs_num > 1) + qsort(node->literal_specs, node->literal_specs_num, sizeof(struct literal_spec), compare_literal_spec); - /* not found, allocate a new one */ - stem = strndup(buf, stem_len); - if (!stem) - return -1; + if (node->regex_specs_num > 1) + qsort(node->regex_specs, node->regex_specs_num, sizeof(struct regex_spec), compare_regex_spec); - r = store_stem(data, stem, stem_len); - if (r < 0) - free(stem); + if (node->children_num > 1) + qsort(node->children, node->children_num, sizeof(struct spec_node), compare_spec_node); - return r; + for (uint32_t i = 0; i < node->children_num; i++) + sort_spec_node(&node->children[i], node); } -/* This will always check for buffer over-runs and either read the next entry - * if buf != NULL or skip over the entry (as these areas are mapped in the - * current buffer). */ -static inline int next_entry(void *buf, struct mmap_area *fp, size_t bytes) +static inline void sort_specs(struct saved_data *data) { - if (bytes > fp->next_len) - return -1; - - if (buf) - memcpy(buf, fp->next_addr, bytes); - - fp->next_addr = (char *)fp->next_addr + bytes; - fp->next_len -= bytes; - return 0; + sort_spec_node(data->root, NULL); } -static inline int compile_regex(struct spec *spec, const char **errbuf) +static inline int compile_regex(struct regex_spec *spec, const char **errbuf) { - char *reg_buf, *anchored_regex, *cp; + const char *reg_buf; + char *anchored_regex, *cp; struct regex_error_data error_data; static char regex_error_format_buffer[256]; size_t len; @@ -423,29 +520,387 @@ static inline int compile_regex(struct spec *spec, const char **errbuf) return 0; } +#define GROW_ARRAY(arr) ({ \ + int ret_; \ + if ((arr ## _num) < (arr ## _alloc)) { \ + ret_ = 0; \ + } else { \ + size_t addedsize_ = ((arr ## _alloc) >> 1) + ((arr ## _alloc >> 4)) + 4; \ + size_t newsize_ = addedsize_ + (arr ## _alloc); \ + if (newsize_ < (arr ## _alloc) || newsize_ >= (typeof(arr ## _alloc))-1) { \ + errno = EOVERFLOW; \ + ret_ = -1; \ + } else { \ + typeof(arr) tmp_ = reallocarray(arr, newsize_, sizeof(*(arr))); \ + if (!tmp_) { \ + ret_ = -1; \ + } else { \ + (arr) = tmp_; \ + (arr ## _alloc) = newsize_; \ + ret_ = 0; \ + } \ + } \ + } \ + ret_; \ +}) + +static int insert_spec(const struct selabel_handle *rec, struct saved_data *data, + const char *prefix, char *regex, uint8_t file_kind, char *context, + const char *path, unsigned int lineno) +{ + size_t prefix_len; + bool has_meta; + + if (data->num_specs == UINT64_MAX) { + free(regex); + free(context); + errno = EOVERFLOW; + return -1; + } + + has_meta = regex_has_meta_chars(regex, &prefix_len, path, lineno); + + /* Ensured by read_spec_entry() */ + assert(prefix_len < UINT16_MAX); + + if (has_meta) { + struct spec_node *node = data->root; + const char *p = regex; + uint32_t id; + int depth = 0, rc; + + while (depth < SPEC_NODE_MAX_DEPTH) { + const char *q; + size_t regex_stem_len, stem_len; + char *stem = NULL; + bool child_found; + + q = strchr(p + 1, '/'); + if (!q) + break; + + regex_stem_len = q - p - 1; + /* Double slashes */ + if (regex_stem_len == 0) { + p = q; + continue; + } + + rc = regex_simplify(p + 1, regex_stem_len, &stem, path, lineno); + if (rc < 0) { + free(regex); + free(context); + return -1; + } + if (rc == 0) + break; + + stem_len = strlen(stem); + if (stem_len >= UINT16_MAX) { + free(stem); + break; + } + + if (depth == 0 && prefix && strcmp(prefix + 1, stem) != 0) { + free(stem); + free(regex); + free(context); + return 0; + } + + child_found = false; + for (uint32_t i = 0; i < node->children_num; i++) { + if (node->children[i].stem_len == stem_len && strncmp(node->children[i].stem, stem, stem_len) == 0) { + child_found = true; + node = &node->children[i]; + break; + } + } + + if (!child_found) { + rc = GROW_ARRAY(node->children); + if (rc) { + free(stem); + free(regex); + free(context); + return -1; + } + + id = node->children_num++; + node->children[id] = (struct spec_node) { + .stem = stem, + .stem_len = stem_len, + }; + + node = &node->children[id]; + } else { + free(stem); + } + + p += regex_stem_len + 1; + depth++; + } + + rc = GROW_ARRAY(node->regex_specs); + if (rc) { + free(regex); + free(context); + return -1; + } + + id = node->regex_specs_num++; + + node->regex_specs[id] = (struct regex_spec) { + .regex_str = regex, + .prefix_len = prefix_len, + .regex_compiled = false, + .regex_lock = PTHREAD_MUTEX_INITIALIZER, + .file_kind = file_kind, + .any_matches = false, + .lr.ctx_raw = context, + .lr.ctx_trans = NULL, + .lr.lineno = lineno, + .lr.validated = false, + .lr.lock = PTHREAD_MUTEX_INITIALIZER, + }; + + data->num_specs++; + + if (rec->validating) { + const char *errbuf = NULL; + + if (compile_regex(&node->regex_specs[id], &errbuf)) { + COMPAT_LOG(SELINUX_ERROR, + "%s: line %u has invalid regex %s: %s\n", + path, lineno, regex, errbuf); + return -1; + } + + if (strcmp(context, "<>") != 0) { + rc = compat_validate(rec, &node->regex_specs[id].lr, path, lineno); + if (rc < 0) + return rc; + } + } + } else { /* !has_meta */ + struct spec_node *node = data->root; + char *literal_regex = NULL; + const char *p; + uint32_t id; + int depth = 0, rc; + + rc = regex_simplify(regex, strlen(regex), &literal_regex, path, lineno); + if (rc != 1) { + if (rc == 0) { + COMPAT_LOG(SELINUX_ERROR, + "%s: line %u failed to simplify regex %s\n", + path, lineno, regex); + errno = EINVAL; + } + free(regex); + free(context); + return -1; + } + + p = literal_regex; + + while (depth < SPEC_NODE_MAX_DEPTH) { + const char *q; + size_t length; + char *stem; + bool child_found; + + if (*p != '/') + break; + + q = strchr(p + 1, '/'); + if (!q) + break; + + length = q - p - 1; + /* Double slashes */ + if (length == 0) { + p = q; + continue; + } + + /* Ensured by read_spec_entry() */ + assert(length < UINT16_MAX); + + if (depth == 0 && prefix && strncmp(prefix + 1, p + 1, length) != 0) { + free(literal_regex); + free(regex); + free(context); + return 0; + } + + child_found = false; + for (uint32_t i = 0; i < node->children_num; i++) { + if (node->children[i].stem_len == length && strncmp(node->children[i].stem, p + 1, length) == 0) { + child_found = true; + node = &node->children[i]; + break; + } + } + + if (!child_found) { + rc = GROW_ARRAY(node->children); + if (rc) { + free(literal_regex); + free(regex); + free(context); + return -1; + } + + stem = strndup(p + 1, length); + if (!stem) { + free(literal_regex); + free(regex); + free(context); + return -1; + } + + id = node->children_num++; + node->children[id] = (struct spec_node) { + .stem = stem, + .stem_len = length, + }; + + node = &node->children[id]; + } + + p = q; + depth++; + } + + rc = GROW_ARRAY(node->literal_specs); + if (rc) { + free(literal_regex); + free(regex); + free(context); + return -1; + } + + id = node->literal_specs_num++; + + assert(prefix_len == strlen(literal_regex)); + + node->literal_specs[id] = (struct literal_spec) { + .regex_str = regex, + .prefix_len = prefix_len, + .literal_match = literal_regex, + .file_kind = file_kind, + .any_matches = false, + .lr.ctx_raw = context, + .lr.ctx_trans = NULL, + .lr.lineno = lineno, + .lr.validated = false, + .lr.lock = PTHREAD_MUTEX_INITIALIZER, + }; + + data->num_specs++; + + if (rec->validating && strcmp(context, "<>") != 0) { + rc = compat_validate(rec, &node->literal_specs[id].lr, path, lineno); + if (rc < 0) + return rc; + } + + } + + return 0; +} + +#undef GROW_ARRAY + +static inline void free_spec_node(struct spec_node *node) +{ + for (uint32_t i = 0; i < node->literal_specs_num; i++) { + struct literal_spec *lspec = &node->literal_specs[i]; + + free(lspec->lr.ctx_raw); + free(lspec->lr.ctx_trans); + __pthread_mutex_destroy(&lspec->lr.lock); + + if (lspec->from_mmap) + continue; + + free(lspec->literal_match); + free(lspec->regex_str); + } + free(node->literal_specs); + + for (uint32_t i = 0; i < node->regex_specs_num; i++) { + struct regex_spec *rspec = &node->regex_specs[i]; + + free(rspec->lr.ctx_raw); + free(rspec->lr.ctx_trans); + __pthread_mutex_destroy(&rspec->lr.lock); + regex_data_free(rspec->regex); + __pthread_mutex_destroy(&rspec->regex_lock); + + if (rspec->from_mmap) + continue; + + free(rspec->regex_str); + } + free(node->regex_specs); + + for (uint32_t i = 0; i < node->children_num; i++) + free_spec_node(&node->children[i]); + free(node->children); + + if (!node->from_mmap) + free(node->stem); +} + +/* This will always check for buffer over-runs and either read the next entry + * if buf != NULL or skip over the entry (as these areas are mapped in the + * current buffer). */ +static inline int next_entry(void *buf, struct mmap_area *fp, size_t bytes) +{ + if (bytes > fp->next_len) + return -1; + + if (buf) + memcpy(buf, fp->next_addr, bytes); + + fp->next_addr = (unsigned char *)fp->next_addr + bytes; + fp->next_len -= bytes; + return 0; +} + /* This service is used by label_file.c process_file() and * utils/sefcontext_compile.c */ static inline int process_line(struct selabel_handle *rec, - const char *path, const char *prefix, - char *line_buf, unsigned lineno) + const char *path, const char *prefix, + char *line_buf, size_t nread, unsigned lineno) { - int items, len, rc; + int items; char *regex = NULL, *type = NULL, *context = NULL; - struct saved_data *data = (struct saved_data *)rec->data; - struct spec *spec_arr; - unsigned int nspec = data->nspec; + struct saved_data *data = rec->data; const char *errbuf = NULL; + uint8_t file_kind = LABEL_FILE_KIND_ALL; - items = read_spec_entries(line_buf, &errbuf, 3, ®ex, &type, &context); + if (prefix) { + if (prefix[0] != '/' || + prefix[1] == '\0' || + strchr(prefix + 1, '/') != NULL) { + errno = EINVAL; + return -1; + } + } + + items = read_spec_entries(line_buf, nread, &errbuf, 3, ®ex, &type, &context); if (items < 0) { if (errbuf) { - selinux_log(SELINUX_ERROR, - "%s: line %u error due to: %s\n", path, - lineno, errbuf); + COMPAT_LOG(SELINUX_ERROR, + "%s: line %u error due to: %s\n", path, + lineno, errbuf); } else { - selinux_log(SELINUX_ERROR, - "%s: line %u error due to: %m\n", path, - lineno); + COMPAT_LOG(SELINUX_ERROR, + "%s: line %u error due to: %m\n", path, + lineno); } free(regex); free(type); @@ -458,81 +913,38 @@ static inline int process_line(struct selabel_handle *rec, if (items < 2) { COMPAT_LOG(SELINUX_ERROR, - "%s: line %u is missing fields\n", path, - lineno); + "%s: line %u is missing fields\n", path, + lineno); if (items == 1) free(regex); errno = EINVAL; return -1; - } else if (items == 2) { - /* The type field is optional. */ - context = type; - type = 0; - } - - len = get_stem_from_spec(regex); - if (len && prefix && strncmp(prefix, regex, len)) { - /* Stem of regex does not match requested prefix, discard. */ - free(regex); - free(type); - free(context); - return 0; } - rc = grow_specs(data); - if (rc) - return rc; - - spec_arr = data->spec_arr; - - /* process and store the specification in spec. */ - spec_arr[nspec].stem_id = find_stem_from_spec(data, regex); - spec_arr[nspec].regex_str = regex; - __pthread_mutex_init(&spec_arr[nspec].regex_lock, NULL); - spec_arr[nspec].regex_compiled = false; - - spec_arr[nspec].type_str = type; - spec_arr[nspec].mode = 0; - - spec_arr[nspec].lr.ctx_raw = context; - spec_arr[nspec].lr.lineno = lineno; - - /* - * bump data->nspecs to cause closef() to cover it in its free - * but do not bump nspec since it's used below. - */ - data->nspec++; - - if (rec->validating - && compile_regex(&spec_arr[nspec], &errbuf)) { - COMPAT_LOG(SELINUX_ERROR, - "%s: line %u has invalid regex %s: %s\n", - path, lineno, regex, errbuf); - errno = EINVAL; - return -1; + if (items == 2) { + /* The type field is optional. */ + context = type; + type = NULL; } if (type) { - mode_t mode = string_to_mode(type); + file_kind = string_to_file_kind(type); - if (mode == (mode_t)-1) { + if (file_kind == LABEL_FILE_KIND_INVALID) { COMPAT_LOG(SELINUX_ERROR, "%s: line %u has invalid file type %s\n", path, lineno, type); + free(regex); + free(type); + free(context); errno = EINVAL; return -1; } - spec_arr[nspec].mode = mode; - } - - /* Determine if specification has - * any meta characters in the RE */ - spec_hasMetaChars(&spec_arr[nspec]); - if (strcmp(context, "<>") && rec->validating) - return compat_validate(rec, &spec_arr[nspec].lr, path, lineno); + free(type); + } - return 0; + return insert_spec(rec, data, prefix, regex, file_kind, context, path, lineno); } #endif /* _SELABEL_FILE_H_ */ diff --git a/libselinux/src/label_internal.h b/libselinux/src/label_internal.h index ea60cd9a05..743dbf9472 100644 --- a/libselinux/src/label_internal.h +++ b/libselinux/src/label_internal.h @@ -71,8 +71,9 @@ extern void digest_gen_hash(struct selabel_digest *digest); struct selabel_lookup_rec { char * ctx_raw; char * ctx_trans; - int validated; - unsigned lineno; + pthread_mutex_t lock; /* lock for validation and translation */ + unsigned int lineno; + bool validated; }; struct selabel_handle { @@ -143,6 +144,6 @@ compat_validate(const struct selabel_handle *rec, * The read_spec_entries function may be used to * replace sscanf to read entries from spec files. */ -extern int read_spec_entries(char *line_buf, const char **errbuf, int num_args, ...); +extern int read_spec_entries(char *line_buf, size_t nread, const char **errbuf, int num_args, ...); #endif /* _SELABEL_INTERNAL_H_ */ diff --git a/libselinux/src/label_media.c b/libselinux/src/label_media.c index d535ef8601..0510b5b1ac 100644 --- a/libselinux/src/label_media.c +++ b/libselinux/src/label_media.c @@ -177,6 +177,7 @@ static void close(struct selabel_handle *rec) free(spec->key); free(spec->lr.ctx_raw); free(spec->lr.ctx_trans); + __pthread_mutex_destroy(&spec->lr.lock); } if (spec_arr) diff --git a/libselinux/src/label_support.c b/libselinux/src/label_support.c index f7ab929256..978ba828c1 100644 --- a/libselinux/src/label_support.c +++ b/libselinux/src/label_support.c @@ -4,6 +4,7 @@ * Author : Richard Haines */ +#include #include #include #include @@ -21,10 +22,11 @@ * errno will be set. * */ -static inline int read_spec_entry(char **entry, char **ptr, int *len, const char **errbuf) +static inline int read_spec_entry(char **entry, const char **ptr, size_t *len, const char **errbuf) { + const char *tmp_buf; + *entry = NULL; - char *tmp_buf = NULL; while (isspace((unsigned char)**ptr) && **ptr != '\0') (*ptr)++; @@ -43,6 +45,9 @@ static inline int read_spec_entry(char **entry, char **ptr, int *len, const char } if (*len) { + if (*len >= UINT16_MAX) + return -1; + *entry = strndup(tmp_buf, *len); if (!*entry) return -1; @@ -62,22 +67,23 @@ static inline int read_spec_entry(char **entry, char **ptr, int *len, const char * This function calls read_spec_entry() to do the actual string processing. * As such, can return anything from that function as well. */ -int read_spec_entries(char *line_buf, const char **errbuf, int num_args, ...) +int read_spec_entries(char *line_buf, size_t nread, const char **errbuf, int num_args, ...) { - char **spec_entry, *buf_p; - int len, rc, items, entry_len = 0; + char **spec_entry; + const char *buf_p; + size_t entry_len = 0; + int rc, items; va_list ap; *errbuf = NULL; - len = strlen(line_buf); - if (line_buf[len - 1] == '\n') - line_buf[len - 1] = '\0'; + if (line_buf[nread - 1] == '\n') + line_buf[nread - 1] = '\0'; else /* Handle case if line not \n terminated by bumping * the len for the check below (as the line is NUL * terminated by getline(3)) */ - len++; + nread++; buf_p = line_buf; while (isspace((unsigned char)*buf_p)) @@ -94,7 +100,7 @@ int read_spec_entries(char *line_buf, const char **errbuf, int num_args, ...) while (items < num_args) { spec_entry = va_arg(ap, char **); - if (len - 1 == buf_p - line_buf) { + if (buf_p[0] == '\0' || nread - 1 == (size_t)(buf_p - line_buf)) { va_end(ap); return items; } diff --git a/libselinux/src/label_x.c b/libselinux/src/label_x.c index c0d1d4752a..1a5b926878 100644 --- a/libselinux/src/label_x.c +++ b/libselinux/src/label_x.c @@ -204,6 +204,7 @@ static void close(struct selabel_handle *rec) free(spec->key); free(spec->lr.ctx_raw); free(spec->lr.ctx_trans); + __pthread_mutex_destroy(&spec->lr.lock); } if (spec_arr) diff --git a/libselinux/src/regex.c b/libselinux/src/regex.c index 88d82fedb2..182c8c896c 100644 --- a/libselinux/src/regex.c +++ b/libselinux/src/regex.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -18,7 +19,6 @@ /* If the compiler doesn't define __BYTE_ORDER__, try to use the C * library header definitions. */ -#include #ifndef __BYTE_ORDER #error Neither __BYTE_ORDER__ nor __BYTE_ORDER defined. Unable to determine endianness. #endif @@ -116,13 +116,15 @@ int regex_load_mmap(struct mmap_area *mmap_area, struct regex_data **regex, int do_load_precompregex, bool *regex_compiled) { int rc; - uint32_t entry_len; + uint32_t data_u32, entry_len; *regex_compiled = false; - rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t)); + rc = next_entry(&data_u32, mmap_area, sizeof(uint32_t)); if (rc < 0) return -1; + entry_len = be32toh(data_u32); + if (entry_len && do_load_precompregex) { /* * this should yield exactly one because we store one pattern at @@ -169,7 +171,7 @@ int regex_writef(struct regex_data *regex, FILE *fp, int do_write_precompregex) int rc = 0; size_t len; PCRE2_SIZE serialized_size; - uint32_t to_write = 0; + uint32_t to_write = 0, data_u32; PCRE2_UCHAR *bytes = NULL; if (do_write_precompregex) { @@ -177,14 +179,15 @@ int regex_writef(struct regex_data *regex, FILE *fp, int do_write_precompregex) rc = pcre2_serialize_encode((const pcre2_code **)®ex->regex, 1, &bytes, &serialized_size, NULL); if (rc != 1 || serialized_size >= UINT32_MAX) { - rc = -1; + rc = -3; goto out; } to_write = serialized_size; } /* write serialized pattern's size */ - len = fwrite(&to_write, sizeof(uint32_t), 1, fp); + data_u32 = htobe32(to_write); + len = fwrite(&data_u32, sizeof(uint32_t), 1, fp); if (len != 1) { rc = -1; goto out; @@ -355,11 +358,15 @@ int regex_load_mmap(struct mmap_area *mmap_area, struct regex_data **regex, int do_load_precompregex __attribute__((unused)), bool *regex_compiled) { int rc; - uint32_t entry_len; + uint32_t data_u32, entry_len; size_t info_len; - rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t)); - if (rc < 0 || !entry_len) + rc = next_entry(&data_u32, mmap_area, sizeof(uint32_t)); + if (rc < 0) + return -1; + + entry_len = be32toh(data_u32); + if (!entry_len) return -1; *regex = regex_data_create(); @@ -380,10 +387,12 @@ int regex_load_mmap(struct mmap_area *mmap_area, struct regex_data **regex, if (rc < 0 || info_len != entry_len) goto err; - rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t)); + rc = next_entry(&data_u32, mmap_area, sizeof(uint32_t)); if (rc < 0) goto err; + entry_len = be32toh(data_u32); + if (entry_len) { (*regex)->lsd.study_data = (void *)mmap_area->next_addr; (*regex)->lsd.flags |= PCRE_EXTRA_STUDY_DATA; @@ -424,45 +433,45 @@ int regex_writef(struct regex_data *regex, FILE *fp, { int rc; size_t len; - uint32_t to_write; + uint32_t data_u32; size_t size; pcre_extra *sd = get_pcre_extra(regex); /* determine the size of the pcre data in bytes */ rc = pcre_fullinfo(regex->regex, NULL, PCRE_INFO_SIZE, &size); - if (rc < 0) - return -1; + if (rc < 0 || size >= UINT32_MAX) + return -3; /* write the number of bytes in the pcre data */ - to_write = size; - len = fwrite(&to_write, sizeof(uint32_t), 1, fp); + data_u32 = htobe32(size); + len = fwrite(&data_u32, sizeof(uint32_t), 1, fp); if (len != 1) return -1; /* write the actual pcre data as a char array */ - len = fwrite(regex->regex, 1, to_write, fp); - if (len != to_write) + len = fwrite(regex->regex, 1, size, fp); + if (len != size) return -1; if (sd) { /* determine the size of the pcre study info */ rc = pcre_fullinfo(regex->regex, sd, PCRE_INFO_STUDYSIZE, &size); - if (rc < 0) - return -1; + if (rc < 0 || size >= UINT32_MAX) + return -3; } else size = 0; /* write the number of bytes in the pcre study data */ - to_write = size; - len = fwrite(&to_write, sizeof(uint32_t), 1, fp); + data_u32 = htobe32(size); + len = fwrite(&data_u32, sizeof(uint32_t), 1, fp); if (len != 1) return -1; if (sd) { /* write the actual pcre study data as a char array */ - len = fwrite(sd->study_data, 1, to_write, fp); - if (len != to_write) + len = fwrite(sd->study_data, 1, size, fp); + if (len != size) return -1; } diff --git a/libselinux/utils/.gitignore b/libselinux/utils/.gitignore index b33113609b..2e10b14fd9 100644 --- a/libselinux/utils/.gitignore +++ b/libselinux/utils/.gitignore @@ -16,6 +16,7 @@ getseuser matchpathcon policyvers sefcontext_compile +selabel_compare selabel_digest selabel_get_digests_all_partial_matches selabel_lookup diff --git a/libselinux/utils/sefcontext_compile.c b/libselinux/utils/sefcontext_compile.c index 8677189e03..9106c7dd02 100644 --- a/libselinux/utils/sefcontext_compile.c +++ b/libselinux/utils/sefcontext_compile.c @@ -1,25 +1,25 @@ -#include +#include #include +#include #include #include #include #include -#include -#include -#include -#include + #include #include +#include "../src/avc_sidtab.h" #include "../src/label_file.h" #include "../src/regex.h" + static const char *policy_file; static int ctx_err; static int validate_context(char **ctxp) { - char *ctx = *ctxp; + const char *ctx = *ctxp; if (policy_file && sepol_check_context(ctx) < 0) { ctx_err = -1; @@ -35,20 +35,20 @@ static int process_file(struct selabel_handle *rec, const char *filename) int rc; char *line_buf = NULL; size_t line_len = 0; + ssize_t nread; FILE *context_file; const char *prefix = NULL; - context_file = fopen(filename, "r"); + context_file = fopen(filename, "re"); if (!context_file) { - fprintf(stderr, "Error opening %s: %s\n", - filename, strerror(errno)); + fprintf(stderr, "Error opening %s: %m\n", filename); return -1; } line_num = 0; rc = 0; - while (getline(&line_buf, &line_len, context_file) > 0) { - rc = process_line(rec, filename, prefix, line_buf, ++line_num); + while ((nread = getline(&line_buf, &line_len, context_file)) > 0) { + rc = process_line(rec, filename, prefix, line_buf, nread, ++line_num); if (rc || ctx_err) { /* With -p option need to check and fail if ctx err as * process_line() context validation on Linux does not @@ -65,211 +65,445 @@ static int process_file(struct selabel_handle *rec, const char *filename) return rc; } +static int literal_spec_to_sidtab(const struct literal_spec *lspec, struct sidtab *stab) +{ + security_id_t dummy; + + return sidtab_context_to_sid(stab, lspec->lr.ctx_raw, &dummy); +} + +static int regex_spec_to_sidtab(const struct regex_spec *rspec, struct sidtab *stab) +{ + security_id_t dummy; + + return sidtab_context_to_sid(stab, rspec->lr.ctx_raw, &dummy); +} + +static int spec_node_to_sidtab(const struct spec_node *node, struct sidtab *stab) +{ + int rc; + + for (uint32_t i = 0; i < node->literal_specs_num; i++) { + rc = literal_spec_to_sidtab(&node->literal_specs[i], stab); + if (rc) + return rc; + } + + for (uint32_t i = 0; i < node->regex_specs_num; i++) { + rc = regex_spec_to_sidtab(&node->regex_specs[i], stab); + if (rc) + return rc; + } + + for (uint32_t i = 0; i < node->children_num; i++) { + rc = spec_node_to_sidtab(&node->children[i], stab); + if (rc) + return rc; + } + + return 0; +} + +static int create_sidtab(const struct saved_data *data, struct sidtab *stab) +{ + int rc; + + rc = sidtab_init(stab); + if (rc < 0) + return rc; + + return spec_node_to_sidtab(data->root, stab); +} + + /* * File Format * - * u32 - magic number - * u32 - version - * u32 - length of pcre version EXCLUDING nul - * char - pcre version string EXCLUDING nul - * u32 - number of stems - * ** Stems - * u32 - length of stem EXCLUDING nul - * char - stem char array INCLUDING nul - * u32 - number of regexs - * ** Regexes - * u32 - length of upcoming context INCLUDING nul - * char - char array of the raw context - * u32 - length of the upcoming regex_str - * char - char array of the original regex string including the stem. - * u32 - mode bits for >= SELINUX_COMPILED_FCONTEXT_MODE - * mode_t for <= SELINUX_COMPILED_FCONTEXT_PCRE_VERS - * s32 - stemid associated with the regex - * u32 - spec has meta characters - * u32 - The specs prefix_len if >= SELINUX_COMPILED_FCONTEXT_PREFIX_LEN - * u32 - data length of the pcre regex - * char - a buffer holding the raw pcre regex info - * u32 - data length of the pcre regex study daya - * char - a buffer holding the raw pcre regex study data + * The format uses network byte-order. + * + * u32 - magic number + * u32 - version + * u32 - length of upcoming pcre version EXCLUDING nul + * [char] - pcre version string EXCLUDING nul + * u32 - length of upcoming pcre architecture EXCLUDING nul + * [char] - pcre architecture string EXCLUDING nul + * u64 - number of total specifications + * u32 - number of upcoming context definitions + * [Ctx] - array of context definitions + * Node - root node + * + * Context Definition Format (Ctx) + * + * u16 - length of upcoming raw context EXCLUDING nul + * [char] - char array of the raw context EXCLUDING nul + * + * Node Format + * + * u16 - length of upcoming stem INCLUDING nul + * [char] - stem char array INCLUDING nul + * u32 - number of upcoming literal specifications + * [LSpec] - array of literal specifications + * u32 - number of upcoming regular expression specifications + * [RSpec] - array of regular expression specifications + * u32 - number of upcoming child nodes + * [Node] - array of child nodes + * + * Literal Specification Format (LSpec) + * + * u32 - context table index for raw context (1-based) + * u16 - length of upcoming regex_str INCLUDING nul + * [char] - char array of the original regex string including the stem INCLUDING nul + * u16 - length of upcoming literal match INCLUDING nul + * [char] - char array of the simplified literal match INCLUDING nul + * u8 - file kind (LABEL_FILE_KIND_*) + * + * Regular Expression Specification Format (RSpec) + * + * u32 - context table index for raw context (1-based) + * u16 - length of upcoming regex_str INCLUDING nul + * [char] - char array of the original regex string including the stem INCLUDING nul + * u16 - length of the fixed path prefix + * u8 - file kind (LABEL_FILE_KIND_*) + * [Regex] - serialized pattern of regex, subject to underlying regex library */ -static int write_binary_file(struct saved_data *data, int fd, - int do_write_precompregex) + + +static int security_id_compare(const void *a, const void *b) { - struct spec *specs = data->spec_arr; - FILE *bin_file; + const struct security_id *sid_a = a, *sid_b = b; + + return (sid_a->id > sid_b->id) - (sid_a->id < sid_b->id); +} + +static int write_sidtab(FILE *bin_file, const struct sidtab *stab) +{ + struct security_id *sids; + uint32_t data_u32, index; + uint16_t data_u16; + size_t len; + + /* write number of entries */ + data_u32 = htobe32(stab->nel); + len = fwrite(&data_u32, sizeof(uint32_t), 1, bin_file); + if (len != 1) + return -1; + + /* sort entries by id */ + sids = calloc(stab->nel, sizeof(*sids)); + if (!sids) + return -1; + index = 0; + for (unsigned i = 0; i < SIDTAB_SIZE; i++) { + const struct sidtab_node *cur = stab->htable[i]; + + while (cur) { + sids[index++] = cur->sid_s; + cur = cur->next; + } + } + assert(index == stab->nel); + qsort(sids, stab->nel, sizeof(struct security_id), security_id_compare); + assert(sids[0].id == 1); + assert(sids[stab->nel - 1].id == stab->nel); + + /* write raw contexts sorted by id */ + for (uint32_t i = 0; i < stab->nel; i++) { + const char *ctx = sids[i].ctx; + size_t ctx_len = strlen(ctx); + + if (ctx_len == 0 || ctx_len >= UINT16_MAX) { + free(sids); + return -2; + } + data_u16 = htobe16(ctx_len); + len = fwrite(&data_u16, sizeof(uint16_t), 1, bin_file); + if (len != 1) { + free(sids); + return -1; + } + len = fwrite(ctx, sizeof(char), ctx_len, bin_file); + if (len != ctx_len) { + free(sids); + return -1; + } + } + + free(sids); + return 0; +} + +static int write_literal_spec(FILE *bin_file, const struct literal_spec *lspec, const struct sidtab *stab) +{ + const struct security_id *sid; + const char *orig_regex, *literal_match; + size_t orig_regex_len, literal_match_len; + uint32_t data_u32; + uint16_t data_u16; + uint8_t data_u8; + size_t len; + + /* write raw context sid */ + sid = sidtab_context_lookup(stab, lspec->lr.ctx_raw); + assert(sid); /* should be set via create_sidtab() */ + data_u32 = htobe32(sid->id); + len = fwrite(&data_u32, sizeof(uint32_t), 1, bin_file); + if (len != 1) + return -1; + + /* write original regex string */ + orig_regex = lspec->regex_str; + orig_regex_len = strlen(orig_regex); + if (orig_regex_len == 0 || orig_regex_len >= UINT16_MAX) + return -2; + orig_regex_len += 1; + data_u16 = htobe16(orig_regex_len); + len = fwrite(&data_u16, sizeof(uint16_t), 1, bin_file); + if (len != 1) + return -1; + len = fwrite(orig_regex, sizeof(char), orig_regex_len, bin_file); + if (len != orig_regex_len) + return -1; + + /* write literal match string */ + literal_match = lspec->literal_match; + literal_match_len = strlen(literal_match); + if (literal_match_len == 0 || literal_match_len >= UINT16_MAX) + return -2; + literal_match_len += 1; + data_u16 = htobe16(literal_match_len); + len = fwrite(&data_u16, sizeof(uint16_t), 1, bin_file); + if (len != 1) + return -1; + len = fwrite(literal_match, sizeof(char), literal_match_len, bin_file); + if (len != literal_match_len) + return -1; + + /* write file kind */ + data_u8 = lspec->file_kind; + len = fwrite(&data_u8, sizeof(uint8_t), 1, bin_file); + if (len != 1) + return -1; + + return 0; +} + +static int write_regex_spec(FILE *bin_file, bool do_write_precompregex, const struct regex_spec *rspec, const struct sidtab *stab) +{ + const struct security_id *sid; + const char *regex; + size_t regex_len; + uint32_t data_u32; + uint16_t data_u16; + uint8_t data_u8; size_t len; - uint32_t magic = SELINUX_MAGIC_COMPILED_FCONTEXT; - uint32_t section_len; - uint32_t i; int rc; - const char *reg_version; - const char *reg_arch; - bin_file = fdopen(fd, "w"); - if (!bin_file) { - perror("fopen output_file"); - exit(EXIT_FAILURE); + /* write raw context sid */ + sid = sidtab_context_lookup(stab, rspec->lr.ctx_raw); + assert(sid); /* should be set via create_sidtab() */ + data_u32 = htobe32(sid->id); + len = fwrite(&data_u32, sizeof(uint32_t), 1, bin_file); + if (len != 1) + return -1; + + /* write regex string */ + regex = rspec->regex_str; + regex_len = strlen(regex); + if (regex_len == 0 || regex_len >= UINT16_MAX) + return -2; + regex_len += 1; + data_u16 = htobe16(regex_len); + len = fwrite(&data_u16, sizeof(uint16_t), 1, bin_file); + if (len != 1) + return -1; + len = fwrite(regex, sizeof(char), regex_len, bin_file); + if (len != regex_len) + return -1; + + /* write prefix length */ + data_u16 = htobe16(rspec->prefix_len); + len = fwrite(&data_u16, sizeof(uint16_t), 1, bin_file); + if (len != 1) + return -1; + + /* write file kind */ + data_u8 = rspec->file_kind; + len = fwrite(&data_u8, sizeof(uint8_t), 1, bin_file); + if (len != 1) + return -1; + + /* Write serialized regex */ + rc = regex_writef(rspec->regex, bin_file, do_write_precompregex); + if (rc < 0) + return rc; + + return 0; +} + +static int write_spec_node(FILE *bin_file, bool do_write_precompregex, const struct spec_node *node, const struct sidtab *stab) +{ + size_t stem_len; + uint32_t data_u32; + uint16_t data_u16; + size_t len; + int rc; + + stem_len = node->stem_len; + if ((stem_len == 0 && node->parent) || stem_len >= UINT16_MAX) + return -2; + stem_len += 1; + data_u16 = htobe16(stem_len); + len = fwrite(&data_u16, sizeof(uint16_t), 1, bin_file); + if (len != 1) + return -1; + len = fwrite(node->stem ?: "", sizeof(char), stem_len, bin_file); + if (len != stem_len) + return -1; + + /* write number of literal specs */ + data_u32 = htobe32(node->literal_specs_num); + len = fwrite(&data_u32, sizeof(uint32_t), 1, bin_file); + if (len != 1) + return -1; + + /* write literal specs */ + for (uint32_t i = 0; i < node->literal_specs_num; i++) { + rc = write_literal_spec(bin_file, &node->literal_specs[i], stab); + if (rc) + return rc; + } + + /* write number of regex specs */ + data_u32 = htobe32(node->regex_specs_num); + len = fwrite(&data_u32, sizeof(uint32_t), 1, bin_file); + if (len != 1) + return -1; + + /* write regex specs */ + for (uint32_t i = 0; i < node->regex_specs_num; i++) { + rc = write_regex_spec(bin_file, do_write_precompregex, &node->regex_specs[i], stab); + if (rc) + return rc; + } + + /* write number of child nodes */ + data_u32 = htobe32(node->children_num); + len = fwrite(&data_u32, sizeof(uint32_t), 1, bin_file); + if (len != 1) + return -1; + + /* write child nodes */ + for (uint32_t i = 0; i < node->children_num; i++) { + rc = write_spec_node(bin_file, do_write_precompregex, &node->children[i], stab); + if (rc) + return rc; } + return 0; +} + +static int write_binary_file(const struct saved_data *data, const struct sidtab *stab, + int fd, const char *path, bool do_write_precompregex, + const char *progname) +{ + FILE *bin_file; + const char *reg_arch, *reg_version; + size_t len, reg_arch_len, reg_version_len; + uint64_t data_u64; + uint32_t data_u32; + int rc; + + bin_file = fdopen(fd, "we"); + if (!bin_file) { + fprintf(stderr, "%s: failed to open %s: %m\n", progname, path); + close(fd); + return -1; + } + /* write some magic number */ - len = fwrite(&magic, sizeof(uint32_t), 1, bin_file); + data_u32 = htobe32(SELINUX_MAGIC_COMPILED_FCONTEXT); + len = fwrite(&data_u32, sizeof(uint32_t), 1, bin_file); if (len != 1) - goto err; + goto err_write; /* write the version */ - section_len = SELINUX_COMPILED_FCONTEXT_MAX_VERS; - len = fwrite(§ion_len, sizeof(uint32_t), 1, bin_file); + data_u32 = htobe32(SELINUX_COMPILED_FCONTEXT_MAX_VERS); + len = fwrite(&data_u32, sizeof(uint32_t), 1, bin_file); if (len != 1) - goto err; + goto err_write; /* write version of the regex back-end */ reg_version = regex_version(); if (!reg_version) - goto err; - section_len = strlen(reg_version); - len = fwrite(§ion_len, sizeof(uint32_t), 1, bin_file); + goto err_check; + reg_version_len = strlen(reg_version); + if (reg_version_len == 0 || reg_version_len >= UINT32_MAX) + goto err_check; + data_u32 = htobe32(reg_version_len); + len = fwrite(&data_u32, sizeof(uint32_t), 1, bin_file); if (len != 1) - goto err; - len = fwrite(reg_version, sizeof(char), section_len, bin_file); - if (len != section_len) - goto err; + goto err_write; + len = fwrite(reg_version, sizeof(char), reg_version_len, bin_file); + if (len != reg_version_len) + goto err_write; /* write regex arch string */ reg_arch = regex_arch_string(); if (!reg_arch) - goto err; - section_len = strlen(reg_arch); - len = fwrite(§ion_len, sizeof(uint32_t), 1, bin_file); + goto err_check; + reg_arch_len = strlen(reg_arch); + if (reg_arch_len == 0 || reg_arch_len >= UINT32_MAX) + goto err_check; + data_u32 = htobe32(reg_arch_len); + len = fwrite(&data_u32, sizeof(uint32_t), 1, bin_file); if (len != 1) - goto err; - len = fwrite(reg_arch, sizeof(char), section_len, bin_file); - if (len != section_len) - goto err; - - /* write the number of stems coming */ - section_len = data->num_stems; - len = fwrite(§ion_len, sizeof(uint32_t), 1, bin_file); + goto err_write; + len = fwrite(reg_arch, sizeof(char), reg_arch_len, bin_file); + if (len != reg_arch_len) + goto err_write; + + /* write number of total specifications */ + data_u64 = htobe64(data->num_specs); + len = fwrite(&data_u64, sizeof(uint64_t), 1, bin_file); if (len != 1) - goto err; - - for (i = 0; i < section_len; i++) { - char *stem = data->stem_arr[i].buf; - uint32_t stem_len = data->stem_arr[i].len; + goto err_write; - /* write the strlen (aka no nul) */ - len = fwrite(&stem_len, sizeof(uint32_t), 1, bin_file); - if (len != 1) - goto err; - - /* include the nul in the file */ - stem_len += 1; - len = fwrite(stem, sizeof(char), stem_len, bin_file); - if (len != stem_len) - goto err; - } + /* write context table */ + rc = write_sidtab(bin_file, stab); + if (rc) + return rc; - /* write the number of regexes coming */ - section_len = data->nspec; - len = fwrite(§ion_len, sizeof(uint32_t), 1, bin_file); - if (len != 1) + rc = write_spec_node(bin_file, do_write_precompregex, data->root, stab); + if (rc) goto err; - for (i = 0; i < section_len; i++) { - char *context = specs[i].lr.ctx_raw; - char *regex_str = specs[i].regex_str; - mode_t mode = specs[i].mode; - size_t prefix_len = specs[i].prefix_len; - int32_t stem_id = specs[i].stem_id; - struct regex_data *re = specs[i].regex; - uint32_t to_write; - - /* length of the context string (including nul) */ - to_write = strlen(context) + 1; - len = fwrite(&to_write, sizeof(uint32_t), 1, bin_file); - if (len != 1) - goto err; - - /* original context string (including nul) */ - len = fwrite(context, sizeof(char), to_write, bin_file); - if (len != to_write) - goto err; - - /* length of the original regex string (including nul) */ - to_write = strlen(regex_str) + 1; - len = fwrite(&to_write, sizeof(uint32_t), 1, bin_file); - if (len != 1) - goto err; - - /* original regex string */ - len = fwrite(regex_str, sizeof(char), to_write, bin_file); - if (len != to_write) - goto err; - - /* binary F_MODE bits */ - to_write = mode; - len = fwrite(&to_write, sizeof(uint32_t), 1, bin_file); - if (len != 1) - goto err; - - /* stem for this regex (could be -1) */ - len = fwrite(&stem_id, sizeof(stem_id), 1, bin_file); - if (len != 1) - goto err; - - /* does this spec have a metaChar? */ - to_write = specs[i].hasMetaChars; - len = fwrite(&to_write, sizeof(to_write), 1, bin_file); - if (len != 1) - goto err; - - /* For SELINUX_COMPILED_FCONTEXT_PREFIX_LEN */ - to_write = prefix_len; - len = fwrite(&to_write, sizeof(to_write), 1, bin_file); - if (len != 1) - goto err; - - /* Write regex related data */ - rc = regex_writef(re, bin_file, do_write_precompregex); - if (rc < 0) - goto err; - } - - rc = 0; out: - fclose(bin_file); + if (fclose(bin_file) && rc == 0) { + fprintf(stderr, "%s: failed to close %s: %m\n", progname, path); + rc = -1; + } return rc; -err: - rc = -1; - goto out; -} -static void free_specs(struct saved_data *data) -{ - struct spec *specs = data->spec_arr; - unsigned int num_entries = data->nspec; - unsigned int i; - - for (i = 0; i < num_entries; i++) { - free(specs[i].lr.ctx_raw); - free(specs[i].lr.ctx_trans); - free(specs[i].regex_str); - free(specs[i].type_str); - regex_data_free(specs[i].regex); - } - free(specs); +err_check: + rc = -2; + goto err; - num_entries = data->num_stems; - for (i = 0; i < num_entries; i++) - free(data->stem_arr[i].buf); - free(data->stem_arr); +err_write: + rc = -1; + goto err; - memset(data, 0, sizeof(*data)); +err: + fprintf(stderr, "%s: failed to compile file context specifications: %s\n", + progname, + (rc == -3) ? "regex serialization failure" : + ((rc == -2) ? "invalid fcontext specification" : "write failure")); + goto out; } static __attribute__ ((__noreturn__)) void usage(const char *progname) { fprintf(stderr, - "usage: %s [-o out_file] [-p policy_file] fc_file\n" + "usage: %s [-iV] [-o out_file] [-p policy_file] fc_file\n" "Where:\n\t" "-o Optional file name of the PCRE formatted binary\n\t" " file to be output. If not specified the default\n\t" @@ -287,28 +521,32 @@ static __attribute__ ((__noreturn__)) void usage(const char *progname) " Arch identifier format (PCRE2):\n\t" " --, e.g.,\n\t" " \"8-8-el\" for x86_64.\n\t" + "-V Print binary output format version and exit.\n\t" "fc_file The text based file contexts file to be processed.\n", progname); - exit(EXIT_FAILURE); + exit(EXIT_FAILURE); } int main(int argc, char *argv[]) { - const char *path = NULL; + const char *path; const char *out_file = NULL; - int do_write_precompregex = 1; + bool do_write_precompregex = true; char stack_path[PATH_MAX + 1]; char *tmp = NULL; + size_t len; int fd, rc, opt; FILE *policy_fp = NULL; struct stat buf; struct selabel_handle *rec = NULL; struct saved_data *data = NULL; + struct spec_node *root = NULL; + struct sidtab stab = {}; if (argc < 2) usage(argv[0]); - while ((opt = getopt(argc, argv, "io:p:r")) > 0) { + while ((opt = getopt(argc, argv, "io:p:rV")) > 0) { switch (opt) { case 'o': out_file = optarg; @@ -317,18 +555,20 @@ int main(int argc, char *argv[]) policy_file = optarg; break; case 'r': - do_write_precompregex = 0; + do_write_precompregex = false; break; case 'i': - printf("%s (%s)\n", regex_version(), - regex_arch_string()); + printf("%s (%s)\n", regex_version(), regex_arch_string()); + return 0; + case 'V': + printf("Compiled fcontext format version %d\n", SELINUX_COMPILED_FCONTEXT_MAX_VERS); return 0; default: usage(argv[0]); } } - if (optind >= argc) + if (optind + 1 != argc) usage(argv[0]); path = argv[optind]; @@ -339,7 +579,7 @@ int main(int argc, char *argv[]) /* Open binary policy if supplied. */ if (policy_file) { - policy_fp = fopen(policy_file, "r"); + policy_fp = fopen(policy_file, "re"); if (!policy_fp) { fprintf(stderr, "%s: failed to open %s: %s\n", @@ -373,7 +613,7 @@ int main(int argc, char *argv[]) * error is detected, the process will be aborted. */ rec->validating = 1; selinux_set_callback(SELINUX_CB_VALIDATE, - (union selinux_callback)&validate_context); + (union selinux_callback) { .func_validate = &validate_context }); data = (struct saved_data *)calloc(1, sizeof(*data)); if (!data) { @@ -384,6 +624,17 @@ int main(int argc, char *argv[]) exit(EXIT_FAILURE); } + root = calloc(1, sizeof(*root)); + if (!root) { + fprintf(stderr, "%s: calloc failed: %s\n", argv[0], strerror(errno)); + free(data); + free(rec); + if (policy_fp) + fclose(policy_fp); + exit(EXIT_FAILURE); + } + + data->root = root; rec->data = data; rc = process_file(rec, path); @@ -392,9 +643,11 @@ int main(int argc, char *argv[]) goto err; } - rc = sort_specs(data); - if (rc) { - fprintf(stderr, "%s: sort_specs failed\n", argv[0]); + sort_specs(data); + + rc = create_sidtab(data, &stab); + if (rc < 0) { + fprintf(stderr, "%s: failed to generate sidtab: %s\n", argv[0], strerror(errno)); goto err; } @@ -403,40 +656,41 @@ int main(int argc, char *argv[]) else rc = snprintf(stack_path, sizeof(stack_path), "%s.bin", path); - if (rc < 0 || rc >= (int)sizeof(stack_path)) { + if (rc < 0 || (size_t)rc >= sizeof(stack_path)) { fprintf(stderr, "%s: snprintf failed\n", argv[0]); goto err; } + len = rc; - tmp = malloc(strlen(stack_path) + 7); + tmp = malloc(len + 7); if (!tmp) { fprintf(stderr, "%s: malloc failed: %s\n", argv[0], strerror(errno)); goto err; } - rc = sprintf(tmp, "%sXXXXXX", stack_path); - if (rc < 0) { - fprintf(stderr, "%s: sprintf failed\n", argv[0]); + rc = snprintf(tmp, len + 7, "%sXXXXXX", stack_path); + if (rc < 0 || (size_t)rc >= len + 7) { + fprintf(stderr, "%s: snprintf failed\n", argv[0]); goto err; } - fd = mkstemp(tmp); + fd = mkstemp(tmp); if (fd < 0) { fprintf(stderr, "%s: mkstemp %s failed: %s\n", argv[0], tmp, strerror(errno)); + close(fd); goto err; } rc = fchmod(fd, buf.st_mode); if (rc < 0) { fprintf(stderr, "%s: fchmod %s failed: %s\n", argv[0], tmp, strerror(errno)); + close(fd); goto err_unlink; } - rc = write_binary_file(data, fd, do_write_precompregex); - if (rc < 0) { - fprintf(stderr, "%s: write_binary_file %s failed\n", argv[0], tmp); + rc = write_binary_file(data, &stab, fd, tmp, do_write_precompregex, argv[0]); + if (rc < 0) goto err_unlink; - } rc = rename(tmp, stack_path); if (rc < 0) { @@ -449,9 +703,11 @@ int main(int argc, char *argv[]) if (policy_fp) fclose(policy_fp); - free_specs(data); - free(rec); + sidtab_destroy(&stab); + free_spec_node(data->root); + free(data->root); free(data); + free(rec); free(tmp); return rc; diff --git a/libselinux/utils/selabel_compare.c b/libselinux/utils/selabel_compare.c new file mode 100644 index 0000000000..9ca6eff1c8 --- /dev/null +++ b/libselinux/utils/selabel_compare.c @@ -0,0 +1,122 @@ +#include +#include +#include +#include + +#include + + +static void usage(const char *progname) +{ + fprintf(stderr, + "usage: %s [-b backend] [-v] file1 file2\n\n" + "Where:\n\t" + "-b The backend - \"file\", \"media\", \"x\", \"db\" or \"prop\" (defaults to \"file\")\n\t" + "-v Validate entries against loaded policy.\n\t" + "file1/file2 Files containing the specs.\n", + progname); +} + +static int compare(const char *file1, const char *file2, const char *validate, unsigned int backend) +{ + struct selabel_handle *hnd1, *hnd2; + const struct selinux_opt selabel_option1[] = { + { SELABEL_OPT_PATH, file1 }, + { SELABEL_OPT_VALIDATE, validate } + }; + const struct selinux_opt selabel_option2[] = { + { SELABEL_OPT_PATH, file2 }, + { SELABEL_OPT_VALIDATE, validate } + }; + enum selabel_cmp_result result; + + hnd1 = selabel_open(backend, selabel_option1, 2); + if (!hnd1) { + fprintf(stderr, "ERROR: selabel_open - Could not obtain handle for %s: %m\n", file1); + return EXIT_FAILURE; + } + + hnd2 = selabel_open(backend, selabel_option2, 2); + if (!hnd2) { + fprintf(stderr, "ERROR: selabel_open - Could not obtain handle for %s: %m\n", file2); + selabel_close(hnd1); + return EXIT_FAILURE; + } + + result = selabel_cmp(hnd1, hnd2); + + selabel_close(hnd2); + selabel_close(hnd1); + + switch (result) { + case SELABEL_SUBSET: + printf("spec %s is a subset of spec %s\n", file1, file2); + break; + case SELABEL_EQUAL: + printf("spec %s is equal to spec %s\n", file1, file2); + break; + case SELABEL_SUPERSET: + printf("spec %s is a superset of spec %s\n", file1, file2); + break; + case SELABEL_INCOMPARABLE: + printf("spec %s is uncomparable to spec %s\n", file1, file2); + break; + default: + fprintf(stderr, "ERROR: selabel_cmp - Unexpected result %d\n", result); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +int main(int argc, char *argv[]) +{ + unsigned int backend = SELABEL_CTX_FILE; + int opt; + const char *validate = NULL, *file1 = NULL, *file2 = NULL; + + if (argc < 3) { + usage(argv[0]); + return EXIT_FAILURE; + } + + while ((opt = getopt(argc, argv, "b:v")) > 0) { + switch (opt) { + case 'b': + if (!strcasecmp(optarg, "file")) { + backend = SELABEL_CTX_FILE; + } else if (!strcmp(optarg, "media")) { + backend = SELABEL_CTX_MEDIA; + } else if (!strcmp(optarg, "x")) { + backend = SELABEL_CTX_X; + } else if (!strcmp(optarg, "db")) { + backend = SELABEL_CTX_DB; + } else if (!strcmp(optarg, "prop")) { + backend = SELABEL_CTX_ANDROID_PROP; + } else if (!strcmp(optarg, "service")) { + backend = SELABEL_CTX_ANDROID_SERVICE; + } else { + fprintf(stderr, "Unknown backend: %s\n", optarg); + usage(argv[0]); + return EXIT_FAILURE; + } + break; + case 'v': + validate = (char *)1; + break; + default: + usage(argv[0]); + return EXIT_FAILURE; + } + } + + if (argc != optind + 2) { + usage(argv[0]); + return EXIT_FAILURE; + } + + file1 = argv[optind++]; + file2 = argv[optind]; + + return compare(file1, file2, validate, backend); +} diff --git a/policycoreutils/.gitignore b/policycoreutils/.gitignore index 47c9cc5296..33e7414cbc 100644 --- a/policycoreutils/.gitignore +++ b/policycoreutils/.gitignore @@ -9,4 +9,5 @@ setfiles/restorecon setfiles/restorecon_xattr setfiles/setfiles setsebool/setsebool +unsetfiles/unsetfiles hll/pp/pp diff --git a/policycoreutils/Makefile b/policycoreutils/Makefile index b930b2973b..32ad02017b 100644 --- a/policycoreutils/Makefile +++ b/policycoreutils/Makefile @@ -1,4 +1,4 @@ -SUBDIRS = setfiles load_policy newrole run_init secon sestatus semodule setsebool scripts po man hll +SUBDIRS = setfiles load_policy newrole run_init secon sestatus semodule setsebool scripts po man hll unsetfiles all install relabel clean indent: @for subdir in $(SUBDIRS); do \ diff --git a/policycoreutils/unsetfiles/Makefile b/policycoreutils/unsetfiles/Makefile new file mode 100644 index 0000000000..9e5edc0447 --- /dev/null +++ b/policycoreutils/unsetfiles/Makefile @@ -0,0 +1,26 @@ +PREFIX ?= /usr +SBINDIR ?= $(PREFIX)/sbin +MANDIR ?= $(PREFIX)/share/man + +override CFLAGS += -D_GNU_SOURCE +override LDLIBS += -lselinux + + +all: unsetfiles + +unsetfiles: unsetfiles.o + +install: all + test -d $(DESTDIR)$(SBINDIR) || install -m 755 -d $(DESTDIR)$(SBINDIR) + test -d $(DESTDIR)$(MANDIR)/man1 || install -m 755 -d $(DESTDIR)$(MANDIR)/man1 + install -m 755 unsetfiles $(DESTDIR)$(SBINDIR) + install -m 644 unsetfiles.1 $(DESTDIR)$(MANDIR)/man1/ + +clean: + -rm -f unsetfiles *.o + +indent: + ../../scripts/Lindent $(wildcard *.[ch]) + +relabel: install + /sbin/restorecon $(DESTDIR)$(SBINDIR)/unsetfiles diff --git a/policycoreutils/unsetfiles/unsetfiles.1 b/policycoreutils/unsetfiles/unsetfiles.1 new file mode 100644 index 0000000000..49d0c82140 --- /dev/null +++ b/policycoreutils/unsetfiles/unsetfiles.1 @@ -0,0 +1,46 @@ +.TH UNSETFILES "1" "December 2023" "Security Enhanced Linux" +.SH NAME +unsetfiles \- Remove SELinux file security contexts. +.SH SYNOPSIS +.B unsetfiles +.RB [ \-hnrvx ] +.IR pathname \ ... + +.SH DESCRIPTION +.P +This program removes the SELinux file security contexts of files. It can help +cleaning extended file attributes after disabling SELinux. +.P +.B unsetfiles +will only work on SELinux disabled systems, since removing file security +contexts is not supported by SELinux. + +.SH OPTIONS +.TP +.B \-h +Show usage information and exit. +.TP +.B \-n +Do not actually remove any SELinux file security contexts. +.TP +.B \-r +Remove SELinux file security contexts recursive. +.TP +.B \-v +Be verbose about performed actions. +.TP +.B \-x +Do not cross filesystem boundaries. + +.SH ARGUMENTS +.TP +.IR pathname \ ... +One or more path names to operate on. + +.SH SEE ALSO +.BR restorecon (8), +.BR setfiles (8) + +.SH AUTHORS +.nf +Christian Göttsche (cgzones@googlemail.com) diff --git a/policycoreutils/unsetfiles/unsetfiles.c b/policycoreutils/unsetfiles/unsetfiles.c new file mode 100644 index 0000000000..6293d00fbc --- /dev/null +++ b/policycoreutils/unsetfiles/unsetfiles.c @@ -0,0 +1,183 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +#define XATTR_NAME_SELINUX "security.selinux" + + +static void usage(const char *progname) +{ + fprintf(stderr, "usage: %s [-nrvx] \n\n" + "Options:\n" + "\t-n\tdon't remove any file labels\n" + "\t-r\tremove labels recursive\n" + "\t-v\tbe verbose\n" + "\t-x\tdo not cross filesystem boundaries\n", + progname); +} + +static void unset(int atfd, const char *path, const char *fullpath, + bool dry_run, bool recursive, bool verbose, + dev_t root_dev) +{ + ssize_t ret; + int fd, rc; + DIR *dir; + + ret = lgetxattr(fullpath, XATTR_NAME_SELINUX, NULL, 0); + if (ret <= 0) { + if (errno != ENODATA && errno != ENOTSUP) + fprintf(stderr, "Failed to get SELinux label of %s: %m\n", fullpath); + else if (verbose) + printf("Failed to get SELinux label of %s: %m\n", fullpath); + } else { + if (dry_run) { + printf("Would remove SELinux label of %s\n", fullpath); + } else { + if (verbose) + printf("Removing label of %s\n", fullpath); + + rc = lremovexattr(fullpath, XATTR_NAME_SELINUX); + if (rc < 0) + fprintf(stderr, "Failed to remove SELinux label of %s: %m\n", fullpath); + } + } + + if (!recursive) + return; + + fd = openat(atfd, path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); + if (fd < 0) { + if (errno != ENOTDIR) + fprintf(stderr, "Failed to open %s: %m\n", fullpath); + return; + } + + if (root_dev != (dev_t)-1) { + struct stat sb; + + rc = fstat(fd, &sb); + if (rc == -1) { + fprintf(stderr, "Failed to stat directory %s: %m\n", fullpath); + close(fd); + return; + } + + if (sb.st_dev != root_dev) { + if (verbose) + printf("Skipping directory %s due to filesystem boundary\n", fullpath); + + close(fd); + return; + } + } + + dir = fdopendir(fd); + if (!dir) { + fprintf(stderr, "Failed to open directory %s: %m\n", fullpath); + close(fd); + return; + } + + while (true) { + const struct dirent *entry; + char *nextfullpath; + + errno = 0; + entry = readdir(dir); + if (!entry) { + if (errno) + fprintf(stderr, "Failed to iterate directory %s: %m\n", fullpath); + break; + } + + if (entry->d_name[0] == '.' && (entry->d_name[1] == '\0' || (entry->d_name[1] == '.' && entry->d_name[2] == '\0'))) + continue; + + rc = asprintf(&nextfullpath, "%s/%s", strcmp(fullpath, "/") == 0 ? "" : fullpath, entry->d_name); + if (rc < 0) { + fprintf(stderr, "Out of memory!\n"); + closedir(dir); + return; + } + + unset(dirfd(dir), entry->d_name, nextfullpath, dry_run, recursive, verbose, root_dev); + + free(nextfullpath); + } + + closedir(dir); +} + + +int main(int argc, char *argv[]) +{ + bool dry_run = false, recursive = false, verbose = false, same_dev = false; + int c; + + while ((c = getopt(argc, argv, "hnrvx")) != -1) { + switch (c) { + case 'h': + usage(argv[0]); + return EXIT_SUCCESS; + case 'n': + dry_run = true; + break; + case 'r': + recursive = true; + break; + case 'v': + verbose = true; + break; + case 'x': + same_dev = true; + break; + default: + usage(argv[0]); + return EXIT_FAILURE; + } + } + + if (optind >= argc) { + usage(argv[0]); + return EXIT_FAILURE; + } + + if (is_selinux_enabled()) { + fprintf(stderr, "Removing SELinux attributes on a SELinux enabled system is not supported!\n"); + return EXIT_FAILURE; + } + + for (int index = optind; index < argc; index++) { + dev_t root_dev = (dev_t)-1; + + if (same_dev) { + struct stat sb; + int rc; + + rc = stat(argv[index], &sb); + if (rc == -1) { + fprintf(stderr, "Failed to stat %s: %m\n", argv[index]); + continue; + } + + root_dev = sb.st_dev; + } + unset(AT_FDCWD, argv[index], argv[index], dry_run, recursive, verbose, root_dev); + } + + return EXIT_SUCCESS; +} diff --git a/scripts/oss-fuzz.sh b/scripts/oss-fuzz.sh index 069f130aed..9376b6e42d 100755 --- a/scripts/oss-fuzz.sh +++ b/scripts/oss-fuzz.sh @@ -44,10 +44,13 @@ export LIB_FUZZING_ENGINE=${LIB_FUZZING_ENGINE:--fsanitize=fuzzer} rm -rf "$DESTDIR" make -C libsepol clean +make -C libselinux clean # LIBSO and LIBMAP shouldn't be expanded here because their values are unknown until Makefile # has been read by make # shellcheck disable=SC2016 make -C libsepol V=1 LD_SONAME_FLAGS='-soname,$(LIBSO),--version-script=$(LIBMAP)' -j"$(nproc)" install +# shellcheck disable=SC2016 +make -C libselinux V=1 LD_SONAME_FLAGS='-soname,$(LIBSO),--version-script=libselinux.map' -j"$(nproc)" install ## secilc fuzzer ## @@ -84,3 +87,25 @@ $CXX $CXXFLAGS $LIB_FUZZING_ENGINE checkpolicy-fuzzer.o checkpolicy/*.o "$DESTDI zip -j "$OUT/checkpolicy-fuzzer_seed_corpus.zip" checkpolicy/fuzz/min_pol.mls.conf cp checkpolicy/fuzz/checkpolicy-fuzzer.dict "$OUT/" + +## selabel-file text fcontext based fuzzer ## + +# CFLAGS, CXXFLAGS and LIB_FUZZING_ENGINE have to be split to be accepted by +# the compiler/linker so they shouldn't be quoted +# shellcheck disable=SC2086 +$CC $CFLAGS -DUSE_PCRE2 -DPCRE2_CODE_UNIT_WIDTH=8 -c -o selabel_file_text-fuzzer.o libselinux/fuzz/selabel_file_text-fuzzer.c +# shellcheck disable=SC2086 +$CXX $CXXFLAGS $LIB_FUZZING_ENGINE selabel_file_text-fuzzer.o "$DESTDIR/usr/lib/libselinux.a" -lpcre2-8 -o "$OUT/selabel_file_text-fuzzer" + +zip -j "$OUT/selabel_file_text-fuzzer_seed_corpus.zip" libselinux/fuzz/input + +## selabel-file compiled fcontext based fuzzer ## + +# CFLAGS, CXXFLAGS and LIB_FUZZING_ENGINE have to be split to be accepted by +# the compiler/linker so they shouldn't be quoted +# shellcheck disable=SC2086 +$CC $CFLAGS -DUSE_PCRE2 -DPCRE2_CODE_UNIT_WIDTH=8 -c -o selabel_file_compiled-fuzzer.o libselinux/fuzz/selabel_file_compiled-fuzzer.c +# shellcheck disable=SC2086 +$CXX $CXXFLAGS $LIB_FUZZING_ENGINE selabel_file_compiled-fuzzer.o "$DESTDIR/usr/lib/libselinux.a" -lpcre2-8 -o "$OUT/selabel_file_compiled-fuzzer" + +zip -j "$OUT/selabel_file_compiled-fuzzer_seed_corpus.zip" libselinux/fuzz/input