On Mon, Jan 22, 2024 at 9:37 AM Christian Göttsche <cgzones@xxxxxxxxxxxxxx> wrote: > > 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@xxxxxxxxxxxxxx> Acked-by: James Carter <jwcart2@xxxxxxxxx> > --- > checkpolicy/Makefile | 3 + > checkpolicy/fuzz/checkpolicy-fuzzer.c | 274 +++++++++++++++++++++++ > checkpolicy/fuzz/checkpolicy-fuzzer.dict | 101 +++++++++ > checkpolicy/fuzz/min_pol.conf | 60 +++++ > checkpolicy/fuzz/min_pol.mls.conf | 65 ++++++ > checkpolicy/module_compiler.c | 11 + > checkpolicy/module_compiler.h | 4 + > checkpolicy/policy_scan.l | 8 + > scripts/oss-fuzz.sh | 14 ++ > 9 files changed, 540 insertions(+) > create mode 100644 checkpolicy/fuzz/checkpolicy-fuzzer.c > create mode 100644 checkpolicy/fuzz/checkpolicy-fuzzer.dict > create mode 100644 checkpolicy/fuzz/min_pol.conf > create mode 100644 checkpolicy/fuzz/min_pol.mls.conf > > diff --git a/checkpolicy/Makefile b/checkpolicy/Makefile > index 036ab905..6e8008e3 100644 > --- a/checkpolicy/Makefile > +++ b/checkpolicy/Makefile > @@ -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 > diff --git a/checkpolicy/fuzz/checkpolicy-fuzzer.c b/checkpolicy/fuzz/checkpolicy-fuzzer.c > new file mode 100644 > index 00000000..0d749a02 > --- /dev/null > +++ b/checkpolicy/fuzz/checkpolicy-fuzzer.c > @@ -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; > +} > diff --git a/checkpolicy/fuzz/checkpolicy-fuzzer.dict b/checkpolicy/fuzz/checkpolicy-fuzzer.dict > new file mode 100644 > index 00000000..fb7e66c0 > --- /dev/null > +++ b/checkpolicy/fuzz/checkpolicy-fuzzer.dict > @@ -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" > diff --git a/checkpolicy/fuzz/min_pol.conf b/checkpolicy/fuzz/min_pol.conf > new file mode 100644 > index 00000000..ff6d50e5 > --- /dev/null > +++ b/checkpolicy/fuzz/min_pol.conf > @@ -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; > diff --git a/checkpolicy/fuzz/min_pol.mls.conf b/checkpolicy/fuzz/min_pol.mls.conf > new file mode 100644 > index 00000000..6d15846b > --- /dev/null > +++ b/checkpolicy/fuzz/min_pol.mls.conf > @@ -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; > diff --git a/checkpolicy/module_compiler.c b/checkpolicy/module_compiler.c > index 3188af89..74a9f93c 100644 > --- a/checkpolicy/module_compiler.c > +++ b/checkpolicy/module_compiler.c > @@ -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 > diff --git a/checkpolicy/module_compiler.h b/checkpolicy/module_compiler.h > index 29b824b4..e43bc6c0 100644 > --- a/checkpolicy/module_compiler.h > +++ b/checkpolicy/module_compiler.h > @@ -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 > diff --git a/checkpolicy/policy_scan.l b/checkpolicy/policy_scan.l > index c998ff8b..19c05a58 100644 > --- a/checkpolicy/policy_scan.l > +++ b/checkpolicy/policy_scan.l > @@ -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; > } > > diff --git a/scripts/oss-fuzz.sh b/scripts/oss-fuzz.sh > index 72d275e8..069f130a 100755 > --- a/scripts/oss-fuzz.sh > +++ b/scripts/oss-fuzz.sh > @@ -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/" > -- > 2.43.0 > >