Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

checkpolicy/oss-fuzz: add libfuzz based fuzzer #313

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Next Next commit
checkpolicy: add libfuzz based fuzzer
Introduce a libfuzz[1] based fuzzer testing the parsing and policy
generation code used within checkpolicy(8) and checkmodule(8), similar
to the fuzzer for secilc(8).
The fuzzer will work on generated source policy input and try to parse,
link, expand, optimize, sort and output it.
This fuzzer will also ensure policy validation is not too strict by
checking compilable source policies are valid.

Build the fuzzer in the oss-fuzz script.

[1]: https://llvm.org/docs/LibFuzzer.html

Signed-off-by: Christian Göttsche <[email protected]>
  • Loading branch information
cgzones committed Jan 22, 2024
commit 8aed8807f7c1ac974c016f8b424baffaef50fddb
3 changes: 3 additions & 0 deletions checkpolicy/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ lex.yy.c: policy_scan.l y.tab.c
test: checkpolicy
./tests/test_roundtrip.sh

# helper target for fuzzing
checkobjects: $(CHECKOBJS)

install: all
-mkdir -p $(DESTDIR)$(BINDIR)
-mkdir -p $(DESTDIR)$(MANDIR)/man8
Expand Down
274 changes: 274 additions & 0 deletions checkpolicy/fuzz/checkpolicy-fuzzer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
#include <assert.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);


// 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);

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 check_level(hashtab_key_t key, hashtab_datum_t datum, void *arg __attribute__ ((unused)))
{
const level_datum_t *levdatum = (level_datum_t *) datum;

// TODO: drop member defined if proven to be always set
if (!levdatum->isalias && !levdatum->defined) {
fprintf(stderr,
"Error: sensitivity %s was not used in a level definition!\n",
key);
abort();
}

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, policyvers;

sepol_debug(VERBOSE);

/* Take the first byte whether to parse as MLS policy
* and the second byte as policy version. */
if (size < 2)
return 0;
switch (data[0]) {
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[1] - 'A';
if (policyvers < POLICYDB_VERSION_MIN || policyvers > POLICYDB_VERSION_MAX)
return 0;
data += 2;
size -= 2;

if (policydb_init(&parsepolicydb))
goto exit;

parsepolicydb.policy_type = POLICY_BASE;
parsepolicydb.mls = mls;
parsepolicydb.handle_unknown = DENY_UNKNOWN;
policydb_set_target_platform(&parsepolicydb, SEPOL_TARGET_SELINUX);

if (read_source_policy(&parsepolicydb, data, size))
goto exit;

(void) hashtab_map(parsepolicydb.p_levels.table, check_level, NULL);

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);

finalpolicydb = &kernpolicydb;
} else {
assert(parsepolicydb.policy_type == POLICY_MOD);
assert(parsepolicydb.handle_unknown == SEPOL_DENY_UNKNOWN);
assert(parsepolicydb.mls == mls);

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;
}
101 changes: 101 additions & 0 deletions checkpolicy/fuzz/checkpolicy-fuzzer.dict
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Keyword dictionary

"clone"
"common"
"class"
"constrain"
"validatetrans"
"inherits"
"sid"
"role"
"roles"
"roleattribute"
"attribute_role"
"types"
"typealias"
"typeattribute"
"typebounds"
"type"
"bool"
"tunable"
"if"
"else"
"alias"
"attribute"
"expandattribute"
"type_transition"
"type_member"
"type_change"
"role_transition"
"range_transition"
"sensitivity"
"dominance"
"category"
"level"
"range"
"mlsconstrain"
"mlsvalidatetrans"
"user"
"neverallow"
"allow"
"auditallow"
"auditdeny"
"dontaudit"
"allowxperm"
"auditallowxperm"
"dontauditxperm"
"neverallowxperm"
"source"
"target"
"sameuser"
"module"
"require"
"optional"
"or"
"and"
"not"
"xor"
"eq"
"true"
"false"
"dom"
"domby"
"incomp"
"fscon"
"ibpkeycon"
"ibendportcon"
"portcon"
"netifcon"
"nodecon"
"pirqcon"
"iomemcon"
"ioportcon"
"pcidevicecon"
"devicetreecon"
"fs_use_xattr"
"fs_use_task"
"fs_use_trans"
"genfscon"
"r1"
"r2"
"r3"
"u1"
"u2"
"u3"
"t1"
"t2"
"t3"
"l1"
"l2"
"h1"
"h2"
"policycap"
"permissive"
"default_user"
"default_role"
"default_type"
"default_range"
"low-high"
"high"
"low"
"glblub"