]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#4272] Copy fuzzing harnesses from Ada Logics
authorAndrei Pavel <andrei@isc.org>
Tue, 3 Mar 2026 12:58:12 +0000 (14:58 +0200)
committerAndrei Pavel <andrei@isc.org>
Thu, 6 Aug 2026 07:34:24 +0000 (10:34 +0300)
60 files changed:
src/fuzz/build.sh [new file with mode: 0755]
src/fuzz/corp/dhcp-4-sample1.json [new file with mode: 0644]
src/fuzz/corp/dhcp-4-sample2.json [new file with mode: 0644]
src/fuzz/corp/dhcp-6-sample1.json [new file with mode: 0644]
src/fuzz/corp/dhcp-6-sample2.json [new file with mode: 0644]
src/fuzz/corp/dhcp-6-sample3.json [new file with mode: 0644]
src/fuzz/corp/dhcp-6-sample4.json [new file with mode: 0644]
src/fuzz/corp/leases.csv [new file with mode: 0644]
src/fuzz/corp/sample.csv [new file with mode: 0644]
src/fuzz/corp/sample2.json [new file with mode: 0644]
src/fuzz/corp/sample3.json [new file with mode: 0644]
src/fuzz/fuzz_agent.cc [new file with mode: 0644]
src/fuzz/fuzz_agent.dict [new file with mode: 0644]
src/fuzz/fuzz_cc.cc [new file with mode: 0644]
src/fuzz/fuzz_crypto.cc [new file with mode: 0644]
src/fuzz/fuzz_cryptolink.cc [new file with mode: 0644]
src/fuzz/fuzz_d2.cc [new file with mode: 0644]
src/fuzz/fuzz_d2.dict [new file with mode: 0644]
src/fuzz/fuzz_dhcp_parser.dict [new file with mode: 0644]
src/fuzz/fuzz_dhcp_parser4.cc [new file with mode: 0644]
src/fuzz/fuzz_dhcp_parser6.cc [new file with mode: 0644]
src/fuzz/fuzz_dhcp_pkt.dict [new file with mode: 0644]
src/fuzz/fuzz_dhcp_pkt4.cc [new file with mode: 0644]
src/fuzz/fuzz_dhcp_pkt6.cc [new file with mode: 0644]
src/fuzz/fuzz_dhcp_pkt_process4.cc [new file with mode: 0644]
src/fuzz/fuzz_dhcp_pkt_process6.cc [new file with mode: 0644]
src/fuzz/fuzz_dhcpsrv.cc [new file with mode: 0644]
src/fuzz/fuzz_dhcpsrv.dict [new file with mode: 0644]
src/fuzz/fuzz_dhcpsrv_csv_lease.cc [new file with mode: 0644]
src/fuzz/fuzz_dns.cc [new file with mode: 0644]
src/fuzz/fuzz_dns.dict [new file with mode: 0644]
src/fuzz/fuzz_encode.cc [new file with mode: 0644]
src/fuzz/fuzz_eval.dict [new file with mode: 0644]
src/fuzz/fuzz_eval4.cc [new file with mode: 0644]
src/fuzz/fuzz_eval6.cc [new file with mode: 0644]
src/fuzz/fuzz_hook_ddns_tuning4.cc [new file with mode: 0644]
src/fuzz/fuzz_hook_ddns_tuning6.cc [new file with mode: 0644]
src/fuzz/fuzz_hook_flex_id4.cc [new file with mode: 0644]
src/fuzz/fuzz_hook_flex_id6.cc [new file with mode: 0644]
src/fuzz/fuzz_hook_lease_query4.cc [new file with mode: 0644]
src/fuzz/fuzz_hook_lease_query6.cc [new file with mode: 0644]
src/fuzz/fuzz_hook_radius4.cc [new file with mode: 0644]
src/fuzz/fuzz_hook_radius6.cc [new file with mode: 0644]
src/fuzz/fuzz_hook_run_script4.cc [new file with mode: 0644]
src/fuzz/fuzz_hook_run_script6.cc [new file with mode: 0644]
src/fuzz/fuzz_hook_tsig.cc [new file with mode: 0644]
src/fuzz/fuzz_hook_user_chk4.cc [new file with mode: 0644]
src/fuzz/fuzz_hook_user_chk6.cc [new file with mode: 0644]
src/fuzz/fuzz_http.cc [new file with mode: 0644]
src/fuzz/fuzz_http.dict [new file with mode: 0644]
src/fuzz/fuzz_ioaddress.cc [new file with mode: 0644]
src/fuzz/fuzz_mysql4.cc [new file with mode: 0644]
src/fuzz/fuzz_mysql6.cc [new file with mode: 0644]
src/fuzz/fuzz_pgsql4.cc [new file with mode: 0644]
src/fuzz/fuzz_pgsql6.cc [new file with mode: 0644]
src/fuzz/fuzz_util.cc [new file with mode: 0644]
src/fuzz/helper_func.cc [new file with mode: 0644]
src/fuzz/helper_func.h [new file with mode: 0644]
src/fuzz/mysqlmock.cc [new file with mode: 0644]
src/fuzz/pgmock.cc [new file with mode: 0644]

