#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 = 0, fcontext_data3_len = 0, 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; if (fcontext_data2_len) { 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; if (fcontext_data3_len) { 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, 0); if (rc) { assert(errno != 0); goto cleanup; } fclose(fp); fp = NULL; if (fcontext_data2_len) { 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, 1); if (rc) { assert(errno != 0); goto cleanup; } fclose(fp); fp = NULL; } if (fcontext_data3_len) { 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, 2); if (rc) { assert(errno != 0); goto cleanup; } fclose(fp); fp = NULL; } sort_specs(&sdata); assert(cmp(&rec, &rec) == SELABEL_EQUAL); errno = 0; result = lookup_all(&rec, key, mode, partial, find_all, NULL); 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; }