fuzz.cc 7.24 KB
Newer Older
1
// Copyright (C) 2016-2019  Internet Systems Consortium, Inc. ("ISC")
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
//
// 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>

#ifdef ENABLE_AFL

#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 <boost/lexical_cast.hpp>

#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

#include <iostream>
#include <sstream>
#include <fstream>
#include <ctime>

using namespace isc;
using namespace isc::dhcp;
using namespace std;

// Constants defined in the Fuzz class definition.
constexpr size_t        Fuzz::BUFFER_SIZE;
constexpr size_t        Fuzz::MAX_SEND_SIZE;
38
constexpr long          Fuzz::MAX_LOOP_COUNT;
39 40

// Constructor
41 42 43
Fuzz::Fuzz(int ipversion, uint16_t port) :
    address_(nullptr), interface_(nullptr), loop_max_(MAX_LOOP_COUNT),
    port_(port), sockaddr_len_(0), sockaddr_ptr_(nullptr), sockfd_(-1) {
44 45 46 47 48 49 50

    try {
        stringstream reason;    // Used to construct exception messages

        // Set up address structures.
        setAddress(ipversion);

51 52 53 54 55 56 57 58 59 60
        // Create the socket through which packets read from stdin will be sent
        // to the port on which Kea is listening.  This is closed in the
        // destructor.
        sockfd_ = socket((ipversion == 4) ? AF_INET : AF_INET6, SOCK_DGRAM, 0);
        if (sockfd_ < 0) {
            LOG_FATAL(fuzz_logger, FUZZ_SOCKET_CREATE_FAIL)
                      .arg(strerror(errno));
            return;
        }

61
        // Check if the hard-coded maximum loop count is being overridden
62
        const char *loop_max_ptr = getenv("KEA_AFL_LOOP_MAX");
63 64 65 66 67 68 69 70 71 72
        if (loop_max_ptr != 0) {
            try {
                loop_max_ = boost::lexical_cast<long>(loop_max_ptr);
            } catch (const boost::bad_lexical_cast&) {
                reason << "cannot convert port number specification "
                       << loop_max_ptr << " to an integer";
                isc_throw(FuzzInitFail, reason.str());
            }

            if (loop_max_ <= 0) {
73
                reason << "KEA_AFL_LOOP_MAX is " << loop_max_ << ". "
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
                       << "It must be an integer greater than zero.";
                isc_throw(FuzzInitFail, reason.str());
            }
        }

    } 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_FATAL(fuzz_logger, FUZZ_INIT_FAIL).arg(e.what());
        throw;
    }

    LOG_INFO(fuzz_logger, FUZZ_INIT_COMPLETE).arg(interface_).arg(address_)
             .arg(port_).arg(loop_max_);
}

// Destructor
Fuzz::~Fuzz() {
92
    static_cast<void>(close(sockfd_));
93 94 95 96 97 98 99 100
}

