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:
parent
8195f702e7
commit
a96168e762
6
.clusterfuzzlite/Dockerfile
Normal file
6
.clusterfuzzlite/Dockerfile
Normal 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
58
.clusterfuzzlite/build.sh
Executable 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
|
1
.clusterfuzzlite/project.yaml
Normal file
1
.clusterfuzzlite/project.yaml
Normal file
@ -0,0 +1 @@
|
||||
language: c++
|
36
.clusterfuzzlite/run-locally.sh
Executable file
36
.clusterfuzzlite/run-locally.sh
Executable 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
|
197
.gitlab-ci.yml
197
.gitlab-ci.yml
@ -2,32 +2,52 @@ variables:
|
||||
# Locale settings do not affect the build, but might affect tests.
|
||||
LC_ALL: C
|
||||
|
||||
CI_REGISTRY_IMAGE: registry.gitlab.isc.org/isc-projects/kea
|
||||
# Fuzzing
|
||||
CFL_ARTIFACTS_DIR: '/tmp/cfl-artifacts'
|
||||
CFL_CACHE_DIR: '/ccache/cfl-cache'
|
||||
CFL_IMAGE: 'gcr.io/oss-fuzz-base/clusterfuzzlite-run-fuzzers'
|
||||
CFL_PLATFORM: gitlab
|
||||
FUZZ_SECONDS: 600 # 10 min (ClusterFuzzLite defaults)
|
||||
FUZZING_ARGS: '-rss_limit_mb=8192'
|
||||
LD_LIBRARY_PATH: "/opt/kea/lib:/usr/lib/x86_64-linux-gnu:/lib/x86_64-linux-gnu:/builds/isc-projects/kea"
|
||||
PARALLEL_FUZZING: true
|
||||
CCACHE_BASEDIR: "${CI_PROJECT_DIR}"
|
||||
CCACHE_DIR: "${CI_PROJECT_DIR}/ccache"
|
||||
|
||||
# Setting this variable will affect all Security templates
|
||||
# (SAST, Dependency Scanning, ...)
|
||||
# SAST
|
||||
SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"
|
||||
|
||||
# Leave only bandit, flawfinder, semgrep.
|
||||
SAST_EXCLUDED_ANALYZERS: "eslint, spotbugs"
|
||||
|
||||
image: "${CI_REGISTRY_IMAGE}:latest"
|
||||
image: 'registry.gitlab.isc.org/isc-projects/kea:latest'
|
||||
|
||||
stages:
|
||||
- test
|
||||
- fuzz
|
||||
|
||||
# Do not run the test stage on pipeline schedule trigger.
|
||||
.base_rules_for_test_jobs: &rules_for_test_stage
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE != 'schedule'
|
||||
when: always
|
||||
- if: $CI_PIPELINE_SOURCE == 'schedule'
|
||||
when: never
|
||||
|
||||
are-database-scripts-in-sync:
|
||||
stage: test
|
||||
<<: *rules_for_test_stage
|
||||
script:
|
||||
- ./src/share/database/scripts/utils/are-scripts-in-sync.py
|
||||
|
||||
check-for-json-errors-in-doc:
|
||||
stage: test
|
||||
<<: *rules_for_test_stage
|
||||
script:
|
||||
- ./tools/check-for-json-errors-in-doc.sh
|
||||
|
||||
danger:
|
||||
stage: test
|
||||
<<: *rules_for_test_stage
|
||||
before_script:
|
||||
- export CI_MERGE_REQUEST_ID=$(git ls-remote -q origin merge-requests\*\head | grep $CI_COMMIT_SHA | sed 's/.*refs\/merge-requests\/\([0-9]*\)\/head/\1/g')
|
||||
- export CI_PROJECT_PATH=$CI_PROJECT_ID #some version of gitlab has problems with searching by project path
|
||||
@ -38,26 +58,31 @@ danger:
|
||||
|
||||
duplicate-includes:
|
||||
stage: test
|
||||
<<: *rules_for_test_stage
|
||||
script:
|
||||
- ./tools/check-for-duplicate-includes.sh
|
||||
|
||||
duplicate-log-messages:
|
||||
stage: test
|
||||
<<: *rules_for_test_stage
|
||||
script:
|
||||
- ./tools/check-messages.py
|
||||
|
||||
uninstalled-headers:
|
||||
stage: test
|
||||
<<: *rules_for_test_stage
|
||||
script:
|
||||
- ./tools/find-uninstalled-headers.py
|
||||
|
||||
missing-api-commands:
|
||||
stage: test
|
||||
<<: *rules_for_test_stage
|
||||
script:
|
||||
- ./tools/check-for-missing-api-commands.sh
|
||||
|
||||
missing-config-h-include:
|
||||
stage: test
|
||||
<<: *rules_for_test_stage
|
||||
script:
|
||||
- FILES=$(./tools/add-config-h.sh -n)
|
||||
- printf '%s\n' "${FILES}"
|
||||
@ -65,6 +90,7 @@ missing-config-h-include:
|
||||
|
||||
missing-git-attribute:
|
||||
stage: test
|
||||
<<: *rules_for_test_stage
|
||||
script:
|
||||
- git_diff=$(git diff)
|
||||
- if test -n "${git_diff}"; then printf '%s\n\ngit diff should be empty here under all circumstances. CI broken?\n' "${git_diff}"; exit 1; fi
|
||||
@ -74,6 +100,7 @@ missing-git-attribute:
|
||||
|
||||
shellcheck:
|
||||
stage: test
|
||||
<<: *rules_for_test_stage
|
||||
script:
|
||||
- ./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
|
||||
|
||||
bandit:
|
||||
stage: test
|
||||
<<: *rules_for_test_stage
|
||||
script:
|
||||
- bandit -r ./src -x ./.git
|
||||
|
||||
pycodestyle:
|
||||
stage: test
|
||||
<<: *rules_for_test_stage
|
||||
script:
|
||||
# - *get_modified_files
|
||||
# - INPUT="${MODIFIED_FILES}"
|
||||
@ -100,6 +130,7 @@ pycodestyle:
|
||||
|
||||
pylint:
|
||||
stage: test
|
||||
<<: *rules_for_test_stage
|
||||
script:
|
||||
# - *get_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.
|
||||
- pylint --jobs "$(nproc || gnproc || echo 1)" --rcfile ./.gitlab/ci/pylint.rc --enable all ${PYTHON_SCRIPTS} || true
|
||||
|
||||
|
||||
############################## Fuzzing ##############################
|
||||
|
||||
# Fuzz code changes. Fuzzes all merge requests.
|
||||
fuzz:
|
||||
image:
|
||||
name: "${CFL_IMAGE}"
|
||||
entrypoint: ['']
|
||||
stage: fuzz
|
||||
tags:
|
||||
- docker-fuzz
|
||||
needs: []
|
||||
parallel:
|
||||
matrix:
|
||||
- SANITIZER: [address, undefined]
|
||||
rules:
|
||||
# On merge request.
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
variables:
|
||||
MODE: "code-change"
|
||||
when: manual
|
||||
allow_failure: true
|
||||
# And on push to master.
|
||||
- if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH
|
||||
when: always
|
||||
before_script:
|
||||
# Get GitLab's container id.
|
||||
- export CFL_CONTAINER_ID=`docker ps -q -f "label=com.gitlab.gitlab-runner.job.id=$CI_JOB_ID" -f "label=com.gitlab.gitlab-runner.type=build"`
|
||||
script:
|
||||
# local cfl-cache to mounted volume
|
||||
- if ! test -L cfl-cache; then ln -s /cfl-cache cfl-cache; fi
|
||||
# Will build and run the fuzzers.
|
||||
- python3 "/opt/oss-fuzz/infra/cifuzz/cifuzz_combined_entrypoint.py"
|
||||
artifacts:
|
||||
# Upload artifacts when a crash makes the job fail.
|
||||
when: always
|
||||
expire_in: 30 days
|
||||
paths:
|
||||
- "${CFL_ARTIFACTS_DIR}"
|
||||
|
||||
# Batch fuzzing enables continuous, regular fuzzing on your latest HEAD
|
||||
# and allows a corpus of inputs to build up over time, which greatly improves
|
||||
# the effectiveness of fuzzing. Batch fuzzing should be run on a schedule.
|
||||
fuzz-batch:
|
||||
image:
|
||||
name: "${CFL_IMAGE}"
|
||||
entrypoint: ['']
|
||||
stage: fuzz
|
||||
needs: []
|
||||
tags:
|
||||
- docker-fuzz
|
||||
variables:
|
||||
FUZZ_SECONDS: 86400 # 24 hours
|
||||
rules:
|
||||
- if: $MODE == "batch"
|
||||
before_script:
|
||||
- export CFL_CONTAINER_ID=`docker ps -q -f "label=com.gitlab.gitlab-runner.job.id=$CI_JOB_ID" -f "label=com.gitlab.gitlab-runner.type=build"`
|
||||
script:
|
||||
- python3 "/opt/oss-fuzz/infra/cifuzz/cifuzz_combined_entrypoint.py"
|
||||
artifacts:
|
||||
when: always
|
||||
expire_in: 30 days
|
||||
paths:
|
||||
- "${CFL_ARTIFACTS_DIR}"
|
||||
|
||||
# Corpus pruning is a helper function that minimizes the corpuses by
|
||||
# removing corpus files (testcases) that do not increase the fuzzer’s
|
||||
# code coverage.
|
||||
fuzz-prune:
|
||||
image:
|
||||
name: "${CFL_IMAGE}"
|
||||
entrypoint: ['']
|
||||
stage: fuzz
|
||||
needs: []
|
||||
tags:
|
||||
- docker-fuzz
|
||||
rules:
|
||||
- if: $MODE == "prune"
|
||||
before_script:
|
||||
- export CFL_CONTAINER_ID=`docker ps -q -f "label=com.gitlab.gitlab-runner.job.id=$CI_JOB_ID" -f "label=com.gitlab.gitlab-runner.type=build"`
|
||||
script:
|
||||
- python3 "/opt/oss-fuzz/infra/cifuzz/cifuzz_combined_entrypoint.py"
|
||||
artifacts:
|
||||
when: always
|
||||
expire_in: 30 days
|
||||
paths:
|
||||
- "${CFL_ARTIFACTS_DIR}"
|
||||
|
||||
# Continuous builds are used when a crash is found during MR fuzzing to determine
|
||||
# whether the crash was newly introduced. If the crash was not newly introduced,
|
||||
# MR fuzzing will not report it. This means that there will be fewer unrelated
|
||||
# failures when running code change fuzzing.
|
||||
fuzz-build:
|
||||
image:
|
||||
name: "${CFL_IMAGE}"
|
||||
entrypoint: ['']
|
||||
stage: fuzz
|
||||
needs: []
|
||||
tags:
|
||||
- docker-fuzz
|
||||
rules:
|
||||
# Use $CI_DEFAULT_BRANCH or $CFL_BRANCH.
|
||||
- if: $CI_COMMIT_BRANCH == $CFL_BRANCH && $CI_PIPELINE_SOURCE == "push"
|
||||
variables:
|
||||
MODE: "code-change"
|
||||
UPLOAD_BUILD: "true"
|
||||
before_script:
|
||||
- export CFL_CONTAINER_ID=`docker ps -q -f "label=com.gitlab.gitlab-runner.job.id=$CI_JOB_ID" -f "label=com.gitlab.gitlab-runner.type=build"`
|
||||
script:
|
||||
- python3 "/opt/oss-fuzz/infra/cifuzz/cifuzz_combined_entrypoint.py"
|
||||
artifacts:
|
||||
when: always
|
||||
expire_in: 30 days
|
||||
paths:
|
||||
- "${CFL_ARTIFACTS_DIR}"
|
||||
|
||||
# scheduled job - generates periodic coverage reports
|
||||
fuzz-coverage:
|
||||
image:
|
||||
name: "${CFL_IMAGE}"
|
||||
entrypoint: ['']
|
||||
stage: fuzz
|
||||
needs: []
|
||||
tags:
|
||||
- docker-fuzz
|
||||
variables:
|
||||
SANITIZER: "coverage"
|
||||
rules:
|
||||
- if: $MODE == "coverage"
|
||||
before_script:
|
||||
- export CFL_CONTAINER_ID=`cut -c9- < /proc/1/cpuset`
|
||||
script:
|
||||
- python3 "/opt/oss-fuzz/infra/cifuzz/cifuzz_combined_entrypoint.py"
|
||||
after_script:
|
||||
- shasum /opt/kea/sbin/*
|
||||
- shasum /tmp/not-out/*/*
|
||||
- shasum ${OUT}/*/*
|
||||
artifacts:
|
||||
when: always
|
||||
expire_in: 30 days
|
||||
paths:
|
||||
- "${CFL_ARTIFACTS_DIR}"
|
||||
|
||||
|
||||
############################### SAST ################################
|
||||
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/sast/
|
||||
#
|
||||
@ -119,9 +294,16 @@ include:
|
||||
|
||||
.sast-analyzer:
|
||||
extends: sast
|
||||
stage: test
|
||||
<<: *rules_for_test_stage
|
||||
allow_failure: true
|
||||
script:
|
||||
- /analyzer run
|
||||
rules:
|
||||
- if: $SAST_DISABLED
|
||||
when: never
|
||||
- if: $CI_PIPELINE_SOURCE == 'schedule'
|
||||
when: never
|
||||
|
||||
flawfinder-sast:
|
||||
extends: .sast-analyzer
|
||||
@ -131,11 +313,12 @@ flawfinder-sast:
|
||||
SAST_ANALYZER_IMAGE_TAG: latest
|
||||
SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/flawfinder:$SAST_ANALYZER_IMAGE_TAG"
|
||||
rules:
|
||||
- if: $SAST_DISABLED
|
||||
when: never
|
||||
- if: $SAST_EXCLUDED_ANALYZERS =~ /flawfinder/
|
||||
when: never
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
exists:
|
||||
- '**/*.cc'
|
||||
- '**/*.h'
|
||||
|
||||
semgrep-sast:
|
||||
extends: .sast-analyzer
|
||||
|
@ -2,7 +2,7 @@ ACLOCAL_AMFLAGS = -I m4macros ${ACLOCAL_FLAGS}
|
||||
# ^^^^^^^^ This has to be the first line and cannot come later in this
|
||||
# Makefile.am due to some bork in some versions of autotools.
|
||||
|
||||
SUBDIRS = tools . ext src doc m4macros @PREMIUM_DIR@ @CONTRIB_DIR@
|
||||
SUBDIRS = tools . ext src fuzz doc m4macros @PREMIUM_DIR@ @CONTRIB_DIR@
|
||||
|
||||
USE_LCOV=@USE_LCOV@
|
||||
LCOV=@LCOV@
|
||||
@ -173,7 +173,6 @@ cppcheck:
|
||||
docs:
|
||||
$(MAKE) -C doc/sphinx
|
||||
|
||||
|
||||
# These steps are necessary during installation
|
||||
install-exec-hook:
|
||||
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
|
||||
# hooks libraries, so we need to include it in the distribution.
|
||||
pkginclude_HEADERS = config.h kea_version.h
|
||||
|
||||
.PHONY: clean-coverage coverage cppcheck docs report-coverage
|
||||
|
@ -1428,6 +1428,10 @@ AC_ARG_ENABLE([fuzzing],
|
||||
AC_MSG_RESULT("no. Fuzzing requires C++17 support.")
|
||||
AC_MSG_ERROR("Fuzzing requires C++17 support.")
|
||||
fi
|
||||
if test "${enable_gtest}" = 'no'; then
|
||||
AC_MSG_RESULT("no. Fuzzing requires gtest to be enabled.")
|
||||
AC_MSG_ERROR("Fuzzing requires gtest to be enabled.")
|
||||
fi
|
||||
enable_fuzzing=${enableval}],
|
||||
[enable_fuzzing=no]
|
||||
)
|
||||
@ -1503,6 +1507,7 @@ if (echo ${runstatedir} | grep -q localstatedir); then
|
||||
runstatedir="$(eval echo ${runstatedir})"
|
||||
fi
|
||||
|
||||
AC_CONFIG_FILES([kea_version.h])
|
||||
AC_CONFIG_FILES([Makefile])
|
||||
AC_CONFIG_FILES([doc/Makefile])
|
||||
AC_CONFIG_FILES([doc/sphinx/Makefile])
|
||||
@ -1510,7 +1515,9 @@ AC_CONFIG_FILES([doc/devel/Makefile])
|
||||
AC_CONFIG_FILES([ext/Makefile])
|
||||
AC_CONFIG_FILES([ext/gtest/Makefile])
|
||||
AC_CONFIG_FILES([ext/coroutine/Makefile])
|
||||
AC_CONFIG_FILES([kea_version.h])
|
||||
AC_CONFIG_FILES([fuzz/Makefile])
|
||||
AC_CONFIG_FILES([fuzz/input/Makefile])
|
||||
AC_CONFIG_FILES([fuzz/tests/Makefile])
|
||||
AC_CONFIG_FILES([m4macros/Makefile])
|
||||
AC_CONFIG_FILES([src/Makefile])
|
||||
AC_CONFIG_FILES([src/bin/Makefile])
|
||||
|
@ -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
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
/**
|
||||
|
||||
@page fuzzer Fuzzing Kea
|
||||
|
||||
@section fuzzIntro Introduction
|
||||
@ -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
|
||||
such as crashes or hangs.
|
||||
|
||||
Fuzz testing of Kea uses the AFL (American Fuzzy Lop) program. In this, Kea is
|
||||
built using an AFL-supplied program that not only compiles the software but
|
||||
also instruments it. When run, AFL generates test cases and monitors the
|
||||
execution of Kea as it processes them. AFL will adjust the input based on
|
||||
these measurements, seeking to discover and test new execution paths.
|
||||
There are two ways to fuzz Kea.
|
||||
|
||||
@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
|
||||
|
||||
@ -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
|
||||
the content of subsequent files adjusted accordingly.
|
||||
|
||||
@section fuzzBuild Building Kea for Fuzzing
|
||||
@subsection fuzzBuild Building Kea for Fuzzing
|
||||
|
||||
Whatever tests are done, Kea needs to be built with fuzzing in mind. The steps
|
||||
for this are:
|
||||
@ -53,15 +166,15 @@ for this are:
|
||||
|
||||
-# Build Kea. Kea should be compiled and built as usual, although the
|
||||
following additional steps should be observed:
|
||||
- Set the environment variable CXX to point to the afl-clang-fast++
|
||||
- Set the environment variable CXX to point to the afl-clang-fast
|
||||
compiler.
|
||||
- Specify a value of "--prefix" on the command line to set the directory
|
||||
into which Kea is installed.
|
||||
- Add the "--enable-fuzz" switch to the "configure" command line.
|
||||
- Add the "--enable-fuzzing" switch to the "configure" command line.
|
||||
.
|
||||
For example:
|
||||
@code
|
||||
CXX=/opt/afl/afl-clang-fast++ ./configure --enable-fuzz --prefix=$HOME/installed
|
||||
CXX=afl-clang-fast ./configure --enable-fuzzing --prefix=$HOME/installed
|
||||
make
|
||||
@endcode
|
||||
|
||||
@ -80,8 +193,6 @@ for this are:
|
||||
simpler to install the programs in the directories set by "--prefix" and run
|
||||
them from there.
|
||||
|
||||
@section fuzzRun Running the Fuzzer
|
||||
|
||||
@subsection fuzzRunNetwork Fuzzing with Network Packets
|
||||
|
||||
-# In this type of fuzzing, Kea is processing packets from the fuzzer over a
|
||||
@ -95,20 +206,20 @@ for this are:
|
||||
using the loopback interface "lo" and IPv4 address 10.53.0.1, the
|
||||
configuration file would contain the following snippet:
|
||||
@code
|
||||
{
|
||||
"Dhcp4": {
|
||||
:
|
||||
"interfaces-config": {
|
||||
"interfaces": ["lo/10.53.0.1"]
|
||||
"interfaces": [
|
||||
"lo/10.53.0.1"
|
||||
]
|
||||
},
|
||||
"subnet4": [
|
||||
{
|
||||
:
|
||||
"interface": "lo",
|
||||
:
|
||||
"interface": "lo"
|
||||
}
|
||||
],
|
||||
:
|
||||
]
|
||||
}
|
||||
}
|
||||
@endcode
|
||||
|
||||
-# The specification of the interface and address in the configuration file
|
||||
@ -140,7 +251,7 @@ for this are:
|
||||
data. Ensure that only the payload of the UDP packet is exported.
|
||||
- The "-o" switch specifies a directory (in this example called "fuzz-out")
|
||||
that AFL will use to hold packets it has generated and packets that it has
|
||||
found causes crashes or hangs.
|
||||
found causing crashes or hangs.
|
||||
- "--" Separates the AFL command line from that of Kea.
|
||||
- "./kea-dhcp6" is the program being fuzzed. As mentioned above, this
|
||||
should be an executable image, and it will be simpler to fuzz one
|
||||
@ -164,7 +275,7 @@ for this are:
|
||||
@subsection fuzzRunConfig Fuzzing with Configuration Files
|
||||
|
||||
AFL can be used to check the parsing of the configuration files. In this type
|
||||
of fuzzing, AFL generates configuration files which is passes to Kea to check.
|
||||
of fuzzing, AFL generates configuration files which it passes to Kea to check.
|
||||
Steps for this fuzzing are:
|
||||
|
||||
-# Build Kea as described above.
|
||||
@ -201,8 +312,6 @@ Steps for this fuzzing are:
|
||||
will replace these with the name of a file it has created when starting
|
||||
Kea.
|
||||
|
||||
@section Fuzzing Internals
|
||||
|
||||
@subsection fuzzInternalNetwork Fuzzing with Network Packets
|
||||
|
||||
The AFL fuzzer delivers packets to Kea's stdin. Although the part of Kea
|
||||
@ -217,7 +326,7 @@ while (not shutting down) {
|
||||
Read and process one packet
|
||||
}
|
||||
@endcode
|
||||
When --enable-fuzz is specified, this is conceptually modified to:
|
||||
When --enable-fuzzing is specified, this is conceptually modified to:
|
||||
@code{.unparsed}
|
||||
while (not shutting down) {
|
||||
Read stdin and copy data to address/port on which Kea is listening
|
||||
@ -225,10 +334,10 @@ while (not shutting down) {
|
||||
}
|
||||
@endcode
|
||||
|
||||
Implementation is via an object of class "Fuzz". When created, it identifies
|
||||
an interface, address and port on which Kea is listening and creates the
|
||||
appropriate address structures for these. The port is passed as an argument to
|
||||
the constructor because at the point at which the object is constructed, that
|
||||
Implementation is via an object of class "PacketFuzzer". When created, it
|
||||
identifies an interface, address and port on which Kea is listening and creates
|
||||
the appropriate address structures for these. The port is passed as an argument
|
||||
to the constructor because at the point at which the object is constructed, that
|
||||
information is readily available. The interface and address are picked up from
|
||||
the environment variables mentioned above. Consideration was given to
|
||||
extracting the interface and address information from the configuration file,
|
||||
@ -264,7 +373,7 @@ leaks).
|
||||
No changes were required to Kea source code to fuzz configuration files. In
|
||||
fact, other than compiling with afl-clang++ and installing the resultant
|
||||
executable, no other steps are required. In particular, there is no need to
|
||||
use the "--enable-fuzz" switch in the configuration command line (although
|
||||
use the "--enable-fuzzing" switch in the configuration command line (although
|
||||
doing so will not cause any problems).
|
||||
|
||||
@subsection fuzzThreads Changes Required for Multi-Threaded Kea
|
||||
@ -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
|
||||
the AFL input in the background.
|
||||
|
||||
@section fuzzNotes Notes
|
||||
|
||||
@subsection fuzzNotesUnitTests Unit Test Failures
|
||||
|
||||
If unit tests are built when --enable-fuzzing is specified, note that tests
|
||||
which check or use the DHCP servers (i.e. the unit tests in src/bin/dhcp4,
|
||||
src/bin/dhcp6 and src/bin/kea-admin) will fail. With no AFL-related
|
||||
environment variables defined, a C++ exception will be thrown with the
|
||||
description "no fuzzing interface has been set". However, if the
|
||||
KEA_AFL_INTERFACE and KEA_AFL_ADDRESS variables are set to valid values, the
|
||||
tests will hang.
|
||||
If unit tests are built when --enable-fuzzing is specified and with the AFL
|
||||
compiler, note that tests which check or use the DHCP servers (i.e. the unit
|
||||
tests in src/bin/dhcp4, src/bin/dhcp6 and src/bin/kea-admin) will fail.
|
||||
With no AFL-related environment variables defined, a C++ exception will be
|
||||
thrown with the description "no fuzzing interface has been set".
|
||||
However, if the `KEA_AFL_INTERFACE` and `KEA_AFL_ADDRESS` variables are set to
|
||||
valid values, the tests will hang.
|
||||
|
||||
Both these results are expected and should cause no concern. The exception is
|
||||
thrown by the fuzzing object constructor when it attempts to create the address
|
||||
@ -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.)
|
||||
|
||||
|
||||
*/
|
||||
|
@ -168,8 +168,6 @@ Messages printed on debuglevel 40
|
||||
- DHCP4_CLASSES_ASSIGNED_AFTER_SUBNET_SELECTION
|
||||
- DHCP4_CLASS_ASSIGNED
|
||||
- DHCP4_CLASS_UNCONFIGURED
|
||||
- DHCP4_CLASS_UNDEFINED
|
||||
- DHCP4_CLASS_UNTESTABLE
|
||||
- DHCP4_DHCP4O6_HOOK_SUBNET4_SELECT_DROP
|
||||
- DHCP4_DHCP4O6_HOOK_SUBNET4_SELECT_SKIP
|
||||
- DHCP4_DHCP4O6_PACKET_RECEIVED
|
||||
@ -186,6 +184,8 @@ Messages printed on debuglevel 40
|
||||
- DHCP4_LEASE_QUERY_RECEIVED
|
||||
- DHCP4_LEASE_QUERY_RESPONSE_SENT
|
||||
- DHCP4_PACKET_QUEUE_FULL
|
||||
- DHCP4_REQUIRED_CLASS_NO_TEST
|
||||
- DHCP4_REQUIRED_CLASS_UNDEFINED
|
||||
- DHCP4_SHUTDOWN
|
||||
- DHCP4_SHUTDOWN_REQUEST
|
||||
- DHCP6_BUFFER_RECEIVED
|
||||
@ -193,8 +193,6 @@ Messages printed on debuglevel 40
|
||||
- DHCP6_CLASSES_ASSIGNED_AFTER_SUBNET_SELECTION
|
||||
- DHCP6_CLASS_ASSIGNED
|
||||
- DHCP6_CLASS_UNCONFIGURED
|
||||
- DHCP6_CLASS_UNDEFINED
|
||||
- DHCP6_CLASS_UNTESTABLE
|
||||
- DHCP6_DHCP4O6_PACKET_RECEIVED
|
||||
- DHCP6_FLEX_ID
|
||||
- DHCP6_HOOK_BUFFER_SEND_SKIP
|
||||
@ -213,6 +211,8 @@ Messages printed on debuglevel 40
|
||||
- DHCP6_LEASE_QUERY_REPLY_SENT
|
||||
- DHCP6_PACKET_PROCESS_FAIL
|
||||
- DHCP6_PACKET_QUEUE_FULL
|
||||
- DHCP6_REQUIRED_CLASS_NO_TEST
|
||||
- DHCP6_REQUIRED_CLASS_UNDEFINED
|
||||
- DHCP6_REQUIRED_OPTIONS_CHECK_FAIL
|
||||
- DHCP6_SHUTDOWN
|
||||
- DHCP6_SHUTDOWN_REQUEST
|
||||
|
7
fuzz/.gitignore
vendored
Normal file
7
fuzz/.gitignore
vendored
Normal 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
88
fuzz/Makefile.am
Normal 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
1
fuzz/dict.dat
Normal file
@ -0,0 +1 @@
|
||||
PD_POOLS="pd-pools"
|
76
fuzz/fuzz.cc
Normal file
76
fuzz/fuzz.cc
Normal 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
35
fuzz/fuzz.h
Normal 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"
|
72
fuzz/fuzz_config_kea_dhcp4.cc
Normal file
72
fuzz/fuzz_config_kea_dhcp4.cc
Normal 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"
|
72
fuzz/fuzz_config_kea_dhcp6.cc
Normal file
72
fuzz/fuzz_config_kea_dhcp6.cc
Normal 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
174
fuzz/fuzz_http_endpoint.cc
Normal 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"
|
121
fuzz/fuzz_packets_kea_dhcp4.cc
Normal file
121
fuzz/fuzz_packets_kea_dhcp4.cc
Normal 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"
|
124
fuzz/fuzz_packets_kea_dhcp6.cc
Normal file
124
fuzz/fuzz_packets_kea_dhcp6.cc
Normal 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"
|
122
fuzz/fuzz_unix_socket_kea_dhcp4.cc
Normal file
122
fuzz/fuzz_unix_socket_kea_dhcp4.cc
Normal 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"
|
128
fuzz/fuzz_unix_socket_kea_dhcp6.cc
Normal file
128
fuzz/fuzz_unix_socket_kea_dhcp6.cc
Normal 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
43
fuzz/input/Makefile.am
Normal 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
|
1
fuzz/input/fuzz-config-kea-dhcp4/doc-examples
Symbolic link
1
fuzz/input/fuzz-config-kea-dhcp4/doc-examples
Symbolic link
@ -0,0 +1 @@
|
||||
../../../doc/examples/kea4
|
0
fuzz/input/fuzz-config-kea-dhcp4/empty
Normal file
0
fuzz/input/fuzz-config-kea-dhcp4/empty
Normal file
1
fuzz/input/fuzz-config-kea-dhcp4/one-byte
Normal file
1
fuzz/input/fuzz-config-kea-dhcp4/one-byte
Normal file
@ -0,0 +1 @@
|
||||
0a
|
1
fuzz/input/fuzz-config-kea-dhcp6/doc-examples
Symbolic link
1
fuzz/input/fuzz-config-kea-dhcp6/doc-examples
Symbolic link
@ -0,0 +1 @@
|
||||
../../../doc/examples/kea6
|
0
fuzz/input/fuzz-config-kea-dhcp6/empty
Normal file
0
fuzz/input/fuzz-config-kea-dhcp6/empty
Normal file
1
fuzz/input/fuzz-config-kea-dhcp6/one-byte
Normal file
1
fuzz/input/fuzz-config-kea-dhcp6/one-byte
Normal file
@ -0,0 +1 @@
|
||||
0a
|
3
fuzz/input/fuzz-http-endpoint/config-get
Normal file
3
fuzz/input/fuzz-http-endpoint/config-get
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"command": "config-get"
|
||||
}
|
4
fuzz/input/fuzz-http-endpoint/config-get-with-service
Normal file
4
fuzz/input/fuzz-http-endpoint/config-get-with-service
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"command": "config-get",
|
||||
"service": [ "dhcp6" ]
|
||||
}
|
0
fuzz/input/fuzz-http-endpoint/empty
Normal file
0
fuzz/input/fuzz-http-endpoint/empty
Normal file
1
fuzz/input/fuzz-http-endpoint/empty-json-map
Normal file
1
fuzz/input/fuzz-http-endpoint/empty-json-map
Normal file
@ -0,0 +1 @@
|
||||
{}
|
1
fuzz/input/fuzz-http-endpoint/one-byte
Normal file
1
fuzz/input/fuzz-http-endpoint/one-byte
Normal file
@ -0,0 +1 @@
|
||||
0a
|
3
fuzz/input/fuzz-http-endpoint/one-entry-json-map
Normal file
3
fuzz/input/fuzz-http-endpoint/one-entry-json-map
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"a": 1
|
||||
}
|
1
fuzz/input/fuzz-packets-kea-dhcp4/dhcp-payload-only
Normal file
1
fuzz/input/fuzz-packets-kea-dhcp4/dhcp-payload-only
Normal file
@ -0,0 +1 @@
|
||||
210101060100000000000000000000000000000000000000000a010001000c0102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501013707011c02030f060c3d0701000c01020304ff
|
0
fuzz/input/fuzz-packets-kea-dhcp4/empty
Normal file
0
fuzz/input/fuzz-packets-kea-dhcp4/empty
Normal file
1
fuzz/input/fuzz-packets-kea-dhcp4/full-dhcp-packet
Normal file
1
fuzz/input/fuzz-packets-kea-dhcp4/full-dhcp-packet
Normal file
@ -0,0 +1 @@
|
||||
000400010006d6b6574a0cce713b0800450001229b384000401194910a010001ffffffff00430043010e0b210101060100000000000000000000000000000000000000000a010001000c0102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501013707011c02030f060c3d0701000c01020304ff
|
1
fuzz/input/fuzz-packets-kea-dhcp4/one-byte
Normal file
1
fuzz/input/fuzz-packets-kea-dhcp4/one-byte
Normal file
@ -0,0 +1 @@
|
||||
0a
|
1
fuzz/input/fuzz-packets-kea-dhcp4/udp-header
Normal file
1
fuzz/input/fuzz-packets-kea-dhcp4/udp-header
Normal file
@ -0,0 +1 @@
|
||||
000400010006d6b6574a0cce713b0800450001229b384000401194910a010001ffffffff00430043010e0b
|
1
fuzz/input/fuzz-packets-kea-dhcp6/dhcp-payload-only
Normal file
1
fuzz/input/fuzz-packets-kea-dhcp6/dhcp-payload-only
Normal file
@ -0,0 +1 @@
|
||||
010000000001000e000100012b8b4659000c010203040003000c0000000100000e10000015180006000400170018000800020000
|
0
fuzz/input/fuzz-packets-kea-dhcp6/empty
Normal file
0
fuzz/input/fuzz-packets-kea-dhcp6/empty
Normal file
1
fuzz/input/fuzz-packets-kea-dhcp6/full-dhcp-packet
Normal file
1
fuzz/input/fuzz-packets-kea-dhcp6/full-dhcp-packet
Normal file
@ -0,0 +1 @@
|
||||
000400010006d6b6574a0cce000086dd6001d3f9003c110120010db8000100000000000000000001ff02000000000000000000000001000202220223003c2d0e010000000001000e000100012b8b4659000c010203040003000c0000000100000e10000015180006000400170018000800020000
|
1
fuzz/input/fuzz-packets-kea-dhcp6/one-byte
Normal file
1
fuzz/input/fuzz-packets-kea-dhcp6/one-byte
Normal file
@ -0,0 +1 @@
|
||||
0a
|
1
fuzz/input/fuzz-packets-kea-dhcp6/udp-header
Normal file
1
fuzz/input/fuzz-packets-kea-dhcp6/udp-header
Normal file
@ -0,0 +1 @@
|
||||
000400010006d6b6574a0cce000086dd6001d3f9003c110120010db8000100000000000000000001ff02000000000000000000000001000202220223003c2d0e
|
3
fuzz/input/fuzz-unix-socket-kea-dhcp4/config-get
Normal file
3
fuzz/input/fuzz-unix-socket-kea-dhcp4/config-get
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"command": "config-get"
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"command": "config-get",
|
||||
"service": [ "dhcp4" ]
|
||||
}
|
0
fuzz/input/fuzz-unix-socket-kea-dhcp4/empty
Normal file
0
fuzz/input/fuzz-unix-socket-kea-dhcp4/empty
Normal file
1
fuzz/input/fuzz-unix-socket-kea-dhcp4/empty-json-map
Normal file
1
fuzz/input/fuzz-unix-socket-kea-dhcp4/empty-json-map
Normal file
@ -0,0 +1 @@
|
||||
{}
|
1
fuzz/input/fuzz-unix-socket-kea-dhcp4/one-byte
Normal file
1
fuzz/input/fuzz-unix-socket-kea-dhcp4/one-byte
Normal file
@ -0,0 +1 @@
|
||||
0a
|
3
fuzz/input/fuzz-unix-socket-kea-dhcp4/one-entry-json-map
Normal file
3
fuzz/input/fuzz-unix-socket-kea-dhcp4/one-entry-json-map
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"a": 1
|
||||
}
|
3
fuzz/input/fuzz-unix-socket-kea-dhcp6/config-get
Normal file
3
fuzz/input/fuzz-unix-socket-kea-dhcp6/config-get
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"command": "config-get"
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"command": "config-get",
|
||||
"service": [ "dhcp6" ]
|
||||
}
|
0
fuzz/input/fuzz-unix-socket-kea-dhcp6/empty
Normal file
0
fuzz/input/fuzz-unix-socket-kea-dhcp6/empty
Normal file
1
fuzz/input/fuzz-unix-socket-kea-dhcp6/empty-json-map
Normal file
1
fuzz/input/fuzz-unix-socket-kea-dhcp6/empty-json-map
Normal file
@ -0,0 +1 @@
|
||||
{}
|
1
fuzz/input/fuzz-unix-socket-kea-dhcp6/one-byte
Normal file
1
fuzz/input/fuzz-unix-socket-kea-dhcp6/one-byte
Normal file
@ -0,0 +1 @@
|
||||
0a
|
3
fuzz/input/fuzz-unix-socket-kea-dhcp6/one-entry-json-map
Normal file
3
fuzz/input/fuzz-unix-socket-kea-dhcp6/one-entry-json-map
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"a": 1
|
||||
}
|
20
fuzz/input/kea-dhcp4.conf
Normal file
20
fuzz/input/kea-dhcp4.conf
Normal 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
27
fuzz/input/kea-dhcp6.conf
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
26
fuzz/input/regenerate-cpp-sources.sh
Executable file
26
fuzz/input/regenerate-cpp-sources.sh
Executable 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
24
fuzz/kea-dhcp4.h
Normal 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
31
fuzz/kea-dhcp6.h
Normal 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
11
fuzz/libfuzzer.sh
Executable 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
124
fuzz/main.cc
Normal 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
112
fuzz/setup.sh
Executable 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
16
fuzz/tests/Makefile.am
Normal 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
1
fuzz/tests/setup.sh
Symbolic link
@ -0,0 +1 @@
|
||||
../setup.sh
|
11
fuzz/tests/test-fuzz-config-kea-dhcp4.sh
Executable file
11
fuzz/tests/test-fuzz-config-kea-dhcp4.sh
Executable 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}"
|
11
fuzz/tests/test-fuzz-config-kea-dhcp6.sh
Executable file
11
fuzz/tests/test-fuzz-config-kea-dhcp6.sh
Executable 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}"
|
11
fuzz/tests/test-fuzz-http-endpoint.sh
Executable file
11
fuzz/tests/test-fuzz-http-endpoint.sh
Executable 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}"
|
11
fuzz/tests/test-fuzz-packets-kea-dhcp4.sh
Executable file
11
fuzz/tests/test-fuzz-packets-kea-dhcp4.sh
Executable 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}"
|
11
fuzz/tests/test-fuzz-packets-kea-dhcp6.sh
Executable file
11
fuzz/tests/test-fuzz-packets-kea-dhcp6.sh
Executable 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}"
|
11
fuzz/tests/test-fuzz-unix-socket-kea-dhcp4.sh
Executable file
11
fuzz/tests/test-fuzz-unix-socket-kea-dhcp4.sh
Executable 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}"
|
11
fuzz/tests/test-fuzz-unix-socket-kea-dhcp6.sh
Executable file
11
fuzz/tests/test-fuzz-unix-socket-kea-dhcp6.sh
Executable 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}"
|
@ -41,7 +41,7 @@
|
||||
#include <dhcpsrv/lease_mgr.h>
|
||||
#include <dhcpsrv/lease_mgr_factory.h>
|
||||
#include <dhcpsrv/ncr_generator.h>
|
||||
#include <dhcpsrv/packet-fuzzer.h>
|
||||
#include <dhcpsrv/packet_fuzzer.h>
|
||||
#include <dhcpsrv/resource_handler.h>
|
||||
#include <dhcpsrv/shared_network.h>
|
||||
#include <dhcpsrv/subnet.h>
|
||||
@ -1144,7 +1144,7 @@ Dhcpv4Srv::run() {
|
||||
}
|
||||
|
||||
// Set up structures needed for fuzzing.
|
||||
PacketFuzzer fuzzer(4, server_port_, interface, address);
|
||||
PacketFuzzer fuzzer(server_port_, interface, address);
|
||||
|
||||
// The next line is needed as a signature for AFL to recognize that we are
|
||||
// running persistent fuzzing. This has to be in the main image file.
|
||||
@ -5180,7 +5180,8 @@ void Dhcpv4Srv::discardPackets() {
|
||||
}
|
||||
|
||||
uint16_t Dhcpv4Srv::getServerPort() const {
|
||||
char const* const randomize(getenv("KEA_DHCP4_FUZZING_RANDOMIZE_PORT"));
|
||||
#ifdef FUZZING
|
||||
char const* const randomize(getenv("KEA_DHCP4_FUZZING_ROTATE_PORT"));
|
||||
if (randomize) {
|
||||
InterprocessSyncFile file("kea-dhcp4-fuzzing-randomize-port");
|
||||
InterprocessSyncLocker locker(file);
|
||||
@ -5209,6 +5210,7 @@ uint16_t Dhcpv4Srv::getServerPort() const {
|
||||
locker.unlock();
|
||||
return port;
|
||||
}
|
||||
#endif // FUZZING
|
||||
return server_port_;
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@
|
||||
#include <dhcpsrv/lease_mgr.h>
|
||||
#include <dhcpsrv/lease_mgr_factory.h>
|
||||
#include <dhcpsrv/ncr_generator.h>
|
||||
#include <dhcpsrv/packet-fuzzer.h>
|
||||
#include <dhcpsrv/packet_fuzzer.h>
|
||||
#include <dhcpsrv/subnet.h>
|
||||
#include <dhcpsrv/subnet_selector.h>
|
||||
#include <dhcpsrv/utils.h>
|
||||
@ -616,7 +616,7 @@ Dhcpv6Srv::run() {
|
||||
}
|
||||
|
||||
// Set up structures needed for fuzzing.
|
||||
PacketFuzzer fuzzer(6, server_port_, interface, address);
|
||||
PacketFuzzer fuzzer(server_port_, interface, address);
|
||||
|
||||
// The next line is needed as a signature for AFL to recognize that we are
|
||||
// running persistent fuzzing. This has to be in the main image file.
|
||||
@ -4920,7 +4920,8 @@ void Dhcpv6Srv::discardPackets() {
|
||||
}
|
||||
|
||||
uint16_t Dhcpv6Srv::getServerPort() const {
|
||||
char const* const randomize(getenv("KEA_DHCP6_FUZZING_RANDOMIZE_PORT"));
|
||||
#ifdef FUZZING
|
||||
char const* const randomize(getenv("KEA_DHCP6_FUZZING_ROTATE_PORT"));
|
||||
if (randomize) {
|
||||
InterprocessSyncFile file("kea-dhcp6-fuzzing-randomize-port");
|
||||
InterprocessSyncLocker locker(file);
|
||||
@ -4949,6 +4950,7 @@ uint16_t Dhcpv6Srv::getServerPort() const {
|
||||
locker.unlock();
|
||||
return port;
|
||||
}
|
||||
#endif // FUZZING
|
||||
return server_port_;
|
||||
}
|
||||
|
||||
|
@ -173,7 +173,7 @@ libkea_dhcpsrv_la_SOURCES += parsers/simple_parser6.cc
|
||||
libkea_dhcpsrv_la_SOURCES += parsers/simple_parser6.h
|
||||
|
||||
if FUZZING
|
||||
libkea_dhcpsrv_la_SOURCES += packet-fuzzer.cc packet-fuzzer.h
|
||||
libkea_dhcpsrv_la_SOURCES += packet_fuzzer.cc packet_fuzzer.h
|
||||
libkea_dhcpsrv_la_SOURCES += fuzz_log.cc fuzz_log.h
|
||||
libkea_dhcpsrv_la_SOURCES += fuzz_messages.cc fuzz_messages.h
|
||||
endif # FUZZING
|
||||
@ -352,7 +352,7 @@ libkea_dhcpsrv_include_HEADERS = \
|
||||
|
||||
if FUZZING
|
||||
libkea_dhcpsrv_include_HEADERS += \
|
||||
packet-fuzzer.h \
|
||||
packet_fuzzer.h \
|
||||
fuzz_log.h \
|
||||
fuzz_messages.h
|
||||
endif
|
||||
|
@ -94,7 +94,6 @@ LeaseMgrFactory::destroy() {
|
||||
.arg(getLeaseMgrPtr()->getType());
|
||||
getLeaseMgrPtr().reset();
|
||||
}
|
||||
getLeaseMgrPtr().reset();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -8,8 +8,9 @@
|
||||
|
||||
#ifdef FUZZING
|
||||
|
||||
#include <asiolink/io_address.h>
|
||||
#include <dhcp/dhcp6.h>
|
||||
#include <dhcpsrv/packet-fuzzer.h>
|
||||
#include <dhcpsrv/packet_fuzzer.h>
|
||||
#include <dhcpsrv/fuzz_log.h>
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
@ -26,6 +27,7 @@
|
||||
#include <ctime>
|
||||
|
||||
using namespace isc;
|
||||
using namespace isc::asiolink;
|
||||
using namespace isc::dhcp;
|
||||
using namespace std;
|
||||
|
||||
@ -35,8 +37,7 @@ constexpr size_t PacketFuzzer::MAX_SEND_SIZE;
|
||||
constexpr long PacketFuzzer::MAX_LOOP_COUNT;
|
||||
|
||||
// Constructor
|
||||
PacketFuzzer::PacketFuzzer(int const ipversion,
|
||||
uint16_t const port,
|
||||
PacketFuzzer::PacketFuzzer(uint16_t const port,
|
||||
string const interface,
|
||||
string const address)
|
||||
: loop_max_(MAX_LOOP_COUNT), sockaddr_len_(0), sockaddr_ptr_(nullptr), sockfd_(-1) {
|
||||
@ -55,25 +56,26 @@ PacketFuzzer::PacketFuzzer(int const ipversion,
|
||||
try {
|
||||
loop_max_ = boost::lexical_cast<long>(loop_max_ptr);
|
||||
} catch (const boost::bad_lexical_cast&) {
|
||||
reason << "cannot convert loop count " << loop_max_ptr
|
||||
<< " to an integer";
|
||||
isc_throw(FuzzInitFail, reason.str());
|
||||
isc_throw(FuzzInitFail,
|
||||
"cannot convert loop count " << loop_max_ptr << " to an integer");
|
||||
}
|
||||
|
||||
if (loop_max_ <= 0) {
|
||||
reason << "KEA_AFL_LOOP_MAX is " << loop_max_ << ". "
|
||||
<< "It must be an integer greater than zero.";
|
||||
isc_throw(FuzzInitFail, reason.str());
|
||||
isc_throw(FuzzInitFail, "KEA_AFL_LOOP_MAX is "
|
||||
<< loop_max_ << ". "
|
||||
<< "It must be an integer greater than zero.");
|
||||
}
|
||||
}
|
||||
|
||||
IOAddress io_address(address);
|
||||
|
||||
// Set up address structures used to route the packets from AFL to Kea.
|
||||
createAddressStructures(ipversion, port, interface, address);
|
||||
createAddressStructures(port, interface, io_address);
|
||||
|
||||
// Create the socket through which packets read from stdin will be sent
|
||||
// to the port on which Kea is listening. This is closed in the
|
||||
// destructor.
|
||||
sockfd_ = socket((ipversion == 4) ? AF_INET : AF_INET6, SOCK_DGRAM, 0);
|
||||
sockfd_ = socket(io_address.isV4() ? AF_INET : AF_INET6, SOCK_DGRAM, 0);
|
||||
if (sockfd_ < 0) {
|
||||
LOG_FATAL(fuzz_logger, FUZZ_SOCKET_CREATE_FAIL)
|
||||
.arg(strerror(errno));
|
||||
@ -98,38 +100,35 @@ PacketFuzzer::~PacketFuzzer() {
|
||||
|
||||
// Set up address structures.
|
||||
void
|
||||
PacketFuzzer::createAddressStructures(int const ipversion,
|
||||
uint16_t const port,
|
||||
string const interface,
|
||||
string const address) {
|
||||
stringstream reason; // Used in error messages
|
||||
PacketFuzzer::createAddressStructures(uint16_t const port,
|
||||
string const& interface,
|
||||
IOAddress const& io_address) {
|
||||
string const address(io_address.toText());
|
||||
|
||||
// Set up the appropriate data structure depending on the address given.
|
||||
if (ipversion == 6 && address.find(":") != string::npos) {
|
||||
if (io_address.isV6()) {
|
||||
// Expecting IPv6 and the address contains a colon, so assume it is an
|
||||
// an IPv6 address.
|
||||
memset(&servaddr6_, 0, sizeof (servaddr6_));
|
||||
|
||||
servaddr6_.sin6_family = AF_INET6;
|
||||
if (inet_pton(AF_INET6, address.c_str(), &servaddr6_.sin6_addr) != 1) {
|
||||
reason << "inet_pton() failed: can't convert "
|
||||
<< address << " to an IPv6 address" << endl;
|
||||
isc_throw(FuzzInitFail, reason.str());
|
||||
isc_throw(FuzzInitFail,
|
||||
"inet_pton() failed: can't convert " << address << " to an IPv6 address");
|
||||
}
|
||||
servaddr6_.sin6_port = htons(port);
|
||||
|
||||
// Interface ID is needed for IPv6 address structures.
|
||||
servaddr6_.sin6_scope_id = if_nametoindex(interface.c_str());
|
||||
if (servaddr6_.sin6_scope_id == 0) {
|
||||
reason << "error retrieving interface ID for "
|
||||
<< interface << ": " << strerror(errno);
|
||||
isc_throw(FuzzInitFail, reason.str());
|
||||
isc_throw(FuzzInitFail,
|
||||
"error retrieving interface ID for " << interface << ": " << strerror(errno));
|
||||
}
|
||||
|
||||
sockaddr_ptr_ = reinterpret_cast<sockaddr*>(&servaddr6_);
|
||||
sockaddr_len_ = sizeof(servaddr6_);
|
||||
|
||||
} else if (ipversion == 4 && address.find(".") != string::npos) {
|
||||
} else if (io_address.isV4()) {
|
||||
// Expecting an IPv4 address and it contains a dot, so assume it is.
|
||||
// This check is done after the IPv6 check, as it is possible for an
|
||||
// IPv4 address to be embedded in an IPv6 one.
|
||||
@ -137,9 +136,8 @@ PacketFuzzer::createAddressStructures(int const ipversion,
|
||||
|
||||
servaddr4_.sin_family = AF_INET;
|
||||
if (inet_pton(AF_INET, address.c_str(), &servaddr4_.sin_addr) != 1) {
|
||||
reason << "inet_pton() failed: can't convert "
|
||||
<< address << " to an IPv6 address" << endl;
|
||||
isc_throw(FuzzInitFail, reason.str());
|
||||
isc_throw(FuzzInitFail,
|
||||
"inet_pton() failed: can't convert " << address << " to an IPv4 address");
|
||||
}
|
||||
servaddr4_.sin_port = htons(port);
|
||||
|
||||
@ -147,12 +145,9 @@ PacketFuzzer::createAddressStructures(int const ipversion,
|
||||
sockaddr_len_ = sizeof(servaddr4_);
|
||||
|
||||
} else {
|
||||
reason << "Expected IP version (" << ipversion << ") is not "
|
||||
<< "4 or 6, or the given address " << address << " does not "
|
||||
<< "match the IP version expected";
|
||||
isc_throw(FuzzInitFail, reason.str());
|
||||
// Should never happen.
|
||||
isc_throw(FuzzInitFail, "unknown IOAddress IP version");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
@ -9,6 +9,7 @@
|
||||
|
||||
#ifdef FUZZING
|
||||
|
||||
#include <asiolink/io_address.h>
|
||||
#include <exceptions/exceptions.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
@ -16,10 +17,7 @@
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
namespace isc {
|
||||
|
||||
@ -69,12 +67,9 @@ public:
|
||||
/// Sets up data structures to access the address/port being used to
|
||||
/// transfer data from AFL to Kea.
|
||||
///
|
||||
/// @param ipversion Either 4 or 6 depending on what IP version the
|
||||
/// server responds to.
|
||||
/// @param port Port on which the server is listening, and hence the
|
||||
/// port to which the fuzzer will send input from AFL.
|
||||
PacketFuzzer(int const ipversion,
|
||||
uint16_t const port,
|
||||
PacketFuzzer(uint16_t const port,
|
||||
std::string const interface,
|
||||
std::string const address);
|
||||
|
||||
@ -110,19 +105,15 @@ private:
|
||||
/// Create the address structures describing the address/port on whick Kea
|
||||
/// is listening for packets from AFL.
|
||||
///
|
||||
/// @param ipversion Either 4 or 6 depending on which IP version address
|
||||
/// is expected.
|
||||
/// @param interface Interface through which the fuzzer is sending packets
|
||||
/// to Kea.
|
||||
/// @param address Address on the interface that will be used.
|
||||
/// @param port Port to be used.
|
||||
/// @param port Port to be used.
|
||||
/// @param interface Interface through which the fuzzer is sending packets to Kea.
|
||||
/// @param io_address Address on the interface that will be used.
|
||||
///
|
||||
/// @throws FuzzInitFail Thrown if the address is not in the expected
|
||||
/// format.
|
||||
void createAddressStructures(int const ipversion,
|
||||
uint16_t const port,
|
||||
std::string const interface,
|
||||
std::string const address);
|
||||
void createAddressStructures(uint16_t const port,
|
||||
std::string const& interface,
|
||||
isc::asiolink::IOAddress const& io_address);
|
||||
|
||||
// Other member variables.
|
||||
long loop_max_; //< Maximum number of loop iterations
|
@ -131,6 +131,10 @@ libdhcpsrv_unittests_SOURCES += tracking_lease_mgr_unittest.cc
|
||||
libdhcpsrv_unittests_SOURCES += network_state_unittest.cc
|
||||
libdhcpsrv_unittests_SOURCES += network_unittest.cc
|
||||
|
||||
if FUZZING
|
||||
libdhcpsrv_unittests_SOURCES += packet_fuzzer_unittest.cc
|
||||
endif
|
||||
|
||||
libdhcpsrv_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
|
||||
|
||||
libdhcpsrv_unittests_CXXFLAGS = $(AM_CXXFLAGS)
|
||||
|
32
src/lib/dhcpsrv/tests/packet_fuzzer_unittest.cc
Normal file
32
src/lib/dhcpsrv/tests/packet_fuzzer_unittest.cc
Normal 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
|
@ -183,7 +183,7 @@ public:
|
||||
class MultiThreadingInvalidOperation : public Exception {
|
||||
public:
|
||||
MultiThreadingInvalidOperation(const char* file, size_t line, const char* what) :
|
||||
isc::Exception(file, line, what) {};
|
||||
isc::Exception(file, line, what) {}
|
||||
};
|
||||
|
||||
///
|
||||
|
@ -183,18 +183,20 @@ Path::replaceParentPath(string const& replacement) {
|
||||
TemporaryDirectory::TemporaryDirectory() {
|
||||
char dir[]("/tmp/kea-tmpdir-XXXXXX");
|
||||
char const* dir_name = mkdtemp(dir);
|
||||
if(!dir_name) {
|
||||
if (!dir_name) {
|
||||
isc_throw(Unexpected, "mkdtemp failed " << dir << ": " << strerror(errno));
|
||||
}
|
||||
dir_name_ = string(dir_name);
|
||||
}
|
||||
|
||||
TemporaryDirectory::~TemporaryDirectory() {
|
||||
rmdir(dir_name_.c_str());
|
||||
DIR *dir(opendir(dir_name_.c_str()));
|
||||
if (!dir) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct dirent *i;
|
||||
string filepath;
|
||||
|
||||
while ((i = readdir(dir))) {
|
||||
if (strcmp(i->d_name, ".") == 0 || strcmp(i->d_name, "..") == 0) {
|
||||
continue;
|
||||
|
Loading…
x
Reference in New Issue
Block a user