]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#640,!351] Convert fuzzing code to C++ class
authorStephen Morris <stephen@isc.org>
Wed, 12 Jun 2019 09:22:02 +0000 (10:22 +0100)
committerStephen Morris <stephen@isc.org>
Tue, 1 Oct 2019 16:00:21 +0000 (17:00 +0100)
Also add logging via Kea logging subsystem.

configure.ac
src/bin/dhcp6/dhcp6_srv.cc
src/lib/dhcpsrv/Makefile.am
src/lib/dhcpsrv/fuzz.cc
src/lib/dhcpsrv/fuzz.h
src/lib/dhcpsrv/fuzz_log.cc [new file with mode: 0644]
src/lib/dhcpsrv/fuzz_log.h [new file with mode: 0644]
src/lib/dhcpsrv/fuzz_messages.mes [new file with mode: 0644]

index 85ee8b2ac68ba3574ed69325b220f49ceb139f07..ee431356b918bce2a6d895ede30bd125dfce78ca 100755 (executable)
@@ -1599,10 +1599,10 @@ 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)
+AM_CONDITIONAL(ENABLE_AFL, test x$enable_fuzz != xno)
 
 if test "x$enable_fuzz" != "xno" ; then
-    AC_DEFINE([FUZZ], [1], [AFL fuzzing was enabled.])
+    AC_DEFINE([ENABLE_AFL], [1], [AFL fuzzing was enabled.])
 fi
 
 
index b5f4a1a155971b9bb70a16deaa13ef6c5b50574b..d28992a2ec0621d74263efd752a9f160d8f272ee 100644 (file)
@@ -13,6 +13,7 @@
 #include <dhcp/docsis3_option_defs.h>
 #include <dhcp/duid.h>
 #include <dhcp/duid_factory.h>
+#include <dhcpsrv/fuzz.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option6_addrlst.h>
@@ -65,8 +66,6 @@
 #endif
 #include <dhcpsrv/memfile_lease_mgr.h>
 
-#include <dhcp6/fuzz.h>
-
 #include <boost/bind.hpp>
 #include <boost/foreach.hpp>
 #include <boost/tokenizer.hpp>