diff --git a/src/fuzz/build.sh b/src/fuzz/build.sh
new file mode 100755 (executable)
index 0000000..57b9cec
--- /dev/null
@@ -0,0 +1,178 @@
+#!/bin/bash -eu
+# Copyright (C) 2025 Ada Logics Ltd.
+#
+# 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/.
+################################################################################
+
+# Compile log4cplus
+cd $SRC/log4cplus
+./configure --prefix=/usr --enable-static --disable-shared --with-pic
+make -j"$(nproc)"
+make install
+
+# Configure flags
+cd $SRC/kea
+export CXXFLAGS="${CXXFLAGS:-} -gdwarf-4"
+export LDFLAGS="${LDFLAGS:-} -gdwarf-4"
+
+CPP_ARGS="-stdlib=libc++  \
+  -DCHRONO_SAME_DURATION=1 -D_LIBCPP_ENABLE_CXX17_REMOVED_AUTO_PTR \
+  -D_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION -D_GLIBCXX_USE_DEPRECATED=1"
+LD_ARGS="-stdlib=libc++"
+
+if [ "$SANITIZER" = "coverage" ]; then
+  CPP_ARGS="${CPP_ARGS} -fprofile-instr-generate -fcoverage-mapping"
+fi
+
+if [ "$SANITIZER" = "coverage" ] || [ "$SANITIZER" = "introspector" ] || [ "$SANITIZER" = "none" ]; then
+  SANITIZER_CHOICE=
+else
+  SANITIZER_CHOICE="-D b_sanitize=${SANITIZER}"
+  CPP_ARGS="${CPP_ARGS} -fsanitize=fuzzer-no-link"
+fi
+
+meson setup build --prefix="$OUT" $SANITIZER_CHOICE -D cpp_std=c++17 \
+  -D fuzz=enabled -D tests=enabled -D crypto=openssl -D default_library=static \
+  -D default_both_libraries=static -D cpp_args="$CPP_ARGS" -D cpp_link_args="$LD_ARGS" \
+  -D postgresql=enabled -D mysql=enabled -D krb5=enabled -D b_ndebug=true
+meson compile --verbose -C build
+
+# Package static library
+find $SRC/kea/build/src/lib -type f -name '*.o' -print0 | xargs -0 llvm-ar rcsD libkea.a
+llvm-ranlib libkea.a
+
+# Find necessary static libraries
+BUILD_BASEDIR="$SRC/kea/build/src"
+KEA_STATIC_LIBS="/usr/lib/liblog4cplus.a libkea.a  "
+KEA_STATIC_LIBS+=$(find $BUILD_BASEDIR/bin \( -path '/src/kea/build/src/bin/dhcp4/*' -o -path '/src/kea/build/src/bin/dhcp6/*' \) -prune -o -type f -name '*.a' -print)
+KEA_STATIC_LIBS_TEST="$KEA_STATIC_LIBS $SRC/kea/build/subprojects/googletest-1.15.2/googletest/libgtest-all.a"
+
+INCLUDES="-I. -I$SRC -I$SRC/kea-fuzzer -Isrc -Ibuild -Isrc/lib -Isrc/bin -Isrc/hooks -Isrc/hooks/d2 -Isrc/hooks/d2/gss_tsig "
+INCLUDES+="-Isrc/hooks/dhcp/pgsql -Isrc/hooks/dhcp/mysql -Isrc/hooks/dhcp/user_chk -I/usr/include/postgresql -I/usr/include/mariadb"
+KEA_INCLUDES="$INCLUDES -I/src/kea/subprojects/googletest-1.15.2/googletest/include -Ifuzz"
+LIBS="-lpthread -ldl -lm -lc++ -lc++abi -lssl -lcrypto -lkrb5 -lgssapi_krb5"
+export CXXFLAGS="${CXXFLAGS} -std=c++17 -stdlib=libc++ -Wno-unused-parameter -Wno-unused-value"
+
+# Build non-dhcp specific fuzzers
+for fuzzer in fuzz_cc fuzz_d2 fuzz_agent fuzz_util fuzz_dhcpsrv fuzz_dhcpsrv_csv_lease fuzz_dns fuzz_encode fuzz_cryptolink
+do
+  extra_lib=""
+  case "$fuzzer" in fuzz_hook_tsig)
+    extra_lib="$SRC/kea/build/src/hooks/d2/gss_tsig/libddns_gss_tsig.a"
+    ;;
+  esac
+  
+  # fuzz_dns, fuzz_encode, and fuzz_cryptolink don't need helper_func.cc
+  if [ "$fuzzer" = "fuzz_dns" ] || [ "$fuzzer" = "fuzz_encode" ] || [ "$fuzzer" = "fuzz_cryptolink" ]; then
+    $CXX $CXXFLAGS "$SRC/kea-fuzzer/${fuzzer}.cc"  \
+      -Wl,--start-group $KEA_STATIC_LIBS $extra_lib -Wl,--end-group  \
+      $INCLUDES $LIBS $LIB_FUZZING_ENGINE -o "$OUT/${fuzzer}"
+  else
+    $CXX $CXXFLAGS "$SRC/kea-fuzzer/helper_func.cc" \
+      "$SRC/kea-fuzzer/${fuzzer}.cc"  \
+      -Wl,--start-group $KEA_STATIC_LIBS $extra_lib -Wl,--end-group  \
+      $INCLUDES $LIBS $LIB_FUZZING_ENGINE -o "$OUT/${fuzzer}"
+  fi
+
+  if [ -f "$SRC/kea-fuzzer/${fuzzer}.dict" ]; then
+    cp $SRC/kea-fuzzer/${fuzzer}.dict $OUT
+  fi
+done
+
+for DHCPVER in 4 6
+do
+  for fuzzer in fuzz_dhcp_parser fuzz_eval fuzz_dhcp_pkt fuzz_pgsql \
+                fuzz_mysql fuzz_dhcp_pkt_process fuzz_hook_run_script \
+                fuzz_hook_radius fuzz_hook_ddns_tuning fuzz_hook_lease_query \
+                fuzz_hook_flex_id fuzz_hook_user_chk
+  do
+    extra_lib=""
+    case "$fuzzer" in fuzz_pgsql)
+      extra_lib="$SRC/kea-fuzzer/pgmock.cc "
+      extra_lib+="$SRC/kea/build/src/hooks/dhcp/pgsql/libdhcp_pgsql.a"
+      ;;
+    esac
+    case "$fuzzer" in fuzz_mysql)
+      extra_lib="$SRC/kea-fuzzer/mysqlmock.cc "
+      extra_lib+="$SRC/kea/build/src/hooks/dhcp/mysql/libdhcp_mysql.a"
+      ;;
+    esac
+    case "$fuzzer" in fuzz_dhcp_pkt_process)
+      extra_lib="$SRC/kea/build/src/hooks/dhcp/lease_cmds/libdhcp_lease_cmds.a"
+      cp $SRC/kea-fuzzer/fuzz_dhcp_pkt.dict $OUT/${fuzzer}${DHCPVER}.dict
+      ;;
+    esac
+    case "$fuzzer" in fuzz_hook_run_script)
+      extra_lib="$SRC/kea/build/src/hooks/dhcp/run_script/libdhcp_run_script.a"
+      cp $SRC/kea-fuzzer/fuzz_dhcp_pkt.dict $OUT/${fuzzer}${DHCPVER}.dict
+      ;;
+    esac
+    case "$fuzzer" in fuzz_hook_radius)
+      extra_lib="$SRC/kea/build/src/hooks/dhcp/radius/libdhcp_radius.a"
+      cp $SRC/kea-fuzzer/fuzz_dhcp_pkt.dict $OUT/${fuzzer}${DHCPVER}.dict
+      ;;
+    esac
+    case "$fuzzer" in fuzz_hook_ddns_tuning)
+      extra_lib="$SRC/kea/build/src/hooks/dhcp/ddns_tuning/libdhcp_ddns_tuning.a"
+      cp $SRC/kea-fuzzer/fuzz_dhcp_pkt.dict $OUT/${fuzzer}${DHCPVER}.dict
+      ;;
+    esac
+    case "$fuzzer" in fuzz_hook_lease_query)
+      extra_lib="$SRC/kea/build/src/hooks/dhcp/lease_query/libdhcp_lease_query.a"
+      cp $SRC/kea-fuzzer/fuzz_dhcp_pkt.dict $OUT/${fuzzer}${DHCPVER}.dict
+      ;;
+    esac
+    case "$fuzzer" in fuzz_hook_flex_id)
+      extra_lib="$SRC/kea/build/src/hooks/dhcp/flex_id/libdhcp_flex_id.a"
+      cp $SRC/kea-fuzzer/fuzz_dhcp_pkt.dict $OUT/${fuzzer}${DHCPVER}.dict
+      ;;
+    esac
+    case "$fuzzer" in fuzz_hook_user_chk)
+      extra_lib="$SRC/kea/build/src/hooks/dhcp/user_chk/libdhcp_user_chk.a"
+      cp $SRC/kea-fuzzer/fuzz_dhcp_pkt.dict $OUT/${fuzzer}${DHCPVER}.dict
+      ;;
+    esac
+
+    $CXX $CXXFLAGS -Wl,--start-group "$SRC/kea-fuzzer/helper_func.cc" \
+      "$SRC/kea-fuzzer/${fuzzer}${DHCPVER}.cc" $extra_lib \
+      $KEA_STATIC_LIBS $BUILD_BASEDIR/bin/dhcp$DHCPVER/libdhcp$DHCPVER.a \
+      -Wl,--end-group $INCLUDES $LIBS \
+      $LIB_FUZZING_ENGINE -o "$OUT/${fuzzer}${DHCPVER}"
+
+    if [ -f "$SRC/kea-fuzzer/${fuzzer}.dict" ]; then
+      cp $SRC/kea-fuzzer/${fuzzer}.dict $OUT/${fuzzer}${DHCPVER}.dict
+    fi
+  done
+
+  # Compile fuzzers from kea repository
+  for fuzzer in fuzz_config_kea_dhcp fuzz_http_endpoint_kea_dhcp fuzz_packets_kea_dhcp fuzz_unix_socket_kea_dhcp
+  do
+    # Skip fuzz_http_endpoint_kea_dhcp6 as it requires real IPv6 binding which is not enabled in OSS-Fuzz
+    if [ "$fuzzer" = "fuzz_http_endpoint_kea_dhcp" ] && [ "$DHCPVER" = "6" ]; then
+      continue
+    fi
+
+    $CXX $CXXFLAGS -Wl,--start-group "$SRC/kea/fuzz/${fuzzer}${DHCPVER}.cc" \
+      $SRC/kea/fuzz/fuzz.cc $KEA_STATIC_LIBS_TEST \
+      $BUILD_BASEDIR/bin/dhcp$DHCPVER/libdhcp$DHCPVER.a \
+      $KEA_INCLUDES $LIBS $LIB_FUZZING_ENGINE -Wl,--end-group \
+      -o "$OUT/${fuzzer}${DHCPVER}"
+  done
+done
+
+# Prepare maximum size option for configuration parsing related fuzzers
+for fuzzer in fuzz_eval4 fuzz_eval6 fuzz_dhcp_parser4 fuzz_dhcp_parser6 \
+              fuzz_dhcp_pkt4 fuzz_dhcp_pkt6 fuzz_cc fuzz_d2 fuzz_agent \
+              fuzz_config_kea_dhcp4 fuzz_config_kea_dhcp6
+do
+    echo -e "[libfuzzer]\nmax_len=25600" > $OUT/$fuzzer.options
+done
+
+# Prepare the seeds
+zip -j $OUT/fuzz_dhcpsrv_seed_corpus.zip $SRC/kea-fuzzer/corp/*.json
+zip -j $OUT/fuzz_dhcp_parser4_seed_corpus.zip $SRC/kea-fuzzer/corp/*.json
+zip -j $OUT/fuzz_dhcp_parser6_seed_corpus.zip $SRC/kea-fuzzer/corp/*.json
+zip -j $OUT/fuzz_agent_seed_corpus.zip $SRC/kea/src/bin/agent/tests/testdata/*.json
+zip -j $OUT/fuzz_d2_seed_corpus.zip $SRC/kea/src/bin/d2/tests/testdata/*.json
diff --git a/src/fuzz/corp/dhcp-4-sample1.json b/src/fuzz/corp/dhcp-4-sample1.json
new file mode 100644 (file)
index 0000000..a706787
--- /dev/null
@@ -0,0 +1,223 @@
+// This is an example configuration of the Kea DHCPv4 server 1:
+//
+// - uses High Availability hooks library and Lease Commands hooks library
+//   to enable High Availability function for the DHCP server. This config
+//   file is for the primary (the active) server.
+// - uses memfile, which stores lease data in a local CSV file
+// - it assumes a single /24 addressing over a link that is directly reachable
+//   (no DHCP relays)
+// - there is a handful of IP reservations
+//
+// It is expected to run with a standby (the passive) server, which has a very similar
+// configuration. The only difference is that "this-server-name" must be set to "server2" on the
+// other server. Also, the interface configuration depends on the network settings of the
+// particular machine.
+
+{
+
+"Dhcp4": {
+
+    // Add names of your network interfaces to listen on.
+    "interfaces-config": {
+        // The DHCPv4 server listens on this interface. When changing this to
+        // the actual name of your interface, make sure to also update the
+        // interface parameter in the subnet definition below.
+        "interfaces": [ "enp0s8" ]
+    },
+
+    // Control socket is required for communication between the Control
+    // Agent and the DHCP server. High Availability requires Control Agent
+    // to be running because lease updates are sent over the RESTful
+    // API between the HA peers.
+    "control-socket": {
+        "socket-type": "unix",
+        "socket-name": "/tmp/kea4-ctrl-socket"
+    },
+
+    // Use Memfile lease database backend to store leases in a CSV file.
+    // Depending on how Kea was compiled, it may also support SQL databases
+    // (MySQL and/or PostgreSQL) and even Cassandra. Those database backends
+    // require more parameters, like name, host and possibly user and password.
+    // There are dedicated examples for each backend. See Section 7.2.2 "Lease
+    // Storage" for details.
+    "lease-database": {
+        // Memfile is the simplest and easiest backend to use. It's an in-memory
+        // database with data being written to a CSV file. It is very similar to
+        // what ISC DHCP does.
+        "type": "memfile"
+    },
+
+    // Let's configure some global parameters. The home network is not very dynamic
+    // and there's no shortage of addresses, so no need to recycle aggresively.
+    "valid-lifetime": 43200, // leases will be valid for 12h
+    "renew-timer": 21600, // clients should renew every 6h
+    "rebind-timer": 32400, // clients should start looking for other servers after 9h
+
+    // Kea will clean up its database of expired leases once per hour. However, it
+    // will keep the leases in expired state for 2 days. This greatly increases the
+    // chances for returning devices to get the same address again. To guarantee that,
+    // use host reservation.
+    "expired-leases-processing": {
+        "reclaim-timer-wait-time": 3600,
+        "hold-reclaimed-time": 172800,
+        "max-reclaim-leases": 0,
+        "max-reclaim-time": 0
+    },
+
+    // HA requires two hooks libraries to be loaded: libdhcp_lease_cmds.so and
+    // libdhcp_ha.so. The former handles incoming lease updates from the HA peers.
+    // The latter implements high availability feature for Kea. Note the library name
+    // should be the same, but the path is OS specific.
+    "hooks-libraries": [
+        // The lease_cmds library must be loaded because HA makes use of it to
+        // deliver lease updates to the server as well as synchronize the
+        // lease database after failure.
+        {
+            "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_lease_cmds.so"
+        },
+
+        {
+            // The HA hooks library should be loaded.
+            "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_ha.so",
+            "parameters": {
+                // Each server should have the same HA configuration, except for the
+                // "this-server-name" parameter.
+                "high-availability": [ {
+                    // This parameter points to this server instance. The respective
+                    // HA peers must have this parameter set to their own names.
+                    "this-server-name": "server1",
+                    // The HA mode is set to hot-standby. In this mode, the active server handles
+                    // all the traffic. The standby takes over if the primary becomes unavailable.
+                    "mode": "hot-standby",
+                    // Heartbeat is to be sent every 10 seconds if no other control
+                    // commands are transmitted.
+                    "heartbeat-delay": 10000,
+                    // Maximum time for partner's response to a heartbeat, after which
+                    // failure detection is started. This is specified in milliseconds.
+                    // If we don't hear from the partner in 60 seconds, it's time to
+                    // start worrying.
+                    "max-response-delay": 60000,
+                    // The following parameters control how the server detects the
+                    // partner's failure. The ACK delay sets the threshold for the
+                    // 'secs' field of the received discovers. This is specified in
+                    // milliseconds.
+                    "max-ack-delay": 5000,
+                    // This specifies the number of clients which send messages to
+                    // the partner but appear to not receive any response.
+                    "max-unacked-clients": 5,
+                    // This specifies the maximum timeout (in milliseconds) for the server
+                    // to complete sync. If you have a large deployment (high tens or
+                    // hundreds of thousands of clients), you may need to increase it
+                    // further. The default value is 60000ms (60 seconds).
+                    "sync-timeout": 60000,
+                    "peers": [
+                        // This is the configuration of this server instance.
+                        {
+                            "name": "server1",
+                            // This specifies the URL of our server instance. The
+                            // Control Agent must run along with our DHCPv4 server
+                            // instance and the "http-host" and "http-port" must be
+                            // set to the corresponding values.
+                            "url": "http://192.168.1.2:8000/",
+                            // This server is primary. The other one must be
+                            // secondary.
+                            "role": "primary"
+                        },
+                        // This is the configuration of our HA peer.
+                        {
+                            "name": "server2",
+                            // Specifies the URL on which the partner's control
+                            // channel can be reached. The Control Agent is required
+                            // to run on the partner's machine with "http-host" and
+                            // "http-port" values set to the corresponding values.
+                            "url": "http://192.168.1.3:8000/",
+                            // The partner is a secondary. Our is primary.
+                            "role": "standby"
+                        }
+                    ]
+                } ]
+            }
+        }
+    ],
+
+    // This example contains a single subnet declaration.
+    "subnet4": [
+        {
+            // Subnet prefix.
+            "subnet": "192.168.1.0/24",
+
+            // There are no relays in this network, so we need to tell Kea that this subnet
+            // is reachable directly via the specified interface.
+            "interface": "enp0s8",
+
+            // Specify a dynamic address pool.
+            "pools": [
+                {
+                    "pool": "192.168.1.100-192.168.1.199"
+                }
+            ],
+
+            // These are options that are subnet specific. In most cases, you need to define at
+            // least routers option, as without this option your clients will not be able to reach
+            // their default gateway and will not have Internet connectivity. If you have many
+            // subnets and they share the same options (e.g. DNS servers typically is the same
+            // everywhere), you may define options at the global scope, so you don't repeat them
+            // for every network.
+            "option-data": [
+                {
+                    // For each IPv4 subnet you typically need to specify at least one router.
+                    "name": "routers",
+                    "data": "192.168.1.1"
+                },
+                {
+                    // Using cloudflare or Quad9 is a reasonable option. Change this
+                    // to your own DNS servers is you have them. Another popular
+                    // choice is 8.8.8.8, owned by Google. Using third party DNS
+                    // service raises some privacy concerns.
+                    "name": "domain-name-servers",
+                    "data": "1.1.1.1,9.9.9.9"
+                }
+            ],
+
+            // Some devices should get a static address. Since the .100 - .199 range is dynamic,
+            // let's use the lower address space for this. There are many ways how reservation
+            // can be defined, but using MAC address (hw-address) is by far the most popular one.
+            // You can use client-id, duid and even custom defined flex-id that may use whatever
+            // parts of the packet you want to use as identifiers. Also, there are many more things
+            // you can specify in addition to just an IP address: extra options, next-server, hostname,
+            // assign device to client classes etc. See the Kea ARM, Section 8.3 for details.
+            // The reservations are subnet specific.
+            "reservations": [
+                {
+                    "hw-address": "1a:1b:1c:1d:1e:1f",
+                    "ip-address": "192.168.1.10"
+                },
+                {
+                    "client-id": "01:11:22:33:44:55:66",
+                    "ip-address": "192.168.1.11"
+                }
+            ]
+        }
+    ],
+
+    // Logging configuration starts here.
+    "loggers": [
+    {
+        // This section affects kea-dhcp4, which is the base logger for DHCPv4 component. It tells
+        // DHCPv4 server to write all log messages (on severity INFO or higher) to a file. The file
+        // will be rotated once it grows to 2MB and up to 4 files will be kept. The debuglevel
+        // (range 0 to 99) is used only when logging on DEBUG level.
+        "name": "kea-dhcp4",
+        "output_options": [
+            {
+                "output": "/var/log/kea-dhcp4.log",
+                "maxsize": 2048000,
+                "maxver": 4
+            }
+        ],
+        "severity": "INFO",
+        "debuglevel": 0
+    }
+  ]
+}
+}
diff --git a/src/fuzz/corp/dhcp-4-sample2.json b/src/fuzz/corp/dhcp-4-sample2.json
new file mode 100644 (file)
index 0000000..ee9b6f3
--- /dev/null
@@ -0,0 +1,223 @@
+// This is an example configuration of the Kea DHCPv4 server 2:
+//
+// - uses High Availability hooks library and Lease Commands hooks library
+//   to enable High Availability function for the DHCP server. This config
+//   file is for the primary (the active) server.
+// - uses memfile, which stores lease data in a local CSV file
+// - it assumes a single /24 addressing over a link that is directly reachable
+//   (no DHCP relays)
+// - there is a handful of IP reservations
+//
+// It is expected to run with a primary (the active) server, which has a very similar
+// configuration. The only difference is that "this-server-name" must be set to "server2" on the
+// other server. Also, the interface configuration depends on the network settings of the
+// particular machine.
+
+{
+
+"Dhcp4": {
+
+    // Add names of your network interfaces to listen on.
+    "interfaces-config": {
+        // The DHCPv4 server listens on this interface. When changing this to
+        // the actual name of your interface, make sure to also update the
+        // interface parameter in the subnet definition below.
+        "interfaces": [ "enp0s8" ]
+    },
+
+    // Control socket is required for communication between the Control
+    // Agent and the DHCP server. High Availability requires Control Agent
+    // to be running because lease updates are sent over the RESTful
+    // API between the HA peers.
+    "control-socket": {
+        "socket-type": "unix",
+        "socket-name": "/tmp/kea4-ctrl-socket"
+    },
+
+    // Use Memfile lease database backend to store leases in a CSV file.
+    // Depending on how Kea was compiled, it may also support SQL databases
+    // (MySQL and/or PostgreSQL) and even Cassandra. Those database backends
+    // require more parameters, like name, host and possibly user and password.
+    // There are dedicated examples for each backend. See Section 7.2.2 "Lease
+    // Storage" for details.
+    "lease-database": {
+        // Memfile is the simplest and easiest backend to use. It's an in-memory
+        // database with data being written to a CSV file. It is very similar to
+        // what ISC DHCP does.
+        "type": "memfile"
+    },
+
+    // Let's configure some global parameters. The home network is not very dynamic
+    // and there's no shortage of addresses, so no need to recycle aggresively.
+    "valid-lifetime": 43200, // leases will be valid for 12h
+    "renew-timer": 21600, // clients should renew every 6h
+    "rebind-timer": 32400, // clients should start looking for other servers after 9h
+
+    // Kea will clean up its database of expired leases once per hour. However, it
+    // will keep the leases in expired state for 2 days. This greatly increases the
+    // chances for returning devices to get the same address again. To guarantee that,
+    // use host reservation.
+    "expired-leases-processing": {
+        "reclaim-timer-wait-time": 3600,
+        "hold-reclaimed-time": 172800,
+        "max-reclaim-leases": 0,
+        "max-reclaim-time": 0
+    },
+
+    // HA requires two hooks libraries to be loaded: libdhcp_lease_cmds.so and
+    // libdhcp_ha.so. The former handles incoming lease updates from the HA peers.
+    // The latter implements high availability feature for Kea. Note the library name
+    // should be the same, but the path is OS specific.
+    "hooks-libraries": [
+        // The lease_cmds library must be loaded because HA makes use of it to
+        // deliver lease updates to the server as well as synchronize the
+        // lease database after failure.
+        {
+            "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_lease_cmds.so"
+        },
+
+        {
+            // The HA hooks library should be loaded.
+            "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_ha.so",
+            "parameters": {
+                // Each server should have the same HA configuration, except for the
+                // "this-server-name" parameter.
+                "high-availability": [ {
+                    // This parameter points to this server instance. The respective
+                    // HA peers must have this parameter set to their own names.
+                    "this-server-name": "server2",
+                    // The HA mode is set to hot-standby. In this mode, the active server handles
+                    // all the traffic. The standby takes over if the primary becomes unavailable.
+                    "mode": "hot-standby",
+                    // Heartbeat is to be sent every 10 seconds if no other control
+                    // commands are transmitted.
+                    "heartbeat-delay": 10000,
+                    // Maximum time for partner's response to a heartbeat, after which
+                    // failure detection is started. This is specified in milliseconds.
+                    // If we don't hear from the partner in 60 seconds, it's time to
+                    // start worrying.
+                    "max-response-delay": 60000,
+                    // The following parameters control how the server detects the
+                    // partner's failure. The ACK delay sets the threshold for the
+                    // 'secs' field of the received discovers. This is specified in
+                    // milliseconds.
+                    "max-ack-delay": 5000,
+                    // This specifies the number of clients which send messages to
+                    // the partner but appear to not receive any response.
+                    "max-unacked-clients": 5,
+                    // This specifies the maximum timeout (in milliseconds) for the server
+                    // to complete sync. If you have a large deployment (high tens or
+                    // hundreds of thousands of clients), you may need to increase it
+                    // further. The default value is 60000ms (60 seconds).
+                    "sync-timeout": 60000,
+                    "peers": [
+                        // This is the configuration of this server instance.
+                        {
+                            "name": "server1",
+                            // This specifies the URL of the partner's server instance. The
+                            // Control Agent must run along with partner DHCPv4 server
+                            // instance and the "http-host" and "http-port" must be
+                            // set to the corresponding values.
+                            "url": "http://192.168.1.2:8000/",
+                            // That server is primary. Our server must be
+                            // standby.
+                            "role": "primary"
+                        },
+                        // This is our (server2) configuration.
+                        {
+                            "name": "server2",
+                            // Specifies the URL on which the control agent for this server
+                            // can be reached. The Control Agent is required
+                            // to run on this machine with "http-host" and
+                            // "http-port" values set to the corresponding values.
+                            "url": "http://192.168.1.3:8000/",
+                            // Our server is standby. The other server is primary.
+                            "role": "standby"
+                        }
+                    ]
+                } ]
+            }
+        }
+    ],
+
+    // This example contains a single subnet declaration.
+    "subnet4": [
+        {
+            // Subnet prefix.
+            "subnet": "192.168.1.0/24",
+
+            // There are no relays in this network, so we need to tell Kea that this subnet
+            // is reachable directly via the ethX interface.
+            "interface": "enp0s8",
+
+            // Specify a dynamic address pool.
+            "pools": [
+                {
+                    "pool": "192.168.1.100-192.168.1.199"
+                }
+            ],
+
+            // These are options that are subnet specific. In most cases, you need to define at
+            // least routers option, as without this option your clients will not be able to reach
+            // their default gateway and will not have Internet connectivity. If you have many
+            // subnets and they share the same options (e.g. DNS servers typically is the same
+            // everywhere), you may define options at the global scope, so you don't repeat them
+            // for every network.
+            "option-data": [
+                {
+                    // For each IPv4 subnet you typically need to specify at least one router.
+                    "name": "routers",
+                    "data": "192.168.1.1"
+                },
+                {
+                    // Using cloudflare or Quad9 is a reasonable option. Change this
+                    // to your own DNS servers is you have them. Another popular
+                    // choice is 8.8.8.8, owned by Google. Using third party DNS
+                    // service raises some privacy concerns.
+                    "name": "domain-name-servers",
+                    "data": "1.1.1.1,9.9.9.9"
+                }
+            ],
+
+            // Some devices should get a static address. Since the .100 - .199 range is dynamic,
+            // let's use the lower address space for this. There are many ways how reservation
+            // can be defined, but using MAC address (hw-address) is by far the most popular one.
+            // You can use client-id, duid and even custom defined flex-id that may use whatever
+            // parts of the packet you want to use as identifiers. Also, there are many more things
+            // you can specify in addition to just an IP address: extra options, next-server, hostname,
+            // assign device to client classes etc. See the Kea ARM, Section 8.3 for details.
+            // The reservations are subnet specific.
+            "reservations": [
+                {
+                    "hw-address": "1a:1b:1c:1d:1e:1f",
+                    "ip-address": "192.168.1.10"
+                },
+                {
+                    "client-id": "01:11:22:33:44:55:66",
+                    "ip-address": "192.168.1.11"
+                }
+            ]
+        }
+    ],
+
+    // Logging configuration starts here.
+    "loggers": [
+    {
+        // This section affects kea-dhcp4, which is the base logger for DHCPv4 component. It tells
+        // DHCPv4 server to write all log messages (on severity INFO or higher) to a file. The file
+        // will be rotated once it grows to 2MB and up to 4 files will be kept. The debuglevel
+        // (range 0 to 99) is used only when logging on DEBUG level.
+        "name": "kea-dhcp4",
+        "output_options": [
+            {
+                "output": "/var/log/kea-dhcp4.log",
+                "maxsize": 2048000,
+                "maxver": 4
+            }
+        ],
+        "severity": "INFO",
+        "debuglevel": 0
+    }
+  ]
+}
+}
diff --git a/src/fuzz/corp/dhcp-6-sample1.json b/src/fuzz/corp/dhcp-6-sample1.json
new file mode 100644 (file)
index 0000000..ed3288d
--- /dev/null
@@ -0,0 +1,38 @@
+{
+# DHCPv6 configuration starts on the next line
+"Dhcp6": {
+
+# First we set up global values
+    "valid-lifetime": 4000,
+    "renew-timer": 1000,
+    "rebind-timer": 2000,
+    "preferred-lifetime": 3000,
+
+# Next we set up the interfaces to be used by the server.
+    "interfaces-config": {
+        "interfaces": [ "eth0" ]
+    },
+
+# And we specify the type of lease database
+    "lease-database": {
+        "type": "memfile",
+        "persist": true,
+        "name": "/var/lib/kea/dhcp6.leases"
+    },
+
+# Finally, we list the subnets from which we will be leasing addresses.
+    "subnet6": [
+        {
+            "id": 1,
+            "subnet": "2001:db8:1::/64",
+            "pools": [
+                {
+                    "pool": "2001:db8:1::1-2001:db8:1::ffff"
+                }
+             ]
+        }
+    ]
+# DHCPv6 configuration ends with the next line
+}
+
+}
diff --git a/src/fuzz/corp/dhcp-6-sample2.json b/src/fuzz/corp/dhcp-6-sample2.json
new file mode 100644 (file)
index 0000000..4e01e22
--- /dev/null
@@ -0,0 +1,68 @@
+{
+    "Dhcp6": {
+        "client-classes": [
+            {
+                "name": "reserved_class"
+            },
+            {
+                "name": "unreserved_class",
+                "test": "not member('reserved_class')"
+            }
+        ],
+        "reservations": [
+            {
+                "hw-address": "aa:bb:cc:dd:ee:fe",
+                "client-classes": [ "reserved_class" ]
+            }
+        ],
+        "reservations-global": true,
+        "reservations-in-subnet": false,
+        "shared-networks": [
+            {
+                "name": "net",
+                "subnet6": [
+                    {
+                        "id": 1,
+                        "subnet": "2001:db8:1::/64",
+                        "pools": [
+                            {
+                                "pool": "2001:db8:1::10 - 2001:db8:1::20",
+                                "client-classes": [ "unreserved_class" ]
+                            },
+                            {
+                                "pool": "2001:db8:1::30 - 2001:db8:1::40",
+                                "client-classes": [ "unreserved_class" ]
+                            }
+                        ]
+                    },
+                    {
+                        "id": 2,
+                        "subnet": "2001:db8:2::/64",
+                        "pools": [
+                            {
+                                "pool": "2001:db8:2::10 - 2001:db8:2::20",
+                                "client-classes": [ "reserved_class" ]
+                            },
+                            {
+                                "pool": "2001:db8:2::30 - 2001:db8:2::40",
+                                "client-classes": [ "reserved_class" ]
+                            }
+                        ]
+                    },
+                    {
+                        "id": 3,
+                        "subnet": "2001:db8:3::/64",
+                        "pools": [
+                            {
+                                "pool": "2001:db8:3::10 - 2001:db8:3::20"
+                            },
+                            {
+                                "pool": "2001:db8:3::30 - 2001:db8:3::40"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+}
diff --git a/src/fuzz/corp/dhcp-6-sample3.json b/src/fuzz/corp/dhcp-6-sample3.json
new file mode 100644 (file)
index 0000000..e9a3e10
--- /dev/null
@@ -0,0 +1,39 @@
+"Dhcp6": {
+    "client-classes": [
+        {
+            "name": "blocked",
+            "option-data": [
+                {
+                    "name": "dns-servers",
+                    "data": "2001:db8::2"
+                }
+            ]
+        }
+    ],
+    "reservations": [
+        // Clients on this list will be added to the KNOWN class. Some
+        // will also be added to the blocked class.
+        { "duid": "01:02:03:04:05:0A:0B:0C:0D:0E",
+          "client-classes": [ "blocked" ] },
+        { "duid": "02:03:04:05:0A:0B:0C:0D:0E:0F" }
+    ],
+    "reservations-in-subnet": true,
+
+    "subnet6": [
+        {
+            "id": 1,
+            "subnet": "2001:db8:1::/48",
+            "pools": [
+                {
+                    "pool": "2001:db8:1:1::/64"
+                }
+            ],
+            "option-data": [
+                {
+                    "name": "dns-servers",
+                    "data": "2001:db8::1"
+                }
+            ]
+        }
+    ]
+}
diff --git a/src/fuzz/corp/dhcp-6-sample4.json b/src/fuzz/corp/dhcp-6-sample4.json
new file mode 100644 (file)
index 0000000..6a68b73
--- /dev/null
@@ -0,0 +1,35 @@
+{
+  "Dhcp6": {
+    "valid-lifetime": 4000,
+    "renew-timer": 1000,
+    "rebind-timer": 2000,
+    "preferred-lifetime": 3000,
+    "interfaces-config": {
+      "interfaces": ["eth0"]
+    },
+    "lease-database": {
+      "type": "memfile",
+      "persist": true,
+      "name": "/var/lib/kea/dhcp6.leases"
+    },
+    "option-data": [
+      {
+        "space": "dhcp6",
+        "name": "dns-servers",
+        "data": "2001:db8::1, 2001:db8::2"
+      }
+    ],
+    "subnet6": [
+      {
+        "subnet": "2001:db8:1::/64",
+        "id": 1024,
+        "pools": [
+          {
+            "start": "2001:db8:1::100",
+            "end": "2001:db8:1::200"
+          }
+        ]
+      }
+    ]
+  }
+}
diff --git a/src/fuzz/corp/leases.csv b/src/fuzz/corp/leases.csv
new file mode 100644 (file)
index 0000000..ddd05f2
--- /dev/null
@@ -0,0 +1,3 @@
+lease_id,ip_address,subnet_id,state,valid_since,valid_until,mac_address,client_id,hostname
+1,192.168.1.100,1,0,2023-10-27 10:00:00,2023-10-27 11:00:00,00:11:22:33:44:55,None,device1
+2,192.168.1.101,1,0,2023-10-27 10:05:00,2023-10-27 11:05:00,AA:BB:CC:DD:EE:FF,None,device2
\ No newline at end of file
diff --git a/src/fuzz/corp/sample.csv b/src/fuzz/corp/sample.csv
new file mode 100644 (file)
index 0000000..05f6c71
--- /dev/null
@@ -0,0 +1,51 @@
+EMPLOYEE_ID,FIRST_NAME,LAST_NAME,EMAIL,PHONE_NUMBER,HIRE_DATE,JOB_ID,SALARY,COMMISSION_PCT,MANAGER_ID,DEPARTMENT_ID
+198,Donald,OConnell,DOCONNEL,650.507.9833,21-JUN-07,SH_CLERK,2600, - ,124,50
+199,Douglas,Grant,DGRANT,650.507.9844,13-JAN-08,SH_CLERK,2600, - ,124,50
+200,Jennifer,Whalen,JWHALEN,515.123.4444,17-SEP-03,AD_ASST,4400, - ,101,10
+201,Michael,Hartstein,MHARTSTE,515.123.5555,17-FEB-04,MK_MAN,13000, - ,100,20
+202,Pat,Fay,PFAY,603.123.6666,17-AUG-05,MK_REP,6000, - ,201,20
+203,Susan,Mavris,SMAVRIS,515.123.7777,07-JUN-02,HR_REP,6500, - ,101,40
+204,Hermann,Baer,HBAER,515.123.8888,07-JUN-02,PR_REP,10000, - ,101,70
+205,Shelley,Higgins,SHIGGINS,515.123.8080,07-JUN-02,AC_MGR,12008, - ,101,110
+206,William,Gietz,WGIETZ,515.123.8181,07-JUN-02,AC_ACCOUNT,8300, - ,205,110
+100,Steven,King,SKING,515.123.4567,17-JUN-03,AD_PRES,24000, - , - ,90
+101,Neena,Kochhar,NKOCHHAR,515.123.4568,21-SEP-05,AD_VP,17000, - ,100,90
+102,Lex,De Haan,LDEHAAN,515.123.4569,13-JAN-01,AD_VP,17000, - ,100,90
+103,Alexander,Hunold,AHUNOLD,590.423.4567,03-JAN-06,IT_PROG,9000, - ,102,60
+104,Bruce,Ernst,BERNST,590.423.4568,21-MAY-07,IT_PROG,6000, - ,103,60
+105,David,Austin,DAUSTIN,590.423.4569,25-JUN-05,IT_PROG,4800, - ,103,60
+106,Valli,Pataballa,VPATABAL,590.423.4560,05-FEB-06,IT_PROG,4800, - ,103,60
+107,Diana,Lorentz,DLORENTZ,590.423.5567,07-FEB-07,IT_PROG,4200, - ,103,60
+108,Nancy,Greenberg,NGREENBE,515.124.4569,17-AUG-02,FI_MGR,12008, - ,101,100
+109,Daniel,Faviet,DFAVIET,515.124.4169,16-AUG-02,FI_ACCOUNT,9000, - ,108,100
+110,John,Chen,JCHEN,515.124.4269,28-SEP-05,FI_ACCOUNT,8200, - ,108,100
+111,Ismael,Sciarra,ISCIARRA,515.124.4369,30-SEP-05,FI_ACCOUNT,7700, - ,108,100
+112,Jose Manuel,Urman,JMURMAN,515.124.4469,07-MAR-06,FI_ACCOUNT,7800, - ,108,100
+113,Luis,Popp,LPOPP,515.124.4567,07-DEC-07,FI_ACCOUNT,6900, - ,108,100
+114,Den,Raphaely,DRAPHEAL,515.127.4561,07-DEC-02,PU_MAN,11000, - ,100,30
+115,Alexander,Khoo,AKHOO,515.127.4562,18-MAY-03,PU_CLERK,3100, - ,114,30
+116,Shelli,Baida,SBAIDA,515.127.4563,24-DEC-05,PU_CLERK,2900, - ,114,30
+117,Sigal,Tobias,STOBIAS,515.127.4564,24-JUL-05,PU_CLERK,2800, - ,114,30
+118,Guy,Himuro,GHIMURO,515.127.4565,15-NOV-06,PU_CLERK,2600, - ,114,30
+119,Karen,Colmenares,KCOLMENA,515.127.4566,10-AUG-07,PU_CLERK,2500, - ,114,30
+120,Matthew,Weiss,MWEISS,650.123.1234,18-JUL-04,ST_MAN,8000, - ,100,50
+121,Adam,Fripp,AFRIPP,650.123.2234,10-APR-05,ST_MAN,8200, - ,100,50
+122,Payam,Kaufling,PKAUFLIN,650.123.3234,01-MAY-03,ST_MAN,7900, - ,100,50
+123,Shanta,Vollman,SVOLLMAN,650.123.4234,10-OCT-05,ST_MAN,6500, - ,100,50
+124,Kevin,Mourgos,KMOURGOS,650.123.5234,16-NOV-07,ST_MAN,5800, - ,100,50
+125,Julia,Nayer,JNAYER,650.124.1214,16-JUL-05,ST_CLERK,3200, - ,120,50
+126,Irene,Mikkilineni,IMIKKILI,650.124.1224,28-SEP-06,ST_CLERK,2700, - ,120,50
+127,James,Landry,JLANDRY,650.124.1334,14-JAN-07,ST_CLERK,2400, - ,120,50
+128,Steven,Markle,SMARKLE,650.124.1434,08-MAR-08,ST_CLERK,2200, - ,120,50
+129,Laura,Bissot,LBISSOT,650.124.5234,20-AUG-05,ST_CLERK,3300, - ,121,50
+130,Mozhe,Atkinson,MATKINSO,650.124.6234,30-OCT-05,ST_CLERK,2800, - ,121,50
+131,James,Marlow,JAMRLOW,650.124.7234,16-FEB-05,ST_CLERK,2500, - ,121,50
+132,TJ,Olson,TJOLSON,650.124.8234,10-APR-07,ST_CLERK,2100, - ,121,50
+133,Jason,Mallin,JMALLIN,650.127.1934,14-JUN-04,ST_CLERK,3300, - ,122,50
+134,Michael,Rogers,MROGERS,650.127.1834,26-AUG-06,ST_CLERK,2900, - ,122,50
+135,Ki,Gee,KGEE,650.127.1734,12-DEC-07,ST_CLERK,2400, - ,122,50
+136,Hazel,Philtanker,HPHILTAN,650.127.1634,06-FEB-08,ST_CLERK,2200, - ,122,50
+137,Renske,Ladwig,RLADWIG,650.121.1234,14-JUL-03,ST_CLERK,3600, - ,123,50
+138,Stephen,Stiles,SSTILES,650.121.2034,26-OCT-05,ST_CLERK,3200, - ,123,50
+139,John,Seo,JSEO,650.121.2019,12-FEB-06,ST_CLERK,2700, - ,123,50
+140,Joshua,Patel,JPATEL,650.121.1834,06-APR-06,ST_CLERK,2500, - ,123,50
\ No newline at end of file
diff --git a/src/fuzz/corp/sample2.json b/src/fuzz/corp/sample2.json
new file mode 100644 (file)
index 0000000..9eea6ad
--- /dev/null
@@ -0,0 +1,88 @@
+{"web-app": {
+  "servlet": [   
+    {
+      "servlet-name": "cofaxCDS",
+      "servlet-class": "org.cofax.cds.CDSServlet",
+      "init-param": {
+        "configGlossary:installationAt": "Philadelphia, PA",
+        "configGlossary:adminEmail": "ksm@pobox.com",
+        "configGlossary:poweredBy": "Cofax",
+        "configGlossary:poweredByIcon": "/images/cofax.gif",
+        "configGlossary:staticPath": "/content/static",
+        "templateProcessorClass": "org.cofax.WysiwygTemplate",
+        "templateLoaderClass": "org.cofax.FilesTemplateLoader",
+        "templatePath": "templates",
+        "templateOverridePath": "",
+        "defaultListTemplate": "listTemplate.htm",
+        "defaultFileTemplate": "articleTemplate.htm",
+        "useJSP": false,
+        "jspListTemplate": "listTemplate.jsp",
+        "jspFileTemplate": "articleTemplate.jsp",
+        "cachePackageTagsTrack": 200,
+        "cachePackageTagsStore": 200,
+        "cachePackageTagsRefresh": 60,
+        "cacheTemplatesTrack": 100,
+        "cacheTemplatesStore": 50,
+        "cacheTemplatesRefresh": 15,
+        "cachePagesTrack": 200,
+        "cachePagesStore": 100,
+        "cachePagesRefresh": 10,
+        "cachePagesDirtyRead": 10,
+        "searchEngineListTemplate": "forSearchEnginesList.htm",
+        "searchEngineFileTemplate": "forSearchEngines.htm",
+        "searchEngineRobotsDb": "WEB-INF/robots.db",
+        "useDataStore": true,
+        "dataStoreClass": "org.cofax.SqlDataStore",
+        "redirectionClass": "org.cofax.SqlRedirection",
+        "dataStoreName": "cofax",
+        "dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver",
+        "dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon",
+        "dataStoreUser": "sa",
+        "dataStorePassword": "dataStoreTestQuery",
+        "dataStoreTestQuery": "SET NOCOUNT ON;select test='test';",
+        "dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log",
+        "dataStoreInitConns": 10,
+        "dataStoreMaxConns": 100,
+        "dataStoreConnUsageLimit": 100,
+        "dataStoreLogLevel": "debug",
+        "maxUrlLength": 500}},
+    {
+      "servlet-name": "cofaxEmail",
+      "servlet-class": "org.cofax.cds.EmailServlet",
+      "init-param": {
+      "mailHost": "mail1",
+      "mailHostOverride": "mail2"}},
+    {
+      "servlet-name": "cofaxAdmin",
+      "servlet-class": "org.cofax.cds.AdminServlet"},
+    {
+      "servlet-name": "fileServlet",
+      "servlet-class": "org.cofax.cds.FileServlet"},
+    {
+      "servlet-name": "cofaxTools",
+      "servlet-class": "org.cofax.cms.CofaxToolsServlet",
+      "init-param": {
+        "templatePath": "toolstemplates/",
+        "log": 1,
+        "logLocation": "/usr/local/tomcat/logs/CofaxTools.log",
+        "logMaxSize": "",
+        "dataLog": 1,
+        "dataLogLocation": "/usr/local/tomcat/logs/dataLog.log",
+        "dataLogMaxSize": "",
+        "removePageCache": "/content/admin/remove?cache=pages&id=",
+        "removeTemplateCache": "/content/admin/remove?cache=templates&id=",
+        "fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder",
+        "lookInContext": 1,
+        "adminGroupID": 4,
+        "betaServer": true}}],
+  "servlet-mapping": {
+    "cofaxCDS": "/",
+    "cofaxEmail": "/cofaxutil/aemail/*",
+    "cofaxAdmin": "/admin/*",
+    "fileServlet": "/static/*",
+    "cofaxTools": "/tools/*"},
+  "taglib": {
+    "taglib-uri": "cofax.tld",
+    "taglib-location": "/WEB-INF/tlds/cofax.tld"}}}
diff --git a/src/fuzz/corp/sample3.json b/src/fuzz/corp/sample3.json
new file mode 100644 (file)
index 0000000..19498e4
--- /dev/null
@@ -0,0 +1,26 @@
+[
+  {
+    "name": "Meowsy",
+    "species" : "cat",
+    "foods": {
+      "likes": ["tuna", "catnip"],
+      "dislikes": ["ham", "zucchini"]
+    }
+  },
+  {
+    "name": "Barky",
+    "species" : "dog",
+    "foods": {
+      "likes": ["bones", "carrots"],
+      "dislikes": ["tuna"]
+    }
+  },
+  {
+    "name": "Purrpaws",
+    "species" : "cat",
+    "foods": {
+      "likes": ["mice"],
+      "dislikes": ["cookies"]
+    }
+  }
+]
\ No newline at end of file
diff --git a/src/fuzz/fuzz_agent.cc b/src/fuzz/fuzz_agent.cc
new file mode 100644 (file)
index 0000000..f46c767
--- /dev/null
@@ -0,0 +1,68 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <agent/parser_context.h>
+#include <agent/simple_parser.h>
+#include <agent/ca_cfg_mgr.h>
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+
+#include <string>
+#include <memory>
+
+using namespace isc::agent;
+using namespace isc::data;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    FuzzedDataProvider fdp(data, size);
+    bool checkOnly = fdp.ConsumeBool();
+
+    AgentSimpleParser simpleParser;
+    CtrlAgentCfgContextPtr ctxPtr(new CtrlAgentCfgContext());
+    ParserContext ctx;
+    ElementPtr elem;
+
+    // Generate random parsing mode
+    const uint8_t mode = fdp.ConsumeIntegralInRange<uint8_t>(0, 2);
+    ParserContext::ParserType type = ParserContext::PARSER_JSON;
+    if (mode == 1) {
+        type = ParserContext::PARSER_AGENT;
+    } else if (mode == 2) {
+        type = ParserContext::PARSER_SUB_AGENT;
+    }
+
+    const std::string payload = fdp.ConsumeRemainingBytesAsString();
+
+    // Target context parseString
+    try {
+        ctx.parseString(payload, type);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    }
+
+    // Parse payload to JSON
+    try {
+        elem = Element::fromJSON(payload);
+    } catch (...) {
+        // If failed to parse the payload, early exit
+        return 0;
+    }
+
+    // Target SimpleParser
+    try {
+        AgentSimpleParser::setAllDefaults(elem);
+        simpleParser.checkTlsSetup(elem);
+        simpleParser.parse(ctxPtr, elem, checkOnly);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    }
+
+    return 0;
+}
diff --git a/src/fuzz/fuzz_agent.dict b/src/fuzz/fuzz_agent.dict
new file mode 100644 (file)
index 0000000..2daa66a
--- /dev/null
@@ -0,0 +1,156 @@
+"{"
+"}"
+"["
+"]"
+":"
+","
+"\""
+"\"\""
+"null"
+"true"
+"false"
+" "
+"\\n"
+"\\r\\n"
+"\"Control-agent\""
+"\"control-agent\""
+"\"http-host\""
+"\"http-port\""
+"\"http-ciphers\""
+"\"http-headers\""
+"\"http-max-body-size\""
+"\"http-status\""
+"\"http-response-headers\""
+"\"localhost\""
+"\"127.0.0.1\""
+"\"::1\""
+"\"0.0.0.0\""
+"0"
+"53"
+"67"
+"80"
+"443"
+"8080"
+"65535"
+"\"tls\""
+"\"enable-tls\""
+"\"cert-file\""
+"\"key-file\""
+"\"ca-file\""
+"\"trust-anchor\""
+"\"verify-peer\""
+"\"cipher-list\""
+"\"cipher-suite\""
+"\"protocols\""
+"\"min-tls-version\""
+"\"max-tls-version\""
+"\"/etc/kea/ctrl-agent.crt\""
+"\"/etc/kea/ctrl-agent.key\""
+"\"/etc/ssl/certs/ca-certificates.crt\""
+"\"/tmp/kea.crt\""
+"\"/tmp/kea.key\""
+"\"REQUIRED\""
+"\"OPTIONAL\""
+"\"NONE\""
+"\"TLSv1.2\""
+"\"TLSv1.3\""
+"\"control-sockets\""
+"\"dhcp4\""
+"\"dhcp6\""
+"\"d2\""
+"\"netconf\""
+"\"socket-type\""
+"\"socket-name\""
+"\"socket-url\""
+"\"unix\""
+"\"http\""
+"\"/tmp/kea4-ctrl.sock\""
+"\"/tmp/kea6-ctrl.sock\""
+"\"/tmp/kea-d2-ctrl.sock\""
+"\"http://127.0.0.1:8000/\""
+"\"http://[::1]:8000/\""
+"\"commands\""
+"\"command\""
+"\"arguments\""
+"\"access-list\""
+"\"whitelist\""
+"\"blacklist\""
+"\"allowed-clients\""
+"\"paths\""
+"\"path\""
+"\"GET\""
+"\"POST\""
+"\"Logging\""
+"\"name\""
+"\"output_options\""
+"\"output\""
+"\"flush\""
+"\"maxsize\""
+"\"maxver\""
+"\"file-name\""
+"\"severity\""
+"\"debuglevel\""
+"\"INFO\""
+"\"WARN\""
+"\"ERROR\""
+"\"DEBUG\""
+"\"kea-ctrl-agent\""
+"\"stdout\""
+"\"stderr\""
+"\"/var/log/kea-ctrl-agent.log\""
+"\"interfaces\""
+"\"hooks-libraries\""
+"\"library\""
+"\"parameters\""
+"\"user-context\""
+"\"comment\""
+"\"config-control\""
+"\"config-databases\""
+"\"database\""
+"\"type\""
+"\"host\""
+"\"port\""
+"\"user\""
+"\"password\""
+"\"name\""
+"\"Control-agent\":{"
+"\"http-host\":\""
+"\"http-port\":"
+"\"enable-tls\":"
+"\"cert-file\":\""
+"\"key-file\":\""
+"\"ca-file\":\""
+"\"verify-peer\":"
+"\"control-sockets\":{"
+"\"dhcp4\":{"
+"\"dhcp6\":{"
+"\"d2\":{"
+"\"netconf\":{"
+"\"socket-type\":\""
+"\"socket-name\":\""
+"\"Logging\":[{"
+"\"output_options\":[{"
+"\"severity\":\""
+"\"debuglevel\":"
+"}]"
+"}]"
+"}"
+"\"\""
+"\"   \""
+"\"\\u0000\""
+"\"\\uD800\""
+"\"\\x\""
+"-1"
+"2147483647"
+"4294967295"
+"9223372036854775807"
+"3.14159"
+"\"[],{}:\""
+"\":{"
+"\": ["
+"]}"
+"}]"
+"},"
+"],"
+"\" : "
+" , "
diff --git a/src/fuzz/fuzz_cc.cc b/src/fuzz/fuzz_cc.cc
new file mode 100644 (file)
index 0000000..61e6054
--- /dev/null
@@ -0,0 +1,100 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+#include <cc/json_feed.h>
+#include <cc/simple_parser.h>
+#include <asiolink/io_address.h>
+
+#include <string>
+#include <vector>
+
+using namespace isc;
+using namespace isc::data;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    FuzzedDataProvider fdp(data, size);
+
+    ElementPtr elem;
+
+    std::string val1 = fdp.ConsumeRandomLengthString(8);
+    std::string val2 = fdp.ConsumeRandomLengthString(8);
+    Element::types type1 = static_cast<Element::types>(fdp.ConsumeIntegralInRange<int>(0, 8));
+    Element::types type2 = static_cast<Element::types>(fdp.ConsumeIntegralInRange<int>(0, 8));
+
+    SimpleRequiredKeywords required;
+    required.push_back(val1);
+    required.push_back(val2);
+
+    SimpleKeywords keywords;
+    keywords[val1] = type1;
+    keywords[val2] = type2;
+
+    ParamsList params;
+    params.push_back(val1);
+    params.push_back(val2);
+
+    const std::string payload = fdp.ConsumeRemainingBytesAsString();
+
+    // Target JSONFeed with random data
+    try {
+        config::JSONFeed feed;
+        feed.initModel();
+        feed.postBuffer(payload.c_str(), payload.length());
+        feed.poll();
+        feed.needData();
+        feed.feedOk();
+        feed.getProcessedText();
+        feed.toElement();
+        feed.getErrorMessage();
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    }
+
+    // Try parse payload to Element pointer
+    try {
+        elem = Element::fromJSON(payload);
+    } catch (...) {
+        // Early exit for invalid json
+        return 0;
+    }
+
+    // Target parseIntTriplet
+    try {
+        SimpleParser parser;
+        parser.parseIntTriplet(elem, val1);
+        parser.parseIntTriplet(elem, val2);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    }
+
+    // Target checkRequired
+    try {
+        SimpleParser::checkRequired(required, elem);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    }
+
+    // Target checkKeywords
+    try {
+        SimpleParser::checkKeywords(keywords, elem);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    }
+
+    // Target deriveParams
+    try {
+        SimpleParser::deriveParams(elem, Element::createMap(), params);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    }
+
+    return 0;
+}
diff --git a/src/fuzz/fuzz_crypto.cc b/src/fuzz/fuzz_crypto.cc
new file mode 100644 (file)
index 0000000..b9dc736
--- /dev/null
@@ -0,0 +1,65 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <dns/tsig.h>
+#include <cryptolink/cryptolink.h>
+#include <cryptolink/crypto_hmac.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <string>
+#include <memory>
+
+using namespace isc::dns;
+using namespace isc::cryptolink;
+using namespace isc::util;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    FuzzedDataProvider fdp(data, size);
+
+    std::string secret = fdp.ConsumeRandomLengthString(64);
+    std::string tsig_data = fdp.ConsumeRandomLengthString(1024);
+
+    // TSIGContext
+    try {
+        // Create TSIGContext
+        TSIGKey tsigkey("fuzz_key:" + secret);
+        TSIGContext ctx(tsigkey);
+
+        // TSIGContext sign
+        uint16_t qid = fdp.ConsumeIntegral<uint16_t>();
+        ctx.sign(qid, tsig_data.data(), tsig_data.size());
+
+        // Create TSIGRecord
+        Name name("fuzz_record");
+        TSIGRecord record(name, rdata::any::TSIG(fdp.ConsumeRandomLengthString(1024)));
+
+        ctx.verify(&record, tsig_data.data(), tsig_data.size());
+    } catch (const isc::Exception&) {
+        // Slient ezceptions
+    }
+
+    // HMAC
+    try {
+        // HMAC Sign
+        OutputBuffer hmac(256);
+        signHMAC(tsig_data.data(), tsig_data.size(), secret.data(), secret.size(),
+            static_cast<HashAlgorithm>(fdp.ConsumeIntegralInRange<int>(0, 6)), hmac);
+
+        // HMAC Verify
+        std::string sig = fdp.ConsumeRandomLengthString(256);
+        verifyHMAC(tsig_data.data(), tsig_data.size(), secret.data(), secret.size(),
+            static_cast<HashAlgorithm>(fdp.ConsumeIntegralInRange<int>(0, 6)),
+            sig.data(), sig.size());
+    } catch (const isc::Exception&) {
+        // Slient ezceptions
+    }
+
+    return 0;
+}
diff --git a/src/fuzz/fuzz_cryptolink.cc b/src/fuzz/fuzz_cryptolink.cc
new file mode 100644 (file)
index 0000000..700dadf
--- /dev/null
@@ -0,0 +1,264 @@
+// Copyright (C) 2025 Ada Logics Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <exceptions/exceptions.h>
+#include <cryptolink/cryptolink.h>
+#include <cryptolink/crypto_hash.h>
+#include <cryptolink/crypto_hmac.h>
+#include <cryptolink/crypto_rng.h>
+
+#include <string>
+#include <vector>
+#include <cstddef>
+
+using namespace isc::cryptolink;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    if (size < 3) {
+        return 0;
+    }
+
+    FuzzedDataProvider fdp(data, size);
+    
+    // Choose which crypto operation to test
+    uint8_t path = fdp.ConsumeIntegralInRange<uint8_t>(0, 9);
+    
+    // Pick a hash algorithm
+    HashAlgorithm hash_alg = fdp.PickValueInArray({
+        HashAlgorithm::MD5,
+        HashAlgorithm::SHA1,
+        HashAlgorithm::SHA224,
+        HashAlgorithm::SHA256,
+        HashAlgorithm::SHA384,
+        HashAlgorithm::SHA512
+    });
+    
+    try {
+        switch (path) {
+            case 0: {
+                // Test Hash creation and update
+                Hash* hash = CryptoLink::getCryptoLink().createHash(hash_alg);
+                if (hash) {
+                    std::vector<uint8_t> input_data = fdp.ConsumeRemainingBytes<uint8_t>();
+                    if (!input_data.empty()) {
+                        hash->update(input_data.data(), input_data.size());
+                    }
+                    std::vector<uint8_t> digest = hash->final(hash->getOutputLength());
+                    delete hash;
+                }
+                break;
+            }
+            
+            case 1: {
+                // Test Hash with multiple updates
+                Hash* hash = CryptoLink::getCryptoLink().createHash(hash_alg);
+                if (hash) {
+                    size_t num_updates = fdp.ConsumeIntegralInRange<size_t>(1, 10);
+                    for (size_t i = 0; i < num_updates && fdp.remaining_bytes() > 0; i++) {
+                        size_t chunk_size = fdp.ConsumeIntegralInRange<size_t>(0, fdp.remaining_bytes());
+                        std::vector<uint8_t> chunk = fdp.ConsumeBytes<uint8_t>(chunk_size);
+                        if (!chunk.empty()) {
+                            hash->update(chunk.data(), chunk.size());
+                        }
+                    }
+                    std::vector<uint8_t> digest = hash->final(hash->getOutputLength());
+                    delete hash;
+                }
+                break;
+            }
+            
+            case 2: {
+                // Test Hash with OutputBuffer
+                Hash* hash = CryptoLink::getCryptoLink().createHash(hash_alg);
+                if (hash) {
+                    std::vector<uint8_t> input_data = fdp.ConsumeBytes<uint8_t>(
+                        fdp.ConsumeIntegralInRange<size_t>(0, size)
+                    );
+                    if (!input_data.empty()) {
+                        hash->update(input_data.data(), input_data.size());
+                    }
+                    isc::util::OutputBuffer result(hash->getOutputLength());
+                    size_t len = fdp.ConsumeIntegralInRange<size_t>(0, hash->getOutputLength() * 2);
+                    hash->final(result, len);
+                    delete hash;
+                }
+                break;
+            }
+            
+            case 3: {
+                // Test Hash with void* result
+                Hash* hash = CryptoLink::getCryptoLink().createHash(hash_alg);
+                if (hash) {
+                    std::vector<uint8_t> input_data = fdp.ConsumeBytes<uint8_t>(
+                        fdp.ConsumeIntegralInRange<size_t>(0, size)
+                    );
+                    if (!input_data.empty()) {
+                        hash->update(input_data.data(), input_data.size());
+                    }
+                    std::vector<uint8_t> result(hash->getOutputLength() * 2);
+                    size_t len = fdp.ConsumeIntegralInRange<size_t>(0, result.size());
+                    hash->final(result.data(), len);
+                    delete hash;
+                }
+                break;
+            }
+            
+            case 4: {
+                // Test HMAC creation and signing
+                size_t secret_len = fdp.ConsumeIntegralInRange<size_t>(1, 256);
+                std::vector<uint8_t> secret = fdp.ConsumeBytes<uint8_t>(secret_len);
+                if (secret.empty()) {
+                    secret.push_back(0);  // Ensure non-empty secret
+                }
+                
+                HMAC* hmac = CryptoLink::getCryptoLink().createHMAC(
+                    secret.data(), secret.size(), hash_alg
+                );
+                if (hmac) {
+                    std::vector<uint8_t> input_data = fdp.ConsumeRemainingBytes<uint8_t>();
+                    if (!input_data.empty()) {
+                        hmac->update(input_data.data(), input_data.size());
+                    }
+                    std::vector<uint8_t> signature = hmac->sign(hmac->getOutputLength());
+                    delete hmac;
+                }
+                break;
+            }
+            
+            case 5: {
+                // Test HMAC with multiple updates
+                size_t secret_len = fdp.ConsumeIntegralInRange<size_t>(1, 256);
+                std::vector<uint8_t> secret = fdp.ConsumeBytes<uint8_t>(secret_len);
+                if (secret.empty()) {
+                    secret.push_back(0);
+                }
+                
+                HMAC* hmac = CryptoLink::getCryptoLink().createHMAC(
+                    secret.data(), secret.size(), hash_alg
+                );
+                if (hmac) {
+                    size_t num_updates = fdp.ConsumeIntegralInRange<size_t>(1, 10);
+                    for (size_t i = 0; i < num_updates && fdp.remaining_bytes() > 0; i++) {
+                        size_t chunk_size = fdp.ConsumeIntegralInRange<size_t>(0, fdp.remaining_bytes());
+                        std::vector<uint8_t> chunk = fdp.ConsumeBytes<uint8_t>(chunk_size);
+                        if (!chunk.empty()) {
+                            hmac->update(chunk.data(), chunk.size());
+                        }
+                    }
+                    std::vector<uint8_t> signature = hmac->sign(hmac->getOutputLength());
+                    delete hmac;
+                }
+                break;
+            }
+            
+            case 6: {
+                // Test HMAC with OutputBuffer
+                size_t secret_len = fdp.ConsumeIntegralInRange<size_t>(1, 256);
+                std::vector<uint8_t> secret = fdp.ConsumeBytes<uint8_t>(secret_len);
+                if (secret.empty()) {
+                    secret.push_back(0);
+                }
+                
+                HMAC* hmac = CryptoLink::getCryptoLink().createHMAC(
+                    secret.data(), secret.size(), hash_alg
+                );
+                if (hmac) {
+                    std::vector<uint8_t> input_data = fdp.ConsumeBytes<uint8_t>(
+                        fdp.ConsumeIntegralInRange<size_t>(0, size)
+                    );
+                    if (!input_data.empty()) {
+                        hmac->update(input_data.data(), input_data.size());
+                    }
+                    isc::util::OutputBuffer result(hmac->getOutputLength());
+                    size_t len = fdp.ConsumeIntegralInRange<size_t>(0, hmac->getOutputLength() * 2);
+                    hmac->sign(result, len);
+                    delete hmac;
+                }
+                break;
+            }
+            
+            case 7: {
+                // Test HMAC verification
+                size_t secret_len = fdp.ConsumeIntegralInRange<size_t>(1, 256);
+                std::vector<uint8_t> secret = fdp.ConsumeBytes<uint8_t>(secret_len);
+                if (secret.empty()) {
+                    secret.push_back(0);
+                }
+                
+                HMAC* hmac = CryptoLink::getCryptoLink().createHMAC(
+                    secret.data(), secret.size(), hash_alg
+                );
+                if (hmac) {
+                    std::vector<uint8_t> input_data = fdp.ConsumeBytes<uint8_t>(
+                        fdp.ConsumeIntegralInRange<size_t>(0, size)
+                    );
+                    if (!input_data.empty()) {
+                        hmac->update(input_data.data(), input_data.size());
+                    }
+                    
+                    // Generate signature
+                    std::vector<uint8_t> signature = hmac->sign(hmac->getOutputLength());
+                    
+                    // Verify with same data (should succeed)
+                    HMAC* verify_hmac = CryptoLink::getCryptoLink().createHMAC(
+                        secret.data(), secret.size(), hash_alg
+                    );
+                    if (verify_hmac) {
+                        if (!input_data.empty()) {
+                            verify_hmac->update(input_data.data(), input_data.size());
+                        }
+                        verify_hmac->verify(signature.data(), signature.size());
+                        delete verify_hmac;
+                    }
+                    delete hmac;
+                }
+                break;
+            }
+            
+            case 8: {
+                // Test HMAC with long secret (should be hashed)
+                size_t secret_len = fdp.ConsumeIntegralInRange<size_t>(256, 1024);
+                std::vector<uint8_t> secret = fdp.ConsumeBytes<uint8_t>(secret_len);
+                if (secret.size() < 64) {
+                    secret.resize(64, 0x42);  // Pad to ensure long secret
+                }
+                
+                HMAC* hmac = CryptoLink::getCryptoLink().createHMAC(
+                    secret.data(), secret.size(), hash_alg
+                );
+                if (hmac) {
+                    std::vector<uint8_t> input_data = fdp.ConsumeRemainingBytes<uint8_t>();
+                    if (!input_data.empty()) {
+                        hmac->update(input_data.data(), input_data.size());
+                    }
+                    std::vector<uint8_t> signature = hmac->sign(hmac->getOutputLength());
+                    delete hmac;
+                }
+                break;
+            }
+            
+            case 9: {
+                // Test RNG generation
+                size_t rng_len = fdp.ConsumeIntegralInRange<size_t>(0, 1024);
+                std::vector<uint8_t> random_data = isc::cryptolink::random(rng_len);
+                
+                // Test Qid generation
+                uint16_t qid = isc::cryptolink::generateQid();
+                (void)qid;  // Use the variable
+                break;
+            }
+        }
+    } catch (const isc::Exception&) {
+        // Expected for invalid algorithms, key lengths, etc.
+    } catch (const std::exception&) {
+        // Catch any standard library exceptions
+    }
+    
+    return 0;
+}
diff --git a/src/fuzz/fuzz_d2.cc b/src/fuzz/fuzz_d2.cc
new file mode 100644 (file)
index 0000000..d9944d8
--- /dev/null
@@ -0,0 +1,130 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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/.
+////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <d2/parser_context.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/d2_simple_parser.h>
+#include <d2srv/d2_update_message.h>
+#include <dhcp_ddns/ncr_msg.h>
+#include <dns/message.h>
+#include <dns/name.h>
+#include <dns/tsig.h>
+#include <dns/tsigkey.h>
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <string>
+#include <memory>
+
+using namespace isc::d2;
+using namespace isc::data;
+using namespace isc::dhcp_ddns;
+using namespace isc::dns;
+using namespace isc::util;
+
+static const D2ParserContext::ParserType types[] = {
+    D2ParserContext::PARSER_JSON,
+    D2ParserContext::PARSER_DHCPDDNS,
+    D2ParserContext::PARSER_SUB_DHCPDDNS,
+    D2ParserContext::PARSER_TSIG_KEY,
+    D2ParserContext::PARSER_TSIG_KEYS,
+    D2ParserContext::PARSER_DDNS_DOMAIN,
+    D2ParserContext::PARSER_DDNS_DOMAINS,
+    D2ParserContext::PARSER_DNS_SERVER,
+    D2ParserContext::PARSER_DNS_SERVERS,
+    D2ParserContext::PARSER_HOOKS_LIBRARY
+};
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    FuzzedDataProvider fdp(data, size);
+    bool checkOnly = fdp.ConsumeBool();
+    uint8_t index = fdp.ConsumeIntegralInRange<uint8_t>(0, static_cast<uint8_t>(sizeof(types) / sizeof(types[0]) - 1));
+
+    D2SimpleParser simpleParser;
+    D2CfgContextPtr ctxPtr(new D2CfgContext());
+    D2ParserContext ctx;
+    ElementPtr elem;
+
+    // Generate random parsing mode
+    D2ParserContext::ParserType type = types[index];
+
+    const std::string payload = fdp.ConsumeRemainingBytesAsString();
+
+    // Target context parseString
+    try {
+        ctx.parseString(payload, type);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    }
+
+    // Parse payload to JSON
+    try {
+        elem = Element::fromJSON(payload);
+    } catch (...) {
+        // If failed to parse the payload, early exit
+        return 0;
+    }
+
+    // Target SimpleParser
+    try {
+        D2SimpleParser::setAllDefaults(elem);
+        simpleParser.parse(ctxPtr, elem, checkOnly);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    }
+
+    // Prepare buffer
+    InputBuffer buf(reinterpret_cast<const uint8_t*>(payload.data()), payload.size());
+
+    // Target NameChangeRequest::fromtFormat
+    try {
+        NameChangeRequest::fromFormat(NameChangeFormat::FMT_JSON, buf);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    }
+
+    // Target Message fromWire
+    try {
+        Message msg(Message::PARSE);
+        msg.fromWire(buf);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    }
+
+    // Target D2UpdateMessage fromWire (Inbound)
+    try {
+        TSIGKey tsigkey("fuzz_key:fuzz_key");
+        TSIGContext tsigctx(tsigkey);
+        D2UpdateMessage message(D2UpdateMessage::INBOUND);
+        message.fromWire(payload.data(), payload.size(), &tsigctx) ;
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    }
+
+    // Target D2UpdateMessage fromWire (Outbound)
+    try {
+        TSIGKey tsigkey("fuzz_key:fuzz_key");
+        TSIGContext tsigctx(tsigkey);
+        D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
+        message.fromWire(payload.data(), payload.size(), &tsigctx);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    }
+
+    return 0;
+}
diff --git a/src/fuzz/fuzz_d2.dict b/src/fuzz/fuzz_d2.dict
new file mode 100644 (file)
index 0000000..ad91bdd
--- /dev/null
@@ -0,0 +1,158 @@
+"{"
+"}"
+"["
+"]"
+":"
+","
+"\""
+"\"\""
+"null"
+"true"
+"false"
+" "
+"\\n"
+"\\r\\n"
+"\"DhcpDdns\""
+"\"dhcp-ddns\""
+"\"ip-address\""
+"\"port\""
+"\"dns-servers\""
+"\"dns-server-timeout\""
+"\"enable-updates\""
+"\"ncr-protocol\""
+"\"ncr-format\""
+"\"max-queue-size\""
+"\"max-ncrs-per-iteration\""
+"\"qualifying-suffix\""
+"\"forward-ddns\""
+"\"reverse-ddns\""
+"\"ddns-domains\""
+"\"ddns-replace-client-name\""
+"\"override-client-update\""
+"\"generated-prefix\""
+"\"remove-on-renew\""
+"\"update-on-renew\""
+"\"resend-on-failure\""
+"\"retry-interval\""
+"\"ttl\""
+"\"rrtype\""
+"\"rrclass\""
+"\"fqdn\""
+"\"lease-address\""
+"\"lease6-address\""
+"\"client-id\""
+"\"dhcid\""
+"\"change-type\""
+"\"ADD\""
+"\"REMOVE\""
+"\"A\""
+"\"AAAA\""
+"\"PTR\""
+"\"TXT\""
+"\"IN\""
+"\"UDP\""
+"\"TCP\""
+"\"JSON\""
+"\"WIRE\""
+"\"tsig-keys\""
+"\"tsig-keys-dns\""
+"\"tsig-keys-servers\""
+"\"tsig-keys-local\""
+"\"tsig-keys-remote\""
+"\"tsig-keys-names\""
+"\"name\""
+"\"algorithm\""
+"\"secret\""
+"\"digest-bits\""
+"\"HMAC-MD5\""
+"\"HMAC-SHA1\""
+"\"HMAC-SHA224\""
+"\"HMAC-SHA256\""
+"\"HMAC-SHA384\""
+"\"HMAC-SHA512\""
+"\"control-socket\""
+"\"socket-type\""
+"\"socket-name\""
+"\"unix\""
+"\"http\""
+"\"http-host\""
+"\"http-port\""
+"\"http-ciphers\""
+"\"http-headers\""
+"\"http-max-body-size\""
+"\"Logging\""
+"\"output_options\""
+"\"output\""
+"\"file-name\""
+"\"severity\""
+"\"debuglevel\""
+"\"INFO\""
+"\"WARN\""
+"\"ERROR\""
+"\"DEBUG\""
+"\"stdout\""
+"\"stderr\""
+"\"/var/log/kea-dhcp-ddns.log\""
+"\"127.0.0.1\""
+"\"::1\""
+"\"0.0.0.0\""
+"0"
+"53"
+"53001"
+"65535"
+"\"Control-agent\""
+"\"hooks-libraries\""
+"\"library\""
+"\"parameters\""
+"\"user-context\""
+"\"comment\""
+"\"DhcpDdns\":{"
+"\"dns-servers\":[{"
+"\"ip-address\":\""
+"\"port\":"
+"\"tsig-keys\":["
+"\"tsig-keys\":[{"
+"\"name\":\""
+"\"algorithm\":\""
+"\"secret\":\""
+"\"digest-bits\":"
+"\"forward-ddns\":{"
+"\"reverse-ddns\":{"
+"\"ddns-domains\":["
+"\"ddns-domains\":[{"
+"\"qualifying-suffix\":\""
+"\"enable-updates\":"
+"\"ncr-protocol\":\""
+"\"ncr-format\":\""
+"\"max-queue-size\":"
+"\"max-ncrs-per-iteration\":"
+"\"dns-server-timeout\":"
+"\"control-socket\":{"
+"\"socket-type\":\""
+"\"socket-name\":\""
+"\"Logging\":[{"
+"\"output_options\":[{"
+"\"severity\":\""
+"\"debuglevel\":"
+"}]"
+"}]"
+"}"
+"\"\""
+"\"   \""
+"\"\\u0000\""
+"\"\\uD800\""
+"\"\\x\""
+"-1"
+"2147483647"
+"4294967295"
+"9223372036854775807"
+"3.14159"
+"\"[],{}:\""
+"\":{"
+"\": ["
+"]}"
+"}]"
+"},"
+"],"
+"\" : "
+" , "
diff --git a/src/fuzz/fuzz_dhcp_parser.dict b/src/fuzz/fuzz_dhcp_parser.dict
new file mode 100644 (file)
index 0000000..0180269
--- /dev/null
@@ -0,0 +1,901 @@
+"{"
+"}"
+"["
+"]"
+":"
+","
+"Dhcp4"
+"interfaces-config"
+"interfaces"
+"*"
+"rebind-timer"
+"renew-timer"
+"valid-lifetime"
+"subnet4"
+"id"
+"pools"
+"pool"
+"subnet"
+"interface"
+"option-data"
+"name"
+"data"
+"csv-format"
+"always-send"
+"never-send"
+"hosts-database"
+"type"
+"mysql"
+"postgresql"
+"user"
+"password"
+"hooks-libraries"
+"hosts-databases"
+"control-socket"
+"control-sockets"
+"authentication"
+"clients"
+"user-file"
+"password-file"
+"client-classes"
+"test"
+"template-test"
+"option-def"
+"code"
+"uint32"
+"next-server"
+"server-hostname"
+"boot-file-name"
+"user-context"
+"secure"
+"eth0"
+"eth1"
+"10.254.226.0/25"
+"10.254.226.0/24"
+"192.0.2.0/24"
+"192.0.2.1 - 192.0.2.100"
+"default-ip-ttl"
+"ip-forwarding"
+"false"
+"true"
+"FF"
+"interfaces-config\": { \"interfaces\": [ \"*\" ] }"
+"option-data\": ["
+"{ \"name\": "
+"{ \"code\": "
+"{ \"pool\": "
+"subnet4\": [ {"
+"pools\": [ {"
+"id\": 1"
+"interface\": \"eth0\""
+"Dhcp6"
+"subnet6"
+"pd-pools"
+"lease-database"
+"preferred-lifetime"
+"min-preferred-lifetime"
+"max-preferred-lifetime"
+"min-valid-lifetime"
+"max-valid-lifetime"
+"rapid-commit"
+"duid"
+"duid-type"
+"interface-id"
+"relay"
+"relay-supplied-options"
+"ia-na"
+"ia-ta"
+"ia-pd"
+"prefix"
+"prefix-len"
+"delegated-len"
+"excluded-prefix"
+"excluded-prefix-len"
+"dns-servers"
+"domain-search"
+"renew-timer\": "
+"rebind-timer\": "
+"valid-lifetime\": "
+"preferred-lifetime\": "
+"rapid-commit\": true"
+"rapid-commit\": false"
+"lease-database\": {"
+"type\": \"memfile\""
+"type\": \"mysql\""
+"type\": \"postgresql\""
+"persist\": true"
+"persist\": false"
+"subnet6\": [ {"
+"pd-pools\": [ {"
+"{ \"prefix\": "
+"{ \"prefix-len\": "
+"{ \"delegated-len\": "
+"{ \"excluded-prefix\": "
+"{ \"excluded-prefix-len\": "
+"{ \"interface-id\": "
+"option-data\": [ {"
+"{ \"name\": \"dns-servers\", \"data\": "
+"{ \"name\": \"domain-search\", \"data\": "
+"{ \"name\": \"client-id\", \"data\": "
+"{ \"name\": \"server-id\", \"data\": "
+"{ \"name\": \"elapsed-time\", \"data\": "
+"{ \"name\": \"oro\", \"data\": "
+"{ \"name\": \"rapid-commit\" "
+"client-classes\": ["
+"user-context\": {"
+"hooks-libraries\": ["
+"authentication\": {"
+"interfaces-config\": { \"interfaces\": ["
+"2001:db8::/32"
+"2001:db8:1::/48"
+"2001:db8:1::/64"
+"2001:db8:2::/56"
+"2001:db8:1::10 - 2001:db8:1::ffff"
+"fe80::1"
+"::1"
+"::/0"
+"duid\": \"00:01:00:01:24:60:9b:d0:aa:bb:cc:dd:ee:ff\""
+"server-id\": \"00:02:00:01:aa:bb:cc:dd:ee:ff:00:11:22:33\""
+"client-id\": \"00:03:00:01:11:22:33:44:55:66\""
+"prefix\": \"2001:db8:2::\""
+"prefix-len\": 56"
+"delegated-len\": 64"
+"excluded-prefix\": \"2001:db8:2::\""
+"excluded-prefix-len\": 64"
+"pool\": \"2001:db8:1::/64\""
+"pool\": \"2001:db8:1::10 - 2001:db8:1::ffff\""
+"interface\": \"eth1\""
+"null"
+"0"
+"1"
+"56"
+"64"
+"3600"
+"7200"
+"86400"
+"socket-address"
+"socket-port"
+"cert-required"
+"http-headers"
+"http_header_params"
+"value"
+"http_header"
+"basic"
+"auth_params"
+"realm"
+"directory"
+"dhcp-queue-control"
+"queue_control_params"
+"enable-queue"
+"queue-type"
+"capacity"
+"constant string"
+"sub_dhcp_ddns"
+"dhcp_ddns_params"
+"enable-updates"
+"server-ip"
+"server-port"
+"sender-ip"
+"max-queue-size"
+"ncr-format"
+"config-control"
+"udp"
+"config-databases"
+"config-fetch-wait-time"
+"lenient-option-parsing"
+"compatibility"
+"ignore-rai-link-selection"
+"pool-id"
+"option-data-map"
+"encapsulate"
+"encapsulate_space"
+"additional-class"
+"prefix/len"
+"min-max"
+"/"
+"-"
+"ip-addresses"
+"socket-type"
+"unix"
+"http"
+"https"
+"ignore-dhcp-server-identifier"
+"exclude-first-last-24"
+"ncr-protocol"
+"UDP"
+"JSON"
+"PAD"
+"END"
+"127.0.0.1"
+"0.0.0.0"
+"::"
+"2001:db8::"
+"2001:db8::/64"
+"192.0.2.1-192.0.2.254"
+"192.0.2.1"
+"255.255.255.255"
+"127.0.0.1/24"
+"::/128"
+"0/0"
+"len"
+"min"
+"max"
+"poolMaker"
+"poolMaker(min,max)"
+"poolMaker(addr,len)"
+"ip-address"
+"ip"
+"address"
+"addresses"
+"subnet-id"
+"subnet-id-max"
+"reservations"
+"reservations-list"
+"reservation"
+"host-reservation"
+"host-reservations"
+"option-space"
+"DHCP4_OPTION_SPACE"
+"DHCP6_OPTION_SPACE"
+"encapsulates"
+"array_type"
+"record_types"
+"record"
+"record-field"
+"pad"
+"end"
+"server"
+"sender"
+"IPv4"
+"IPv6"
+"v4"
+"v6"
+"IPv4_zero"
+"IPv6_zero"
+"address/mask"
+"fe80::/64"
+"192.168.1.0/24"
+"10.0.0.0/8"
+"invalid-ip"
+"not-an-ip"
+"BADVALUE"
+"BadValue"
+"DhcpConfigError"
+"D2ClientError"
+"Unexpected"
+"OutOfRange"
+"Failed to parse pool"
+"Failed to create pool"
+"Failed to create subnet"
+"Invalid prefix length"
+"prefix length"
+"Missing /"
+"Invalid subnet syntax"
+"Failed to convert"
+"invalid option code"
+"invalid option space name"
+"invalid parameter next-server"
+"map"
+"list"
+"string"
+"integer"
+"boolean"
+"stringValue"
+"intValue"
+"contains"
+"get"
+"getPosition"
+"getType"
+"listValue"
+"mapValue"
+"size"
+"add"
+"addAddress"
+"addRecordField"
+"validate"
+"parse"
+"parseDdnsParameters"
+"parseCacheParams"
+"parseAdaptiveLeaseTimeParam"
+"parseOfferLft"
+"parseAllocatorParams"
+"parsePdAllocatorParams"
+"parseTeePercents"
+"parseDdnsParams"
+"createOptionDataListParser"
+"createPoolsListParser"
+"createPoolConfigParser"
+"createPdPoolConfigParser"
+"createAllocatorParamsParser"
+"createPdAllocatorParamsParser"
+"HTTP"
+"HTTPS"
+"unix-socket"
+"sender-port"
+"option"
+"dhcp4"
+"dhcp6"
+"match-client-id"
+"authoritative"
+"offer-lifetime"
+"t1-percent"
+"t2-percent"
+"ipv4-range-format"
+"ipv6-range-format"
+"ipv6-prefix-format"
+"invalid-option"
+"invalid-parameter"
+"NotFound"
+"Error"
+"Exception"
+s0="end of file"
+s1="error"
+s2="invalid token"
+s3="null"
+s4="Dhcp4"
+s5="config-control"
+s6="config-databases"
+s7="config-fetch-wait-time"
+s8="interfaces-config"
+s9="interfaces"
+s10="dhcp-socket-type"
+s11="raw"
+s12="udp"
+s13="outbound-interface"
+s14="same-as-inbound"
+s15="use-routing"
+s16="re-detect"
+s17="service-sockets-require-all"
+s18="service-sockets-retry-wait-time"
+s19="service-sockets-max-retries"
+s20="sanity-checks"
+s21="lease-checks"
+s22="extended-info-checks"
+s23="echo-client-id"
+s24="match-client-id"
+s25="authoritative"
+s26="next-server"
+s27="server-hostname"
+s28="boot-file-name"
+s29="offer-lifetime"
+s30="stash-agent-options"
+s31="lease-database"
+s32="hosts-database"
+s33="hosts-databases"
+s34="type"
+s35="user"
+s36="password"
+s37="host"
+s38="port"
+s39="persist"
+s40="lfc-interval"
+s41="readonly"
+s42="connect-timeout"
+s43="read-timeout"
+s44="write-timeout"
+s45="tcp-user-timeout"
+s46="max-reconnect-tries"
+s47="reconnect-wait-time"
+s48="on-fail"
+s49="stop-retry-exit"
+s50="serve-retry-exit"
+s51="serve-retry-continue"
+s52="retry-on-startup"
+s53="max-row-errors"
+s54="trust-anchor"
+s55="cert-file"
+s56="key-file"
+s57="ssl-mode"
+s58="disable"
+s59="prefer"
+s60="require"
+s61="verify-ca"
+s62="verify-full"
+s63="cipher-list"
+s64="valid-lifetime"
+s65="min-valid-lifetime"
+s66="max-valid-lifetime"
+s67="renew-timer"
+s68="rebind-timer"
+s69="calculate-tee-times"
+s70="t1-percent"
+s71="t2-percent"
+s72="cache-threshold"
+s73="cache-max-age"
+s74="adaptive-lease-time-threshold"
+s75="decline-probation-period"
+s76="server-tag"
+s77="statistic-default-sample-count"
+s78="statistic-default-sample-age"
+s79="ddns-send-updates"
+s80="ddns-override-no-update"
+s81="ddns-override-client-update"
+s82="ddns-replace-client-name"
+s83="ddns-generated-prefix"
+s84="ddns-qualifying-suffix"
+s85="ddns-update-on-renew"
+s86="ddns-use-conflict-resolution"
+s87="ddns-ttl-percent"
+s88="ddns-ttl"
+s89="ddns-ttl-min"
+s90="ddns-ttl-mix"
+s91="store-extended-info"
+s92="subnet4"
+s93="4o6-interface"
+s94="4o6-interface-id"
+s95="4o6-subnet"
+s96="option-def"
+s97="option-data"
+s98="name"
+s99="data"
+s100="code"
+s101="space"
+s102="csv-format"
+s103="always-send"
+s104="never-send"
+s105="record-types"
+s106="encapsulate"
+s107="array"
+s108="parked-packet-limit"
+s109="allocator"
+s110="ddns-conflict-resolution-mode"
+s111="check-with-dhcid"
+s112="no-check-with-dhcid"
+s113="check-exists-with-dhcid"
+s114="no-check-without-dhcid"
+s115="shared-networks"
+s116="pools"
+s117="pool"
+s118="user-context"
+s119="comment"
+s120="subnet"
+s121="interface"
+s122="id"
+s123="reservations-global"
+s124="reservations-in-subnet"
+s125="reservations-out-of-pool"
+s126="host-reservation-identifiers"
+s127="client-classes"
+s128="require-client-classes"
+s129="evaluate-additional-classes"
+s130="test"
+s131="template-test"
+s132="only-if-required"
+s133="only-in-additional-list"
+s134="client-class"
+s135="pool-id"
+s136="reservations"
+s137="ip-address"
+s138="duid"
+s139="hw-address"
+s140="circuit-id"
+s141="client-id"
+s142="hostname"
+s143="flex-id"
+s144="relay"
+s145="ip-addresses"
+s146="hooks-libraries"
+s147="library"
+s148="parameters"
+s149="expired-leases-processing"
+s150="reclaim-timer-wait-time"
+s151="flush-reclaimed-timer-wait-time"
+s152="hold-reclaimed-time"
+s153="max-reclaim-leases"
+s154="max-reclaim-time"
+s155="unwarned-reclaim-cycles"
+s156="dhcp4o6-port"
+s157="multi-threading"
+s158="enable-multi-threading"
+s159="thread-pool-size"
+s160="packet-queue-size"
+s161="control-socket"
+s162="control-sockets"
+s163="socket-type"
+s164="unix"
+s165="http"
+s166="https"
+s167="socket-name"
+s168="socket-address"
+s169="socket-port"
+s170="authentication"
+s171="basic"
+s172="realm"
+s173="directory"
+s174="clients"
+s175="user-file"
+s176="password-file"
+s177="cert-required"
+s178="http-headers"
+s179="value"
+s180="dhcp-queue-control"
+s181="enable-queue"
+s182="queue-type"
+s183="capacity"
+s184="dhcp-ddns"
+s185="enable-updates"
+s186="server-ip"
+s187="server-port"
+s188="sender-ip"
+s189="sender-port"
+s190="max-queue-size"
+s191="ncr-protocol"
+s192="ncr-format"
+s193="tcp"
+s194="JSON"
+s195="when-present"
+s196="never"
+s197="always"
+s198="when-not-present"
+s199="hostname-char-set"
+s200="hostname-char-replacement"
+s201="early-global-reservations-lookup"
+s202="ip-reservations-unique"
+s203="reservations-lookup-first"
+s204="loggers"
+s205="output-options"
+s206="output"
+s207="debuglevel"
+s208="severity"
+s209="flush"
+s210="maxsize"
+s211="maxver"
+s212="pattern"
+s213="compatibility"
+s214="lenient-option-parsing"
+s215="ignore-dhcp-server-identifier"
+s216="ignore-rai-link-selection"
+s217="exclude-first-last-24"
+s218="TOPLEVEL_JSON"
+s219="TOPLEVEL_DHCP4"
+s220="SUB_DHCP4"
+s221="SUB_INTERFACES4"
+s222="SUB_SUBNET4"
+s223="SUB_POOL4"
+s224="SUB_RESERVATION"
+s225="SUB_OPTION_DEFS"
+s226="SUB_OPTION_DEF"
+s227="SUB_OPTION_DATA"
+s228="SUB_HOOKS_LIBRARY"
+s229="SUB_DHCP_DDNS"
+s230="SUB_CONFIG_CONTROL"
+s231="constant string"
+s232="integer"
+s233="floating point"
+s234="boolean"
+s235="$accept"
+s236="start"
+s237="value"
+s238="sub_json"
+s239="map2"
+s240="map_value"
+s241="map_content"
+s242="not_empty_map"
+s243="list_generic"
+s244="list_content"
+s245="not_empty_list"
+s246="list_strings"
+s247="list_strings_content"
+s248="not_empty_list_strings"
+s249="unknown_map_entry"
+s250="syntax_map"
+s251="global_object"
+s252="global_object_comma"
+s253="sub_dhcp4"
+s254="global_params"
+s255="global_param"
+s256="valid_lifetime"
+s257="min_valid_lifetime"
+s258="max_valid_lifetime"
+s259="renew_timer"
+s260="rebind_timer"
+s261="calculate_tee_times"
+s262="t1_percent"
+s263="t2_percent"
+s264="cache_threshold"
+s265="cache_max_age"
+s266="adaptive_lease_time_threshold"
+s267="decline_probation_period"
+s268="server_tag"
+s269="parked_packet_limit"
+s270="allocator"
+s271="echo_client_id"
+s272="match_client_id"
+s273="authoritative"
+s274="ddns_send_updates"
+s275="ddns_override_no_update"
+s276="ddns_override_client_update"
+s277="ddns_replace_client_name"
+s278="ddns_replace_client_name_value"
+s279="ddns_generated_prefix"
+s280="ddns_qualifying_suffix"
+s281="ddns_update_on_renew"
+s282="ddns_use_conflict_resolution"
+s283="ddns_conflict_resolution_mode"
+s284="ddns_conflict_resolution_mode_value"
+s285="ddns_ttl_percent"
+s286="ddns_ttl"
+s287="ddns_ttl_min"
+s288="ddns_ttl_max"
+s289="hostname_char_set"
+s290="hostname_char_replacement"
+s291="store_extended_info"
+s292="statistic_default_sample_count"
+s293="statistic_default_sample_age"
+s294="early_global_reservations_lookup"
+s295="ip_reservations_unique"
+s296="reservations_lookup_first"
+s297="offer_lifetime"
+s298="stash_agent_options"
+s299="interfaces_config"
+s300="interfaces_config_params"
+s301="interfaces_config_param"
+s302="sub_interfaces4"
+s303="interfaces_list"
+s304="dhcp_socket_type"
+s305="socket_type"
+s306="outbound_interface"
+s307="outbound_interface_value"
+s308="re_detect"
+s309="service_sockets_require_all"
+s310="service_sockets_retry_wait_time"
+s311="service_sockets_max_retries"
+s312="lease_database"
+s313="sanity_checks"
+s314="sanity_checks_params"
+s315="sanity_checks_param"
+s316="lease_checks"
+s317="extended_info_checks"
+s318="hosts_database"
+s319="hosts_databases"
+s320="database_list"
+s321="not_empty_database_list"
+s322="database"
+s323="database_map_params"
+s324="database_map_param"
+s325="database_type"
+s326="user"
+s327="password"
+s328="host"
+s329="port"
+s330="name"
+s331="persist"
+s332="lfc_interval"
+s333="readonly"
+s334="connect_timeout"
+s335="read_timeout"
+s336="write_timeout"
+s337="tcp_user_timeout"
+s338="max_reconnect_tries"
+s339="reconnect_wait_time"
+s340="on_fail"
+s341="on_fail_mode"
+s342="retry_on_startup"
+s343="max_row_errors"
+s344="trust_anchor"
+s345="cert_file"
+s346="key_file"
+s347="ssl_mode"
+s348="cipher_list"
+s349="host_reservation_identifiers"
+s350="host_reservation_identifiers_list"
+s351="host_reservation_identifier"
+s352="duid_id"
+s353="hw_address_id"
+s354="circuit_id"
+s355="client_id"
+s356="flex_id"
+s357="dhcp_multi_threading"
+s358="multi_threading_params"
+s359="multi_threading_param"
+s360="enable_multi_threading"
+s361="thread_pool_size"
+s362="packet_queue_size"
+s363="hooks_libraries"
+s364="hooks_libraries_list"
+s365="not_empty_hooks_libraries_list"
+s366="hooks_library"
+s367="sub_hooks_library"
+s368="hooks_params"
+s369="hooks_param"
+s370="library"
+s371="parameters"
+s372="expired_leases_processing"
+s373="expired_leases_params"
+s374="expired_leases_param"
+s375="reclaim_timer_wait_time"
+s376="flush_reclaimed_timer_wait_time"
+s377="hold_reclaimed_time"
+s378="max_reclaim_leases"
+s379="max_reclaim_time"
+s380="unwarned_reclaim_cycles"
+s381="subnet4_list"
+s382="subnet4_list_content"
+s383="not_empty_subnet4_list"
+s384="subnet4"
+s385="sub_subnet4"
+s386="subnet4_params"
+s387="subnet4_param"
+s388="subnet"
+s389="subnet_4o6_interface"
+s390="subnet_4o6_interface_id"
+s391="subnet_4o6_subnet"
+s392="interface"
+s393="client_class"
+s394="network_client_classes"
+s395="require_client_classes"
+s396="evaluate_additional_classes"
+s397="reservations_global"
+s398="reservations_in_subnet"
+s399="reservations_out_of_pool"
+s400="id"
+s401="shared_networks"
+s402="shared_networks_content"
+s403="shared_networks_list"
+s404="shared_network"
+s405="shared_network_params"
+s406="shared_network_param"
+s407="option_def_list"
+s408="sub_option_def_list"
+s409="option_def_list_content"
+s410="not_empty_option_def_list"
+s411="option_def_entry"
+s412="sub_option_def"
+s413="option_def_params"
+s414="not_empty_option_def_params"
+s415="option_def_param"
+s416="option_def_name"
+s417="code"
+s418="option_def_code"
+s419="option_def_type"
+s420="option_def_record_types"
+s421="space"
+s422="option_def_space"
+s423="option_def_encapsulate"
+s424="option_def_array"
+s425="option_data_list"
+s426="option_data_list_content"
+s427="not_empty_option_data_list"
+s428="option_data_entry"
+s429="sub_option_data"
+s430="option_data_params"
+s431="not_empty_option_data_params"
+s432="option_data_param"
+s433="option_data_name"
+s434="option_data_data"
+s435="option_data_code"
+s436="option_data_space"
+s437="option_data_csv_format"
+s438="option_data_always_send"
+s439="option_data_never_send"
+s440="option_data_client_classes"
+s441="pools_list"
+s442="pools_list_content"
+s443="not_empty_pools_list"
+s444="pool_list_entry"
+s445="sub_pool4"
+s446="pool_params"
+s447="pool_param"
+s448="pool_entry"
+s449="pool_id"
+s450="user_context"
+s451="comment"
+s452="reservations"
+s453="reservations_list"
+s454="not_empty_reservations_list"
+s455="reservation"
+s456="sub_reservation"
+s457="reservation_params"
+s458="not_empty_reservation_params"
+s459="reservation_param"
+s460="next_server"
+s461="server_hostname"
+s462="boot_file_name"
+s463="ip_address"
+s464="duid"
+s465="hw_address"
+s466="client_id_value"
+s467="circuit_id_value"
+s468="flex_id_value"
+s469="hostname"
+s470="reservation_client_classes"
+s471="relay"
+s472="relay_map"
+s473="ip_addresses"
+s474="client_classes"
+s475="client_classes_list"
+s476="client_class_entry"
+s477="client_class_params"
+s478="not_empty_client_class_params"
+s479="client_class_param"
+s480="client_class_name"
+s481="client_class_test"
+s482="client_class_template_test"
+s483="only_if_required"
+s484="only_in_additional_list"
+s485="dhcp4o6_port"
+s486="control_socket"
+s487="control_sockets"
+s488="control_socket_list"
+s489="not_empty_control_socket_list"
+s490="control_socket_entry"
+s491="control_socket_params"
+s492="control_socket_param"
+s493="control_socket_type"
+s494="control_socket_type_value"
+s495="control_socket_name"
+s496="control_socket_address"
+s497="control_socket_port"
+s498="cert_required"
+s499="http_headers"
+s500="http_header_list"
+s501="not_empty_http_header_list"
+s502="http_header"
+s503="http_header_params"
+s504="http_header_param"
+s505="header_value"
+s506="authentication"
+s507="auth_params"
+s508="auth_param"
+s509="auth_type"
+s510="auth_type_value"
+s511="realm"
+s512="directory"
+s513="clients"
+s514="clients_list"
+s515="not_empty_clients_list"
+s516="basic_auth"
+s517="clients_params"
+s518="clients_param"
+s519="user_file"
+s520="password_file"
+s521="dhcp_queue_control"
+s522="queue_control_params"
+s523="queue_control_param"
+s524="enable_queue"
+s525="queue_type"
+s526="capacity"
+s527="arbitrary_map_entry"
+s528="dhcp_ddns"
+s529="sub_dhcp_ddns"
+s530="dhcp_ddns_params"
+s531="dhcp_ddns_param"
+s532="enable_updates"
+s533="server_ip"
+s534="server_port"
+s535="sender_ip"
+s536="sender_port"
+s537="max_queue_size"
+s538="ncr_protocol"
+s539="ncr_protocol_value"
+s540="ncr_format"
+s541="config_control"
+s542="sub_config_control"
+s543="config_control_params"
+s544="config_control_param"
+s545="config_databases"
+s546="config_fetch_wait_time"
+s547="loggers"
+s548="loggers_entries"
+s549="logger_entry"
+s550="logger_params"
+s551="logger_param"
+s552="debuglevel"
+s553="severity"
+s554="output_options_list"
+s555="output_options_list_content"
+s556="output_entry"
+s557="output_params_list"
+s558="output_params"
+s559="output"
+s560="flush"
+s561="maxsize"
+s562="maxver"
+s563="pattern"
+s564="compatibility"
+s565="compatibility_params"
+s566="compatibility_param"
+s567="lenient_option_parsing"
+s568="ignore_dhcp_server_identifier"
+s569="ignore_rai_link_selection"
+s570="exclude_first_last_24"
diff --git a/src/fuzz/fuzz_dhcp_parser4.cc b/src/fuzz/fuzz_dhcp_parser4.cc
new file mode 100644 (file)
index 0000000..a56cdc1
--- /dev/null
@@ -0,0 +1,219 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <cc/command_interpreter.h>
+#include <cc/data.h>
+
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/json_config_parser.h>
+#include <dhcp4/parser_context.h>
+#include <log/logger_support.h>
+#include <process/daemon.h>
+
+#include "helper_func.h"
+
+#include <array>
+#include <cstdlib>
+#include <string>
+#include <unistd.h>
+#include <vector>
+
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+
+using ControlledDhcpvSrv = ControlledDhcpv4Srv;
+static constexpr Parser4Context::ParserType parserTypes[] = {
+    Parser4Context::PARSER_JSON,
+    Parser4Context::PARSER_INTERFACES,
+    Parser4Context::PARSER_OPTION_DATA,
+    Parser4Context::PARSER_OPTION_DEF,
+    Parser4Context::PARSER_OPTION_DEFS,
+    Parser4Context::PARSER_HOST_RESERVATION,
+    Parser4Context::PARSER_HOOKS_LIBRARY,
+    Parser4Context::PARSER_DHCP_DDNS,
+    Parser4Context::PARSER_CONFIG_CONTROL,
+    Parser4Context::PARSER_HOST_RESERVATION,
+    Parser4Context::PARSER_DHCP4,
+    Parser4Context::SUBPARSER_DHCP4,
+    Parser4Context::PARSER_SUBNET4,
+    Parser4Context::PARSER_POOL4,
+};
+
+static const char *cmds[] = {"config-get",
+                             "config-hash-get",
+                             "config-write",
+                             "config-set",
+                             "config-test",
+                             "config-reload",
+                             "dhcp-disable",
+                             "dhcp-enable",
+                             "version-get",
+                             "build-report",
+                             "leases-reclaim",
+                             "server-tag-get",
+                             "config-backend-pull",
+                             "status-get",
+                             "statistic-set-max-sample-count-all",
+                             "statistic-set-max-sample-age-all",
+                             "subnet4-select-test",
+                             "subnet4o6-select-test",
+                             "lfc-start",
+                             "shutdown"};
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+  // Initialise logging
+  setenv("KEA_LOGGER_DESTINATION", "/dev/null", 0);
+  setenv("KEA_LOCKFILE_DIR", "/tmp", 0);
+  setenv("KEA_PIDFILE_DIR", "/tmp", 0);
+  setenv("KEA_LFC_EXECUTABLE", "/bin/true", 0);
+  try {
+    isc::log::initLogger("fuzzer");
+    isc::process::Daemon::loggerInit("fuzzer", false);
+    isc::process::Daemon::setDefaultLoggerName("fuzzer");
+  } catch (...) {
+    // Early exit if logging initialisation failed
+    return 0;
+  }
+
+  // Set variables
+  FuzzedDataProvider fdp(data, size);
+  Parser4Context ctx;
+  ControlledDhcpv4Srv srv(0, 0);
+
+  // Get random flags
+  const bool checkOnly = fdp.ConsumeBool();
+  const bool extraChecks = fdp.ConsumeBool();
+
+  // Get random type and command
+  Parser4Context::ParserType type =
+      parserTypes[fdp.ConsumeIntegralInRange<int>(0, 13)];
+  std::string cmdStr =
+      std::string(cmds[fdp.ConsumeIntegralInRange<int>(0, 19)]);
+
+  // If no more remaining bytes, early exit
+  if (fdp.remaining_bytes() <= 0) {
+    return 0;
+  }
+
+  // Provide two type of payload with different length to avoid
+  // timeout from parsing trusted configuration file
+  std::string limit_payload = fdp.ConsumeRandomLengthString(25600);
+  std::string full_payload(reinterpret_cast<const char*>(data), size);
+
+  // First target based on the raw payload entire. This makes seeding a lot
+  // easier.
+  try {
+    ElementPtr rawTree = ctx.parseString(limit_payload, Parser4Context::PARSER_JSON);
+
+    // Configure the server with valid tree
+    if (rawTree) {
+        configureDhcp4Server(srv, rawTree, false, extraChecks);
+        ControlledDhcpv4Srv::checkConfig(rawTree);
+        ControlledDhcpv4Srv::processConfig(rawTree);
+    }
+  } catch (const isc::Exception&) {
+  }
+
+  try {
+    ElementPtr tree = ctx.parseString(limit_payload, type);
+
+    // Configure the server with valid tree
+    if (tree) {
+      if (type == Parser4Context::PARSER_JSON ||
+          type == Parser4Context::PARSER_DHCP4) {
+        configureDhcp4Server(srv, tree, checkOnly, extraChecks);
+        ControlledDhcpv4Srv::checkConfig(tree);
+        ControlledDhcpv4Srv::processConfig(tree);
+      }
+    }
+  } catch (const isc::Exception&) {
+  }
+
+  // File base parsing
+  try {
+    std::string path = fuzz::writeTempFile(limit_payload, "json");
+    if (!path.empty()) {
+      ElementPtr fileTree = ctx.parseFile(path, Parser4Context::PARSER_DHCP4);
+      if (fileTree) {
+        configureDhcp4Server(srv, fileTree, checkOnly, extraChecks);
+        ControlledDhcpv4Srv::checkConfig(fileTree);
+        ControlledDhcpv4Srv::processConfig(fileTree);
+      }
+      unlink(path.c_str());
+    }
+  } catch (const isc::Exception&) {
+  }
+
+  // Command parsing
+  try {
+    ElementPtr args = fuzz::parseJSON(full_payload);
+    ElementPtr cmd = Element::create(cmdStr);
+
+    // Configure root element
+    ElementPtr root = Element::createMap();
+    root->set("command", cmd);
+    root->set("arguments", args);
+
+    // Transform to const element
+    ConstElementPtr cmd_const = cmd;
+    ConstElementPtr root_const = root;
+
+    parseCommand(cmd_const, root_const);
+
+    // Response answer parsing
+    int status = 0;
+    parseAnswer(status, fuzz::parseJSON(full_payload));
+  } catch(const isc::Exception&) {}
+
+    // Try fuzzing specific deeper fuzzers directly
+
+    // Subnets6ListConfigParser
+    try {
+        ElementPtr elem = fuzz::parseJSON(full_payload);
+        SrvConfigPtr srv = SrvConfigPtr(new SrvConfig());
+        Subnets6ListConfigParser parser(fdp.ConsumeBool());
+        parser.parse(srv, elem, fdp.ConsumeBool());
+    } catch (const isc::Exception&) {
+        // Known exceptions
+    }
+
+    // RelayInfoParser
+    try {
+        Option::Universe opt = Option::V4;
+        ElementPtr elem = fuzz::parseJSON(full_payload);
+        Network::RelayInfoPtr info = Network::RelayInfoPtr(new Network::RelayInfo());
+        RelayInfoParser parser(opt);
+        parser.parse(info, elem);
+    } catch (const isc::Exception&) {
+        // Known exceptions
+    }
+
+    // PdPoolParser
+    try {
+        ElementPtr elem = fuzz::parseJSON(full_payload);
+        PoolStoragePtr pools(new PoolStorage());
+        PdPoolParser parser = PdPoolParser();
+        parser.parse(pools, elem, fdp.ConsumeBool());
+    } catch (const isc::Exception&) {
+        // Known exceptions
+    }
+
+    // CompatibilityParser
+    try {
+        ElementPtr elem = fuzz::parseJSON(full_payload);
+        SrvConfig srv = SrvConfig();
+        CompatibilityParser parser = CompatibilityParser();
+        parser.parse(elem, srv);
+    } catch (const isc::Exception&) {
+        // Known exceptions
+    }
+
+  return 0;
+}
diff --git a/src/fuzz/fuzz_dhcp_parser6.cc b/src/fuzz/fuzz_dhcp_parser6.cc
new file mode 100644 (file)
index 0000000..152a990
--- /dev/null
@@ -0,0 +1,199 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <cc/data.h>
+#include <cc/command_interpreter.h>
+
+#include <dhcp6/parser_context.h>
+#include <dhcp6/json_config_parser.h>
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <process/daemon.h>
+#include <log/logger_support.h>
+
+#include "helper_func.h"
+
+#include <array>
+#include <vector>
+#include <string>
+#include <cstdlib>
+#include <unistd.h>
+
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+
+using ControlledDhcpvSrv = ControlledDhcpv6Srv;
+static constexpr Parser6Context::ParserType parserTypes[] = {
+    Parser6Context::PARSER_JSON, Parser6Context::PARSER_INTERFACES,
+    Parser6Context::PARSER_OPTION_DATA, Parser6Context::PARSER_OPTION_DEF,
+    Parser6Context::PARSER_OPTION_DEFS, Parser6Context::PARSER_HOST_RESERVATION,
+    Parser6Context::PARSER_HOOKS_LIBRARY, Parser6Context::PARSER_DHCP_DDNS,
+    Parser6Context::PARSER_CONFIG_CONTROL, Parser6Context::PARSER_HOST_RESERVATION,
+    Parser6Context::PARSER_DHCP6, Parser6Context::SUBPARSER_DHCP6,
+    Parser6Context::PARSER_SUBNET6, Parser6Context::PARSER_POOL6,
+};
+
+static const char* cmds[] = {
+    "config-get","config-hash-get","config-write","config-set","config-test",
+    "config-reload","dhcp-disable","dhcp-enable","version-get","build-report",
+    "leases-reclaim","server-tag-get","config-backend-pull","status-get",
+    "statistic-set-max-sample-count-all","statistic-set-max-sample-age-all",
+    "subnet6-select-test","subnet6o6-select-test","lfc-start","shutdown"
+};
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    // Initialise logging
+    setenv("KEA_LOGGER_DESTINATION", "/dev/null", 0);
+    setenv("KEA_LOCKFILE_DIR", "/tmp", 0);
+    setenv("KEA_PIDFILE_DIR", "/tmp", 0);
+    setenv("KEA_LFC_EXECUTABLE", "/bin/true", 0);
+    try {
+        isc::log::initLogger("fuzzer");
+        isc::process::Daemon::loggerInit("fuzzer", false);
+        isc::process::Daemon::setDefaultLoggerName("fuzzer");
+    } catch (const isc::Exception&) {
+        // Early exit if logging initialisation failed
+        return 0;
+    }
+
+    // Set variables
+    FuzzedDataProvider fdp(data, size);
+    Parser6Context ctx;
+    ControlledDhcpv6Srv srv(0, 0);
+
+    // Get random flags
+    const bool checkOnly = fdp.ConsumeBool();
+    const bool extraChecks = fdp.ConsumeBool();
+
+    // Get random type and command
+    Parser6Context::ParserType type = parserTypes[fdp.ConsumeIntegralInRange<int>(0, 13)];
+    std::string cmdStr = std::string(cmds[fdp.ConsumeIntegralInRange<int>(0, 19)]);
+
+    // If no more remaining bytes, early exit
+    if (fdp.remaining_bytes() <= 0) {
+      return 0;
+    }
+
+    // Provide two type of payload with different length to avoid
+    // timeout from parsing trusted configuration file
+    std::string limit_payload = fdp.ConsumeRandomLengthString(25600);
+    std::string full_payload(reinterpret_cast<const char*>(data), size);
+
+    // Perform an evaluation of the raw data.
+    try {
+        // General parsing
+        ElementPtr rawTree = ctx.parseString(limit_payload, Parser6Context::PARSER_JSON);
+
+        // Configure the server with valid tree
+        if (rawTree) {
+                configureDhcp6Server(srv, rawTree, false, true);
+                ControlledDhcpv6Srv::checkConfig(rawTree);
+                ControlledDhcpv6Srv::processConfig(rawTree);
+        }
+    } catch(const isc::Exception&){}
+
+    // Generate random string
+    try {
+        // General parsing
+        ElementPtr tree = ctx.parseString(limit_payload, type);
+
+        // Configure the server with valid tree
+        if (tree) {
+            if (type == Parser6Context::PARSER_JSON || type == Parser6Context::PARSER_DHCP6){
+                configureDhcp6Server(srv, tree, checkOnly, extraChecks);
+                ControlledDhcpv6Srv::checkConfig(tree);
+                ControlledDhcpv6Srv::processConfig(tree);
+            }
+        }
+    } catch(const isc::Exception&){}
+
+    try {
+        // File base parsing
+        std::string path = fuzz::writeTempFile(limit_payload, "json");
+        if (!path.empty()) {
+            ElementPtr fileTree = ctx.parseFile(path, Parser6Context::PARSER_DHCP6);
+            if (fileTree) {
+                configureDhcp6Server(srv, fileTree, checkOnly, extraChecks);
+                ControlledDhcpv6Srv::checkConfig(fileTree);
+                ControlledDhcpv6Srv::processConfig(fileTree);
+            }
+            unlink(path.c_str());
+        }
+    }
+    catch (const isc::Exception&){}
+
+    try{
+        // Command parsing
+        ElementPtr args = fuzz::parseJSON(full_payload);
+        ElementPtr cmd = Element::create(cmdStr);
+
+        // Configure root element
+        ElementPtr root = Element::createMap();
+        root->set("command", cmd);
+        root->set("arguments", args);
+
+        // Transform to const element
+        ConstElementPtr cmd_const = cmd;
+        ConstElementPtr root_const = root;
+
+        parseCommand(cmd_const, root_const);
+
+        // Response answer parsing
+        int status = 0;
+        parseAnswer(status, fuzz::parseJSON(full_payload));
+    } catch (const isc::Exception&) {
+        // Known exceptions
+    }
+
+    // Try fuzzing specific deeper fuzzers directly
+
+    // Subnets6ListConfigParser
+    try {
+        ElementPtr elem = fuzz::parseJSON(full_payload);
+        SrvConfigPtr srv = SrvConfigPtr(new SrvConfig());
+        Subnets6ListConfigParser parser(fdp.ConsumeBool());
+        parser.parse(srv, elem, fdp.ConsumeBool());
+    } catch (const isc::Exception&) {
+        // Known exceptions
+    }
+
+    // RelayInfoParser
+    try {
+        Option::Universe opt = Option::V6;
+        ElementPtr elem = fuzz::parseJSON(full_payload);
+        Network::RelayInfoPtr info = Network::RelayInfoPtr(new Network::RelayInfo());
+        RelayInfoParser parser(opt);
+        parser.parse(info, elem);
+    } catch (const isc::Exception&) {
+        // Known exceptions
+    }
+
+    // Pool6Parser
+    try {
+        ElementPtr elem = fuzz::parseJSON(full_payload);
+        PoolStoragePtr pools(new PoolStorage());
+        Pool6Parser parser = Pool6Parser();
+        parser.parse(pools, elem, AF_INET6, fdp.ConsumeBool());
+    } catch (const isc::Exception&) {
+        // Known exceptions
+    }
+
+    // CompatibilityParser
+    try {
+        ElementPtr elem = fuzz::parseJSON(full_payload);
+        SrvConfig srv = SrvConfig();
+        CompatibilityParser parser = CompatibilityParser();
+        parser.parse(elem, srv);
+    } catch (const isc::Exception&) {
+        // Known exceptions
+    }
+
+    return 0;
+}
+
diff --git a/src/fuzz/fuzz_dhcp_pkt.dict b/src/fuzz/fuzz_dhcp_pkt.dict
new file mode 100644 (file)
index 0000000..9bd2b5e
--- /dev/null
@@ -0,0 +1,161 @@
+"\x63\x82\x53\x63"
+"{"
+"}"
+"["
+"]"
+":"
+","
+"Dhcp4"
+"interfaces-config"
+"interfaces"
+"*"
+"rebind-timer"
+"renew-timer"
+"valid-lifetime"
+"subnet4"
+"id"
+"pools"
+"pool"
+"subnet"
+"interface"
+"option-data"
+"name"
+"data"
+"csv-format"
+"always-send"
+"never-send"
+"hosts-database"
+"type"
+"mysql"
+"postgresql"
+"user"
+"password"
+"hooks-libraries"
+"hosts-databases"
+"control-socket"
+"control-sockets"
+"authentication"
+"clients"
+"user-file"
+"password-file"
+"client-classes"
+"test"
+"template-test"
+"option-def"
+"code"
+"uint32"
+"next-server"
+"server-hostname"
+"boot-file-name"
+"user-context"
+"secure"
+"eth0"
+"eth1"
+"10.254.226.0/25"
+"10.254.226.0/24"
+"192.0.2.0/24"
+"192.0.2.1 - 192.0.2.100"
+"default-ip-ttl"
+"ip-forwarding"
+"false"
+"true"
+"FF"
+"interfaces-config\": { \"interfaces\": [ \"*\" ] }"
+"option-data\": ["
+"{ \"name\": "
+"{ \"code\": "
+"{ \"pool\": "
+"subnet4\": [ {"
+"pools\": [ {"
+"id\": 1"
+"interface\": \"eth0\""
+
+"Dhcp6"
+"subnet6"
+"pd-pools"
+"lease-database"
+"preferred-lifetime"
+"min-preferred-lifetime"
+"max-preferred-lifetime"
+"min-valid-lifetime"
+"max-valid-lifetime"
+"rapid-commit"
+"duid"
+"duid-type"
+"interface-id"
+"relay"
+"relay-supplied-options"
+"ia-na"
+"ia-ta"
+"ia-pd"
+"prefix"
+"prefix-len"
+"delegated-len"
+"excluded-prefix"
+"excluded-prefix-len"
+"dns-servers"
+"domain-search"
+"renew-timer\": "
+"rebind-timer\": "
+"valid-lifetime\": "
+"preferred-lifetime\": "
+"rapid-commit\": true"
+"rapid-commit\": false"
+"lease-database\": {"
+"type\": \"memfile\""
+"type\": \"mysql\""
+"type\": \"postgresql\""
+"persist\": true"
+"persist\": false"
+"subnet6\": [ {"
+"pd-pools\": [ {"
+"{ \"prefix\": "
+"{ \"prefix-len\": "
+"{ \"delegated-len\": "
+"{ \"excluded-prefix\": "
+"{ \"excluded-prefix-len\": "
+"{ \"interface-id\": "
+"option-data\": [ {"
+"{ \"name\": \"dns-servers\", \"data\": "
+"{ \"name\": \"domain-search\", \"data\": "
+"{ \"name\": \"client-id\", \"data\": "
+"{ \"name\": \"server-id\", \"data\": "
+"{ \"name\": \"elapsed-time\", \"data\": "
+"{ \"name\": \"oro\", \"data\": "
+"{ \"name\": \"rapid-commit\" "
+"client-classes\": ["
+"{ \"name\": "
+"user-context\": {"
+"hooks-libraries\": ["
+"authentication\": {"
+"interfaces-config\": { \"interfaces\": ["
+"2001:db8::/32"
+"2001:db8:1::/48"
+"2001:db8:1::/64"
+"2001:db8:2::/56"
+"2001:db8:1::10 - 2001:db8:1::ffff"
+"fe80::1"
+"::1"
+"::/0"
+"duid\": \"00:01:00:01:24:60:9b:d0:aa:bb:cc:dd:ee:ff\""
+"server-id\": \"00:02:00:01:aa:bb:cc:dd:ee:ff:00:11:22:33\""
+"client-id\": \"00:03:00:01:11:22:33:44:55:66\""
+"prefix\": \"2001:db8:2::\""
+"prefix-len\": 56"
+"delegated-len\": 64"
+"excluded-prefix\": \"2001:db8:2::\""
+"excluded-prefix-len\": 64"
+"pool\": \"2001:db8:1::/64\""
+"pool\": \"2001:db8:1::10 - 2001:db8:1::ffff\""
+"interface\": \"eth0\""
+"interface\": \"eth1\""
+"true"
+"false"
+"null"
+"0"
+"1"
+"56"
+"64"
+"3600"
+"7200"
+"86400"
diff --git a/src/fuzz/fuzz_dhcp_pkt4.cc b/src/fuzz/fuzz_dhcp_pkt4.cc
new file mode 100644 (file)
index 0000000..272981d
--- /dev/null
@@ -0,0 +1,174 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt4o6.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/protocol_util.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/option_vendor_class.h>
+#include <log/logger_support.h>
+#include <process/daemon.h>
+#include <util/buffer.h>
+#include <util/filesystem.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+#include <list>
+#include <memory>
+#include <iostream>
+#include <filesystem>
+#include <fstream>
+#include <string>
+#include <cstdio>
+
+#include "helper_func.h"
+
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace isc::util;
+
+static thread_local FuzzedDataProvider* fdp = nullptr;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    if (size < 236) {
+        // package size requires at least 236 bytes
+        return 0;
+    }
+
+    // Randomly enable validatePath checking
+    fdp = new FuzzedDataProvider(data, size);
+    isc::util::file::PathChecker::enableEnforcement(fdp->ConsumeBool());
+
+    // Initialise logging
+    setenv("KEA_LOGGER_DESTINATION", "/dev/null", 0);
+    setenv("KEA_LOCKFILE_DIR", "/tmp", 0);
+    setenv("KEA_PIDFILE_DIR", "/tmp", 0);
+    setenv("KEA_LFC_EXECUTABLE", "/bin/true", 0);
+    try {
+        isc::log::initLogger("fuzzer");
+        isc::process::Daemon::loggerInit("fuzzer", false);
+        isc::process::Daemon::setDefaultLoggerName("fuzzer");
+    } catch (...) {
+        // Early exit if logging initialisation failed
+        return 0;
+    }
+
+    // Create temporary configuration file
+    std::string path = fuzz::writeTempConfig(true);
+    if (path.empty()) {
+        // Early exit if configuration file creation failed
+        fuzz::deleteTempFile(path);
+        return 0;
+    }
+
+    OptionCollection options;
+    std::list<uint16_t> deferred;
+    std::unique_ptr<ControlledDhcpv4Srv> srv;
+    std::vector<uint8_t> buf(data, data + size);
+
+    try {
+        // Package parsing
+        Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(data, size));
+        pkt->toText(fdp->ConsumeBool());
+        pkt->getType();
+        pkt->getTransid();
+        pkt->unpack();
+        pkt->pack();
+        pkt->getName();
+        pkt->getName(fdp->ConsumeIntegral<uint8_t>());
+        pkt->getLabel();           
+        pkt->getMAC(fdp->ConsumeIntegral<uint16_t>());
+    } catch (...) {}
+
+    // OptionVendorClass parsing
+    try {
+        OptionBuffer buf(data, data + size);
+        OptionVendorClassPtr vendor_class;
+        vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V4,
+            buf.begin(),
+            buf.end()));
+    }catch(...){
+    }
+
+    try {
+        // Package parsing for 4o6
+        Pkt4Ptr pkt4 = Pkt4Ptr(new Pkt4(data, size));
+        Pkt6Ptr pkt6 = Pkt6Ptr(new Pkt6(data, size));
+        Pkt4o6Ptr pkt = Pkt4o6Ptr(new Pkt4o6(pkt4, pkt6));
+        pkt->toText();
+        pkt->getType();
+        pkt->getTransid();
+        pkt->unpack();
+        pkt->pack();
+        pkt->getName();
+        pkt->getName(fdp->ConsumeIntegral<uint8_t>());
+        pkt->getLabel();   
+        pkt->getMAC(fdp->ConsumeIntegral<uint16_t>());
+    } catch (...) {}
+
+    try {
+        // Protocol parsing
+        InputBuffer buf(data, size);
+        Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+        decodeEthernetHeader(buf, pkt);
+        decodeIpUdpHeader(buf, pkt);
+        calcChecksum(data, size, fdp->ConsumeIntegral<uint32_t>());
+    } catch (...) {}
+
+    // OptionVendor parsing
+    try{
+        OptionBuffer buf(data, data + size);
+        OptionVendorPtr vendor;
+        vendor.reset(new OptionVendor(Option::V4, buf.begin() + 2, buf.end()));
+        OutputBuffer output(0);
+        vendor->pack(output);
+    }
+    catch (...){}
+
+    try {
+        // Package parsing
+        Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(data, size));
+
+        // Option parsing
+        LibDHCP::unpackOptions4(buf, DHCP4_OPTION_SPACE, options, deferred, false);
+        for (auto& kv : options) {
+            auto opt = kv.second;
+            if (!opt) {
+                continue;
+            }
+            opt->getType();
+            opt->toText();
+        }
+/*
+        // Server initialisation
+        srv.reset(new ControlledDhcpv4Srv());
+        srv->init(path);
+
+        // Process packet
+        if (srv) {
+            srv->processPacket(pkt);
+            srv->processDhcp4Query(pkt, fdp->ConsumeBool());
+        }
+*/
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    }
+
+  //  srv.reset();
+
+    // Remove temp configuration file
+    fuzz::deleteTempFile(path);
+    delete fdp;
+    return 0;
+}
diff --git a/src/fuzz/fuzz_dhcp_pkt6.cc b/src/fuzz/fuzz_dhcp_pkt6.cc
new file mode 100644 (file)
index 0000000..0b7418a
--- /dev/null
@@ -0,0 +1,131 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <dhcp/pkt6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcp/option_vendor_class.h>
+#include <log/logger_support.h>
+#include <process/daemon.h>
+#include <util/filesystem.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+#include <list>
+#include <memory>
+#include <iostream>
+#include <filesystem>
+#include <fstream>
+#include <string>
+#include <cstdio>
+
+#include "helper_func.h"
+
+using namespace isc::dhcp;
+using namespace isc::hooks;
+
+static thread_local FuzzedDataProvider* fdp = nullptr;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    if (size < 236) {
+        // package size requires at least 236 bytes
+        return 0;
+    }
+
+    // Randomly enable validatePath checking
+    fdp = new FuzzedDataProvider(data, size);
+    isc::util::file::PathChecker::enableEnforcement(fdp->ConsumeBool());
+
+    // Initialise logging
+    setenv("KEA_LOGGER_DESTINATION", "/dev/null", 0);
+    setenv("KEA_LOCKFILE_DIR", "/tmp", 0);
+    setenv("KEA_PIDFILE_DIR", "/tmp", 0);
+    setenv("KEA_LFC_EXECUTABLE", "/bin/true", 0);
+    try {
+        isc::log::initLogger("fuzzer");
+        isc::process::Daemon::loggerInit("fuzzer", false);
+        isc::process::Daemon::setDefaultLoggerName("fuzzer");
+    } catch (...) {
+        // Early exit if logging initialisation failed
+        return 0;
+    }
+
+    // Create temporary configuration file
+    std::string path = fuzz::writeTempConfig(true);
+    if (path.empty()) {
+        // Early exit if configuration file creation failed
+        fuzz::deleteTempFile(path);
+        return 0;
+    }
+
+    OptionCollection options;
+    std::unique_ptr<ControlledDhcpv6Srv> srv;
+    std::vector<uint8_t> buf(data, data + size);
+
+    try {
+        Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(data, size));
+        pkt->toText();
+        pkt->getType();
+        pkt->getTransid();
+        pkt->unpack();
+        pkt->pack();
+        pkt->getMAC(fdp->ConsumeIntegral<uint32_t>());
+        pkt->getName(fdp->ConsumeIntegral<uint8_t>());
+        pkt->getLabel();
+    } catch (...) {}
+
+    // OptionVendor parsing
+    try {
+        OptionBuffer buf(data, data + size);
+        OptionVendorClassPtr vendor_class;
+        vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V6,
+            buf.begin(),
+            buf.end()));
+    }catch(...){}
+
+    try {
+        // Package parsing
+        Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(data, size));
+        pkt->toText();
+        pkt->getType();
+        pkt->getTransid();
+
+        // Option parsing
+        LibDHCP::unpackOptions6(buf, DHCP6_OPTION_SPACE, options);
+        for (auto& kv : options) {
+            auto opt = kv.second;
+            if (!opt) {
+                continue;
+            }
+            opt->getType();
+            opt->toText();
+        }
+
+        // Server initialisation
+        srv.reset(new ControlledDhcpv6Srv());
+        srv->init(path);
+
+        // Process packet
+        if (srv) {
+            srv->processPacket(pkt);
+            srv->processDhcp6Query(pkt);
+        }
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    }
+
+    srv.reset();
+
+    // Remove temp configuration file
+    fuzz::deleteTempFile(path);
+    delete fdp;
+    return 0;
+}
diff --git a/src/fuzz/fuzz_dhcp_pkt_process4.cc b/src/fuzz/fuzz_dhcp_pkt_process4.cc
new file mode 100644 (file)
index 0000000..d23fb66
--- /dev/null
@@ -0,0 +1,271 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <dhcp/dhcp4.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcpsrv/callout_handle_store.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <log/logger_support.h>
+#include <util/filesystem.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+#include <list>
+#include <memory>
+#include <iostream>
+#include <filesystem>
+#include <fstream>
+#include <string>
+#include <cstdio>
+
+#include "helper_func.h"
+
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace isc::util;
+
+extern "C" int lease4_offer(CalloutHandle& handle);
+extern "C" int leases4_committed(CalloutHandle& handle);
+
+namespace isc {
+    namespace dhcp {
+        class MyDhcpv4Srv : public ControlledDhcpv4Srv {
+            public:
+                bool fuzz_accept(const Pkt4Ptr& pkt) {
+                    return accept(pkt);
+                }
+
+                static void fuzz_sanityCheck(const Pkt4Ptr& query) {
+                    ControlledDhcpv4Srv::sanityCheck(query);
+                }
+
+                void fuzz_classifyPacket(const Pkt4Ptr& pkt) {
+                    classifyPacket(pkt);
+                }
+
+                ConstSubnet4Ptr fuzz_selectSubnet(const Pkt4Ptr& query,
+                                                  bool& drop,
+                                                  bool allow_answer_park = true) {
+                    return selectSubnet(query, drop, allow_answer_park);
+                }
+        };
+    }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    if (size < 236) {
+        // package size requires at least 236 bytes
+        return 0;
+    }
+
+    // Disable validatePath checking to allow writing configuration file to /tmp
+    isc::util::file::PathChecker::enableEnforcement(false);
+
+    // Initialise logging
+    setenv("KEA_LOGGER_DESTINATION", "/dev/null", 0);
+    setenv("KEA_LOCKFILE_DIR", "/tmp", 0);
+    setenv("KEA_PIDFILE_DIR", "/tmp", 0);
+    setenv("KEA_LFC_EXECUTABLE", "/bin/true", 0);
+    try {
+        isc::log::initLogger("fuzzer");
+        isc::process::Daemon::loggerInit("fuzzer", false);
+        isc::process::Daemon::setDefaultLoggerName("fuzzer");
+    } catch (...) {
+        // Early exit if logging initialisation failed
+        return 0;
+    }
+
+    // Creating temp config file
+    std::string path = fuzz::writeTempConfig(true);
+    if (path.empty()) {
+        // Early exit if configuration file creation failed
+        fuzz::deleteTempFile(path);
+        return 0;
+    }
+
+    // Creating temp lease file
+    std::string lease_path = fuzz::writeTempLease(true);
+
+    Pkt4Ptr pkt;
+    std::unique_ptr<MyDhcpv4Srv> srv;
+
+    // Package parsing
+    try {
+        // Add fixed magic cookie and correct hardware address
+        std::vector<uint8_t> buf(data, data + size);
+        if (size >= 240) {
+            // Max hardware address length is 20
+            buf[2] = 20;
+
+            // Magic cookie fixed value 0x63825363
+            buf[236] = 0x63;
+            buf[237] = 0x82;
+            buf[238] = 0x53;
+            buf[239] = 0x63;
+        }
+
+        pkt = Pkt4Ptr(new Pkt4(buf.data(), buf.size()));
+        pkt->unpack();
+    } catch (...) {
+        // Early exit if package parsing failed.
+        return 0;
+    }
+
+    // Configure random value in packet
+    FuzzedDataProvider fdp(data, size);
+    uint8_t typeChoice = fdp.ConsumeIntegralInRange<uint8_t>(0, 8);
+    pkt->setType(static_cast<DHCPMessageType>(typeChoice));
+
+    // Server initialisation
+    try {
+        srv.reset(new MyDhcpv4Srv());
+        srv->init(path);
+    } catch (...) {
+        // Early exit if server initialisation failed.
+        return 0;
+    }
+
+    if (!srv) {
+        // Early exit if server initialisation failed.
+        return 0;
+    }
+
+    // Call classifyPacket for packet checking
+    try {
+        srv->fuzz_classifyPacket(pkt);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Call accept for packet checking
+    try {
+        srv->fuzz_accept(pkt);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Call sanityCheck for packet checking
+    try {
+        MyDhcpv4Srv::fuzz_sanityCheck(pkt);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Prepare client context
+    AllocEngine::ClientContext4Ptr ctx(new AllocEngine::ClientContext4());
+
+    // Call earlyGHRLookup
+    try {
+        srv->earlyGHRLookup(pkt, ctx);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Call select subnet
+    try {
+        bool drop = false;
+        if (!ctx) {
+            ctx.reset(new AllocEngine::ClientContext4());
+        }
+        if (ctx) {
+            ctx->subnet_ = srv->fuzz_selectSubnet(pkt, drop, false);
+        }
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Call processLocalizedQuery4
+    try {
+        srv->processLocalizedQuery4(ctx, false);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Prepare callout handle
+    CalloutHandlePtr handle = getCalloutHandle(pkt);
+    Pkt4Ptr rsp;
+
+    // Call lease4_offer
+    try {
+        uint8_t mac_addr[6];
+        for (size_t i = 0; i < 6; ++i) {
+            mac_addr[i] = fdp.ConsumeIntegral<uint8_t>();
+        }
+        HWAddr hw(mac_addr, sizeof(mac_addr), HTYPE_ETHER);
+        Lease4Collection leases = LeaseMgrFactory::instance().getLease4(hw);
+        handle->setArgument("leases4", leases);
+        if (!ctx) {
+            ctx.reset(new AllocEngine::ClientContext4());
+        }
+        if (ctx) {
+            handle->setArgument("offer_lifetime", ctx->offer_lft_);
+            handle->setArgument("old_lease", ctx->old_lease_);
+            handle->setArgument("host", ctx->currentHost());
+        }
+        handle->setArgument("query4", pkt);
+        handle->setArgument("response4", rsp);
+
+        lease4_offer(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean up to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    // Call lease4_committed
+    try {
+        uint8_t mac_addr[6];
+        for (size_t i = 0; i < 6; ++i) {
+            mac_addr[i] = fdp.ConsumeIntegral<uint8_t>();
+        }
+        HWAddr hw(mac_addr, sizeof(mac_addr), HTYPE_ETHER);
+        Lease4Collection leases = LeaseMgrFactory::instance().getLease4(hw);
+        handle->setArgument("leases4", leases);
+        handle->setArgument("query4", pkt);
+        handle->setArgument("response4", rsp);
+
+        leases4_committed(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean up to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    srv.reset();
+
+    // Remove temp files
+    fuzz::deleteTempFile(path);
+    fuzz::deleteTempFile(lease_path);
+    return 0;
+}
diff --git a/src/fuzz/fuzz_dhcp_pkt_process6.cc b/src/fuzz/fuzz_dhcp_pkt_process6.cc
new file mode 100644 (file)
index 0000000..cbe7813
--- /dev/null
@@ -0,0 +1,219 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <dhcp/dhcp6.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcpsrv/callout_handle_store.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <log/logger_support.h>
+#include <util/filesystem.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+#include <list>
+#include <memory>
+#include <iostream>
+#include <filesystem>
+#include <fstream>
+#include <string>
+#include <cstdio>
+#include <cstdlib>
+
+#include "helper_func.h"
+
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace isc::util;
+
+extern "C" int leases6_committed(CalloutHandle& handle);
+
+namespace isc {
+    namespace dhcp {
+        class MyDhcpv6Srv : public ControlledDhcpv6Srv {
+            public:
+                void fuzz_sanityCheck(const Pkt6Ptr& query) {
+                    sanityCheck(query);
+                }
+
+                void fuzz_classifyPacket(const Pkt6Ptr& pkt) {
+                    classifyPacket(pkt);
+                }
+
+                ConstSubnet6Ptr fuzz_selectSubnet(const Pkt6Ptr& question, bool& drop) {
+                    return selectSubnet(question, drop);
+                }
+        };
+    }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    if (size < 236) {
+        // package size requires at least 236 bytes
+        return 0;
+    }
+
+    // Disable validatePath checking to allow writing configuration file to /tmp
+    isc::util::file::PathChecker::enableEnforcement(false);
+
+    // Force DUID file to /tmp
+    setenv("KEA_DHCP_DATA_DIR", "/tmp", 1);
+
+    // Initialise logging
+    setenv("KEA_LOGGER_DESTINATION", "/dev/null", 0);
+    setenv("KEA_LOCKFILE_DIR", "/tmp", 0);
+    setenv("KEA_PIDFILE_DIR", "/tmp", 0);
+    setenv("KEA_LFC_EXECUTABLE", "/bin/true", 0);
+    try {
+        isc::log::initLogger("fuzzer");
+        isc::process::Daemon::loggerInit("fuzzer", false);
+        isc::process::Daemon::setDefaultLoggerName("fuzzer");
+    } catch (...) {
+        // Early exit if logging initialisation failed
+        return 0;
+    }
+
+    // Creating temp config file
+    std::string path = fuzz::writeTempConfig(false);
+    if (path.empty()) {
+        // Early exit if configuration file creation failed
+        fuzz::deleteTempFile(path);
+        return 0;
+    }
+
+    // Creating temp lease file
+    std::string lease_path = fuzz::writeTempLease(false);
+
+    Pkt6Ptr pkt;
+    std::unique_ptr<MyDhcpv6Srv> srv;
+
+    // Package parsing
+    try {
+        pkt = Pkt6Ptr(new Pkt6(data, size));
+        pkt->unpack();
+    } catch (...) {
+        // Early exit if package parsing failed.
+        return 0;
+    }
+
+    // Configure random value in packet
+    FuzzedDataProvider fdp(data, size);
+    uint8_t typeChoice = fdp.ConsumeIntegralInRange<uint8_t>(0, 37);
+    pkt->setType(static_cast<DHCPv6MessageType>(typeChoice));
+
+    // Server initialisation
+    try {
+        srv.reset(new MyDhcpv6Srv());
+        srv->init(path);
+    } catch (...) {
+        // Early exit if server initialisation failed.
+        return 0;
+    }
+
+    if (!srv) {
+        // Early exit if server initialisation failed.
+        return 0;
+    }
+
+    // Call classifyPacket for packet checking
+    try {
+        srv->fuzz_classifyPacket(pkt);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Call sanityCheck for packet checking
+    try {
+        srv->fuzz_sanityCheck(pkt);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Call process functions after the accept and check
+    try {
+        srv->processDhcp6Query(pkt);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Prepare client context
+    AllocEngine::ClientContext6 ctx;
+
+    // Call earlyGHRLookup
+    try {
+        srv->earlyGHRLookup(pkt, ctx);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Call select subnet
+    try {
+        bool drop = false;
+        ctx.subnet_ = srv->fuzz_selectSubnet(pkt, drop);
+    } catch (const isc::Exception& e) {
+       // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Call processLocalizedQuery6
+    try {
+        srv->processLocalizedQuery6(ctx);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Prepare callout handle
+    CalloutHandlePtr handle = getCalloutHandle(pkt);
+    Pkt6Ptr rsp;
+
+    // Call lease4_committed
+    try {
+        uint8_t mac_addr[6];
+        for (size_t i = 0; i < 6; ++i) {
+            mac_addr[i] = fdp.ConsumeIntegral<uint8_t>();
+        }
+        HWAddr hw(mac_addr, sizeof(mac_addr), HTYPE_ETHER);
+        Lease6Collection leases = LeaseMgrFactory::instance().getLease6(hw);
+        handle->setArgument("leases6", leases);
+        handle->setArgument("query6", pkt);
+        handle->setArgument("response6", rsp);
+
+        leases6_committed(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean up to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    srv.reset();
+
+    // Remove temp files
+    fuzz::deleteTempFile(path);
+    fuzz::deleteTempFile(lease_path);
+
+    return 0;
+}
diff --git a/src/fuzz/fuzz_dhcpsrv.cc b/src/fuzz/fuzz_dhcpsrv.cc
new file mode 100644 (file)
index 0000000..7740a60
--- /dev/null
@@ -0,0 +1,211 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <string>
+
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+
+#include <dhcpsrv/srv_config.h>
+#include <dhcpsrv/cfg_option_def.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/cfg_iface.h>
+#include <dhcpsrv/cfg_duid.h>
+#include <dhcpsrv/cfg_expiration.h>
+#include <dhcpsrv/cfg_mac_source.h>
+#include <dhcpsrv/client_class_def.h>
+
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <dhcpsrv/parsers/option_data_parser.h>
+#include <dhcpsrv/parsers/ifaces_config_parser.h>
+#include <dhcpsrv/parsers/duid_config_parser.h>
+#include <dhcpsrv/parsers/multi_threading_config_parser.h>
+#include <dhcpsrv/parsers/sanity_checks_parser.h>
+#include <dhcpsrv/parsers/expiration_config_parser.h>
+#include <dhcpsrv/parsers/client_class_def_parser.h>
+#include <dhcpsrv/parsers/host_reservation_parser.h>
+#include <dhcpsrv/parsers/simple_parser4.h>
+#include <dhcpsrv/parsers/simple_parser6.h>
+
+#include "helper_func.h"
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) {
+    FuzzedDataProvider fdp(Data, Size);
+    std::string payload = fdp.ConsumeRandomLengthString();
+
+    std::string raw_payload(reinterpret_cast<const char*>(Data), Size);
+    ElementPtr payload_elem =  nullptr;
+    try {
+        payload_elem = Element::fromJSON(payload);
+    } catch (...) {
+        return 0;
+    }
+
+
+    try {
+        // Simple Parsing
+        SimpleParser4::setAllDefaults(payload_elem);
+        SimpleParser4::deriveParameters(payload_elem);
+        SimpleParser6::setAllDefaults(payload_elem);
+        SimpleParser6::deriveParameters(payload_elem);
+    } catch (const isc::Exception&) {}
+    try {
+        // Configuration Option definition parsing
+        //ElementPtr elem = fuzz::parseJSON(payload);
+        CfgOptionDefPtr defs(new CfgOptionDef());
+        OptionDefListParser defp4(AF_INET);
+        defp4.parse(defs, payload_elem);
+        //elem = fuzz::parseJSON(fdp.ConsumeRandomLengthString());
+        OptionDefListParser defp6(AF_INET6);
+        defp6.parse(defs, payload_elem);
+    } catch (const isc::Exception&) {}
+    try {
+
+        // Configuration Option data parsing
+        //ElementPtr elem = fuzz::parseJSON(payload);
+        CfgOptionPtr opts(new CfgOption());
+        CfgOptionDefPtr defs(new CfgOptionDef());
+        OptionDataListParser odlp4(AF_INET, defs);
+        odlp4.parse(opts, payload_elem, fdp.ConsumeBool());
+        //elem = fuzz::parseJSON(fdp.ConsumeRandomLengthString());
+        OptionDataListParser odlp6(AF_INET6, defs);
+        odlp6.parse(opts, payload_elem, fdp.ConsumeBool());
+    } catch (const isc::Exception&) {}
+    try {
+
+        // Interfaces configuration parsing
+        //ElementPtr elem = fuzz::parseJSON(payload);
+        CfgIfacePtr ifcfg(new CfgIface());
+        IfacesConfigParser ifparser4(AF_INET, false);
+        ifparser4.parse(ifcfg, payload_elem);
+    } catch (const isc::Exception&) {}
+    try {
+        //ElementPtr elem = fuzz::parseJSON(payload);
+        CfgIfacePtr ifcfg(new CfgIface());
+        IfacesConfigParser ifparser6(AF_INET6, false);
+        ifparser6.parse(ifcfg, payload_elem);
+    } catch (const isc::Exception&) {}
+    try {
+
+        // DUID configuration parsing
+        ElementPtr elem = fuzz::parseJSON(payload);
+        CfgDUIDPtr duid(new CfgDUID());
+        DUIDConfigParser duidp;
+        duidp.parse(duid, elem);
+    } catch (const isc::Exception&) {}
+    try {
+
+        // Configuration expiration parsing
+        //ElementPtr elem = fuzz::parseJSON(payload);
+        CfgExpirationPtr exp(new CfgExpiration());
+        ExpirationConfigParser expp;
+        expp.parse(payload_elem, exp);
+    } catch (const isc::Exception&) {}
+    try {
+
+        // MAC list parsing
+        CfgMACSource macs;
+        MACSourcesListConfigParser macp;
+        //ElementPtr elem = fuzz::parseJSON(payload);
+        macp.parse(macs, payload_elem);
+    } catch (const isc::Exception&) {}
+    try {
+
+        // Multi-Threading configuration parsing
+        SrvConfig srv;
+        //ElementPtr elem = fuzz::parseJSON(payload);
+        MultiThreadingConfigParser mtcp;
+        mtcp.parse(srv, payload_elem);
+    } catch (const isc::Exception&) {}
+
+    try {
+
+        // Sanity Check parsing
+        SrvConfig srv;
+        //ElementPtr elem = fuzz::parseJSON(payload);
+        SanityChecksParser scp;
+        scp.parse(srv, payload_elem);
+    } catch (const isc::Exception&) {}
+    try {
+
+        // Client Class definition parsing
+        // ElementPtr elem = fuzz::parseJSON(payload);
+        ClientClassDictionaryPtr dict(new ClientClassDictionary());
+        ClientClassDefParser ccdp;
+        ccdp.parse(dict, payload_elem, AF_INET);
+        ccdp.parse(dict, payload_elem, AF_INET6);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    }
+
+
+    try {
+        // SubnetConfigParser parsing
+        //ElementPtr elem = fuzz::parseJSON(payload);
+        Subnet4ConfigParser scf(fdp.ConsumeBool());
+        scf.parse(payload_elem, fdp.ConsumeBool());
+    }
+    catch (const isc::Exception&) {
+        // Slient exceptions
+    }
+
+
+    // ControlSocketsParser
+    try {
+        ElementPtr elem = fuzz::parseJSON(payload);
+        SrvConfig srv;
+        ControlSocketsParser csp;
+        csp.parse(srv, elem);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    }
+
+    try {
+        // Subnet6ConfigParser parsing
+        ElementPtr elem = fuzz::parseJSON(payload);
+        Subnet6ConfigParser scf(fdp.ConsumeBool());
+        scf.parse(elem, fdp.ConsumeBool());
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    }
+
+    try {
+        // D2ClientConfigParser parsing
+        ElementPtr elem = fuzz::parseJSON(payload);
+        D2ClientConfigParser d2p;
+        d2p.parse(elem);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    }
+
+    // Host Reservation parsing
+    try {
+        // Host Reservation parsing
+        ElementPtr elem = fuzz::parseJSON(payload);
+        HostReservationParser4 hrp;
+        hrp.parse(SubnetID(10), elem, fdp.ConsumeBool());
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    }
+
+    try {
+        // Host Reservation parsing
+        ElementPtr elem = fuzz::parseJSON(payload);
+        HostReservationParser6 hrp;
+        hrp.parse(SubnetID(10), elem, fdp.ConsumeBool());
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    }    
+    return 0;
+}
diff --git a/src/fuzz/fuzz_dhcpsrv.dict b/src/fuzz/fuzz_dhcpsrv.dict
new file mode 100644 (file)
index 0000000..9f94caa
--- /dev/null
@@ -0,0 +1,327 @@
+"{"
+"}"
+"["
+"]"
+":"
+","
+"true"
+"false"
+"null"
+"0"
+"1"
+"2"
+"3"
+"4"
+"5"
+"6"
+"7"
+"8"
+"9"
+"Dhcp4"
+"Dhcp6"
+"interfaces-config"
+"interfaces"
+"*"
+"re-detect"
+"dhcp-socket-type"
+"raw"
+"udp"
+"outbound-interface"
+"same-as-inbound"
+"use-routing"
+"listen-dhcp-init-reboot"
+"lease-database"
+"type"
+"memfile"
+"mysql"
+"postgresql"
+"host"
+"port"
+"name"
+"user"
+"password"
+"persist"
+"lfc-interval"
+"lfc-cleanup-interval"
+"max-row-errors"
+"hooks-libraries"
+"library"
+"parameters"
+"loggers"
+"name"
+"output_options"
+"output"
+"severity"
+"debuglevel"
+"shared-networks"
+"shared-networks6"
+"name"
+"id"
+"subnet4"
+"subnet6"
+"subnet"
+"pools"
+"pool"
+"prefix"
+"prefix-len"
+"excluded-prefix"
+"excluded-prefix-len"
+"interface"
+"relay"
+"ip-address"
+"ip-addresses"
+"option-data"
+"option-def"
+"option-data-fields"
+"client-class"
+"client-classes"
+"require-client-classes"
+"match-client-id"
+"authoritative"
+"valid-lifetime"
+"min-valid-lifetime"
+"max-valid-lifetime"
+"preferred-lifetime"
+"renew-timer"
+"rebind-timer"
+"t1-percent"
+"t2-percent"
+"expired-leases-processing"
+"reclaim-timer-wait-time"
+"hold-reclaimed-time"
+"max-reclaim-leases"
+"max-reclaim-time"
+"unwarned-reclaim-cycles"
+"space"
+"dhcp4"
+"dhcp6"
+"code"
+"encapsulate"
+"array"
+"record-types"
+"record-len"
+"csv-format"
+"always-send"
+"never-send"
+"data"
+"len"
+"hex"
+"string"
+"uint8"
+"uint16"
+"uint32"
+"ipv4-address"
+"ipv6-address"
+"fqdn"
+"boolean"
+"name"
+"routers"
+"domain-name-servers"
+"domain-name"
+"netmask"
+"broadcast-address"
+"time-servers"
+"ntp-servers"
+"tftp-server-name"
+"server-name"
+"boot-file-name"
+"vendor-encapsulated-options"
+"vendor-specific-information"
+"hostname"
+"client-identifier"
+"parameter-request-list"
+"dns-servers"
+"domain-search"
+"ia-na"
+"ia-ta"
+"ia-pd"
+"fqdn"
+"sntp-servers"
+"information-refresh-time"
+"vendor-specific-information"
+"server-id"
+"duid"
+"duid-config"
+"duid-type"
+"LLT"
+"LL"
+"EN"
+"UUID"
+"identifier"
+"time"
+"htype"
+"hw-address"
+"enterprise-id"
+"mac-sources"
+"any"
+"client-id"
+"duid"
+"hwaddr"
+"remote-id"
+"subscriber-id"
+"circuit-id"
+"client-linkaddr"
+"src-addr"
+"multi-threading"
+"enable-multi-threading"
+"thread-pool-size"
+"packet-queue-size"
+"sanity-checks"
+"lease-checks"
+"extended-info-checks"
+"warn-if-queries-too-frequent"
+"hwaddr-duplicates"
+"client-id-duplicates"
+"pd-duplicates"
+"client-classes"
+"test"
+"only-if-required"
+"template-test"
+"evaluate"
+"expression"
+"evaluate-add"
+"option"
+"substring"
+"concat"
+"ifelse"
+"equals"
+"not"
+"and"
+"or"
+"hexstring"
+"pkt4"
+"pkt6"
+"relay4"
+"relay6"
+"vendor"
+"vendor-class"
+"hardware"
+"src"
+"dst"
+"reservations"
+"hostname"
+"ip-addresses"
+"ip-address"
+"duid"
+"hw-address"
+"client-id"
+"flex-id"
+"next-server"
+"server-hostname"
+"boot-file-name"
+"require-client-classes"
+"reservation-mode"
+"disabled"
+"out-of-pool"
+"global"
+"unassigned"
+"rapid-commit"
+"ddns-qualifying-suffix"
+"ddns-override-no-update"
+"ddns-override-client-update"
+"ddns-replace-client-name"
+"lease-database"
+"host-reservation-identifiers"
+"store-extended-info"
+"rebind-timer"
+"{"
+"}"
+"["
+"]"
+":"
+","
+"\""
+"\\"
+"{}"
+"[]"
+"\"\""
+"{\"name\":\"x\"}"
+"{\"id\":1}"
+"{\"code\":1}"
+"{\"data\":\"01:02:03\"}"
+"{\"option-data\":[]}"
+"{\"option-def\":[]}"
+"{\"interfaces\":[\"*\"]}"
+"{\"subnet4\":[{\"subnet\":\"192.0.2.0/24\"}]}"
+"{\"subnet6\":[{\"subnet\":\"2001:db8::/64\"}]}"
+"{\"shared-networks\":[{\"name\":\"net-a\",\"subnet4\":[]}]}"
+"192.0.2.0/24"
+"198.51.100.0/24"
+"203.0.113.0/24"
+"10.0.0.0/8"
+"172.16.0.0/12"
+"0.0.0.0"
+"255.255.255.255"
+"2001:db8::/32"
+"2001:db8::/64"
+"::"
+"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
+"prefixes"
+"hostname"
+"ip-addresses"
+"excluded-prefixes"
+"option-data"
+"client-classes"
+"user-context"
+"socket-address"
+"socket-port"
+"cert-required"
+"http-headers"
+"http_header_params"
+"value"
+"http_header"
+"type"
+"basic"
+"auth_params"
+"realm"
+"clients"
+"directory"
+"user-file"
+"password-file"
+"dhcp-queue-control"
+"queue_control_params"
+"enable-queue"
+"queue-type"
+"capacity"
+"constant string"
+"sub_dhcp_ddns"
+"dhcp_ddns_params"
+"enable-updates"
+"server-ip"
+"server-port"
+"sender-ip"
+"max-queue-size"
+"ncr-format"
+"config-control"
+"udp"
+"config-databases"
+"config-fetch-wait-time"
+"lenient-option-parsing"
+"compatibility"
+"test"
+"template-test"
+"option-def"
+"option-data"
+"user-context"
+"only-if-required"
+"only-in-additional-list"
+"next-server"
+"server-hostname"
+"boot-file-name"
+"offer-lifetime"
+"preferred-lifetime"
+"offer-lifetime"
+"adaptive-lease-time-threshold"
+"require-client-classes"
+"evaluate-additional-classes"
+"client-classes"
+"client-class"
+"cache-threshold"
+"cache-max-age"
+"iterative"
+"allocator"
+"pd-allocator"
+"reservations-global"
+"reservations-in-subnet"
+"reservations-out-of-pool"
+"store-extended-info"
diff --git a/src/fuzz/fuzz_dhcpsrv_csv_lease.cc b/src/fuzz/fuzz_dhcpsrv_csv_lease.cc
new file mode 100644 (file)
index 0000000..9f131b4
--- /dev/null
@@ -0,0 +1,52 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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_address.h>
+#include <dhcp/duid.h>
+#include <dhcpsrv/csv_lease_file4.h>
+#include <dhcpsrv/csv_lease_file6.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/testutils/lease_file_io.h>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) {
+       char filename[256];
+       sprintf(filename, "/tmp/libfuzzer.%d", getpid());
+
+       FILE *fp = fopen(filename, "wb");
+       if (!fp)
+               return 0;
+       fwrite(Data, Size, 1, fp);
+       fclose(fp);
+
+    try {
+        CSVLeaseFile4 lease_file(filename);
+        lease_file.open(false);
+        Lease4Ptr lease;
+        lease_file.next(lease);
+        lease_file.close();
+    } catch (const std::exception&) {
+      // ignore any errors
+    }
+
+    try {
+        CSVLeaseFile6 lease_file(filename);
+        lease_file.open(false);
+        Lease6Ptr lease;
+        lease_file.next(lease);
+        lease_file.close();
+    } catch (const std::exception&) {
+        // ignore any errors
+    }
+
+    unlink(filename);
+    return 0;
+}
diff --git a/src/fuzz/fuzz_dns.cc b/src/fuzz/fuzz_dns.cc
new file mode 100644 (file)
index 0000000..e484fc9
--- /dev/null
@@ -0,0 +1,323 @@
+// Copyright (C) 2025 Ada Logics Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <dns/exceptions.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/opcode.h>
+#include <dns/question.h>
+#include <dns/rcode.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+#include <dns/rrtype.h>
+#include <dns/tsig.h>
+#include <dns/tsigkey.h>
+#include <dns/tsigrecord.h>
+#include <dns/master_lexer.h>
+#include <dns/master_loader.h>
+#include <util/buffer.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
+using namespace isc::dns;
+using namespace isc::util;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    if (size < 2) {
+        return 0;
+    }
+
+    FuzzedDataProvider fdp(data, size);
+    
+    // Get a choice for which fuzzing path to take
+    uint8_t choice = fdp.ConsumeIntegral<uint8_t>();
+    
+    // Reserve some data for different operations
+    std::vector<uint8_t> wire_data = fdp.ConsumeBytes<uint8_t>(fdp.remaining_bytes() / 2);
+    std::string string_data = fdp.ConsumeRemainingBytesAsString();
+    
+    // Fuzz DNS Name parsing from string
+    if (choice % 8 == 0 && !string_data.empty()) {
+        try {
+            Name name(string_data);
+            // Try various Name operations
+            try {
+                std::string text = name.toText();
+                OutputBuffer buffer(0);
+                name.toWire(buffer);
+                
+                // Try splitting at different positions
+                if (name.getLabelCount() > 0) {
+                    Name stripped = name.split(0);
+                    Name reversed = name.reverse();
+                }
+                
+                // Try comparison operations
+                Name root = Name::ROOT_NAME();
+                name.compare(root);
+                
+            } catch (const std::exception&) {
+                // Ignore exceptions from operations
+            }
+        } catch (const std::exception&) {
+            // Ignore exceptions from parsing
+        }
+    }
+    
+    // Fuzz DNS Name parsing from wire format
+    if (choice % 8 == 1 && !wire_data.empty()) {
+        try {
+            InputBuffer buffer(&wire_data[0], wire_data.size());
+            Name name(buffer);
+            
+            // Try operations on the parsed name
+            try {
+                name.toText();
+                name.getLabelCount();
+                name.getLength();
+            } catch (const std::exception&) {
+                // Ignore exceptions
+            }
+        } catch (const std::exception&) {
+            // Ignore exceptions from parsing
+        }
+    }
+    
+    // Fuzz DNS Message parsing from wire
+    if (choice % 8 == 2 && !wire_data.empty()) {
+        try {
+            InputBuffer buffer(&wire_data[0], wire_data.size());
+            Message message(Message::PARSE);
+            message.fromWire(buffer);
+            
+            // Try various Message operations
+            try {
+                message.getHeaderFlag(Message::HEADERFLAG_AA);
+                message.getRcode();
+                message.getQid();
+                message.getRRCount(Message::SECTION_ANSWER);
+                
+                // Try iterating through sections
+                for (int sec = Message::SECTION_QUESTION;
+                     sec <= Message::SECTION_ADDITIONAL;
+                     ++sec) {
+                    Message::Section section = static_cast<Message::Section>(sec);
+                    try {
+                        auto it = message.beginSection(section);
+                        auto it_end = message.endSection(section);
+                        while (it != it_end) {
+                            ++it;
+                        }
+                    } catch (const std::exception&) {
+                        // Ignore iteration exceptions
+                    }
+                }
+                
+                // Try rendering back to wire
+                MessageRenderer renderer;
+                try {
+                    message.toWire(renderer);
+                } catch (const std::exception&) {
+                    // Ignore rendering exceptions
+                }
+                
+            } catch (const std::exception&) {
+                // Ignore operation exceptions
+            }
+        } catch (const std::exception&) {
+            // Ignore parsing exceptions
+        }
+    }
+    
+    // Fuzz Question parsing
+    if (choice % 8 == 3 && !wire_data.empty()) {
+        try {
+            InputBuffer buffer(&wire_data[0], wire_data.size());
+            Question question(buffer);
+            
+            try {
+                question.toText();
+                question.getName();
+                question.getType();
+                question.getClass();
+                
+                OutputBuffer out_buffer(0);
+                question.toWire(out_buffer);
+            } catch (const std::exception&) {
+                // Ignore operation exceptions
+            }
+        } catch (const std::exception&) {
+            // Ignore parsing exceptions
+        }
+    }
+    
+    // Fuzz RRset operations
+    if (choice % 8 == 4 && !string_data.empty() && !wire_data.empty()) {
+        try {
+            Name name(string_data);
+            RRsetPtr rrset = RRsetPtr(new RRset(name, RRClass::IN(), 
+                                                 RRType::A(), RRTTL(3600)));
+            
+            // Try parsing RDATA from wire
+            try {
+                InputBuffer buffer(&wire_data[0], wire_data.size());
+                if (wire_data.size() >= 4) {
+                    rdata::ConstRdataPtr rdata = 
+                        rdata::createRdata(RRType::A(), RRClass::IN(), 
+                                          buffer, wire_data.size());
+                    rrset->addRdata(rdata);
+                }
+            } catch (const std::exception&) {
+                // Ignore RDATA parsing exceptions
+            }
+            
+            // Try RRset operations
+            try {
+                rrset->toText();
+                rrset->getRdataCount();
+                
+                OutputBuffer out_buffer(0);
+                rrset->toWire(out_buffer);
+            } catch (const std::exception&) {
+                // Ignore operation exceptions
+            }
+        } catch (const std::exception&) {
+            // Ignore exceptions
+        }
+    }
+    
+    // Fuzz TSIG operations
+    if (choice % 8 == 5 && !string_data.empty() && wire_data.size() >= 16) {
+        try {
+            // Try creating a TSIG key
+            TSIGKey key(string_data + ":secret");
+            
+            // Try creating TSIG RDATA and then a TSIG record
+            try {
+                InputBuffer buffer(&wire_data[0], wire_data.size());
+                // Try to parse TSIG RDATA
+                rdata::ConstRdataPtr rdata = 
+                    rdata::createRdata(RRType::TSIG(), RRClass::ANY(), 
+                                      buffer, wire_data.size());
+                const rdata::any::TSIG& tsig_rdata = 
+                    dynamic_cast<const rdata::any::TSIG&>(*rdata);
+                
+                // Create a TSIGRecord
+                Name key_name(string_data);
+                TSIGRecord tsig(key_name, tsig_rdata);
+                tsig.toText();
+                
+                OutputBuffer out_buffer(0);
+                tsig.toWire(out_buffer);
+            } catch (const std::exception&) {
+                // Ignore TSIG parsing exceptions
+            }
+            
+            // Try TSIG context operations (sign operation is public)
+            try {
+                TSIGContext ctx(key);
+                // Try signing some data
+                if (!wire_data.empty()) {
+                    ConstTSIGRecordPtr tsig_record = ctx.sign(0, &wire_data[0], wire_data.size());
+                }
+            } catch (const std::exception&) {
+                // Ignore context exceptions
+            }
+        } catch (const std::exception&) {
+            // Ignore key creation exceptions
+        }
+    }
+    
+    // Fuzz MasterLexer with string input
+    if (choice % 8 == 6 && !string_data.empty()) {
+        try {
+            std::istringstream iss(string_data);
+            MasterLexer lexer;
+            lexer.pushSource(iss);
+            
+            // Try tokenizing (loop until we hit EOF token)
+            for (int i = 0; i < 100; ++i) {
+                try {
+                    const MasterToken& token = lexer.getNextToken();
+                    
+                    // Stop if we hit EOF
+                    if (token.getType() == MasterToken::END_OF_FILE) {
+                        break;
+                    }
+                    
+                    // Access token properties based on type
+                    if (token.getType() == MasterToken::STRING ||
+                        token.getType() == MasterToken::QSTRING) {
+                        token.getString();
+                        token.getStringRegion();
+                    } else if (token.getType() == MasterToken::NUMBER) {
+                        token.getNumber();
+                    } else if (token.getType() == MasterToken::ERROR) {
+                        token.getErrorCode();
+                        token.getErrorText();
+                    }
+                } catch (const std::exception&) {
+                    break;
+                }
+            }
+        } catch (const std::exception&) {
+            // Ignore lexer exceptions
+        }
+    }
+    
+    // Fuzz Message rendering operations
+    if (choice % 8 == 7 && !string_data.empty()) {
+        try {
+            Message message(Message::RENDER);
+            message.setQid(fdp.ConsumeIntegral<uint16_t>());
+            message.setOpcode(Opcode::QUERY());
+            message.setRcode(Rcode::NOERROR());
+            
+            // Try setting various flags
+            message.setHeaderFlag(Message::HEADERFLAG_AA, 
+                                 fdp.ConsumeBool());
+            message.setHeaderFlag(Message::HEADERFLAG_RD, 
+                                 fdp.ConsumeBool());
+            message.setHeaderFlag(Message::HEADERFLAG_RA, 
+                                 fdp.ConsumeBool());
+            
+            // Try adding a question
+            try {
+                Name qname(string_data);
+                QuestionPtr question(new Question(qname, RRClass::IN(), 
+                                                   RRType::A()));
+                message.addQuestion(question);
+            } catch (const std::exception&) {
+                // Ignore question addition exceptions
+            }
+            
+            // Try rendering
+            try {
+                MessageRenderer renderer;
+                message.toWire(renderer);
+            } catch (const std::exception&) {
+                // Ignore rendering exceptions
+            }
+        } catch (const std::exception&) {
+            // Ignore message creation exceptions
+        }
+    }
+    
+    return 0;
+}
diff --git a/src/fuzz/fuzz_dns.dict b/src/fuzz/fuzz_dns.dict
new file mode 100644 (file)
index 0000000..84e8d77
--- /dev/null
@@ -0,0 +1,109 @@
+# DNS fuzzing dictionary
+# Common DNS wire format markers
+"\x00"
+"\x01"
+"\x02"
+"\x03"
+"\x04"
+"\x05"
+"\x0c"
+"\x0d"
+"\x1f"
+"\x20"
+"\x3f"
+"\x40"
+"\x7f"
+"\xff"
+
+# DNS header patterns
+"\x00\x00\x01\x00"
+"\x00\x00\x81\x80"
+"\x00\x01\x00\x00"
+"\x00\x00\x00\x01"
+
+# Common DNS names and labels
+"."
+".."
+"example"
+"example.com"
+"example.org"
+"localhost"
+"www"
+"ns"
+"ns1"
+"ns2"
+"mail"
+"@"
+"*"
+"_"
+
+# DNS escape sequences
+"\\"
+"\\."
+"\\@"
+"\\000"
+"\\255"
+"\\032"
+
+# RR type values (in text)
+"A"
+"AAAA"
+"NS"
+"SOA"
+"MX"
+"CNAME"
+"PTR"
+"TXT"
+"SRV"
+"TSIG"
+"OPT"
+"DNSKEY"
+"DS"
+"RRSIG"
+"NSEC"
+"NSEC3"
+
+# Class values
+"IN"
+"CH"
+"HS"
+"ANY"
+"NONE"
+
+# TSIG algorithm names
+"HMAC-MD5.SIG-ALG.REG.INT"
+"hmac-sha1"
+"hmac-sha256"
+"hmac-sha384"
+"hmac-sha512"
+
+# Long labels (near 63-byte limit)
+"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+
+# Maximum length domain name components
+"a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.0.1.2.3.4.5.6.7.8.9"
+
+# DNS query/response patterns
+"\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00"
+"\x00\x00\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00"
+
+# Common port numbers and values
+"53"
+"5353"
+"853"
+
+# EDNS OPT pseudo-RR
+"\x00\x00\x29"
+
+# Compression pointers
+"\xc0\x00"
+"\xc0\x0c"
+"\xc0\x10"
+"\xc0\x20"
+"\xc0\xff"
+
+# TSIG MAC sizes
+"\x00\x10"
+"\x00\x14"
+"\x00\x20"
diff --git a/src/fuzz/fuzz_encode.cc b/src/fuzz/fuzz_encode.cc
new file mode 100644 (file)
index 0000000..99446d9
--- /dev/null
@@ -0,0 +1,213 @@
+// Copyright (C) 2025 Ada Logics Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <exceptions/exceptions.h>
+#include <util/encode/encode.h>
+
+#include <string>
+#include <vector>
+#include <cstddef>
+
+using namespace isc::util::encode;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    if (size < 2) {
+        return 0;
+    }
+
+    FuzzedDataProvider fdp(data, size);
+    
+    // Choose which encoding/decoding path to test
+    uint8_t path = fdp.ConsumeIntegralInRange<uint8_t>(0, 11);
+    
+    std::vector<uint8_t> binary_data;
+    std::string encoded_str;
+    std::vector<uint8_t> decoded_output;
+    
+    switch (path) {
+        case 0: {
+            // Test Base64 encoding from binary data
+            try {
+                size_t bin_size = fdp.ConsumeIntegralInRange<size_t>(0, size);
+                binary_data = fdp.ConsumeBytes<uint8_t>(bin_size);
+                encoded_str = encodeBase64(binary_data);
+                // Verify round-trip
+                decodeBase64(encoded_str, decoded_output);
+            } catch (const isc::Exception&) {
+                // Expected for invalid input
+            }
+            break;
+        }
+        
+        case 1: {
+            // Test Base64 decoding from string
+            try {
+                encoded_str = fdp.ConsumeRemainingBytesAsString();
+                decodeBase64(encoded_str, decoded_output);
+            } catch (const isc::Exception&) {
+                // Expected for invalid Base64 strings
+            }
+            break;
+        }
+        
+        case 2: {
+            // Test Base32Hex encoding from binary data
+            try {
+                size_t bin_size = fdp.ConsumeIntegralInRange<size_t>(0, size);
+                binary_data = fdp.ConsumeBytes<uint8_t>(bin_size);
+                encoded_str = encodeBase32Hex(binary_data);
+                // Verify round-trip
+                decodeBase32Hex(encoded_str, decoded_output);
+            } catch (const isc::Exception&) {
+                // Expected for invalid input
+            }
+            break;
+        }
+        
+        case 3: {
+            // Test Base32Hex decoding from string
+            try {
+                encoded_str = fdp.ConsumeRemainingBytesAsString();
+                decodeBase32Hex(encoded_str, decoded_output);
+            } catch (const isc::Exception&) {
+                // Expected for invalid Base32Hex strings
+            }
+            break;
+        }
+        
+        case 4: {
+            // Test Base16 (hex) encoding from binary data
+            try {
+                size_t bin_size = fdp.ConsumeIntegralInRange<size_t>(0, size);
+                binary_data = fdp.ConsumeBytes<uint8_t>(bin_size);
+                encoded_str = encodeHex(binary_data);
+                // Verify round-trip
+                decodeHex(encoded_str, decoded_output);
+            } catch (const isc::Exception&) {
+                // Expected for invalid input
+            }
+            break;
+        }
+        
+        case 5: {
+            // Test Base16 (hex) decoding from string
+            try {
+                encoded_str = fdp.ConsumeRemainingBytesAsString();
+                decodeHex(encoded_str, decoded_output);
+            } catch (const isc::Exception&) {
+                // Expected for invalid hex strings
+            }
+            break;
+        }
+        
+        case 6: {
+            // Test Base64 with various padding scenarios
+            try {
+                std::string test_str = fdp.ConsumeRandomLengthString();
+                // Add various padding permutations
+                test_str += fdp.ConsumeBool() ? "=" : "";
+                test_str += fdp.ConsumeBool() ? "=" : "";
+                decodeBase64(test_str, decoded_output);
+            } catch (const isc::Exception&) {
+                // Expected for invalid padding
+            }
+            break;
+        }
+        
+        case 7: {
+            // Test Base32Hex with various padding scenarios
+            try {
+                std::string test_str = fdp.ConsumeRandomLengthString();
+                // Add various padding permutations
+                for (int i = 0; i < fdp.ConsumeIntegralInRange(0, 6); i++) {
+                    test_str += "=";
+                }
+                decodeBase32Hex(test_str, decoded_output);
+            } catch (const isc::Exception&) {
+                // Expected for invalid padding
+            }
+            break;
+        }
+        
+        case 8: {
+            // Test mixed case Base64 (should be case-sensitive)
+            try {
+                encoded_str = fdp.ConsumeRemainingBytesAsString();
+                // Mix uppercase and lowercase
+                for (auto& c : encoded_str) {
+                    if (fdp.ConsumeBool() && isalpha(c)) {
+                        c = (isupper(c)) ? tolower(c) : toupper(c);
+                    }
+                }
+                decodeBase64(encoded_str, decoded_output);
+            } catch (const isc::Exception&) {
+                // Expected for case errors
+            }
+            break;
+        }
+        
+        case 9: {
+            // Test mixed case Base32Hex (case-insensitive)
+            try {
+                encoded_str = fdp.ConsumeRemainingBytesAsString();
+                // Mix uppercase and lowercase
+                for (auto& c : encoded_str) {
+                    if (fdp.ConsumeBool() && isalpha(c)) {
+                        c = (isupper(c)) ? tolower(c) : toupper(c);
+                    }
+                }
+                decodeBase32Hex(encoded_str, decoded_output);
+            } catch (const isc::Exception&) {
+                // May succeed due to case-insensitivity
+            }
+            break;
+        }
+        
+        case 10: {
+            // Test Base16 with mixed case (case-insensitive)
+            try {
+                encoded_str = fdp.ConsumeRemainingBytesAsString();
+                // Mix uppercase and lowercase
+                for (auto& c : encoded_str) {
+                    if (fdp.ConsumeBool() && isalpha(c)) {
+                        c = (isupper(c)) ? tolower(c) : toupper(c);
+                    }
+                }
+                decodeHex(encoded_str, decoded_output);
+            } catch (const isc::Exception&) {
+                // May succeed due to case-insensitivity
+            }
+            break;
+        }
+        
+        case 11: {
+            // Test encoding/decoding with whitespace injection
+            try {
+                std::string test_str = fdp.ConsumeRandomLengthString();
+                // Inject whitespace characters
+                size_t insertions = fdp.ConsumeIntegralInRange<size_t>(0, 10);
+                for (size_t i = 0; i < insertions && test_str.size() > 0; i++) {
+                    size_t pos = fdp.ConsumeIntegralInRange<size_t>(0, test_str.size());
+                    char ws = fdp.PickValueInArray({' ', '\t', '\n', '\r'});
+                    test_str.insert(pos, 1, ws);
+                }
+                
+                // Try decoding with all encoders
+                try { decodeBase64(test_str, decoded_output); } catch (...) {}
+                try { decodeBase32Hex(test_str, decoded_output); } catch (...) {}
+                try { decodeHex(test_str, decoded_output); } catch (...) {}
+            } catch (const isc::Exception&) {
+                // Expected for whitespace handling
+            }
+            break;
+        }
+    }
+    
+    return 0;
+}
diff --git a/src/fuzz/fuzz_eval.dict b/src/fuzz/fuzz_eval.dict
new file mode 100644 (file)
index 0000000..af42d50
--- /dev/null
@@ -0,0 +1,63 @@
+"=="
+"option"
+"relay4"
+"relay6"
+"peeraddr"
+"linkaddr"
+"text"
+"hex"
+"exists"
+"pkt"
+"iface"
+"src"
+"dst"
+"len"
+"pkt4"
+"mac"
+"hlen"
+"htype"
+"ciaddr"
+"giaddr"
+"yiaddr"
+"siaddr"
+"pkt6"
+"msgtype"
+"transid"
+"vendor"
+"vendor-class"
+"data"
+"enterprise"
+"substring"
+"lcase"
+"ucase"
+"split"
+"all"
+"concat"
+"ifelse"
+"sifelse"
+"hexstring"
+"addrtotext"
+"int8totext"
+"int16totext"
+"int32totext"
+"uint8totext"
+"uint16totext"
+"uint32totext"
+"not"
+"and"
+"sand"
+"or"
+"sor"
+"member"
+"match"
+"."
+"("
+")"
+"["
+"]"
+","
+"*"
+"+"
+"0x"
+"\""
+"'"
diff --git a/src/fuzz/fuzz_eval4.cc b/src/fuzz/fuzz_eval4.cc
new file mode 100644 (file)
index 0000000..6f33f48
--- /dev/null
@@ -0,0 +1,77 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <eval/eval_context.h>
+#include <eval/evaluate.h>
+#include <eval/dependency.h>
+
+#include <dhcp/pkt4.h>
+#include <dhcp/dhcp4.h>
+
+#include <cstdlib>
+#include <string>
+
+using namespace isc;
+using namespace isc::eval;
+using namespace isc::dhcp;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) {
+    FuzzedDataProvider fdp(Data, Size);
+    EvalContext ctx(Option::V4);
+    auto idx = fdp.ConsumeIntegralInRange<uint8_t>(1, 18);
+    const std::string payload = fdp.ConsumeRemainingBytesAsString();
+
+    try {
+        Pkt4 pkt(idx, 0);
+        // Fuzz boolean parsing
+        if (ctx.parseString(payload, EvalContext::PARSER_BOOL)) {
+            ValueStack vs;
+            Expression& exp_bool = ctx.expression_;
+            ExpressionPtr exp_bool_ptr(new Expression(exp_bool));
+
+            evaluateRaw(exp_bool, pkt, vs);
+            evaluateBool(exp_bool, pkt);
+            evaluateString(exp_bool, pkt);
+            dependOnClass(exp_bool_ptr, payload);
+        }
+    } catch(const isc::Exception&){}
+
+    // Fuzz string parsing
+    try {
+        Pkt4 pkt(idx, 0);
+        if (ctx.parseString(payload, EvalContext::PARSER_STRING)) {
+            ValueStack vs;
+            Expression& exp_str = ctx.expression_;
+            ExpressionPtr exp_str_ptr(new Expression(exp_str));
+
+            evaluateRaw(exp_str, pkt, vs);
+            evaluateBool(exp_str, pkt);
+            evaluateString(exp_str, pkt);
+            dependOnClass(exp_str_ptr, payload);
+        }
+    } catch(const isc::Exception&){}
+
+    location loc;
+    try {
+        // Fuzz converter
+        ctx.convertOptionCode(payload, loc);
+    } catch(const isc::Exception&) {}
+
+    try {
+        ctx.convertOptionName(payload, loc);
+    } catch(const isc::Exception&) {}
+
+    try {
+        ctx.convertNestLevelNumber(payload, loc);
+    } catch(const isc::Exception&) {
+        // Slient exceptions
+    }
+
+    return 0;
+}
diff --git a/src/fuzz/fuzz_eval6.cc b/src/fuzz/fuzz_eval6.cc
new file mode 100644 (file)
index 0000000..f06138b
--- /dev/null
@@ -0,0 +1,77 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <eval/eval_context.h>
+#include <eval/evaluate.h>
+#include <eval/dependency.h>
+
+#include <dhcp/pkt6.h>
+#include <dhcp/dhcp6.h>
+
+#include <cstdlib>
+#include <string>
+
+using namespace isc;
+using namespace isc::eval;
+using namespace isc::dhcp;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) {
+    FuzzedDataProvider fdp(Data, Size);
+    EvalContext ctx(Option::V6);
+
+    auto idx =  fdp.ConsumeIntegralInRange<uint8_t>(1, 18);
+    const std::string payload = fdp.ConsumeRemainingBytesAsString();
+    // Fuzz boolean parsing
+    try {
+        Pkt6 pkt(idx, 0);
+        if (ctx.parseString(payload, EvalContext::PARSER_BOOL)) {
+            ValueStack vs;
+            Expression& exp_bool = ctx.expression_;
+            ExpressionPtr exp_bool_ptr(new Expression(exp_bool));
+
+            evaluateRaw(exp_bool, pkt, vs);
+            evaluateBool(exp_bool, pkt);
+            evaluateString(exp_bool, pkt);
+            dependOnClass(exp_bool_ptr, payload);
+        }
+    } catch(const isc::Exception&) {}
+
+    try {
+        // Fuzz string parsing
+        Pkt6 pkt(idx, 0);
+        if (ctx.parseString(payload, EvalContext::PARSER_STRING)) {
+            ValueStack vs;
+            Expression& exp_str = ctx.expression_;
+            ExpressionPtr exp_str_ptr(new Expression(exp_str));
+
+            evaluateRaw(exp_str, pkt, vs);
+            evaluateBool(exp_str, pkt);
+            evaluateString(exp_str, pkt);
+            dependOnClass(exp_str_ptr, payload);
+        }
+    } catch(const isc::Exception&) {}
+
+    location loc;
+    try {
+        // Fuzz converter
+        ctx.convertOptionCode(payload, loc);
+    } catch(const isc::Exception&) {}
+
+    try {
+        ctx.convertOptionName(payload, loc);
+    } catch(const isc::Exception&) {}
+
+    try {
+        ctx.convertNestLevelNumber(payload, loc);
+    } catch(const isc::Exception&) {
+        // Slient exceptions
+    }
+
+    return 0;
+}
diff --git a/src/fuzz/fuzz_hook_ddns_tuning4.cc b/src/fuzz/fuzz_hook_ddns_tuning4.cc
new file mode 100644 (file)
index 0000000..9cad2b0
--- /dev/null
@@ -0,0 +1,203 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <dhcp/dhcp4.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcpsrv/callout_handle_store.h>
+#include <log/logger_support.h>
+#include <util/filesystem.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+#include <list>
+#include <memory>
+#include <iostream>
+#include <filesystem>
+#include <fstream>
+#include <string>
+#include <cstdio>
+
+#include "helper_func.h"
+
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace isc::util;
+
+extern "C" int ddns4_update(CalloutHandle& handle);
+
+namespace isc {
+    namespace dhcp {
+        class MyDhcpv4Srv : public ControlledDhcpv4Srv {
+            public:
+                void fuzz_classifyPacket(const Pkt4Ptr& pkt) {
+                    classifyPacket(pkt);
+                }
+
+                ConstSubnet4Ptr fuzz_selectSubnet(const Pkt4Ptr& query,
+                                                  bool& drop,
+                                                  bool allow_answer_park = true) {
+                    return selectSubnet(query, drop, allow_answer_park);
+                }
+        };
+    }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    if (size < 236) {
+        // package size requires at least 236 bytes
+        return 0;
+    }
+
+    // Disable validatePath checking to allow writing configuration file to /tmp
+    isc::util::file::PathChecker::enableEnforcement(false);
+
+    // Initialise logging
+    setenv("KEA_LOGGER_DESTINATION", "/dev/null", 0);
+    setenv("KEA_LOCKFILE_DIR", "/tmp", 0);
+    setenv("KEA_PIDFILE_DIR", "/tmp", 0);
+    setenv("KEA_LFC_EXECUTABLE", "/bin/true", 0);
+    try {
+        isc::log::initLogger("fuzzer");
+        isc::process::Daemon::loggerInit("fuzzer", false);
+        isc::process::Daemon::setDefaultLoggerName("fuzzer");
+    } catch (...) {
+        // Early exit if logging initialisation failed
+        return 0;
+    }
+
+    // Creating temp config file
+    std::string path = fuzz::writeTempConfig(true);
+    if (path.empty()) {
+        // Early exit if configuration file creation failed
+        fuzz::deleteTempFile(path);
+        return 0;
+    }
+
+    // Creating temp lease file
+    std::string lease_path = fuzz::writeTempLease(true);
+
+    Pkt4Ptr pkt;
+    std::unique_ptr<MyDhcpv4Srv> srv;
+
+    // Package parsing
+    try {
+        // Add fixed magic cookie and correct hardware address
+        std::vector<uint8_t> buf(data, data + size);
+        if (size >= 240) {
+            // Max hardware address length is 20
+            buf[2] = 20;
+
+            // Magic cookie fixed value 0x63825363
+            buf[236] = 0x63;
+            buf[237] = 0x82;
+            buf[238] = 0x53;
+            buf[239] = 0x63;
+        }
+
+        pkt = Pkt4Ptr(new Pkt4(buf.data(), buf.size()));
+        pkt->unpack();
+    } catch (...) {
+        // Early exit if package parsing failed.
+        return 0;
+    }
+
+    // Configure random value in packet
+    FuzzedDataProvider fdp(data, size);
+    uint8_t typeChoice = fdp.ConsumeIntegralInRange<uint8_t>(0, 8);
+    pkt->setType(static_cast<DHCPMessageType>(typeChoice));
+
+    // Server initialisation
+    try {
+        srv.reset(new MyDhcpv4Srv());
+        srv->init(path);
+    } catch (...) {
+        // Early exit if server initialisation failed.
+        return 0;
+    }
+
+    if (!srv) {
+        // Early exit if server initialisation failed.
+        return 0;
+    }
+
+    // Call classifyPacket for packet checking
+    try {
+        srv->fuzz_classifyPacket(pkt);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Prepare client context
+    CalloutHandlePtr handle = getCalloutHandle(pkt);
+    AllocEngine::ClientContext4Ptr ctx(new AllocEngine::ClientContext4());
+
+    // Call earlyGHRLookup
+    try {
+        srv->earlyGHRLookup(pkt, ctx);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Call select subnet
+    try {
+        bool drop = false;
+        if (!ctx) {
+            ctx.reset(new AllocEngine::ClientContext4());
+        }
+        if (ctx) {
+            ctx->subnet_ = srv->fuzz_selectSubnet(pkt, drop, false);
+        }
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Fuzz ddns4_update
+    try {
+        Pkt4Ptr rsp;
+        handle = getCalloutHandle(pkt);
+        handle->setArgument("query4", pkt);
+        handle->setArgument("response4", rsp);
+        handle->setArgument("hostname", fdp.ConsumeRandomLengthString(32));
+        handle->setArgument("fwd-update", fdp.ConsumeBool());
+        handle->setArgument("rev-update", fdp.ConsumeBool());
+        if (!ctx) {
+            ctx.reset(new AllocEngine::ClientContext4());
+        }
+        if (ctx) {
+            handle->setArgument("ddns-params", ctx->getDdnsParams());
+            handle->setArgument("subnet4", ctx->subnet_);
+        }
+        ddns4_update(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean handle to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    srv.reset();
+
+    // Remove temp files
+    fuzz::deleteTempFile(path);
+    fuzz::deleteTempFile(lease_path);
+    return 0;
+}
diff --git a/src/fuzz/fuzz_hook_ddns_tuning6.cc b/src/fuzz/fuzz_hook_ddns_tuning6.cc
new file mode 100644 (file)
index 0000000..f9ff810
--- /dev/null
@@ -0,0 +1,183 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <dhcp/dhcp6.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcpsrv/callout_handle_store.h>
+#include <log/logger_support.h>
+#include <util/filesystem.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+#include <list>
+#include <memory>
+#include <iostream>
+#include <filesystem>
+#include <fstream>
+#include <string>
+#include <cstdio>
+#include <cstdlib>
+
+#include "helper_func.h"
+
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace isc::util;
+
+extern "C" int ddns6_update(CalloutHandle& handle);
+
+namespace isc {
+    namespace dhcp {
+        class MyDhcpv6Srv : public ControlledDhcpv6Srv {
+            public:
+                void fuzz_classifyPacket(const Pkt6Ptr& pkt) {
+                    classifyPacket(pkt);
+                }
+
+                ConstSubnet6Ptr fuzz_selectSubnet(const Pkt6Ptr& question, bool& drop) {
+                    return selectSubnet(question, drop);
+                }
+        };
+    }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    if (size < 236) {
+        // package size requires at least 236 bytes
+        return 0;
+    }
+
+    // Disable validatePath checking to allow writing configuration file to /tmp
+    isc::util::file::PathChecker::enableEnforcement(false);
+
+    // Force DUID file to /tmp
+    setenv("KEA_DHCP_DATA_DIR", "/tmp", 1);
+
+    // Initialise logging
+    setenv("KEA_LOGGER_DESTINATION", "/dev/null", 0);
+    setenv("KEA_LOCKFILE_DIR", "/tmp", 0);
+    setenv("KEA_PIDFILE_DIR", "/tmp", 0);
+    setenv("KEA_LFC_EXECUTABLE", "/bin/true", 0);
+    try {
+        isc::log::initLogger("fuzzer");
+        isc::process::Daemon::loggerInit("fuzzer", false);
+        isc::process::Daemon::setDefaultLoggerName("fuzzer");
+    } catch (...) {
+        // Early exit if logging initialisation failed
+        return 0;
+    }
+
+    // Creating temp config file
+    std::string path = fuzz::writeTempConfig(false);
+    if (path.empty()) {
+        // Early exit if configuration file creation failed
+        fuzz::deleteTempFile(path);
+        return 0;
+    }
+
+    // Creating temp lease file
+    std::string lease_path = fuzz::writeTempLease(false);
+
+    Pkt6Ptr pkt;
+    std::unique_ptr<MyDhcpv6Srv> srv;
+
+    // Package parsing
+    try {
+        pkt = Pkt6Ptr(new Pkt6(data, size));
+        pkt->unpack();
+    } catch (...) {
+        // Early exit if package parsing failed.
+        return 0;
+    }
+
+    // Configure random value in packet
+    FuzzedDataProvider fdp(data, size);
+    uint8_t typeChoice = fdp.ConsumeIntegralInRange<uint8_t>(0, 37);
+    pkt->setType(static_cast<DHCPv6MessageType>(typeChoice));
+
+    // Server initialisation
+    try {
+        srv.reset(new MyDhcpv6Srv());
+        srv->init(path);
+    } catch (...) {
+        // Early exit if server initialisation failed.
+        return 0;
+    }
+
+    if (!srv) {
+        // Early exit if server initialisation failed.
+        return 0;
+    }
+
+    // Call classifyPacket for packet checking
+    try {
+        srv->fuzz_classifyPacket(pkt);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Prepare client context
+    CalloutHandlePtr handle = getCalloutHandle(pkt);
+    AllocEngine::ClientContext6 ctx;
+
+    // Call earlyGHRLookup
+    try {
+        srv->earlyGHRLookup(pkt, ctx);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Call select subnet
+    try {
+        bool drop = false;
+        ctx.subnet_ = srv->fuzz_selectSubnet(pkt, drop);
+    } catch (const isc::Exception& e) {
+       // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Fuzz ddns6_update
+    try {
+        Pkt6Ptr rsp;
+        handle = getCalloutHandle(pkt);
+        handle->setArgument("query6", pkt);
+        handle->setArgument("response6", rsp);
+        handle->setArgument("hostname", fdp.ConsumeRandomLengthString(32));
+        handle->setArgument("fwd-update", fdp.ConsumeBool());
+        handle->setArgument("rev-update", fdp.ConsumeBool());
+        handle->setArgument("ddns-params", ctx.getDdnsParams());
+        handle->setArgument("subnet6", ctx.subnet_);
+        ddns6_update(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean up to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    srv.reset();
+
+    // Remove temp files
+    fuzz::deleteTempFile(path);
+    fuzz::deleteTempFile(lease_path);
+
+    return 0;
+}
diff --git a/src/fuzz/fuzz_hook_flex_id4.cc b/src/fuzz/fuzz_hook_flex_id4.cc
new file mode 100644 (file)
index 0000000..4de4f2b
--- /dev/null
@@ -0,0 +1,104 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <dhcp/dhcp4.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcpsrv/callout_handle_store.h>
+#include <log/logger_support.h>
+#include <util/filesystem.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+#include <list>
+#include <memory>
+#include <iostream>
+#include <filesystem>
+#include <fstream>
+#include <string>
+#include <cstdio>
+
+#include "helper_func.h"
+
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace isc::util;
+
+extern "C" int pkt4_receive(CalloutHandle& handle);
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    if (size < 236) {
+        // package size requires at least 236 bytes
+        return 0;
+    }
+
+    // Initialise logging
+    setenv("KEA_LOGGER_DESTINATION", "/dev/null", 0);
+    setenv("KEA_LOCKFILE_DIR", "/tmp", 0);
+    setenv("KEA_PIDFILE_DIR", "/tmp", 0);
+    setenv("KEA_LFC_EXECUTABLE", "/bin/true", 0);
+    try {
+        isc::log::initLogger("fuzzer");
+        isc::process::Daemon::loggerInit("fuzzer", false);
+        isc::process::Daemon::setDefaultLoggerName("fuzzer");
+    } catch (...) {
+        // Early exit if logging initialisation failed
+        return 0;
+    }
+
+    Pkt4Ptr pkt;
+
+    // Package parsing
+    try {
+        // Add fixed magic cookie and correct hardware address
+        std::vector<uint8_t> buf(data, data + size);
+        if (size >= 240) {
+            // Max hardware address length is 20
+            buf[2] = 20;
+
+            // Magic cookie fixed value 0x63825363
+            buf[236] = 0x63;
+            buf[237] = 0x82;
+            buf[238] = 0x53;
+            buf[239] = 0x63;
+        }
+
+        pkt = Pkt4Ptr(new Pkt4(buf.data(), buf.size()));
+        pkt->unpack();
+    } catch (...) {
+        // Early exit if package parsing failed.
+        return 0;
+    }
+
+    // Configure random value in packet
+    FuzzedDataProvider fdp(data, size);
+    uint8_t typeChoice = fdp.ConsumeIntegralInRange<uint8_t>(0, 8);
+    pkt->setType(static_cast<DHCPMessageType>(typeChoice));
+
+    CalloutHandlePtr handle = getCalloutHandle(pkt);
+
+    // Fuzz pkt4_receive
+    try {
+        handle->setArgument("query4", pkt);
+        pkt4_receive(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean handle to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    return 0;
+}
diff --git a/src/fuzz/fuzz_hook_flex_id6.cc b/src/fuzz/fuzz_hook_flex_id6.cc
new file mode 100644 (file)
index 0000000..a2e6aca
--- /dev/null
@@ -0,0 +1,95 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <dhcp/dhcp6.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcpsrv/callout_handle_store.h>
+#include <log/logger_support.h>
+#include <util/filesystem.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+#include <list>
+#include <memory>
+#include <iostream>
+#include <filesystem>
+#include <fstream>
+#include <string>
+#include <cstdio>
+#include <cstdlib>
+
+#include "helper_func.h"
+
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace isc::util;
+
+extern "C" int pkt6_receive(CalloutHandle& handle);
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    if (size < 236) {
+        // package size requires at least 236 bytes
+        return 0;
+    }
+
+    // Force DUID file to /tmp
+    setenv("KEA_DHCP_DATA_DIR", "/tmp", 1);
+
+    // Initialise logging
+    setenv("KEA_LOGGER_DESTINATION", "/dev/null", 0);
+    setenv("KEA_LOCKFILE_DIR", "/tmp", 0);
+    setenv("KEA_PIDFILE_DIR", "/tmp", 0);
+    setenv("KEA_LFC_EXECUTABLE", "/bin/true", 0);
+    try {
+        isc::log::initLogger("fuzzer");
+        isc::process::Daemon::loggerInit("fuzzer", false);
+        isc::process::Daemon::setDefaultLoggerName("fuzzer");
+    } catch (...) {
+        // Early exit if logging initialisation failed
+        return 0;
+    }
+
+    Pkt6Ptr pkt;
+
+    // Package parsing
+    try {
+        pkt = Pkt6Ptr(new Pkt6(data, size));
+        pkt->unpack();
+    } catch (...) {
+        // Early exit if package parsing failed.
+        return 0;
+    }
+
+    // Configure random value in packet
+    FuzzedDataProvider fdp(data, size);
+    uint8_t typeChoice = fdp.ConsumeIntegralInRange<uint8_t>(0, 37);
+    pkt->setType(static_cast<DHCPv6MessageType>(typeChoice));
+
+    CalloutHandlePtr handle = getCalloutHandle(pkt);
+
+    // Fuzz pkt6_receive
+    try {
+        handle->setArgument("query6", pkt);
+        pkt6_receive(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean handle to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    return 0;
+}
diff --git a/src/fuzz/fuzz_hook_lease_query4.cc b/src/fuzz/fuzz_hook_lease_query4.cc
new file mode 100644 (file)
index 0000000..2204a19
--- /dev/null
@@ -0,0 +1,105 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <dhcp/dhcp4.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcpsrv/callout_handle_store.h>
+#include <log/logger_support.h>
+#include <util/filesystem.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+#include <list>
+#include <memory>
+#include <iostream>
+#include <filesystem>
+#include <fstream>
+#include <string>
+#include <cstdio>
+
+#include "helper_func.h"
+
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace isc::util;
+
+extern "C" int buffer4_receive(CalloutHandle& handle);
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    if (size < 236) {
+        // package size requires at least 236 bytes
+        return 0;
+    }
+
+    // Initialise logging
+    setenv("KEA_LOGGER_DESTINATION", "/dev/null", 0);
+    setenv("KEA_LOCKFILE_DIR", "/tmp", 0);
+    setenv("KEA_PIDFILE_DIR", "/tmp", 0);
+    setenv("KEA_LFC_EXECUTABLE", "/bin/true", 0);
+    try {
+        isc::log::initLogger("fuzzer");
+        isc::process::Daemon::loggerInit("fuzzer", false);
+        isc::process::Daemon::setDefaultLoggerName("fuzzer");
+    } catch (...) {
+        // Early exit if logging initialisation failed
+        return 0;
+    }
+
+    Pkt4Ptr pkt;
+
+    // Package parsing
+    try {
+        // Add fixed magic cookie and correct hardware address
+        std::vector<uint8_t> buf(data, data + size);
+        if (size >= 240) {
+            // Max hardware address length is 20
+            buf[2] = 20;
+
+            // Magic cookie fixed value 0x63825363
+            buf[236] = 0x63;
+            buf[237] = 0x82;
+            buf[238] = 0x53;
+            buf[239] = 0x63;
+        }
+
+        pkt = Pkt4Ptr(new Pkt4(buf.data(), buf.size()));
+        pkt->unpack();
+    } catch (...) {
+        // Early exit if package parsing failed.
+        return 0;
+    }
+
+    // Configure random value in packet
+    FuzzedDataProvider fdp(data, size);
+    uint8_t typeChoice = fdp.ConsumeIntegralInRange<uint8_t>(0, 8);
+    pkt->setType(static_cast<DHCPMessageType>(typeChoice));
+
+    CalloutHandlePtr handle = getCalloutHandle(pkt);
+
+    // Fuzz buffer4_receive
+    try {
+        handle = getCalloutHandle(pkt);
+        handle->setArgument("query4", pkt);
+        buffer4_receive(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean handle to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    return 0;
+}
diff --git a/src/fuzz/fuzz_hook_lease_query6.cc b/src/fuzz/fuzz_hook_lease_query6.cc
new file mode 100644 (file)
index 0000000..e507b27
--- /dev/null
@@ -0,0 +1,96 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <dhcp/dhcp6.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcpsrv/callout_handle_store.h>
+#include <log/logger_support.h>
+#include <util/filesystem.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+#include <list>
+#include <memory>
+#include <iostream>
+#include <filesystem>
+#include <fstream>
+#include <string>
+#include <cstdio>
+#include <cstdlib>
+
+#include "helper_func.h"
+
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace isc::util;
+
+extern "C" int buffer6_receive(CalloutHandle& handle);
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    if (size < 236) {
+        // package size requires at least 236 bytes
+        return 0;
+    }
+
+    // Force DUID file to /tmp
+    setenv("KEA_DHCP_DATA_DIR", "/tmp", 1);
+
+    // Initialise logging
+    setenv("KEA_LOGGER_DESTINATION", "/dev/null", 0);
+    setenv("KEA_LOCKFILE_DIR", "/tmp", 0);
+    setenv("KEA_PIDFILE_DIR", "/tmp", 0);
+    setenv("KEA_LFC_EXECUTABLE", "/bin/true", 0);
+    try {
+        isc::log::initLogger("fuzzer");
+        isc::process::Daemon::loggerInit("fuzzer", false);
+        isc::process::Daemon::setDefaultLoggerName("fuzzer");
+    } catch (...) {
+        // Early exit if logging initialisation failed
+        return 0;
+    }
+
+    Pkt6Ptr pkt;
+
+    // Package parsing
+    try {
+        pkt = Pkt6Ptr(new Pkt6(data, size));
+        pkt->unpack();
+    } catch (...) {
+        // Early exit if package parsing failed.
+        return 0;
+    }
+
+    // Configure random value in packet
+    FuzzedDataProvider fdp(data, size);
+    uint8_t typeChoice = fdp.ConsumeIntegralInRange<uint8_t>(0, 37);
+    pkt->setType(static_cast<DHCPv6MessageType>(typeChoice));
+
+    CalloutHandlePtr handle = getCalloutHandle(pkt);
+
+    // Fuzz buffer6_receive
+    try {
+        handle = getCalloutHandle(pkt);
+        handle->setArgument("query6", pkt);
+        buffer6_receive(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean handle to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    return 0;
+}
diff --git a/src/fuzz/fuzz_hook_radius4.cc b/src/fuzz/fuzz_hook_radius4.cc
new file mode 100644 (file)
index 0000000..cd16813
--- /dev/null
@@ -0,0 +1,232 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <dhcp/dhcp4.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcpsrv/callout_handle_store.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <log/logger_support.h>
+#include <util/filesystem.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+#include <list>
+#include <memory>
+#include <iostream>
+#include <filesystem>
+#include <fstream>
+#include <string>
+#include <cstdio>
+
+#include "helper_func.h"
+
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace isc::util;
+
+extern "C" int subnet4_select(CalloutHandle& handle);
+extern "C" int lease4_release(CalloutHandle& handle);
+extern "C" int lease4_decline(CalloutHandle& handle);
+
+namespace isc {
+    namespace dhcp {
+        class MyDhcpv4Srv : public ControlledDhcpv4Srv {
+            public:
+                void fuzz_classifyPacket(const Pkt4Ptr& pkt) {
+                    classifyPacket(pkt);
+                }
+        };
+    }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    if (size < 236) {
+        // package size requires at least 236 bytes
+        return 0;
+    }
+
+    // Disable validatePath checking to allow writing configuration file to /tmp
+    isc::util::file::PathChecker::enableEnforcement(false);
+
+    // Initialise logging
+    setenv("KEA_LOGGER_DESTINATION", "/dev/null", 0);
+    setenv("KEA_LOCKFILE_DIR", "/tmp", 0);
+    setenv("KEA_PIDFILE_DIR", "/tmp", 0);
+    setenv("KEA_LFC_EXECUTABLE", "/bin/true", 0);
+    try {
+        isc::log::initLogger("fuzzer");
+        isc::process::Daemon::loggerInit("fuzzer", false);
+        isc::process::Daemon::setDefaultLoggerName("fuzzer");
+    } catch (...) {
+        // Early exit if logging initialisation failed
+        return 0;
+    }
+
+    // Creating temp config file
+    std::string path = fuzz::writeTempConfig(true);
+    if (path.empty()) {
+        // Early exit if configuration file creation failed
+        fuzz::deleteTempFile(path);
+        return 0;
+    }
+
+    // Creating temp lease file
+    std::string lease_path = fuzz::writeTempLease(true);
+
+    Pkt4Ptr pkt;
+    std::unique_ptr<MyDhcpv4Srv> srv;
+
+    // Package parsing
+    try {
+        // Add fixed magic cookie and correct hardware address
+        std::vector<uint8_t> buf(data, data + size);
+        if (size >= 240) {
+            // Max hardware address length is 20
+            buf[2] = 20;
+
+            // Magic cookie fixed value 0x63825363
+            buf[236] = 0x63;
+            buf[237] = 0x82;
+            buf[238] = 0x53;
+            buf[239] = 0x63;
+        }
+
+        pkt = Pkt4Ptr(new Pkt4(buf.data(), buf.size()));
+        pkt->unpack();
+    } catch (...) {
+        // Early exit if package parsing failed.
+        return 0;
+    }
+
+    // Configure random value in packet
+    FuzzedDataProvider fdp(data, size);
+    uint8_t typeChoice = fdp.ConsumeIntegralInRange<uint8_t>(0, 8);
+    pkt->setType(static_cast<DHCPMessageType>(typeChoice));
+
+    // Server initialisation
+    try {
+        srv.reset(new MyDhcpv4Srv());
+        srv->init(path);
+    } catch (...) {
+        // Early exit if server initialisation failed.
+        return 0;
+    }
+
+    if (!srv) {
+        // Early exit if server initialisation failed.
+        return 0;
+    }
+
+    // Call classifyPacket for packet checking
+    try {
+        srv->fuzz_classifyPacket(pkt);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Prepare client context
+    CalloutHandlePtr handle = nullptr;
+    AllocEngine::ClientContext4Ptr ctx(new AllocEngine::ClientContext4());
+
+    // Call earlyGHRLookup
+    try {
+        srv->earlyGHRLookup(pkt, ctx);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Fuzz subnet4_select
+    try {
+        handle = getCalloutHandle(pkt);
+        Pkt4Ptr rsp;
+        CfgMgr& cfgmgr = CfgMgr::instance();
+        handle = getCalloutHandle(pkt);
+        handle->setArgument("query4", pkt);
+        handle->setArgument("subnet4collection",
+                            cfgmgr.getCurrentCfg()->getCfgSubnets4()->getAll());
+        if (!ctx) {
+            ctx.reset(new AllocEngine::ClientContext4());
+        }
+        if (ctx) {
+            handle->setArgument("subnet4", ctx->subnet_);
+        }
+        subnet4_select(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean handle to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    // Call lease4_release
+    try {
+        handle = getCalloutHandle(pkt);
+        uint8_t mac_addr[6];
+        for (size_t i = 0; i < 6; ++i) {
+            mac_addr[i] = fdp.ConsumeIntegral<uint8_t>();
+        }
+        HWAddr hw(mac_addr, sizeof(mac_addr), HTYPE_ETHER);
+        Lease4Collection leases = LeaseMgrFactory::instance().getLease4(hw);
+        handle->setArgument("leases4", leases);
+        handle->setArgument("query4", pkt);
+
+        lease4_release(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean up to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    // Call lease4_decline
+    try {
+        uint8_t mac_addr[6];
+        for (size_t i = 0; i < 6; ++i) {
+            mac_addr[i] = fdp.ConsumeIntegral<uint8_t>();
+        }
+        HWAddr hw(mac_addr, sizeof(mac_addr), HTYPE_ETHER);
+        Lease4Collection leases = LeaseMgrFactory::instance().getLease4(hw);
+        handle->setArgument("leases4", leases);
+        handle->setArgument("query4", pkt);
+
+        lease4_decline(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean up to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    srv.reset();
+
+    // Remove temp files
+    fuzz::deleteTempFile(path);
+    fuzz::deleteTempFile(lease_path);
+    return 0;
+}
diff --git a/src/fuzz/fuzz_hook_radius6.cc b/src/fuzz/fuzz_hook_radius6.cc
new file mode 100644 (file)
index 0000000..c7af450
--- /dev/null
@@ -0,0 +1,217 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <dhcp/dhcp6.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcpsrv/callout_handle_store.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <log/logger_support.h>
+#include <util/filesystem.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+#include <list>
+#include <memory>
+#include <iostream>
+#include <filesystem>
+#include <fstream>
+#include <string>
+#include <cstdio>
+#include <cstdlib>
+
+#include "helper_func.h"
+
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace isc::util;
+
+extern "C" int subnet6_select(CalloutHandle& handle);
+extern "C" int lease6_release(CalloutHandle& handle);
+extern "C" int lease6_decline(CalloutHandle& handle);
+
+namespace isc {
+    namespace dhcp {
+        class MyDhcpv6Srv : public ControlledDhcpv6Srv {
+            public:
+                void fuzz_classifyPacket(const Pkt6Ptr& pkt) {
+                    classifyPacket(pkt);
+                }
+        };
+    }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    if (size < 236) {
+        // package size requires at least 236 bytes
+        return 0;
+    }
+
+    // Disable validatePath checking to allow writing configuration file to /tmp
+    isc::util::file::PathChecker::enableEnforcement(false);
+
+    // Force DUID file to /tmp
+    setenv("KEA_DHCP_DATA_DIR", "/tmp", 1);
+
+    // Initialise logging
+    setenv("KEA_LOGGER_DESTINATION", "/dev/null", 0);
+    setenv("KEA_LOCKFILE_DIR", "/tmp", 0);
+    setenv("KEA_PIDFILE_DIR", "/tmp", 0);
+    setenv("KEA_LFC_EXECUTABLE", "/bin/true", 0);
+    try {
+        isc::log::initLogger("fuzzer");
+        isc::process::Daemon::loggerInit("fuzzer", false);
+        isc::process::Daemon::setDefaultLoggerName("fuzzer");
+    } catch (...) {
+        // Early exit if logging initialisation failed
+        return 0;
+    }
+
+    // Creating temp config file
+    std::string path = fuzz::writeTempConfig(false);
+    if (path.empty()) {
+        // Early exit if configuration file creation failed
+        fuzz::deleteTempFile(path);
+        return 0;
+    }
+
+    // Creating temp lease file
+    std::string lease_path = fuzz::writeTempLease(false);
+
+    Pkt6Ptr pkt;
+    std::unique_ptr<MyDhcpv6Srv> srv;
+
+    // Package parsing
+    try {
+        pkt = Pkt6Ptr(new Pkt6(data, size));
+        pkt->unpack();
+    } catch (...) {
+        // Early exit if package parsing failed.
+        return 0;
+    }
+
+    // Configure random value in packet
+    FuzzedDataProvider fdp(data, size);
+    uint8_t typeChoice = fdp.ConsumeIntegralInRange<uint8_t>(0, 37);
+    pkt->setType(static_cast<DHCPv6MessageType>(typeChoice));
+
+    // Server initialisation
+    try {
+        srv.reset(new MyDhcpv6Srv());
+        srv->init(path);
+    } catch (...) {
+        // Early exit if server initialisation failed.
+        return 0;
+    }
+
+    if (!srv) {
+        // Early exit if server initialisation failed.
+        return 0;
+    }
+
+    // Call classifyPacket for packet checking
+    try {
+        srv->fuzz_classifyPacket(pkt);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Prepare client context
+    CalloutHandlePtr handle = nullptr;
+    AllocEngine::ClientContext6 ctx;
+
+    // Call earlyGHRLookup
+    try {
+        srv->earlyGHRLookup(pkt, ctx);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Fuzz subnet6_select
+    try {
+        Pkt6Ptr rsp;
+        CfgMgr& cfgmgr = CfgMgr::instance();
+        handle = getCalloutHandle(pkt);
+        handle->setArgument("query6", pkt);
+        handle->setArgument("subnet6", ctx.subnet_);
+        handle->setArgument("subnet6collection",
+                            cfgmgr.getCurrentCfg()->getCfgSubnets6()->getAll());
+        subnet6_select(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean handle to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    // Call lease6_decline
+    try {
+        uint8_t mac_addr[6];
+        for (size_t i = 0; i < 6; ++i) {
+            mac_addr[i] = fdp.ConsumeIntegral<uint8_t>();
+        }
+        HWAddr hw(mac_addr, sizeof(mac_addr), HTYPE_ETHER);
+        Lease6Collection leases = LeaseMgrFactory::instance().getLease6(hw);
+        handle->setArgument("leases6", leases);
+        handle->setArgument("query6", pkt);
+
+        lease6_decline(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean up to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    // Call lease6_release
+    try {
+        uint8_t mac_addr[6];
+        for (size_t i = 0; i < 6; ++i) {
+            mac_addr[i] = fdp.ConsumeIntegral<uint8_t>();
+        }
+        HWAddr hw(mac_addr, sizeof(mac_addr), HTYPE_ETHER);
+        Lease6Collection leases = LeaseMgrFactory::instance().getLease6(hw);
+        handle->setArgument("leases6", leases);
+        handle->setArgument("query6", pkt);
+
+        lease6_release(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean up to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    srv.reset();
+
+    // Remove temp files
+    fuzz::deleteTempFile(path);
+    fuzz::deleteTempFile(lease_path);
+
+    return 0;
+}
diff --git a/src/fuzz/fuzz_hook_run_script4.cc b/src/fuzz/fuzz_hook_run_script4.cc
new file mode 100644 (file)
index 0000000..daa4082
--- /dev/null
@@ -0,0 +1,232 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <dhcp/dhcp4.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcpsrv/callout_handle_store.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <log/logger_support.h>
+#include <util/filesystem.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+#include <list>
+#include <memory>
+#include <iostream>
+#include <filesystem>
+#include <fstream>
+#include <string>
+#include <cstdio>
+
+#include "helper_func.h"
+
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace isc::util;
+
+extern "C" int lease4_release(CalloutHandle& handle);
+extern "C" int lease4_decline(CalloutHandle& handle);
+extern "C" int leases4_committed(CalloutHandle& handle);
+
+namespace isc {
+    namespace dhcp {
+        class MyDhcpv4Srv : public ControlledDhcpv4Srv {
+            public:
+                void fuzz_classifyPacket(const Pkt4Ptr& pkt) {
+                    classifyPacket(pkt);
+                }
+        };
+    }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    if (size < 236) {
+        // package size requires at least 236 bytes
+        return 0;
+    }
+
+    // Disable validatePath checking to allow writing configuration file to /tmp
+    isc::util::file::PathChecker::enableEnforcement(false);
+
+    // Initialise logging
+    setenv("KEA_LOGGER_DESTINATION", "/dev/null", 0);
+    setenv("KEA_LOCKFILE_DIR", "/tmp", 0);
+    setenv("KEA_PIDFILE_DIR", "/tmp", 0);
+    setenv("KEA_LFC_EXECUTABLE", "/bin/true", 0);
+    try {
+        isc::log::initLogger("fuzzer");
+        isc::process::Daemon::loggerInit("fuzzer", false);
+        isc::process::Daemon::setDefaultLoggerName("fuzzer");
+    } catch (...) {
+        // Early exit if logging initialisation failed
+        return 0;
+    }
+
+    // Creating temp config file
+    std::string path = fuzz::writeTempConfig(true);
+    if (path.empty()) {
+        // Early exit if configuration file creation failed
+        fuzz::deleteTempFile(path);
+        return 0;
+    }
+
+    // Creating temp lease file
+    std::string lease_path = fuzz::writeTempLease(true);
+
+    Pkt4Ptr pkt;
+    std::unique_ptr<MyDhcpv4Srv> srv;
+
+    // Package parsing
+    try {
+        // Add fixed magic cookie and correct hardware address
+        std::vector<uint8_t> buf(data, data + size);
+        if (size >= 240) {
+            // Max hardware address length is 20
+            buf[2] = 20;
+
+            // Magic cookie fixed value 0x63825363
+            buf[236] = 0x63;
+            buf[237] = 0x82;
+            buf[238] = 0x53;
+            buf[239] = 0x63;
+        }
+
+        pkt = Pkt4Ptr(new Pkt4(buf.data(), buf.size()));
+        pkt->unpack();
+    } catch (...) {
+        // Early exit if package parsing failed.
+        return 0;
+    }
+
+    // Configure random value in packet
+    FuzzedDataProvider fdp(data, size);
+    uint8_t typeChoice = fdp.ConsumeIntegralInRange<uint8_t>(0, 8);
+    pkt->setType(static_cast<DHCPMessageType>(typeChoice));
+
+    // Server initialisation
+    try {
+        srv.reset(new MyDhcpv4Srv());
+        srv->init(path);
+    } catch (...) {
+        // Early exit if server initialisation failed.
+        return 0;
+    }
+
+    if (!srv) {
+        // Early exit if server initialisation failed.
+        return 0;
+    }
+
+    // Call classifyPacket for packet checking
+    try {
+        srv->fuzz_classifyPacket(pkt);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Prepare client context
+    CalloutHandlePtr handle = nullptr;
+    AllocEngine::ClientContext4Ptr ctx(new AllocEngine::ClientContext4());
+
+    // Call earlyGHRLookup
+    try {
+        srv->earlyGHRLookup(pkt, ctx);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Call lease4_release
+    try {
+        handle = getCalloutHandle(pkt);
+        uint8_t mac_addr[6];
+        for (size_t i = 0; i < 6; ++i) {
+            mac_addr[i] = fdp.ConsumeIntegral<uint8_t>();
+        }
+        HWAddr hw(mac_addr, sizeof(mac_addr), HTYPE_ETHER);
+        Lease4Collection leases = LeaseMgrFactory::instance().getLease4(hw);
+        handle->setArgument("leases4", leases);
+        handle->setArgument("query4", pkt);
+
+        lease4_release(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean up to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    // Call lease4_decline
+    try {
+        handle = getCalloutHandle(pkt);
+        uint8_t mac_addr[6];
+        for (size_t i = 0; i < 6; ++i) {
+            mac_addr[i] = fdp.ConsumeIntegral<uint8_t>();
+        }
+        HWAddr hw(mac_addr, sizeof(mac_addr), HTYPE_ETHER);
+        Lease4Collection leases = LeaseMgrFactory::instance().getLease4(hw);
+        handle->setArgument("leases4", leases);
+        handle->setArgument("query4", pkt);
+
+        lease4_decline(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean up to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    // Call lease4_committed
+    try {
+        handle = getCalloutHandle(pkt);
+        Pkt4Ptr rsp;
+        uint8_t mac_addr[6];
+        for (size_t i = 0; i < 6; ++i) {
+            mac_addr[i] = fdp.ConsumeIntegral<uint8_t>();
+        }
+        HWAddr hw(mac_addr, sizeof(mac_addr), HTYPE_ETHER);
+        Lease4Collection leases = LeaseMgrFactory::instance().getLease4(hw);
+        handle->setArgument("leases4", leases);
+        handle->setArgument("query4", pkt);
+        handle->setArgument("response4", rsp);
+
+        leases4_committed(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean up to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    srv.reset();
+
+    // Remove temp files
+    fuzz::deleteTempFile(path);
+    fuzz::deleteTempFile(lease_path);
+    return 0;
+}
diff --git a/src/fuzz/fuzz_hook_run_script6.cc b/src/fuzz/fuzz_hook_run_script6.cc
new file mode 100644 (file)
index 0000000..e3af617
--- /dev/null
@@ -0,0 +1,255 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <dhcp/dhcp6.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcpsrv/callout_handle_store.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <log/logger_support.h>
+#include <util/filesystem.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+#include <list>
+#include <memory>
+#include <iostream>
+#include <filesystem>
+#include <fstream>
+#include <string>
+#include <cstdio>
+#include <cstdlib>
+
+#include "helper_func.h"
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace isc::util;
+
+extern "C" int lease6_release(CalloutHandle& handle);
+extern "C" int lease6_decline(CalloutHandle& handle);
+extern "C" int leases6_committed(CalloutHandle& handle);
+extern "C" int addr6_register(CalloutHandle& handle);
+
+namespace isc {
+    namespace dhcp {
+        class MyDhcpv6Srv : public ControlledDhcpv6Srv {
+            public:
+                void fuzz_classifyPacket(const Pkt6Ptr& pkt) {
+                    classifyPacket(pkt);
+                }
+        };
+    }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    if (size < 236) {
+        // package size requires at least 236 bytes
+        return 0;
+    }
+
+    // Disable validatePath checking to allow writing configuration file to /tmp
+    isc::util::file::PathChecker::enableEnforcement(false);
+
+    // Force DUID file to /tmp
+    setenv("KEA_DHCP_DATA_DIR", "/tmp", 1);
+
+    // Initialise logging
+    setenv("KEA_LOGGER_DESTINATION", "/dev/null", 0);
+    setenv("KEA_LOCKFILE_DIR", "/tmp", 0);
+    setenv("KEA_PIDFILE_DIR", "/tmp", 0);
+    setenv("KEA_LFC_EXECUTABLE", "/bin/true", 0);
+    try {
+        isc::log::initLogger("fuzzer");
+        isc::process::Daemon::loggerInit("fuzzer", false);
+        isc::process::Daemon::setDefaultLoggerName("fuzzer");
+    } catch (...) {
+        // Early exit if logging initialisation failed
+        return 0;
+    }
+
+    // Creating temp config file
+    std::string path = fuzz::writeTempConfig(false);
+    if (path.empty()) {
+        // Early exit if configuration file creation failed
+        fuzz::deleteTempFile(path);
+        return 0;
+    }
+
+    // Creating temp lease file
+    std::string lease_path = fuzz::writeTempLease(false);
+
+    Pkt6Ptr pkt;
+    std::unique_ptr<MyDhcpv6Srv> srv;
+
+    // Package parsing
+    try {
+        pkt = Pkt6Ptr(new Pkt6(data, size));
+        pkt->unpack();
+    } catch (...) {
+        // Early exit if package parsing failed.
+        return 0;
+    }
+
+    // Configure random value in packet
+    FuzzedDataProvider fdp(data, size);
+    uint8_t typeChoice = fdp.ConsumeIntegralInRange<uint8_t>(0, 37);
+    pkt->setType(static_cast<DHCPv6MessageType>(typeChoice));
+
+    // Server initialisation
+    try {
+        srv.reset(new MyDhcpv6Srv());
+        srv->init(path);
+    } catch (...) {
+        // Early exit if server initialisation failed.
+        return 0;
+    }
+
+    if (!srv) {
+        // Early exit if server initialisation failed.
+        return 0;
+    }
+
+    // Call classifyPacket for packet checking
+    try {
+        srv->fuzz_classifyPacket(pkt);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Prepare client context
+    CalloutHandlePtr handle = nullptr;
+    AllocEngine::ClientContext6 ctx;
+
+    // Call earlyGHRLookup
+    try {
+        srv->earlyGHRLookup(pkt, ctx);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Call lease6_decline
+    try {
+        handle = getCalloutHandle(pkt);
+        uint8_t mac_addr[6];
+        for (size_t i = 0; i < 6; ++i) {
+            mac_addr[i] = fdp.ConsumeIntegral<uint8_t>();
+        }
+        HWAddr hw(mac_addr, sizeof(mac_addr), HTYPE_ETHER);
+        Lease6Collection leases = LeaseMgrFactory::instance().getLease6(hw);
+        handle->setArgument("leases6", leases);
+        handle->setArgument("query6", pkt);
+
+        lease6_decline(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean up to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    // Call lease6_release
+    try {
+        handle = getCalloutHandle(pkt);
+        uint8_t mac_addr[6];
+        for (size_t i = 0; i < 6; ++i) {
+            mac_addr[i] = fdp.ConsumeIntegral<uint8_t>();
+        }
+        HWAddr hw(mac_addr, sizeof(mac_addr), HTYPE_ETHER);
+        Lease6Collection leases = LeaseMgrFactory::instance().getLease6(hw);
+        handle->setArgument("leases6", leases);
+        handle->setArgument("query6", pkt);
+
+        lease6_release(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean up to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    // Call leases6_committed
+    try {
+        handle = getCalloutHandle(pkt);
+        uint8_t mac_addr[6];
+        for (size_t i = 0; i < 6; ++i) {
+            mac_addr[i] = fdp.ConsumeIntegral<uint8_t>();
+        }
+        HWAddr hw(mac_addr, sizeof(mac_addr), HTYPE_ETHER);
+        Lease6Collection leases = LeaseMgrFactory::instance().getLease6(hw);
+        handle->setArgument("leases6", leases);
+        handle->setArgument("deleted_leases6", leases);
+        handle->setArgument("query6", pkt);
+
+        leases6_committed(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean up to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    // Call addr6_register
+    try {
+        handle = getCalloutHandle(pkt);
+        Pkt6Ptr rsp;
+        uint8_t mac_addr[6];
+        for (size_t i = 0; i < 6; ++i) {
+            mac_addr[i] = fdp.ConsumeIntegral<uint8_t>();
+        }
+        HWAddr hw(mac_addr, sizeof(mac_addr), HTYPE_ETHER);
+        Lease6Collection leases = LeaseMgrFactory::instance().getLease6(hw);
+        handle->setArgument("new_leases6", leases);
+        handle->setArgument("response6", rsp);
+        handle->setArgument("query6", pkt);
+
+        IOAddress addr = ctx.query_->getRemoteAddr();
+        handle->setArgument("address6", addr);
+        handle->setArgument("old_leases6",
+                            LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr));
+
+        addr6_register(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean up to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    srv.reset();
+
+    // Remove temp files
+    fuzz::deleteTempFile(path);
+    fuzz::deleteTempFile(lease_path);
+
+    return 0;
+}
diff --git a/src/fuzz/fuzz_hook_tsig.cc b/src/fuzz/fuzz_hook_tsig.cc
new file mode 100644 (file)
index 0000000..43479e2
--- /dev/null
@@ -0,0 +1,66 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <dns/name.h>
+#include <dns/tsig.h>
+#include <dns/rdata.h>
+
+#include <gss_tsig_context.h>
+#include <gss_tsig_key.h>
+#include <tkey_exchange.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <string>
+#include <vector>
+#include <memory>
+
+using namespace isc;
+using namespace isc::dns;
+using namespace isc::gss_tsig;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    FuzzedDataProvider fdp(data, size);
+
+    // Prepare basic key crypto information
+    std::string key_name = fdp.ConsumeRandomLengthString(64);
+    if (key_name.empty()) key_name = "fuzz-key";
+    std::string tsig_rdata_txt = fdp.ConsumeRandomLengthString(1024);
+    std::string owner_txt = fdp.ConsumeRandomLengthString(128);
+    const uint16_t qid = fdp.ConsumeIntegral<uint16_t>();
+    const bool do_chunked = fdp.ConsumeBool();
+
+    // Target correct key sign and verify
+    try {
+        std::vector<uint8_t> payload = fdp.ConsumeBytes<uint8_t>(fdp.ConsumeIntegralInRange<size_t>(0, 2048));
+        GssTsigKey key(key_name, payload);
+        GssTsigContext ctx(key);
+
+        std::string wire = fdp.ConsumeRandomLengthString(2048);
+        ctx.sign(qid, wire.data(), wire.size());
+
+        Name owner_name(owner_txt.empty() ? "fuzz." : owner_txt.c_str());
+        rdata::any::TSIG tsig_rdata(tsig_rdata_txt);
+        TSIGRecord record(owner_name, tsig_rdata);
+
+        ctx.verify(&record, wire.data(), wire.size());
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    }
+
+    // Target key exchange
+    try {
+        auto val = fdp.ConsumeIntegralInRange<int>(-5, 10);
+        TKeyExchange::statusToText(static_cast<TKeyExchange::Status>(val));
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    }
+
+    return 0;
+}
diff --git a/src/fuzz/fuzz_hook_user_chk4.cc b/src/fuzz/fuzz_hook_user_chk4.cc
new file mode 100644 (file)
index 0000000..60a832d
--- /dev/null
@@ -0,0 +1,240 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <dhcp/dhcp4.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/user_chk/user.h>
+#include <dhcp/user_chk/user_data_source.h>
+#include <dhcp/user_chk/user_file.h>
+#include <dhcp/user_chk/user_registry.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcpsrv/callout_handle_store.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <log/logger_support.h>
+#include <util/filesystem.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+#include <list>
+#include <memory>
+#include <iostream>
+#include <filesystem>
+#include <fstream>
+#include <string>
+#include <cstdio>
+
+#include "helper_func.h"
+
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace isc::util;
+using namespace user_chk;
+
+extern "C" UserRegistryPtr user_registry;
+extern "C" int pkt4_receive(CalloutHandle& handle);
+extern "C" int subnet4_select(CalloutHandle& handle);
+
+namespace isc {
+    namespace dhcp {
+        class MyDhcpv4Srv : public ControlledDhcpv4Srv {
+            public:
+                void fuzz_classifyPacket(const Pkt4Ptr& pkt) {
+                    classifyPacket(pkt);
+                }
+        };
+    }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    if (size < 236) {
+        // package size requires at least 236 bytes
+        return 0;
+    }
+
+    FuzzedDataProvider fdp(data, size);
+
+    // Disable validatePath checking to allow writing configuration file to /tmp
+    isc::util::file::PathChecker::enableEnforcement(false);
+
+    // Initialise logging
+    setenv("KEA_LOGGER_DESTINATION", "/dev/null", 0);
+    setenv("KEA_LOCKFILE_DIR", "/tmp", 0);
+    setenv("KEA_PIDFILE_DIR", "/tmp", 0);
+    setenv("KEA_LFC_EXECUTABLE", "/bin/true", 0);
+    try {
+        isc::log::initLogger("fuzzer");
+        isc::process::Daemon::loggerInit("fuzzer", false);
+        isc::process::Daemon::setDefaultLoggerName("fuzzer");
+    } catch (...) {
+        // Early exit if logging initialisation failed
+        return 0;
+    }
+
+    // Creating temp config file
+    std::string path = fuzz::writeTempConfig(true);
+    if (path.empty()) {
+        // Early exit if configuration file creation failed
+        fuzz::deleteTempFile(path);
+        return 0;
+    }
+
+    // Creating temp lease file
+    std::string lease_path = fuzz::writeTempLease(true);
+
+    // Creating temp user file
+    std::string user_path = fuzz::writeTempUserFile();
+
+    // Creating user registry
+    try {
+        user_registry.reset(new UserRegistry());
+        UserDataSourcePtr user_file(new UserFile(user_path));
+        user_registry->setSource(user_file);
+        user_registry->refresh();
+    } catch (std::exception& e) {
+        // Early exit if user registry failed to create.
+        return 0;
+    }
+
+    try {
+        for (int i = 0; i < fdp.ConsumeIntegralInRange<int>(1, 5); i++) {
+            if (fdp.ConsumeBool()) {
+                std::vector<uint8_t> mac = fdp.ConsumeBytes<uint8_t>(6);
+                UserPtr user = UserPtr(new User(UserId::HW_ADDRESS, mac));
+                user_registry->addUser(user);
+            } else {
+                const size_t len = fdp.ConsumeIntegralInRange<size_t>(2, 64);
+                std::vector<uint8_t> duid = fdp.ConsumeBytes<uint8_t>(len);
+                UserPtr user = UserPtr(new User(UserId::DUID, duid));
+                user_registry->addUser(user);
+            }
+        }
+    } catch (...) {
+        // Slient exceptions
+    }
+
+    Pkt4Ptr pkt;
+    std::unique_ptr<MyDhcpv4Srv> srv;
+
+    // Package parsing
+    try {
+        // Add fixed magic cookie and correct hardware address
+        std::vector<uint8_t> buf(data, data + size);
+        if (size >= 240) {
+            // Max hardware address length is 20
+            buf[2] = 20;
+
+            // Magic cookie fixed value 0x63825363
+            buf[236] = 0x63;
+            buf[237] = 0x82;
+            buf[238] = 0x53;
+            buf[239] = 0x63;
+        }
+
+        pkt = Pkt4Ptr(new Pkt4(buf.data(), buf.size()));
+        pkt->unpack();
+    } catch (...) {
+        // Early exit if package parsing failed.
+        return 0;
+    }
+
+    // Configure random value in packet
+    uint8_t typeChoice = fdp.ConsumeIntegralInRange<uint8_t>(0, 8);
+    pkt->setType(static_cast<DHCPMessageType>(typeChoice));
+
+    // Server initialisation
+    try {
+        srv.reset(new MyDhcpv4Srv());
+        srv->init(path);
+    } catch (...) {
+        // Early exit if server initialisation failed.
+        return 0;
+    }
+
+    if (!srv) {
+        // Early exit if server initialisation failed.
+        return 0;
+    }
+
+    CalloutHandlePtr handle = nullptr;
+
+    // Fuzz pkt4_receive
+    try {
+        handle = getCalloutHandle(pkt);
+        handle->setArgument("query4", pkt);
+        pkt4_receive(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean handle to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    // Call classifyPacket for packet checking
+    try {
+        srv->fuzz_classifyPacket(pkt);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Prepare client context
+    AllocEngine::ClientContext4Ptr ctx(new AllocEngine::ClientContext4());
+
+    // Call earlyGHRLookup
+    try {
+        srv->earlyGHRLookup(pkt, ctx);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Fuzz subnet4_select
+    try {
+        handle = getCalloutHandle(pkt);
+        Pkt4Ptr rsp;
+        CfgMgr& cfgmgr = CfgMgr::instance();
+        handle = getCalloutHandle(pkt);
+        handle->setArgument("query4", pkt);
+        handle->setArgument("subnet4collection",
+                            cfgmgr.getCurrentCfg()->getCfgSubnets4()->getAll());
+        if (!ctx) {
+            ctx.reset(new AllocEngine::ClientContext4());
+        }
+        if (ctx) {
+            handle->setArgument("subnet4", ctx->subnet_);
+        }
+        subnet4_select(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean handle to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    srv.reset();
+
+    // Remove temp files
+    fuzz::deleteTempFile(path);
+    fuzz::deleteTempFile(lease_path);
+    fuzz::deleteTempFile(user_path);
+    return 0;
+}
diff --git a/src/fuzz/fuzz_hook_user_chk6.cc b/src/fuzz/fuzz_hook_user_chk6.cc
new file mode 100644 (file)
index 0000000..8a3bfdd
--- /dev/null
@@ -0,0 +1,231 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <dhcp/dhcp6.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/user_chk/user.h>
+#include <dhcp/user_chk/user_data_source.h>
+#include <dhcp/user_chk/user_file.h>
+#include <dhcp/user_chk/user_registry.h>
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcpsrv/callout_handle_store.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <log/logger_support.h>
+#include <util/filesystem.h>
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+#include <list>
+#include <memory>
+#include <iostream>
+#include <filesystem>
+#include <fstream>
+#include <string>
+#include <cstdio>
+#include <cstdlib>
+
+#include "helper_func.h"
+
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace isc::util;
+using namespace user_chk;
+
+extern "C" UserRegistryPtr user_registry;
+extern "C" int pkt6_receive(CalloutHandle& handle);
+extern "C" int subnet6_select(CalloutHandle& handle);
+
+namespace isc {
+    namespace dhcp {
+        class MyDhcpv6Srv : public ControlledDhcpv6Srv {
+            public:
+                void fuzz_classifyPacket(const Pkt6Ptr& pkt) {
+                    classifyPacket(pkt);
+                }
+        };
+    }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    if (size < 236) {
+        // package size requires at least 236 bytes
+        return 0;
+    }
+
+    FuzzedDataProvider fdp(data, size);
+
+    // Disable validatePath checking to allow writing configuration file to /tmp
+    isc::util::file::PathChecker::enableEnforcement(false);
+
+    // Force DUID file to /tmp
+    setenv("KEA_DHCP_DATA_DIR", "/tmp", 1);
+
+    // Initialise logging
+    setenv("KEA_LOGGER_DESTINATION", "/dev/null", 0);
+    setenv("KEA_LOCKFILE_DIR", "/tmp", 0);
+    setenv("KEA_PIDFILE_DIR", "/tmp", 0);
+    setenv("KEA_LFC_EXECUTABLE", "/bin/true", 0);
+    try {
+        isc::log::initLogger("fuzzer");
+        isc::process::Daemon::loggerInit("fuzzer", false);
+        isc::process::Daemon::setDefaultLoggerName("fuzzer");
+    } catch (...) {
+        // Early exit if logging initialisation failed
+        return 0;
+    }
+
+    // Creating temp config file
+    std::string path = fuzz::writeTempConfig(false);
+    if (path.empty()) {
+        // Early exit if configuration file creation failed
+        fuzz::deleteTempFile(path);
+        return 0;
+    }
+
+    // Creating temp lease file
+    std::string lease_path = fuzz::writeTempLease(false);
+
+    // Creating temp user file
+    std::string user_path = fuzz::writeTempUserFile();
+
+    // Creating user registry
+    try {
+        user_registry.reset(new UserRegistry());
+        UserDataSourcePtr user_file(new UserFile(user_path));
+        user_registry->setSource(user_file);
+        user_registry->refresh();
+    } catch (std::exception& e) {
+        // Early exit if user registry failed to create.
+        return 0;
+    }
+
+    try {
+        for (int i = 0; i < fdp.ConsumeIntegralInRange<int>(1, 5); i++) {
+            if (fdp.ConsumeBool()) {
+                std::vector<uint8_t> mac = fdp.ConsumeBytes<uint8_t>(6);
+                UserPtr user = UserPtr(new User(UserId::HW_ADDRESS, mac));
+                user_registry->addUser(user);
+            } else {
+                const size_t len = fdp.ConsumeIntegralInRange<size_t>(2, 64);
+                std::vector<uint8_t> duid = fdp.ConsumeBytes<uint8_t>(len);
+                UserPtr user = UserPtr(new User(UserId::DUID, duid));
+                user_registry->addUser(user);
+            }
+        }
+    } catch (...) {
+        // Slient exceptions
+    }
+
+    Pkt6Ptr pkt;
+    std::unique_ptr<MyDhcpv6Srv> srv;
+
+    // Package parsing
+    try {
+        pkt = Pkt6Ptr(new Pkt6(data, size));
+        pkt->unpack();
+    } catch (...) {
+        // Early exit if package parsing failed.
+        return 0;
+    }
+
+    // Configure random value in packet
+    uint8_t typeChoice = fdp.ConsumeIntegralInRange<uint8_t>(0, 37);
+    pkt->setType(static_cast<DHCPv6MessageType>(typeChoice));
+
+    // Server initialisation
+    try {
+        srv.reset(new MyDhcpv6Srv());
+        srv->init(path);
+    } catch (...) {
+        // Early exit if server initialisation failed.
+        return 0;
+    }
+
+    if (!srv) {
+        // Early exit if server initialisation failed.
+        return 0;
+    }
+
+    CalloutHandlePtr handle = nullptr;
+
+    // Fuzz pkt6_receive
+    try {
+        handle = getCalloutHandle(pkt);
+        std::vector<uint8_t> duid = fdp.ConsumeBytes<uint8_t>(fdp.ConsumeIntegralInRange<int>(2, 128));
+        OptionPtr option = OptionPtr(new Option(Option::V4, 1, duid));
+        pkt->addOption(option);
+        handle->setArgument("query6", pkt);
+        pkt6_receive(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean handle to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    // Call classifyPacket for packet checking
+    try {
+        srv->fuzz_classifyPacket(pkt);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Prepare client context
+    AllocEngine::ClientContext6 ctx;
+
+    // Call earlyGHRLookup
+    try {
+        srv->earlyGHRLookup(pkt, ctx);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Fuzz subnet6_select
+    try {
+        handle = getCalloutHandle(pkt);
+        Pkt6Ptr rsp;
+        CfgMgr& cfgmgr = CfgMgr::instance();
+        handle = getCalloutHandle(pkt);
+        handle->setArgument("query6", pkt);
+        handle->setArgument("subnet6", ctx.subnet_);
+        handle->setArgument("subnet6collection",
+                            cfgmgr.getCurrentCfg()->getCfgSubnets6()->getAll());
+        subnet6_select(*handle);
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    } catch (const boost::exception& e) {
+        // Slient exceptions
+    }
+
+    // Clean handle to avoid mem leak
+    if (handle) {
+        handle->deleteAllArguments();
+    }
+
+    srv.reset();
+
+    // Remove temp files
+    fuzz::deleteTempFile(path);
+    fuzz::deleteTempFile(lease_path);
+    fuzz::deleteTempFile(user_path);
+
+    return 0;
+}
diff --git a/src/fuzz/fuzz_http.cc b/src/fuzz/fuzz_http.cc
new file mode 100644 (file)
index 0000000..43922da
--- /dev/null
@@ -0,0 +1,216 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <log/logger_support.h>
+#include <process/daemon.h>
+
+#include <cc/data.h>
+
+#include <http/basic_auth_config.h>
+#include <http/cfg_http_header.h>
+#include <http/date_time.h>
+#include <http/http_types.h>
+#include <http/request.h>
+#include <http/request_parser.h>
+#include <http/response.h>
+#include <http/response_parser.h>
+#include <http/url.h>
+
+#include <cstdlib>
+#include <cstring>
+#include <string>
+#include <vector>
+#include <algorithm>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::http;
+
+static constexpr HttpRequest::Method requestMethods[] = {
+    HttpRequest::Method::HTTP_GET, HttpRequest::Method::HTTP_POST,
+    HttpRequest::Method::HTTP_HEAD, HttpRequest::Method::HTTP_PUT,
+    HttpRequest::Method::HTTP_DELETE, HttpRequest::Method::HTTP_OPTIONS,
+    HttpRequest::Method::HTTP_CONNECT, HttpRequest::Method::HTTP_METHOD_UNKNOWN
+};
+
+static constexpr HttpStatusCode statusCodes[] = {
+    HttpStatusCode::OK, HttpStatusCode::CREATED, HttpStatusCode::ACCEPTED,
+    HttpStatusCode::NO_CONTENT, HttpStatusCode::MULTIPLE_CHOICES,
+    HttpStatusCode::MOVED_PERMANENTLY, HttpStatusCode::MOVED_TEMPORARILY,
+    HttpStatusCode::NOT_MODIFIED, HttpStatusCode::BAD_REQUEST,
+    HttpStatusCode::UNAUTHORIZED, HttpStatusCode::FORBIDDEN,
+    HttpStatusCode::NOT_FOUND, HttpStatusCode::REQUEST_TIMEOUT,
+    HttpStatusCode::INTERNAL_SERVER_ERROR, HttpStatusCode::NOT_IMPLEMENTED,
+    HttpStatusCode::BAD_GATEWAY, HttpStatusCode::SERVICE_UNAVAILABLE
+};
+
+template <typename ParserT>
+inline void requestResponseParsing(ParserT& parser, const std::string& payload) {
+    size_t off = 0;
+
+    // Parse the payload until data use up
+    while (off < payload.size()) {
+        size_t remain = payload.size() - off;
+        size_t chunk = 1;
+        chunk = std::min(remain, (off % 32) + 1);
+
+        parser.postBuffer(reinterpret_cast<const uint8_t*>(payload.data()) + off, chunk);
+        off += chunk;
+        parser.poll();
+        if (!parser.needData()) {
+            parser.poll();
+        }
+    }
+
+    // Finishing the parsing
+    if (parser.needData()) {
+        parser.postBuffer(nullptr, 0);
+        parser.poll();
+    }
+
+    parser.getErrorMessage();
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) {
+    FuzzedDataProvider fdp(Data, Size);
+
+    HttpRequest request;
+    HttpResponse response;
+
+    // Generate random value
+    const std::string key = fdp.ConsumeBytesAsString(10);
+    const HttpVersion version = fdp.ConsumeBool()?HttpVersion::HTTP_11():HttpVersion::HTTP_10();
+    const HttpRequest::Method requestMethod = requestMethods[fdp.ConsumeIntegralInRange<int>(0, 7)];
+    const HttpStatusCode statusCode = statusCodes[fdp.ConsumeIntegralInRange<int>(0, 16)];
+
+    // Generate payload
+    const std::string payload = fdp.ConsumeRemainingBytesAsString();
+
+    try {
+        // Request parsing
+        HttpRequestParser requestParser(request);
+        requestParser.initModel();
+
+        // Feed payload into request parser and functions
+        requestResponseParsing(requestParser, payload);
+        request.getHttpVersion();
+        request.context();
+        request.getMethod();
+        request.getUri();
+        request.getBasicAuth();
+        request.requireHttpVersion(version);
+        request.requireHttpMethod(requestMethod);
+        request.getHeaderValue(key);
+        request.getHeader(key);
+        request.requireHeader(key);
+        request.finalize();
+        request.reset();
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    } catch (const boost::exception&) {
+        // Slient exceptions
+    }
+
+    try {
+        // Response parsing
+        HttpResponseParser responseParser(response);
+        responseParser.initModel();
+
+        // Feed payload into response parser and functions
+        requestResponseParsing(responseParser, payload);
+        response.getStatusCode();
+        response.getHttpVersion();
+        response.context();
+        response.requireHttpVersion(version);
+        response.getHeaderValue(key);
+        response.getHeader(key);
+        response.requireHeader(key);
+        response.statusCodeToNumber(statusCode);
+        response.statusCodeToString(statusCode);
+        response.isClientError(statusCode);
+        response.isServerError(statusCode);
+        response.create();
+        response.finalize();
+        response.reset();
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    } catch (const boost::exception&) {
+        // Slient exceptions
+    }
+
+    try {
+        // Response JSON parsing
+        HttpResponseJson json(version, statusCode);
+        json.setBodyAsJson(Element::create(payload));
+        json.finalize();
+        json.reset();
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    } catch (const boost::exception&) {
+        // Slient exceptions
+    }
+
+    try {
+        // Configuration headers parsing
+        ConstElementPtr elem = Element::fromJSON(payload);
+        CfgHttpHeaders headers = parseCfgHttpHeaders(elem);
+        copyHttpHeaders(headers, request);
+        copyHttpHeaders(headers, response);
+        CfgHttpHeaderstoElement(headers);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    } catch (const boost::exception&) {
+        // Slient exceptions
+    }
+
+    try{
+        // Basic Authentication Configuration parsing
+        BasicHttpAuthConfig cfg;
+        cfg.parse(Element::fromJSON(payload));
+        cfg.toElement();
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    } catch (const boost::exception&) {
+        // Slient exceptions
+    }
+
+    try{
+        // Parse url
+        Url url(payload);
+        url.isValid();
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    } catch (const boost::exception&) {
+        // Slient exceptions
+    }
+
+    try{
+        // Parse Datetime
+        HttpDateTime::fromAny(payload);
+        HttpDateTime::fromRfc1123(payload);
+        HttpDateTime::fromRfc850(payload);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    } catch (const boost::exception&) {
+        // Slient exceptions
+    }
+
+    try {
+        request.finalize();
+        response.finalize();
+        request.reset();
+        response.reset();
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    } catch (const boost::exception&) {
+        // Slient exceptions
+    }
+
+    return 0;
+}
diff --git a/src/fuzz/fuzz_http.dict b/src/fuzz/fuzz_http.dict
new file mode 100644 (file)
index 0000000..b91cda5
--- /dev/null
@@ -0,0 +1,91 @@
+"GET"
+"POST"
+"PUT"
+"PATCH"
+"DELETE"
+"HEAD"
+"OPTIONS"
+"TRACE"
+"CONNECT"
+"HTTP/1.0"
+"HTTP/1.1"
+"HTTP/2.0"
+"200 OK"
+"400 Bad Request"
+"401 Unauthorized"
+"403 Forbidden"
+"404 Not Found"
+"500 Internal Server Error"
+"\x0d\x0a"
+"\x0d\x0a\x0d\x0a"
+": "
+":"
+" "
+"Host"
+"User-Agent"
+"Accept"
+"Accept-Encoding"
+"Accept-Language"
+"Connection"
+"Content-Type"
+"Content-Length"
+"Transfer-Encoding"
+"Authorization"
+"Cookie"
+"Set-Cookie"
+"Location"
+"Cache-Control"
+"Pragma"
+"Date"
+"Server"
+"Referer"
+"Upgrade"
+"Upgrade-Insecure-Requests"
+"X-Forwarded-For"
+"X-Forwarded-Proto"
+"X-Real-IP"
+"keep-alive"
+"close"
+"gzip"
+"deflate"
+"br"
+"chunked"
+"application/json"
+"text/plain"
+"text/html"
+"application/octet-stream"
+" / "
+" * "
+" /index.html "
+" /api "
+" HTTP/1.1\x0d\x0a"
+"HTTP/1.1 "
+"HTTP/1.0 "
+"0\x0d\x0a\x0d\x0a"
+"\x0d\x0a0\x0d\x0a\x0d\x0a"
+"1\x0d\x0aA\x0d\x0a0\x0d\x0a\x0d\x0a"
+"4\x0d\x0aWiki\x0d\x0a5\x0d\x0apedia\x0d\x0a0\x0d\x0a\x0d\x0a"
+"{"
+"}"
+"["
+"]"
+","
+"\""
+"\\\""
+"\"http-headers\""
+"\"name\""
+"\"value\""
+"\"Content-Type\""
+"\"Content-Length\""
+"\"Host\""
+"\"User-Agent\""
+"Basic "
+"Bearer "
+"sessionid="
+"Path="
+"Domain="
+"Secure"
+"HttpOnly"
+"SameSite="
+"Transfer-Encoding: chunked\x0d\x0a"
+"Content-Length: 0\x0d\x0a"
diff --git a/src/fuzz/fuzz_ioaddress.cc b/src/fuzz/fuzz_ioaddress.cc
new file mode 100644 (file)
index 0000000..4cc4bec
--- /dev/null
@@ -0,0 +1,34 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <asiolink/io_address.h>
+#include <asiolink/io_error.h>
+
+using isc::asiolink::IOAddress;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  std::string s(reinterpret_cast<const char*>(data), size);
+
+  try {
+    IOAddress addr(s);
+    addr.toText();
+    addr.isV4();
+    addr.isV6();
+    addr.getFamily();
+    addr.toBytes();
+
+    std::vector<uint8_t> bytes = addr.toBytes();
+    IOAddress::fromBytes(addr.getFamily(), &bytes[0]);
+  } catch (const std::exception&) {
+    // Catch exceptions
+  }
+
+  return 0;
+}
diff --git a/src/fuzz/fuzz_mysql4.cc b/src/fuzz/fuzz_mysql4.cc
new file mode 100644 (file)
index 0000000..94cb6ef
--- /dev/null
@@ -0,0 +1,164 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <asiolink/io_address.h>
+#include <database/database_connection.h>
+#include <database/server_selector.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/cfg_option.h>
+
+#include <log/logger_support.h>
+#include <process/daemon.h>
+#include <exceptions/exceptions.h>
+
+#include <mysql_cb_impl.h>
+#include <mysql_cb_dhcp4.h>
+
+#include <cstdint>
+#include <cstddef>
+#include <string>
+#include <set>
+#include <vector>
+#include <map>
+#include <utility>
+#include <iostream>
+
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+extern "C" void mysqlmock_load_bytes(const uint8_t* data, size_t size);
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    // Initialise logging
+    setenv("KEA_LOGGER_DESTINATION", "/dev/null", 0);
+    setenv("KEA_LOCKFILE_DIR", "/tmp", 0);
+    setenv("KEA_PIDFILE_DIR", "/tmp", 0);
+    setenv("KEA_LFC_EXECUTABLE", "/bin/true", 0);
+    try {
+        isc::log::initLogger("fuzzer");
+        isc::process::Daemon::loggerInit("fuzzer", false);
+        isc::process::Daemon::setDefaultLoggerName("fuzzer");
+    } catch (...) {
+        // Early exit if logging initialisation failed
+        return 0;
+    }
+
+    FuzzedDataProvider fdp(data, size);
+    mysqlmock_load_bytes(data, size);
+    DbCallback db_cb;
+
+    // Prepare tags
+    std::set<std::string> tags;
+    std::string tag = fdp.ConsumeRandomLengthString(16);
+    if (tag.empty()) {
+        tag = "default-tag";
+    }
+    tags.insert(tag);
+
+    // Prepare DatabaseConnection parameter maps
+    DatabaseConnection::ParameterMap params;
+    std::string dbname = fdp.ConsumeRandomLengthString(16);
+    if (dbname.empty()) {
+        dbname = "kea_fuzz";
+    }
+    params["name"] = dbname;
+    params[fdp.ConsumeRandomLengthString(16)] = fdp.ConsumeRandomLengthString(16);
+    params[fdp.ConsumeRandomLengthString(16)] = fdp.ConsumeRandomLengthString(16);
+
+    // Prepare server selector
+    ServerSelector selector = ServerSelector::UNASSIGNED();
+    try {
+        switch (fdp.ConsumeIntegralInRange<int>(0, 3)) {
+            case 0: selector = ServerSelector::ALL(); break;
+            case 1: selector = ServerSelector::ONE(tag); break;
+            case 2: selector = ServerSelector::MULTIPLE(tags); break;
+            case 3: selector = ServerSelector::ANY(); break;
+        }
+    } catch (const isc::Exception&) {
+        // Silent exceptions use default UNASSIGNED
+    }
+
+    try {
+        MySqlConfigBackendImpl backend("v4", params, db_cb);
+        MySqlConfigBackendDHCPv4 dhcp_backend(params);
+
+        // Target getGlobalParameter4
+        try {
+            dhcp_backend.getGlobalParameter4(selector, fdp.ConsumeRandomLengthString(32));
+        } catch (const isc::Exception& e) {
+            // Slient exceptions
+        }
+
+        // Target getAllSubnets4
+        try {
+            dhcp_backend.getAllSubnets4(selector);
+        } catch (const isc::Exception& e) {
+            // Slient exceptions
+        }
+
+        // Target createUpdateSubnet4
+        try {
+            IOAddress address("127.0.0.1");
+
+            uint32_t a1 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t b1 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t c1 = fdp.ConsumeIntegral<uint32_t>();
+            Triplet<uint32_t> t1(a1, b1, c1);
+
+            uint32_t a2 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t b2 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t c2 = fdp.ConsumeIntegral<uint32_t>();
+            Triplet<uint32_t> t2(a2, b2, c2);
+
+            uint32_t a3 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t b3 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t c3 = fdp.ConsumeIntegral<uint32_t>();
+            Triplet<uint32_t> t3(a3, b3, c3);
+
+            SubnetID sid = static_cast<SubnetID>(fdp.ConsumeIntegralInRange<uint32_t>(1, UINT32_MAX));
+            Subnet4Ptr subnet(Subnet4::create(address,
+                                              fdp.ConsumeIntegralInRange(0, 32),
+                                              t1, t2, t3, sid));
+            dhcp_backend.createUpdateSubnet4(selector, subnet);
+        } catch (const isc::Exception& e) {
+            // Slient exceptions
+        }
+
+        // Target deleteSubnet4
+        try {
+            dhcp_backend.deleteSubnet4(selector, fdp.ConsumeRandomLengthString(32));
+        } catch (const isc::Exception& e) {
+            // Slient exceptions
+        }
+
+        // Target createUpdateOption4
+        try {
+            OptionBuffer opt_buf;
+            OptionPtr opt(new Option(Option::V4, fdp.ConsumeIntegralInRange<uint16_t>(1, 254), opt_buf));
+            OptionDescriptorPtr opt_desc(new OptionDescriptor(opt, true, true));
+
+            std::string opt_space = "dhcp4";
+            std::string opt_name  = fdp.ConsumeRandomLengthString(32);
+            if (opt_name.empty()) {
+                opt_name = "fuzz-opt";
+            }
+
+            dhcp_backend.createUpdateOption4(selector, opt_name, opt_desc);
+        } catch (const isc::Exception& e) {
+            // Slient exceptions
+        }
+    } catch (const isc::Exception&) {
+        // Silent top-level exceptions
+    }
+
+    return 0;
+}
diff --git a/src/fuzz/fuzz_mysql6.cc b/src/fuzz/fuzz_mysql6.cc
new file mode 100644 (file)
index 0000000..074585d
--- /dev/null
@@ -0,0 +1,169 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <asiolink/io_address.h>
+#include <database/database_connection.h>
+#include <database/server_selector.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/cfg_option.h>
+
+#include <log/logger_support.h>
+#include <process/daemon.h>
+#include <exceptions/exceptions.h>
+
+#include <mysql_cb_impl.h>
+#include <mysql_cb_dhcp6.h>
+
+#include <cstdint>
+#include <cstddef>
+#include <string>
+#include <set>
+#include <vector>
+#include <map>
+#include <utility>
+#include <iostream>
+
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+extern "C" void mysqlmock_load_bytes(const uint8_t* data, size_t size);
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    // Initialise logging
+    setenv("KEA_LOGGER_DESTINATION", "/dev/null", 0);
+    setenv("KEA_LOCKFILE_DIR", "/tmp", 0);
+    setenv("KEA_PIDFILE_DIR", "/tmp", 0);
+    setenv("KEA_LFC_EXECUTABLE", "/bin/true", 0);
+    try {
+        isc::log::initLogger("fuzzer");
+        isc::process::Daemon::loggerInit("fuzzer", false);
+        isc::process::Daemon::setDefaultLoggerName("fuzzer");
+    } catch (...) {
+        // Early exit if logging initialisation failed
+        return 0;
+    }
+
+    FuzzedDataProvider fdp(data, size);
+    mysqlmock_load_bytes(data, size);
+    DbCallback db_cb;
+
+    // Prepare tags
+    std::set<std::string> tags;
+    std::string tag = fdp.ConsumeRandomLengthString(16);
+    if (tag.empty()) {
+        tag = "default-tag";
+    }
+    tags.insert(tag);
+
+    // Prepare DatabaseConnection parameter maps
+    DatabaseConnection::ParameterMap params;
+    std::string dbname = fdp.ConsumeRandomLengthString(16);
+    if (dbname.empty()) {
+        dbname = "kea_fuzz";
+    }
+    params["name"] = dbname;
+    params[fdp.ConsumeRandomLengthString(16)] = fdp.ConsumeRandomLengthString(16);
+    params[fdp.ConsumeRandomLengthString(16)] = fdp.ConsumeRandomLengthString(16);
+
+    // Prepare server selector
+    ServerSelector selector = ServerSelector::UNASSIGNED();
+    try {
+        switch (fdp.ConsumeIntegralInRange<int>(0, 3)) {
+            case 0: selector = ServerSelector::ALL(); break;
+            case 1: selector = ServerSelector::ONE(tag); break;
+            case 2: selector = ServerSelector::MULTIPLE(tags); break;
+            case 3: selector = ServerSelector::ANY(); break;
+        }
+    } catch (const isc::Exception&) {
+        // Silent exceptions use default UNASSIGNED
+    }
+
+    try {
+        MySqlConfigBackendImpl backend("v6", params, db_cb);
+        MySqlConfigBackendDHCPv6 dhcp_backend(params);
+
+        // Target getGlobalParameter6
+        try {
+            dhcp_backend.getGlobalParameter6(selector, fdp.ConsumeRandomLengthString(32));
+        } catch (const isc::Exception& e) {
+            // Slient exceptions
+        }
+
+        // Target getAllSubnets6
+        try {
+            dhcp_backend.getAllSubnets6(selector);
+        } catch (const isc::Exception& e) {
+            // Slient exceptions
+        }
+
+        // Target createUpdateSubnet6
+        try {
+            IOAddress address("::1");
+
+            uint32_t a1 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t b1 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t c1 = fdp.ConsumeIntegral<uint32_t>();
+            Triplet<uint32_t> t1(a1, b1, c1);
+
+            uint32_t a2 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t b2 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t c2 = fdp.ConsumeIntegral<uint32_t>();
+            Triplet<uint32_t> t2(a2, b2, c2);
+
+            uint32_t a3 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t b3 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t c3 = fdp.ConsumeIntegral<uint32_t>();
+            Triplet<uint32_t> t3(a3, b3, c3);
+
+            uint32_t a4 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t b4 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t c4 = fdp.ConsumeIntegral<uint32_t>();
+            Triplet<uint32_t> t4(a4, b4, c4);
+
+            SubnetID sid = static_cast<SubnetID>(fdp.ConsumeIntegralInRange<uint32_t>(1, UINT32_MAX));
+            Subnet6Ptr subnet(Subnet6::create(address,
+                                              fdp.ConsumeIntegralInRange(0, 32),
+                                              t1, t2, t3, t4, sid));
+            dhcp_backend.createUpdateSubnet6(selector, subnet);
+        } catch (const isc::Exception& e) {
+            // Slient exceptions
+        }
+
+        // Target deleteSubnet6
+        try {
+            dhcp_backend.deleteSubnet6(selector, fdp.ConsumeRandomLengthString(32));
+        } catch (const isc::Exception& e) {
+            // Slient exceptions
+        }
+
+        // Target createUpdateOption6
+        try {
+            OptionBuffer opt_buf;
+            OptionPtr opt(new Option(Option::V4, fdp.ConsumeIntegralInRange<uint16_t>(1, 254), opt_buf));
+            OptionDescriptorPtr opt_desc(new OptionDescriptor(opt, true, true));
+
+            std::string opt_space = "dhcp4";
+            std::string opt_name  = fdp.ConsumeRandomLengthString(32);
+            if (opt_name.empty()) {
+                opt_name = "fuzz-opt";
+            }
+
+            dhcp_backend.createUpdateOption6(selector, opt_name, opt_desc);
+        } catch (const isc::Exception& e) {
+            // Slient exceptions
+        }
+    } catch (const isc::Exception&) {
+        // Silent top-level exceptions
+    }
+
+    return 0;
+}
diff --git a/src/fuzz/fuzz_pgsql4.cc b/src/fuzz/fuzz_pgsql4.cc
new file mode 100644 (file)
index 0000000..4903746
--- /dev/null
@@ -0,0 +1,195 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <asiolink/io_address.h>
+#include <database/database_connection.h>
+#include <database/server_selector.h>
+#include <dhcp/pgsql/pgsql_cb_impl.h>
+#include <dhcp/pgsql/pgsql_cb_dhcp4.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/host.h>
+#include <pgsql/pgsql_exchange.h>
+
+#include <log/logger_support.h>
+#include <process/daemon.h>
+#include <exceptions/exceptions.h>
+
+#include <cstdint>
+#include <cstddef>
+#include <string>
+#include <vector>
+#include <map>
+#include <utility>
+#include <iostream>
+
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+extern "C" void pgmock_load_bytes(const uint8_t* data, size_t size);
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    // Initialise logging
+    setenv("KEA_LOGGER_DESTINATION", "/dev/null", 0);
+    setenv("KEA_LOCKFILE_DIR", "/tmp", 0);
+    setenv("KEA_PIDFILE_DIR", "/tmp", 0);
+    setenv("KEA_LFC_EXECUTABLE", "/bin/true", 0);
+    try {
+        isc::log::initLogger("fuzzer");
+        isc::process::Daemon::loggerInit("fuzzer", false);
+        isc::process::Daemon::setDefaultLoggerName("fuzzer");
+    } catch (...) {
+        // Early exit if logging initialisation failed
+        return 0;
+    }
+
+    FuzzedDataProvider fdp(data, size);
+    pgmock_load_bytes(data, size);
+
+    DbCallback db_cb;
+    size_t index = 0;
+
+    // Prepare tags
+    std::set<std::string> tags;
+    std::string tag = fdp.ConsumeRandomLengthString(16);
+    if (tag.size() == 0) {
+        tag = "default-tag";
+    }
+    tags.insert(tag);
+
+    // Preparer DatabaseConnection parameter map
+    DatabaseConnection::ParameterMap params;
+    params["name"] = fdp.ConsumeRandomLengthString(32);
+    params[fdp.ConsumeRandomLengthString(32)] = fdp.ConsumeRandomLengthString(32);
+    params[fdp.ConsumeRandomLengthString(32)] = fdp.ConsumeRandomLengthString(32);
+    params[fdp.ConsumeRandomLengthString(32)] = fdp.ConsumeRandomLengthString(32);
+
+    // Prepare Binding array
+    PsqlBindArray binds;
+    binds.add(fdp.ConsumeRandomLengthString(256));
+    binds.add(fdp.ConsumeRandomLengthString(256));
+    binds.add(fdp.ConsumeRandomLengthString(256));
+
+    // Prepare server selector
+    ServerSelector selector = ServerSelector::UNASSIGNED();
+    try {
+        switch (fdp.ConsumeIntegralInRange<int>(0, 3)) {
+            case 0:
+                selector = ServerSelector::ALL();
+                break;
+            case 1:
+                selector = ServerSelector::ONE(tag);
+                break;
+            case 2:
+                selector = ServerSelector::MULTIPLE(tags);
+                break;
+            case 3:
+                selector = ServerSelector::ANY();
+                break;
+        }
+    } catch (const isc::Exception&) {
+        // Slient exceptions use default unassigned server selector
+    }
+
+    try {
+        // Prepare PgSql backend
+        PgSqlConfigBackendImpl backend(fdp.ConsumeRandomLengthString(32), params, db_cb, index);
+        PgSqlConfigBackendDHCPv4 dhcp_backend(params);
+
+        // Target selectQuery
+        try {
+            backend.selectQuery(0, binds, PgSqlConnection::ConsumeResultRowFun([](PgSqlResult&, int) {}));
+        } catch (const isc::Exception&) {
+            // Slient exceptions
+        }
+
+        // Target insertQuery
+        try {
+            backend.insertQuery(0, binds);
+        } catch (const isc::Exception&) {
+            // Slient exceptions
+        }
+
+        // Target updateDeleteQuery
+        try {
+            backend.updateDeleteQuery(0, binds);
+        } catch (const isc::Exception&) {
+            // Slient exceptions
+        }
+
+        // Target upper level getGlobalParameter4
+        try {
+            dhcp_backend.getGlobalParameter4(selector, fdp.ConsumeRandomLengthString(32));
+        } catch (const isc::Exception&) {
+            // Slient exceptions
+        }
+
+        // Target upper level getAllSubnets4
+        try {
+            dhcp_backend.getAllSubnets4(selector);
+        } catch (const isc::Exception&) {
+            // Slient exceptions
+        }
+
+        // Target upper level createUpdateSubnet4
+        try {
+            IOAddress address(fdp.ConsumeRandomLengthString(15));
+            uint32_t a1 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t b1 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t c1 = fdp.ConsumeIntegral<uint32_t>();
+            Triplet<uint32_t> t1(a1, b1, c1);
+
+            uint32_t a2 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t b2 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t c2 = fdp.ConsumeIntegral<uint32_t>();
+            Triplet<uint32_t> t2(a2, b2, c2);
+
+            uint32_t a3 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t b3 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t c3 = fdp.ConsumeIntegral<uint32_t>();
+            Triplet<uint32_t> t3(a3, b3, c3);
+
+            SubnetID sid = static_cast<SubnetID>(fdp.ConsumeIntegral<uint32_t>());
+
+            Subnet4Ptr subnet(Subnet4::create(address, fdp.ConsumeIntegralInRange(0, 32), t1, t2, t3, sid));
+            dhcp_backend.createUpdateSubnet4(selector, subnet);
+        } catch (const isc::Exception&) {
+            // Slient exceptions
+        }
+
+        // Target upper level deleteSubnet4
+        try {
+            dhcp_backend.deleteSubnet4(selector, fdp.ConsumeRandomLengthString(32));
+        } catch (const isc::Exception&) {
+            // Slient exceptions
+        }
+
+        // Target upper level createUpdateOption4
+        try {
+            OptionBuffer opt_buf;
+            OptionPtr opt(new Option(Option::V4, 0, opt_buf));
+            OptionDescriptorPtr opt_desc(new OptionDescriptor(opt, true, true));
+
+            std::string opt_space = "dhcp4";
+            std::string opt_name  = fdp.ConsumeRandomLengthString(32);
+            if (opt_name.empty()) {
+                opt_name = "fuzz-opt";
+            }
+
+            dhcp_backend.createUpdateOption4(selector, opt_name, opt_desc);
+        } catch (const isc::Exception&) {
+            // Slient exceptions
+        }
+    } catch (const isc::Exception& e) {
+        // Slient exceptions
+    }
+
+    return 0;
+}
diff --git a/src/fuzz/fuzz_pgsql6.cc b/src/fuzz/fuzz_pgsql6.cc
new file mode 100644 (file)
index 0000000..633250f
--- /dev/null
@@ -0,0 +1,199 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <asiolink/io_address.h>
+#include <database/database_connection.h>
+#include <database/server_selector.h>
+#include <dhcp/pgsql/pgsql_cb_impl.h>
+#include <dhcp/pgsql/pgsql_cb_dhcp6.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/host.h>
+#include <pgsql/pgsql_exchange.h>
+
+#include <log/logger_support.h>
+#include <process/daemon.h>
+#include <exceptions/exceptions.h>
+
+#include <cstdint>
+#include <cstddef>
+#include <string>
+#include <vector>
+#include <map>
+#include <utility>
+#include <iostream>
+
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+extern "C" void pgmock_load_bytes(const uint8_t* data, size_t size);
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    // Initialise logging
+    setenv("KEA_LOGGER_DESTINATION", "/dev/null", 0);
+    setenv("KEA_LOCKFILE_DIR", "/tmp", 0);
+    setenv("KEA_PIDFILE_DIR", "/tmp", 0);
+    setenv("KEA_LFC_EXECUTABLE", "/bin/true", 0);
+    try {
+        isc::log::initLogger("fuzzer");
+        isc::process::Daemon::loggerInit("fuzzer", false);
+        isc::process::Daemon::setDefaultLoggerName("fuzzer");
+    } catch (...) {
+        // Early exit if logging initialisation failed
+        return 0;
+    }
+
+    FuzzedDataProvider fdp(data, size);
+    pgmock_load_bytes(data, size);
+
+    DbCallback db_cb;
+    size_t index = 0;
+
+    // Prepare tags
+    std::set<std::string> tags;
+    std::string tag = fdp.ConsumeRandomLengthString(16);
+    if (tag.size() == 0) {
+        tag = "default-tag";
+    }
+    tags.insert(tag);
+
+    // Preparer DatabaseConnection parameter map
+    DatabaseConnection::ParameterMap params;
+    params[fdp.ConsumeRandomLengthString(32)] = fdp.ConsumeRandomLengthString(32);
+    params[fdp.ConsumeRandomLengthString(32)] = fdp.ConsumeRandomLengthString(32);
+    params[fdp.ConsumeRandomLengthString(32)] = fdp.ConsumeRandomLengthString(32);
+
+    // Prepare Binding array
+    PsqlBindArray binds;
+    binds.add(fdp.ConsumeRandomLengthString(256));
+    binds.add(fdp.ConsumeRandomLengthString(256));
+    binds.add(fdp.ConsumeRandomLengthString(256));
+
+    // Prepare server selector
+    ServerSelector selector = ServerSelector::UNASSIGNED();
+    try {
+        switch (fdp.ConsumeIntegralInRange<int>(0, 3)) {
+            case 0:
+                selector = ServerSelector::ALL();
+                break;
+            case 1:
+                selector = ServerSelector::ONE(tag);
+                break;
+            case 2:
+                selector = ServerSelector::MULTIPLE(tags);
+                break;
+            case 3:
+                selector = ServerSelector::ANY();
+                break;
+        }
+    } catch (const isc::Exception&) {
+        // Slient exceptions use default unassigned server selector
+    }
+
+    try {
+        // Prepare PgSql backend
+        PgSqlConfigBackendImpl backend(fdp.ConsumeRandomLengthString(32), params, db_cb, index);
+        PgSqlConfigBackendDHCPv6 dhcp_backend(params);
+
+        // Target selectQuery
+        try {
+            backend.selectQuery(0, binds, PgSqlConnection::ConsumeResultRowFun([](PgSqlResult&, int) {}));
+        } catch (const isc::Exception&) {
+            // Slient exceptions
+        }
+
+        // Target insertQuery
+        try {
+            backend.insertQuery(0, binds);
+        } catch (const isc::Exception&) {
+            // Slient exceptions
+        }
+
+        // Target updateDeleteQuery
+        try {
+            backend.updateDeleteQuery(0, binds);
+        } catch (const isc::Exception&) {
+            // Slient exceptions
+        }
+
+        // Target upper level getGlobalParameter6
+        try {
+            dhcp_backend.getGlobalParameter6(selector, fdp.ConsumeRandomLengthString(32));
+        } catch (const isc::Exception&) {
+            // Slient exceptions
+        }
+
+        // Target upper level getAllSubnets6
+        try {
+            dhcp_backend.getAllSubnets6(selector);
+        } catch (const isc::Exception&) {
+            // Slient exceptions
+        }
+
+        // Target upper level createUpdateSubnet6
+        try {
+            IOAddress address(fdp.ConsumeRandomLengthString(64));
+            uint32_t a1 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t b1 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t c1 = fdp.ConsumeIntegral<uint32_t>();
+            Triplet<uint32_t> t1(a1, b1, c1);
+
+            uint32_t a2 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t b2 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t c2 = fdp.ConsumeIntegral<uint32_t>();
+            Triplet<uint32_t> t2(a2, b2, c2);
+
+            uint32_t a3 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t b3 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t c3 = fdp.ConsumeIntegral<uint32_t>();
+            Triplet<uint32_t> t3(a3, b3, c3);
+
+            uint32_t a4 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t b4 = fdp.ConsumeIntegral<uint32_t>();
+            uint32_t c4 = fdp.ConsumeIntegral<uint32_t>();
+            Triplet<uint32_t> t4(a4, b4, c4);
+
+            SubnetID sid = static_cast<SubnetID>(fdp.ConsumeIntegral<uint32_t>());
+
+            Subnet6Ptr subnet(Subnet6::create(address, fdp.ConsumeIntegralInRange(0, 128), t1, t2, t3, t4, sid));
+            dhcp_backend.createUpdateSubnet6(selector, subnet);
+        } catch (const isc::Exception&) {
+            // Slient exceptions
+        }
+
+        // Target upper level deleteSubnet6
+        try {
+            dhcp_backend.deleteSubnet6(selector, fdp.ConsumeRandomLengthString(32));
+        } catch (const isc::Exception&) {
+            // Slient exceptions
+        }
+
+        // Target upper level createUpdateOption6
+        try {
+            OptionBuffer opt_buf;
+            OptionPtr opt(new Option(Option::V6, 0, opt_buf));
+            OptionDescriptorPtr opt_desc(new OptionDescriptor(opt, true, true));
+
+            std::string opt_space = "dhcp4";
+            std::string opt_name  = fdp.ConsumeRandomLengthString(32);
+            if (opt_name.empty()) {
+                opt_name = "fuzz-opt";
+            }
+
+            dhcp_backend.createUpdateOption6(selector, opt_name, opt_desc);
+        } catch (const isc::Exception&) {
+            // Slient exceptions
+        }
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    }
+
+    return 0;
+}
diff --git a/src/fuzz/fuzz_util.cc b/src/fuzz/fuzz_util.cc
new file mode 100644 (file)
index 0000000..50347ad
--- /dev/null
@@ -0,0 +1,156 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <exceptions/exceptions.h>
+#include <util/str.h>
+#include <util/csv_file.h>
+#include <util/encode/utf8.h>
+#include <util/boost_time_utils.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+#include <string>
+#include <vector>
+#include <cstddef>
+
+using namespace boost::posix_time;
+using namespace isc::util;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    FuzzedDataProvider fdp(data, size);
+
+    const bool isEscape = fdp.ConsumeBool();
+    const std::string delim = fdp.ConsumeBytesAsString(1);
+    const std::string payload = fdp.ConsumeRemainingBytesAsString();
+
+    std::vector<uint8_t> out;
+
+    // Target str tokens
+    try {
+        str::tokens(payload, delim, isEscape);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    } catch (const boost::exception&) {
+        // Slient exceptions
+    }
+
+    // Target str quotedStringToBinary
+    try {
+        str::quotedStringToBinary(payload);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    } catch (const boost::exception&) {
+        // Slient exceptions
+    }
+
+    // Target str decodeFormattedHexString
+    try {
+        str::decodeFormattedHexString(payload, out);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    } catch (const boost::exception&) {
+        // Slient exceptions
+    }
+
+    // Target str decodeColonSeparatedHexString
+    try {
+        str::decodeColonSeparatedHexString(payload, out);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    } catch (const boost::exception&) {
+        // Slient exceptions
+    }
+
+    // Target str decodeSeparatedHexString
+    try {
+        str::decodeSeparatedHexString(payload, delim, out);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    } catch (const boost::exception&) {
+        // Slient exceptions
+    }
+
+    // Target str trim
+    try {
+        str::trim(payload);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    } catch (const boost::exception&) {
+        // Slient exceptions
+    }
+
+    // Target str lowercase/uppercase
+    try {
+        std::string temp = payload;
+        str::lowercase(temp);
+        str::uppercase(temp);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    } catch (const boost::exception&) {
+        // Slient exceptions
+    }
+
+    // Target CSVRow
+    try {
+        CSVRow row(payload, delim[0]);
+        for (int i = 0; i < row.getValuesCount(); i++) {
+            row.readAt(i);
+            row.readAtEscaped(i);
+        }
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    } catch (const boost::exception&) {
+        // Slient exceptions
+    }
+
+    // Target encodeUtf8
+    try {
+        encode::encodeUtf8(payload);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    } catch (const boost::exception&) {
+        // Slient exceptions
+    }
+
+    // Prepare posix_time object
+    ptime pt;
+    try {
+        pt = time_from_string(payload);
+    } catch (...) {
+        // Failed for time_from_string, try from_iso_extended_string
+        try {
+            pt = from_iso_extended_string(payload);
+        } catch (...) {
+            // Failed to create posix_time object, early exit
+            return 0;
+        }
+    }
+
+    // Target ptimeToText
+    try {
+        ptimeToText(pt);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    } catch (const boost::exception&) {
+        // Slient exceptions
+    }
+
+    // Target durationToText
+    try {
+        time_duration td = pt.time_of_day();
+        isc::util::durationToText(td);
+    } catch (const isc::Exception&) {
+        // Slient exceptions
+    } catch (const boost::exception&) {
+        // Slient exceptions
+    }
+
+    return 0;
+}
+
diff --git a/src/fuzz/helper_func.cc b/src/fuzz/helper_func.cc
new file mode 100644 (file)
index 0000000..7534230
--- /dev/null
@@ -0,0 +1,70 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 "helper_func.h"
+
+#include <cstdlib>
+#include <filesystem>
+#include <fstream>
+#include <unistd.h>
+
+namespace fs = std::filesystem;
+
+using namespace isc::data;
+
+namespace fuzz {
+    std::string writeTempConfig(bool isV4) {
+        return writeTempFile(isV4? JSON_CONFIG4 : JSON_CONFIG6);
+    }
+
+    std::string writeTempLease(bool isV4) {
+        if (isV4) {
+            return writeTempFile(LEASE4, "", "/tmp/kea-leases4.csv");
+        } else {
+            return writeTempFile(LEASE6, "", "/tmp/kea-leases6.csv");
+        }
+    }
+
+    std::string writeTempUserFile() {
+        return writeTempFile(USER, "", "/tmp/users.txt");
+    }
+
+    std::string writeTempFile(const std::string& payload, const char* suffix, const std::string& explicit_path) {
+        std::string path = explicit_path;
+        if (explicit_path.empty()) {
+            const long r = std::rand();
+            const pid_t pid = ::getpid();
+            path = std::string("/tmp/kea_fuzz_") + std::to_string(pid) +
+                   "_" + std::to_string(r) + "." + (suffix ? suffix : "tmp");
+        }
+
+        std::ofstream ofs(path.c_str(), std::ios::binary);
+        if (ofs.good()) {
+            ofs.write(payload.data(), static_cast<std::streamsize>(payload.size()));
+            ofs.close();
+            return path;
+        }
+        return std::string();
+    }
+
+    void deleteTempFile(std::string file_path) {
+        if (fs::exists(file_path)) {
+            try {
+                fs::remove(file_path);
+            } catch (...) {
+                // Slient exceptions
+            }
+        }
+    }
+
+    isc::data::ElementPtr parseJSON(const std::string& s) {
+        try {
+            return Element::fromJSON(s);
+        } catch (...) {
+            return Element::createMap();
+        }
+    }
+}
diff --git a/src/fuzz/helper_func.h b/src/fuzz/helper_func.h
new file mode 100644 (file)
index 0000000..e20ae6c
--- /dev/null
@@ -0,0 +1,86 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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/.
+////////////////////////////////////////////////////////////////////////////////
+#pragma once
+
+#include <string>
+#include <cc/data.h>
+
+static const std::string JSON_CONFIG4 = R"CONFIG(
+    {
+        "Dhcp4":{
+            "interfaces-config": {
+                "interfaces": [ "eth0" ]
+            },
+            "lease-database": {
+                "type": "memfile",
+                "lfc-interval": 3600,
+                "name": "/tmp/kea-leases4.csv"
+            },
+            "valid-lifetime": 4000,
+            "subnet4": [{
+                "pools": [ { "pool":  "192.0.2.1 - 192.0.2.200" } ],
+                "id": 1,
+                "subnet": "192.0.2.0/24",
+                "interface": "eth0"
+            }],
+            "loggers": [{
+                "name": "kea-dhcp4",
+                "output-options": [{
+                    "output": "stdout"
+                }],
+                "severity": "INFO"
+            }]
+        }
+    })CONFIG";
+
+static const std::string JSON_CONFIG6 = R"CONFIG(
+    {
+        "Dhcp6": {
+            "interfaces-config": {
+                "interfaces": [ "eth0" ]
+            },
+            "option-data": [{
+                "name": "dns-servers",
+                "data": "2001:db8::1, 2001:db8::2"
+            }],
+            "lease-database": {
+                "type": "memfile",
+                "lfc-interval": 3600,
+                "name": "/tmp/kea-leases6.csv"
+            }
+        }
+    })CONFIG";
+
+static const std::string LEASE4 = R"LEASE(
+address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id
+192.0.2.10,32:30,33:30,40,1642000000,50,1,1,one.example.com,0,,0
+192.0.2.11,,31:32:33,40,1643210000,50,1,1,,1,{  },0
+192.0.2.12,32:32,,40,1643212345,50,1,1,three&#x2cexample&#x2ccom,2,{ "a": 1&#x2c "b": "c" },0
+192.0.2.13,aa:bb:cc:dd:ee:01,01:23:45:67:89:ab,86400,1767225600,1,1,1,host1.example.test,0,{  },0
+)LEASE";
+
+static const std::string LEASE6 = R"LEASE(
+address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,state,user_context,hwtype,hwaddr_source,pool_id
+::10,32:30:33,30,1642000000,40,50,1,60,128,1,1,one.example.com,38:30,0,,90,16,0
+::11,32:31:33,30,1643210000,40,50,1,60,128,1,1,,38:30,1,{  },90,1,0
+::12,32:32:33,30,1643212345,40,50,1,60,128,1,1,three&#x2cexample&#x2ccom,38:30,2,{ "a": 1&#x2c "b": "c" },90,4,0
+2001:db8::100,00:01:00:01:12:34:56:78:aa:bb:cc:dd,86400,1767225600,1,43200,0,1,128,1,1,host1v6.example.test,aa:bb:cc:dd:ee:01,0,{  },1,1,0
+)LEASE";
+
+static const std::string USER = R"USER({ "type" : "HW_ADDR", "id" : "01AC00F03344", "opt1" : "true" }
+{ "type" : "HW_ADDR", "id" : "01:AC:00:F0:33:45", "opt1" : "true" }
+{ "type" : "DUID", "id" : "225060de0a0b", "opt1" : "true" }
+{ "type" : "DUID", "id" : "22:50:60:de:0a:0c", "opt1" : "true" })USER";
+
+namespace fuzz {
+    std::string writeTempConfig(bool isV4);
+    std::string writeTempLease(bool isV4);
+    std::string writeTempUserFile();
+    std::string writeTempFile(const std::string& payload, const char* suffix = "json", const std::string& explicit_path = "");
+    void deleteTempFile(std::string file_path);
+    isc::data::ElementPtr parseJSON(const std::string& s);
+}
diff --git a/src/fuzz/mysqlmock.cc b/src/fuzz/mysqlmock.cc
new file mode 100644 (file)
index 0000000..600ab84
--- /dev/null
@@ -0,0 +1,491 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <mariadb/mysql.h>
+
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <vector>
+#include <string>
+#include <algorithm>
+#include <unordered_map>
+
+struct StmtState;
+
+static thread_local FuzzedDataProvider* g_fdp = nullptr;
+static thread_local std::string g_mysql_error;
+static thread_local std::string g_stmt_error;
+static thread_local std::string g_tls_cipher;
+
+struct MockResRow {
+    enum ColKind {
+        CK_UINT64, CK_UINT32, CK_UINT8, CK_STRING, CK_BLOB
+    } kind;
+    std::string s;
+    std::vector<uint8_t> blob;
+    uint64_t u64 = 0;
+    uint32_t u32 = 0;
+    uint8_t u8 = 0;
+};
+
+struct StmtState {
+    MYSQL* mysql;
+    std::string sql;
+    std::vector<std::vector<MockResRow>> rows;
+    size_t fetch_index = 0;
+    MYSQL_BIND* res_binds = nullptr;
+    size_t res_binds_count = 0;
+    bool has_rows = false;
+    bool is_version_stmt = false;
+    unsigned int field_count = 0;
+};
+
+static thread_local std::unordered_map<MYSQL_STMT*, StmtState*> g_stmt_state;
+static thread_local std::vector<MYSQL_STMT*> g_all_stmts;
+static thread_local std::vector<StmtState*> g_live_stmts;
+
+extern "C" void mysqlmock_load_bytes(const uint8_t* data, size_t size) {
+    for (auto* st : g_all_stmts) {
+       free(st);
+    }
+    g_all_stmts.clear();
+    for (auto& kv : g_stmt_state) {
+        delete kv.second;
+    }
+    g_stmt_state.clear();
+    g_stmt_state.rehash(0);
+    delete g_fdp;
+    g_fdp = new FuzzedDataProvider(data, size);
+}
+
+static bool is_like(const std::string& hay, const char* needle) {
+    std::string h = hay;
+    std::string n = needle ? needle : "";
+    std::transform(h.begin(), h.end(), h.begin(), ::tolower);
+    std::transform(n.begin(), n.end(), n.begin(), ::tolower);
+    return h.find(n) != std::string::npos;
+}
+
+static MYSQL_STMT* make_stmt() {
+    auto s = new StmtState();
+    s->mysql = reinterpret_cast<MYSQL*>(0x1);
+    g_live_stmts.push_back(s);
+    return reinterpret_cast<MYSQL_STMT*>(s);
+}
+
+static StmtState* SS(MYSQL_STMT* st) {
+    if (!st) {
+        return nullptr;
+    }
+    auto it = g_stmt_state.find(st);
+    return (it == g_stmt_state.end()) ? nullptr : it->second;
+}
+
+static StmtState* ensure_state(MYSQL_STMT* st) {
+    auto s = SS(st);
+    if (s) return s;
+    s = new StmtState();
+    s->mysql = reinterpret_cast<MYSQL*>(0x1);
+    g_stmt_state[st] = s;
+    g_live_stmts.push_back(s);
+    return s;
+}
+
+static unsigned int infer_field_count_from_sql(const std::string& sql) {
+    std::string s = sql;
+    std::transform(s.begin(), s.end(), s.begin(), ::tolower);
+    auto psel = s.find("select");
+    if (psel == std::string::npos) {
+        return 0;
+    }
+    auto pfrom = s.find(" from ", psel);
+    if (pfrom == std::string::npos) {
+        return 0;
+    }
+    std::string proj = s.substr(psel + 6, pfrom - (psel + 6));
+
+    proj.erase(0, proj.find_first_not_of(" \t\r\n"));
+    proj.erase(proj.find_last_not_of(" \t\r\n")+1);
+    if (proj.rfind("distinct", 0) == 0) {
+        proj.erase(0, 8);
+        proj.erase(0, proj.find_first_not_of(" \t\r\n"));
+    }
+
+    unsigned int cols = 0;
+    int paren = 0;
+    bool in_s = false, in_d = false;
+    for (size_t i = 0; i < proj.size(); ++i) {
+        char c = proj[i];
+        if (!in_d && c=='\'' && (i==0 || proj[i-1] != '\\')) {
+            in_s = !in_s;
+        } else if (!in_s && c=='"' && (i==0 || proj[i-1] != '\\')) {
+            in_d = !in_d;
+        } else if (!in_s && !in_d) {
+            if (c=='(') {
+                paren++;
+            } else if (c==')' && paren>0) {
+                paren--;
+            } else if (c==',' && paren==0) {
+                cols++;
+            }
+        }
+    }
+    if (!proj.empty()) {
+        cols++;
+    }
+    return cols;
+}
+
+static void fill_version_stmt(StmtState* s) {
+    s->is_version_stmt = true;
+    s->has_rows = true;
+    s->rows.clear();
+    s->field_count = 2;
+    std::vector<MockResRow> r;
+    MockResRow c1;
+    c1.kind = MockResRow::CK_UINT32;
+    c1.u32 = 32;
+    MockResRow c2;
+    c2.kind = MockResRow::CK_UINT32;
+    c2.u32 = 0;
+    r.push_back(c1);
+    r.push_back(c2);
+    s->rows.push_back(r);
+}
+
+static void fill_no_rows(StmtState* s, unsigned int cols = 0) {
+    s->is_version_stmt = false;
+    s->has_rows = false;
+    s->rows.clear();
+    s->fetch_index = 0;
+    s->field_count = cols;
+}
+
+static void fill_fuzz_rows(StmtState* s, unsigned int ncols) {
+    s->is_version_stmt = false;
+    s->has_rows = true;
+    s->rows.clear();
+    s->fetch_index = 0;
+    s->field_count = ncols ? ncols : 1;;
+    unsigned int nrows = g_fdp->ConsumeIntegralInRange<unsigned int>(1, 3);
+    for (unsigned int r = 0; r < nrows; ++r) {
+        std::vector<MockResRow> row;
+        row.reserve(ncols);
+        for (unsigned int c = 0; c < ncols; ++c) {
+            int k = g_fdp->ConsumeIntegralInRange<int>(0, 4);
+            MockResRow cell;
+            switch (k) {
+                case 0:
+                    cell.kind = MockResRow::CK_UINT64;
+                    cell.u64 = g_fdp->ConsumeIntegral<uint64_t>();
+                    break;
+                case 1:
+                    cell.kind = MockResRow::CK_UINT32;
+                    cell.u32 = g_fdp->ConsumeIntegral<uint32_t>();
+                    break;
+                case 2:
+                    cell.kind = MockResRow::CK_UINT8;
+                    cell.u8 = g_fdp->ConsumeIntegral<uint8_t>();
+                    break;
+                case 3:
+                    cell.kind = MockResRow::CK_STRING;
+                    cell.s = g_fdp->ConsumeRandomLengthString(32);
+                    break;
+                case 4: {
+                    cell.kind = MockResRow::CK_BLOB;
+                    size_t n = g_fdp->ConsumeIntegralInRange<size_t>(0, 32);
+                    cell.blob = g_fdp->ConsumeBytes<uint8_t>(n);
+                    break;
+                }
+            }
+            row.push_back(cell);
+        }
+        s->rows.push_back(std::move(row));
+    }
+}
+
+extern "C" {
+    int mysql_server_init(int argc, char **argv, char **groups) {
+        return 0;
+    }
+
+    MYSQL* mysql_init(MYSQL* in) {
+        return in ? in : reinterpret_cast<MYSQL*>(0x1);
+    }
+
+    void mysql_close(MYSQL*) {}
+
+    void mysql_free_result(MYSQL_RES*) {}
+
+    void mysql_server_end(void) {}
+
+    MYSQL* mysql_real_connect(MYSQL* mysql,
+                              const char*,
+                              const char*,
+                              const char*,
+                              const char*,
+                              unsigned int,
+                              const char*,
+                              unsigned long) {
+        return mysql ? mysql : reinterpret_cast<MYSQL*>(0x1);
+    }
+
+    unsigned int mysql_errno(MYSQL*) {
+        return 0;
+    }
+
+    const char* mysql_error(MYSQL*) {
+        g_mysql_error = g_fdp ? g_fdp->ConsumeRandomLengthString(32) : std::string();
+        return g_mysql_error.c_str();
+    }
+
+    MYSQL_STMT* mysql_stmt_init(MYSQL*) {
+        auto* stmt = static_cast<MYSQL_STMT*>(std::calloc(1, sizeof(MYSQL_STMT)));
+        if (!stmt) {
+            return nullptr;
+        }
+        stmt->mysql = reinterpret_cast<MYSQL*>(0x1);
+        g_all_stmts.push_back(stmt);
+        auto* s = ensure_state(stmt);
+        s->mysql = stmt->mysql;
+        return stmt;
+    }
+
+    int mysql_stmt_prepare(MYSQL_STMT* stmt, const char* q, unsigned long len) {
+        if (!stmt) {
+            return 1;
+        }
+        stmt->mysql = reinterpret_cast<MYSQL*>(0x1);
+        auto* s = ensure_state(stmt);
+
+        const uintptr_t pq = reinterpret_cast<uintptr_t>(q);
+        const bool ptr_ok  = (q != nullptr) && (pq > 0x10000);
+        const bool len_ok  = (len > 0);
+        if (ptr_ok && len_ok) {
+            size_t copy_len = std::min<size_t>(len, 4096);
+            s->sql.assign(q, copy_len);
+        } else {
+            s->sql = "SELECT version, minor FROM schema_version";
+        }
+
+        if (is_like(s->sql, "schema_version") || is_like(s->sql, "select version") || is_like(s->sql, "get_version")) {
+            fill_version_stmt(s);
+            return 0;
+        }
+
+        unsigned int cols = infer_field_count_from_sql(s->sql);
+        if (cols == 0) {
+            cols = g_fdp->ConsumeIntegralInRange<unsigned int>(1, 8);
+        }
+        s->field_count = cols;
+
+        if (g_fdp->ConsumeBool()) {
+            fill_fuzz_rows(s, cols);
+        } else {
+            fill_no_rows(s);
+        }
+        return 0;
+    }
+
+    my_bool mysql_stmt_close(MYSQL_STMT* stmt) {
+        if (stmt) {
+            auto it = g_stmt_state.find(stmt);
+            if (it != g_stmt_state.end()) {
+                delete it->second;
+                g_stmt_state.erase(it);
+                if (g_stmt_state.empty()) {
+                    g_stmt_state.rehash(0);
+                }
+            }
+            auto it2 = std::find(g_all_stmts.begin(), g_all_stmts.end(), stmt);
+            if (it2 != g_all_stmts.end()) {
+                g_all_stmts.erase(it2);
+            }
+            free(stmt);
+        }
+        return 0;
+    }
+
+    my_bool mysql_stmt_bind_result(MYSQL_STMT* stmt, MYSQL_BIND* bnd) {
+        auto* s = ensure_state(stmt);
+        s->res_binds = bnd;
+        s->res_binds_count = bnd ? static_cast<size_t>(mysql_stmt_field_count(stmt)) : 0;
+        return 0;
+    }
+
+    int mysql_stmt_execute(MYSQL_STMT*) {
+        return 0;
+    }
+
+    int mysql_stmt_store_result(MYSQL_STMT*) {
+        return 0;
+    }
+
+    my_bool mysql_stmt_free_result(MYSQL_STMT* stmt) {
+        auto s = SS(stmt);
+        if (s) {
+            s->fetch_index = 0;
+        }
+        if (stmt) {
+            stmt->mysql = reinterpret_cast<MYSQL*>(0x1);
+        }
+        return 0;
+    }
+
+    my_ulonglong mysql_stmt_affected_rows(MYSQL_STMT*) {
+        return 0ULL;
+    }
+
+    my_bool mysql_stmt_reset(MYSQL_STMT* stmt) {
+        auto s = SS(stmt);
+        if (s) {
+            s->fetch_index = 0;
+        }
+        if (stmt) {
+            stmt->mysql = reinterpret_cast<MYSQL*>(0x1);
+        }
+        return 0;
+    }
+
+    unsigned int mysql_stmt_errno(MYSQL_STMT*) {
+        return 0;
+    }
+
+    const char* mysql_stmt_error(MYSQL_STMT*) {
+        g_stmt_error = g_fdp ? g_fdp->ConsumeRandomLengthString(32) : std::string();
+        return g_stmt_error.c_str();
+    }
+
+    int mysql_options(MYSQL*, enum mysql_option, const void*) {
+        return 0;
+    }
+
+    my_bool mysql_autocommit(MYSQL*, my_bool) {
+        return 0;
+    }
+
+    my_bool mysql_commit(MYSQL*) {
+        return 0;
+    }
+
+    my_bool mysql_rollback(MYSQL*) {
+        return 0;
+    }
+
+    int mysql_query(MYSQL*, const char*) {
+        return 0;
+    }
+
+    my_bool mysql_stmt_bind_param(MYSQL_STMT*, MYSQL_BIND*) {
+        return 0;
+    }
+
+    unsigned int mysql_stmt_field_count(MYSQL_STMT* stmt) {
+        auto s = SS(stmt);
+        return s ? s->field_count : 0u;
+    }
+
+    MYSQL_RES* mysql_stmt_result_metadata(MYSQL_STMT* stmt) {
+        auto s = SS(stmt);
+        if (!s) {
+            return reinterpret_cast<MYSQL_RES*>(0x1);
+        }
+        if (s->is_version_stmt) {
+            return reinterpret_cast<MYSQL_RES*>(0x1);
+        }
+        return nullptr;
+    }
+
+    my_ulonglong mysql_insert_id(MYSQL*) {
+        return g_fdp ? g_fdp->ConsumeIntegral<my_ulonglong>() : 0ULL;
+    }
+
+    const char* mysql_get_ssl_cipher(MYSQL*) {
+        if (g_fdp && g_fdp->ConsumeBool()) {
+            g_tls_cipher = g_fdp->ConsumeRandomLengthString(64);
+            return g_tls_cipher.c_str();
+        }
+        return "TLS_FAKE_CIPHER_WITH_FAKE_SHA256";
+    }
+
+    int mysql_stmt_fetch(MYSQL_STMT* stmt) {
+        auto s = SS(stmt);
+
+        if (!s || !s->has_rows){
+             return MYSQL_NO_DATA;
+        }
+        if (s->fetch_index >= s->rows.size()) {
+            return MYSQL_NO_DATA;
+        }
+        if (!s->res_binds) {
+            return MYSQL_NO_DATA;
+        }
+
+        const auto& row = s->rows[s->fetch_index++];
+        size_t cols = row.size();
+        if (s->field_count && cols > s->field_count) {
+            cols = s->field_count;
+        }
+        if (cols > s->res_binds_count) {
+            cols = s->res_binds_count;
+        }
+
+        for (size_t i = 0; i < cols; ++i) {
+            const auto& cell = row[i];
+            MYSQL_BIND& b = s->res_binds[i];
+            if (!b.buffer) {
+                continue;
+            }
+            switch (cell.kind) {
+                case MockResRow::CK_UINT32: {
+                    uint32_t v = cell.u32;
+                    if (b.buffer_length == 0 || b.buffer_length >= sizeof(v)) {
+                        memcpy(b.buffer, &v, sizeof(v));
+                        if (b.length) *b.length = sizeof(v);
+                    }
+                    break;
+                }
+                case MockResRow::CK_UINT64: {
+                    uint64_t v = cell.u64;
+                    if (b.buffer_length == 0 || b.buffer_length >= sizeof(v)) {
+                        memcpy(b.buffer, &v, sizeof(v));
+                        if (b.length) *b.length = sizeof(v);
+                    }
+                    break;
+                }
+                case MockResRow::CK_UINT8: {
+                    uint8_t v = cell.u8;
+                    if (b.buffer_length == 0 || b.buffer_length >= sizeof(v)) {
+                        memcpy(b.buffer, &v, sizeof(v));
+                        if (b.length) *b.length = sizeof(v);
+                    }
+                    break;
+                }
+                case MockResRow::CK_STRING: {
+                    if (b.buffer_length > 0) {
+                        size_t n = std::min<size_t>(b.buffer_length - 1, cell.s.size());
+                        memcpy(b.buffer, cell.s.data(), n);
+                        reinterpret_cast<char*>(b.buffer)[n] = '\0';
+                        if (b.length) *b.length = n;
+                    }
+                    break;
+                }
+                case MockResRow::CK_BLOB: {
+                    if (b.buffer_length > 0) {
+                        size_t n = std::min<size_t>(b.buffer_length, cell.blob.size());
+                        memcpy(b.buffer, cell.blob.data(), n);
+                        if (b.length) *b.length = n;
+                    }
+                    break;
+                }
+            }
+        }
+        return 0;
+    }
+}
diff --git a/src/fuzz/pgmock.cc b/src/fuzz/pgmock.cc
new file mode 100644 (file)
index 0000000..b71f021
--- /dev/null
@@ -0,0 +1,266 @@
+// Copyright (C) 2025 Ada Logcis Ltd.
+//
+// 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 <fuzzer/FuzzedDataProvider.h>
+
+#include <cstdlib>
+#include <cstring>
+#include <cstddef>
+#include <cstdint>
+
+struct pg_conn {};
+using  PGconn = pg_conn;
+
+struct pg_result {
+    int   status;
+    int   ntuples;
+    int   nfields;
+    char** field_names;
+    char** values;
+};
+using PGresult = pg_result;
+
+enum {
+    PGRES_EMPTY_QUERY = 0, PGRES_COMMAND_OK = 1,
+    PGRES_TUPLES_OK = 2, PGRES_FATAL_ERROR = 7
+};
+
+static thread_local FuzzedDataProvider* g_fdp = nullptr;
+
+extern "C" void pgmock_load_bytes(const uint8_t* data, size_t size) {
+    delete g_fdp;
+    g_fdp = new FuzzedDataProvider(data, size);
+}
+
+// Helper to duplcate string and drop const for return
+static char* dupstr(const char* s) {
+    if (!s) {
+        return nullptr;
+    }
+
+    size_t n = std::strlen(s) + 1;
+    char* p = static_cast<char*>(std::malloc(n));
+    if (p) {
+        std::memcpy(p, s, n);
+    }
+
+    return p;
+}
+
+// Helper to make a fuzz row result
+static PGresult* make_fuzz_result() {
+    int nfields = g_fdp->ConsumeIntegralInRange<int>(2, 8);
+
+    PGresult* r = static_cast<PGresult*>(std::calloc(1, sizeof(PGresult)));
+    r->status = PGRES_TUPLES_OK;
+    r->ntuples = 1;
+    r->nfields = nfields;
+
+    r->field_names = static_cast<char**>(std::calloc(nfields, sizeof(char*)));
+    r->values = static_cast<char**>(std::calloc((size_t)nfields, sizeof(char*)));
+
+    for (int i = 0; i < nfields; ++i) {
+        r->field_names[i] = dupstr(g_fdp->ConsumeRandomLengthString(32).c_str());
+
+        // Provide random type of data return
+        int kind = g_fdp->ConsumeIntegralInRange<int>(0, 3);
+        if (kind == 0) {
+            unsigned v = g_fdp->ConsumeIntegralInRange<unsigned>(0, 9999u);
+            char buf[32];
+            std::snprintf(buf, sizeof(buf), "%u", v);
+            r->values[i] = dupstr(buf);
+        } else if (kind == 1) {
+            r->values[i] = dupstr(g_fdp->ConsumeBool() ? "1" : "0");
+        } else if (kind == 2) {
+            // IPv4 loopback as text
+            r->values[i] = dupstr("127.0.0.1");
+        } else {
+            // IPv6 loopback as text
+            r->values[i] = dupstr("::1");
+        }
+    }
+    return r;
+}
+
+// Helper to make fixed version query for kea
+static PGresult* make_version_result() {
+    PGresult* r = static_cast<PGresult*>(std::calloc(1, sizeof(PGresult)));
+    r->status  = PGRES_TUPLES_OK;
+    r->ntuples = 1;
+    r->nfields = 2;
+
+    r->field_names = static_cast<char**>(std::calloc(2, sizeof(char*)));
+    r->field_names[0] = dupstr("version");
+    r->field_names[1] = dupstr("minor");
+
+    r->values = static_cast<char**>(std::calloc(2, sizeof(char*)));
+    r->values[0] = dupstr("31");
+    r->values[1] = dupstr("0");
+
+    return r;
+}
+
+// Helper to make success reply to update or delete query
+static PGresult* make_command_ok_result() {
+    PGresult* r = static_cast<PGresult*>(std::calloc(1, sizeof(PGresult)));
+    r->status  = PGRES_COMMAND_OK;
+    r->ntuples = 0;
+    r->nfields = 0;
+    r->field_names = nullptr;
+    r->values = nullptr;
+
+    return r;
+}
+
+// List of mock functions
+extern "C" {
+    PGconn* PQconnectdb(const char*) {
+        return static_cast<PGconn*>(std::calloc(1, sizeof(PGconn)));
+    }
+
+    int PQstatus(const PGconn* c) {
+        return c ? 0 : 1;
+    }
+
+    void PQfinish(PGconn* c) {
+        std::free(c);
+    }
+
+    char* PQerrorMessage(const PGconn*) {
+        return const_cast<char*>("");
+    }
+
+    PGresult* PQexec(PGconn* , const char* query) {
+        if (g_fdp->ConsumeBool()) {
+            return make_version_result();
+        }
+        return make_fuzz_result();
+    }
+
+    PGresult* PQexecParams(PGconn*, const char* cmd, int, const void*,
+                           const char* const*, const int*, const int*, int) {
+        if (g_fdp->ConsumeBool()) {
+            return make_version_result();
+        }
+        return make_fuzz_result();
+    }
+
+    PGresult* PQprepare(PGconn*, const char*, const char*, int, const unsigned int*) {
+        return make_command_ok_result();
+    }
+
+    PGresult* PQexecPrepared(PGconn*, const char* name, int, const char* const*,
+                             const int*, const int*, int) {
+        if (g_fdp->ConsumeBool()) {
+            return make_version_result();
+        }
+        return make_fuzz_result();
+    }
+
+    int PQresultStatus(const PGresult* r) {
+        return r ? r->status : PGRES_FATAL_ERROR;
+     }
+
+    int PQntuples(const PGresult* r) {
+        return r ? r->ntuples : 0;
+    }
+
+    int PQnfields(const PGresult* r) {
+        return r ? r->nfields : 0;
+    }
+
+    char* PQfname(const PGresult* r, int i) {
+        if (r && i >= 0 && i < r->nfields) {
+            return r->field_names[i];
+        }
+        return const_cast<char*>("");
+    }
+
+    char* PQgetvalue(const PGresult* r, int row, int col) {
+        if (r && row == 0 && col >= 0 && col < r->nfields) {
+            return r->values[col];
+        }
+        return const_cast<char*>("");
+    }
+
+    int PQgetlength(const PGresult* r, int, int col) {
+        if (r && col >= 0 && col < r->nfields && r->values && r->values[col]) {
+            return static_cast<int>(std::strlen(r->values[col]));
+        }
+        return 0;
+    }
+
+    int PQgetisnull(const PGresult*, int, int) {
+        return 0;
+    }
+
+    int PQbinaryTuples(const PGresult*) {
+        return 0;
+    }
+
+    int PQfformat(const PGresult*, int) {
+        return 0;
+    }
+
+    int PQfsize(const PGresult*, int) {
+        return -1;
+    }
+
+    void PQclear(PGresult* r) {
+        if (!r) {
+            return;
+        }
+
+        if (r->field_names) {
+            for (int i=0; i<r->nfields; ++i) {
+                if (r->field_names[i]) {
+                    std::free(r->field_names[i]);
+                }
+            }
+            std::free(r->field_names);
+            r->field_names = nullptr;
+        }
+
+        const int count = (r->ntuples > 0 && r->nfields > 0)
+                           ? r->ntuples * r->nfields : 0;
+        if (r->values) {
+            for (int i = 0; i < count; ++i) {
+                if (r->values[i]) {
+                    std::free(r->values[i]);
+                }
+            }
+            std::free(r->values);
+            r->values = nullptr;
+        }
+
+        std::free(r);
+    }
+
+    const char* PQcmdTuples(const PGresult*) {
+        return "0";
+    }
+
+    char* PQresultErrorField(const PGresult*, int) {
+        return const_cast<char*>("");
+    }
+
+    unsigned char* PQunescapeBytea(const unsigned char*, size_t* to_length) {
+        if (to_length) {
+            *to_length = 0;
+        }
+
+        unsigned char* p = static_cast<unsigned char*>(std::malloc(1));
+        if (p) {
+            p[0] = 0;
+        }
+
+        return p;
+    }
+
+    void PQfreemem(void* p) {
+        std::free(p);
+    }
+}