2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-22 01:49:48 +00:00

[#3605] Integrate a new fuzzing solution in Kea

The solution is based on clusterfuzzlite, libfuzzer, and oss-fuzz
technologies.

- Add the .clusterfuzzlite directory.
- Add the fuzz CI stage and fuzzing CI jobs.
- Add the fuzzing targets in the fuzz directory.
- Document fuzzing in doxygen.
This commit is contained in:
Andrei Pavel 2024-10-15 12:41:41 +03:00
parent 8195f702e7
commit a96168e762
No known key found for this signature in database
GPG Key ID: D4E804481939CB21
83 changed files with 2121 additions and 116 deletions

View File

@ -0,0 +1,6 @@
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"

58
.clusterfuzzlite/build.sh Executable file
View File

@ -0,0 +1,58 @@
#!/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

View File

@ -0,0 +1 @@
language: c++

36
.clusterfuzzlite/run-locally.sh Executable file
View File

@ -0,0 +1,36 @@
#!/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

View File

@ -2,32 +2,52 @@ variables:
# Locale settings do not affect the build, but might affect tests. # Locale settings do not affect the build, but might affect tests.
LC_ALL: C 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
# (SAST, Dependency Scanning, ...)
SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers" SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"
# Leave only bandit, flawfinder, semgrep. # Leave only bandit, flawfinder, semgrep.
SAST_EXCLUDED_ANALYZERS: "eslint, spotbugs" SAST_EXCLUDED_ANALYZERS: "eslint, spotbugs"
image: "${CI_REGISTRY_IMAGE}:latest" image: 'registry.gitlab.isc.org/isc-projects/kea:latest'
stages: stages:
- test - 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: are-database-scripts-in-sync:
stage: test stage: test
<<: *rules_for_test_stage
script: script:
- ./src/share/database/scripts/utils/are-scripts-in-sync.py - ./src/share/database/scripts/utils/are-scripts-in-sync.py
check-for-json-errors-in-doc: check-for-json-errors-in-doc:
stage: test stage: test
<<: *rules_for_test_stage
script: script:
- ./tools/check-for-json-errors-in-doc.sh - ./tools/check-for-json-errors-in-doc.sh
danger: danger:
stage: test stage: test
<<: *rules_for_test_stage
before_script: 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_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 - export CI_PROJECT_PATH=$CI_PROJECT_ID #some version of gitlab has problems with searching by project path
@ -38,26 +58,31 @@ danger:
duplicate-includes: duplicate-includes:
stage: test stage: test
<<: *rules_for_test_stage
script: script:
- ./tools/check-for-duplicate-includes.sh - ./tools/check-for-duplicate-includes.sh
duplicate-log-messages: duplicate-log-messages:
stage: test stage: test
<<: *rules_for_test_stage
script: script:
- ./tools/check-messages.py - ./tools/check-messages.py
uninstalled-headers: uninstalled-headers:
stage: test stage: test
<<: *rules_for_test_stage
script: script:
- ./tools/find-uninstalled-headers.py - ./tools/find-uninstalled-headers.py
missing-api-commands: missing-api-commands:
stage: test stage: test
<<: *rules_for_test_stage
script: script:
- ./tools/check-for-missing-api-commands.sh - ./tools/check-for-missing-api-commands.sh
missing-config-h-include: missing-config-h-include:
stage: test stage: test
<<: *rules_for_test_stage
script: script:
- FILES=$(./tools/add-config-h.sh -n) - FILES=$(./tools/add-config-h.sh -n)
- printf '%s\n' "${FILES}" - printf '%s\n' "${FILES}"
@ -65,6 +90,7 @@ missing-config-h-include:
missing-git-attribute: missing-git-attribute:
stage: test stage: test
<<: *rules_for_test_stage
script: script:
- git_diff=$(git diff) - 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 - 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
@ -74,6 +100,7 @@ missing-git-attribute:
shellcheck: shellcheck:
stage: test stage: test
<<: *rules_for_test_stage
script: script:
- ./tools/shellcheck-all.sh - ./tools/shellcheck-all.sh
@ -87,11 +114,14 @@ shellcheck:
- if test -z "${PYTHON_SCRIPTS}"; then echo "No python scripts to check. Exiting early."; exit 0; fi - if test -z "${PYTHON_SCRIPTS}"; then echo "No python scripts to check. Exiting early."; exit 0; fi
bandit: bandit:
stage: test
<<: *rules_for_test_stage
script: script:
- bandit -r ./src -x ./.git - bandit -r ./src -x ./.git
pycodestyle: pycodestyle:
stage: test stage: test
<<: *rules_for_test_stage
script: script:
# - *get_modified_files # - *get_modified_files
# - INPUT="${MODIFIED_FILES}" # - INPUT="${MODIFIED_FILES}"
@ -100,6 +130,7 @@ pycodestyle:
pylint: pylint:
stage: test stage: test
<<: *rules_for_test_stage
script: script:
# - *get_modified_files # - *get_modified_files
# - INPUT="${MODIFIED_FILES}" # - INPUT="${MODIFIED_FILES}"
@ -108,6 +139,150 @@ pylint:
# 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. # 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 - 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 fuzzers
# 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 ################################ ############################### SAST ################################
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/sast/ # Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/sast/
# #
@ -119,9 +294,16 @@ include:
.sast-analyzer: .sast-analyzer:
extends: sast extends: sast
stage: test
<<: *rules_for_test_stage
allow_failure: true allow_failure: true
script: script:
- /analyzer run - /analyzer run
rules:
- if: $SAST_DISABLED
when: never
- if: $CI_PIPELINE_SOURCE == 'schedule'
when: never
flawfinder-sast: flawfinder-sast:
extends: .sast-analyzer extends: .sast-analyzer
@ -131,11 +313,12 @@ flawfinder-sast:
SAST_ANALYZER_IMAGE_TAG: latest SAST_ANALYZER_IMAGE_TAG: latest
SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/flawfinder:$SAST_ANALYZER_IMAGE_TAG" SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/flawfinder:$SAST_ANALYZER_IMAGE_TAG"
rules: rules:
- if: $SAST_DISABLED
when: never
- if: $SAST_EXCLUDED_ANALYZERS =~ /flawfinder/ - if: $SAST_EXCLUDED_ANALYZERS =~ /flawfinder/
when: never when: never
- if: $CI_COMMIT_BRANCH - if: $CI_COMMIT_BRANCH
exists: exists:
- '**/*.cc' - '**/*.cc'
- '**/*.h' - '**/*.h'
semgrep-sast:
extends: .sast-analyzer

View File

@ -2,7 +2,7 @@ ACLOCAL_AMFLAGS = -I m4macros ${ACLOCAL_FLAGS}
# ^^^^^^^^ This has to be the first line and cannot come later in this # ^^^^^^^^ This has to be the first line and cannot come later in this
# Makefile.am due to some bork in some versions of autotools. # 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@ USE_LCOV=@USE_LCOV@
LCOV=@LCOV@ LCOV=@LCOV@
@ -173,7 +173,6 @@ cppcheck:
docs: docs:
$(MAKE) -C doc/sphinx $(MAKE) -C doc/sphinx
# These steps are necessary during installation # These steps are necessary during installation
install-exec-hook: install-exec-hook:
mkdir -p $(DESTDIR)${localstatedir}/log/ mkdir -p $(DESTDIR)${localstatedir}/log/
@ -188,3 +187,5 @@ CLEANFILES = $(abs_top_builddir)/logger_lockfile
# config.h may be included by headers supplied for building user-written # config.h may be included by headers supplied for building user-written
# hooks libraries, so we need to include it in the distribution. # hooks libraries, so we need to include it in the distribution.
pkginclude_HEADERS = config.h kea_version.h pkginclude_HEADERS = config.h kea_version.h
.PHONY: clean-coverage coverage cppcheck docs report-coverage

View File

@ -1428,6 +1428,10 @@ AC_ARG_ENABLE([fuzzing],
AC_MSG_RESULT("no. Fuzzing requires C++17 support.") AC_MSG_RESULT("no. Fuzzing requires C++17 support.")
AC_MSG_ERROR("Fuzzing requires C++17 support.") AC_MSG_ERROR("Fuzzing requires C++17 support.")
fi 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=${enableval}],
[enable_fuzzing=no] [enable_fuzzing=no]
) )
@ -1503,6 +1507,7 @@ if (echo ${runstatedir} | grep -q localstatedir); then
runstatedir="$(eval echo ${runstatedir})" runstatedir="$(eval echo ${runstatedir})"
fi fi
AC_CONFIG_FILES([kea_version.h])
AC_CONFIG_FILES([Makefile]) AC_CONFIG_FILES([Makefile])
AC_CONFIG_FILES([doc/Makefile]) AC_CONFIG_FILES([doc/Makefile])
AC_CONFIG_FILES([doc/sphinx/Makefile]) AC_CONFIG_FILES([doc/sphinx/Makefile])
@ -1510,7 +1515,9 @@ AC_CONFIG_FILES([doc/devel/Makefile])
AC_CONFIG_FILES([ext/Makefile]) AC_CONFIG_FILES([ext/Makefile])
AC_CONFIG_FILES([ext/gtest/Makefile]) AC_CONFIG_FILES([ext/gtest/Makefile])
AC_CONFIG_FILES([ext/coroutine/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([m4macros/Makefile])
AC_CONFIG_FILES([src/Makefile]) AC_CONFIG_FILES([src/Makefile])
AC_CONFIG_FILES([src/bin/Makefile]) AC_CONFIG_FILES([src/bin/Makefile])

View File

@ -1,10 +1,11 @@
// 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 // 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 // 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/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
/** /**
@page fuzzer Fuzzing Kea @page fuzzer Fuzzing Kea
@section fuzzIntro Introduction @section fuzzIntro Introduction
@ -13,13 +14,125 @@ Fuzzing is a software-testing technique whereby a program is presented with a
variety of generated data as input and is monitored for abnormal conditions variety of generated data as input and is monitored for abnormal conditions
such as crashes or hangs. such as crashes or hangs.
Fuzz testing of Kea uses the AFL (American Fuzzy Lop) program. In this, Kea is There are two ways to fuzz Kea.
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.
@section fuzzTypes Types of Kea Fuzzing 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.
- 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 @subsection fuzzTypeNetwork Fuzzing with Network Packets
@ -38,7 +151,7 @@ dictionary of valid keywords and runs Kea in configuration file check mode on
them. As with network packet fuzzing, the behaviour of Kea is monitored and them. As with network packet fuzzing, the behaviour of Kea is monitored and
the content of subsequent files adjusted accordingly. 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 Whatever tests are done, Kea needs to be built with fuzzing in mind. The steps
for this are: for this are:
@ -53,15 +166,15 @@ for this are:
-# Build Kea. Kea should be compiled and built as usual, although the -# Build Kea. Kea should be compiled and built as usual, although the
following additional steps should be observed: 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. compiler.
- Specify a value of "--prefix" on the command line to set the directory - Specify a value of "--prefix" on the command line to set the directory
into which Kea is installed. 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: For example:
@code @code
CXX=/opt/afl/afl-clang-fast++ ./configure --enable-fuzz --prefix=$HOME/installed CXX=afl-clang-fast ./configure --enable-fuzzing --prefix=$HOME/installed
make make
@endcode @endcode
@ -80,8 +193,6 @@ for this are:
simpler to install the programs in the directories set by "--prefix" and run simpler to install the programs in the directories set by "--prefix" and run
them from there. them from there.
@section fuzzRun Running the Fuzzer
@subsection fuzzRunNetwork Fuzzing with Network Packets @subsection fuzzRunNetwork Fuzzing with Network Packets
-# In this type of fuzzing, Kea is processing packets from the fuzzer over a -# In this type of fuzzing, Kea is processing packets from the fuzzer over a
@ -95,20 +206,20 @@ for this are:
using the loopback interface "lo" and IPv4 address 10.53.0.1, the using the loopback interface "lo" and IPv4 address 10.53.0.1, the
configuration file would contain the following snippet: configuration file would contain the following snippet:
@code @code
{
"Dhcp4": { "Dhcp4": {
:
"interfaces-config": { "interfaces-config": {
"interfaces": ["lo/10.53.0.1"] "interfaces": [
"lo/10.53.0.1"
]
}, },
"subnet4": [ "subnet4": [
{ {
: "interface": "lo"
"interface": "lo",
:
} }
], ]
:
} }
}
@endcode @endcode
-# The specification of the interface and address in the configuration file -# The specification of the interface and address in the configuration file
@ -140,7 +251,7 @@ for this are:
data. Ensure that only the payload of the UDP packet is exported. data. Ensure that only the payload of the UDP packet is exported.
- The "-o" switch specifies a directory (in this example called "fuzz-out") - 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 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. - "--" Separates the AFL command line from that of Kea.
- "./kea-dhcp6" is the program being fuzzed. As mentioned above, this - "./kea-dhcp6" is the program being fuzzed. As mentioned above, this
should be an executable image, and it will be simpler to fuzz one should be an executable image, and it will be simpler to fuzz one
@ -164,7 +275,7 @@ for this are:
@subsection fuzzRunConfig Fuzzing with Configuration Files @subsection fuzzRunConfig Fuzzing with Configuration Files
AFL can be used to check the parsing of the configuration files. In this type 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: Steps for this fuzzing are:
-# Build Kea as described above. -# Build Kea as described above.
@ -201,8 +312,6 @@ Steps for this fuzzing are:
will replace these with the name of a file it has created when starting will replace these with the name of a file it has created when starting
Kea. Kea.
@section Fuzzing Internals
@subsection fuzzInternalNetwork Fuzzing with Network Packets @subsection fuzzInternalNetwork Fuzzing with Network Packets
The AFL fuzzer delivers packets to Kea's stdin. Although the part of Kea The AFL fuzzer delivers packets to Kea's stdin. Although the part of Kea
@ -217,7 +326,7 @@ while (not shutting down) {
Read and process one packet Read and process one packet
} }
@endcode @endcode
When --enable-fuzz is specified, this is conceptually modified to: When --enable-fuzzing is specified, this is conceptually modified to:
@code{.unparsed} @code{.unparsed}
while (not shutting down) { while (not shutting down) {
Read stdin and copy data to address/port on which Kea is listening Read stdin and copy data to address/port on which Kea is listening
@ -225,10 +334,10 @@ while (not shutting down) {
} }
@endcode @endcode
Implementation is via an object of class "Fuzz". When created, it identifies Implementation is via an object of class "PacketFuzzer". When created, it
an interface, address and port on which Kea is listening and creates the identifies an interface, address and port on which Kea is listening and creates
appropriate address structures for these. The port is passed as an argument to the appropriate address structures for these. The port is passed as an argument
the constructor because at the point at which the object is constructed, that 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 information is readily available. The interface and address are picked up from
the environment variables mentioned above. Consideration was given to the environment variables mentioned above. Consideration was given to
extracting the interface and address information from the configuration file, extracting the interface and address information from the configuration file,
@ -264,7 +373,7 @@ leaks).
No changes were required to Kea source code to fuzz configuration files. In No changes were required to Kea source code to fuzz configuration files. In
fact, other than compiling with afl-clang++ and installing the resultant fact, other than compiling with afl-clang++ and installing the resultant
executable, no other steps are required. In particular, there is no need to 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). doing so will not cause any problems).
@subsection fuzzThreads Changes Required for Multi-Threaded Kea @subsection fuzzThreads Changes Required for Multi-Threaded Kea
@ -278,17 +387,15 @@ above was adopted for the single-threaded Kea 1.6. Should Kea be modified to
become multi-threaded, the fuzzing code will need to be changed back to reading become multi-threaded, the fuzzing code will need to be changed back to reading
the AFL input in the background. the AFL input in the background.
@section fuzzNotes Notes
@subsection fuzzNotesUnitTests Unit Test Failures @subsection fuzzNotesUnitTests Unit Test Failures
If unit tests are built when --enable-fuzzing is specified, note that tests If unit tests are built when --enable-fuzzing is specified and with the AFL
which check or use the DHCP servers (i.e. the unit tests in src/bin/dhcp4, compiler, note that tests which check or use the DHCP servers (i.e. the unit
src/bin/dhcp6 and src/bin/kea-admin) will fail. With no AFL-related tests in src/bin/dhcp4, src/bin/dhcp6 and src/bin/kea-admin) will fail.
environment variables defined, a C++ exception will be thrown with the With no AFL-related environment variables defined, a C++ exception will be
description "no fuzzing interface has been set". However, if the thrown with the description "no fuzzing interface has been set".
KEA_AFL_INTERFACE and KEA_AFL_ADDRESS variables are set to valid values, the However, if the `KEA_AFL_INTERFACE` and `KEA_AFL_ADDRESS` variables are set to
tests will hang. valid values, the tests will hang.
Both these results are expected and should cause no concern. The exception is 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 thrown by the fuzzing object constructor when it attempts to create the address
@ -299,5 +406,4 @@ the test. (Should random input be supplied on stdin, e.g. from the keyboard,
the test will most likely fail as the input is unlikely to be that expected by the test will most likely fail as the input is unlikely to be that expected by
the test.) the test.)
*/ */

View File

@ -168,8 +168,6 @@ Messages printed on debuglevel 40
- DHCP4_CLASSES_ASSIGNED_AFTER_SUBNET_SELECTION - DHCP4_CLASSES_ASSIGNED_AFTER_SUBNET_SELECTION
- DHCP4_CLASS_ASSIGNED - DHCP4_CLASS_ASSIGNED
- DHCP4_CLASS_UNCONFIGURED - DHCP4_CLASS_UNCONFIGURED
- DHCP4_CLASS_UNDEFINED
- DHCP4_CLASS_UNTESTABLE
- DHCP4_DHCP4O6_HOOK_SUBNET4_SELECT_DROP - DHCP4_DHCP4O6_HOOK_SUBNET4_SELECT_DROP
- DHCP4_DHCP4O6_HOOK_SUBNET4_SELECT_SKIP - DHCP4_DHCP4O6_HOOK_SUBNET4_SELECT_SKIP
- DHCP4_DHCP4O6_PACKET_RECEIVED - DHCP4_DHCP4O6_PACKET_RECEIVED
@ -186,6 +184,8 @@ Messages printed on debuglevel 40
- DHCP4_LEASE_QUERY_RECEIVED - DHCP4_LEASE_QUERY_RECEIVED
- DHCP4_LEASE_QUERY_RESPONSE_SENT - DHCP4_LEASE_QUERY_RESPONSE_SENT
- DHCP4_PACKET_QUEUE_FULL - DHCP4_PACKET_QUEUE_FULL
- DHCP4_REQUIRED_CLASS_NO_TEST
- DHCP4_REQUIRED_CLASS_UNDEFINED
- DHCP4_SHUTDOWN - DHCP4_SHUTDOWN
- DHCP4_SHUTDOWN_REQUEST - DHCP4_SHUTDOWN_REQUEST
- DHCP6_BUFFER_RECEIVED - DHCP6_BUFFER_RECEIVED
@ -193,8 +193,6 @@ Messages printed on debuglevel 40
- DHCP6_CLASSES_ASSIGNED_AFTER_SUBNET_SELECTION - DHCP6_CLASSES_ASSIGNED_AFTER_SUBNET_SELECTION
- DHCP6_CLASS_ASSIGNED - DHCP6_CLASS_ASSIGNED
- DHCP6_CLASS_UNCONFIGURED - DHCP6_CLASS_UNCONFIGURED
- DHCP6_CLASS_UNDEFINED
- DHCP6_CLASS_UNTESTABLE
- DHCP6_DHCP4O6_PACKET_RECEIVED - DHCP6_DHCP4O6_PACKET_RECEIVED
- DHCP6_FLEX_ID - DHCP6_FLEX_ID
- DHCP6_HOOK_BUFFER_SEND_SKIP - DHCP6_HOOK_BUFFER_SEND_SKIP
@ -213,6 +211,8 @@ Messages printed on debuglevel 40
- DHCP6_LEASE_QUERY_REPLY_SENT - DHCP6_LEASE_QUERY_REPLY_SENT
- DHCP6_PACKET_PROCESS_FAIL - DHCP6_PACKET_PROCESS_FAIL
- DHCP6_PACKET_QUEUE_FULL - DHCP6_PACKET_QUEUE_FULL
- DHCP6_REQUIRED_CLASS_NO_TEST
- DHCP6_REQUIRED_CLASS_UNDEFINED
- DHCP6_REQUIRED_OPTIONS_CHECK_FAIL - DHCP6_REQUIRED_OPTIONS_CHECK_FAIL
- DHCP6_SHUTDOWN - DHCP6_SHUTDOWN
- DHCP6_SHUTDOWN_REQUEST - DHCP6_SHUTDOWN_REQUEST

7
fuzz/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
/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

88
fuzz/Makefile.am Normal file
View File

@ -0,0 +1,88 @@
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

1
fuzz/dict.dat Normal file
View File

@ -0,0 +1 @@
PD_POOLS="pd-pools"

76
fuzz/fuzz.cc Normal file
View File

@ -0,0 +1,76 @@
// 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"

35
fuzz/fuzz.h Normal file
View File

@ -0,0 +1,35 @@
// 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"

View File

@ -0,0 +1,72 @@
// 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"

View File

@ -0,0 +1,72 @@
// 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"

174
fuzz/fuzz_http_endpoint.cc Normal file
View File

@ -0,0 +1,174 @@
// 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"

View File

@ -0,0 +1,121 @@
// 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"

View File

@ -0,0 +1,124 @@
// 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"

View File

@ -0,0 +1,122 @@
// 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"

View File

@ -0,0 +1,128 @@
// 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"

43
fuzz/input/Makefile.am Normal file
View File

@ -0,0 +1,43 @@
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

View File

@ -0,0 +1 @@
../../../doc/examples/kea4

View File

View File

@ -0,0 +1 @@
0a

View File

@ -0,0 +1 @@
../../../doc/examples/kea6

View File

View File

@ -0,0 +1 @@
0a

View File

@ -0,0 +1,3 @@
{
"command": "config-get"
}

View File

@ -0,0 +1,4 @@
{
"command": "config-get",
"service": [ "dhcp6" ]
}

View File

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1 @@
0a

View File

@ -0,0 +1,3 @@
{
"a": 1
}

View File

@ -0,0 +1 @@
210101060100000000000000000000000000000000000000000a010001000c0102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501013707011c02030f060c3d0701000c01020304ff

View File

View File

@ -0,0 +1 @@
000400010006d6b6574a0cce713b0800450001229b384000401194910a010001ffffffff00430043010e0b210101060100000000000000000000000000000000000000000a010001000c0102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501013707011c02030f060c3d0701000c01020304ff

View File

@ -0,0 +1 @@
0a

View File

@ -0,0 +1 @@
000400010006d6b6574a0cce713b0800450001229b384000401194910a010001ffffffff00430043010e0b

View File

@ -0,0 +1 @@
010000000001000e000100012b8b4659000c010203040003000c0000000100000e10000015180006000400170018000800020000

View File

View File

@ -0,0 +1 @@
000400010006d6b6574a0cce000086dd6001d3f9003c110120010db8000100000000000000000001ff02000000000000000000000001000202220223003c2d0e010000000001000e000100012b8b4659000c010203040003000c0000000100000e10000015180006000400170018000800020000

View File

@ -0,0 +1 @@
0a

View File

@ -0,0 +1 @@
000400010006d6b6574a0cce000086dd6001d3f9003c110120010db8000100000000000000000001ff02000000000000000000000001000202220223003c2d0e

View File

@ -0,0 +1,3 @@
{
"command": "config-get"
}

View File

@ -0,0 +1,4 @@
{
"command": "config-get",
"service": [ "dhcp4" ]
}

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1 @@
0a

View File

@ -0,0 +1,3 @@
{
"a": 1
}

View File

@ -0,0 +1,3 @@
{
"command": "config-get"
}

View File

@ -0,0 +1,4 @@
{
"command": "config-get",
"service": [ "dhcp6" ]
}

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1 @@
0a

View File

@ -0,0 +1,3 @@
{
"a": 1
}

20
fuzz/input/kea-dhcp4.conf Normal file
View File

@ -0,0 +1,20 @@
{
"Dhcp4": {
"interfaces-config": {
"interfaces": [
"*"
]
},
"subnet4": [
{
"id": 1,
"pools": [
{
"pool": "127.0.0.0/8"
}
],
"subnet": "127.0.0.0/8"
}
]
}
}

27
fuzz/input/kea-dhcp6.conf Normal file
View File

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

View File

@ -0,0 +1,26 @@
#!/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

24
fuzz/kea-dhcp4.h Normal file
View File

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

31
fuzz/kea-dhcp6.h Normal file
View File

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

11
fuzz/libfuzzer.sh Executable file
View File

@ -0,0 +1,11 @@
#!/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

124
fuzz/main.cc Normal file
View File

@ -0,0 +1,124 @@
// 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;
}

112
fuzz/setup.sh Executable file
View File

@ -0,0 +1,112 @@
#!/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

16
fuzz/tests/Makefile.am Normal file
View File

@ -0,0 +1,16 @@
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

1
fuzz/tests/setup.sh Symbolic link
View File

@ -0,0 +1 @@
../setup.sh

View File

@ -0,0 +1,11 @@
#!/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}"

View File

@ -0,0 +1,11 @@
#!/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}"

View File

@ -0,0 +1,11 @@
#!/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}"

View File

@ -0,0 +1,11 @@
#!/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}"

View File

@ -0,0 +1,11 @@
#!/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}"

View File

@ -0,0 +1,11 @@
#!/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}"

View File

@ -0,0 +1,11 @@
#!/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}"

View File

@ -41,7 +41,7 @@
#include <dhcpsrv/lease_mgr.h> #include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h> #include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/ncr_generator.h> #include <dhcpsrv/ncr_generator.h>
#include <dhcpsrv/packet-fuzzer.h> #include <dhcpsrv/packet_fuzzer.h>
#include <dhcpsrv/resource_handler.h> #include <dhcpsrv/resource_handler.h>
#include <dhcpsrv/shared_network.h> #include <dhcpsrv/shared_network.h>
#include <dhcpsrv/subnet.h> #include <dhcpsrv/subnet.h>
@ -1144,7 +1144,7 @@ Dhcpv4Srv::run() {
} }
// Set up structures needed for fuzzing. // 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 // 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. // running persistent fuzzing. This has to be in the main image file.
@ -5180,7 +5180,8 @@ void Dhcpv4Srv::discardPackets() {
} }
uint16_t Dhcpv4Srv::getServerPort() const { 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) { if (randomize) {
InterprocessSyncFile file("kea-dhcp4-fuzzing-randomize-port"); InterprocessSyncFile file("kea-dhcp4-fuzzing-randomize-port");
InterprocessSyncLocker locker(file); InterprocessSyncLocker locker(file);
@ -5209,6 +5210,7 @@ uint16_t Dhcpv4Srv::getServerPort() const {
locker.unlock(); locker.unlock();
return port; return port;
} }
#endif // FUZZING
return server_port_; return server_port_;
} }

