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 <cgzones@googlemail.com>
Acked-by: James Carter <jwcart2@gmail.com>
This commit is contained in:
Christian Göttsche 2024-01-22 14:54:53 +01:00 committed by James Carter
parent 90db06c524
commit 595c4163f0
9 changed files with 540 additions and 0 deletions

View File

@ -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

View File

@ -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;
}

View File

@ -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"

View File

@ -0,0 +1,60 @@
class process
class blk_file
class chr_file
class dir
class fifo_file
class file
class lnk_file
class sock_file
sid kernel
sid security
sid unlabeled
sid fs
sid file
sid file_labels
sid init
sid any_socket
sid port
sid netif
sid netmsg
sid node
sid igmp_packet
sid icmp_socket
sid tcp_socket
sid sysctl_modprobe
sid sysctl
sid sysctl_fs
sid sysctl_kernel
sid sysctl_net
sid sysctl_net_unix
sid sysctl_vm
sid sysctl_dev
sid kmod
sid policy
sid scmp_packet
sid devnull
class process { dyntransition transition }
default_role { blk_file } source;
default_role { chr_file } source;
default_role { dir } source;
default_role { fifo_file } source;
default_role { file } source;
default_role { lnk_file } source;
default_role { sock_file } source;
type sys_isid;
typealias sys_isid alias { dpkg_script_t rpm_script_t };
allow sys_isid self : process { dyntransition transition };
role sys_role;
role sys_role types { sys_isid };
user sys_user roles sys_role;
sid kernel sys_user:sys_role:sys_isid
sid security sys_user:sys_role:sys_isid
sid unlabeled sys_user:sys_role:sys_isid
sid file sys_user:sys_role:sys_isid
sid port sys_user:sys_role:sys_isid
sid netif sys_user:sys_role:sys_isid
sid netmsg sys_user:sys_role:sys_isid
sid node sys_user:sys_role:sys_isid
sid devnull sys_user:sys_role:sys_isid
fs_use_trans devpts sys_user:sys_role:sys_isid;
fs_use_trans devtmpfs sys_user:sys_role:sys_isid;

View File

@ -0,0 +1,65 @@
class process
class blk_file
class chr_file
class dir
class fifo_file
class file
class lnk_file
class sock_file
sid kernel
sid security
sid unlabeled
sid fs
sid file
sid file_labels
sid init
sid any_socket
sid port
sid netif
sid netmsg
sid node
sid igmp_packet
sid icmp_socket
sid tcp_socket
sid sysctl_modprobe
sid sysctl
sid sysctl_fs
sid sysctl_kernel
sid sysctl_net
sid sysctl_net_unix
sid sysctl_vm
sid sysctl_dev
sid kmod
sid policy
sid scmp_packet
sid devnull
class process { dyntransition transition }
default_role { blk_file } source;
default_role { chr_file } source;
default_role { dir } source;
default_role { fifo_file } source;
default_role { file } source;
default_role { lnk_file } source;
default_role { sock_file } source;
sensitivity s0;
dominance { s0 }
category c0;
level s0:c0;
mlsconstrain process transition t1 eq t2;
type sys_isid;
typealias sys_isid alias { dpkg_script_t rpm_script_t };
allow sys_isid self : process { dyntransition transition };
role sys_role;
role sys_role types { sys_isid };
user sys_user roles sys_role level s0 range s0 - s0:c0;
sid kernel sys_user:sys_role:sys_isid:s0
sid security sys_user:sys_role:sys_isid:s0
sid unlabeled sys_user:sys_role:sys_isid:s0
sid file sys_user:sys_role:sys_isid:s0
sid port sys_user:sys_role:sys_isid:s0
sid netif sys_user:sys_role:sys_isid:s0
sid netmsg sys_user:sys_role:sys_isid:s0
sid node sys_user:sys_role:sys_isid:s0
sid devnull sys_user:sys_role:sys_isid:s0
fs_use_trans devpts sys_user:sys_role:sys_isid:s0;
fs_use_trans devtmpfs sys_user:sys_role:sys_isid:s0;

View File

@ -1492,3 +1492,14 @@ static void pop_stack(void)
free(stack_top);
stack_top = parent;
}
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
void module_compiler_reset(void)
{
while (stack_top)
pop_stack();
last_block = NULL;
next_decl_id = 1;
}
#endif

View File

@ -106,4 +106,8 @@ int begin_optional_else(int pass);
* return -1. */
int end_avrule_block(int pass);
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
void module_compiler_reset(void);
#endif
#endif

View File

@ -312,6 +312,7 @@ GLBLUB { return(GLBLUB); }
%%
int yyerror(const char *msg)
{
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if (source_file[0])
fprintf(stderr, "%s:%lu:",
source_file, source_lineno);
@ -322,6 +323,10 @@ int yyerror(const char *msg)
yytext,
policydb_lineno,
linebuf[0], linebuf[1]);
#else
(void)msg;
#endif
policydb_errors++;
return -1;
}
@ -331,6 +336,7 @@ int yywarn(const char *msg)
if (werror)
return yyerror(msg);
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if (source_file[0])
fprintf(stderr, "%s:%lu:",
source_file, source_lineno);
@ -341,6 +347,8 @@ int yywarn(const char *msg)
yytext,
policydb_lineno,
linebuf[0], linebuf[1]);
#endif
return 0;
}

View File

@ -70,3 +70,17 @@ $CC $CFLAGS -c -o binpolicy-fuzzer.o libsepol/fuzz/binpolicy-fuzzer.c
$CXX $CXXFLAGS $LIB_FUZZING_ENGINE binpolicy-fuzzer.o "$DESTDIR/usr/lib/libsepol.a" -o "$OUT/binpolicy-fuzzer"
zip -j "$OUT/binpolicy-fuzzer_seed_corpus.zip" libsepol/fuzz/policy.bin
## checkpolicy fuzzer ##
make -C checkpolicy clean
make -C checkpolicy V=1 -j"$(nproc)" checkobjects
# CFLAGS, CXXFLAGS and LIB_FUZZING_ENGINE have to be split to be accepted by
# the compiler/linker so they shouldn't be quoted
# shellcheck disable=SC2086
$CC $CFLAGS -Icheckpolicy/ -c -o checkpolicy-fuzzer.o checkpolicy/fuzz/checkpolicy-fuzzer.c
# shellcheck disable=SC2086
$CXX $CXXFLAGS $LIB_FUZZING_ENGINE checkpolicy-fuzzer.o checkpolicy/*.o "$DESTDIR/usr/lib/libsepol.a" -o "$OUT/checkpolicy-fuzzer"
zip -j "$OUT/checkpolicy-fuzzer_seed_corpus.zip" checkpolicy/fuzz/min_pol.mls.conf
cp checkpolicy/fuzz/checkpolicy-fuzzer.dict "$OUT/"