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