View File

@ -40,7 +40,7 @@
#include <dhcpsrv/lease_mgr.h> #include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h> #include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/ncr_generator.h> #include <dhcpsrv/ncr_generator.h>
#include <dhcpsrv/packet-fuzzer.h> #include <dhcpsrv/packet_fuzzer.h>
#include <dhcpsrv/subnet.h> #include <dhcpsrv/subnet.h>
#include <dhcpsrv/subnet_selector.h> #include <dhcpsrv/subnet_selector.h>
#include <dhcpsrv/utils.h> #include <dhcpsrv/utils.h>
@ -616,7 +616,7 @@ Dhcpv6Srv::run() {
} }
// Set up structures needed for fuzzing. // 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 // 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. // running persistent fuzzing. This has to be in the main image file.
@ -4920,7 +4920,8 @@ void Dhcpv6Srv::discardPackets() {
} }
uint16_t Dhcpv6Srv::getServerPort() const { 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) { if (randomize) {
InterprocessSyncFile file("kea-dhcp6-fuzzing-randomize-port"); InterprocessSyncFile file("kea-dhcp6-fuzzing-randomize-port");
InterprocessSyncLocker locker(file); InterprocessSyncLocker locker(file);
@ -4949,6 +4950,7 @@ uint16_t Dhcpv6Srv::getServerPort() const {
locker.unlock(); locker.unlock();
return port; return port;
} }
#endif // FUZZING
return server_port_; return server_port_;
} }

View File

@ -173,7 +173,7 @@ libkea_dhcpsrv_la_SOURCES += parsers/simple_parser6.cc
libkea_dhcpsrv_la_SOURCES += parsers/simple_parser6.h libkea_dhcpsrv_la_SOURCES += parsers/simple_parser6.h
if FUZZING 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_log.cc fuzz_log.h
libkea_dhcpsrv_la_SOURCES += fuzz_messages.cc fuzz_messages.h libkea_dhcpsrv_la_SOURCES += fuzz_messages.cc fuzz_messages.h
endif # FUZZING endif # FUZZING
@ -352,7 +352,7 @@ libkea_dhcpsrv_include_HEADERS = \
if FUZZING if FUZZING
libkea_dhcpsrv_include_HEADERS += \ libkea_dhcpsrv_include_HEADERS += \
packet-fuzzer.h \ packet_fuzzer.h \
fuzz_log.h \ fuzz_log.h \
fuzz_messages.h fuzz_messages.h
endif endif

View File

@ -94,7 +94,6 @@ LeaseMgrFactory::destroy() {
.arg(getLeaseMgrPtr()->getType()); .arg(getLeaseMgrPtr()->getType());
getLeaseMgrPtr().reset(); getLeaseMgrPtr().reset();
} }
getLeaseMgrPtr().reset();
} }
void void

