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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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/"