mirror of
https://github.com/SELinuxProject/selinux
synced 2025-04-22 15:25:24 +00:00
Handle the case where either separated trailing input is empty or non-
existent by initializing the size to 0 and only call related code on
non-zero size.
Fixes: 8997f543
("libselinux: add selabel_file(5) fuzzer")
Reported-by: oss-fuzz (issue 388319478)
Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
297 lines
5.9 KiB
C
297 lines
5.9 KiB
C
#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 = 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, "<<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;
|
|
}
|