diff --git a/Makefile.am b/Makefile.am index 12228869..35d2e76a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -356,6 +356,7 @@ EXTRA_DIST = \ test/tests/rijndael_backwards_compatibility.pl \ test/tests/os_compatibility.pl \ test/tests/fault_injection.pl \ + test/tests/afl_fuzzing.pl \ test/tests/gpg_hmac.pl \ test/tests/gpg_no_pw.pl \ test/tests/gpg_no_pw_hmac.pl \ diff --git a/configure.ac b/configure.ac index 3a024b8b..b4276e91 100644 --- a/configure.ac +++ b/configure.ac @@ -167,6 +167,21 @@ if test "x$want_fuzzing_interfaces" = "xyes"; then AC_DEFINE([FUZZING_INTERFACES], [1], [Define for fuzzing interfaces support]) fi +dnl Decide whether or not to compile in support for the 'American Fuzzy Lop' +dnl fuzzer from Michal Zalewski - this is for testing purposes only +dnl +want_afl_fuzzing_support=no +AC_ARG_ENABLE([afl-fuzzing], + [AS_HELP_STRING([--enable-afl-fuzzing], + [Build fwknop binaries with support for the American Fuzzy Lop fuzzer @<:@default is to disable@:>@])], + [want_afl_fuzzing_support=$enableval], + []) + +if test "x$want_afl_fuzzing_support" = "xyes"; then + AC_DEFINE([AFL_FUZZING], [1], [Define for AFL fuzzing support]) + AC_DEFINE([FUZZING_INTERFACES], [1], [Define for fuzzing interfaces support]) +fi + dnl Decide whether or not to enable UDP server mode (no libpcap dependency) dnl want_udp_server=no diff --git a/lib/base64.c b/lib/base64.c index b8423c25..5b511215 100644 --- a/lib/base64.c +++ b/lib/base64.c @@ -36,6 +36,7 @@ #include "base64.h" #include "fko_common.h" +#if !AFL_FUZZING static unsigned char map2[] = { 0x3e, 0xff, 0xff, 0xff, 0x3f, 0x34, 0x35, 0x36, @@ -49,13 +50,24 @@ static unsigned char map2[] = 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33 }; +#endif int b64_decode(const char *in, unsigned char *out) { - int i, v; + int i; unsigned char *dst = out; +#if ! AFL_FUZZING + int v; +#endif +#if AFL_FUZZING + /* short circuit base64 decoding in AFL fuzzing mode - just copy + * data as-is. + */ + for (i = 0; in[i]; i++) + *dst++ = in[i]; +#else v = 0; for (i = 0; in[i] && in[i] != '='; i++) { unsigned int index= in[i]-43; @@ -68,6 +80,7 @@ b64_decode(const char *in, unsigned char *out) if (i & 3) *dst++ = v >> (6 - 2 * (i & 3)); } +#endif *dst = '\0'; diff --git a/lib/fko_decode.c b/lib/fko_decode.c index 778e1e35..bd88d505 100644 --- a/lib/fko_decode.c +++ b/lib/fko_decode.c @@ -75,6 +75,10 @@ last_field(char *str) static int verify_digest(char *tbuf, int t_size, fko_ctx_t ctx) { +#if AFL_FUZZING + return FKO_SUCCESS; +#endif + switch(ctx->digest_type) { case FKO_DIGEST_MD5: @@ -113,6 +117,12 @@ verify_digest(char *tbuf, int t_size, fko_ctx_t ctx) static int is_valid_digest_len(int t_size, fko_ctx_t ctx) { +#if AFL_FUZZING + ctx->digest_type = FKO_DIGEST_SHA256; + ctx->digest_len = t_size; + return FKO_SUCCESS; +#endif + switch(t_size) { case MD5_B64_LEN: diff --git a/server/cmd_opts.h b/server/cmd_opts.h index f4cd7833..2db2899e 100644 --- a/server/cmd_opts.h +++ b/server/cmd_opts.h @@ -153,13 +153,14 @@ enum { /* Our getopt_long options string. */ -#define GETOPTS_OPTION_STRING "a:c:C:d:Dfhi:Kl:O:p:P:RStvV" +#define GETOPTS_OPTION_STRING "Aa:c:C:d:Dfhi:Kl:O:p:P:RStvV" /* Our program command-line options... */ static struct option cmd_opts[] = { {"access-file", 1, NULL, 'a'}, + {"afl-fuzzing", 0, NULL, 'A'}, {"config-file", 1, NULL, 'c'}, {"packet-limit", 1, NULL, 'C'}, {"digest-file", 1, NULL, 'd'}, diff --git a/server/config_init.c b/server/config_init.c index 55cb78f7..a0523d24 100644 --- a/server/config_init.c +++ b/server/config_init.c @@ -1031,6 +1031,14 @@ config_init(fko_srv_options_t *opts, int argc, char **argv) GETOPTS_OPTION_STRING, cmd_opts, &index)) != -1) { switch(cmd_arg) { + case 'A': +#if AFL_FUZZING + opts->afl_fuzzing = 1; +#else + log_msg(LOG_ERR, "[*] fwknopd not compiled with AFL fuzzing support"); + clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE); +#endif + break; case 'a': set_config_entry(opts, CONF_ACCESS_FILE, optarg); break; diff --git a/server/fwknopd.c b/server/fwknopd.c index 1aea657f..d9dbddb9 100644 --- a/server/fwknopd.c +++ b/server/fwknopd.c @@ -58,11 +58,19 @@ static void setup_pid(fko_srv_options_t *opts); static void init_digest_cache(fko_srv_options_t *opts); static void set_locale(fko_srv_options_t *opts); static pid_t get_running_pid(const fko_srv_options_t *opts); +#if AFL_FUZZING +static void afl_pkt_from_stdin(fko_srv_options_t *opts); +#endif #if HAVE_LIBFIU static void enable_fault_injections(fko_srv_options_t * const opts); #endif +#if AFL_FUZZING +#define AFL_MAX_PKT_SIZE 1024 +#define AFL_DUMP_CTX_SIZE 4096 +#endif + int main(int argc, char **argv) { @@ -114,7 +122,8 @@ main(int argc, char **argv) /* Make sure we have a valid run dir and path leading to digest file * in case it configured to be somewhere other than the run dir. */ - if(! check_dir_path((const char *)opts.config[CONF_FWKNOP_RUN_DIR], "Run", 0)) + if(!opts.afl_fuzzing + && ! check_dir_path((const char *)opts.config[CONF_FWKNOP_RUN_DIR], "Run", 0)) clean_exit(&opts, NO_FW_CLEANUP, EXIT_FAILURE); /* Initialize the firewall rules handler based on the fwknopd.conf @@ -175,6 +184,12 @@ main(int argc, char **argv) clean_exit(&opts, NO_FW_CLEANUP, EXIT_SUCCESS); } +#if AFL_FUZZING + /* SPA data from STDIN. */ + if(opts.afl_fuzzing) + afl_pkt_from_stdin(&opts); +#endif + /* Prepare the firewall - i.e. flush any old rules and (for iptables) * create fwknop chains. */ @@ -274,10 +289,69 @@ static void set_locale(fko_srv_options_t *opts) return; } +#if AFL_FUZZING +static void afl_pkt_from_stdin(fko_srv_options_t *opts) +{ + FILE *fp = NULL; + fko_ctx_t decode_ctx = NULL; + unsigned char spa_pkt[AFL_MAX_PKT_SIZE] = {0}; + int res = 0, es = EXIT_SUCCESS; + char dump_buf[AFL_DUMP_CTX_SIZE]; + + fp = fdopen(STDIN_FILENO, "r"); + if(fp != NULL) + { + if(fgets((char *)spa_pkt, AFL_MAX_PKT_SIZE, fp) == NULL) + { + fclose(fp); + clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE); + } + + fclose(fp); + + fko_new(&decode_ctx); + + res = fko_set_encoded_data(decode_ctx, (char *) spa_pkt, + strlen((char *)spa_pkt), 0, FKO_DIGEST_SHA256); + + if(res == FKO_SUCCESS) + res = fko_set_spa_data(decode_ctx, (const char *) spa_pkt); + if(res == FKO_SUCCESS) + res = fko_decode_spa_data(decode_ctx); + if(res == FKO_SUCCESS) + res = dump_ctx_to_buffer(decode_ctx, dump_buf, sizeof(dump_buf)); + if(res == FKO_SUCCESS) + log_msg(LOG_INFO, "%s", dump_buf); + + fko_destroy(decode_ctx); + + if(res == FKO_SUCCESS) + { + log_msg(LOG_INFO, "SPA packet decode success: %s", fko_errstr(res)); + es = EXIT_SUCCESS; + } + else + { + log_msg(LOG_ERR, "Could not decode SPA packet: %s", fko_errstr(res)); + es = EXIT_FAILURE; + } + } + else + log_msg(LOG_ERR, "Could not acquire SPA packet from stdin."); + + clean_exit(opts, NO_FW_CLEANUP, es); +} +#endif + static void init_digest_cache(fko_srv_options_t *opts) { int rp_cache_count; +#if AFL_FUZZING + if(opts->afl_fuzzing) + return; +#endif + if(strncasecmp(opts->config[CONF_ENABLE_DIGEST_PERSISTENCE], "Y", 1) == 0) { rp_cache_count = replay_cache_init(opts); @@ -311,6 +385,11 @@ static void setup_pid(fko_srv_options_t *opts) { pid_t old_pid; +#if AFL_FUZZING + if(opts->afl_fuzzing) + return; +#endif + /* If we are a new process (just being started), proceed with normal * start-up. Otherwise, we are here as a result of a signal sent to an * existing process and we want to restart. diff --git a/server/fwknopd_common.h b/server/fwknopd_common.h index 2353728e..f06b6e15 100644 --- a/server/fwknopd_common.h +++ b/server/fwknopd_common.h @@ -577,6 +577,7 @@ typedef struct fko_srv_options unsigned char fw_list_all; /* List all current firewall rules */ unsigned char fw_flush; /* Flush current firewall rules */ unsigned char test; /* Test mode flag */ + unsigned char afl_fuzzing; /* SPA pkts from stdin for AFL fuzzing */ unsigned char verbose; /* Verbose mode flag */ unsigned char exit_after_parse_config; /* Parse config and exit */ unsigned char enable_udp_server; /* Enable UDP server mode */ diff --git a/test/afl/README b/test/afl/README new file mode 100644 index 00000000..ce15db85 --- /dev/null +++ b/test/afl/README @@ -0,0 +1,50 @@ + +The fwknop project supports various fuzzing strategies, and one of the most +important is usage of the 'American Fuzzy Lop' (AFL) fuzzer written by Michal +Zalewski (see: https://code.google.com/p/american-fuzzy-lop/). Because AFL is +not designed to handle encryption schemes (see the README included in the AFL +sources for more information on this), a special --enable-afl-fuzzing command +line switch is available to the fwknop autoconf configure script. This argument +allows encryption and base64 encoding to be bypassed when feeding SPA packet +data to fwknopd via stdin. It is the feature that enables AFL fuzzing. + +This directory contains enabling scripts in order to make it easy to fuzz +fwknop with AFL. It is assumed that AFL is installed and in your path. The +files are in this directory are organized as follows: + +afl-compile.sh - Script to compile fwknop underneath afl-gcc. +afl-fuzzing.in - AFL input directory which contains input test cases. +afl-fuzzing.out - AFL output directory. +afl-run.sh - Script to invoke AFL with proper command line arguments to + fwknopd for fuzzing enablement. + +Here is an example of what fwknopd produces when compiled for AFL support when +a dummy SPA packet is provided in non-encoded/encrypted form via fwknopd's +stdin: + +$ ./fwknopd-stdin-test.sh ++ SPA_PKT=1716411011200157:root:1397329899:2.0.1:1:127.0.0.2,tcp/22:AAAAA ++ LD_LIBRARY_PATH=../../lib/.libs ../../server/.libs/fwknopd -c ../conf/default_fwknopd.conf -a ../conf/default_access.conf -A -f -t ++ echo -n 1716411011200157:root:1397329899:2.0.1:1:127.0.0.2,tcp/22:AAAAA +Warning: REQUIRE_SOURCE_ADDRESS not enabled for access stanza source: 'ANY' +SPA Field Values: +================= + Random Value: 1716411011200157 + Username: root + Timestamp: 1397329899 + FKO Version: 2.0.1 + Message Type: 1 (Access msg) + Message String: 127.0.0.2,tcp/22 + Nat Access: + Server Auth: + Client Timeout: 0 + Digest Type: 3 (SHA256) + HMAC Type: 0 (None) +Encryption Type: 1 (Rijndael) +Encryption Mode: 2 (CBC) + Encoded Data: 1716411011200157:root:1397329899:2.0.1:1:127.0.0.2,tcp/22 +SPA Data Digest: AAAAA + HMAC: + Final SPA Data: 200157:root:1397329899:2.0.1:1:127.0.0.2,tcp/22:AAAAA + +SPA packet decode success: Success diff --git a/test/afl/afl-compile.sh b/test/afl/afl-compile.sh new file mode 100755 index 00000000..b0e3230c --- /dev/null +++ b/test/afl/afl-compile.sh @@ -0,0 +1,8 @@ +#!/bin/sh -x + +cd ../../ +CC=afl-gcc ./extras/apparmor/configure_args.sh --enable-afl-fuzzing +make clean +make +cd test/afl +exit diff --git a/test/afl/afl-fuzzing.in/spa.start b/test/afl/afl-fuzzing.in/spa.start new file mode 100644 index 00000000..153d2c1d --- /dev/null +++ b/test/afl/afl-fuzzing.in/spa.start @@ -0,0 +1 @@ +1716411011200157:root:1397329899:2.0.1:1:127.0.0.2,tcp/22:AAAAA \ No newline at end of file diff --git a/test/afl/afl-fuzzing.in/spa2.start b/test/afl/afl-fuzzing.in/spa2.start new file mode 100644 index 00000000..7b4f3a9d --- /dev/null +++ b/test/afl/afl-fuzzing.in/spa2.start @@ -0,0 +1 @@ +3145808919615481:root:1397329899:2.0.1:0:127.0.0.2,echo fwknoptest > /tmp/fwknoptest:AAAAA diff --git a/test/afl/afl-fuzzing.in/spa3.start b/test/afl/afl-fuzzing.in/spa3.start new file mode 100644 index 00000000..14322aed --- /dev/null +++ b/test/afl/afl-fuzzing.in/spa3.start @@ -0,0 +1 @@ +1716411011200157:root:1397329899:2.0.1:4:127.0.0.2,tcp/22:192.168.10.1,12345:1234:AAAAAA diff --git a/test/afl/afl-run.sh b/test/afl/afl-run.sh new file mode 100755 index 00000000..712e4ca0 --- /dev/null +++ b/test/afl/afl-run.sh @@ -0,0 +1,16 @@ +#!/bin/sh -x + +OLD_DIR=afl-out-archive +OUT_DIR=afl-fuzzing.out + +[ ! -d $OLD_DIR ] && mkdir $OLD_DIR +TS=`date +"%m%d%y%H%M%S"` +[ -d $OUT_DIR ] && mv $OUT_DIR "$OLD_DIR/$OUT_DIR-$TS" +mkdir $OUT_DIR + +### make sure that a basic SPA packet to stdin in fwknopd -A mode works +./fwknopd-stdin-test.sh || exit + +LD_LIBRARY_PATH=../../lib/.libs afl-fuzz -i afl-fuzzing.in -o $OUT_DIR ../../server/.libs/fwknopd -c ../conf/default_fwknopd.conf -a ../conf/default_access.conf -A -f -t + +exit diff --git a/test/afl/fwknopd-stdin-test.sh b/test/afl/fwknopd-stdin-test.sh new file mode 100755 index 00000000..06b46694 --- /dev/null +++ b/test/afl/fwknopd-stdin-test.sh @@ -0,0 +1,6 @@ +#!/bin/sh -x + +SPA_PKT="1716411011200157:root:1397329899:2.0.1:1:127.0.0.2,tcp/22:AAAAA" +#SPA_PKT="1716411011200157:root:1397329899:2.0.1:1:127.0.0.2,tcp/22:A" + +echo -n $SPA_PKT | LD_LIBRARY_PATH=../../lib/.libs ../../server/.libs/fwknopd -c ../conf/default_fwknopd.conf -a ../conf/default_access.conf -A -f -t diff --git a/test/test-fwknop.pl b/test/test-fwknop.pl index 82b506c8..df04c4f5 100755 --- a/test/test-fwknop.pl +++ b/test/test-fwknop.pl @@ -103,6 +103,7 @@ my @test_files = ( "$tests_dir/rijndael_hmac.pl", "$tests_dir/rijndael_hmac_fuzzing.pl", "$tests_dir/fault_injection.pl", + "$tests_dir/afl_fuzzing.pl", "$tests_dir/os_compatibility.pl", "$tests_dir/perl_FKO_module.pl", "$tests_dir/python_fko.pl", @@ -128,6 +129,7 @@ our @rijndael_hmac = (); ### from tests/rijndael_hmac.pl our @rijndael_fuzzing = (); ### from tests/rijndael_fuzzing.pl our @rijndael_hmac_fuzzing = (); ### from tests/rijndael_hmac_fuzzing.pl our @fault_injection = (); ### from tests/fault_injection.pl +our @afl_fuzzing = (); ### from tests/alf_fuzzing.pl our @gpg_no_pw = (); ### from tests/gpg_now_pw.pl our @gpg_no_pw_hmac = (); ### from tests/gpg_now_pw_hmac.pl our @gpg = (); ### from tests/gpg.pl @@ -733,6 +735,7 @@ my @tests = ( @rijndael_hmac, @rijndael_hmac_fuzzing, @fault_injection, + @afl_fuzzing, @os_compatibility, @perl_FKO_module, @python_fko, diff --git a/test/tests/afl_fuzzing.pl b/test/tests/afl_fuzzing.pl new file mode 100644 index 00000000..e0405cd7 --- /dev/null +++ b/test/tests/afl_fuzzing.pl @@ -0,0 +1,13 @@ +@afl_fuzzing = ( + ### run fwknopd with the -A arg for American Fuzzy Lop support regardless + ### of whether AFL support has been compiled in - if not, an error is + ### thrown and is therefore good for code coverage + { + 'category' => 'AFL', + 'subcategory' => 'FUZZING', + 'detail' => 'pkt to stdin', + 'function' => \&generic_exec, + 'cmdline' => 'echo -n "1716411011200157:root:1397329899:2.0.1:1:127.0.0.2,tcp/22:AAAAA" | ' . + "$lib_view_str $valgrind_str $fwknopdCmd $default_server_conf_args -A -f -v", + }, +);