On Tue, Feb 13, 2024 at 3:33 PM James Carter <jwcart2@xxxxxxxxx> wrote: > > 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> Merged. Thanks, Jim > > > --- > > 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 > > > >