2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-09-02 15:05:16 +00:00

[#3605] Prepare existing code for fuzzing

- 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.
This commit is contained in:
Andrei Pavel
2024-10-14 10:05:31 +03:00
parent 978f0f87ab
commit 58d4caa865
20 changed files with 391 additions and 156 deletions

View File

@@ -204,6 +204,9 @@ AM_CONDITIONAL(USE_CLANGPP, test "X${CLANGPP}" = "Xyes")
# 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
@@ -346,6 +349,15 @@ if less_than "5" "$CXX_DUMP_VERSION"; then
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
@@ -676,7 +688,7 @@ int main() {
# 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
@@ -1405,26 +1417,49 @@ if test "x$VALGRIND" != "xno"; then
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])
[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 "x$enable_fuzzing" != "xno" ; then
AC_DEFINE([ENABLE_AFL], [1], [AFL fuzzing was enabled.])
AC_MSG_CHECKING([for AFL enabled compiler])
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
])],
[AC_MSG_RESULT([yes])],
[AC_MSG_ERROR([set CXX to afl-clang-fast++ when --enable-fuzzing is used])])
[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
# is required if command line arguments are parsed multiple times
@@ -2148,7 +2183,8 @@ Developer:
Generate Messages Files: $enable_generate_messages
Perfdhcp: $enable_perfdhcp
Kea-shell: $shell_report
Enable fuzzing: $enable_fuzzing
Fuzzing: $fuzzing_enabled
AFL: $have_afl
END

View File

@@ -4,6 +4,8 @@ CXX_SAVED=$CXX
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
@@ -250,4 +252,7 @@ for retry in "none" "--std=c++14" "--std=c++1y" "fail"; do
break
done
AC_MSG_CHECKING([c++14 support])
AC_MSG_RESULT([yes])
])

23
m4macros/ax_cpp17.m4 Normal file
View File

@@ -0,0 +1,23 @@
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}"
])

View File

@@ -1,9 +1,8 @@
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"

View File

