- Separate ENABLE_AFL into ENABLE_FUZZING and HAVE_AFL.
- Add the --disable-unicode flag required in the oss-fuzz container.
- Add checking of support for C++17.
- Make Kea compile with afl++.
- Rotate ports in `getServerPort()` functions under an env var.
- Fix some destruction issues that would result in crashes when fuzzing.
- Add some checks in the UnixControlClient that prevent some crashes when fuzzing.
- Add `isc::util::isSocket()` function.
- Change `isc::util::file::Path` to not append a trailing slash to allow
chained calls of `parentPath()`.
- Add `isc::util::file::TemporaryDirectory` useful when fuzzing.
# Check for C++14 features support
AX_ISC_CPP14
+# Check for C++17 features support
+AX_ISC_CPP17
+
# Check for C++20 compiler support.
AX_ISC_CPP20
CPPP="$CPPP -P"
fi
+# Kea does not support unicode aka wide character strings. Some systems force it
+# by default in headers. Provide a way to explicitly disable it.
+AC_ARG_ENABLE(unicode,
+ [AS_HELP_STRING([--disable-unicode], [Explicitly disable unicode])],
+ [case "${enableval}" in
+ yes) AC_MSG_ERROR(["You are trying to explicitly enable unicode. Kea does not support unicode."]) ;;
+ no) KEA_CXXFLAGS="${KEA_CXXFLAGS} -U_UNICODE -UUNICODE" ;;
+ esac])
+
case "$host" in
*-solaris*)
MULTITHREADING_FLAG=-pthreads
# usable or not.
# Let's be optimistic and assume it is by testing only the negative case.
if test "${usable_regex}" = 'no'; then
- AC_MSG_ERROR([Need proper regex functionality.])]
+ AC_MSG_ERROR([Need proper regex functionality.])
fi
# Check for NETCONF support. If NETCONF was enabled in the build, and this check
found_valgrind="found"
fi
+AC_MSG_CHECKING([for fuzzing])
AC_ARG_ENABLE([fuzzing],
- [AS_HELP_STRING([--enable-fuzzing],
- [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_fuzzing=$enableval], [enable_fuzzing=no])
-AM_CONDITIONAL([ENABLE_AFL], [test x$enable_fuzzing != xno])
-
-if test "x$enable_fuzzing" != "xno" ; then
- AC_DEFINE([ENABLE_AFL], [1], [AFL fuzzing was enabled.])
- AC_MSG_CHECKING([for AFL enabled compiler])
- AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],
- [#ifndef __AFL_COMPILER
- #error AFL compiler required
- #endif
- ])],
- [AC_MSG_RESULT([yes])],
- [AC_MSG_ERROR([set CXX to afl-clang-fast++ when --enable-fuzzing is used])])
+ [AS_HELP_STRING(
+ [--enable-fuzzing[[=mode]]],
+ [indicates that the code will be built for fuzzing purposes.
+ Code built this way is unusable as a regular server.
+ Mode can be ci or standalone. [default=no]])],
+ [if test ! "${CPP17_SUPPORTED}"; then
+ AC_MSG_RESULT("no. Fuzzing requires C++17 support.")
+ AC_MSG_ERROR("Fuzzing requires C++17 support.")
+ fi
+ enable_fuzzing=${enableval}],
+ [enable_fuzzing=no]
+)
+AM_CONDITIONAL([FUZZING], [test "${enable_fuzzing}" != 'no'])
+AM_CONDITIONAL([FUZZING_IN_CI], [test "${enable_fuzzing}" = 'ci'])
+fuzzing_enabled='no'
+if test "${enable_fuzzing}" != 'no' ; then
+ fuzzing_enabled='yes'
+ AC_DEFINE([FUZZING], [true], [Fuzzing enabled.])
+
+ if test "${enable_fuzzing}" = 'ci'; then
+ fuzzing_enabled='yes, running in CI'
+ AC_DEFINE([FUZZING_IN_CI], [true], [Fuzzing running in CI.])
+ fi
+fi
+AC_MSG_RESULT(${fuzzing_enabled})
+
+# Check for AFL.
+AC_MSG_CHECKING([for AFL compiler])
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],
+ [#ifndef __AFL_COMPILER
+ #error AFL compiler required
+ #endif
+ ])],
+ [have_afl='yes'],
+ [have_afl='no'])
+AC_MSG_RESULT([${have_afl}])
+AM_CONDITIONAL([HAVE_AFL], [test "${have_afl}" = 'yes'])
+if test "${have_afl}" = 'yes'; then
+ AC_DEFINE([HAVE_AFL], [true], [AFL compiler 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
Generate Messages Files: $enable_generate_messages
Perfdhcp: $enable_perfdhcp
Kea-shell: $shell_report
- Enable fuzzing: $enable_fuzzing
+ Fuzzing: $fuzzing_enabled
+ AFL: $have_afl
END
feature=
for retry in "none" "--std=c++14" "--std=c++1y" "fail"; do
if test "$retry" = "fail"; then
+ AC_MSG_CHECKING([c++14 support])
+ AC_MSG_RESULT([no])
AC_MSG_ERROR([$feature (a C++14 feature) is not supported])
fi
if test "$retry" != "none"; then
break
done
+AC_MSG_CHECKING([c++14 support])
+AC_MSG_RESULT([yes])
+
])
--- /dev/null
+AC_DEFUN([AX_ISC_CPP17], [
+ AC_MSG_CHECKING([c++17 support])
+
+ # Save flags.
+ CPPFLAGS_SAVED="${CPPFLAGS}"
+
+ # Provide -std=c++17 flag temporarily.
+ CPPFLAGS="${CPPFLAGS} -std=c++17"
+
+ # Check that the filesystem library is supported.
+ AC_LINK_IFELSE(
+ [AC_LANG_PROGRAM(
+ [#include <filesystem>],
+ [std::filesystem::path cwd = std::filesystem::current_path();]
+ )],
+ [AC_MSG_RESULT([yes])
+ CPP17_SUPPORTED=true],
+ [AC_MSG_RESULT([no])
+ CPP17_SUPPORTED=false])
+
+ # Restore flags.
+ CPPFLAGS="${CPPFLAGS_SAVED}"
+])
AC_DEFUN([AX_ISC_CPP20], [
- AC_MSG_CHECKING(c++20 support)
+ AC_MSG_CHECKING([c++20 support])
# Save flags.
CPPFLAGS_SAVED="${CPPFLAGS}"
- LIBS_SAVED="${LIBS}"
# Provide -std=c++20 flag temporarily.
CPPFLAGS="${CPPFLAGS} -std=c++20"
#include <dhcpsrv/cfg_shared_networks.h>
#include <dhcpsrv/cfg_subnets4.h>
#include <dhcpsrv/dhcpsrv_exceptions.h>
-#include <dhcpsrv/fuzz.h>
#include <dhcpsrv/host_data_source_factory.h>
#include <dhcpsrv/host_mgr.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/ncr_generator.h>
+#include <dhcpsrv/packet-fuzzer.h>
#include <dhcpsrv/resource_handler.h>
#include <dhcpsrv/shared_network.h>
#include <dhcpsrv/subnet.h>
#include <stats/stats_mgr.h>
#include <util/encode/encode.h>
#include <util/str.h>
+#include <log/interprocess/interprocess_sync_file.h>
#include <log/logger.h>
#include <cryptolink/cryptolink.h>
#include <process/cfgrpt/config_report.h>
#include <boost/pointer_cast.hpp>
#include <boost/shared_ptr.hpp>
+
+#include <chrono>
#include <functional>
#include <iomanip>
#include <set>
using namespace isc::dhcp_ddns;
using namespace isc::hooks;
using namespace isc::log;
+using namespace isc::log::interprocess;
using namespace isc::stats;
using namespace isc::util;
using namespace std;
+using namespace std::chrono_literals;
namespace ph = std::placeholders;
namespace {
int
Dhcpv4Srv::run() {
-#ifdef ENABLE_AFL
+#ifdef HAVE_AFL
+ // Get the values of the environment variables used to control the
+ // fuzzing.
+
+ // Specfies the interface to be used to pass packets from AFL to Kea.
+ const char* interface = getenv("KEA_AFL_INTERFACE");
+ if (!interface) {
+ isc_throw(FuzzInitFail, "no fuzzing interface has been set");
+ }
+
+ // The address on the interface to be used.
+ const char* address = getenv("KEA_AFL_ADDRESS");
+ if (!address) {
+ isc_throw(FuzzInitFail, "no fuzzing address has been set");
+ }
+
// Set up structures needed for fuzzing.
- Fuzz fuzzer(4, server_port_);
- //
+ PacketFuzzer fuzzer(4, server_port_, interface, address);
+
// The next line is needed as a signature for AFL to recognize that we are
// running persistent fuzzing. This has to be in the main image file.
while (__AFL_LOOP(fuzzer.maxLoopCount())) {
fuzzer.transfer();
#else
while (!shutdown_) {
-#endif // ENABLE_AFL
+#endif // HAVE_AFL
try {
runOne();
// Handle events registered by hooks using external IOService objects.
HooksManager::clearParkingLots();
}
+uint16_t Dhcpv4Srv::getServerPort() const {
+ char const* const randomize(getenv("KEA_DHCP4_FUZZING_RANDOMIZE_PORT"));
+ if (randomize) {
+ InterprocessSyncFile file("kea-dhcp4-fuzzing-randomize-port");
+ InterprocessSyncLocker locker(file);
+ while (!locker.lock()) {
+ this_thread::sleep_for(1s);
+ }
+ fstream port_file;
+ port_file.open("/tmp/port4.txt", ios::in);
+ string line;
+ int port;
+ getline(port_file, line);
+ port_file.close();
+ if (line.empty()) {
+ port = 2000;
+ } else {
+ port = stoi(line);
+ if (port < 3000) {
+ ++port;
+ } else {
+ port = 2000;
+ }
+ }
+ port_file.open("/tmp/port4.txt", ios::out | ios::trunc);
+ port_file << to_string(port) << endl;
+ port_file.close();
+ locker.unlock();
+ return port;
+ }
+ return server_port_;
+}
+
std::list<std::list<std::string>> Dhcpv4Srv::jsonPathsToRedact() const {
static std::list<std::list<std::string>> const list({
{"config-control", "config-databases", "[]"},
/// for testing purposes only.
///
/// @return UDP port on which server should listen.
- uint16_t getServerPort() const {
- return (server_port_);
- }
+ uint16_t getServerPort() const;
/// @brief Return bool value indicating that broadcast flags should be set
/// on sockets.
#include <dhcp/docsis3_option_defs.h>
#include <dhcp/duid.h>
#include <dhcp/duid_factory.h>
-#include <dhcpsrv/fuzz.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option6_addrlst.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/ncr_generator.h>
+#include <dhcpsrv/packet-fuzzer.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/subnet_selector.h>
#include <dhcpsrv/utils.h>
#include <util/encode/encode.h>
#include <util/pointer_util.h>
#include <util/range_utilities.h>
+#include <log/interprocess/interprocess_sync_file.h>
#include <log/logger.h>
#include <cryptolink/cryptolink.h>
#include <process/cfgrpt/config_report.h>
using namespace isc::dhcp_ddns;
using namespace isc::hooks;
using namespace isc::log;
+using namespace isc::log::interprocess;
using namespace isc::stats;
using namespace isc::util;
using namespace std;
int
Dhcpv6Srv::run() {
-#ifdef ENABLE_AFL
+#ifdef HAVE_AFL
+ // Get the values of the environment variables used to control the
+ // fuzzing.
+
+ // Specfies the interface to be used to pass packets from AFL to Kea.
+ const char* interface = getenv("KEA_AFL_INTERFACE");
+ if (!interface) {
+ isc_throw(FuzzInitFail, "no fuzzing interface has been set");
+ }
+
+ // The address on the interface to be used.
+ const char* address = getenv("KEA_AFL_ADDRESS");
+ if (!address) {
+ isc_throw(FuzzInitFail, "no fuzzing address has been set");
+ }
+
// Set up structures needed for fuzzing.
- Fuzz fuzzer(6, server_port_);
- //
+ PacketFuzzer fuzzer(6, server_port_, interface, address);
+
// The next line is needed as a signature for AFL to recognize that we are
// running persistent fuzzing. This has to be in the main image file.
while (__AFL_LOOP(fuzzer.maxLoopCount())) {
fuzzer.transfer();
#else
while (!shutdown_) {
-#endif // ENABLE_AFL
+#endif // HAVE_AFL
try {
runOne();
// Handle events registered by hooks using external IOService objects.
HooksManager::clearParkingLots();
}
+uint16_t Dhcpv6Srv::getServerPort() const {
+ char const* const randomize(getenv("KEA_DHCP6_FUZZING_RANDOMIZE_PORT"));
+ if (randomize) {
+ InterprocessSyncFile file("kea-dhcp6-fuzzing-randomize-port");
+ InterprocessSyncLocker locker(file);
+ while (!locker.lock()) {
+ this_thread::sleep_for(1s);
+ }
+ fstream port_file;
+ port_file.open("/tmp/port6.txt", ios::in);
+ string line;
+ int port;
+ getline(port_file, line);
+ port_file.close();
+ if (line.empty()) {
+ port = 2000;
+ } else {
+ port = stoi(line);
+ if (port < 3000) {
+ ++port;
+ } else {
+ port = 2000;
+ }
+ }
+ port_file.open("/tmp/port6.txt", ios::out | ios::trunc);
+ port_file << to_string(port) << endl;
+ port_file.close();
+ locker.unlock();
+ return port;
+ }
+ return server_port_;
+}
+
/// @todo This logic to be modified if we decide to support infinite lease times.
void
Dhcpv6Srv::setTeeTimes(uint32_t preferred_lft, const Subnet6Ptr& subnet, Option6IAPtr& resp) {
/// for testing purposes only.
///
/// @return UDP port on which server should listen.
- uint16_t getServerPort() const {
- return (server_port_);
- }
+ uint16_t getServerPort() const;
//@}
/// @brief Starts DHCP_DDNS client IO if DDNS updates are enabled.
libkea_dhcpsrv_la_SOURCES += parsers/simple_parser6.cc
libkea_dhcpsrv_la_SOURCES += parsers/simple_parser6.h
-if ENABLE_AFL
-libkea_dhcpsrv_la_SOURCES += fuzz.cc fuzz.h
+if FUZZING
+libkea_dhcpsrv_la_SOURCES += packet-fuzzer.cc packet-fuzzer.h
libkea_dhcpsrv_la_SOURCES += fuzz_log.cc fuzz_log.h
libkea_dhcpsrv_la_SOURCES += fuzz_messages.cc fuzz_messages.h
-endif
+endif # FUZZING
libkea_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS)
libkea_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS)
utils.h \
writable_host_data_source.h
-if ENABLE_AFL
+if FUZZING
libkea_dhcpsrv_include_HEADERS += \
- fuzz.h \
+ packet-fuzzer.h \
fuzz_log.h \
fuzz_messages.h
endif
-// Copyright (C) 2022-2023 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2022-2024 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
return;
}
// Remove the callbacks.
- auto& lease_mgr = LeaseMgrFactory::instance();
- lease_mgr.unregisterCallbacks(subnet_id_, pool_type_);
+ LeaseMgrFactory::instance().unregisterCallbacks(subnet_id_, pool_type_);
}
bool
if (getLeaseMgrPtr()) {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CLOSE_DB)
.arg(getLeaseMgrPtr()->getType());
+ getLeaseMgrPtr().reset();
}
getLeaseMgrPtr().reset();
}
/// user-supplied backends (so that there is no need to modify the code).
class LeaseMgrFactory {
public:
+ ~LeaseMgrFactory() {
+ destroy();
+ }
+
/// @brief Create an instance of a lease manager.
///
/// Each database backend has its own lease manager type. This static
-// Copyright (C) 2016-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2024 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
#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
+#ifdef FUZZING
#include <dhcp/dhcp6.h>
-#include <dhcpsrv/fuzz.h>
+#include <dhcpsrv/packet-fuzzer.h>
#include <dhcpsrv/fuzz_log.h>
#include <boost/lexical_cast.hpp>
#include <string.h>
#include <signal.h>
+#include <algorithm>
#include <iostream>
#include <sstream>
#include <fstream>
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;
-constexpr long Fuzz::MAX_LOOP_COUNT;
+// Constants defined in the PacketFuzzer class definition.
+constexpr size_t PacketFuzzer::BUFFER_SIZE;
+constexpr size_t PacketFuzzer::MAX_SEND_SIZE;
+constexpr long PacketFuzzer::MAX_LOOP_COUNT;
// Constructor
-Fuzz::Fuzz(int ipversion, uint16_t port) :
- loop_max_(MAX_LOOP_COUNT), sockaddr_len_(0), sockaddr_ptr_(nullptr),
- sockfd_(-1) {
+PacketFuzzer::PacketFuzzer(int const ipversion,
+ uint16_t const port,
+ string const interface,
+ string const address)
+ : loop_max_(MAX_LOOP_COUNT), sockaddr_len_(0), sockaddr_ptr_(nullptr), sockfd_(-1) {
try {
stringstream reason; // Used to construct exception messages
- // Get the values of the environment variables used to control the
- // fuzzing.
-
- // Specfies the interface to be used to pass packets from AFL to Kea.
- const char* interface = getenv("KEA_AFL_INTERFACE");
- if (! interface) {
- isc_throw(FuzzInitFail, "no fuzzing interface has been set");
- }
-
- // The address on the interface to be used.
- const char* address = getenv("KEA_AFL_ADDRESS");
- if (address == 0) {
- isc_throw(FuzzInitFail, "no fuzzing address has been set");
- }
-
// Number of Kea packet-read loops before Kea exits and AFL starts a
// new instance. This is optional: the default is set by the constant
// MAX_LOOP_COUNT.
- const char *loop_max_ptr = getenv("KEA_AFL_LOOP_MAX");
- if (loop_max_ptr != 0) {
+ const char *loop_max_ptr(nullptr);
+#ifdef HAVE_AFL
+ loop_max_ptr = getenv("KEA_AFL_LOOP_MAX");
+#endif
+ if (loop_max_ptr) {
try {
loop_max_ = boost::lexical_cast<long>(loop_max_ptr);
} catch (const boost::bad_lexical_cast&) {
}
// Set up address structures used to route the packets from AFL to Kea.
- createAddressStructures(ipversion, interface, address, port);
+ createAddressStructures(ipversion, port, interface, address);
// 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
-Fuzz::~Fuzz() {
+PacketFuzzer::~PacketFuzzer() {
static_cast<void>(close(sockfd_));
}
// Set up address structures.
void
-Fuzz::createAddressStructures(int ipversion, const char* interface,
- const char* address, uint16_t port) {
+PacketFuzzer::createAddressStructures(int const ipversion,
+ uint16_t const port,
+ string const interface,
+ string const address) {
stringstream reason; // Used in error messages
// Set up the appropriate data structure depending on the address given.
- if ((ipversion == 6) && (strstr(address, ":") != NULL)) {
+ if (ipversion == 6 && address.find(":") != string::npos) {
// Expecting IPv6 and the address contains a colon, so assume it is an
// an IPv6 address.
memset(&servaddr6_, 0, sizeof (servaddr6_));
servaddr6_.sin6_family = AF_INET6;
- if (inet_pton(AF_INET6, address, &servaddr6_.sin6_addr) != 1) {
+ if (inet_pton(AF_INET6, address.c_str(), &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);
+ servaddr6_.sin6_scope_id = if_nametoindex(interface.c_str());
if (servaddr6_.sin6_scope_id == 0) {
reason << "error retrieving interface ID for "
<< interface << ": " << strerror(errno);
sockaddr_ptr_ = reinterpret_cast<sockaddr*>(&servaddr6_);
sockaddr_len_ = sizeof(servaddr6_);
- } else if ((ipversion == 4) && (strstr(address, ".") != NULL)) {
+ } else if (ipversion == 4 && address.find(".") != string::npos) {
// 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 embedded in an IPv6 one.
memset(&servaddr4_, 0, sizeof(servaddr4_));
servaddr4_.sin_family = AF_INET;
- if (inet_pton(AF_INET, address, &servaddr4_.sin_addr) != 1) {
+ if (inet_pton(AF_INET, address.c_str(), &servaddr4_.sin_addr) != 1) {
reason << "inet_pton() failed: can't convert "
<< address << " to an IPv6 address" << endl;
isc_throw(FuzzInitFail, reason.str());
}
+void
+PacketFuzzer::transfer() const {
+ // Read from stdin. Just return if nothing is read (or there is an error)
+ // and hope that this does not cause a hang.
+ uint8_t buf[BUFFER_SIZE];
+ ssize_t const length(read(0, buf, sizeof(buf)));
+
+ transfer(&buf[0], length);
+}
// This is the main fuzzing function. It receives data from fuzzing engine over
// stdin and then sends it to the configured UDP socket.
void
-Fuzz::transfer(void) const {
-
- // Read from stdin. Just return if nothing is read (or there is an error)
- // and hope that this does not cause a hang.
+PacketFuzzer::transfer(uint8_t const* data, size_t size) const {
char buf[BUFFER_SIZE];
- ssize_t length = read(0, buf, sizeof(buf));
+ ssize_t const length(size);
+
+ if (data) {
+ memcpy(&buf[0], data, min(BUFFER_SIZE, size));
+ }
// Save the errno in case there was an error because if debugging is
// enabled, the following LOG_DEBUG call may destroy its value.
size_t send_len = (length < MAX_SEND_SIZE) ? length : MAX_SEND_SIZE;
ssize_t sent = sendto(sockfd_, buf, send_len, 0, sockaddr_ptr_,
sockaddr_len_);
- if (sent > 0) {
- LOG_DEBUG(fuzz_logger, FUZZ_DBG_TRACE_DETAIL, FUZZ_SEND).arg(sent);
+ if (sent < 0) {
+ LOG_ERROR(fuzz_logger, FUZZ_SEND_ERROR).arg(strerror(errno));
} else if (sent != length) {
LOG_WARN(fuzz_logger, FUZZ_SHORT_SEND).arg(length).arg(sent);
} else {
- LOG_ERROR(fuzz_logger, FUZZ_SEND_ERROR).arg(strerror(errno));
+ LOG_DEBUG(fuzz_logger, FUZZ_DBG_TRACE_DETAIL, FUZZ_SEND).arg(sent);
}
} else {
// Read did not get any bytes. A zero-length read (EOF) may have been
LOG_ERROR(fuzz_logger, FUZZ_READ_FAIL).arg(strerror(errnum));
}
}
-
}
-#endif // ENABLE_AFL
+#endif // FUZZING
-// Copyright (C) 2016-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2024 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 FUZZ_H
-#define FUZZ_H
+#ifndef DHCPSRV_PACKET_FUZZER_H
+#define DHCPSRV_PACKET_FUZZER_H
-#ifdef ENABLE_AFL
+#ifdef FUZZING
#include <exceptions/exceptions.h>
#include <thread>
namespace isc {
-
/// @brief AFL Fuzzing
///
/// is listening. Kea then reads the data from that port and processes it
/// in the usual way.
///
-/// The Fuzz class handles the transfer of data between AFL and Kea. After
+/// The PacketFuzzer class handles the transfer of data between AFL and Kea. After
/// suitable initialization, its transfer() method is called in the main
/// processing loop, right before Kea waits for input. The method handles the
/// read from stdin and the write to the selected address port.
-class Fuzz {
+class PacketFuzzer {
public:
/// @brief size of the buffer used to transfer data between AFL and Kea.
///
/// environment variable KEA_AFL_LOOP_MAX.
static constexpr long MAX_LOOP_COUNT = 1000;
-
/// @brief Constructor
///
/// Sets up data structures to access the address/port being used to
/// server responds to.
/// @param port Port on which the server is listening, and hence the
/// port to which the fuzzer will send input from AFL.
- Fuzz(int ipversion, uint16_t port);
+ PacketFuzzer(int const ipversion,
+ uint16_t const port,
+ std::string const interface,
+ std::string const address);
/// @brief Destructor
///
/// Closes the socket used for transferring data from stdin to the selected
/// interface.
- ~Fuzz();
+ ~PacketFuzzer();
/// @brief Transfer Data
///
/// Called immediately prior to Kea reading data, this reads stdin (where
/// AFL will have sent the packet being tested) and copies the data to the
/// interface on which Kea is listening.
- void transfer(void) const;
+ void transfer() const;
+ void transfer(uint8_t const* data, size_t size) const;
/// @brief Return Max Loop Count
///
///
/// @throws FuzzInitFail Thrown if the address is not in the expected
/// format.
- void createAddressStructures(int ipversion, const char* interface,
- const char* address, uint16_t port);
+ void createAddressStructures(int const ipversion,
+ uint16_t const port,
+ std::string const interface,
+ std::string const address);
// Other member variables.
long loop_max_; //< Maximum number of loop iterations
struct sockaddr_in servaddr4_; //< IPv6 address information
struct sockaddr_in6 servaddr6_; //< IPv6 address information
int sockfd_; //< Socket used to transfer data
-};
-
+}; // class PacketFuzzer
/// @brief Exception thrown if fuzzing initialization fails.
class FuzzInitFail : public Exception {
public:
FuzzInitFail(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
-};
+}; // class FuzzInitFail
-}
+} // namespace isc
-#endif // ENABLE_AFL
+#endif // FUZZING
-#endif // FUZZ_H
+#endif // DHCPSRV_PACKET_FUZZER_H
libkea_testutils_la_SOURCES += gtest_utils.h
libkea_testutils_la_SOURCES += multi_threading_utils.h
libkea_testutils_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libkea_testutils_la_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+
libkea_testutils_la_LIBADD = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
libkea_testutils_la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+libkea_testutils_la_LIBADD += $(GTEST_LDADD)
endif
# Include common libraries being used by shell-based tests.
-// Copyright (C) 2015-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2024 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
}
bool UnixControlClient::sendCommand(const std::string& command) {
+ if (socket_fd_ < 0) {
+ ADD_FAILURE() << "send command with closed socket";
+ return (false);
+ }
// Send command
int bytes_sent = send(socket_fd_, command.c_str(), command.length(), 0);
if (bytes_sent < command.length()) {
}
int UnixControlClient::selectCheck(const unsigned int timeout_sec) {
+ if (socket_fd_ < 0) {
+ ADD_FAILURE() << "select check with closed socket";
+ return -1;
+ }
+ if (socket_fd_ > 1023) {
+ ADD_FAILURE() << "select check with out of bound socket";
+ return -1;
+ }
int maxfd = 0;
fd_set read_fds;
FD_ZERO(&read_fds);
// Add this socket to listening set
- FD_SET(socket_fd_, &read_fds);
+ FD_SET(socket_fd_, &read_fds);
maxfd = socket_fd_;
struct timeval select_timeout;
return (select(maxfd + 1, &read_fds, NULL, NULL, &select_timeout));
}
-};
-};
-};
+}
+}
+}
-// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2024 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
int socket_fd_;
};
-}; // end of isc::dhcp::test namespace
-}; // end of isc::dhcp namespace
-}; // end of isc namespace
+} // end of isc::dhcp::test namespace
+} // end of isc::dhcp namespace
+} // end of isc namespace
#endif // UNIX_CONTROL_CLIENT_H
#include <util/filesystem.h>
#include <util/str.h>
-#include <algorithm>
-#include <cctype>
-#include <cerrno>
-#include <cstring>
+#include <cstdio>
+#include <cstdlib>
#include <fstream>
-#include <iostream>
#include <string>
+#include <dirent.h>
#include <fcntl.h>
using namespace isc::util::str;
umask(orig_umask_);
}
+bool
+isSocket(string const& path) {
+ struct stat statbuf;
+ if (::stat(path.c_str(), &statbuf) < 0) {
+ return (false);
+ }
+ return ((statbuf.st_mode & S_IFMT) == S_IFSOCK);
+}
+
Path::Path(string const& full_name) {
if (!full_name.empty()) {
bool dir_present = false;
if (last_slash != string::npos) {
// Found the last slash, so extract directory component and
// set where the scan for the last_dot should terminate.
- parent_path_ = full_name.substr(0, last_slash + 1);
+ parent_path_ = full_name.substr(0, last_slash);
if (last_slash == full_name.size()) {
- // The entire string was a directory, so exit not and don't
+ // The entire string was a directory, so exit and don't
// do any more searching.
return;
}
string
Path::str() const {
- return (parent_path_ + stem_ + extension_);
+ return (parent_path_ + ((parent_path_.empty() || parent_path_ == "/") ? string() : "/") + stem_ + extension_);
}
string
string const trimmed_replacement(trim(replacement));
if (trimmed_replacement.empty()) {
parent_path_ = string();
- } else if (trimmed_replacement.at(trimmed_replacement.size() - 1) == '/') {
+ } else if (trimmed_replacement == "/") {
parent_path_ = trimmed_replacement;
+ } else if (trimmed_replacement.at(trimmed_replacement.size() - 1) == '/') {
+ parent_path_ = trimmed_replacement.substr(0, trimmed_replacement.size() - 1);
} else {
- parent_path_ = trimmed_replacement + '/';
+ parent_path_ = trimmed_replacement;
}
return (*this);
}
+TemporaryDirectory::TemporaryDirectory() {
+ char dir[]("/tmp/kea-tmpdir-XXXXXX");
+ char const* dir_name = mkdtemp(dir);
+ if(!dir_name) {
+ isc_throw(Unexpected, "mkdtemp failed " << dir << ": " << strerror(errno));
+ }
+ dir_name_ = string(dir_name);
+}
+
+TemporaryDirectory::~TemporaryDirectory() {
+ rmdir(dir_name_.c_str());
+ DIR *dir(opendir(dir_name_.c_str()));
+ struct dirent *i;
+ string filepath;
+
+ while ((i = readdir(dir))) {
+ if (strcmp(i->d_name, ".") == 0 || strcmp(i->d_name, "..") == 0) {
+ continue;
+ }
+
+ filepath = dir_name_ + '/' + i->d_name;
+ remove(filepath.c_str());
+ }
+ closedir(dir);
+ rmdir(dir_name_.c_str());
+}
+
+string TemporaryDirectory::dirName() {
+ return dir_name_;
+}
+
} // namespace file
} // namespace util
} // namespace isc
namespace util {
namespace file {
-/// \brief Get the content of a regular file.
+/// @brief Get the content of a regular file.
///
/// \param file_name The file name.
///
std::string
getContent(const std::string& file_name);
-/// \brief Check if there is a file or directory at the given path.
+/// @brief Check if there is a file or directory at the given path.
///
/// \param path The path being checked.
///
bool
exists(const std::string& path);
-/// \brief Check if there is a directory at the given path.
+/// @brief Check if there is a directory at the given path.
///
/// \param path The path being checked.
///
bool
isDir(const std::string& path);
-/// \brief Check if there is a file at the given path.
+/// @brief Check if there is a file at the given path.
///
/// \param path The path being checked.
///
bool
isFile(const std::string& path);
-/// \brief RAII device to limit access of created files.
+/// @brief RAII device to limit access of created files.
struct Umask {
- /// \brief Constructor
+ /// @brief Constructor
///
/// Set wanted bits in umask.
Umask(mode_t mask);
- /// \brief Destructor.
+ /// @brief Destructor.
///
/// Restore umask.
~Umask();
private:
- /// \brief Original umask.
+ /// @brief Original umask.
mode_t orig_umask_;
};
-/// \brief Paths on a filesystem
+bool
+isSocket(const std::string& path);
+
+/// @brief Paths on a filesystem
struct Path {
- /// \brief Constructor
+ /// @brief Constructor
///
/// Splits the full name into components.
Path(std::string const& path);
- /// \brief Get the path in textual format.
+ /// @brief Get the path in textual format.
///
/// Counterpart for std::filesystem::path::string.
///
/// \return stored filename.
std::string str() const;
- /// \brief Get the parent path.
+ /// @brief Get the parent path.
///
/// Counterpart for std::filesystem::path::parent_path.
///
/// \return parent path of current path.
std::string parentPath() const;
- /// \brief Get the base name of the file without the extension.
+ /// @brief Get the base name of the file without the extension.
///
/// Counterpart for std::filesystem::path::stem.
///
/// \return the base name of current path without the extension.
std::string stem() const;
- /// \brief Get the extension of the file.
+ /// @brief Get the extension of the file.
///
/// Counterpart for std::filesystem::path::extension.
///
/// \return extension of current path.
std::string extension() const;
- /// \brief Get the name of the file, extension included.
+ /// @brief Get the name of the file, extension included.
///
/// Counterpart for std::filesystem::path::filename.
///
/// \return name + extension of current path.
std::string filename() const;
- /// \brief Identifies the extension in {replacement}, trims it, and
+ /// @brief Identifies the extension in {replacement}, trims it, and
/// replaces this instance's extension with it.
///
/// Counterpart for std::filesystem::path::replace_extension.
/// \return The current instance after the replacement was done.
Path& replaceExtension(std::string const& replacement = std::string());
- /// \brief Trims {replacement} and replaces this instance's parent path with
+ /// @brief Trims {replacement} and replaces this instance's parent path with
/// it.
///
/// The change is done in the members and {this} is returned to allow call
Path& replaceParentPath(std::string const& replacement = std::string());
private:
- /// \brief Parent path.
+ /// @brief Parent path.
std::string parent_path_;
- /// \brief Stem.
+ /// @brief Stem.
std::string stem_;
- /// \brief File name extension.
+ /// @brief File name extension.
std::string extension_;
};
+struct TemporaryDirectory {
+ TemporaryDirectory();
+ ~TemporaryDirectory();
+ std::string dirName();
+private:
+ std::string dir_name_;
+};
+
} // namespace file
} // namespace util
} // namespace isc
TEST(PathTest, components) {
// Complete name
Path fname("/alpha/beta/gamma.delta");
- EXPECT_EQ("/alpha/beta/", fname.parentPath());
+ EXPECT_EQ("/alpha/beta/gamma.delta", fname.str());
+ EXPECT_EQ("/alpha/beta", fname.parentPath());
EXPECT_EQ("gamma", fname.stem());
EXPECT_EQ(".delta", fname.extension());
EXPECT_EQ("gamma.delta", fname.filename());
/// @brief Check replaceExtension.
TEST(PathTest, replaceExtension) {
Path fname("a.b");
+ EXPECT_EQ("a.b", fname.str());
EXPECT_EQ("a", fname.replaceExtension("").str());
EXPECT_EQ("a.f", fname.replaceExtension(".f").str());
EXPECT_EQ("a.b", fname.str());
fname.replaceParentPath("/just/some/dir/");
- EXPECT_EQ("/just/some/dir/", fname.parentPath());
+ EXPECT_EQ("/just/some/dir", fname.parentPath());
EXPECT_EQ("/just/some/dir/a.b", fname.str());
fname.replaceParentPath("/just/some/dir");
- EXPECT_EQ("/just/some/dir/", fname.parentPath());
+ EXPECT_EQ("/just/some/dir", fname.parentPath());
EXPECT_EQ("/just/some/dir/a.b", fname.str());
fname.replaceParentPath("/");
EXPECT_EQ("a.b", fname.str());
fname = Path("/first/a.b");
- EXPECT_EQ("/first/", fname.parentPath());
+ EXPECT_EQ("/first", fname.parentPath());
EXPECT_EQ("/first/a.b", fname.str());
fname.replaceParentPath("/just/some/dir");
- EXPECT_EQ("/just/some/dir/", fname.parentPath());
+ EXPECT_EQ("/just/some/dir", fname.parentPath());
EXPECT_EQ("/just/some/dir/a.b", fname.str());
}