]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#640] Initial fuzzing code added for Kea6
authorTomek Mrugalski <tomasz@isc.org>
Mon, 21 Nov 2016 10:53:04 +0000 (11:53 +0100)
committerStephen Morris <stephen@isc.org>
Fri, 20 Dec 2019 17:55:45 +0000 (17:55 +0000)
Proof of concept code to fuzz Kea-DHCPv6.

13 files changed:
configure.ac
doc/fuzz.txt [new file with mode: 0644]
src/bin/dhcp6/Makefile.am
src/bin/dhcp6/dhcp6_srv.cc
src/bin/dhcp6/fuzz.cc [new file with mode: 0644]
src/bin/dhcp6/fuzz.h [new file with mode: 0644]
src/bin/dhcp6/tests/fuzz-config/fuzz.json [new file with mode: 0644]
src/bin/dhcp6/tests/fuzz-data/release-fqdn [new file with mode: 0644]
src/bin/dhcp6/tests/fuzz-data/renew-fqdn [new file with mode: 0644]
src/bin/dhcp6/tests/fuzz-data/request-simple [new file with mode: 0644]
src/bin/dhcp6/tests/fuzz-data/solicit-docsis-relayed [new file with mode: 0644]
src/bin/dhcp6/tests/fuzz-data/solicit-fqdn [new file with mode: 0644]
src/bin/dhcp6/tests/fuzz-data/solicit-simple [new file with mode: 0644]

index 9542edcb120bfd3ff2db95e0ac9d4d865c05fdbf..0a8075fe003ffc8d79c86bb580f00d605448fcf1 100755 (executable)
@@ -1460,6 +1460,17 @@ if test "x$VALGRIND" != "xno"; then
    found_valgrind="found"
 fi
 
+AC_ARG_ENABLE(fuzz, [AC_HELP_STRING([--enable-fuzz],
+  [indicates that the code will be built with AFL (American Fuzzy Lop) support.
+   Code built this way is unusable as a regular server. [default=no]])],
+   enable_fuzz=$enableval, enable_fuzz=no)
+AM_CONDITIONAL(FUZZ, test x$enable_fuzz != xno)
+
+if test "x$enable_fuzz" != "xno" ; then
+    AC_DEFINE([FUZZ], [1], [AFL fuzzing was enabled.])
+fi
+
+
 # Check for optreset in unistd.h. On BSD systems the optreset is
 # used to reset the state of getopt() function. Resetting its state
 # is required if command line arguments are parsed multiple times
@@ -1976,6 +1987,7 @@ Developer:
   Generate Messages Files:   $enable_generate_messages
   Perfdhcp:                  $enable_perfdhcp
   Kea-shell:                 $shell_report
+  Enable fuzz:               $enable_fuzz
 
 END
 
