From: Stephen Morris Date: Wed, 12 Jun 2019 09:22:02 +0000 (+0100) Subject: [#640,!351] Convert fuzzing code to C++ class X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=adbc2cce6039fd7c74438c07b0c4ff8a8ceccf68;p=thirdparty%2Fkea.git [#640,!351] Convert fuzzing code to C++ class Also add logging via Kea logging subsystem. --- diff --git a/configure.ac b/configure.ac index 85ee8b2ac6..ee431356b9 100755 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index b5f4a1a155..d28992a2ec 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -65,8 +66,6 @@ #endif #include -#include - #include #include #include @@ -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); } diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am index ce80221747..5825db402b 100644 --- a/src/lib/dhcpsrv/Makefile.am +++ b/src/lib/dhcpsrv/Makefile.am @@ -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 diff --git a/src/lib/dhcpsrv/fuzz.cc b/src/lib/dhcpsrv/fuzz.cc index a2f50ad32b..0824355c69 100644 --- a/src/lib/dhcpsrv/fuzz.cc +++ b/src/lib/dhcpsrv/fuzz.cc @@ -1,280 +1,252 @@ -/* - * 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 - -#define ENABLE_AFL +#include #ifdef ENABLE_AFL -#include + +#ifndef __AFL_LOOP +#error To use American Fuzzy Lop you have to set CXX to afl-clang-fast++ +#endif #include +#include +#include -#include -#include -#include +#include +#include #include #include #include -#include -#include -#include -#include - -#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 +#include +#include +#include +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(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 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 lock(sync_mutex_); + sync_cond_.notify_all(); } + +#endif // ENABLE_AFL diff --git a/src/lib/dhcpsrv/fuzz.h b/src/lib/dhcpsrv/fuzz.h index 98d9cdf047..341ff42561 100644 --- a/src/lib/dhcpsrv/fuzz.h +++ b/src/lib/dhcpsrv/fuzz.h @@ -1,29 +1,129 @@ -/* - * 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 + +#include +#include +#include +#include + +#include +#include +#include + +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 index 0000000000..e214aa0a2d --- /dev/null +++ b/src/lib/dhcpsrv/fuzz_log.cc @@ -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 + +#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 index 0000000000..89a3820d2d --- /dev/null +++ b/src/lib/dhcpsrv/fuzz_log.h @@ -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 +#include + +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 index 0000000000..86ec49945d --- /dev/null +++ b/src/lib/dhcpsrv/fuzz_messages.mes @@ -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