Skip to content

Commit

Permalink
libselinux: add selabel_file(5) fuzzer
Browse files Browse the repository at this point in the history
Add two fuzzers reading and performing lookup on selabel_file(5)
databases.  One fuzzer takes input in form of a textual fcontext
definition, the other one takes compiled fcontexts definitions.  The
lookup key and whether to lookup any or a specific file type is also
part of the generated input.

CC: Evgeny Vereshchagin <evverx@gmail.com>
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
---
v2: add patch
  • Loading branch information
cgzones committed Nov 5, 2024
1 parent d5921fb commit a2f80a8
Show file tree
Hide file tree
Showing 6 changed files with 566 additions and 20 deletions.
Empty file added libselinux/fuzz/input
Empty file.
281 changes: 281 additions & 0 deletions libselinux/fuzz/selabel_file_compiled-fuzzer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>

#include <selinux/label.h>

#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, "<<none>>") != 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, "<<none>>") != 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;
}
Loading

0 comments on commit a2f80a8

Please sign in to comment.