--- /dev/null
+FROM registry.gitlab.isc.org/isc-projects/kea:fuzz-latest
+
+# Copy repo and link build.sh so that it runs from a location relative to the Kea repo.
+WORKDIR "${SRC}"
+COPY . "${SRC}/kea"
+RUN ln -s "${SRC}/kea/.clusterfuzzlite/build.sh" "${SRC}/build.sh"
--- /dev/null
+#!/bin/bash -eu
+
+# https://reports.kea.isc.org/new-fuzzer.html
+
+script_path="$(dirname "$(readlink -f "${0}")")"
+cd "${script_path}/.."
+
+# Use a wrapper function to allow "return 1" instead of "exit 1" which may have
+# unforeseen consequences in case this script is sourced.
+install_kea() {
+ # ccache
+ export CCACHE_DIR=/cache
+ export PATH="/usr/lib/ccache:$PATH"
+ export KEA_BUILD_DIR="${KEA_BUILD_DIR-/builds/isc-projects/kea}"
+
+ cxxflags=
+ autoreconf -i
+ if test "${SANITIZER}" = 'none'; then
+ cxxflags="${cxxflags} -fno-sanitize=all"
+ enable_fuzzing='--enable-fuzzing'
+ else
+ cxxflags="${cxxflags} -fsanitize=${SANITIZER}"
+ enable_fuzzing='--enable-fuzzing=ci'
+ fi
+ export CXXFLAGS="${cxxflags}"
+ export LDFLAGS='-L/usr/lib/gcc/x86_64-linux-gnu/9 -lstdc++fs'
+ if ! ./configure --enable-boost-headers-only --prefix='/opt/kea' "${enable_fuzzing}" --with-gtest=/usr/src/googletest/googletest; then
+ printf './configure failed. Here is config.log:\n'
+ cat config.log
+ return 1
+ fi
+ make -j "$(nproc)"
+ make install
+
+ # Copy internal libraries.
+ # SC2156 (warning): Injecting filenames is fragile and insecure. Use parameters.
+ # shellcheck disable=SC2156
+ find "/opt/kea/lib" -mindepth 1 -maxdepth 1 -exec sh -c "cp {} ${KEA_BUILD_DIR}" ';'
+
+ # Copy the binaries.
+ for fuzzer in fuzz-config-kea-dhcp4 fuzz-packets-kea-dhcp4 fuzz-unix-socket-kea-dhcp4 \
+ fuzz-config-kea-dhcp6 fuzz-packets-kea-dhcp6 fuzz-unix-socket-kea-dhcp6 \
+ fuzz-http-endpoint \
+ ; do
+ cp "/opt/kea/sbin/${fuzzer}" "${OUT}/${fuzzer}"
+ # copy all required libraries
+ echo "ldd ${OUT}/${fuzzer}: "
+ ldd "${OUT}/${fuzzer}"
+ EXTENDED_PATH=$(readelf -d "${OUT}/${fuzzer}" | grep 'R.*PATH' | cut -d '[' -f 2 | cut -d ']' -f 1)
+ patchelf --set-rpath "/usr/lib/x86_64-linux-gnu:/lib/x86_64-linux-gnu:${EXTENDED_PATH}" "${OUT}/${fuzzer}"
+ readelf -d "${OUT}/${fuzzer}" | grep 'R.*PATH' || true
+ for i in $(ldd "${OUT}/${fuzzer}" | cut -f 2 | cut -d ' ' -f 3); do
+ cp "${i}" "${KEA_BUILD_DIR}"
+ done
+ done
+}
+
+install_kea
--- /dev/null
+language: c++
--- /dev/null
+#!/bin/sh
+
+# Change to parent directory, so that the script can be called from anywhere.
+parent_path=$(cd "$(dirname "${0}")" && pwd)
+cd "${parent_path}" || exit 1
+
+mkdir -p build/out
+mkdir -p build/work
+
+cd .. || exit 2
+
+docker build -t kea-fuzzing -f .clusterfuzzlite/Dockerfile .
+
+docker_run() {
+ docker run \
+ --interactive \
+ --privileged \
+ --platform linux/amd64 \
+ --rm \
+ --shm-size=2g \
+ -e ARCHITECTURE=x86_64 \
+ -e CIFUZZ=true \
+ -e FUZZING_ARGS='-rss_limit_mb=8192' \
+ -e FUZZING_ENGINE=libfuzzer \
+ -e FUZZING_LANGUAGE=c++ \
+ -e KEA_BUILD_DIR=/src \
+ -e SANITIZER=address \
+ -v "${parent_path}/build/out:/out" \
+ -v "${parent_path}/build/work:/work" \
+ kea-fuzzing \
+ "${@}"
+}
+
+docker_run
+
+docker_run compile
# Locale settings do not affect the build, but might affect tests.
LC_ALL: C
- CI_REGISTRY_IMAGE: registry.gitlab.isc.org/isc-projects/kea
+ # Fuzzing
+ CFL_ARTIFACTS_DIR: '/tmp/cfl-artifacts'
+ CFL_CACHE_DIR: '/ccache/cfl-cache'
+ CFL_IMAGE: 'gcr.io/oss-fuzz-base/clusterfuzzlite-run-fuzzers'
+ CFL_PLATFORM: gitlab
+ FUZZ_SECONDS: 600 # 10 min (ClusterFuzzLite defaults)
+ FUZZING_ARGS: '-rss_limit_mb=8192'
+ LD_LIBRARY_PATH: "/opt/kea/lib:/usr/lib/x86_64-linux-gnu:/lib/x86_64-linux-gnu:/builds/isc-projects/kea"
+ PARALLEL_FUZZING: true
+ CCACHE_BASEDIR: "${CI_PROJECT_DIR}"
+ CCACHE_DIR: "${CI_PROJECT_DIR}/ccache"
- # Setting this variable will affect all Security templates
- # (SAST, Dependency Scanning, ...)
+ # SAST
SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"
-
# Leave only bandit, flawfinder, semgrep.
SAST_EXCLUDED_ANALYZERS: "eslint, spotbugs"
-image: "${CI_REGISTRY_IMAGE}:latest"
+image: 'registry.gitlab.isc.org/isc-projects/kea:latest'
stages:
- test
+ - fuzz
+
+# Do not run the test stage on pipeline schedule trigger.
+.base_rules_for_test_jobs: &rules_for_test_stage
+ rules:
+ - if: $CI_PIPELINE_SOURCE != 'schedule'
+ when: always
+ - if: $CI_PIPELINE_SOURCE == 'schedule'
+ when: never
are-database-scripts-in-sync:
stage: test
+ <<: *rules_for_test_stage
script:
- ./src/share/database/scripts/utils/are-scripts-in-sync.py
check-for-json-errors-in-doc:
stage: test
+ <<: *rules_for_test_stage
script:
- ./tools/check-for-json-errors-in-doc.sh
danger:
stage: test
+ <<: *rules_for_test_stage
before_script:
- export CI_MERGE_REQUEST_ID=$(git ls-remote -q origin merge-requests\*\head | grep $CI_COMMIT_SHA | sed 's/.*refs\/merge-requests\/\([0-9]*\)\/head/\1/g')
- export CI_PROJECT_PATH=$CI_PROJECT_ID #some version of gitlab has problems with searching by project path
duplicate-includes:
stage: test
+ <<: *rules_for_test_stage
script:
- ./tools/check-for-duplicate-includes.sh
duplicate-log-messages:
stage: test
+ <<: *rules_for_test_stage
script:
- ./tools/check-messages.py
uninstalled-headers:
stage: test
+ <<: *rules_for_test_stage
script:
- ./tools/find-uninstalled-headers.py
missing-api-commands:
stage: test
+ <<: *rules_for_test_stage
script:
- ./tools/check-for-missing-api-commands.sh
missing-config-h-include:
stage: test
+ <<: *rules_for_test_stage
script:
- FILES=$(./tools/add-config-h.sh -n)
- printf '%s\n' "${FILES}"
missing-git-attribute:
stage: test
+ <<: *rules_for_test_stage
script:
- git_diff=$(git diff)
- if test -n "${git_diff}"; then printf '%s\n\ngit diff should be empty here under all circumstances. CI broken?\n' "${git_diff}"; exit 1; fi
shellcheck:
stage: test
+ <<: *rules_for_test_stage
script:
- ./tools/shellcheck-all.sh
- if test -z "${PYTHON_SCRIPTS}"; then echo "No python scripts to check. Exiting early."; exit 0; fi
bandit:
+ stage: test
+ <<: *rules_for_test_stage
script:
- bandit -r ./src -x ./.git
pycodestyle:
stage: test
+ <<: *rules_for_test_stage
script:
# - *get_modified_files
# - INPUT="${MODIFIED_FILES}"
pylint:
stage: test
+ <<: *rules_for_test_stage
script:
# - *get_modified_files
# - INPUT="${MODIFIED_FILES}"
# If we reached this point, it means pylint passed. Run again with all warnings enabled, but ignore the return code to show a list of improvements that the developer could do, even when CI is passing.
- pylint --jobs "$(nproc || gnproc || echo 1)" --rcfile ./.gitlab/ci/pylint.rc --enable all ${PYTHON_SCRIPTS} || true
+
+############################## Fuzzing ##############################
+
+# Fuzz code changes. Fuzzes all merge requests.
+fuzz:
+ image:
+ name: "${CFL_IMAGE}"
+ entrypoint: ['']
+ stage: fuzz
+ tags:
+ - docker-fuzz
+ needs: []
+ parallel:
+ matrix:
+ - SANITIZER: [address, undefined]
+ rules:
+ # On merge request.
+ - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+ variables:
+ MODE: "code-change"
+ when: manual
+ allow_failure: true
+ # And on push to master.
+ - if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH
+ when: always
+ before_script:
+ # Get GitLab's container id.
+ - export CFL_CONTAINER_ID=`docker ps -q -f "label=com.gitlab.gitlab-runner.job.id=$CI_JOB_ID" -f "label=com.gitlab.gitlab-runner.type=build"`
+ script:
+ # local cfl-cache to mounted volume
+ - if ! test -L cfl-cache; then ln -s /cfl-cache cfl-cache; fi
+ # Will build and run the fuzzers.
+ - python3 "/opt/oss-fuzz/infra/cifuzz/cifuzz_combined_entrypoint.py"
+ artifacts:
+ # Upload artifacts when a crash makes the job fail.
+ when: always
+ expire_in: 30 days
+ paths:
+ - "${CFL_ARTIFACTS_DIR}"
+
+# Batch fuzzing enables continuous, regular fuzzing on your latest HEAD
+# and allows a corpus of inputs to build up over time, which greatly improves
+# the effectiveness of fuzzing. Batch fuzzing should be run on a schedule.
+fuzz-batch:
+ image:
+ name: "${CFL_IMAGE}"
+ entrypoint: ['']
+ stage: fuzz
+ needs: []
+ tags:
+ - docker-fuzz
+ variables:
+ FUZZ_SECONDS: 86400 # 24 hours
+ rules:
+ - if: $MODE == "batch"
+ before_script:
+ - export CFL_CONTAINER_ID=`docker ps -q -f "label=com.gitlab.gitlab-runner.job.id=$CI_JOB_ID" -f "label=com.gitlab.gitlab-runner.type=build"`
+ script:
+ - python3 "/opt/oss-fuzz/infra/cifuzz/cifuzz_combined_entrypoint.py"
+ artifacts:
+ when: always
+ expire_in: 30 days
+ paths:
+ - "${CFL_ARTIFACTS_DIR}"
+
+# Corpus pruning is a helper function that minimizes the corpuses by
+# removing corpus files (testcases) that do not increase the fuzzer’s
+# code coverage.
+fuzz-prune:
+ image:
+ name: "${CFL_IMAGE}"
+ entrypoint: ['']
+ stage: fuzz
+ needs: []
+ tags:
+ - docker-fuzz
+ rules:
+ - if: $MODE == "prune"
+ before_script:
+ - export CFL_CONTAINER_ID=`docker ps -q -f "label=com.gitlab.gitlab-runner.job.id=$CI_JOB_ID" -f "label=com.gitlab.gitlab-runner.type=build"`
+ script:
+ - python3 "/opt/oss-fuzz/infra/cifuzz/cifuzz_combined_entrypoint.py"
+ artifacts:
+ when: always
+ expire_in: 30 days
+ paths:
+ - "${CFL_ARTIFACTS_DIR}"
+
+# Continuous builds are used when a crash is found during MR fuzzing to determine
+# whether the crash was newly introduced. If the crash was not newly introduced,
+# MR fuzzing will not report it. This means that there will be fewer unrelated
+# failures when running code change fuzzing.
+fuzz-build:
+ image:
+ name: "${CFL_IMAGE}"
+ entrypoint: ['']
+ stage: fuzz
+ needs: []
+ tags:
+ - docker-fuzz
+ rules:
+ # Use $CI_DEFAULT_BRANCH or $CFL_BRANCH.
+ - if: $CI_COMMIT_BRANCH == $CFL_BRANCH && $CI_PIPELINE_SOURCE == "push"
+ variables:
+ MODE: "code-change"
+ UPLOAD_BUILD: "true"
+ before_script:
+ - export CFL_CONTAINER_ID=`docker ps -q -f "label=com.gitlab.gitlab-runner.job.id=$CI_JOB_ID" -f "label=com.gitlab.gitlab-runner.type=build"`
+ script:
+ - python3 "/opt/oss-fuzz/infra/cifuzz/cifuzz_combined_entrypoint.py"
+ artifacts:
+ when: always
+ expire_in: 30 days
+ paths:
+ - "${CFL_ARTIFACTS_DIR}"
+
+# scheduled job - generates periodic coverage reports
+fuzz-coverage:
+ image:
+ name: "${CFL_IMAGE}"
+ entrypoint: ['']
+ stage: fuzz
+ needs: []
+ tags:
+ - docker-fuzz
+ variables:
+ SANITIZER: "coverage"
+ rules:
+ - if: $MODE == "coverage"
+ before_script:
+ - export CFL_CONTAINER_ID=`cut -c9- < /proc/1/cpuset`
+ script:
+ - python3 "/opt/oss-fuzz/infra/cifuzz/cifuzz_combined_entrypoint.py"
+ after_script:
+ - shasum /opt/kea/sbin/*
+ - shasum /tmp/not-out/*/*
+ - shasum ${OUT}/*/*
+ artifacts:
+ when: always
+ expire_in: 30 days
+ paths:
+ - "${CFL_ARTIFACTS_DIR}"
+
+
############################### SAST ################################
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/sast/
#
.sast-analyzer:
extends: sast
+ stage: test
+ <<: *rules_for_test_stage
allow_failure: true
script:
- /analyzer run
+ rules:
+ - if: $SAST_DISABLED
+ when: never
+ - if: $CI_PIPELINE_SOURCE == 'schedule'
+ when: never
flawfinder-sast:
extends: .sast-analyzer
SAST_ANALYZER_IMAGE_TAG: latest
SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/flawfinder:$SAST_ANALYZER_IMAGE_TAG"
rules:
- - if: $SAST_DISABLED
- when: never
- if: $SAST_EXCLUDED_ANALYZERS =~ /flawfinder/
when: never
- if: $CI_COMMIT_BRANCH
exists:
- '**/*.cc'
- '**/*.h'
+
+semgrep-sast:
+ extends: .sast-analyzer
# ^^^^^^^^ This has to be the first line and cannot come later in this
# Makefile.am due to some bork in some versions of autotools.
-SUBDIRS = tools . ext src doc m4macros @PREMIUM_DIR@ @CONTRIB_DIR@
+SUBDIRS = tools . ext src fuzz doc m4macros @PREMIUM_DIR@ @CONTRIB_DIR@
USE_LCOV=@USE_LCOV@
LCOV=@LCOV@
docs:
$(MAKE) -C doc/sphinx
-
# These steps are necessary during installation
install-exec-hook:
mkdir -p $(DESTDIR)${localstatedir}/log/
# config.h may be included by headers supplied for building user-written
# hooks libraries, so we need to include it in the distribution.
pkginclude_HEADERS = config.h kea_version.h
+
+.PHONY: clean-coverage coverage cppcheck docs report-coverage
AC_MSG_RESULT("no. Fuzzing requires C++17 support.")
AC_MSG_ERROR("Fuzzing requires C++17 support.")
fi
+ if test "${enable_gtest}" = 'no'; then
+ AC_MSG_RESULT("no. Fuzzing requires gtest to be enabled.")
+ AC_MSG_ERROR("Fuzzing requires gtest to be enabled.")
+ fi
enable_fuzzing=${enableval}],
[enable_fuzzing=no]
)
runstatedir="$(eval echo ${runstatedir})"
fi
+AC_CONFIG_FILES([kea_version.h])
AC_CONFIG_FILES([Makefile])
AC_CONFIG_FILES([doc/Makefile])
AC_CONFIG_FILES([doc/sphinx/Makefile])
AC_CONFIG_FILES([ext/Makefile])
AC_CONFIG_FILES([ext/gtest/Makefile])
AC_CONFIG_FILES([ext/coroutine/Makefile])
-AC_CONFIG_FILES([kea_version.h])
+AC_CONFIG_FILES([fuzz/Makefile])
+AC_CONFIG_FILES([fuzz/input/Makefile])
+AC_CONFIG_FILES([fuzz/tests/Makefile])
AC_CONFIG_FILES([m4macros/Makefile])
AC_CONFIG_FILES([src/Makefile])
AC_CONFIG_FILES([src/bin/Makefile])
-// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-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/.
/**
+
@page fuzzer Fuzzing Kea
@section fuzzIntro Introduction
variety of generated data as input and is monitored for abnormal conditions
such as crashes or hangs.
-Fuzz testing of Kea uses the AFL (American Fuzzy Lop) program. In this, Kea is
-built using an AFL-supplied program that not only compiles the software but
-also instruments it. When run, AFL generates test cases and monitors the
-execution of Kea as it processes them. AFL will adjust the input based on
-these measurements, seeking to discover and test new execution paths.
+There are two ways to fuzz Kea.
+
+Option 1. With the libfuzzer harness function LLVMFuzzerTestOneInput.
+
+Option 2. With the AFL (American Fuzzy Lop) compiler.
+
+@section LLVMFuzzerTestOneInput Using the LLVMFuzzerTestOneInput Harness Function
+
+This mode of fuzzing works with virtually any compiler.
+
+There are four types of fuzzers implemented with this mode:
+- Config fuzzer
+- HTTP endpoint fuzzer
+- Packet fuzzer
+- Unix socket fuzzer
+
+There are two binaries under test:
+- `kea-dhcp4`
+- `kea-dhcp6`
+
+Combining the binaries and the fuzzer types results in seven fuzzing binaries:
+- `fuzz/fuzz-config-kea-dhcp4`
+- `fuzz/fuzz-config-kea-dhcp6`
+- `fuzz/fuzz-http-endpoint`
+- `fuzz/fuzz-packets-kea-dhcp4`
+- `fuzz/fuzz-packets-kea-dhcp6`
+- `fuzz/fuzz-unix-socket-kea-dhcp4`
+- `fuzz/fuzz-unix-socket-kea-dhcp6`
+
+@subsection HowToBuild How to Build the LLVM Fuzzer
+
+Use the "--enable-fuzzing" during the configure step. Then just compile as usual.
+
+@code
+./configure --enable-fuzzing
+make
+@endcode
+
+You can check that `config.report` shows these lines:
+
+@code
+Developer:
+[...]
+ Fuzzing: yes
+ AFL: no
+@endcode
+
+Compiling with AFL is permitted, but is not required.
+
+@subsection HowToRun How to Run the LLVM Fuzzer
+
+Each of these binaries has two ways to run. It tries to find a directory called
+`input/<name>` relative to the binary where `<name>` is the name of the binary.
+
+- The first mode: if the directory exists, it recursively takes all the files
+from that directory and provides them as fuzz input one-by-one. All the fuzzers
+have an empty file and a one-byte file as inputs committed to the repository.
+Config fuzzers also have all the files in `doc/examples/kea[46]` symlinked.
-@section fuzzTypes Types of Kea Fuzzing
+- The second mode: if the directory doesn't exist, then it accepts input from
+stdin, just like the old fuzzer did. In this mode, a fuzzer engine can be run on
+it. This is the mode used in CI.
+
+After compiling, all the fuzzers can be run with `make check` in the `fuzz`
+directory. The reasoning behind this is that while writing code, developers can
+quickly check if anything is broken. Obviously, this is not real fuzzing as long
+since the input from the `fuzz/input` directory is the same, but it rather tests
+if the fuzzers were broken during development.
+
+`make check` runs these fuzzers with `sudo`. It may interrupt the process asking
+for a password on systems that don't have passwordless root set up.
+
+@subsection FuzzingStructure The Code Structure of the LLVM Fuzzer
+
+The following functions are required to be implemented in each new fuzzer:
+
+- `int LLVMFuzzerInitialize();` - Does initialization that is required by the
+fuzzing. Is only run once. Is run automatically. It does not need to be run
+explicitly by the fuzzing engine.
+
+- `int LLVMFuzzerTearDown();` - Cleans up the setup like removing leftover
+files. Is automatically run at the beginning and the end of the fuzzing. It does
+not need to be run explicitly by the fuzzing engine.
+
+- `int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size);` - Implements
+the actual fuzzing. Takes the parameter input and achieves the object of the
+fuzzing with it. It needs to start with
+`static bool initialized(DoInitialization());` to do the initialization only
+once. This function is the only one that needs to be run explicitly by the
+fuzzing engine.
+
+The following functions are common to all fuzzers:
+
+- `int main(int, char* argv[])` - Implements the input searching mentioned
+above.
+
+- `bool DoInitialization();` - Sets up logging to prevent spurious logging and
+calls `int LLVMFuzzerInitialize();`.
+
+- `void writeToFile(std::string const& file, std::string const& content);` -
+A helpful function to write to file used in some fuzzers.
+
+@subsection FuzzingConsiderations Development Considerations About LLVM Fuzzing in Kea
+
+Exceptions make it difficult to maintain a fuzzer. We have to triage some of the
+exceptions. For example, JSONError is thrown when an invalid JSON is provided as
+input in config fuzzing. That leads to a core dump and can be interpreted as a
+crash by the fuzzing engine, which is not really what we're interested in
+because this exception is caught in the kea-dhcp[46] binaries. The old way of
+fuzzing may have been better from this point of view, because there was the
+guarantee that the right exceptions were caught and nothing more and we didn't
+have to pay attention to what exceptions needed to be ignored and which weren't.
+
+@section usingAFL Using AFL
+
+In this, Kea is built using an AFL-supplied program that not only compiles the
+software but also instruments it. When run, AFL generates test cases and
+monitors the execution of Kea as it processes them. AFL will adjust the input
+based on these measurements, seeking to discover and test new execution paths.
@subsection fuzzTypeNetwork Fuzzing with Network Packets
them. As with network packet fuzzing, the behaviour of Kea is monitored and
the content of subsequent files adjusted accordingly.
-@section fuzzBuild Building Kea for Fuzzing
+@subsection fuzzBuild Building Kea for Fuzzing
Whatever tests are done, Kea needs to be built with fuzzing in mind. The steps
for this are:
-# Build Kea. Kea should be compiled and built as usual, although the
following additional steps should be observed:
- - Set the environment variable CXX to point to the afl-clang-fast++
+ - Set the environment variable CXX to point to the afl-clang-fast
compiler.
- Specify a value of "--prefix" on the command line to set the directory
into which Kea is installed.
- - Add the "--enable-fuzz" switch to the "configure" command line.
+ - Add the "--enable-fuzzing" switch to the "configure" command line.
.
For example:
@code
- CXX=/opt/afl/afl-clang-fast++ ./configure --enable-fuzz --prefix=$HOME/installed
+ CXX=afl-clang-fast ./configure --enable-fuzzing --prefix=$HOME/installed
make
@endcode
simpler to install the programs in the directories set by "--prefix" and run
them from there.
-@section fuzzRun Running the Fuzzer
-
@subsection fuzzRunNetwork Fuzzing with Network Packets
-# In this type of fuzzing, Kea is processing packets from the fuzzer over a
using the loopback interface "lo" and IPv4 address 10.53.0.1, the
configuration file would contain the following snippet:
@code
+ {
"Dhcp4": {
- :
"interfaces-config": {
- "interfaces": ["lo/10.53.0.1"]
+ "interfaces": [
+ "lo/10.53.0.1"
+ ]
},
"subnet4": [
{
- :
- "interface": "lo",
- :
+ "interface": "lo"
}
- ],
- :
+ ]
}
+ }
@endcode
-# The specification of the interface and address in the configuration file
data. Ensure that only the payload of the UDP packet is exported.
- The "-o" switch specifies a directory (in this example called "fuzz-out")
that AFL will use to hold packets it has generated and packets that it has
- found causes crashes or hangs.
+ found causing crashes or hangs.
- "--" Separates the AFL command line from that of Kea.
- "./kea-dhcp6" is the program being fuzzed. As mentioned above, this
should be an executable image, and it will be simpler to fuzz one
@subsection fuzzRunConfig Fuzzing with Configuration Files
AFL can be used to check the parsing of the configuration files. In this type
-of fuzzing, AFL generates configuration files which is passes to Kea to check.
+of fuzzing, AFL generates configuration files which it passes to Kea to check.
Steps for this fuzzing are:
-# Build Kea as described above.
will replace these with the name of a file it has created when starting
Kea.
-@section Fuzzing Internals
-
@subsection fuzzInternalNetwork Fuzzing with Network Packets
The AFL fuzzer delivers packets to Kea's stdin. Although the part of Kea
Read and process one packet
}
@endcode
-When --enable-fuzz is specified, this is conceptually modified to:
+When --enable-fuzzing is specified, this is conceptually modified to:
@code{.unparsed}
while (not shutting down) {
Read stdin and copy data to address/port on which Kea is listening
}
@endcode
-Implementation is via an object of class "Fuzz". When created, it identifies
-an interface, address and port on which Kea is listening and creates the
-appropriate address structures for these. The port is passed as an argument to
-the constructor because at the point at which the object is constructed, that
+Implementation is via an object of class "PacketFuzzer". When created, it
+identifies an interface, address and port on which Kea is listening and creates
+the appropriate address structures for these. The port is passed as an argument
+to the constructor because at the point at which the object is constructed, that
information is readily available. The interface and address are picked up from
the environment variables mentioned above. Consideration was given to
extracting the interface and address information from the configuration file,
No changes were required to Kea source code to fuzz configuration files. In
fact, other than compiling with afl-clang++ and installing the resultant
executable, no other steps are required. In particular, there is no need to
-use the "--enable-fuzz" switch in the configuration command line (although
+use the "--enable-fuzzing" switch in the configuration command line (although
doing so will not cause any problems).
@subsection fuzzThreads Changes Required for Multi-Threaded Kea
become multi-threaded, the fuzzing code will need to be changed back to reading
the AFL input in the background.
-@section fuzzNotes Notes
-
@subsection fuzzNotesUnitTests Unit Test Failures
-If unit tests are built when --enable-fuzzing is specified, note that tests
-which check or use the DHCP servers (i.e. the unit tests in src/bin/dhcp4,
-src/bin/dhcp6 and src/bin/kea-admin) will fail. With no AFL-related
-environment variables defined, a C++ exception will be thrown with the
-description "no fuzzing interface has been set". However, if the
-KEA_AFL_INTERFACE and KEA_AFL_ADDRESS variables are set to valid values, the
-tests will hang.
+If unit tests are built when --enable-fuzzing is specified and with the AFL
+compiler, note that tests which check or use the DHCP servers (i.e. the unit
+tests in src/bin/dhcp4, src/bin/dhcp6 and src/bin/kea-admin) will fail.
+With no AFL-related environment variables defined, a C++ exception will be
+thrown with the description "no fuzzing interface has been set".
+However, if the `KEA_AFL_INTERFACE` and `KEA_AFL_ADDRESS` variables are set to
+valid values, the tests will hang.
Both these results are expected and should cause no concern. The exception is
thrown by the fuzzing object constructor when it attempts to create the address
the test will most likely fail as the input is unlikely to be that expected by
the test.)
-
*/
- DHCP4_CLASSES_ASSIGNED_AFTER_SUBNET_SELECTION
- DHCP4_CLASS_ASSIGNED
- DHCP4_CLASS_UNCONFIGURED
-- DHCP4_CLASS_UNDEFINED
-- DHCP4_CLASS_UNTESTABLE
- DHCP4_DHCP4O6_HOOK_SUBNET4_SELECT_DROP
- DHCP4_DHCP4O6_HOOK_SUBNET4_SELECT_SKIP
- DHCP4_DHCP4O6_PACKET_RECEIVED
- DHCP4_LEASE_QUERY_RECEIVED
- DHCP4_LEASE_QUERY_RESPONSE_SENT
- DHCP4_PACKET_QUEUE_FULL
+- DHCP4_REQUIRED_CLASS_NO_TEST
+- DHCP4_REQUIRED_CLASS_UNDEFINED
- DHCP4_SHUTDOWN
- DHCP4_SHUTDOWN_REQUEST
- DHCP6_BUFFER_RECEIVED
- DHCP6_CLASSES_ASSIGNED_AFTER_SUBNET_SELECTION
- DHCP6_CLASS_ASSIGNED
- DHCP6_CLASS_UNCONFIGURED
-- DHCP6_CLASS_UNDEFINED
-- DHCP6_CLASS_UNTESTABLE
- DHCP6_DHCP4O6_PACKET_RECEIVED
- DHCP6_FLEX_ID
- DHCP6_HOOK_BUFFER_SEND_SKIP
- DHCP6_LEASE_QUERY_REPLY_SENT
- DHCP6_PACKET_PROCESS_FAIL
- DHCP6_PACKET_QUEUE_FULL
+- DHCP6_REQUIRED_CLASS_NO_TEST
+- DHCP6_REQUIRED_CLASS_UNDEFINED
- DHCP6_REQUIRED_OPTIONS_CHECK_FAIL
- DHCP6_SHUTDOWN
- DHCP6_SHUTDOWN_REQUEST
--- /dev/null
+/fuzz_config_kea_dhcp4
+/fuzz_config_kea_dhcp6
+/fuzz_http_endpoint
+/fuzz_packets_kea_dhcp4
+/fuzz_packets_kea_dhcp6
+/fuzz_unix_socket_kea_dhcp4
+/fuzz_unix_socket_kea_dhcp6
--- /dev/null
+SUBDIRS = . input tests
+
+if FUZZING
+
+AM_CPPFLAGS =
+AM_CPPFLAGS += -I$(top_builddir)/src/bin -I$(top_srcdir)/src/bin
+AM_CPPFLAGS += -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += -DKEA_FUZZ_DIR_INSTALLATION=\"$(datarootdir)/$(PACKAGE_NAME)/fuzzing\"
+AM_CPPFLAGS += -DKEA_FUZZ_DIR_SOURCES=\"$(abs_top_builddir)/fuzz\"
+AM_CPPFLAGS += -DKEA_LFC_INSTALLATION=\"$(prefix)/sbin/kea-lfc\"
+AM_CPPFLAGS += -DKEA_LFC_SOURCES=\"$(abs_top_builddir)/src/bin/lfc/kea-lfc\"
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += $(GTEST_INCLUDES)
+
+AM_CXXFLAGS =
+AM_CXXFLAGS += $(KEA_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+sbin_PROGRAMS =
+sbin_PROGRAMS += fuzz_config_kea_dhcp4
+sbin_PROGRAMS += fuzz_config_kea_dhcp6
+sbin_PROGRAMS += fuzz_http_endpoint
+sbin_PROGRAMS += fuzz_packets_kea_dhcp4
+sbin_PROGRAMS += fuzz_packets_kea_dhcp6
+sbin_PROGRAMS += fuzz_unix_socket_kea_dhcp4
+sbin_PROGRAMS += fuzz_unix_socket_kea_dhcp6
+
+V6_LDADD = $(top_builddir)/src/bin/dhcp6/libdhcp6.la
+V4_LDADD = $(top_builddir)/src/bin/dhcp4/libdhcp4.la
+LDADD =
+LDADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
+LDADD += $(top_builddir)/src/lib/process/libkea-process.la
+LDADD += $(top_builddir)/src/lib/eval/libkea-eval.la
+LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
+LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
+LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
+LDADD += $(top_builddir)/src/lib/http/libkea-http.la
+LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+LDADD += $(top_builddir)/src/lib/database/libkea-database.la
+LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
+LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+LDADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) $(BOOST_LIBS)
+LDADD += $(GTEST_LDADD)
+
+AM_LDFLAGS = $(GTEST_LDFLAGS)
+
+common_sources = fuzz.cc fuzz.h
+if FUZZING_IN_CI
+AM_CXXFLAGS += -fsanitize=fuzzer -gdwarf-4
+else
+common_sources += main.cc
+endif
+
+fuzz_config_kea_dhcp4_SOURCES = $(common_sources)
+fuzz_config_kea_dhcp4_SOURCES += fuzz_config_kea_dhcp4.cc
+fuzz_config_kea_dhcp4_LDADD = $(V4_LDADD) $(LDADD)
+
+fuzz_config_kea_dhcp6_SOURCES = $(common_sources)
+fuzz_config_kea_dhcp6_SOURCES += fuzz_config_kea_dhcp6.cc
+fuzz_config_kea_dhcp6_LDADD = $(V6_LDADD) $(LDADD)
+
+fuzz_http_endpoint_SOURCES = $(common_sources)
+fuzz_http_endpoint_SOURCES += fuzz_http_endpoint.cc
+fuzz_http_endpoint_LDADD = $(LDADD)
+
+fuzz_packets_kea_dhcp4_SOURCES = $(common_sources)
+fuzz_packets_kea_dhcp4_SOURCES += fuzz_packets_kea_dhcp4.cc
+fuzz_packets_kea_dhcp4_LDADD = $(V4_LDADD) $(LDADD)
+
+fuzz_packets_kea_dhcp6_SOURCES = $(common_sources)
+fuzz_packets_kea_dhcp6_SOURCES += fuzz_packets_kea_dhcp6.cc
+fuzz_packets_kea_dhcp6_LDADD = $(V6_LDADD) $(LDADD)
+
+fuzz_unix_socket_kea_dhcp4_SOURCES = $(common_sources)
+fuzz_unix_socket_kea_dhcp4_SOURCES += fuzz_unix_socket_kea_dhcp4.cc
+fuzz_unix_socket_kea_dhcp4_LDADD = $(V4_LDADD) $(LDADD)
+
+fuzz_unix_socket_kea_dhcp6_SOURCES = $(common_sources)
+fuzz_unix_socket_kea_dhcp6_SOURCES += fuzz_unix_socket_kea_dhcp6.cc
+fuzz_unix_socket_kea_dhcp6_LDADD = $(V6_LDADD) $(LDADD)
+
+endif # FUZZING
--- /dev/null
+PD_POOLS="pd-pools"
--- /dev/null
+// Copyright (C) 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/.
+
+#include <config.h>
+
+#include <fuzz.h>
+
+#include <log/logger_support.h>
+#include <process/daemon.h>
+#include <util/filesystem.h>
+#include <util/encode/encode.h>
+
+#include <cassert>
+#include <string>
+
+using namespace isc::process;
+using namespace isc::util::encode;
+using namespace isc::util::file;
+using namespace std;
+
+extern "C" {
+
+string KEA_LFC = isFile(KEA_LFC_INSTALLATION) ? KEA_LFC_INSTALLATION : KEA_LFC_SOURCES;
+// string KEA_FUZZ_DIR = isFile(KEA_FUZZ_DIR_INSTALLATION) ? KEA_FUZZ_DIR_INSTALLATION : KEA_FUZZ_DIR_SOURCES;
+TemporaryDirectory TEMP_DIR = TemporaryDirectory();
+string KEA_FUZZ_DIR = TEMP_DIR.dirName();
+
+bool
+DoInitialization() {
+ LLVMFuzzerTearDown();
+
+ // Spoof the logger just enough to not get LoggingNotInitialized thrown.
+ // We explicitly don't want any logging during fuzzing for performance reasons.
+ setenv("KEA_LOCKFILE_DIR", KEA_FUZZ_DIR.c_str(), 0);
+ setenv("KEA_LFC_EXECUTABLE", "/bin/true", 0);
+ if (!getenv("DEBUG")) {
+ setenv("KEA_LOGGER_DESTINATION", "/dev/null", 0);
+ }
+ setenv("KEA_PIDFILE_DIR", KEA_FUZZ_DIR.c_str(), 0);
+ if (!isc::log::isLoggingInitialized()) {
+ isc::log::initLogger("fuzzer");
+ Daemon::loggerInit("fuzzer", /* verbose = */ false);
+ Daemon::setDefaultLoggerName("fuzzer");
+ }
+
+ return true;
+}
+
+void writeToFile(string const& file, string const& content) {
+ // Create the config file.
+ ofstream out(file, ios::out | ios::trunc);
+ assert(out.is_open());
+ out << content;
+ out.close();
+ assert(!out.is_open());
+}
+
+bool byteStreamToPacketData(uint8_t const* data, size_t size, vector<uint8_t>& byte_stream) {
+ string str(data, data + size);
+ if (!str.empty() && str.at(str.size() - 1) == '\n') {
+ str = str.substr(0, str.size() - 1);
+ }
+ if (str.find_first_not_of("0123456789abcdefABCDEF") != string::npos) {
+ return false;
+ }
+ if (str.size() % 2) {
+ return false;
+ }
+ decodeHex(str, byte_stream);
+ return true;
+}
+
+} // extern "C"
--- /dev/null
+// Copyright (C) 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/.
+
+#include <cstddef>
+#include <cstdint>
+#include <string>
+#include <vector>
+
+extern "C" {
+
+extern std::string KEA_FUZZ_DIR;
+extern std::string KEA_LFC;
+
+bool
+DoInitialization();
+
+int
+LLVMFuzzerInitialize();
+
+int
+LLVMFuzzerTearDown();
+
+int
+LLVMFuzzerTestOneInput(uint8_t const* data, size_t size);
+
+void
+writeToFile(std::string const& file, std::string const& content);
+
+bool
+byteStreamToPacketData(uint8_t const* data, size_t size, std::vector<uint8_t>& byte_stream);
+
+} // extern "C"
\ No newline at end of file
--- /dev/null
+// Copyright (C) 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/.
+
+#include <config.h>
+
+#include <fuzz.h>
+
+#include <cc/command_interpreter.h>
+#include <cc/user_context.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/json_config_parser.h>
+#include <dhcp4/parser_context.h>
+
+#include <cassert>
+#include <util/filesystem.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::process;
+using namespace std;
+
+namespace {
+
+static pid_t const PID(getpid());
+static string const PID_STR(to_string(PID));
+static string const KEA_DHCP4_CONF(KEA_FUZZ_DIR + "/kea-dhcp4-" + PID_STR + ".conf");
+
+} // namespace
+
+extern "C" {
+
+int
+LLVMFuzzerInitialize() {
+ static bool initialized(DoInitialization());
+ assert(initialized);
+
+ return 0;
+}
+
+int
+LLVMFuzzerTearDown() {
+ try {
+ remove(KEA_DHCP4_CONF.c_str());
+ } catch (...) {
+ }
+ return 0;
+}
+
+int
+LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) {
+ // Create the config file.
+ string const string_config(reinterpret_cast<char const*>(data), size);
+ writeToFile(KEA_DHCP4_CONF, string_config);
+
+ // Configure the server.
+ ControlledDhcpv4Srv server;
+ try {
+ server.init(KEA_DHCP4_CONF);
+ } catch (BadValue const&) {
+ } catch (Dhcp4ParseError const&) {
+ }
+
+ return 0;
+}
+
+} // extern "C"
--- /dev/null
+// Copyright (C) 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/.
+
+#include <config.h>
+
+#include <fuzz.h>
+
+#include <cc/command_interpreter.h>
+#include <cc/user_context.h>
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcp6/json_config_parser.h>
+#include <dhcp6/parser_context.h>
+
+#include <cassert>
+#include <util/filesystem.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::process;
+using namespace std;
+
+namespace {
+
+static pid_t const PID(getpid());
+static string const PID_STR(to_string(PID));
+static string const KEA_DHCP6_CONF(KEA_FUZZ_DIR + "/kea-dhcp6-" + PID_STR + ".conf");
+
+} // namespace
+
+extern "C" {
+
+int
+LLVMFuzzerInitialize() {
+ static bool initialized(DoInitialization());
+ assert(initialized);
+
+ return 0;
+}
+
+int
+LLVMFuzzerTearDown() {
+ try {
+ remove(KEA_DHCP6_CONF.c_str());
+ } catch (...) {
+ }
+ return 0;
+}
+
+int
+LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) {
+ // Create the config file.
+ string const string_config(reinterpret_cast<char const*>(data), size);
+ writeToFile(KEA_DHCP6_CONF, string_config);
+
+ // Configure the server.
+ ControlledDhcpv6Srv server;
+ try {
+ server.init(KEA_DHCP6_CONF);
+ } catch (BadValue const&) {
+ } catch (Dhcp6ParseError const&) {
+ }
+
+ return 0;
+}
+
+} // extern "C"
--- /dev/null
+// Copyright (C) 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/.
+
+#include <config.h>
+
+#include <cassert>
+#include <cstdlib>
+#include <iostream>
+#include <list>
+
+#include <fuzz.h>
+
+#include <asiolink/io_service.h>
+#include <asiolink/interval_timer.h>
+#include <cc/data.h>
+#include <config/cmd_http_listener.h>
+#include <http/listener.h>
+#include <http/post_request_json.h>
+#include <http/response.h>
+#include <http/response_json.h>
+#include <http/tests/response_test.h>
+#include <http/testutils/test_http_client.h>
+#include <process/d_controller.h>
+#include <util/filesystem.h>
+#include <util/multi_threading_mgr.h>
+
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::process;
+using namespace isc::http;
+using namespace isc::http::test;
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+void timeoutHandler() {
+ cerr << "Timeout occurred while fuzzing!" << endl;
+ abort();
+}
+
+/// @brief Represents HTTP POST request with JSON body.
+///
+/// In addition to the requirements specified by the @ref PostHttpRequest
+/// this class requires that the "Content-Type" is "application/json".
+///
+/// This class provides methods to parse and retrieve JSON data structures.
+struct PostHttpRequestBytes : PostHttpRequest {
+ /// @brief Constructor for inbound HTTP request.
+ explicit PostHttpRequestBytes() : PostHttpRequest() {
+ requireHeaderValue("Content-Type", "application/json");
+ }
+
+ /// @brief Constructor for outbound HTTP request.
+ ///
+ /// This constructor adds "Content-Type" header with the value of
+ /// "application/json" to the context.
+ ///
+ /// @param method HTTP method, e.g. POST.
+ /// @param uri URI.
+ /// @param version HTTP version.
+ /// @param host_header Host header to be included in the request. The default
+ /// is the empty Host header.
+ /// @param basic_auth Basic HTTP authentication credential. The default
+ /// is no authentication.
+ explicit PostHttpRequestBytes(const Method& method,
+ const string& uri,
+ const HttpVersion& version,
+ const HostHttpHeader& host_header = HostHttpHeader(),
+ const BasicHttpAuthPtr& basic_auth = BasicHttpAuthPtr())
+ : PostHttpRequest(method, uri, version, host_header, basic_auth) {
+ requireHeaderValue("Content-Type", "application/json");
+ context()->headers_.push_back(HttpHeaderContext("Content-Type", "application/json"));
+ }
+
+ /// @brief Sets JSON body for an outbound message.
+ ///
+ /// @param body JSON structure to be used as a body.
+ void setBodyAsBytes(vector<uint8_t> const& input) {
+ context_->body_ = string(input.begin(), input.end());
+ }
+};
+
+using PostHttpRequestBytesPtr = boost::shared_ptr<PostHttpRequestBytes>;
+
+ThreadPool<function<void()>> THREAD_POOL;
+
+static pid_t const PID(getpid());
+static string const PID_STR(to_string(PID));
+
+} // namespace
+
+extern "C" {
+
+int
+LLVMFuzzerInitialize() {
+ static bool initialized(DoInitialization());
+ assert(initialized);
+
+ return 0;
+}
+
+int
+LLVMFuzzerTearDown() {
+ return 0;
+}
+
+int
+LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) {
+ string const address("127.0.0.1");
+ int const port(18000);
+ int const timeout(100000);
+
+ CmdHttpListener listener(IOAddress(address), port);
+ MultiThreadingMgr::instance().setMode(true);
+
+ // Start the server.
+ listener.start();
+
+ // Create a client and specify the URL on which the server can be reached.
+ IOServicePtr io_service(new IOService());
+ IntervalTimer run_io_service_timer(io_service);
+ HttpClient client(io_service, false);
+ stringstream ss;
+ ss << "http://" << address << ":" << port;
+ Url url(ss.str());
+
+ // Initiate request to the server.
+ PostHttpRequestBytesPtr request(new PostHttpRequestBytes(
+ HttpRequest::Method::HTTP_POST, "/", HttpVersion(1, 1)));
+
+ // Body is a map with a specified parameter included.
+ vector<uint8_t> const body(data, data + size);
+ request->setBodyAsBytes(body);
+ request->finalize();
+ HttpResponseJsonPtr response(new HttpResponseJson());
+ client.asyncSendRequest(
+ url, TlsContextPtr(), request, response,
+ [](boost::system::error_code const&,
+ HttpResponsePtr const&,
+ string const&) {
+ });
+
+ // Actually trigger the requests. The requests should be handlded by the
+ // server one after another. While the first request is being processed
+ // the server should queue another one.
+ io_service->getInternalIOService().reset();
+ run_io_service_timer.setup(&timeoutHandler, timeout, IntervalTimer::ONE_SHOT);
+ io_service->runOne();
+ io_service->getInternalIOService().reset();
+ io_service->poll();
+
+ // Make sure that the received responses are different. We check that by
+ // comparing value of the sequence parameters.
+ if (getenv("DEBUG")) {
+ if (response) {
+ cout << response->getBody() << endl;
+ } else {
+ cout << "no response" << endl;
+ }
+ }
+ listener.stop();
+ io_service->poll();
+ client.stop();
+ MultiThreadingMgr::instance().setMode(false);
+
+ return 0;
+}
+
+} // extern "C"
--- /dev/null
+// Copyright (C) 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/.
+
+#include <config.h>
+
+#include <fuzz.h>
+
+#include <cc/command_interpreter.h>
+#include <cc/user_context.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/json_config_parser.h>
+#include <dhcp4/parser_context.h>
+#include <dhcpsrv/packet_fuzzer.h>
+#include <util/encode/encode.h>
+
+#include <cassert>
+#include <cstdlib>
+#include <util/filesystem.h>
+#include <vector>
+
+using namespace isc;
+using namespace isc::config;
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+static string const KEA_DHCP4_CONF(KEA_FUZZ_DIR + "/kea-dhcp4.conf");
+static string KEA_DHCP4_FUZZING_INTERFACE;
+static string KEA_DHCP4_FUZZING_ADDRESS;
+
+} // namespace
+
+extern "C" {
+
+int
+LLVMFuzzerInitialize() {
+ static bool initialized(DoInitialization());
+ assert(initialized);
+
+ setenv("KEA_DHCP4_FUZZING_ROTATE_PORT", "true", 0);
+
+ char const* interface(getenv("KEA_DHCP4_FUZZING_INTERFACE"));
+ KEA_DHCP4_FUZZING_INTERFACE = string(interface ? interface : "lo");
+
+ char const* address(getenv("KEA_DHCP4_FUZZING_ADDRESS"));
+ KEA_DHCP4_FUZZING_ADDRESS = string(address ? address : "127.0.0.1");
+
+ writeToFile(KEA_DHCP4_CONF, R"(
+ {
+ "Dhcp4": {
+ "interfaces-config": {
+ "dhcp-socket-type": "udp",
+ "interfaces": [
+ ")" + KEA_DHCP4_FUZZING_INTERFACE + R"("
+ ]
+ },
+ "lease-database": {
+ "persist": false,
+ "type": "memfile"
+ },
+ "subnet4": [
+ {
+ "id": 1,
+ "pools": [
+ {
+ "pool": "10.0.0.0/8"
+ }
+ ],
+ "subnet": "10.0.0.0/8"
+ }
+ ]
+ }
+ }
+ )");
+
+ // Iterate through the interfaces and expect no errors.
+ for (IfacePtr const& interface : IfaceMgr::instance().getIfaces()) {
+ for (string const& error : interface->getErrors()) {
+ cout << error << endl;
+ }
+ assert(interface->getErrors().empty());
+ }
+
+ return 0;
+}
+
+int
+LLVMFuzzerTearDown() {
+ try {
+ remove(KEA_DHCP4_CONF.c_str());
+ } catch (...) {
+ }
+ return 0;
+}
+
+int
+LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) {
+ vector<uint8_t> byte_stream;
+ bool const valid(byteStreamToPacketData(data, size, byte_stream));
+ if (!valid) {
+ cout << "Invalid input. Skipping..." << endl;
+ return 0;
+ }
+
+ ControlledDhcpv4Srv server;
+ server.init(KEA_DHCP4_CONF);
+
+ // Fuzz.
+ PacketFuzzer fuzzer(ControlledDhcpv4Srv::getInstance()->getServerPort(),
+ KEA_DHCP4_FUZZING_INTERFACE, KEA_DHCP4_FUZZING_ADDRESS);
+ fuzzer.transfer(byte_stream.data(), byte_stream.size());
+ ControlledDhcpv4Srv::getInstance()->runOne();
+
+ return 0;
+}
+
+} // extern "C"
--- /dev/null
+// Copyright (C) 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/.
+
+#include <config.h>
+
+#include <fuzz.h>
+
+#include <cc/command_interpreter.h>
+#include <cc/user_context.h>
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcp6/json_config_parser.h>
+#include <dhcp6/parser_context.h>
+#include <dhcpsrv/packet_fuzzer.h>
+#include <util/encode/encode.h>
+
+#include <cassert>
+#include <cstdlib>
+#include <util/filesystem.h>
+#include <vector>
+
+using namespace isc;
+using namespace isc::config;
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+static string const KEA_DHCP6_CONF(KEA_FUZZ_DIR + "/kea-dhcp6.conf");
+static string KEA_DHCP6_FUZZING_INTERFACE;
+static string KEA_DHCP6_FUZZING_ADDRESS;
+
+} // namespace
+
+extern "C" {
+
+int
+LLVMFuzzerInitialize() {
+ static bool initialized(DoInitialization());
+ assert(initialized);
+
+ setenv("KEA_DHCP6_FUZZING_ROTATE_PORT", "true", 0);
+
+ char const* interface(getenv("KEA_DHCP6_FUZZING_INTERFACE"));
+ KEA_DHCP6_FUZZING_INTERFACE = string(interface ? interface : "lo");
+
+ char const* address(getenv("KEA_DHCP6_FUZZING_ADDRESS"));
+ KEA_DHCP6_FUZZING_ADDRESS = string(address ? address : "::1");
+
+ writeToFile(KEA_DHCP6_CONF, R"(
+ {
+ "Dhcp6": {
+ "interfaces-config": {
+ "interfaces": [
+ ")" + KEA_DHCP6_FUZZING_INTERFACE + R"("
+ ]
+ },
+ "lease-database": {
+ "persist": false,
+ "type": "memfile"
+ },
+ "server-id": {
+ "type": "EN",
+ "persist": false
+ },
+ "subnet6": [
+ {
+ "id": 1,
+ "pools": [
+ {
+ "pool": "2001:db8::/80"
+ }
+ ],
+ "subnet": "2001:db8::/64"
+ }
+ ]
+ }
+ }
+ )");
+
+ // Iterate through the interfaces and expect no errors.
+ for (IfacePtr const& interface : IfaceMgr::instance().getIfaces()) {
+ for (string const& error : interface->getErrors()) {
+ cout << error << endl;
+ }
+ assert(interface->getErrors().empty());
+ }
+
+ return 0;
+}
+
+int
+LLVMFuzzerTearDown() {
+ try {
+ remove(KEA_DHCP6_CONF.c_str());
+ } catch (...) {
+ }
+ return 0;
+}
+
+int
+LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) {
+ vector<uint8_t> byte_stream;
+ bool const valid(byteStreamToPacketData(data, size, byte_stream));
+ if (!valid) {
+ cout << "Invalid input. Skipping..." << endl;
+ return 0;
+ }
+
+ ControlledDhcpv6Srv server;
+ server.init(KEA_DHCP6_CONF);
+
+ // Fuzz.
+ PacketFuzzer fuzzer(ControlledDhcpv6Srv::getInstance()->getServerPort(),
+ KEA_DHCP6_FUZZING_INTERFACE, KEA_DHCP6_FUZZING_ADDRESS);
+ fuzzer.transfer(byte_stream.data(), byte_stream.size());
+ ControlledDhcpv6Srv::getInstance()->runOne();
+
+ return 0;
+}
+
+} // extern "C"
--- /dev/null
+// Copyright (C) 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/.
+
+#include <config.h>
+
+#include <fuzz.h>
+
+#include <asiolink/io_service.h>
+#include <cc/data.h>
+#include <config/command_mgr.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <testutils/unix_control_client.h>
+
+#include <util/filesystem.h>
+
+#include <cassert>
+
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::util;
+using namespace isc::util::file;
+using namespace std;
+
+namespace {
+
+static pid_t const PID(getpid());
+static string const PID_STR(to_string(PID));
+static string const KEA_DHCP4_CONF(KEA_FUZZ_DIR + "/kea-dhcp4-" + PID_STR + ".conf");
+static string const KEA_DHCP4_CSV(KEA_FUZZ_DIR + "/kea-dhcp4-" + PID_STR + ".csv");
+static string const SOCKET(KEA_FUZZ_DIR + "/kea-dhcp4-ctrl-" + PID_STR + ".sock");
+
+static UnixControlClient CLIENT;
+static IOServicePtr IO_SERVICE(new IOService());
+
+} // namespace
+
+extern "C" {
+
+int
+LLVMFuzzerInitialize() {
+ static bool initialized(DoInitialization());
+ assert(initialized);
+
+ // "control-socket" is of explicit interest, but we also specify the memfile
+ // CSV location to make sure that we don't get an error caused by an invalid
+ // file path.
+ writeToFile(KEA_DHCP4_CONF, R"(
+ {
+ "Dhcp4": {
+ "control-socket": {
+ "socket-name": ")" + SOCKET + R"(",
+ "socket-type": "unix"
+ },
+ "lease-database": {
+ "name": ")" + KEA_DHCP4_CSV + R"(",
+ "type": "memfile"
+ }
+ }
+ }
+ )");
+
+ // Iterate through the interfaces and expect no errors.
+ for (IfacePtr const& interface : IfaceMgr::instance().getIfaces()) {
+ for (string const& error : interface->getErrors()) {
+ cout << error << endl;
+ }
+ assert(interface->getErrors().empty());
+ }
+
+ return 0;
+}
+
+int
+LLVMFuzzerTearDown() {
+ try {
+ remove(KEA_DHCP4_CONF.c_str());
+ } catch (...) {
+ }
+ try {
+ remove(KEA_DHCP4_CSV.c_str());
+ } catch (...) {
+ }
+ try {
+ remove(SOCKET.c_str());
+ } catch (...) {
+ }
+ try {
+ remove((SOCKET + ".lock").c_str());
+ } catch (...) {
+ }
+
+ return 0;
+}
+
+int
+LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) {
+ CfgMgr::instance().clear();
+ ControlledDhcpv4Srv server;
+ server.init(KEA_DHCP4_CONF);
+ assert(isSocket(SOCKET));
+
+ string const command(reinterpret_cast<char const*>(data), size);
+ CLIENT.connectToServer(SOCKET);
+ CLIENT.sendCommand(command);
+ ControlledDhcpv4Srv::getInstance()->getIOService()->poll();
+ string response;
+ CLIENT.getResponse(response);
+ ControlledDhcpv4Srv::getInstance()->getIOService()->poll();
+ CLIENT.disconnectFromServer();
+ ControlledDhcpv4Srv::getInstance()->getIOService()->poll();
+
+ return 0;
+}
+
+} // extern "C"
--- /dev/null
+// Copyright (C) 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/.
+
+#include <config.h>
+
+#include <fuzz.h>
+
+#include <asiolink/io_service.h>
+#include <cc/data.h>
+#include <config/command_mgr.h>
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <testutils/unix_control_client.h>
+
+#include <util/filesystem.h>
+
+#include <cassert>
+
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::util;
+using namespace isc::util::file;
+using namespace std;
+
+namespace {
+
+static pid_t const PID(getpid());
+static string const PID_STR(to_string(PID));
+static string const KEA_DHCP6_CONF(KEA_FUZZ_DIR + "/kea-dhcp6-" + PID_STR + ".conf");
+static string const KEA_DHCP6_CSV(KEA_FUZZ_DIR + "/kea-dhcp6-" + PID_STR + ".csv");
+static string const SOCKET(KEA_FUZZ_DIR + "/kea-dhcp6-ctrl-" + PID_STR + ".sock");
+
+static UnixControlClient CLIENT;
+static IOServicePtr IO_SERVICE(new IOService());
+
+} // namespace
+
+extern "C" {
+
+int
+LLVMFuzzerInitialize() {
+ static bool initialized(DoInitialization());
+ assert(initialized);
+
+ // "control-socket" is of explicit interest, but we also specify the memfile
+ // CSV location and the server-id to make sure that we don't get an error
+ // caused by an invalid file path.
+ writeToFile(KEA_DHCP6_CONF, R"(
+ {
+ "Dhcp6": {
+ "control-socket": {
+ "socket-name": ")" + SOCKET + R"(",
+ "socket-type": "unix"
+ },
+ "lease-database": {
+ "name": ")" + KEA_DHCP6_CSV + R"(",
+ "type": "memfile"
+ },
+ "server-id": {
+ "type": "EN",
+ "enterprise-id": 2495,
+ "identifier": "0123456789",
+ "persist": false
+ }
+ }
+ }
+ )");
+
+ // Iterate through the interfaces and expect no errors.
+ for (IfacePtr const& interface : IfaceMgr::instance().getIfaces()) {
+ for (string const& error : interface->getErrors()) {
+ cout << error << endl;
+ }
+ assert(interface->getErrors().empty());
+ }
+
+ return 0;
+}
+
+int
+LLVMFuzzerTearDown() {
+ try {
+ remove(KEA_DHCP6_CONF.c_str());
+ } catch (...) {
+ }
+ try {
+ remove(KEA_DHCP6_CSV.c_str());
+ } catch (...) {
+ }
+ try {
+ remove(SOCKET.c_str());
+ } catch (...) {
+ }
+ try {
+ remove((SOCKET + ".lock").c_str());
+ } catch (...) {
+ }
+
+ return 0;
+}
+
+int
+LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) {
+ CfgMgr::instance().clear();
+ ControlledDhcpv6Srv server;
+ server.init(KEA_DHCP6_CONF);
+ assert(isSocket(SOCKET));
+
+ string const command(reinterpret_cast<char const*>(data), size);
+ CLIENT.connectToServer(SOCKET);
+ CLIENT.sendCommand(command);
+ ControlledDhcpv6Srv::getInstance()->getIOService()->poll();
+ string response;
+ CLIENT.getResponse(response);
+ ControlledDhcpv6Srv::getInstance()->getIOService()->poll();
+ CLIENT.disconnectFromServer();
+ ControlledDhcpv6Srv::getInstance()->getIOService()->poll();
+
+ return 0;
+}
+
+} // extern "C"
--- /dev/null
+SUBDIRS = .
+
+if FUZZING
+
+fuzzdir = "${datarootdir}/${PACKAGE_NAME}/fuzz"
+
+nobase_dist_fuzz_DATA =
+nobase_dist_fuzz_DATA += fuzz-config-kea-dhcp4/empty
+nobase_dist_fuzz_DATA += fuzz-config-kea-dhcp4/one-byte
+nobase_dist_fuzz_DATA += fuzz-config-kea-dhcp6/empty
+nobase_dist_fuzz_DATA += fuzz-config-kea-dhcp6/one-byte
+nobase_dist_fuzz_DATA += fuzz-http-endpoint/config-get
+nobase_dist_fuzz_DATA += fuzz-http-endpoint/config-get-with-service
+nobase_dist_fuzz_DATA += fuzz-http-endpoint/empty
+nobase_dist_fuzz_DATA += fuzz-http-endpoint/empty-json-map
+nobase_dist_fuzz_DATA += fuzz-http-endpoint/one-byte
+nobase_dist_fuzz_DATA += fuzz-http-endpoint/one-entry-json-map
+nobase_dist_fuzz_DATA += fuzz-packets-kea-dhcp4/dhcp-payload-only
+nobase_dist_fuzz_DATA += fuzz-packets-kea-dhcp4/empty
+nobase_dist_fuzz_DATA += fuzz-packets-kea-dhcp4/full-dhcp-packet
+nobase_dist_fuzz_DATA += fuzz-packets-kea-dhcp4/one-byte
+nobase_dist_fuzz_DATA += fuzz-packets-kea-dhcp4/udp-header
+nobase_dist_fuzz_DATA += fuzz-packets-kea-dhcp6/dhcp-payload-only
+nobase_dist_fuzz_DATA += fuzz-packets-kea-dhcp6/empty
+nobase_dist_fuzz_DATA += fuzz-packets-kea-dhcp6/full-dhcp-packet
+nobase_dist_fuzz_DATA += fuzz-packets-kea-dhcp6/one-byte
+nobase_dist_fuzz_DATA += fuzz-packets-kea-dhcp6/udp-header
+nobase_dist_fuzz_DATA += fuzz-unix-socket-kea-dhcp4/config-get
+nobase_dist_fuzz_DATA += fuzz-unix-socket-kea-dhcp4/config-get-with-service
+nobase_dist_fuzz_DATA += fuzz-unix-socket-kea-dhcp4/empty
+nobase_dist_fuzz_DATA += fuzz-unix-socket-kea-dhcp4/empty-json-map
+nobase_dist_fuzz_DATA += fuzz-unix-socket-kea-dhcp4/one-byte
+nobase_dist_fuzz_DATA += fuzz-unix-socket-kea-dhcp4/one-entry-json-map
+nobase_dist_fuzz_DATA += fuzz-unix-socket-kea-dhcp6/config-get
+nobase_dist_fuzz_DATA += fuzz-unix-socket-kea-dhcp6/config-get-with-service
+nobase_dist_fuzz_DATA += fuzz-unix-socket-kea-dhcp6/empty
+nobase_dist_fuzz_DATA += fuzz-unix-socket-kea-dhcp6/empty-json-map
+nobase_dist_fuzz_DATA += fuzz-unix-socket-kea-dhcp6/one-byte
+nobase_dist_fuzz_DATA += fuzz-unix-socket-kea-dhcp6/one-entry-json-map
+nobase_dist_fuzz_DATA += kea-dhcp4.conf
+nobase_dist_fuzz_DATA += kea-dhcp6.conf
+
+endif # FUZZING
--- /dev/null
+../../../doc/examples/kea4
\ No newline at end of file
--- /dev/null
+0a
\ No newline at end of file
--- /dev/null
+../../../doc/examples/kea6
\ No newline at end of file
--- /dev/null
+0a
\ No newline at end of file
--- /dev/null
+{
+ "command": "config-get"
+}
--- /dev/null
+{
+ "command": "config-get",
+ "service": [ "dhcp6" ]
+}
--- /dev/null
+0a
\ No newline at end of file
--- /dev/null
+{
+ "a": 1
+}
--- /dev/null
+210101060100000000000000000000000000000000000000000a010001000c0102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501013707011c02030f060c3d0701000c01020304ff
--- /dev/null
+000400010006d6b6574a0cce713b0800450001229b384000401194910a010001ffffffff00430043010e0b210101060100000000000000000000000000000000000000000a010001000c0102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501013707011c02030f060c3d0701000c01020304ff
--- /dev/null
+0a
\ No newline at end of file
--- /dev/null
+000400010006d6b6574a0cce713b0800450001229b384000401194910a010001ffffffff00430043010e0b
--- /dev/null
+010000000001000e000100012b8b4659000c010203040003000c0000000100000e10000015180006000400170018000800020000
--- /dev/null
+000400010006d6b6574a0cce000086dd6001d3f9003c110120010db8000100000000000000000001ff02000000000000000000000001000202220223003c2d0e010000000001000e000100012b8b4659000c010203040003000c0000000100000e10000015180006000400170018000800020000
--- /dev/null
+0a
\ No newline at end of file
--- /dev/null
+000400010006d6b6574a0cce000086dd6001d3f9003c110120010db8000100000000000000000001ff02000000000000000000000001000202220223003c2d0e
--- /dev/null
+{
+ "command": "config-get"
+}
--- /dev/null
+{
+ "command": "config-get",
+ "service": [ "dhcp4" ]
+}
--- /dev/null
+0a
\ No newline at end of file
--- /dev/null
+{
+ "a": 1
+}
--- /dev/null
+{
+ "command": "config-get"
+}
--- /dev/null
+{
+ "command": "config-get",
+ "service": [ "dhcp6" ]
+}
--- /dev/null
+0a
\ No newline at end of file
--- /dev/null
+{
+ "a": 1
+}
--- /dev/null
+{
+ "Dhcp4": {
+ "interfaces-config": {
+ "interfaces": [
+ "*"
+ ]
+ },
+ "subnet4": [
+ {
+ "id": 1,
+ "pools": [
+ {
+ "pool": "127.0.0.0/8"
+ }
+ ],
+ "subnet": "127.0.0.0/8"
+ }
+ ]
+ }
+}
--- /dev/null
+{
+ "Dhcp6": {
+ "interfaces-config": {
+ "interfaces": [
+ "*"
+ ]
+ },
+ "subnet6": [
+ {
+ "id": 1,
+ "pd-pools": [
+ {
+ "delegated-len": 120,
+ "prefix": "2001:db8:1:0:2::",
+ "prefix-len": 80
+ }
+ ],
+ "pools": [
+ {
+ "pool": "::/80"
+ }
+ ],
+ "subnet": "::/64"
+ }
+ ]
+ }
+}
--- /dev/null
+#!/bin/sh
+
+set -eu
+
+script_path=$(cd "$(dirname "${0}")" && pwd)
+
+cd "${script_path}"
+
+generate() {
+ source="${1}"
+ target="${2}"
+
+ source_content=$(cat "${source}")
+ variable_name=$(echo "${source}" | tr '[:lower:]' '[:upper:]' | sed 's/\./_/' | sed 's/-/_/')
+
+ cat > "${target}" <<HERE_DOCUMENT
+#include <string>
+
+extern std::string ${variable_name} = R"(
+${source_content}
+)";
+HERE_DOCUMENT
+}
+
+generate kea-dhcp4.conf ../kea-dhcp4.h
+generate kea-dhcp6.conf ../kea-dhcp6.h
--- /dev/null
+#include <string>
+
+extern std::string KEA_DHCP4_CONF = R"(
+{
+ "Dhcp4": {
+ "interfaces-config": {
+ "interfaces": [
+ "*"
+ ]
+ },
+ "subnet4": [
+ {
+ "id": 1,
+ "pools": [
+ {
+ "pool": "127.0.0.0/8"
+ }
+ ],
+ "subnet": "127.0.0.0/8"
+ }
+ ]
+ }
+}
+)";
--- /dev/null
+#include <string>
+
+extern std::string KEA_DHCP6_CONF = R"(
+{
+ "Dhcp6": {
+ "interfaces-config": {
+ "interfaces": [
+ "*"
+ ]
+ },
+ "subnet6": [
+ {
+ "id": 1,
+ "pd-pools": [
+ {
+ "delegated-len": 120,
+ "prefix": "2001:db8:1:0:2::",
+ "prefix-len": 80
+ }
+ ],
+ "pools": [
+ {
+ "pool": "::/80"
+ }
+ ],
+ "subnet": "::/64"
+ }
+ ]
+ }
+}
+)";
--- /dev/null
+#!/bin/sh
+
+# 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
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+set -eux
+
+exec "${2}" "${1}/$(basename "${2}").in" -max_total_time=5 -print_pcs=1 -print_final_stats=1 -print_corpus_stats=1 -print_coverage=1
--- /dev/null
+// Copyright (C) 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/.
+
+#include <config.h>
+
+#include <util/filesystem.h>
+#include <fuzz.h>
+
+#include <cassert>
+#include <cstdio>
+#include <fstream>
+#include <iostream>
+#include <list>
+#include <sstream>
+#include <vector>
+
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+using namespace isc::util::file;
+using namespace std;
+
+int
+main(int, char* argv[]) {
+ int const return_code(LLVMFuzzerInitialize());
+ assert(return_code == 0);
+
+ // Determine some paths.
+ Path const this_binary(argv[0]);
+ string const ancestor_path(Path(this_binary.parentPath()).parentPath());
+ string const filename(this_binary.filename());
+ stringstream ss;
+ ss << ancestor_path << "/input/" << filename;
+ Path const p(ss.str());
+
+ // Print start header.
+ if (isatty(fileno(stdin))) {
+ cout << "\033[92m[ RUN ]\033[0m " << filename << endl;
+ } else {
+ cout << "[ RUN ] " << filename << endl;
+ }
+
+ int exit_code(0);
+ string directory(p.str());
+ if (exists(directory)) {
+ // Recursively take all regular files as input.
+ list<string> files;
+
+ struct dirent *dp;
+ DIR *dfd(opendir(p.str().c_str()));
+ while ((dp = readdir(dfd)) != nullptr) {
+ string file(dp->d_name);
+ if (file == "." || file == "..") {
+ continue;
+ }
+ string entry(directory + '/' + dp->d_name);
+
+ if (!isFile(entry)) {
+ continue;
+ }
+
+ // Save file names.
+ files.push_back(entry);
+ }
+
+ // Sort the file names so that the order is the same each time.
+ files.sort();
+
+ for (string& f : files) {
+ // Read content from file.
+ basic_ifstream<uint8_t> file(f, ios::binary);
+
+ if (!file.is_open()) {
+ cerr << "ERROR: could not open file " << f << endl;
+ return 1;
+ }
+
+ // Get the file size.
+ file.seekg(0, std::ios::end);
+ streampos const bytes(file.tellg());
+ file.seekg(0, std::ios::beg);
+
+ // Read the entire file into a vector.
+ vector<uint8_t> buffer(bytes);
+ file.read(buffer.data(), bytes);
+
+ file.close();
+
+ // Fuzz.
+ f.replace(f.find(ancestor_path), ancestor_path.size() + 1, string());
+ cout << "Fuzzing with " << bytes << " byte" << (bytes == 1 ? string() : "s") << " read from "
+ << f << "..." << endl;
+ exit_code |= LLVMFuzzerTestOneInput(buffer.data(), bytes);
+ }
+ } else {
+ // Read input from stdin.
+ cout << "Waiting on input..." << endl;
+ vector<uint8_t> buffer(65536);
+ size_t const bytes(fread(&buffer[0], sizeof(buffer[0]), buffer.size(), stdin));
+
+ // Fuzz.
+ cout << "Fuzzing with " << bytes << " byte" << (bytes == 1 ? "" : "s")
+ << " read from stdin..." << endl;
+ exit_code |= LLVMFuzzerTestOneInput(buffer.data(), bytes);
+ }
+
+ // Tear down the setup.
+ LLVMFuzzerTearDown();
+
+ // Print end header.
+ string const result(exit_code == 0 ? " OK " : " FAILED ");
+ if (isatty(fileno(stdin))) {
+ cout << "\033[92m[" << result << "]\033[0m " << filename << endl;
+ } else {
+ cout << "[" << result << "] " << filename << endl;
+ }
+
+ return exit_code;
+}
--- /dev/null
+#!/bin/sh
+
+# Copyright (C) 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/.
+
+# shellcheck disable=all
+
+# Disable this script altogether for now.
+exit 0
+
+# Disable this script if the interface and address are used from environment variables.
+if test -n "${KEA_DHCP4_FUZZING_INTERFACE+x}" ||
+ test -n "${KEA_DHCP4_FUZZING_ADDRESS+x}" ||
+ test -n "${KEA_DHCP6_FUZZING_INTERFACE+x}" ||
+ test -n "${KEA_DHCP6_FUZZING_ADDRESS+x}"; then
+ printf 'Environment variables set. Will use those. Abandoning.\n'
+ exit 0
+fi
+
+script_path=$(cd "$(dirname "${0}")" && pwd)
+
+cd "${script_path}" > /dev/null
+
+# Add sudo to the fuzzers.
+sudo='if ! sudo -n true; then exec sudo -- "${0}" "${@}"; fi'
+for i in fuzz-*-kea-dhcp[46]; do
+ continue # Disable this loop for now.
+ if ! grep -F "${sudo}" "${i}" > /dev/null; then
+ sed -i "2i${sudo}" "${i}"
+ fi
+done
+
+# Create kea-dhcp{v}-fuzz-* wrapper scripts which adds the afl-fuzz command prefix to kea-dhcp{v}.
+for v in 4 6; do
+ continue # Disable this loop for now.
+ executable="../src/bin/dhcp${v}/kea-dhcp${v}"
+
+ for f in config packets unix-socket; do
+ fuzzed_executable="${executable}-fuzz-${f}"
+ cp "${executable}" "${fuzzed_executable}"
+ mkdir -p "output/config/kea-dhcp${v}"
+ sed -i "s# *exec \"\$progdir/\$program\"#\n\
+ export AFL_DEBUG='1'\n\
+ export AFL_DEBUG_CHILD='1'\n\
+ export AFL_LLVM_MAP_ADDR='true'\n\
+ export AFL_MAP_SIZE='10000000'\n\
+ export KEA_AFL_ADDRESS='10.1.0.1'\n\
+ export KEA_AFL_INTERFACE='vethclient'\n\
+ export KEA_AFL_LOOP_MAX=2\n\
+ exec afl-fuzz -M fuzzer1 -t 20000+ -m 50000 -i 'seeds/${f}' -o 'output/config/kea-dhcp${v}' -x /opt/dict.dat -- \"\$progdir/\$program\"\
+ #g" "${fuzzed_executable}"
+ sed -i "2i${sudo}" "${fuzzed_executable}"
+ done
+done
+
+cd - > /dev/null
+
+# Run again as root.
+if ! sudo -n true; then
+ exec sudo -- "${0}" "${@}"
+fi
+
+# afl-fuzz says:
+# To avoid having crashes misinterpreted as timeouts, please log in as root
+# and temporarily modify /proc/sys/kernel/core_pattern, like so:
+echo core > /proc/sys/kernel/core_pattern
+
+# afl-fuzz says:
+# Whoops, your system uses on-demand CPU frequency scaling, adjusted
+# between 781 and 4882 MHz. Unfortunately, the scaling algorithm in the
+# kernel is imperfect and can miss the short-lived processes spawned by
+# afl-fuzz. To keep things moving, run these commands as root:
+echo performance | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor > /dev/null
+
+ulimit -Sd 41932800
+
+ulimit -c unlimited
+
+# Create a virtual interface for the server to start listening on.
+if ip link show vethclient > /dev/null 2>&1; then
+ ip link delete vethclient
+fi
+if ip link show vethserver > /dev/null 2>&1; then
+ ip link delete vethserver
+fi
+ip link add vethclient type veth peer name vethserver
+ip -4 addr add 10.1.0.1/24 dev vethclient
+ip -6 addr add 2001:db8:1::1/64 dev vethclient
+ip link set dev vethclient up
+ip link set lo up
+ip -4 addr add 10.1.0.2/24 dev vethserver
+ip -6 addr add 2001:db8:1::2/64 dev vethserver
+ip link set dev vethserver up
+ip link set lo up
+
+# Wait for duplicate address detection to be finished so that the
+# interfaces are ready.
+while true; do
+ interface_status=$(
+ ip a s vethserver | grep -E 'inet6.*tentative'
+ ip a s vethclient | grep -E 'inet6.*tentative'
+ )
+ if test -n "${interface_status}"; then
+ printf 'Waiting for the following addresses to be assigned to their interfaces:\n%s\n' "${interface_status}"
+ sleep 1
+ else
+ break
+ fi
+done
--- /dev/null
+SUBDIRS = .
+
+if FUZZING
+
+check_SCRIPTS =
+check_SCRIPTS += test-fuzz-config-kea-dhcp4.sh
+check_SCRIPTS += test-fuzz-config-kea-dhcp6.sh
+check_SCRIPTS += test-fuzz-http-endpoint.sh
+check_SCRIPTS += test-fuzz-packets-kea-dhcp4.sh
+check_SCRIPTS += test-fuzz-packets-kea-dhcp6.sh
+check_SCRIPTS += test-fuzz-unix-socket-kea-dhcp4.sh
+check_SCRIPTS += test-fuzz-unix-socket-kea-dhcp6.sh
+
+TESTS = $(check_SCRIPTS)
+
+endif # FUZZING
--- /dev/null
+../setup.sh
\ No newline at end of file
--- /dev/null
+#!/bin/sh
+
+if ! sudo -n true; then exec sudo -E -- "${0}" "${@}"; fi
+
+script_path=$(cd "$(dirname "${0}")" && pwd)
+
+script_basename=$(basename "${0}")
+
+tested_binary=$(printf '%s' "${script_basename}" | sed 's/test-//g;s/.sh//g')
+
+"${script_path}/../${tested_binary}"
--- /dev/null
+#!/bin/sh
+
+if ! sudo -n true; then exec sudo -E -- "${0}" "${@}"; fi
+
+script_path=$(cd "$(dirname "${0}")" && pwd)
+
+script_basename=$(basename "${0}")
+
+tested_binary=$(printf '%s' "${script_basename}" | sed 's/test-//g;s/.sh//g')
+
+"${script_path}/../${tested_binary}"
--- /dev/null
+#!/bin/sh
+
+if ! sudo -n true; then exec sudo -E -- "${0}" "${@}"; fi
+
+script_path=$(cd "$(dirname "${0}")" && pwd)
+
+script_basename=$(basename "${0}")
+
+tested_binary=$(printf '%s' "${script_basename}" | sed 's/test-//g;s/.sh//g')
+
+"${script_path}/../${tested_binary}"
--- /dev/null
+#!/bin/sh
+
+if ! sudo -n true; then exec sudo -E -- "${0}" "${@}"; fi
+
+script_path=$(cd "$(dirname "${0}")" && pwd)
+
+script_basename=$(basename "${0}")
+
+tested_binary=$(printf '%s' "${script_basename}" | sed 's/test-//g;s/.sh//g')
+
+"${script_path}/../${tested_binary}"
--- /dev/null
+#!/bin/sh
+
+if ! sudo -n true; then exec sudo -E -- "${0}" "${@}"; fi
+
+script_path=$(cd "$(dirname "${0}")" && pwd)
+
+script_basename=$(basename "${0}")
+
+tested_binary=$(printf '%s' "${script_basename}" | sed 's/test-//g;s/.sh//g')
+
+"${script_path}/../${tested_binary}"
--- /dev/null
+#!/bin/sh
+
+if ! sudo -n true; then exec sudo -E -- "${0}" "${@}"; fi
+
+script_path=$(cd "$(dirname "${0}")" && pwd)
+
+script_basename=$(basename "${0}")
+
+tested_binary=$(printf '%s' "${script_basename}" | sed 's/test-//g;s/.sh//g')
+
+"${script_path}/../${tested_binary}"
--- /dev/null
+#!/bin/sh
+
+if ! sudo -n true; then exec sudo -E -- "${0}" "${@}"; fi
+
+script_path=$(cd "$(dirname "${0}")" && pwd)
+
+script_basename=$(basename "${0}")
+
+tested_binary=$(printf '%s' "${script_basename}" | sed 's/test-//g;s/.sh//g')
+
+"${script_path}/../${tested_binary}"
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/ncr_generator.h>
-#include <dhcpsrv/packet-fuzzer.h>
+#include <dhcpsrv/packet_fuzzer.h>
#include <dhcpsrv/resource_handler.h>
#include <dhcpsrv/shared_network.h>
#include <dhcpsrv/subnet.h>
}
// Set up structures needed for fuzzing.
- PacketFuzzer fuzzer(4, server_port_, interface, address);
+ PacketFuzzer fuzzer(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.
}
uint16_t Dhcpv4Srv::getServerPort() const {
- char const* const randomize(getenv("KEA_DHCP4_FUZZING_RANDOMIZE_PORT"));
+#ifdef FUZZING
+ char const* const randomize(getenv("KEA_DHCP4_FUZZING_ROTATE_PORT"));
if (randomize) {
InterprocessSyncFile file("kea-dhcp4-fuzzing-randomize-port");
InterprocessSyncLocker locker(file);
locker.unlock();
return port;
}
+#endif // FUZZING
return server_port_;
}
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/ncr_generator.h>
-#include <dhcpsrv/packet-fuzzer.h>
+#include <dhcpsrv/packet_fuzzer.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/subnet_selector.h>
#include <dhcpsrv/utils.h>
}
// Set up structures needed for fuzzing.
- PacketFuzzer fuzzer(6, server_port_, interface, address);
+ PacketFuzzer fuzzer(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.
}
uint16_t Dhcpv6Srv::getServerPort() const {
- char const* const randomize(getenv("KEA_DHCP6_FUZZING_RANDOMIZE_PORT"));
+#ifdef FUZZING
+ char const* const randomize(getenv("KEA_DHCP6_FUZZING_ROTATE_PORT"));
if (randomize) {
InterprocessSyncFile file("kea-dhcp6-fuzzing-randomize-port");
InterprocessSyncLocker locker(file);
locker.unlock();
return port;
}
+#endif // FUZZING
return server_port_;
}
libkea_dhcpsrv_la_SOURCES += parsers/simple_parser6.h
if FUZZING
-libkea_dhcpsrv_la_SOURCES += packet-fuzzer.cc packet-fuzzer.h
+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 # FUZZING
if FUZZING
libkea_dhcpsrv_include_HEADERS += \
- packet-fuzzer.h \
+ packet_fuzzer.h \
fuzz_log.h \
fuzz_messages.h
endif
.arg(getLeaseMgrPtr()->getType());
getLeaseMgrPtr().reset();
}
- getLeaseMgrPtr().reset();
}
void
#ifdef FUZZING
+#include <asiolink/io_address.h>
#include <dhcp/dhcp6.h>
-#include <dhcpsrv/packet-fuzzer.h>
+#include <dhcpsrv/packet_fuzzer.h>
#include <dhcpsrv/fuzz_log.h>
#include <boost/lexical_cast.hpp>
#include <ctime>
using namespace isc;
+using namespace isc::asiolink;
using namespace isc::dhcp;
using namespace std;
constexpr long PacketFuzzer::MAX_LOOP_COUNT;
// Constructor
-PacketFuzzer::PacketFuzzer(int const ipversion,
- uint16_t const port,
+PacketFuzzer::PacketFuzzer(uint16_t const port,
string const interface,
string const address)
: loop_max_(MAX_LOOP_COUNT), sockaddr_len_(0), sockaddr_ptr_(nullptr), sockfd_(-1) {
try {
loop_max_ = boost::lexical_cast<long>(loop_max_ptr);
} catch (const boost::bad_lexical_cast&) {
- reason << "cannot convert loop count " << loop_max_ptr
- << " to an integer";
- isc_throw(FuzzInitFail, reason.str());
+ isc_throw(FuzzInitFail,
+ "cannot convert loop count " << loop_max_ptr << " to an integer");
}
if (loop_max_ <= 0) {
- reason << "KEA_AFL_LOOP_MAX is " << loop_max_ << ". "
- << "It must be an integer greater than zero.";
- isc_throw(FuzzInitFail, reason.str());
+ isc_throw(FuzzInitFail, "KEA_AFL_LOOP_MAX is "
+ << loop_max_ << ". "
+ << "It must be an integer greater than zero.");
}
}
+ IOAddress io_address(address);
+
// Set up address structures used to route the packets from AFL to Kea.
- createAddressStructures(ipversion, port, interface, address);
+ createAddressStructures(port, interface, io_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.
- sockfd_ = socket((ipversion == 4) ? AF_INET : AF_INET6, SOCK_DGRAM, 0);
+ sockfd_ = socket(io_address.isV4() ? AF_INET : AF_INET6, SOCK_DGRAM, 0);
if (sockfd_ < 0) {
LOG_FATAL(fuzz_logger, FUZZ_SOCKET_CREATE_FAIL)
.arg(strerror(errno));
// Set up address structures.
void
-PacketFuzzer::createAddressStructures(int const ipversion,
- uint16_t const port,
- string const interface,
- string const address) {
- stringstream reason; // Used in error messages
+PacketFuzzer::createAddressStructures(uint16_t const port,
+ string const& interface,
+ IOAddress const& io_address) {
+ string const address(io_address.toText());
// Set up the appropriate data structure depending on the address given.
- if (ipversion == 6 && address.find(":") != string::npos) {
+ if (io_address.isV6()) {
// 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.c_str(), &servaddr6_.sin6_addr) != 1) {
- reason << "inet_pton() failed: can't convert "
- << address << " to an IPv6 address" << endl;
- isc_throw(FuzzInitFail, reason.str());
+ isc_throw(FuzzInitFail,
+ "inet_pton() failed: can't convert " << address << " to an IPv6 address");
}
servaddr6_.sin6_port = htons(port);
// Interface ID is needed for IPv6 address structures.
servaddr6_.sin6_scope_id = if_nametoindex(interface.c_str());
if (servaddr6_.sin6_scope_id == 0) {
- reason << "error retrieving interface ID for "
- << interface << ": " << strerror(errno);
- isc_throw(FuzzInitFail, reason.str());
+ isc_throw(FuzzInitFail,
+ "error retrieving interface ID for " << interface << ": " << strerror(errno));
}
sockaddr_ptr_ = reinterpret_cast<sockaddr*>(&servaddr6_);
sockaddr_len_ = sizeof(servaddr6_);
- } else if (ipversion == 4 && address.find(".") != string::npos) {
+ } else if (io_address.isV4()) {
// 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.
servaddr4_.sin_family = AF_INET;
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());
+ isc_throw(FuzzInitFail,
+ "inet_pton() failed: can't convert " << address << " to an IPv4 address");
}
servaddr4_.sin_port = htons(port);
sockaddr_len_ = sizeof(servaddr4_);
} 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());
+ // Should never happen.
+ isc_throw(FuzzInitFail, "unknown IOAddress IP version");
}
-
}
void
#ifdef FUZZING
+#include <asiolink/io_address.h>
#include <exceptions/exceptions.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
-#include <condition_variable>
-#include <mutex>
#include <string>
-#include <thread>
namespace isc {
/// Sets up data structures to access the address/port being used to
/// transfer data from AFL to Kea.
///
- /// @param ipversion Either 4 or 6 depending on what IP version the
- /// 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.
- PacketFuzzer(int const ipversion,
- uint16_t const port,
+ PacketFuzzer(uint16_t const port,
std::string const interface,
std::string const address);
/// Create the address structures describing the address/port on whick Kea
/// is listening for packets from AFL.
///
- /// @param ipversion Either 4 or 6 depending on which IP version address
- /// is expected.
- /// @param interface Interface through which the fuzzer is sending packets
- /// to Kea.
- /// @param address Address on the interface that will be used.
- /// @param port Port to be used.
+ /// @param port Port to be used.
+ /// @param interface Interface through which the fuzzer is sending packets to Kea.
+ /// @param io_address Address on the interface that will be used.
///
/// @throws FuzzInitFail Thrown if the address is not in the expected
/// format.
- void createAddressStructures(int const ipversion,
- uint16_t const port,
- std::string const interface,
- std::string const address);
+ void createAddressStructures(uint16_t const port,
+ std::string const& interface,
+ isc::asiolink::IOAddress const& io_address);
// Other member variables.
long loop_max_; //< Maximum number of loop iterations
libdhcpsrv_unittests_SOURCES += network_state_unittest.cc
libdhcpsrv_unittests_SOURCES += network_unittest.cc
+if FUZZING
+libdhcpsrv_unittests_SOURCES += packet_fuzzer_unittest.cc
+endif
+
libdhcpsrv_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
libdhcpsrv_unittests_CXXFLAGS = $(AM_CXXFLAGS)
--- /dev/null
+// Copyright (C) 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/.
+
+#include <config.h>
+
+#include <asiolink/io_error.h>
+#include <dhcpsrv/packet_fuzzer.h>
+#include <testutils/gtest_utils.h>
+
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+TEST(PacketFuzzerTest, constructor) {
+ PacketFuzzer(67, "testeth", "127.0.0.1");
+ // v6 requires valid interface. Skip positive test case.
+
+ // Negative test cases
+ EXPECT_THROW_MSG(PacketFuzzer(547, "invalid_eth%", "fe80::1"), FuzzInitFail,
+ "error retrieving interface ID for invalid_eth%: No such device");
+ EXPECT_THROW_MSG(PacketFuzzer(1234, "testeth", "abcd"), isc::asiolink::IOError,
+ "Failed to convert string to address 'abcd': Invalid argument");
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
class MultiThreadingInvalidOperation : public Exception {
public:
MultiThreadingInvalidOperation(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {};
+ isc::Exception(file, line, what) {}
};
///
TemporaryDirectory::TemporaryDirectory() {
char dir[]("/tmp/kea-tmpdir-XXXXXX");
char const* dir_name = mkdtemp(dir);
- if(!dir_name) {
+ 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()));
+ if (!dir) {
+ return;
+ }
+
struct dirent *i;
string filepath;
-
while ((i = readdir(dir))) {
if (strcmp(i->d_name, ".") == 0 || strcmp(i->d_name, "..") == 0) {
continue;