selinux/checkpolicy/fuzz/checkpolicy-fuzzer.c

283 lines
6.0 KiB
C

#include <assert.h>
#include <setjmp.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sepol/debug.h>
#include <sepol/kernel_to_cil.h>
#include <sepol/kernel_to_conf.h>
#include <sepol/module_to_cil.h>
#include <sepol/policydb/policydb.h>
#include <sepol/policydb/hierarchy.h>
#include <sepol/policydb/expand.h>
#include <sepol/policydb/link.h>
#include "module_compiler.h"
#include "queue.h"
extern int policydb_validate(sepol_handle_t *handle, const policydb_t *p);
extern int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
extern int mlspol;
extern policydb_t *policydbp;
extern queue_t id_queue;
extern unsigned int policydb_errors;
extern int yynerrs;
extern FILE *yyin;
extern void init_parser(int);
extern int yyparse(void);
extern void yyrestart(FILE *);
extern int yylex_destroy(void);
extern void set_source_file(const char *name);
jmp_buf fuzzing_pre_parse_stack_state;
// Set to 1 for verbose libsepol logging
#define VERBOSE 0
static ssize_t full_write(int fd, const void *buf, size_t count)
{
ssize_t written = 0;
while (count > 0) {
ssize_t ret = write(fd, buf, count);
if (ret < 0) {
if (errno == EINTR)
continue;
return ret;
}
if (ret == 0)
break;
written += ret;
buf = (const unsigned char *)buf + (size_t)ret;
count -= (size_t)ret;
}
return written;
}
static int read_source_policy(policydb_t *p, const uint8_t *data, size_t size)
{
int fd, rc;
ssize_t wr;
fd = memfd_create("fuzz-input", MFD_CLOEXEC);
if (fd < 0)
return -1;
wr = full_write(fd, data, size);
if (wr < 0 || (size_t)wr != size) {
close(fd);
return -1;
}
fsync(fd);
yynerrs = 0;
yyin = fdopen(fd, "r");
if (!yyin) {
close(fd);
return -1;
}
rewind(yyin);
set_source_file("fuzz-input");
id_queue = queue_create();
if (id_queue == NULL) {
fclose(yyin);
yylex_destroy();
return -1;
}
policydbp = p;
mlspol = p->mls;
init_parser(1);
if (setjmp(fuzzing_pre_parse_stack_state) != 0) {
queue_destroy(id_queue);
fclose(yyin);
yylex_destroy();
return -1;
}
rc = yyparse();
// TODO: drop global variable policydb_errors if proven to be redundant
assert(rc || !policydb_errors);
if (rc || policydb_errors) {
queue_destroy(id_queue);
fclose(yyin);
yylex_destroy();
return -1;
}
rewind(yyin);
init_parser(2);
set_source_file("fuzz-input");
yyrestart(yyin);
rc = yyparse();
assert(rc || !policydb_errors);
if (rc || policydb_errors) {
queue_destroy(id_queue);
fclose(yyin);
yylex_destroy();
return -1;
}
queue_destroy(id_queue);
fclose(yyin);
yylex_destroy();
return 0;
}
static int write_binary_policy(FILE *outfp, policydb_t *p)
{
struct policy_file pf;
policy_file_init(&pf);
pf.type = PF_USE_STDIO;
pf.fp = outfp;
return policydb_write(p, &pf);
}
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
policydb_t parsepolicydb = {};
policydb_t kernpolicydb = {};
policydb_t *finalpolicydb;
sidtab_t sidtab = {};
FILE *devnull = NULL;
int mls, platform, policyvers;
sepol_debug(VERBOSE);
/*
* Take the first byte whether to generate a SELinux or Xen policy,
* the second byte whether to parse as MLS policy,
* and the second byte as policy version.
*/
if (size < 3)
return 0;
switch (data[0]) {
case 'S':
platform = SEPOL_TARGET_SELINUX;
break;
case 'X':
platform = SEPOL_TARGET_XEN;
break;
default:
return 0;
}
switch (data[1]) {
case '0':
mls = 0;
break;
case '1':
mls = 1;
break;
default:
return 0;
}
static_assert(0x7F - 'A' >= POLICYDB_VERSION_MAX, "Max policy version should be representable");
policyvers = data[2] - 'A';
if (policyvers < POLICYDB_VERSION_MIN || policyvers > POLICYDB_VERSION_MAX)
return 0;
data += 3;
size -= 3;
if (policydb_init(&parsepolicydb))
goto exit;
parsepolicydb.policy_type = POLICY_BASE;
parsepolicydb.mls = mls;
parsepolicydb.handle_unknown = DENY_UNKNOWN;
parsepolicydb.policyvers = policyvers;
policydb_set_target_platform(&parsepolicydb, platform);
if (read_source_policy(&parsepolicydb, data, size))
goto exit;
if (parsepolicydb.policy_type == POLICY_BASE) {
if (link_modules(NULL, &parsepolicydb, NULL, 0, VERBOSE))
goto exit;
if (policydb_init(&kernpolicydb))
goto exit;
if (expand_module(NULL, &parsepolicydb, &kernpolicydb, VERBOSE, /*check_assertions=*/0))
goto exit;
(void) check_assertions(NULL, &kernpolicydb, kernpolicydb.global->branch_list->avrules);
(void) hierarchy_check_constraints(NULL, &kernpolicydb);
kernpolicydb.policyvers = policyvers;
assert(kernpolicydb.policy_type == POLICY_KERN);
assert(kernpolicydb.handle_unknown == SEPOL_DENY_UNKNOWN);
assert(kernpolicydb.mls == mls);
assert(kernpolicydb.target_platform == platform);
finalpolicydb = &kernpolicydb;
} else {
assert(parsepolicydb.policy_type == POLICY_MOD);
assert(parsepolicydb.handle_unknown == SEPOL_DENY_UNKNOWN);
assert(parsepolicydb.mls == mls);
assert(parsepolicydb.target_platform == platform);
finalpolicydb = &parsepolicydb;
}
if (policydb_load_isids(finalpolicydb, &sidtab))
goto exit;
if (finalpolicydb->policy_type == POLICY_KERN && policydb_optimize(finalpolicydb))
goto exit;
if (policydb_sort_ocontexts(finalpolicydb))
goto exit;
if (policydb_validate(NULL, finalpolicydb))
/* never generate an invalid policy */
abort();
devnull = fopen("/dev/null", "we");
if (devnull == NULL)
goto exit;
if (write_binary_policy(devnull, finalpolicydb))
abort();
if (finalpolicydb->policy_type == POLICY_KERN && sepol_kernel_policydb_to_conf(devnull, finalpolicydb))
abort();
if (finalpolicydb->policy_type == POLICY_KERN && sepol_kernel_policydb_to_cil(devnull, finalpolicydb))
abort();
if (finalpolicydb->policy_type == POLICY_MOD && sepol_module_policydb_to_cil(devnull, finalpolicydb, /*linked=*/0))
abort();
exit:
if (devnull != NULL)
fclose(devnull);
sepol_sidtab_destroy(&sidtab);
policydb_destroy(&kernpolicydb);
policydb_destroy(&parsepolicydb);
id_queue = NULL;
policydbp = NULL;
module_compiler_reset();
/* Non-zero return values are reserved for future use. */
return 0;
}