From: Andrei Pavel Date: Tue, 3 Mar 2026 12:58:12 +0000 (+0200) Subject: [#4272] Copy fuzzing harnesses from Ada Logics X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f54e477c63d962e8af4abde9effa7ea2f80a75d8;p=thirdparty%2Fkea.git [#4272] Copy fuzzing harnesses from Ada Logics --- diff --git a/src/fuzz/build.sh b/src/fuzz/build.sh new file mode 100755 index 0000000000..57b9cec6c2 --- /dev/null +++ b/src/fuzz/build.sh @@ -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 index 0000000000..a706787f31 --- /dev/null +++ b/src/fuzz/corp/dhcp-4-sample1.json @@ -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 index 0000000000..ee9b6f38c9 --- /dev/null +++ b/src/fuzz/corp/dhcp-4-sample2.json @@ -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 index 0000000000..ed3288d227 --- /dev/null +++ b/src/fuzz/corp/dhcp-6-sample1.json @@ -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 index 0000000000..4e01e22046 --- /dev/null +++ b/src/fuzz/corp/dhcp-6-sample2.json @@ -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 index 0000000000..e9a3e101eb --- /dev/null +++ b/src/fuzz/corp/dhcp-6-sample3.json @@ -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 index 0000000000..6a68b73067 --- /dev/null +++ b/src/fuzz/corp/dhcp-6-sample4.json @@ -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 index 0000000000..ddd05f2998 --- /dev/null +++ b/src/fuzz/corp/leases.csv @@ -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 index 0000000000..05f6c715e6 --- /dev/null +++ b/src/fuzz/corp/sample.csv @@ -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 index 0000000000..9eea6ad6ea --- /dev/null +++ b/src/fuzz/corp/sample2.json @@ -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 index 0000000000..19498e4a3d --- /dev/null +++ b/src/fuzz/corp/sample3.json @@ -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 index 0000000000..f46c76786c --- /dev/null +++ b/src/fuzz/fuzz_agent.cc @@ -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 + +#include +#include +#include + +#include +#include + +#include +#include + +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(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 index 0000000000..2daa66aeab --- /dev/null +++ b/src/fuzz/fuzz_agent.dict @@ -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 index 0000000000..61e6054e9d --- /dev/null +++ b/src/fuzz/fuzz_cc.cc @@ -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 +#include + +#include +#include +#include +#include +#include + +#include +#include + +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(fdp.ConsumeIntegralInRange(0, 8)); + Element::types type2 = static_cast(fdp.ConsumeIntegralInRange(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 index 0000000000..b9dc736255 --- /dev/null +++ b/src/fuzz/fuzz_crypto.cc @@ -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 +#include + +#include +#include +#include + +#include +#include +#include +#include + +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(); + 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(fdp.ConsumeIntegralInRange(0, 6)), hmac); + + // HMAC Verify + std::string sig = fdp.ConsumeRandomLengthString(256); + verifyHMAC(tsig_data.data(), tsig_data.size(), secret.data(), secret.size(), + static_cast(fdp.ConsumeIntegralInRange(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 index 0000000000..700dadf2a6 --- /dev/null +++ b/src/fuzz/fuzz_cryptolink.cc @@ -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 +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +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(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 input_data = fdp.ConsumeRemainingBytes(); + if (!input_data.empty()) { + hash->update(input_data.data(), input_data.size()); + } + std::vector 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(1, 10); + for (size_t i = 0; i < num_updates && fdp.remaining_bytes() > 0; i++) { + size_t chunk_size = fdp.ConsumeIntegralInRange(0, fdp.remaining_bytes()); + std::vector chunk = fdp.ConsumeBytes(chunk_size); + if (!chunk.empty()) { + hash->update(chunk.data(), chunk.size()); + } + } + std::vector 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 input_data = fdp.ConsumeBytes( + fdp.ConsumeIntegralInRange(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(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 input_data = fdp.ConsumeBytes( + fdp.ConsumeIntegralInRange(0, size) + ); + if (!input_data.empty()) { + hash->update(input_data.data(), input_data.size()); + } + std::vector result(hash->getOutputLength() * 2); + size_t len = fdp.ConsumeIntegralInRange(0, result.size()); + hash->final(result.data(), len); + delete hash; + } + break; + } + + case 4: { + // Test HMAC creation and signing + size_t secret_len = fdp.ConsumeIntegralInRange(1, 256); + std::vector secret = fdp.ConsumeBytes(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 input_data = fdp.ConsumeRemainingBytes(); + if (!input_data.empty()) { + hmac->update(input_data.data(), input_data.size()); + } + std::vector signature = hmac->sign(hmac->getOutputLength()); + delete hmac; + } + break; + } + + case 5: { + // Test HMAC with multiple updates + size_t secret_len = fdp.ConsumeIntegralInRange(1, 256); + std::vector secret = fdp.ConsumeBytes(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(1, 10); + for (size_t i = 0; i < num_updates && fdp.remaining_bytes() > 0; i++) { + size_t chunk_size = fdp.ConsumeIntegralInRange(0, fdp.remaining_bytes()); + std::vector chunk = fdp.ConsumeBytes(chunk_size); + if (!chunk.empty()) { + hmac->update(chunk.data(), chunk.size()); + } + } + std::vector signature = hmac->sign(hmac->getOutputLength()); + delete hmac; + } + break; + } + + case 6: { + // Test HMAC with OutputBuffer + size_t secret_len = fdp.ConsumeIntegralInRange(1, 256); + std::vector secret = fdp.ConsumeBytes(secret_len); + if (secret.empty()) { + secret.push_back(0); + } + + HMAC* hmac = CryptoLink::getCryptoLink().createHMAC( + secret.data(), secret.size(), hash_alg + ); + if (hmac) { + std::vector input_data = fdp.ConsumeBytes( + fdp.ConsumeIntegralInRange(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(0, hmac->getOutputLength() * 2); + hmac->sign(result, len); + delete hmac; + } + break; + } + + case 7: { + // Test HMAC verification + size_t secret_len = fdp.ConsumeIntegralInRange(1, 256); + std::vector secret = fdp.ConsumeBytes(secret_len); + if (secret.empty()) { + secret.push_back(0); + } + + HMAC* hmac = CryptoLink::getCryptoLink().createHMAC( + secret.data(), secret.size(), hash_alg + ); + if (hmac) { + std::vector input_data = fdp.ConsumeBytes( + fdp.ConsumeIntegralInRange(0, size) + ); + if (!input_data.empty()) { + hmac->update(input_data.data(), input_data.size()); + } + + // Generate signature + std::vector 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(256, 1024); + std::vector secret = fdp.ConsumeBytes(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 input_data = fdp.ConsumeRemainingBytes(); + if (!input_data.empty()) { + hmac->update(input_data.data(), input_data.size()); + } + std::vector signature = hmac->sign(hmac->getOutputLength()); + delete hmac; + } + break; + } + + case 9: { + // Test RNG generation + size_t rng_len = fdp.ConsumeIntegralInRange(0, 1024); + std::vector 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 index 0000000000..d9944d86c9 --- /dev/null +++ b/src/fuzz/fuzz_d2.cc @@ -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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +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(0, static_cast(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(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 index 0000000000..ad91bdd65a --- /dev/null +++ b/src/fuzz/fuzz_d2.dict @@ -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 index 0000000000..0180269e60 --- /dev/null +++ b/src/fuzz/fuzz_dhcp_parser.dict @@ -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 index 0000000000..a56cdc1a12 --- /dev/null +++ b/src/fuzz/fuzz_dhcp_parser4.cc @@ -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 + +#include +#include + +#include +#include +#include +#include +#include + +#include "helper_func.h" + +#include +#include +#include +#include +#include + +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(0, 13)]; + std::string cmdStr = + std::string(cmds[fdp.ConsumeIntegralInRange(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(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 index 0000000000..152a990f7f --- /dev/null +++ b/src/fuzz/fuzz_dhcp_parser6.cc @@ -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 + +#include +#include + +#include +#include +#include +#include +#include + +#include "helper_func.h" + +#include +#include +#include +#include +#include + +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(0, 13)]; + std::string cmdStr = std::string(cmds[fdp.ConsumeIntegralInRange(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(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 index 0000000000..9bd2b5e666 --- /dev/null +++ b/src/fuzz/fuzz_dhcp_pkt.dict @@ -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 index 0000000000..272981d6dc --- /dev/null +++ b/src/fuzz/fuzz_dhcp_pkt4.cc @@ -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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 deferred; + std::unique_ptr srv; + std::vector 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()); + pkt->getLabel(); + pkt->getMAC(fdp->ConsumeIntegral()); + } 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()); + pkt->getLabel(); + pkt->getMAC(fdp->ConsumeIntegral()); + } 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()); + } 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 index 0000000000..0b7418abd1 --- /dev/null +++ b/src/fuzz/fuzz_dhcp_pkt6.cc @@ -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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 srv; + std::vector 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()); + pkt->getName(fdp->ConsumeIntegral()); + 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 index 0000000000..d23fb669c2 --- /dev/null +++ b/src/fuzz/fuzz_dhcp_pkt_process4.cc @@ -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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 srv; + + // Package parsing + try { + // Add fixed magic cookie and correct hardware address + std::vector 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(0, 8); + pkt->setType(static_cast(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(); + } + 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(); + } + 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 index 0000000000..cbe78139a2 --- /dev/null +++ b/src/fuzz/fuzz_dhcp_pkt_process6.cc @@ -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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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(0, 37); + pkt->setType(static_cast(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(); + } + 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 index 0000000000..7740a60a69 --- /dev/null +++ b/src/fuzz/fuzz_dhcpsrv.cc @@ -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 + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(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 index 0000000000..9f94caaeb7 --- /dev/null +++ b/src/fuzz/fuzz_dhcpsrv.dict @@ -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 index 0000000000..9f131b4a9f --- /dev/null +++ b/src/fuzz/fuzz_dhcpsrv_csv_lease.cc @@ -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 + +#include +#include +#include +#include +#include +#include + +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 index 0000000000..e484fc97c2 --- /dev/null +++ b/src/fuzz/fuzz_dns.cc @@ -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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +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(); + + // Reserve some data for different operations + std::vector wire_data = fdp.ConsumeBytes(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(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(*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()); + 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 index 0000000000..84e8d7763e --- /dev/null +++ b/src/fuzz/fuzz_dns.dict @@ -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 index 0000000000..99446d9772 --- /dev/null +++ b/src/fuzz/fuzz_encode.cc @@ -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 +#include + +#include +#include + +#include +#include +#include + +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(0, 11); + + std::vector binary_data; + std::string encoded_str; + std::vector decoded_output; + + switch (path) { + case 0: { + // Test Base64 encoding from binary data + try { + size_t bin_size = fdp.ConsumeIntegralInRange(0, size); + binary_data = fdp.ConsumeBytes(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(0, size); + binary_data = fdp.ConsumeBytes(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(0, size); + binary_data = fdp.ConsumeBytes(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(0, 10); + for (size_t i = 0; i < insertions && test_str.size() > 0; i++) { + size_t pos = fdp.ConsumeIntegralInRange(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 index 0000000000..af42d50e43 --- /dev/null +++ b/src/fuzz/fuzz_eval.dict @@ -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 index 0000000000..6f33f48448 --- /dev/null +++ b/src/fuzz/fuzz_eval4.cc @@ -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 + +#include +#include +#include + +#include +#include + +#include +#include + +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(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 index 0000000000..f06138b2e9 --- /dev/null +++ b/src/fuzz/fuzz_eval6.cc @@ -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 + +#include +#include +#include + +#include +#include + +#include +#include + +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(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 index 0000000000..9cad2b002d --- /dev/null +++ b/src/fuzz/fuzz_hook_ddns_tuning4.cc @@ -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 +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 srv; + + // Package parsing + try { + // Add fixed magic cookie and correct hardware address + std::vector 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(0, 8); + pkt->setType(static_cast(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 index 0000000000..f9ff810f65 --- /dev/null +++ b/src/fuzz/fuzz_hook_ddns_tuning6.cc @@ -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 +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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(0, 37); + pkt->setType(static_cast(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 index 0000000000..4de4f2bc51 --- /dev/null +++ b/src/fuzz/fuzz_hook_flex_id4.cc @@ -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 +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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(0, 8); + pkt->setType(static_cast(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 index 0000000000..a2e6acab54 --- /dev/null +++ b/src/fuzz/fuzz_hook_flex_id6.cc @@ -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 +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(0, 37); + pkt->setType(static_cast(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 index 0000000000..2204a19cb4 --- /dev/null +++ b/src/fuzz/fuzz_hook_lease_query4.cc @@ -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 +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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(0, 8); + pkt->setType(static_cast(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 index 0000000000..e507b2701e --- /dev/null +++ b/src/fuzz/fuzz_hook_lease_query6.cc @@ -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 +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(0, 37); + pkt->setType(static_cast(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 index 0000000000..cd16813352 --- /dev/null +++ b/src/fuzz/fuzz_hook_radius4.cc @@ -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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 srv; + + // Package parsing + try { + // Add fixed magic cookie and correct hardware address + std::vector 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(0, 8); + pkt->setType(static_cast(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(); + } + 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(); + } + 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 index 0000000000..c7af450431 --- /dev/null +++ b/src/fuzz/fuzz_hook_radius6.cc @@ -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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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(0, 37); + pkt->setType(static_cast(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(); + } + 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(); + } + 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 index 0000000000..daa4082ef4 --- /dev/null +++ b/src/fuzz/fuzz_hook_run_script4.cc @@ -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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 srv; + + // Package parsing + try { + // Add fixed magic cookie and correct hardware address + std::vector 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(0, 8); + pkt->setType(static_cast(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(); + } + 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(); + } + 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(); + } + 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 index 0000000000..e3af6174f0 --- /dev/null +++ b/src/fuzz/fuzz_hook_run_script6.cc @@ -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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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(0, 37); + pkt->setType(static_cast(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(); + } + 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(); + } + 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(); + } + 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(); + } + 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 index 0000000000..43479e260e --- /dev/null +++ b/src/fuzz/fuzz_hook_tsig.cc @@ -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 +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +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(); + const bool do_chunked = fdp.ConsumeBool(); + + // Target correct key sign and verify + try { + std::vector payload = fdp.ConsumeBytes(fdp.ConsumeIntegralInRange(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(-5, 10); + TKeyExchange::statusToText(static_cast(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 index 0000000000..60a832de5f --- /dev/null +++ b/src/fuzz/fuzz_hook_user_chk4.cc @@ -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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(1, 5); i++) { + if (fdp.ConsumeBool()) { + std::vector mac = fdp.ConsumeBytes(6); + UserPtr user = UserPtr(new User(UserId::HW_ADDRESS, mac)); + user_registry->addUser(user); + } else { + const size_t len = fdp.ConsumeIntegralInRange(2, 64); + std::vector duid = fdp.ConsumeBytes(len); + UserPtr user = UserPtr(new User(UserId::DUID, duid)); + user_registry->addUser(user); + } + } + } catch (...) { + // Slient exceptions + } + + Pkt4Ptr pkt; + std::unique_ptr srv; + + // Package parsing + try { + // Add fixed magic cookie and correct hardware address + std::vector 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(0, 8); + pkt->setType(static_cast(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 index 0000000000..8a3bfdd421 --- /dev/null +++ b/src/fuzz/fuzz_hook_user_chk6.cc @@ -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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(1, 5); i++) { + if (fdp.ConsumeBool()) { + std::vector mac = fdp.ConsumeBytes(6); + UserPtr user = UserPtr(new User(UserId::HW_ADDRESS, mac)); + user_registry->addUser(user); + } else { + const size_t len = fdp.ConsumeIntegralInRange(2, 64); + std::vector duid = fdp.ConsumeBytes(len); + UserPtr user = UserPtr(new User(UserId::DUID, duid)); + user_registry->addUser(user); + } + } + } catch (...) { + // Slient exceptions + } + + Pkt6Ptr pkt; + std::unique_ptr 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(0, 37); + pkt->setType(static_cast(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 duid = fdp.ConsumeBytes(fdp.ConsumeIntegralInRange(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 index 0000000000..43922daa88 --- /dev/null +++ b/src/fuzz/fuzz_http.cc @@ -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 + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +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 +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(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(0, 7)]; + const HttpStatusCode statusCode = statusCodes[fdp.ConsumeIntegralInRange(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 index 0000000000..b91cda5fff --- /dev/null +++ b/src/fuzz/fuzz_http.dict @@ -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 index 0000000000..4cc4bece22 --- /dev/null +++ b/src/fuzz/fuzz_ioaddress.cc @@ -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 +#include +#include + +#include +#include + +using isc::asiolink::IOAddress; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + std::string s(reinterpret_cast(data), size); + + try { + IOAddress addr(s); + addr.toText(); + addr.isV4(); + addr.isV6(); + addr.getFamily(); + addr.toBytes(); + + std::vector 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 index 0000000000..94cb6ef4c0 --- /dev/null +++ b/src/fuzz/fuzz_mysql4.cc @@ -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 +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +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 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(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 b1 = fdp.ConsumeIntegral(); + uint32_t c1 = fdp.ConsumeIntegral(); + Triplet t1(a1, b1, c1); + + uint32_t a2 = fdp.ConsumeIntegral(); + uint32_t b2 = fdp.ConsumeIntegral(); + uint32_t c2 = fdp.ConsumeIntegral(); + Triplet t2(a2, b2, c2); + + uint32_t a3 = fdp.ConsumeIntegral(); + uint32_t b3 = fdp.ConsumeIntegral(); + uint32_t c3 = fdp.ConsumeIntegral(); + Triplet t3(a3, b3, c3); + + SubnetID sid = static_cast(fdp.ConsumeIntegralInRange(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(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 index 0000000000..074585d3c6 --- /dev/null +++ b/src/fuzz/fuzz_mysql6.cc @@ -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 +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +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 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(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 b1 = fdp.ConsumeIntegral(); + uint32_t c1 = fdp.ConsumeIntegral(); + Triplet t1(a1, b1, c1); + + uint32_t a2 = fdp.ConsumeIntegral(); + uint32_t b2 = fdp.ConsumeIntegral(); + uint32_t c2 = fdp.ConsumeIntegral(); + Triplet t2(a2, b2, c2); + + uint32_t a3 = fdp.ConsumeIntegral(); + uint32_t b3 = fdp.ConsumeIntegral(); + uint32_t c3 = fdp.ConsumeIntegral(); + Triplet t3(a3, b3, c3); + + uint32_t a4 = fdp.ConsumeIntegral(); + uint32_t b4 = fdp.ConsumeIntegral(); + uint32_t c4 = fdp.ConsumeIntegral(); + Triplet t4(a4, b4, c4); + + SubnetID sid = static_cast(fdp.ConsumeIntegralInRange(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(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 index 0000000000..490374606a --- /dev/null +++ b/src/fuzz/fuzz_pgsql4.cc @@ -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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +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 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(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 b1 = fdp.ConsumeIntegral(); + uint32_t c1 = fdp.ConsumeIntegral(); + Triplet t1(a1, b1, c1); + + uint32_t a2 = fdp.ConsumeIntegral(); + uint32_t b2 = fdp.ConsumeIntegral(); + uint32_t c2 = fdp.ConsumeIntegral(); + Triplet t2(a2, b2, c2); + + uint32_t a3 = fdp.ConsumeIntegral(); + uint32_t b3 = fdp.ConsumeIntegral(); + uint32_t c3 = fdp.ConsumeIntegral(); + Triplet t3(a3, b3, c3); + + SubnetID sid = static_cast(fdp.ConsumeIntegral()); + + 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 index 0000000000..633250f76f --- /dev/null +++ b/src/fuzz/fuzz_pgsql6.cc @@ -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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +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 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(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 b1 = fdp.ConsumeIntegral(); + uint32_t c1 = fdp.ConsumeIntegral(); + Triplet t1(a1, b1, c1); + + uint32_t a2 = fdp.ConsumeIntegral(); + uint32_t b2 = fdp.ConsumeIntegral(); + uint32_t c2 = fdp.ConsumeIntegral(); + Triplet t2(a2, b2, c2); + + uint32_t a3 = fdp.ConsumeIntegral(); + uint32_t b3 = fdp.ConsumeIntegral(); + uint32_t c3 = fdp.ConsumeIntegral(); + Triplet t3(a3, b3, c3); + + uint32_t a4 = fdp.ConsumeIntegral(); + uint32_t b4 = fdp.ConsumeIntegral(); + uint32_t c4 = fdp.ConsumeIntegral(); + Triplet t4(a4, b4, c4); + + SubnetID sid = static_cast(fdp.ConsumeIntegral()); + + 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 index 0000000000..50347add7f --- /dev/null +++ b/src/fuzz/fuzz_util.cc @@ -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 +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +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 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 index 0000000000..7534230d5f --- /dev/null +++ b/src/fuzz/helper_func.cc @@ -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 +#include +#include +#include + +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(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 index 0000000000..e20ae6c2a4 --- /dev/null +++ b/src/fuzz/helper_func.h @@ -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 +#include + +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ˎxampleˌom,2,{ "a": 1, "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ˎxampleˌom,38:30,2,{ "a": 1, "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 index 0000000000..600ab8450e --- /dev/null +++ b/src/fuzz/mysqlmock.cc @@ -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 + +#include + +#include +#include +#include +#include +#include +#include +#include + +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 blob; + uint64_t u64 = 0; + uint32_t u32 = 0; + uint8_t u8 = 0; +}; + +struct StmtState { + MYSQL* mysql; + std::string sql; + std::vector> 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 g_stmt_state; +static thread_local std::vector g_all_stmts; +static thread_local std::vector 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(0x1); + g_live_stmts.push_back(s); + return reinterpret_cast(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(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 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(1, 3); + for (unsigned int r = 0; r < nrows; ++r) { + std::vector row; + row.reserve(ncols); + for (unsigned int c = 0; c < ncols; ++c) { + int k = g_fdp->ConsumeIntegralInRange(0, 4); + MockResRow cell; + switch (k) { + case 0: + cell.kind = MockResRow::CK_UINT64; + cell.u64 = g_fdp->ConsumeIntegral(); + break; + case 1: + cell.kind = MockResRow::CK_UINT32; + cell.u32 = g_fdp->ConsumeIntegral(); + break; + case 2: + cell.kind = MockResRow::CK_UINT8; + cell.u8 = g_fdp->ConsumeIntegral(); + 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(0, 32); + cell.blob = g_fdp->ConsumeBytes(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(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(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(std::calloc(1, sizeof(MYSQL_STMT))); + if (!stmt) { + return nullptr; + } + stmt->mysql = reinterpret_cast(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(0x1); + auto* s = ensure_state(stmt); + + const uintptr_t pq = reinterpret_cast(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(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(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(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(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(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(0x1); + } + if (s->is_version_stmt) { + return reinterpret_cast(0x1); + } + return nullptr; + } + + my_ulonglong mysql_insert_id(MYSQL*) { + return g_fdp ? g_fdp->ConsumeIntegral() : 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(b.buffer_length - 1, cell.s.size()); + memcpy(b.buffer, cell.s.data(), n); + reinterpret_cast(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(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 index 0000000000..b71f021dee --- /dev/null +++ b/src/fuzz/pgmock.cc @@ -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 + +#include +#include +#include +#include + +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(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(2, 8); + + PGresult* r = static_cast(std::calloc(1, sizeof(PGresult))); + r->status = PGRES_TUPLES_OK; + r->ntuples = 1; + r->nfields = nfields; + + r->field_names = static_cast(std::calloc(nfields, sizeof(char*))); + r->values = static_cast(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(0, 3); + if (kind == 0) { + unsigned v = g_fdp->ConsumeIntegralInRange(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(std::calloc(1, sizeof(PGresult))); + r->status = PGRES_TUPLES_OK; + r->ntuples = 1; + r->nfields = 2; + + r->field_names = static_cast(std::calloc(2, sizeof(char*))); + r->field_names[0] = dupstr("version"); + r->field_names[1] = dupstr("minor"); + + r->values = static_cast(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(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(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(""); + } + + 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* PQgetvalue(const PGresult* r, int row, int col) { + if (r && row == 0 && col >= 0 && col < r->nfields) { + return r->values[col]; + } + return const_cast(""); + } + + int PQgetlength(const PGresult* r, int, int col) { + if (r && col >= 0 && col < r->nfields && r->values && r->values[col]) { + return static_cast(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; infields; ++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(""); + } + + unsigned char* PQunescapeBytea(const unsigned char*, size_t* to_length) { + if (to_length) { + *to_length = 0; + } + + unsigned char* p = static_cast(std::malloc(1)); + if (p) { + p[0] = 0; + } + + return p; + } + + void PQfreemem(void* p) { + std::free(p); + } +}