Commit 4939b823 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰 Committed by Stephen Morris

[#640] Initial fuzzing code added for Kea6

Proof of concept code to fuzz Kea-DHCPv6.
parent e6471c98
......@@ -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
......
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.
......@@ -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
......
......@@ -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);
......
/*
* 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 */
}
/*
* 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 */
# 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"
}
]
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment