Kea 3.1.9
packet_fuzzer.cc
Go to the documentation of this file.
1// Copyright (C) 2016-2026 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
9#ifdef FUZZING
10
11#include <asiolink/io_address.h>
12#include <dhcp/dhcp6.h>
14#include <dhcpsrv/fuzz_log.h>
15
16#include <boost/lexical_cast.hpp>
17
18#include <errno.h>
19#include <stdlib.h>
20#include <string.h>
21#include <signal.h>
22
23#include <algorithm>
24#include <cstdlib>
25#include <iostream>
26#include <sstream>
27#include <fstream>
28#include <ctime>
29
30using namespace isc;
31using namespace isc::asiolink;
32using namespace isc::dhcp;
33using namespace std;
34
35// Constants defined in the PacketFuzzer class definition.
36constexpr size_t PacketFuzzer::BUFFER_SIZE;
37constexpr size_t PacketFuzzer::MAX_SEND_SIZE;
38constexpr long PacketFuzzer::MAX_LOOP_COUNT;
39
40// Constructor
41PacketFuzzer::PacketFuzzer(uint16_t const port,
42 string const interface,
43 string const address)
44 : loop_max_(MAX_LOOP_COUNT), sockaddr_len_(0), sockaddr_ptr_(nullptr), sockfd_(-1) {
45
46 try {
47 stringstream reason; // Used to construct exception messages
48
49 // Number of Kea packet-read loops before Kea exits and AFL starts a
50 // new instance. This is optional: the default is set by the constant
51 // MAX_LOOP_COUNT.
52 const char *loop_max_ptr(nullptr);
53#ifdef HAVE_AFL
54 loop_max_ptr = getenv("KEA_AFL_LOOP_MAX");
55#endif
56 if (loop_max_ptr) {
57 try {
58 loop_max_ = boost::lexical_cast<long>(loop_max_ptr);
59 } catch (const boost::bad_lexical_cast&) {
60 isc_throw(FuzzInitFail,
61 "cannot convert loop count " << loop_max_ptr << " to an integer");
62 }
63
64 if (loop_max_ <= 0) {
65 isc_throw(FuzzInitFail, "KEA_AFL_LOOP_MAX is "
66 << loop_max_ << ". "
67 << "It must be an integer greater than zero.");
68 }
69 }
70
71 IOAddress io_address(address);
72
73 // Set up address structures used to route the packets from AFL to Kea.
74 createAddressStructures(port, interface, io_address);
75
76 // Create the socket through which packets read from stdin will be sent
77 // to the port on which Kea is listening. This is closed in the
78 // destructor.
79 sockfd_ = socket(io_address.isV4() ? AF_INET : AF_INET6, SOCK_DGRAM, 0);
80 if (sockfd_ < 0) {
82 .arg(strerror(errno));
83 return;
84 }
85
86 LOG_INFO(fuzz_logger, FUZZ_INIT_COMPLETE).arg(interface).arg(address)
87 .arg(port).arg(loop_max_);
88
89 } catch (const FuzzInitFail& e) {
90 // AFL tends to make it difficult to find out what exactly has failed:
91 // make sure that the error is logged.
92 LOG_FATAL(fuzz_logger, FUZZ_INIT_FAIL).arg(e.what());
93 throw;
94 }
95}
96
97// Destructor
98PacketFuzzer::~PacketFuzzer() {
99 static_cast<void>(close(sockfd_));
100}
101
102// Set up address structures.
103void
104PacketFuzzer::createAddressStructures(uint16_t const port,
105 string const& interface,
106 IOAddress const& io_address) {
107 string const address(io_address.toText());
108
109 // Set up the appropriate data structure depending on the address given.
110 if (io_address.isV6()) {
111 // Expecting IPv6 and the address contains a colon, so assume it is an
112 // an IPv6 address.
113 memset(&servaddr6_, 0, sizeof (servaddr6_));
114
115 servaddr6_.sin6_family = AF_INET6;
116 if (inet_pton(AF_INET6, address.c_str(), &servaddr6_.sin6_addr) != 1) {
117 isc_throw(FuzzInitFail,
118 "inet_pton() failed: can't convert " << address << " to an IPv6 address");
119 }
120 servaddr6_.sin6_port = htons(port);
121
122 // Interface ID is needed for IPv6 address structures.
123 servaddr6_.sin6_scope_id = if_nametoindex(interface.c_str());
124 if (servaddr6_.sin6_scope_id == 0) {
125 isc_throw(FuzzInitFail,
126 "error retrieving interface ID for " << interface << ": " << strerror(errno));
127 }
128
129 sockaddr_ptr_ = reinterpret_cast<sockaddr*>(&servaddr6_);
130 sockaddr_len_ = sizeof(servaddr6_);
131
132 } else if (io_address.isV4()) {
133 // Expecting an IPv4 address and it contains a dot, so assume it is.
134 // This check is done after the IPv6 check, as it is possible for an
135 // IPv4 address to be embedded in an IPv6 one.
136 memset(&servaddr4_, 0, sizeof(servaddr4_));
137
138 servaddr4_.sin_family = AF_INET;
139 if (inet_pton(AF_INET, address.c_str(), &servaddr4_.sin_addr) != 1) {
140 isc_throw(FuzzInitFail,
141 "inet_pton() failed: can't convert " << address << " to an IPv4 address");
142 }
143 servaddr4_.sin_port = htons(port);
144
145 sockaddr_ptr_ = reinterpret_cast<sockaddr*>(&servaddr4_);
146 sockaddr_len_ = sizeof(servaddr4_);
147
148 } else {
149 // Should never happen.
150 isc_throw(FuzzInitFail, "unknown IOAddress IP version");
151 }
152}
153
154void
155PacketFuzzer::transfer() const {
156 // Read from stdin. Just return if nothing is read (or there is an error)
157 // and hope that this does not cause a hang.
158 uint8_t buf[BUFFER_SIZE];
159 ssize_t const length(read(0, buf, sizeof(buf)));
160
161 transfer(&buf[0], length);
162}
163
164// This is the main fuzzing function. It receives data from fuzzing engine over
165// stdin and then sends it to the configured UDP socket.
166void
167PacketFuzzer::transfer(uint8_t const* data, size_t size) const {
168 char buf[BUFFER_SIZE];
169 ssize_t const length(size);
170
171 if (data) {
172 memcpy(&buf[0], data, min(BUFFER_SIZE, size));
173 }
174
175 // Save the errno in case there was an error because if debugging is
176 // enabled, the following LOG_DEBUG call may destroy its value.
177 int errnum = errno;
179
180 if (length > 0) {
181 // Now send the data to the UDP port on which Kea is listening.
182 // Send the data to the main Kea thread. Limit the size of the
183 // packets that can be sent.
184 size_t send_len = (size < MAX_SEND_SIZE) ? size : MAX_SEND_SIZE;
185 ssize_t sent = sendto(sockfd_, buf, send_len, 0, sockaddr_ptr_,
186 sockaddr_len_);
187 if (sent < 0) {
188 LOG_ERROR(fuzz_logger, FUZZ_SEND_ERROR).arg(strerror(errno));
189 } else if (sent != length) {
190 LOG_WARN(fuzz_logger, FUZZ_SHORT_SEND).arg(length).arg(sent);
191 } else {
193 }
194 } else {
195 // Read did not get any bytes. A zero-length read (EOF) may have been
196 // generated by AFL, so don't log that. But otherwise log an error.
197 if (length != 0) {
198 LOG_ERROR(fuzz_logger, FUZZ_READ_FAIL).arg(strerror(errnum));
199 }
200 }
201}
202
203#endif // FUZZING
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition macros.h:32
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition macros.h:20
#define LOG_WARN(LOGGER, MESSAGE)
Macro to conveniently test warn output and log it.
Definition macros.h:26
#define LOG_FATAL(LOGGER, MESSAGE)
Macro to conveniently test fatal output and log it.
Definition macros.h:38
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition macros.h:14
const isc::log::MessageID FUZZ_INIT_FAIL
const isc::log::MessageID FUZZ_SHORT_SEND
const isc::log::MessageID FUZZ_INIT_COMPLETE
const isc::log::MessageID FUZZ_SEND_ERROR
const int FUZZ_DBG_TRACE_DETAIL
Record detailed traces.
Definition fuzz_log.h:30
const isc::log::MessageID FUZZ_SOCKET_CREATE_FAIL
isc::log::Logger fuzz_logger("fuzz")
Logger for the HostMgr and the code it calls.
Definition fuzz_log.h:38
const isc::log::MessageID FUZZ_READ_FAIL
const isc::log::MessageID FUZZ_DATA_READ
const isc::log::MessageID FUZZ_SEND
Defines the logger used by the top-level component of kea-lfc.