@@ -36,12 +36,12 @@
#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>
@@ -55,6 +55,7 @@
#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>
@@ -65,6 +66,8 @@
#include <boost/pointer_cast.hpp>
#include <boost/shared_ptr.hpp>
#include <chrono>
#include <functional>
#include <iomanip>
#include <set>
@@ -78,9 +81,11 @@ using namespace isc::dhcp;
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 {
@@ -1122,10 +1127,25 @@ Dhcpv4Srv::earlyGHRLookup(const Pkt4Ptr& query,
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())) {
@@ -1134,7 +1154,7 @@ Dhcpv4Srv::run() {
fuzzer.transfer();
#else
while (!shutdown_) {
#endif // ENABLE_AFL
#endif // HAVE_AFL
try {
runOne();
// Handle events registered by hooks using external IOService objects.
@@ -5159,6 +5179,39 @@ void Dhcpv4Srv::discardPackets() {
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", "[]"},

View File

@@ -454,9 +454,7 @@ public:
/// 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.

View File

@@ -15,7 +15,6 @@
#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>
@@ -41,6 +40,7 @@
#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>
@@ -54,6 +54,7 @@
#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>
@@ -82,6 +83,7 @@ using namespace isc::dhcp;
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;
@@ -597,10 +599,25 @@ Dhcpv6Srv::initContext(AllocEngine::ClientContext6& ctx, bool& drop) {
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())) {
@@ -609,7 +626,7 @@ Dhcpv6Srv::run() {
fuzzer.transfer();
#else
while (!shutdown_) {
#endif // ENABLE_AFL
#endif // HAVE_AFL
try {
runOne();
// Handle events registered by hooks using external IOService objects.
@@ -4902,6 +4919,39 @@ void Dhcpv6Srv::discardPackets() {
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) {

View File

@@ -240,9 +240,7 @@ public:
/// 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.

View File

@@ -172,11 +172,11 @@ libkea_dhcpsrv_la_SOURCES += parsers/simple_parser4.h
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)
@@ -350,9 +350,9 @@ libkea_dhcpsrv_include_HEADERS = \
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

View File

@@ -1,4 +1,4 @@
// 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
@@ -31,8 +31,7 @@ Allocator::~Allocator() {
return;
}
// Remove the callbacks.
auto& lease_mgr = LeaseMgrFactory::instance();
lease_mgr.unregisterCallbacks(subnet_id_, pool_type_);
LeaseMgrFactory::instance().unregisterCallbacks(subnet_id_, pool_type_);
}
bool

View File

@@ -92,6 +92,7 @@ LeaseMgrFactory::destroy() {
if (getLeaseMgrPtr()) {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CLOSE_DB)
.arg(getLeaseMgrPtr()->getType());
getLeaseMgrPtr().reset();
}
getLeaseMgrPtr().reset();
}

View File

@@ -42,6 +42,10 @@ public:
/// 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

View File

@@ -1,4 +1,4 @@
// 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
@@ -6,14 +6,10 @@
#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>
@@ -23,6 +19,7 @@
#include <string.h>
#include <signal.h>
#include <algorithm>
#include <iostream>
#include <sstream>
#include <fstream>
@@ -32,39 +29,29 @@ 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;
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&) {
@@ -81,7 +68,7 @@ Fuzz::Fuzz(int ipversion, uint16_t port) :
}
// 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
@@ -105,24 +92,26 @@ Fuzz::Fuzz(int ipversion, uint16_t port) :
}
// 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());
@@ -130,7 +119,7 @@ Fuzz::createAddressStructures(int ipversion, const char* interface,
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);
@@ -140,14 +129,14 @@ Fuzz::createAddressStructures(int ipversion, const char* interface,
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());
@@ -166,16 +155,26 @@ Fuzz::createAddressStructures(int ipversion, const char* interface,
}
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.
@@ -189,12 +188,12 @@ Fuzz::transfer(void) const {
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
@@ -203,7 +202,6 @@ Fuzz::transfer(void) const {
LOG_ERROR(fuzz_logger, FUZZ_READ_FAIL).arg(strerror(errnum));
}
}
}
#endif // ENABLE_AFL
#endif // FUZZING

View File

@@ -1,13 +1,13 @@
// 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>
@@ -23,7 +23,6 @@
namespace isc {
/// @brief AFL Fuzzing
///
/// Persistent-mode AFL fuzzing has the AFL fuzzer send packets of data to
@@ -35,12 +34,12 @@ namespace isc {
/// 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.
///
@@ -65,7 +64,6 @@ public:
/// 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
@@ -75,20 +73,24 @@ public:
/// 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
///
@@ -117,8 +119,10 @@ private:
///
/// @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
@@ -127,18 +131,17 @@ private:
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

View File

@@ -17,8 +17,11 @@ libkea_testutils_la_SOURCES += user_context_utils.cc user_context_utils.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.

View File

@@ -1,4 +1,4 @@
// 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
@@ -73,6 +73,10 @@ bool UnixControlClient::connectToServer(const std::string& socket_path) {
}
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()) {
@@ -118,6 +122,14 @@ bool UnixControlClient::getResponse(std::string& response,
}
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;
@@ -134,6 +146,6 @@ int UnixControlClient::selectCheck(const unsigned int timeout_sec) {
return (select(maxfd + 1, &read_fds, NULL, NULL, &select_timeout));
}
};
};
};
}
}
}

View File

@@ -1,4 +1,4 @@
// 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
@@ -59,8 +59,8 @@ public:
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

View File

@@ -10,14 +10,12 @@
#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;
@@ -76,6 +74,15 @@ Umask::~Umask() {
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;
@@ -84,9 +91,9 @@ Path::Path(string const& full_name) {
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;
}
@@ -119,7 +126,7 @@ Path::Path(string const& full_name) {
string
Path::str() const {
return (parent_path_ + stem_ + extension_);
return (parent_path_ + ((parent_path_.empty() || parent_path_ == "/") ? string() : "/") + stem_ + extension_);
}
string
@@ -163,14 +170,47 @@ Path::replaceParentPath(string const& replacement) {
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

View File

@@ -14,7 +14,7 @@ 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.
///
@@ -23,7 +23,7 @@ namespace file {
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.
///
@@ -31,7 +31,7 @@ getContent(const std::string& file_name);
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.
///
@@ -40,7 +40,7 @@ exists(const std::string& path);
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.
///
@@ -49,66 +49,69 @@ isDir(const std::string& path);
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.
@@ -121,7 +124,7 @@ struct Path {
/// \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
@@ -133,16 +136,24 @@ struct Path {
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

View File

@@ -85,7 +85,8 @@ TEST_F(FileUtilTest, umask) {
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());
@@ -94,6 +95,7 @@ TEST(PathTest, components) {
/// @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());
@@ -110,11 +112,11 @@ TEST(PathTest, replaceParentPath) {
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("/");
@@ -126,11 +128,11 @@ TEST(PathTest, 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());
}