diff --git a/doc/fuzz.txt b/doc/fuzz.txt
new file mode 100644 (file)
index 0000000..a7da360
--- /dev/null
@@ -0,0 +1,98 @@
+This file documents the process of initial trial runs for running
+AFL fuzzer for Kea. Currently only Kea-dhcp6 is extended with this
+capability. Once we get more experience with it, we should implement
+this capability for Kea-dhcp4.
+
+I have used Ubuntu 16.04 for this. I read somewhere that FreeBSD is
+ok for fuzzing, but Mac OS is not.
+
+1. Download AFL
+  Homepage: http://lcamtuf.coredump.cx/afl/
+  Version used: 2.35b (afl-latest.tgz)
+
+2. Compile AFL
+  cd afl-2.35b
+  make
+  cd llvm_mode
+  make
+
+the last step requires to have LLVM installed. On
+Ubuntu 16.04 I had to do this:
+  sudo apt-get install llvm
+
+3. Set up path to AFL binaries
+
+ export AFL_PATH=/home/thomson/devel/afl-2.35b
+ export PATH=$PATH:/home/thomson/devel/afl-2.35b
+
+4. Build Kea using AFL
+
+ cd kea
+ git pull
+ git checkout experiments/fuzz
+ autoreconf -i
+ CXX=afl-clang-fast++ ./configure --enable-fuzz --enable-static-link
+ make
+
+ Note: no unit-tests needed. We will be fuzzing the
+ production code only.
+
+5. Configure destination address
+
+ The defaults (see src/bin/dhcp6/fuzz.cc) are:
+ interface: eth0
+ dest address: ff02::1:2
+ dest port: 547
+
+ Those can be changed with the following env. variables:
+ KEA_AFL_INTERFACE
+ KEA_AFL_ADDR
+ KEA_AFL_PORT
+
+ E.g.
+ export KEA_AFL_INTERFACE=eth1
+
+ Overriding the parameters with variables has not been tested.
+
+6. Run fuzzer
+
+ Set up max size of a virtual memory allowed to 4GB:
+ ulimit -v 4096000
+
+ You may be asked by AFL to tweak your kernel. In my case (ubuntu
+ 16.04), I had to tweak the scaling_governor. The instructions AFL
+ gives are very easy to follow.
+
+ Instruct AFL to allow 4096MB of virtual memory and run AFL:
+ afl-fuzz -m 4096 -i tests/fuzz-data -o fuzz-out ./kea-dhcp6 -c tests/fuzz-config/fuzz.json
+
+ Here's what the switches do:
+ -m 4096 - allow Kea to take up to 4GB memory
+ -i tests/fuzz-data - Input seeds. These are the packet files used
+    to initiate the packet randomization. Several examples are in
+    src/bin/dhcp6/tests/fuzz-data. You can extract them using wireshark,
+    right click on a packet, then export as binary data. Make sure you
+    export the payload of UDP content. the first exported byte should
+    by message-type.
+ -o dir - that's the output directory. It doesn't have to exist.
+
+7. Checking that the fuzzer is really working
+
+ a) the harness prints out a line to /tmp/kea-fuzz-harness.txt every
+ time a new packet is sent. This generated 4,5MB of entries in 20
+ minutes. Obviously, this has to be disabled for production fuzzing,
+ but it's good for initial trials.
+
+ b) I have my fuzz.json (which is renamed doc/examples/kea6/simple.json)
+ that tell Kea to use logging on level INFO and write output to a
+ file. This file keeps growing. That's around 3,8MB after 20 minutes.
+
+8. Tweak Kea harness if needed
+
+ There are several variables in src/bin/dhcp6/fuzz.cc that you can
+ tweak. By default, it will write the log to /tmp/kea-fuzz-harness.txt
+ every 5 packets and will terminate after 100.000 packets processed.
+ That mechanism is to avoid cases when Kea gets stuck and technically
+ running, but not processing packets. AFL should be able to restart
+ Kea and continue running.
index 4574bbbb5ce122bbbe6f773c8c3f6ddc5819e2a0..8ad0cfb519b683c9eae9a22a6f7936c87bf5c6a5 100644 (file)
@@ -38,7 +38,7 @@ libdhcp6_la_SOURCES += dhcp6_lexer.ll location.hh position.hh stack.hh
 libdhcp6_la_SOURCES += dhcp6_parser.cc dhcp6_parser.h
 libdhcp6_la_SOURCES += parser_context.cc parser_context.h parser_context_decl.h
 libdhcp6_la_SOURCES += dhcp6_messages.h dhcp6_messages.cc
-EXTRA_DIST += dhcp6_messages.mes
+libdhcp6_la_SOURCES += fuzz.cc
 
 sbin_PROGRAMS = kea-dhcp6
 
index 18e51181f6aab06cdb81d6a11a9bcffb6f0e93e9..27b46277b2025341f22f987935ba78817e745273 100644 (file)
@@ -65,6 +65,8 @@
 #endif
 #include <dhcpsrv/memfile_lease_mgr.h>
 
