Re: [PATCH 01/15] checkpolicy: add libfuzz based fuzzer

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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





[Index of Archives]     [Selinux Refpolicy]     [Linux SGX]     [Fedora Users]     [Fedora Desktop]     [Yosemite Photos]     [Yosemite Camping]     [Yosemite Campsites]     [KDE Users]     [Gnome Users]

  Powered by Linux