From fe1454bb7dbde99e4f8569ec339ff1f71852414f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= Date: Wed, 20 Sep 2017 14:02:07 +0200 Subject: [PATCH] Add better support for LLVM libFuzzer packet parser tests --- .gitignore | 1 + .gitmodules | 3 + Knot.files | 4 +- configure.ac | 18 ++-- tests-fuzz/.gitignore | 6 +- tests-fuzz/Makefile.am | 42 ++++------ tests-fuzz/README.md | 67 +-------------- tests-fuzz/main.c | 136 +++++++++++++++++++++++++++++++ tests-fuzz/packet.c | 38 --------- tests-fuzz/packet_libfuzzer.c | 12 +-- tests-fuzz/packet_libfuzzer.in | 1 + tests-fuzz/{ => wrap}/afl-loop.h | 0 12 files changed, 173 insertions(+), 155 deletions(-) create mode 100644 .gitmodules create mode 100644 tests-fuzz/main.c delete mode 100644 tests-fuzz/packet.c create mode 160000 tests-fuzz/packet_libfuzzer.in rename tests-fuzz/{ => wrap}/afl-loop.h (100%) diff --git a/.gitignore b/.gitignore index 818d5163e..0e3a66a41 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,7 @@ /src/libknot.pc /src/libknot/version.h /src/knot/modules/static_modules.h +/test-driver # dnstap /src/contrib/dnstap/Makefile diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..0d4f4f0c7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tests-fuzz/packet_libfuzzer.in"] + path = tests-fuzz/packet_libfuzzer.in + url = ../fuzzing/packet_libfuzzer.in.git diff --git a/Knot.files b/Knot.files index e1c4c2659..76089285b 100644 --- a/Knot.files +++ b/Knot.files @@ -485,9 +485,9 @@ src/zscanner/tests/processing.h src/zscanner/tests/tests.c src/zscanner/tests/tests.h src/zscanner/tests/zscanner-tool.c -tests-fuzz/afl-loop.h -tests-fuzz/packet.c +tests-fuzz/main.c tests-fuzz/packet_libfuzzer.c +tests-fuzz/wrap/afl-loop.h tests-fuzz/wrap/server.c tests-fuzz/wrap/tcp-handler.c tests-fuzz/wrap/udp-handler.c diff --git a/configure.ac b/configure.ac index 70274e78c..ea787c940 100644 --- a/configure.ac +++ b/configure.ac @@ -523,17 +523,7 @@ AX_CODE_COVERAGE AX_SANITIZER AS_IF([test -n "$sanitize_CFLAGS"], [CFLAGS="$CFLAGS $sanitize_CFLAGS"]) - -# LibFuzzer -AC_ARG_WITH([libfuzzer], - AC_HELP_STRING([--with-libfuzzer=path], [Path to LibFuzzer static library]), - [libfuzzer_LIBS="$withval"], [libfuzzer_LIBS=no] -) -AS_IF([test "$libfuzzer_LIBS" != no -a "$sanitize_coverage_enabled" != yes], [ - AC_MSG_ERROR([Sanitizer coverage required for LibFuzzer.]) -]) -AM_CONDITIONAL([HAVE_LIBFUZZER], [test "$libfuzzer_LIBS" != "no"]) -AC_SUBST([libfuzzer_LIBS]) +AM_CONDITIONAL([SANITIZE_FUZZER], [test "$with_sanitize_fuzzer" != "no"]) AS_IF([test "$enable_documentation" = "yes"],[ @@ -587,10 +577,12 @@ result_msg_base=" $PACKAGE $VERSION Utilities with Dnstap: ${opt_dnstap} Systemd integration: ${enable_systemd} PKCS #11 support: ${enable_pkcs11} - ED25519 support: ${enable_ed25519} + Ed25519 support: ${enable_ed25519} Code coverage: ${enable_code_coverage} Sanitizer: ${with_sanitize} - LibFuzzer: ${libfuzzer_LIBS}" + Sanitizer coverage: ${with_sanitize_coverage} + LibFuzzer: ${with_sanitize_fuzzer} +" result_msg_esc=$(echo -n "$result_msg_base" | sed '$!s/$/\\n/' | tr -d '\n') result_msg_add=" diff --git a/tests-fuzz/.gitignore b/tests-fuzz/.gitignore index c8a7a2024..e043c8967 100644 --- a/tests-fuzz/.gitignore +++ b/tests-fuzz/.gitignore @@ -2,6 +2,8 @@ /Makefile /knotd_stdio -/packet -/packet_libfuzzer /wrap/main.c + +/*.trs +/*.log +/packet_libfuzzer diff --git a/tests-fuzz/Makefile.am b/tests-fuzz/Makefile.am index 1ffb89631..30cc4a4fa 100644 --- a/tests-fuzz/Makefile.am +++ b/tests-fuzz/Makefile.am @@ -2,33 +2,23 @@ AM_CPPFLAGS = \ -include $(top_builddir)/src/config.h \ -I$(top_srcdir)/src \ -I$(top_srcdir)/src/dnssec/lib \ - -DCONFIG_DIR='"${config_dir}"' \ - -DSTORAGE_DIR='"${storage_dir}"' \ - -DRUN_DIR='"${run_dir}"' + -DCONFIG_DIR='"${config_dir}"' \ + -DSTORAGE_DIR='"${storage_dir}"' \ + -DRUN_DIR='"${run_dir}"' \ + -DSRCDIR=\"$(abs_srcdir)\" -LDADD = \ - $(top_builddir)/src/libknot.la +FUZZERS = \ + packet_libfuzzer -check_PROGRAMS = \ - knotd_stdio \ - packet +check_PROGRAMS = $(FUZZERS) -if HAVE_LIBFUZZER -check_PROGRAMS += packet_libfuzzer -packet_libfuzzer_LDADD = $(LDADD) $(libfuzzer_LIBS) -lstdc++ +packet_libfuzzer_SOURCES = packet_libfuzzer.c +packet_libfuzzer_LDADD = $(top_builddir)/src/libknot.la + +if SANITIZE_FUZZER +packet_libfuzzer_LDFLAGS = -fsanitize=fuzzer +else +packet_libfuzzer_SOURCES += main.c +AM_CPPFLAGS += -DTEST_RUN +TESTS = $(FUZZERS) endif - -packet_SOURCES = packet.c afl-loop.h -knotd_stdio_SOURCES = wrap/server.c wrap/tcp-handler.c wrap/udp-handler.c afl-loop.h -nodist_knotd_stdio_SOURCES = wrap/main.c -knotd_stdio_CPPFLAGS = $(AM_CPPFLAGS) $(liburcu_CFLAGS) -knotd_stdio_LDADD = \ - $(top_builddir)/src/libknotd.la $(top_builddir)/src/libcontrib.la \ - $(liburcu_LIBS) -BUILT_SOURCES = wrap/main.c -CLEANFILES = wrap/main.c -wrap/main.c: Makefile $(top_builddir)/src/utils/knotd/main.c - echo '#include "afl-loop.h"' > $@ - $(SED) -e 's/for (;;)/while (__AFL_LOOP(1000))/' $(top_srcdir)/src/utils/knotd/main.c >>$@ - -check-compile: $(check_PROGRAMS) diff --git a/tests-fuzz/README.md b/tests-fuzz/README.md index 175263b0f..d4d7f68b9 100644 --- a/tests-fuzz/README.md +++ b/tests-fuzz/README.md @@ -1,66 +1 @@ -# Fuzzing - -Knot DNS 2.0 includes two fuzzing tests in `tests-fuzz/`: a) a simple -test harness that exercises the packet parsing logic in -`packet.c` and more through test that replaces UDP handler with reads -from stdin in `knotd_stdio.c`. This compiles into a test harness that -is designed to be used with lcamtuf's [American Fuzzy Lop (AFL) -fuzzer](http://lcamtuf.coredump.cx/afl/). We will use knotd_stdio in -the following examples. - -## How it works - -AFL 1.83b includes an experimental feature called ["persistent -mode"](http://lcamtuf.blogspot.com/2015/06/new-in-afl-persistent-mode.html) -that can be used to control AFL's fork server to fuzz inputs and -exercise the program without restarting it. You can use this new -feature along with the included Knot DNS test harness. - -## Using the AFL persistent harness - -### Gathering seed inputs - -Gathering DNS packets for use in fuzzing is left to the tester, but -note that the fuzzing shim includes an environment variable to support -test cases minimization with `afl-cmin`: - -``` -$ cat > knot-afl.conf << EOF -server: - listen: 0.0.0.0@5353 - -log: - - target: stderr - any: error - -control: - listen: /tmp/knot.sock -EOF -$ afl-cmin -i ~/knot-seeds -o ~/knot-seeds-cmin -m 1000000 -t 400000 -- tests-fuzz/knotd_stdio -c knot-afl.conf -``` - -You might want to configure some sample zones and have a test set of -fuzzing data that would end up querying those zones. - -### Compiling the test harness. - -See the AFL [blog post](http://lcamtuf.blogspot.com/2015/06/new-in-afl-persistent-mode.html) -and README for details on how to use LLVM mode and compile binaries -for use with persistent mode. For reference, you can use these -commands to build Knot with the fuzzing harness: - -``` -$ CC=afl-clang-fast ./configure --disable-shared -$ make check -``` - -### Fuzz - -A basic AFL run can then be kicked off as follows: - -``` -AFL_PERSISTENT=1 afl-fuzz -i my_seeds -o my_output_dir -t 10000 -m 100000 -- tests-fuzz/knotd_stdio -c knot-afl.conf -``` - -Note that AFL can be scaled up by supplying the `-M` flag and starting -multiple instances of the fuzzer. +https://gitlab.labs.nic.cz/knot/knot-dns/wikis/Fuzzing diff --git a/tests-fuzz/main.c b/tests-fuzz/main.c new file mode 100644 index 000000000..70d2b2446 --- /dev/null +++ b/tests-fuzz/main.c @@ -0,0 +1,136 @@ +/* + * Copyright(c) 2017 Tim Ruehsen + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); + +#ifdef TEST_RUN + +#include + +static void test_all_from(const char *dirname) +{ + DIR *dirp; + struct dirent *dp; + + if ((dirp = opendir(dirname))) { + while ((dp = readdir(dirp))) { + if (*dp->d_name == '.') continue; + + char fname[strlen(dirname) + strlen(dp->d_name) + 2]; + snprintf(fname, sizeof(fname), "%s/%s", dirname, dp->d_name); + + int fd; + if ((fd = open(fname, O_RDONLY)) == -1) { + fprintf(stderr, "Failed to open %s (%d)\n", fname, errno); + continue; + } + + struct stat st; + if (fstat(fd, &st) != 0) { + fprintf(stderr, "Failed to stat %d (%d)\n", fd, errno); + close(fd); + continue; + } + + uint8_t *data = malloc(st.st_size); + ssize_t n; + if ((n = read(fd, data, st.st_size)) == st.st_size) { + printf("testing %llu bytes from '%s'\n", (unsigned long long) st.st_size, fname); + fflush(stdout); + LLVMFuzzerTestOneInput(data, st.st_size); + fflush(stderr); + } else { + fprintf(stderr, "Failed to read %llu bytes from %s (%d), got %zd\n", + (unsigned long long) st.st_size, fname, errno, n); + } + + free(data); + close(fd); + } + closedir(dirp); + } +} + +int main(int argc, char **argv) +{ + const char *target = strrchr(argv[0], '/'); + target = target ? target + 1 : argv[0]; + + char corporadir[sizeof(SRCDIR) + 1 + strlen(target) + 8]; + + if (strncmp(target, "lt-", 3) == 0) { + target += 3; + } + + snprintf(corporadir, sizeof(corporadir), SRCDIR "/%s.in", target); + + test_all_from(corporadir); + + snprintf(corporadir, sizeof(corporadir), SRCDIR "/%s.repro", target); + + test_all_from(corporadir); + + return 0; +} + +#else + +#ifndef __AFL_LOOP +static int __AFL_LOOP(int n) +{ + static int first = 1; + + if (first) { + first = 0; + return 1; + } + + return 0; +} +#endif + +int main(int argc, char **argv) +{ + unsigned char buf[64 * 1024]; + + while (__AFL_LOOP(10000)) { // only works with afl-clang-fast + int ret = fread(buf, 1, sizeof(buf), stdin); + if (ret < 0) { + return 0; + } + + LLVMFuzzerTestOneInput(buf, ret); + } + + return 0; +} + +#endif /* TEST_RUN */ diff --git a/tests-fuzz/packet.c b/tests-fuzz/packet.c deleted file mode 100644 index feb381ec2..000000000 --- a/tests-fuzz/packet.c +++ /dev/null @@ -1,38 +0,0 @@ -/* Copyright (C) 2015 CZ.NIC, z.s.p.o. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#include -#include -#include -#include - -#include "libknot/libknot.h" -#include "afl-loop.h" - -int main(void) -{ - while (__AFL_LOOP(1000)) { - uint8_t buffer[UINT16_MAX + 1] = { 0 }; - size_t len = fread(buffer, 1, sizeof(buffer), stdin); - - knot_pkt_t *pkt = knot_pkt_new(buffer, len, NULL); - assert(pkt); - int r = knot_pkt_parse(pkt, 0); - knot_pkt_free(&pkt); - - return (r == KNOT_EOK ? 0 : 1); - } -} diff --git a/tests-fuzz/packet_libfuzzer.c b/tests-fuzz/packet_libfuzzer.c index abb2f1f14..0d8dae05c 100644 --- a/tests-fuzz/packet_libfuzzer.c +++ b/tests-fuzz/packet_libfuzzer.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2015 CZ.NIC, z.s.p.o. +/* Copyright (C) 2017 CZ.NIC, z.s.p.o. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,22 +16,18 @@ #include #include -#include -#include #include "libknot/libknot.h" -int LLVMFuzzerTestOneInput(const unsigned char *data, size_t size) +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - uint8_t *copy = malloc(size); - assert(copy); + uint8_t copy[size]; memcpy(copy, data, size); knot_pkt_t *pkt = knot_pkt_new(copy, size, NULL); + assert(pkt); knot_pkt_parse(pkt, 0); knot_pkt_free(&pkt); - free(copy); - return 0; } diff --git a/tests-fuzz/packet_libfuzzer.in b/tests-fuzz/packet_libfuzzer.in new file mode 160000 index 000000000..69e4a9815 --- /dev/null +++ b/tests-fuzz/packet_libfuzzer.in @@ -0,0 +1 @@ +Subproject commit 69e4a98151063910675bce46efcdd151348dae9d diff --git a/tests-fuzz/afl-loop.h b/tests-fuzz/wrap/afl-loop.h similarity index 100% rename from tests-fuzz/afl-loop.h rename to tests-fuzz/wrap/afl-loop.h