+#include <dhcp6/fuzz.h>
+
 #include <boost/bind.hpp>
 #include <boost/foreach.hpp>
 #include <boost/tokenizer.hpp>
@@ -442,6 +444,19 @@ Dhcpv6Srv::initContext(const Pkt6Ptr& pkt,
 }
 
 bool Dhcpv6Srv::run() {
+
+#ifdef FUZZ
+    // AFL fuzzing setup initiated here. At this stage, Kea has loaded its
+    // config, opened sockets, established DB connections, etc. It is truly
+    // ready to process packets. Now it's time to initialize AFL. It will
+    // set up a separate thread that will receive data from fuzzing engine
+    // and will send it as packets to Kea. Kea is supposed to process them
+    // and hopefully not crash in the process. Once the packet processing
+    // is done, Kea should let the AFL know that it's ready for the next
+    // packet. This is done further down in this loop (see kea_fuzz_notify()).
+    kea_fuzz_setup(&shutdown_);
+#endif /* FUZZ */
+
     while (!shutdown_) {
         try {
             run_one();
@@ -451,11 +466,18 @@ bool Dhcpv6Srv::run() {
             // specific catches.
             LOG_ERROR(packet6_logger, DHCP6_PACKET_PROCESS_STD_EXCEPTION)
                 .arg(e.what());
+
         } catch (...) {
             // General catch-all non-standard exception that are not caught
             // by more specific catches.
             LOG_ERROR(packet6_logger, DHCP6_PACKET_PROCESS_EXCEPTION);
         }
+
+#ifdef FUZZ
+        // Ok, this particular packet processing is done.
+        // Let the AFL know about it.
+        kea_fuzz_notify();
+#endif
     }
 
     return (true);
diff --git a/src/bin/dhcp6/fuzz.cc b/src/bin/dhcp6/fuzz.cc
new file mode 100644 (file)
index 0000000..a2f50ad
--- /dev/null
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2016  Internet Systems Consortium, Inc. ("ISC")
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "config.h"
+
+#include <dhcp6/fuzz.h>
+
+#define ENABLE_AFL
+
+#ifdef ENABLE_AFL
+#include <sys/errno.h>
+
+#include <dhcp/dhcp6.h>
+
+#include <iostream>
+#include <fstream>
+#include <ctime>
+
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <unistd.h>
+#include <pthread.h>
+
+
+#ifndef __AFL_LOOP
+#error To use American Fuzzy Lop you have to set CC to afl-clang-fast!!!
+#endif
+
+/// This is how many packets Kea will process until shutting itself down.
+/// AFL should restart it. This safety switch is here for eliminating cases
+/// where Kea goes into a weird state and stops processing packets properly.
+const unsigned int LOOP_COUNT = 100000;
+
+/// This mechanism limits down the number of logs this harness prints.
+/// E.g. when set to 100, it will print a message every 100 packets.
+const unsigned int PRINT_EVERY = 5;
+
+/// This is the place where the harness log message will be printed.
+const std::string PRINT_LOG("/tmp/kea-fuzz-harness.txt");
+
+/*
+ * We are using pthreads directly because we might be using it with unthreaded
+ * version of BIND, where all thread functions are mocks. Since AFL for now only
+ * works on Linux it's not a problem.
+ */
+static pthread_cond_t cond;
+static pthread_mutex_t mutex;
+
+static bool ready;
+
+using namespace std;
+
+static volatile bool * shutdown_reference = NULL;
+
+void kea_shutdown(void) {
+    if (shutdown_reference) {
+        // do we have the reference to shutdown flag from Dhcp6Srv?
+        // If yes, then let's set it to true. Kea will shutdown on
+        // its own.
+        *shutdown_reference = true;
+    } else {
+        // We don't have the pointer yet. Let's terminate abruptly.
+        exit(EXIT_SUCCESS);
+    }
+}
+
+
+// This is the main fuzzing function. It receives data from fuzzing engine.
+// That data is received to stdin and then sent over the configured UDP socket.
+// Then it wait for a conditional, which is called in kea_fuzz_notify() from
+// Kea main loop.
+static void *
+kea_main_client(void *) {
+    const char *host;
+    struct sockaddr_in6 servaddr;
+    int sockfd;
+    int loop;
+    void *buf;
+
+    string iface("eth0");
+    string dst(ALL_DHCP_RELAY_AGENTS_AND_SERVERS);
+    string port("547");
+
+    ofstream f(PRINT_LOG.c_str(), ios::ate);
+
+    const char *iface_ptr = getenv("KEA_AFL_INTERFACE");
+    if (iface_ptr) {
+        iface = string(iface_ptr);
+    }
+
+    const char *dst_ptr = getenv("KEA_AFL_ADDR");
+    if (dst_ptr) {
+        dst = string(dst_ptr);
+    }
+
+    const char *port_ptr = getenv("KEA_AFL_PORT");
+    if (port_ptr) {
+        port = string(port_ptr);
+    }
+
+    unsigned int iface_id = if_nametoindex(iface.c_str());
+
+    f << "Kea AFL setup:" << endl;
+    f << "Interface: " << iface << endl;
+    f << "Interface index: " << iface_id << endl;
+    f << "UDP destination addr: " << dst << endl;
+    f << "UDP destination port: " << port << endl;
+
+    memset(&servaddr, 0, sizeof (servaddr));
+    servaddr.sin6_family = AF_INET6;
+    if (inet_pton(AF_INET6, dst.c_str(), &servaddr.sin6_addr) != 1) {
+        f << "Error: inet_pton() failed: can't convert " << dst
+          << " to address." << endl;
+        exit(EXIT_FAILURE);
+    }
+    servaddr.sin6_port = htons(atoi(port.c_str()));
+    servaddr.sin6_scope_id = iface_id;
+
+    sockfd = socket(AF_INET6, SOCK_DGRAM, 0);
+    if (sockfd < 0) {
+        f << "Failed to create UDP6 socket" << endl;
+        exit(EXIT_FAILURE);
+    }
+
+    buf = malloc(65536);
+    if (!buf) {
+        f << "Failed to allocate a buffer" << endl;
+        exit(EXIT_FAILURE);
+    }
+
+    time_t t;
+
+    loop = LOOP_COUNT;
+    while (loop--) {
+        ssize_t length;
+
+        length = read(0, buf, 65536);
+        if (length <= 0) {
+            usleep(1000000);
+            continue;
+        }
+
+        /* if (length > 4096) {
+            if (getenv("AFL_CMIN")) {
+                ns_server_flushonshutdown(ns_g_server,
+                                          ISC_FALSE);
+                isc_app_shutdown();
+                return (NULL);
+            }
+            raise(SIGSTOP);
+            continue;
+            } */
+
+        if (pthread_mutex_lock(&mutex) != 0) {
+            f << "#### Failed to lock mutex" << endl;
+           abort();
+        }
+
+        ready = false;
+
+        ssize_t sent;
+
+        t = time(0);
+        struct tm * now = localtime(&t);
+
+        if (! (loop%PRINT_EVERY)) {
+            f << (now->tm_year + 1900) << "-" << (now->tm_mon + 1) << "-" << (now->tm_mday)
+              << " " << (now->tm_hour) << ":" << (now->tm_min) << ":" << (now->tm_sec)
+              << " Sending " << length << " bytes to " << dst << "/" << port
+              << " over " << iface << "/" << iface_id << ", loop iteration << "
+              << loop << endl;
+        }
+
+        sent = sendto(sockfd, buf, length, 0,
+                      (struct sockaddr *) &servaddr, sizeof(servaddr));
+        if (sent != length) {
+            f << "#### Error: expected to send " << length
+              << ", but really sent " << sent << endl;
+           f << "#### errno=" << errno << endl;
+        }
+
+        /* unclog */
+        recvfrom(sockfd, buf, 65536, MSG_DONTWAIT, NULL, NULL);
+
+        while (!ready)
+            pthread_cond_wait(&cond, &mutex);
+
+        if (pthread_mutex_unlock(&mutex) != 0) {
+            f << "#### Failed to unlock mutex" << endl;
+           abort();
+        }
+    }
+
+    f << LOOP_COUNT << " packets processed, terminating." << endl;
+    f.close();
+
+    free(buf);
+    close(sockfd);
+
+    // @todo: shutdown kea
+    // ns_server_flushonshutdown(ns_g_server, ISC_FALSE);
+    // isc_app_shutdown();
+    kea_shutdown();
+
+    /*
+     * It's here just for the signature, that's how AFL detects if it's
+     * a 'persistent mode' binary.
+     */
+    __AFL_LOOP(0);
+
+    return (NULL);
+}
+
+#endif /* ENABLE_AFT */
+
+void
+kea_fuzz_notify(void) {
+#ifdef ENABLE_AFL
+    if (getenv("AFL_CMIN")) {
+        kea_shutdown();
+        return;
+    }
+
+    raise(SIGSTOP);
+
+    if (pthread_mutex_lock(&mutex) != 0) {
+        cerr << "#### unable to lock mutex" << endl;
+        abort();
+    }
+
+    ready = true;
+
+    if (pthread_cond_signal(&cond) != 0) {
+        cerr << "#### unable to cond signal" << endl;
+        abort();
+    }
+
+    if (pthread_mutex_unlock(&mutex) != 0) {
+        cerr << "Unable to unlock mutex" << endl;
+        abort();
+    }
+#endif /* ENABLE_AFL */
+}
+
+void
+kea_fuzz_setup(volatile bool* shutdown) {
+#ifdef ENABLE_AFL
+
+    shutdown_reference = shutdown;
+
+    /// @todo: What are those variables? What do they do?
+    if (getenv("__AFL_PERSISTENT") || getenv("AFL_CMIN")) {
+        pthread_t thread;
+
+        if (pthread_mutex_init(&mutex, NULL) != 0) {
+           cerr << "#### unable to init mutex" << endl;
+           abort();
+        }
+
+        if (pthread_cond_init(&cond, NULL) != 0) {
+           cerr << "#### unable to init condition variable" << endl;
+           abort();
+        }
+
+        if (pthread_create(&thread, NULL, kea_main_client, NULL) != 0) {
+           cerr << "#### unable to create fuzz thread" << endl;
+           abort();
+        }
+    }
+
+#endif /* ENABLE_AFL */
+}
diff --git a/src/bin/dhcp6/fuzz.h b/src/bin/dhcp6/fuzz.h
new file mode 100644 (file)
index 0000000..98d9cdf
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016  Internet Systems Consortium, Inc. ("ISC")
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef KEA_FUZZ_H
+#define KEA_FUZZ_H
+
+extern "C" {
+
+void kea_fuzz_notify(void);
+
+/// @brief Sets up Kea fuzzing
+///
+/// @param shutdown pointer to boolean flag that will be set to true to
+///        trigger shutdown procedure
+///
+/// This takes one parameter, which is a pointer to shutdown flag,
+/// which should point to instance of Dhcp6Srv::shutdown_. Kea runs
+/// until something sets this flag to true, which is an indication to
+/// start shutdown procedure.
+void kea_fuzz_setup(volatile bool * shutdown);
+
+};
+
+#endif /* KEA_FUZZ_H */
diff --git a/src/bin/dhcp6/tests/fuzz-config/fuzz.json b/src/bin/dhcp6/tests/fuzz-config/fuzz.json
new file mode 100644 (file)
index 0000000..80d80b7
--- /dev/null
@@ -0,0 +1,61 @@
+# This is an example configuration file for DHCPv6 server in Kea.
+# It's a basic scenario with one IPv6 subnet configured. It is
+# assumed that one subnet (2001:db8:1::/64 is available directly
+# over ethX interface.
+
+{ "Dhcp6":
+
+{
+# Kea is told to listen on ethX interface only.
+  "interfaces-config": {
+    "interfaces": [ "eth0" ]
+  },
+
+# We need to specify the the database used to store leases. As of
+# September 2016, four database backends are supported: MySQL,
+# PostgreSQL, Cassandra, and the in-memory database, Memfile.
+# We'll use memfile  because it doesn't require any prior set up.
+  "lease-database": {
+    "type": "memfile"
+  },
+
+# Addresses will be assigned with preferred and valid lifetimes
+# being 3000 and 4000, respectively. Client is told to start
+# renewing after 1000 seconds. If the server does not respond
+# after 2000 seconds since the lease was granted, client is supposed
+# to start REBIND procedure (emergency renewal that allows switching
+# to a different server).
+  "preferred-lifetime": 3000,
+  "valid-lifetime": 4000,
+  "renew-timer": 1000,
+  "rebind-timer": 2000,
+
+# The following list defines subnets. Each subnet consists of at
+# least subnet and pool entries.
+  "subnet6": [
+    {
+      "pools": [ { "pool": "2001:db8:1::/80" } ],
+      "subnet": "2001:db8:1::/64",
+      "interface": "eth0"
+    }
+  ]
+},
+
+# The following configures logging. It assumes that messages with at least
+# informational level (info, warn, error and fatal) should be logged to stdout.
+"Logging": {
+    "loggers": [
+        {
+            "name": "kea-dhcp6",
+            "output_options": [
+                {
+                    "output": "/tmp/kea-fuzz.log"
+                }
+            ],
+            "debuglevel": 0,
+            "severity": "DEBUG"
+        }
+    ]
+}
+
+}
diff --git a/src/bin/dhcp6/tests/fuzz-data/release-fqdn b/src/bin/dhcp6/tests/fuzz-data/release-fqdn
new file mode 100644 (file)
index 0000000..e3f8bc2
Binary files /dev/null and b/src/bin/dhcp6/tests/fuzz-data/release-fqdn differ
diff --git a/src/bin/dhcp6/tests/fuzz-data/renew-fqdn b/src/bin/dhcp6/tests/fuzz-data/renew-fqdn
new file mode 100644 (file)
index 0000000..0e3b8af
Binary files /dev/null and b/src/bin/dhcp6/tests/fuzz-data/renew-fqdn differ
diff --git a/src/bin/dhcp6/tests/fuzz-data/request-simple b/src/bin/dhcp6/tests/fuzz-data/request-simple
new file mode 100644 (file)
index 0000000..c2bdfff
Binary files /dev/null and b/src/bin/dhcp6/tests/fuzz-data/request-simple differ
diff --git a/src/bin/dhcp6/tests/fuzz-data/solicit-docsis-relayed b/src/bin/dhcp6/tests/fuzz-data/solicit-docsis-relayed
new file mode 100644 (file)
index 0000000..d89de9d
Binary files /dev/null and b/src/bin/dhcp6/tests/fuzz-data/solicit-docsis-relayed differ
diff --git a/src/bin/dhcp6/tests/fuzz-data/solicit-fqdn b/src/bin/dhcp6/tests/fuzz-data/solicit-fqdn
new file mode 100644 (file)
index 0000000..7095f4d
Binary files /dev/null and b/src/bin/dhcp6/tests/fuzz-data/solicit-fqdn differ
diff --git a/src/bin/dhcp6/tests/fuzz-data/solicit-simple b/src/bin/dhcp6/tests/fuzz-data/solicit-simple
new file mode 100644 (file)
index 0000000..e69a815
Binary files /dev/null and b/src/bin/dhcp6/tests/fuzz-data/solicit-simple differ