View File

@ -8,8 +8,9 @@
#ifdef FUZZING #ifdef FUZZING
#include <asiolink/io_address.h>
#include <dhcp/dhcp6.h> #include <dhcp/dhcp6.h>
#include <dhcpsrv/packet-fuzzer.h> #include <dhcpsrv/packet_fuzzer.h>
#include <dhcpsrv/fuzz_log.h> #include <dhcpsrv/fuzz_log.h>
#include <boost/lexical_cast.hpp> #include <boost/lexical_cast.hpp>
@ -26,6 +27,7 @@
#include <ctime> #include <ctime>
using namespace isc; using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp; using namespace isc::dhcp;
using namespace std; using namespace std;
@ -35,8 +37,7 @@ constexpr size_t PacketFuzzer::MAX_SEND_SIZE;
constexpr long PacketFuzzer::MAX_LOOP_COUNT; constexpr long PacketFuzzer::MAX_LOOP_COUNT;
// Constructor // Constructor
PacketFuzzer::PacketFuzzer(int const ipversion, PacketFuzzer::PacketFuzzer(uint16_t const port,
uint16_t const port,
string const interface, string const interface,
string const address) string const address)
: loop_max_(MAX_LOOP_COUNT), sockaddr_len_(0), sockaddr_ptr_(nullptr), sockfd_(-1) { : loop_max_(MAX_LOOP_COUNT), sockaddr_len_(0), sockaddr_ptr_(nullptr), sockfd_(-1) {
@ -55,25 +56,26 @@ PacketFuzzer::PacketFuzzer(int const ipversion,
try { try {
loop_max_ = boost::lexical_cast<long>(loop_max_ptr); loop_max_ = boost::lexical_cast<long>(loop_max_ptr);
} catch (const boost::bad_lexical_cast&) { } catch (const boost::bad_lexical_cast&) {
reason << "cannot convert loop count " << loop_max_ptr isc_throw(FuzzInitFail,
<< " to an integer"; "cannot convert loop count " << loop_max_ptr << " to an integer");
isc_throw(FuzzInitFail, reason.str());
} }
if (loop_max_ <= 0) { if (loop_max_ <= 0) {
reason << "KEA_AFL_LOOP_MAX is " << loop_max_ << ". " isc_throw(FuzzInitFail, "KEA_AFL_LOOP_MAX is "
<< "It must be an integer greater than zero."; << loop_max_ << ". "
isc_throw(FuzzInitFail, reason.str()); << "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. // 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 // 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 // to the port on which Kea is listening. This is closed in the
// destructor. // 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) { if (sockfd_ < 0) {
LOG_FATAL(fuzz_logger, FUZZ_SOCKET_CREATE_FAIL) LOG_FATAL(fuzz_logger, FUZZ_SOCKET_CREATE_FAIL)
.arg(strerror(errno)); .arg(strerror(errno));
@ -98,38 +100,35 @@ PacketFuzzer::~PacketFuzzer() {
// Set up address structures. // Set up address structures.
void void
PacketFuzzer::createAddressStructures(int const ipversion, PacketFuzzer::createAddressStructures(uint16_t const port,
uint16_t const port, string const& interface,
string const interface, IOAddress const& io_address) {
string const address) { string const address(io_address.toText());
stringstream reason; // Used in error messages
// Set up the appropriate data structure depending on the address given. // 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 // Expecting IPv6 and the address contains a colon, so assume it is an
// an IPv6 address. // an IPv6 address.
memset(&servaddr6_, 0, sizeof (servaddr6_)); memset(&servaddr6_, 0, sizeof (servaddr6_));
servaddr6_.sin6_family = AF_INET6; servaddr6_.sin6_family = AF_INET6;
if (inet_pton(AF_INET6, address.c_str(), &servaddr6_.sin6_addr) != 1) { if (inet_pton(AF_INET6, address.c_str(), &servaddr6_.sin6_addr) != 1) {
reason << "inet_pton() failed: can't convert " isc_throw(FuzzInitFail,
<< address << " to an IPv6 address" << endl; "inet_pton() failed: can't convert " << address << " to an IPv6 address");
isc_throw(FuzzInitFail, reason.str());
} }
servaddr6_.sin6_port = htons(port); servaddr6_.sin6_port = htons(port);
// Interface ID is needed for IPv6 address structures. // Interface ID is needed for IPv6 address structures.
servaddr6_.sin6_scope_id = if_nametoindex(interface.c_str()); servaddr6_.sin6_scope_id = if_nametoindex(interface.c_str());
if (servaddr6_.sin6_scope_id == 0) { if (servaddr6_.sin6_scope_id == 0) {
reason << "error retrieving interface ID for " isc_throw(FuzzInitFail,
<< interface << ": " << strerror(errno); "error retrieving interface ID for " << interface << ": " << strerror(errno));
isc_throw(FuzzInitFail, reason.str());
} }
sockaddr_ptr_ = reinterpret_cast<sockaddr*>(&servaddr6_); sockaddr_ptr_ = reinterpret_cast<sockaddr*>(&servaddr6_);
sockaddr_len_ = sizeof(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. // 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 // This check is done after the IPv6 check, as it is possible for an
// IPv4 address to be embedded in an IPv6 one. // IPv4 address to be embedded in an IPv6 one.
@ -137,9 +136,8 @@ PacketFuzzer::createAddressStructures(int const ipversion,
servaddr4_.sin_family = AF_INET; servaddr4_.sin_family = AF_INET;
if (inet_pton(AF_INET, address.c_str(), &servaddr4_.sin_addr) != 1) { if (inet_pton(AF_INET, address.c_str(), &servaddr4_.sin_addr) != 1) {
reason << "inet_pton() failed: can't convert " isc_throw(FuzzInitFail,
<< address << " to an IPv6 address" << endl; "inet_pton() failed: can't convert " << address << " to an IPv4 address");
isc_throw(FuzzInitFail, reason.str());
} }
servaddr4_.sin_port = htons(port); servaddr4_.sin_port = htons(port);
@ -147,12 +145,9 @@ PacketFuzzer::createAddressStructures(int const ipversion,
sockaddr_len_ = sizeof(servaddr4_); sockaddr_len_ = sizeof(servaddr4_);
} else { } else {
reason << "Expected IP version (" << ipversion << ") is not " // Should never happen.
<< "4 or 6, or the given address " << address << " does not " isc_throw(FuzzInitFail, "unknown IOAddress IP version");
<< "match the IP version expected";
isc_throw(FuzzInitFail, reason.str());
} }
} }
void void

View File

@ -9,6 +9,7 @@
#ifdef FUZZING #ifdef FUZZING
#include <asiolink/io_address.h>
#include <exceptions/exceptions.h> #include <exceptions/exceptions.h>
#include <arpa/inet.h> #include <arpa/inet.h>
@ -16,10 +17,7 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <unistd.h> #include <unistd.h>
#include <condition_variable>
#include <mutex>
#include <string> #include <string>
#include <thread>
namespace isc { namespace isc {
@ -69,12 +67,9 @@ public:
/// Sets up data structures to access the address/port being used to /// Sets up data structures to access the address/port being used to
/// transfer data from AFL to Kea. /// 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 /// @param port Port on which the server is listening, and hence the
/// port to which the fuzzer will send input from AFL. /// port to which the fuzzer will send input from AFL.
PacketFuzzer(int const ipversion, PacketFuzzer(uint16_t const port,
uint16_t const port,
std::string const interface, std::string const interface,
std::string const address); std::string const address);
@ -110,19 +105,15 @@ private:
/// Create the address structures describing the address/port on whick Kea /// Create the address structures describing the address/port on whick Kea
/// is listening for packets from AFL. /// is listening for packets from AFL.
/// ///
/// @param ipversion Either 4 or 6 depending on which IP version address /// @param port Port to be used.
/// is expected. /// @param interface Interface through which the fuzzer is sending packets to Kea.
/// @param interface Interface through which the fuzzer is sending packets /// @param io_address Address on the interface that will be used.
/// to Kea.
/// @param address Address on the interface that will be used.
/// @param port Port to be used.
/// ///
/// @throws FuzzInitFail Thrown if the address is not in the expected /// @throws FuzzInitFail Thrown if the address is not in the expected
/// format. /// format.
void createAddressStructures(int const ipversion, void createAddressStructures(uint16_t const port,
uint16_t const port, std::string const& interface,
std::string const interface, isc::asiolink::IOAddress const& io_address);
std::string const address);
// Other member variables. // Other member variables.
long loop_max_; //< Maximum number of loop iterations long loop_max_; //< Maximum number of loop iterations

View File

@ -131,6 +131,10 @@ libdhcpsrv_unittests_SOURCES += tracking_lease_mgr_unittest.cc
libdhcpsrv_unittests_SOURCES += network_state_unittest.cc libdhcpsrv_unittests_SOURCES += network_state_unittest.cc
libdhcpsrv_unittests_SOURCES += network_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_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
libdhcpsrv_unittests_CXXFLAGS = $(AM_CXXFLAGS) libdhcpsrv_unittests_CXXFLAGS = $(AM_CXXFLAGS)

View File

@ -0,0 +1,32 @@
// 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

View File

@ -183,7 +183,7 @@ public:
class MultiThreadingInvalidOperation : public Exception { class MultiThreadingInvalidOperation : public Exception {
public: public:
MultiThreadingInvalidOperation(const char* file, size_t line, const char* what) : MultiThreadingInvalidOperation(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) {}; isc::Exception(file, line, what) {}
}; };
/// ///

View File

@ -183,18 +183,20 @@ Path::replaceParentPath(string const& replacement) {
TemporaryDirectory::TemporaryDirectory() { TemporaryDirectory::TemporaryDirectory() {
char dir[]("/tmp/kea-tmpdir-XXXXXX"); char dir[]("/tmp/kea-tmpdir-XXXXXX");
char const* dir_name = mkdtemp(dir); char const* dir_name = mkdtemp(dir);
if(!dir_name) { if (!dir_name) {
isc_throw(Unexpected, "mkdtemp failed " << dir << ": " << strerror(errno)); isc_throw(Unexpected, "mkdtemp failed " << dir << ": " << strerror(errno));
} }
dir_name_ = string(dir_name); dir_name_ = string(dir_name);
} }
TemporaryDirectory::~TemporaryDirectory() { TemporaryDirectory::~TemporaryDirectory() {
rmdir(dir_name_.c_str());
DIR *dir(opendir(dir_name_.c_str())); DIR *dir(opendir(dir_name_.c_str()));
if (!dir) {
return;
}
struct dirent *i; struct dirent *i;
string filepath; string filepath;
while ((i = readdir(dir))) { while ((i = readdir(dir))) {
if (strcmp(i->d_name, ".") == 0 || strcmp(i->d_name, "..") == 0) { if (strcmp(i->d_name, ".") == 0 || strcmp(i->d_name, "..") == 0) {
continue; continue;