// Parse IP address/port/interface and set up address structures.
void
Fuzz::setAddress(int ipversion) {
    stringstream reason;    // Used in error messages

    // Get the environment for the fuzzing: interface, address and port.
101
    interface_ = getenv("KEA_AFL_INTERFACE");
102 103 104 105
    if (! interface_) {
        isc_throw(FuzzInitFail, "no fuzzing interface has been set");
    }

106 107 108
    // Now the address. (The port is specified via the "-p" command-line
    // switch and passed to this object through the constructor.)
    address_ = getenv("KEA_AFL_ADDRESS");
109 110 111 112
    if (address_ == 0) {
        isc_throw(FuzzInitFail, "no fuzzing address has been set");
    }

113 114 115 116
    // Set up the appropriate data structure depending on the address given.
    if ((strstr(address_, ":") != NULL) && (ipversion == 6)) {
        // Expecting IPv6 and the address contains a colon, so assume it is an
        // an IPv6 address.
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
        memset(&servaddr6_, 0, sizeof (servaddr6_));

        servaddr6_.sin6_family = AF_INET6;
        if (inet_pton(AF_INET6, address_, &servaddr6_.sin6_addr) != 1) {
            reason << "inet_pton() failed: can't convert "
                   << address_ << " to an IPv6 address" << endl;
            isc_throw(FuzzInitFail, reason.str());
        }
        servaddr6_.sin6_port = htons(port_);

        // Interface ID is needed for IPv6 address structures.
        servaddr6_.sin6_scope_id = if_nametoindex(interface_);
        if (servaddr6_.sin6_scope_id == 0) {
            reason << "error retrieving interface ID for "
                   << interface_ << ": " << strerror(errno);
            isc_throw(FuzzInitFail, reason.str());
        }

        sockaddr_ptr_ = reinterpret_cast<sockaddr*>(&servaddr6_);
        sockaddr_len_ = sizeof(servaddr6_);
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154

    } else if ((strstr(address_, ".") != NULL) && (ipversion == 4)) {
        // Expecting an IPv4 address and it contains a dot, so assume it is.
        // This check is done after the IPv6 check, as it is possible for an
        // IPv4 address to be emnbedded in an IPv6 one.
        memset(&servaddr4_, 0, sizeof(servaddr4_));

        servaddr4_.sin_family = AF_INET;
        if (inet_pton(AF_INET, address_, &servaddr4_.sin_addr) != 1) {
            reason << "inet_pton() failed: can't convert "
                   << address_ << " to an IPv6 address" << endl;
            isc_throw(FuzzInitFail, reason.str());
        }
        servaddr4_.sin_port = htons(port_);

        sockaddr_ptr_ = reinterpret_cast<sockaddr*>(&servaddr4_);
        sockaddr_len_ = sizeof(servaddr4_);

155 156 157 158 159 160 161 162 163 164
    } else {
        reason << "Expected IP version (" << ipversion << ") is not "
               << "4 or 6, or the given address " << address_ << " does not "
               << "match the IP version expected";
        isc_throw(FuzzInitFail, reason.str());
    }

}


165 166
// This is the main fuzzing function. It receives data from fuzzing engine over
// stdin and then sends it to the configured UDP socket.
167
void
168
Fuzz::transfer(void) {
169

170 171 172 173
    // Read from stdin.  Just return if nothing is read (or there is an error)
    // and hope that this does not cause a hang.
    char buf[BUFFER_SIZE];
    ssize_t length = read(0, buf, sizeof(buf));
174

175 176 177 178 179
    // Save the errno in case there was an error because if debugging is
    // enabled, the following LOG_DEBUG call may destroy its value.
    int errnum = errno;
    LOG_DEBUG(fuzz_logger, FUZZ_DBG_TRACE_DETAIL, FUZZ_DATA_READ)
              .arg(length);
180

181 182
    if (length > 0) {
        // Now send the data to the UDP port on which Kea is listening.
183 184 185
        // Send the data to the main Kea thread.  Limit the size of the
        // packets that can be sent.
        size_t send_len = (length < MAX_SEND_SIZE) ? length : MAX_SEND_SIZE;
186
        ssize_t sent = sendto(sockfd_, buf, send_len, 0, sockaddr_ptr_,
187
                              sockaddr_len_);
188 189
        if (sent > 0) {
            LOG_DEBUG(fuzz_logger, FUZZ_DBG_TRACE_DETAIL, FUZZ_SEND).arg(sent);
190 191 192
        } else if (sent != length) {
            LOG_WARN(fuzz_logger, FUZZ_SHORT_SEND).arg(length).arg(sent);
        } else {
193
            LOG_ERROR(fuzz_logger, FUZZ_SEND_ERROR).arg(strerror(errno));
194
        }
195 196 197 198 199
    } else {
        // Read did not get any bytes.  A zero-length read (EOF) may have been
        // generated by AFL, so don't log that.  But otherwise log an error.
        if (length != 0) {
            LOG_ERROR(fuzz_logger, FUZZ_READ_FAIL).arg(strerror(errnum));
200 201 202 203 204 205
        }
    }

}

#endif  // ENABLE_AFL