@@ -444,8 +443,7 @@ Dhcpv6Srv::initContext(const Pkt6Ptr& pkt,
 }
 
 bool Dhcpv6Srv::run() {
-
-#ifdef FUZZ
+#ifdef ENABLE_AFL
     // 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
@@ -453,9 +451,9 @@ bool Dhcpv6Srv::run() {
     // 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 */
+    // packet. This is done further down in this loop (see Fuzz::notify()).
+    Fuzz::init(&shutdown_);
+#endif // ENABLE_AFL
 
     while (!shutdown_) {
         try {
@@ -473,13 +471,22 @@ bool Dhcpv6Srv::run() {
             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
+#ifdef ENABLE_AFL
+        // Ok, this particular packet processing is done.  If we are fuzzing,
+        // let AFL know about it.
+        Fuzz::notify();
+#endif // ENABLE_AFL
     }
 
+#ifdef ENABLE_AFL
+    // Ensure that the fuzzing thread has cleanly finished.
+    Fuzz::wait();
+
+    // The next line is needed as a signature for AFL to recognise that
+    // we are running persistent fuzzing.
+    __AFL_LOOP(0);
+#endif  // ENABLE_AFL
+
     return (true);
 }
 
index ce80221747cd3d2e33267b7cf6f73900077e5f94..5825db402be6997be8a87c251ec0cbc30911fb67 100644 (file)
@@ -103,7 +103,6 @@ libkea_dhcpsrv_la_SOURCES += dhcp4o6_ipc.cc dhcp4o6_ipc.h
 libkea_dhcpsrv_la_SOURCES += dhcpsrv_exceptions.h
 libkea_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
 libkea_dhcpsrv_la_SOURCES += dhcpsrv_messages.h dhcpsrv_messages.cc
-libkea_dhcpsrv_la_SOURCES += fuzz.cc fuzz.h
 libkea_dhcpsrv_la_SOURCES += host.cc host.h
 libkea_dhcpsrv_la_SOURCES += host_container.h
 libkea_dhcpsrv_la_SOURCES += host_data_source_factory.cc host_data_source_factory.h
@@ -180,6 +179,12 @@ libkea_dhcpsrv_la_SOURCES += parsers/simple_parser4.h
 libkea_dhcpsrv_la_SOURCES += parsers/simple_parser6.cc
 libkea_dhcpsrv_la_SOURCES += parsers/simple_parser6.h
 
+if ENABLE_AFL
+libkea_dhcpsrv_la_SOURCES += fuzz.cc fuzz.h
+libkea_dhcpsrv_la_SOURCES += fuzz_log.cc fuzz_log.h
+libkea_dhcpsrv_la_SOURCES += fuzz_messages.cc fuzz_messages.h
+endif
+
 libkea_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS)
 libkea_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS)
 libkea_dhcpsrv_la_LIBADD  = $(top_builddir)/src/lib/eval/libkea-eval.la
@@ -226,6 +231,7 @@ endif
 EXTRA_DIST += alloc_engine_messages.mes
 EXTRA_DIST += dhcpsrv_messages.mes
 EXTRA_DIST += hosts_messages.mes
+EXTRA_DIST += fuzz_messages.mes
 
 # If we want to get rid of all generated messages files, we need to use
 # make maintainer-clean. The proper way to introduce custom commands for
@@ -237,6 +243,7 @@ maintainer-clean-local:
        rm -f alloc_engine_messages.h alloc_engine_messages.cc
        rm -f dhcpsrv_messages.h dhcpsrv_messages.cc
        rm -f hosts_messages.h hosts_messages.cc
+       rm -f fuzz_messages.h fuzz_messages.cc
 
 # To regenerate messages files, one can do:
 #
@@ -251,7 +258,8 @@ if GENERATE_MESSAGES
 # Define rule to build logging source files from message file
 messages: alloc_engine_messages.h alloc_engine_messages.cc \
          dhcpsrv_messages.h dhcpsrv_messages.cc \
-         hosts_messages.h hosts_messages.cc
+         hosts_messages.h hosts_messages.cc \
+         fuzz_messages.h fuzz_messages.cc 
        @echo Message files regenerated
 
 alloc_engine_messages.h alloc_engine_messages.cc: alloc_engine_messages.mes
@@ -263,11 +271,15 @@ dhcpsrv_messages.h dhcpsrv_messages.cc: dhcpsrv_messages.mes
 hosts_messages.h hosts_messages.cc: hosts_messages.mes
        $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/dhcpsrv/hosts_messages.mes
 
+fuzz_messages.h fuzz_messages.cc: fuzz_messages.mes
+       $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/dhcpsrv/fuzz_messages.mes
+
 else
 
 messages: alloc_engine_messages.h alloc_engine_messages.cc \
          dhcpsrv_messages.h dhcpsrv_messages.cc \
-         hosts_messages.h hosts_messages.cc
+         hosts_messages.h hosts_messages.cc \
+         fuzz_messages.h fuzz_messages.cc
        @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
 
 endif
index a2f50ad32bb3d99aa9a79338b84b72b9030ed8ef..0824355c69940502cc125bd1bf7d0ea9f950904c 100644 (file)
-/*
- * 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/.
- */
+// 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
+#include <config.h>
 
 #ifdef ENABLE_AFL
-#include <sys/errno.h>
+
+#ifndef __AFL_LOOP
+#error To use American Fuzzy Lop you have to set CXX to afl-clang-fast++
+#endif
 
 #include <dhcp/dhcp6.h>
+#include <dhcpsrv/fuzz.h>
+#include <dhcpsrv/fuzz_log.h>
 
-#include <iostream>
-#include <fstream>
-#include <ctime>
+#include <boost/lexical_cast.hpp>
 
+#include <errno.h>
 #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;
+#include <iostream>
+#include <sstream>
+#include <fstream>
+#include <ctime>
 
+using namespace isc;
+using namespace isc::dhcp;
 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);
-    }
-}
-
+// Constants defined in the class definition
+constexpr size_t        Fuzz::BUFFER_SIZE;
+constexpr useconds_t    Fuzz::SLEEP_INTERVAL;
+constexpr long          Fuzz::LOOP_COUNT;
 
-// 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);
-    }
+// Variables needed to synchronize between main and fuzzing threads.
+condition_variable      Fuzz::sync_cond_;
+mutex                   Fuzz::sync_mutex_;
+thread                  Fuzz::fuzz_thread_;
 
-    const char *dst_ptr = getenv("KEA_AFL_ADDR");
-    if (dst_ptr) {
-        dst = string(dst_ptr);
-    }
+// Address structure used to send data to address/port on which Kea listens
+struct sockaddr_in6     Fuzz::servaddr_;
 
-    const char *port_ptr = getenv("KEA_AFL_PORT");
-    if (port_ptr) {
-        port = string(port_ptr);
-    }
+// Pointer to the shutdown flag used by Kea.  The fuzzing code will set this
+// after the appropriate number of packets have been fuzzed.
+volatile bool*          Fuzz::shutdown_ptr_ = NULL;
 
-    unsigned int iface_id = if_nametoindex(iface.c_str());
+// Flag to state that the 
 
-    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;
+// Fuzzer initialization: create the thread artifacts and start the
+// main thread.
+void
+Fuzz::init(volatile bool* shutdown_flag) {
+    try {
+        stringstream reason;
+
+        // Store reference to shutdown flag.  When the fuzzing loop has read
+        // the set number of packets from kea, it will set this flag to trigger
+        // a Kea shutdown.
+        if (shutdown_flag) {
+            shutdown_ptr_ = shutdown_flag;
+        } else {
+            isc_throw(FuzzInitFail, "must pass shutdown flag to kea_fuzz_init");
         }
 
-        /* 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();
+        // Get the environment for the fuzzing.  First the interface to use.
+        const char *iface_ptr = getenv("KEA_AFL_INTERFACE");
+        if (! iface_ptr) {
+            isc_throw(FuzzInitFail, "no fuzzing interface has been set");
         }
 
-        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;
+        unsigned int iface_id = if_nametoindex(iface_ptr);
+        if (iface_id == 0) {
+            reason << "error retrieving interface ID for "
+                   << iface_ptr << ": " << strerror(errno);
+            isc_throw(FuzzInitFail, reason.str());
         }
 
-        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;
+        // Now the address.
+        const char *address_ptr = getenv("KEA_AFL_ADDRESS");
+        if (address_ptr == 0) {
+            isc_throw(FuzzInitFail, "no fuzzing address has been set");
         }
 
-        /* unclog */
-        recvfrom(sockfd, buf, 65536, MSG_DONTWAIT, NULL, NULL);
-
-        while (!ready)
-            pthread_cond_wait(&cond, &mutex);
+        // ... and the port.
+        unsigned short port = 0;
+        const char *port_ptr = getenv("KEA_AFL_PORT");
+        if (port_ptr == 0) {
+            isc_throw(FuzzInitFail, "no fuzzing port has been set");
+        }
+        try {
+            port = boost::lexical_cast<uint16_t>(port_ptr);
+        } catch (const boost::bad_lexical_cast&) {
+            reason << "cannot convert port number specification "
+                   << port_ptr << " to an integer";
+            isc_throw(FuzzInitFail, reason.str());
+        }
 
-        if (pthread_mutex_unlock(&mutex) != 0) {
-            f << "#### Failed to unlock mutex" << endl;
-           abort();
+        // Set up the IPv6 address structure.
+        memset(&servaddr_, 0, sizeof (servaddr_));
+        servaddr_.sin6_family = AF_INET6;
+        if (inet_pton(AF_INET6, address_ptr, &servaddr_.sin6_addr) != 1) {
+            reason << "inet_pton() failed: can't convert "
+                   << address_ptr << " to an IPv6 address" << endl;
+            isc_throw(FuzzInitFail, reason.str());
         }
+        servaddr_.sin6_port = htons(port);
+        servaddr_.sin6_scope_id = iface_id;
+
+        // Initialization complete.
+        LOG_INFO(fuzz_logger, FUZZ_INTERFACE)
+                 .arg(iface_ptr).arg(address_ptr).arg(port);
+
+        // Start the thread that reads the packets sent by AFL from stdin and
+        // passes them to the port on which Kea is listening.
+        fuzz_thread_ = std::thread(Fuzz::main);
+
+    } catch (const FuzzInitFail& e) {
+        // AFL tends to make it difficult to find out what exactly has failed:
+        // make sure that the error is logged.
+        LOG_ERROR(fuzz_logger, FUZZ_INIT_FAIL).arg(e.what());
+        throw;
     }
-
-    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 */
-
+// 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.
 void
-kea_fuzz_notify(void) {
-#ifdef ENABLE_AFL
-    if (getenv("AFL_CMIN")) {
-        kea_shutdown();
+Fuzz::main(void) {
+    // Create the socket throw which packets read from stdin will be send
+    // to the port on which Kea is listening.
+    int sockfd = socket(AF_INET6, SOCK_DGRAM, 0);
+    if (sockfd < 0) {
+        LOG_FATAL(fuzz_logger, FUZZ_SOCKET_CREATE_FAIL).arg(strerror(errno));
         return;
     }
 
-    raise(SIGSTOP);
-
-    if (pthread_mutex_lock(&mutex) != 0) {
-        cerr << "#### unable to lock mutex" << endl;
-        abort();
-    }
+    // Main loop.  This runs for a fixed number of iterations, after which
+    // Kea will be terminated and AFL will restart it.  The counting of loop
+    // iterations is done here with a separate variable (instead of inside
+    // inside the read loop in the server process using __AFL_LOOP) to ensure
+    // that thread running this function shutdown down between each restart
+    // of the fuzzing process.
+    auto loop = Fuzz::LOOP_COUNT;
+    while (loop-- > 0) {
+
+        // Read from stdin and continue reading (albeit after a pause) even
+        // if there is an error.  Do the same with end of files although, as
+        // the fuzzer generates them in normal operation, don't log them.
+        char buf[BUFFER_SIZE];
+        ssize_t length = read(0, buf, sizeof(buf));
+        if (length <= 0) {
+            LOG_ERROR(fuzz_logger, FUZZ_READ_FAIL).arg(strerror(errno));
+            usleep(SLEEP_INTERVAL);
+            continue;
+        }
+        LOG_DEBUG(fuzz_logger, FUZZ_DBG_TRACE_DETAIL, FUZZ_DATA_READ)
+                  .arg(length);
+
+        // Now send the data to the socket.  This will be picked up by the main
+        // loop and acted upon, after which kea_fuzz_notify will be called.
+        //
+        // The condition variables synchronize the operation: this thread
+        // will read from stdin and write to the socket.  It then blocks until
+        // the main thread has processed the packet, at which point it can read
+        // more data from stdin.
+        //
+        // Since the read() and sendto() calls are blocking, one would think
+        // that the two threads would synchronize even without locks.  However,
+        // the problem seems to arise in the generation of SIGSTOP and the
+        // tying it to a particular packet set to Kea by the fuzzer.  But this
+        // is speculation: the fact is that without the synchronization, the
+        // code doesn't work.
+
+        // Block the completion function until the data has been sent:
+        unique_lock<std::mutex> lock(sync_mutex_);
+
+        // Send the data to the main Kea thread.
+        ssize_t sent = sendto(sockfd, buf, length, 0,
+                          (struct sockaddr *) &servaddr_, sizeof(servaddr_));
+        if (sent < 0) {
+            LOG_ERROR(fuzz_logger, FUZZ_SEND_ERROR).arg(strerror(errno));
+        } else if (sent != length) {
+            LOG_WARN(fuzz_logger, FUZZ_SHORT_SEND).arg(length).arg(sent);
+        } else {
+            LOG_DEBUG(fuzz_logger, FUZZ_DBG_TRACE_DETAIL, FUZZ_SEND).arg(sent);
+        }
 
-    ready = true;
+        // If this is the last loop iteration, set the shutdown flag.  This
+        // is done under the protection of the mutex to avoid the following
+        // scenario:
+        // 
+        // a) This loop reaches its last iteration, then waits for
+        //    notify() to complete.
+        // b) notify() completes and returns.  The main processing loop checks
+        //    the shutdown flag and finds that it is not set.  It enters the
+        //    next iteration of the loop and waits to read something from the
+        //    configured interface.
+        // c) This thread resumes execution, sets the shutdown flag and exits.
+        //
+        // This would leave the main processing loop waiting for a packet that
+        // will never arrive.
+        //
+        // By setting the shutdown flag under the protection of the mutex,
+        // the notify() call will not take place until the flag is set, and
+        // the main thread will see the flag being set and exit.  We might
+        // as well close the socket at the same time.
+        if (loop <= 0) {
+            *shutdown_ptr_ = true;
+            close(sockfd);
+        }
 
-    if (pthread_cond_signal(&cond) != 0) {
-        cerr << "#### unable to cond signal" << endl;
-        abort();
+        // We now need to synchronize with the main thread.  In particular,
+        // we suspend processing until we know that the processing of the
+        // packet by Kea has finished and that the completion function has
+        // raised a SIGSTOP.
+        sync_cond_.wait(lock);
+        lock.unlock();
     }
 
-    if (pthread_mutex_unlock(&mutex) != 0) {
-        cerr << "Unable to unlock mutex" << endl;
-        abort();
-    }
-#endif /* ENABLE_AFL */
+    // Loop has exited, so we should shut down Kea.  Tidy up and signal Kea
+    // to exit.
+    LOG_DEBUG(fuzz_logger, FUZZ_DBG_TRACE, FUZZ_LOOP_EXIT);
+
+    return;
 }
 
+// Waits for the fuzzing thread to terminate.
 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();
-        }
-    }
+Fuzz::wait(void) {
+    fuzz_thread_.join();
+}
 
-#endif /* ENABLE_AFL */
+// Called by the main thread, this notifies AFL that processing for the
+// // last packet has finished.
+void
+Fuzz::notify(void) {
+    LOG_DEBUG(fuzz_logger, FUZZ_DBG_TRACE_DETAIL, FUZZ_NOTIFY_CALLED);
+    raise(SIGSTOP);
+    lock_guard<std::mutex> lock(sync_mutex_);
+    sync_cond_.notify_all();
 }
+
+#endif  // ENABLE_AFL
index 98d9cdf047807f61105842006b0354ec4ec1f1f3..341ff42561e413738ad3487e4f414954aa2859d7 100644 (file)
-/*
- * 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);
+// 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 FUZZ_H
+#define FUZZ_H
+
+#include <exceptions/exceptions.h>
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <mutex>
+#include <condition_variable>
+#include <thread>
+
+namespace isc {
+
+/// @brief AFL Fuzzing Functions
+
+class Fuzz {
+public:
+    /// @brief Initializes Kea fuzzing
+    ///
+    /// This takes one parameter, which is a pointer to the shutdown flag,
+    /// Dhcp6Srv::shutdown_. Kea runs until something sets this flag to true,
+    /// which is an indication to shutdown Kea.
+    ///
+    /// In the case of fuzzing, the shutdown flag is set when a fixed number of
+    /// packets has been received from the fuzzer.  At this point, the fuzzer
+    /// shutdow down Kea and restarts it.
+    ///
+    /// @param shutdown Pointer to boolean flag that will be set to true to
+    ///        trigger the shutdown procedure.
+    static void init(volatile bool* shutdown);
+
+    /// @brief Main Kea Fuzzing Function
+    ///
+    /// This is the main Kea fuzzing function.  It is the entry point for the
+    /// thread that handles the interface between AFL and Kea.  The function
+    /// receives data from the fuzzing engine via stdin, and then sends it to
+    /// the configured UDP socket.  Kea reads it from there, processes it and
+    /// when processing is complete, calls the notification function.
+    ///
+    /// After a given number of packets, this function will shut down Kea.  This
+    /// is recommended by AFL as it avoids any resource leaks (which are not
+    /// caught by AFL) from getting too large and interfering with the fuzzing.
+    ///  AFL will automatically restart the program to continue fuzzing.
+    ///
+    /// Since this runs in a separate thread, errors are logged via the fuzzing
+    /// logger. (Other than initialization - when the thread is not running -
+    /// this is the only use of the fuzzing logger.) If the error is fatal, the
+    /// thread will terminate, something that may cause the fuzzer to hang.
+    static void kea_fuzz_main(void);
+
+    /// @brief Fuzzing Thread
+    ///
+    /// This function is run in a separate thread.  It loops, reading input
+    /// from AFL that appears on stdin and writing it to the address/port on
+    /// which Kea is listening.
+    ///
+    /// After writing the data to the Kea address/port, the function waits
+    /// until processing of the data is complete (as indicated by a condition
+    /// variable set by the notification function) before reading the next
+    /// input.
+    static void main(void);
+
+    /// @brief Notify fuzzing thread that processing is complete
+    ///
+    /// This function is called by the main Kea processing loop when it has
+    /// finished processing a packet.  It raises a SIGSTOP signal, which tells
+    /// the AFL fuzzer that processing for the data it has just sent has
+    /// finished; this causes it to send another fuzzed packet to stdin. It
+    /// also sets a condition variable, so releasing the fuzzing thread to
+    /// read the next data from AFL.
+    static void notify(void);
+
+    /// @brief Wait for fuzzing thread to exit
+    ///
+    /// A short function, called after the fuzzing thread is supposed to have
+    /// finished, to ensure that it really has finished.
+    static void wait(void);
+
+    /// @brief size of the buffer used to transfer data between AFL and Kea.
+    static constexpr size_t BUFFER_SIZE = 65536;
+
+    // @brief Delay before rereading if read from stdin returns an error (us)
+    static constexpr  useconds_t SLEEP_INTERVAL = 50000;
+
+    /// @brief Number of many packets Kea will process until shutting down.
+    ///
+    /// After the shutdown, AFL ill restart it. This safety switch is here for
+    ///  eliminating cases where Kea goes into a weird state and stops
+    ///  processing packets properly.
+    static constexpr long LOOP_COUNT = 1000;
+
+    // Member variables
+
+    /// @brief Condition variable to synchronize between threads.
+    static std::condition_variable  sync_cond_;
+
+    /// @brief Mutex to synchronize between threads.
+    static std::mutex               sync_mutex_;
+
+    /// @brief Holds the separate thread that reads from stdin
+    static std::thread              fuzz_thread_;
+
+    /// @brief Socket structure used for sending data to main thread
+    static struct sockaddr_in6      servaddr_;
+
+    /// @brief Pointer to the Kea shutdown flag
+    static volatile bool*           shutdown_ptr_;
 };
 
-#endif /* KEA_FUZZ_H */
+
+/// @brief Exception thrown if fuzzing initialization fails.
+class FuzzInitFail : public Exception {
+public:
+    FuzzInitFail(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+}
+
+#endif // FUZZ_H
diff --git a/src/lib/dhcpsrv/fuzz_log.cc b/src/lib/dhcpsrv/fuzz_log.cc
new file mode 100644 (file)
index 0000000..e214aa0
--- /dev/null
@@ -0,0 +1,23 @@
+// Copyright (C) 2015-2018 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/.
+
+/// @brief Defines the logger used by the @c isc::dhcp::HostMgr
+
+#include <config.h>
+
+#include "dhcpsrv/fuzz_log.h"
+
+namespace isc {
+namespace dhcp {
+
+extern const int FUZZ_DBG_TRACE = isc::log::DBGLVL_TRACE_BASIC;
+extern const int FUZZ_DBG_TRACE_DETAIL = isc::log::DBGLVL_TRACE_DETAIL;
+
+isc::log::Logger fuzz_logger("fuzz");
+
+} // namespace dhcp
+} // namespace isc
+
diff --git a/src/lib/dhcpsrv/fuzz_log.h b/src/lib/dhcpsrv/fuzz_log.h
new file mode 100644 (file)
index 0000000..89a3820
--- /dev/null
@@ -0,0 +1,43 @@
+// Copyright (C) 2015-2018 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 FUZZ_LOG_H
+#define FUZZ_LOG_H
+
+#include <dhcpsrv/fuzz_messages.h>
+#include <log/macros.h>
+
+namespace isc {
+namespace dhcp {
+
+///@{
+/// \brief Logging levels for fuzzing output
+///
+/// Defines the levels used to output debug messages during fuzzing.
+
+/// @brief Traces normal operations
+///
+/// An example of the normal operation is a report of a packet being received
+/// from the fuzzer.
+extern const int FUZZ_DBG_TRACE;
+
+/// @brief Record detailed traces
+///
+/// Messages logged at this level will log detailed tracing information.
+extern const int FUZZ_DBG_TRACE_DETAIL;
+
+///@}
+
+/// @brief Logger for the @c HostMgr and the code it calls.
+///
+/// Define the logger used to log messages in @c HostMgr and the code it
+/// calls to manage host reservations.
+extern isc::log::Logger fuzz_logger;
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // FUZZ_LOG_H
diff --git a/src/lib/dhcpsrv/fuzz_messages.mes b/src/lib/dhcpsrv/fuzz_messages.mes
new file mode 100644 (file)
index 0000000..86ec499
--- /dev/null
@@ -0,0 +1,57 @@
+# Copyright (C) 2015-2019 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/.
+
+$NAMESPACE isc::dhcp
+
+% FUZZ_DATA_READ read %1 byte(s) from AFL via stdin
+A debug message output to indicate how much data has been received from
+the fuzzer.
+
+% FUZZ_INTERFACE fuzzing will use interface %1 (address %2, port %3)
+An informational message output during fuzzing initialization, this reports
+the details of the interface to be used for fuzzing.
+
+% FUZZ_INIT_FAIL fuzz initialization failure, reason: %1
+A generic message reported via standard Kea logging if the fuzzing
+initialization failed.  The reason for the failure is given in the message.
+
+% FUZZ_LOOP_EXIT main loop has exited, shutting down Kea
+This debug message is output when Kea has processed the number of packets
+given by the hard-coded variable Fuzz::LOOP_COUNT.  Kea is shutting
+itself down and will be restarted by AFL.  This is recommended by the AFL
+documentation as a way of avoiding issues if Kea gets itself into a funny
+state after running for a long time.
+
+% FUZZ_NOTIFY_CALLED kea_fuzz_notify() has been called
+A debug message indicating that the processing of a packet by Kea has
+finished and that the kea_fuzz_notify() function has been called (which
+raises a SIGSTOP informing AFL that Kea is ready to receive another packet).
+
+% FUZZ_READ_FAIL error reading input from fuzzer: %1
+This error is reported if the read of data from the fuzzer (which is received
+over stdin) fails.  If this occurs, the thread will sleep for a short period
+before retrying the read.  The message gives the reason for the failure.
+
+% FUZZ_SEND sent %1 byte(s) to the socket connected to the Kea interface
+A debug message stating that the sendto() call in the main fuzzing function
+has successfully completed.
+
+% FUZZ_SEND_ERROR failed to send data to Kea input socket: %1
+This error will be reported if the sendto() call in the fuzzing thread (which
+sends data received from AFL to the socket on which Kea is listening) fails.
+The reason for the failure is given in the message.  The fuzzing code will
+attempt to continue from this, but it may cause the fuzzing process to fail.
+
+% FUZZ_SHORT_SEND expected to send %d bytes to Kea input socket but only sent %2
+A warning message that is output if the sendto() call (used to send data
+from the fuzzing thread to the main Kea processing) did not send as much
+data as that read from AFL.  This may indicate a problem in the underlying
+communications between the fuzzing thread and the main Kea processing; the c
+
+% FUZZ_SOCKET_CREATE_FAIL failed to crease socket for use by fuzzing thread: %1
+An error message output when the fuzzing code has failed to create a socket
+through which is will copy data received on stdin from the AFL fuzzer to
+the port on which