Proof of concept code to fuzz Kea-DHCPv6.
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
Generate Messages Files: $enable_generate_messages
Perfdhcp: $enable_perfdhcp
Kea-shell: $shell_report
+ Enable fuzz: $enable_fuzz
END
--- /dev/null
+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.
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
#endif
#include <dhcpsrv/memfile_lease_mgr.h>
+#include <dhcp6/fuzz.h>
+
#include <boost/bind.hpp>
#include <boost/foreach.hpp>
#include <boost/tokenizer.hpp>
}
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();
// 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);
--- /dev/null
+/*
+ * 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 */
+}
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+# 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"
+ }
+ ]
+}
+
+}