aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Nardi <12729895+IvanNardi@users.noreply.github.com>2024-06-27 18:07:43 +0200
committerGitHub <noreply@github.com>2024-06-27 18:07:43 +0200
commit83e6e753af1a6123805a4777691da1f8821c01d0 (patch)
tree6029b802f7b533c07ef63b1166b3633e00be8843
parent8f6f73505d34574c090e5ee59ee0c33c0c9732de (diff)
fuzz: pl7m: add a custom mutator for better fuzzing of pcap files (#2483)
Pl7m is a custom mutator (used for structure aware fuzzing) for network traffic packet captures (i.e. pcap files). The output of the mutator is always a valid pcap file, containing the same flows/sessions of the input file. That's it: the mutator only changes the packet payload after the TCP/UDP header, keeping all the original L2/L3 information (IP addresses and L4 ports). See: https://github.com/IvanNardi/pl7m
-rw-r--r--.gitignore11
-rw-r--r--fuzz/Makefile.am84
-rw-r--r--fuzz/fuzz_ndpi_reader.c11
-rw-r--r--src/lib/third_party/include/pl7m.h49
-rw-r--r--src/lib/third_party/src/fuzz/pl7m.c1328
5 files changed, 1482 insertions, 1 deletions
diff --git a/.gitignore b/.gitignore
index 4605ff12b..215324462 100644
--- a/.gitignore
+++ b/.gitignore
@@ -85,6 +85,11 @@
/fuzz/fuzz_gcrypt_gcm
/fuzz/fuzz_gcrypt_cipher
/fuzz/fuzz_ndpi_reader_payload_analyzer
+/fuzz/fuzz_ndpi_reader_pl7m_simplest
+/fuzz/fuzz_ndpi_reader_pl7m_simplest_internal
+/fuzz/fuzz_ndpi_reader_pl7m
+/fuzz/fuzz_ndpi_reader_pl7m_64k
+/fuzz/fuzz_ndpi_reader_pl7m_internal
/fuzz/fuzz_filecfg_protocols
/fuzz/fuzz_filecfg_categories
/fuzz/fuzz_filecfg_category
@@ -96,6 +101,11 @@
/fuzz/fuzz_readerutils_parseprotolist
/fuzz/fuzz_ndpi_reader_alloc_fail_seed_corpus.zip
/fuzz/fuzz_ndpi_reader_seed_corpus.zip
+/fuzz/fuzz_ndpi_reader_pl7m_seed_corpus.zip
+/fuzz/fuzz_ndpi_reader_pl7m_64k_seed_corpus.zip
+/fuzz/fuzz_ndpi_reader_pl7m_internal_seed_corpus.zip
+/fuzz/fuzz_ndpi_reader_pl7m_simplest_seed_corpus.zip
+/fuzz/fuzz_ndpi_reader_pl7m_simplest_internal_seed_corpus.zip
/fuzz/fuzz_quic_get_crypto_data_seed_corpus.zip
/fuzz/fuzz_community_id_seed_corpus.zip
/fuzz/fuzz_is_stun_udp_seed_corpus.zip
@@ -152,6 +162,7 @@
/src/lib/libndpi.so*
/src/lib/protocols/.deps/
/src/lib/third_party/src/.deps/
+/src/lib/third_party/src/fuzz/.deps/
/tags
/test-driver
/tests/Makefile
diff --git a/fuzz/Makefile.am b/fuzz/Makefile.am
index 6b2883fd6..655a88cf2 100644
--- a/fuzz/Makefile.am
+++ b/fuzz/Makefile.am
@@ -11,6 +11,8 @@ bin_PROGRAMS += fuzz_gcrypt_light fuzz_gcrypt_aes fuzz_gcrypt_gcm fuzz_gcrypt_ci
bin_PROGRAMS += fuzz_filecfg_protocols fuzz_filecfg_categories fuzz_filecfg_malicious_sha1 fuzz_filecfg_malicious_ja3 fuzz_filecfg_risk_domains fuzz_filecfg_config fuzz_filecfg_category
#Reader utils
bin_PROGRAMS += fuzz_readerutils_workflow fuzz_readerutils_parseprotolist
+#Mutators
+bin_PROGRAMS += fuzz_ndpi_reader_pl7m fuzz_ndpi_reader_pl7m_64k fuzz_ndpi_reader_pl7m_simplest fuzz_ndpi_reader_pl7m_internal fuzz_ndpi_reader_pl7m_simplest_internal
fuzz_process_packet_SOURCES = fuzz_process_packet.c fuzz_common_code.c
fuzz_process_packet_CFLAGS = @NDPI_CFLAGS@ $(CXXFLAGS)
@@ -632,6 +634,71 @@ fuzz_readerutils_parseprotolist_LINK=$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOL
$(LIBTOOLFLAGS) --mode=link $(CXX) @NDPI_CFLAGS@ $(AM_CXXFLAGS) $(CXXFLAGS) \
$(fuzz_readerutils_parseprotolist_LDFLAGS) @NDPI_LDFLAGS@ $(LDFLAGS) -o $@
+fuzz_ndpi_reader_pl7m_simplest_SOURCES = fuzz_ndpi_reader.c fuzz_common_code.c ../example/reader_util.c ../src/lib/third_party/src/fuzz/pl7m.c
+fuzz_ndpi_reader_pl7m_simplest_CFLAGS = -I../example/ @NDPI_CFLAGS@ $(CXXFLAGS) -I../src/lib/third_party/include/ -DENABLE_PCAP_L7_MUTATOR -DPL7M_USE_SIMPLEST_MUTATOR
+fuzz_ndpi_reader_pl7m_simplest_LDADD = ../src/lib/libndpi.a $(ADDITIONAL_LIBS)
+fuzz_ndpi_reader_pl7m_simplest_LDFLAGS = $(PCAP_LIB) $(LIBS)
+if HAS_FUZZLDFLAGS
+fuzz_ndpi_reader_pl7m_simplest_CFLAGS += $(LIB_FUZZING_ENGINE)
+fuzz_ndpi_reader_pl7m_simplest_LDFLAGS += $(LIB_FUZZING_ENGINE)
+endif
+# force usage of CXX for linker
+fuzz_ndpi_reader_pl7m_simplest_LINK=$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXX) @NDPI_CFLAGS@ $(AM_CXXFLAGS) $(CXXFLAGS) \
+ $(fuzz_ndpi_reader_pl7m_simplest_LDFLAGS) @NDPI_LDFLAGS@ $(LDFLAGS) -o $@
+
+fuzz_ndpi_reader_pl7m_simplest_internal_SOURCES = fuzz_ndpi_reader.c fuzz_common_code.c ../example/reader_util.c ../src/lib/third_party/src/fuzz/pl7m.c
+fuzz_ndpi_reader_pl7m_simplest_internal_CFLAGS = -I../example/ @NDPI_CFLAGS@ $(CXXFLAGS) -I../src/lib/third_party/include/ -DENABLE_PCAP_L7_MUTATOR -DPL7M_USE_SIMPLEST_MUTATOR -DPL7M_USE_INTERNAL_FUZZER_MUTATE
+fuzz_ndpi_reader_pl7m_simplest_internal_LDADD = ../src/lib/libndpi.a $(ADDITIONAL_LIBS)
+fuzz_ndpi_reader_pl7m_simplest_internal_LDFLAGS = $(PCAP_LIB) $(LIBS)
+if HAS_FUZZLDFLAGS
+fuzz_ndpi_reader_pl7m_simplest_internal_CFLAGS += $(LIB_FUZZING_ENGINE)
+fuzz_ndpi_reader_pl7m_simplest_internal_LDFLAGS += $(LIB_FUZZING_ENGINE)
+endif
+# force usage of CXX for linker
+fuzz_ndpi_reader_pl7m_simplest_internal_LINK=$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXX) @NDPI_CFLAGS@ $(AM_CXXFLAGS) $(CXXFLAGS) \
+ $(fuzz_ndpi_reader_pl7m_simplest_internal_LDFLAGS) @NDPI_LDFLAGS@ $(LDFLAGS) -o $@
+
+fuzz_ndpi_reader_pl7m_SOURCES = fuzz_ndpi_reader.c fuzz_common_code.c ../example/reader_util.c ../src/lib/third_party/src/fuzz/pl7m.c
+fuzz_ndpi_reader_pl7m_CFLAGS = -I../example/ @NDPI_CFLAGS@ $(CXXFLAGS) -I../src/lib/third_party/include/ -DENABLE_PCAP_L7_MUTATOR
+fuzz_ndpi_reader_pl7m_LDADD = ../src/lib/libndpi.a $(ADDITIONAL_LIBS)
+fuzz_ndpi_reader_pl7m_LDFLAGS = $(PCAP_LIB) $(LIBS)
+if HAS_FUZZLDFLAGS
+fuzz_ndpi_reader_pl7m_CFLAGS += $(LIB_FUZZING_ENGINE)
+fuzz_ndpi_reader_pl7m_LDFLAGS += $(LIB_FUZZING_ENGINE)
+endif
+# force usage of CXX for linker
+fuzz_ndpi_reader_pl7m_LINK=$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXX) @NDPI_CFLAGS@ $(AM_CXXFLAGS) $(CXXFLAGS) \
+ $(fuzz_ndpi_reader_pl7m_LDFLAGS) @NDPI_LDFLAGS@ $(LDFLAGS) -o $@
+
+fuzz_ndpi_reader_pl7m_64k_SOURCES = fuzz_ndpi_reader.c fuzz_common_code.c ../example/reader_util.c ../src/lib/third_party/src/fuzz/pl7m.c
+fuzz_ndpi_reader_pl7m_64k_CFLAGS = -I../example/ @NDPI_CFLAGS@ $(CXXFLAGS) -I../src/lib/third_party/include/ -DENABLE_PCAP_L7_MUTATOR -DPL7M_USE_64K_PACKETS
+fuzz_ndpi_reader_pl7m_64k_LDADD = ../src/lib/libndpi.a $(ADDITIONAL_LIBS)
+fuzz_ndpi_reader_pl7m_64k_LDFLAGS = $(PCAP_LIB) $(LIBS)
+if HAS_FUZZLDFLAGS
+fuzz_ndpi_reader_pl7m_64k_CFLAGS += $(LIB_FUZZING_ENGINE)
+fuzz_ndpi_reader_pl7m_64k_LDFLAGS += $(LIB_FUZZING_ENGINE)
+endif
+# force usage of CXX for linker
+fuzz_ndpi_reader_pl7m_64k_LINK=$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXX) @NDPI_CFLAGS@ $(AM_CXXFLAGS) $(CXXFLAGS) \
+ $(fuzz_ndpi_reader_pl7m_64k_LDFLAGS) @NDPI_LDFLAGS@ $(LDFLAGS) -o $@
+
+fuzz_ndpi_reader_pl7m_internal_SOURCES = fuzz_ndpi_reader.c fuzz_common_code.c ../example/reader_util.c ../src/lib/third_party/src/fuzz/pl7m.c
+fuzz_ndpi_reader_pl7m_internal_CFLAGS = -I../example/ @NDPI_CFLAGS@ $(CXXFLAGS) -I../src/lib/third_party/include/ -DENABLE_PCAP_L7_MUTATOR -DPL7M_USE_INTERNAL_FUZZER_MUTATE
+fuzz_ndpi_reader_pl7m_internal_LDADD = ../src/lib/libndpi.a $(ADDITIONAL_LIBS)
+fuzz_ndpi_reader_pl7m_internal_LDFLAGS = $(PCAP_LIB) $(LIBS)
+if HAS_FUZZLDFLAGS
+fuzz_ndpi_reader_pl7m_internal_CFLAGS += $(LIB_FUZZING_ENGINE)
+fuzz_ndpi_reader_pl7m_internal_LDFLAGS += $(LIB_FUZZING_ENGINE)
+endif
+# force usage of CXX for linker
+fuzz_ndpi_reader_pl7m_internal_LINK=$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXX) @NDPI_CFLAGS@ $(AM_CXXFLAGS) $(CXXFLAGS) \
+ $(fuzz_ndpi_reader_pl7m_internal_LDFLAGS) @NDPI_LDFLAGS@ $(LDFLAGS) -o $@
+
# required for Google oss-fuzz
# see https://github.com/google/oss-fuzz/tree/master/projects/ndpi
@@ -649,6 +716,21 @@ fuzz_ndpi_reader_alloc_fail_seed_corpus.zip: testpcaps.zip
fuzz_ndpi_reader_payload_analyzer_seed_corpus.zip: testpcaps.zip
cp testpcaps.zip fuzz_ndpi_reader_payload_analyzer_seed_corpus.zip
+fuzz_ndpi_reader_pl7m_simplest_seed_corpus.zip: testpcaps.zip
+ cp testpcaps.zip fuzz_ndpi_reader_pl7m_simplest_seed_corpus.zip
+
+fuzz_ndpi_reader_pl7m_simplest_internal_seed_corpus.zip: testpcaps.zip
+ cp testpcaps.zip fuzz_ndpi_reader_pl7m_simplest_internal_seed_corpus.zip
+
+fuzz_ndpi_reader_pl7m_seed_corpus.zip: testpcaps.zip
+ cp testpcaps.zip fuzz_ndpi_reader_pl7m_seed_corpus.zip
+
+fuzz_ndpi_reader_pl7m_64k_seed_corpus.zip: testpcaps.zip
+ cp testpcaps.zip fuzz_ndpi_reader_pl7m_64k_seed_corpus.zip
+
+fuzz_ndpi_reader_pl7m_internal_seed_corpus.zip: testpcaps.zip
+ cp testpcaps.zip fuzz_ndpi_reader_pl7m_internal_seed_corpus.zip
+
files_corpus_fuzz_quic_get_crypto_data := $(wildcard corpus/fuzz_quic_get_crypto_data/*)
fuzz_quic_get_crypto_data_seed_corpus.zip: $(files_corpus_fuzz_quic_get_crypto_data)
zip -j fuzz_quic_get_crypto_data_seed_corpus.zip $(files_corpus_fuzz_quic_get_crypto_data)
@@ -753,7 +835,7 @@ files_corpus_fuzz_ds_domain_classify := $(wildcard corpus/fuzz_ds_domain_classi
fuzz_ds_domain_classify_seed_corpus.zip: $(files_corpus_fuzz_ds_domain_classify)
zip -j fuzz_ds_domain_classify_seed_corpus.zip $(files_corpus_fuzz_ds_domain_classify)
-corpus: fuzz_ndpi_reader_seed_corpus.zip fuzz_ndpi_reader_alloc_fail_seed_corpus.zip fuzz_ndpi_reader_payload_analyzer_seed_corpus.zip fuzz_quic_get_crypto_data_seed_corpus.zip fuzz_alg_ses_des_seed_corpus.zip fuzz_alg_bins_seed_corpus.zip fuzz_alg_hll_seed_corpus.zip fuzz_alg_jitter_seed_corpus.zip fuzz_ds_libcache_seed_corpus.zip fuzz_community_id_seed_corpus.zip fuzz_serialization_seed_corpus.zip fuzz_ds_ptree_seed_corpus.zip fuzz_alg_crc32_md5_seed_corpus.zip fuzz_alg_bytestream_seed_corpus.zip fuzz_libinjection_seed_corpus.zip fuzz_tls_certificate_seed_corpus.zip fuzz_filecfg_protocols_seed_corpus.zip fuzz_readerutils_workflow_seed_corpus.zip fuzz_readerutils_parseprotolist_seed_corpus.zip fuzz_ds_bitmap64_fuse_seed_corpus.zip fuzz_ds_domain_classify_seed_corpus.zip fuzz_filecfg_protocols_seed_corpus.zip fuzz_filecfg_categories_seed_corpus.zip fuzz_filecfg_config_seed_corpus.zip fuzz_filecfg_category_seed_corpus.zip fuzz_is_stun_udp_seed_corpus.zip fuzz_is_stun_tcp_seed_corpus.zip fuzz_filecfg_malicious_sha1_seed_corpus.zip fuzz_filecfg_malicious_ja3_seed_corpus.zip fuzz_filecfg_risk_domains_seed_corpus.zip
+corpus: fuzz_ndpi_reader_seed_corpus.zip fuzz_ndpi_reader_alloc_fail_seed_corpus.zip fuzz_ndpi_reader_payload_analyzer_seed_corpus.zip fuzz_quic_get_crypto_data_seed_corpus.zip fuzz_alg_ses_des_seed_corpus.zip fuzz_alg_bins_seed_corpus.zip fuzz_alg_hll_seed_corpus.zip fuzz_alg_jitter_seed_corpus.zip fuzz_ds_libcache_seed_corpus.zip fuzz_community_id_seed_corpus.zip fuzz_serialization_seed_corpus.zip fuzz_ds_ptree_seed_corpus.zip fuzz_alg_crc32_md5_seed_corpus.zip fuzz_alg_bytestream_seed_corpus.zip fuzz_libinjection_seed_corpus.zip fuzz_tls_certificate_seed_corpus.zip fuzz_filecfg_protocols_seed_corpus.zip fuzz_readerutils_workflow_seed_corpus.zip fuzz_readerutils_parseprotolist_seed_corpus.zip fuzz_ds_bitmap64_fuse_seed_corpus.zip fuzz_ds_domain_classify_seed_corpus.zip fuzz_filecfg_protocols_seed_corpus.zip fuzz_is_stun_udp_seed_corpus.zip fuzz_is_stun_tcp_seed_corpus.zip fuzz_ndpi_reader_pl7m_simplest_seed_corpus.zip fuzz_ndpi_reader_pl7m_seed_corpus.zip fuzz_ndpi_reader_pl7m_64k_seed_corpus.zip fuzz_ndpi_reader_pl7m_simplest_internal_seed_corpus.zip fuzz_ndpi_reader_pl7m_internal_seed_corpus.zip
cp corpus/fuzz_*seed_corpus.zip .
#Create dictionaries exactly as expected by oss-fuzz.
diff --git a/fuzz/fuzz_ndpi_reader.c b/fuzz/fuzz_ndpi_reader.c
index 41e7be063..f801b3f1c 100644
--- a/fuzz/fuzz_ndpi_reader.c
+++ b/fuzz/fuzz_ndpi_reader.c
@@ -8,6 +8,10 @@
#include <stdint.h>
#include <stdio.h>
+#ifdef ENABLE_PCAP_L7_MUTATOR
+#include "pl7m.h"
+#endif
+
struct ndpi_workflow_prefs *prefs = NULL;
struct ndpi_workflow *workflow = NULL;
struct ndpi_global_context *g_ctx;
@@ -24,6 +28,13 @@ extern void ndpi_report_payload_stats(FILE *out);
extern int force_no_aesni;
#endif
+#ifdef ENABLE_PCAP_L7_MUTATOR
+size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size,
+ size_t MaxSize, unsigned int Seed) {
+ return pl7m_mutator(Data, Size, MaxSize, Seed);
+}
+#endif
+
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
pcap_t * pkts;
const u_char *pkt;
diff --git a/src/lib/third_party/include/pl7m.h b/src/lib/third_party/include/pl7m.h
new file mode 100644
index 000000000..def7c91fc
--- /dev/null
+++ b/src/lib/third_party/include/pl7m.h
@@ -0,0 +1,49 @@
+/*
+MIT License
+
+Copyright (c) 2023-24 Ivan Nardi <nardi.ivan@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+
+#ifndef __PCAP_L7_MUTATOR__
+#define __PCAP_L7_MUTATOR__
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+size_t pl7m_mutator(uint8_t *data, size_t size, size_t max_size,
+ unsigned int seed);
+
+/* Useful (only?) for tests */
+size_t pl7m_mutator_fd(FILE *fd_in, FILE *fd_out, size_t max_size,
+ unsigned int seed);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/lib/third_party/src/fuzz/pl7m.c b/src/lib/third_party/src/fuzz/pl7m.c
new file mode 100644
index 000000000..aa34bd5a5
--- /dev/null
+++ b/src/lib/third_party/src/fuzz/pl7m.c
@@ -0,0 +1,1328 @@
+/*
+MIT License
+
+Copyright (c) 2023-24 Ivan Nardi <nardi.ivan@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <netinet/if_ether.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/icmp6.h>
+#include <netinet/ip6.h>
+#include <netinet/udp.h>
+#include <netinet/tcp.h>
+#include <pcap.h>
+#include <asm/byteorder.h>
+#include <linux/ppp_defs.h>
+
+#include "pl7m.h"
+
+#if defined(__has_feature)
+# if __has_feature(memory_sanitizer)
+#include <sanitizer/msan_interface.h>
+#endif
+#endif
+
+#ifdef __cplusplus
+extern "C"
+#endif
+
+/* Configuration options/defines:
+ * PL7M_ENABLE_ERROR: to enable logging of important/critical errors
+ * PL7M_ENABLE_LOG: to enable verbose logging
+ * PL7M_USE_INTERNAL_FUZZER_MUTATE: instead of using the standard function
+ `LLVMFuzzerMutate()` provided by libfuzz, use a custom/internal logic
+ to randomize the data. It is usefull if you want to use this code
+ without linking to libfuzz
+ * PL7M_USE_SIMPLEST_MUTATOR: instead of fuzzing only the L7 part of the
+ packets, randomize the entire data. Note that the output of the
+ mutator will be a valid pcap file anyway
+ * PL7M_DISABLE_PACKET_MUTATION: disable mutations at packet level (see
+ below). The output trace contains the same packets (in the same order
+ and with the same timestamp) of the input trace
+ * PL7M_DISABLE_PAYLOAD_MUTATION: disable mutations at payload level
+ (see below)
+ * PL7M_USE_64K_PACKETS: allow packets with maximum size (~64k) instead of
+ the standard size (~1526). Useful for handling TSO packets or for
+ checking integer overflow on u_int16_t variables (i.e. ip length...).
+ Note that this option might lead to significant bigger corpus
+
+ Mutations happens at two different levels:
+ * packet level: each packet might be dropped, duplicated, swapped or
+ its direction might be swapped (i.e. from client->server to server->client)
+ * payload level: packet (L5/7) payload (i.e. data after TCP/UDP header)
+ is changed
+*/
+
+
+
+#ifndef IPPROTO_IPV4
+#define IPPROTO_IPV4 4
+#endif
+#ifndef IPPROTO_OSPF
+#define IPPROTO_OSPF 89
+#endif
+#ifndef IPPROTO_VRRP
+#define IPPROTO_VRRP 112
+#endif
+#ifndef IPPROTO_PGM
+#define IPPROTO_PGM 113
+#endif
+
+#ifdef PL7M_ENABLE_ERROR
+#define derr(fmt, args...) \
+ do { \
+ fprintf(stderr, "" fmt, ## args);\
+ } while (0)
+#else
+#define derr(fmt, args...) \
+ do { \
+ } while (0)
+#endif
+
+#ifdef PL7M_ENABLE_LOG
+#define ddbg(fmt, args...) \
+ do { \
+ fprintf(stderr, "" fmt, ## args);\
+ } while (0)
+#else
+#define ddbg(fmt, args...) \
+ do { \
+ } while (0)
+#endif
+
+
+#ifdef PL7M_USE_64K_PACKETS
+#define MAX_PKT_LENGTH (26 + 20 + 1024 * 64) /* Max possible size: ethernet + ip(v4) + 64k ip payload */
+#else
+#define MAX_PKT_LENGTH (26 + 1500) /* "Standard" maximum packet size */
+#endif
+
+#ifndef PL7M_USE_INTERNAL_FUZZER_MUTATE
+size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize);
+#endif
+
+
+struct gre_header {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+ u_int16_t rec:3,
+ srr:1,
+ seq:1,
+ key:1,
+ routing:1,
+ csum:1,
+ version:3,
+ reserved:4,
+ ack:1;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+ u_int16_t csum:1,
+ routing:1,
+ key:1,
+ seq:1,
+ srr:1,
+ rec:3,
+ ack:1,
+ reserved:4,
+ version:3;
+#else
+#error "Adjust your <asm/byteorder.h> defines"
+#endif
+ __u16 protocol;
+};
+
+struct m_pkt {
+ unsigned char *raw_data;
+ struct pcap_pkthdr header;
+
+ int l2_offset;
+ int prev_l3_offset;
+ u_int16_t prev_l3_proto;
+ int l3_offset;
+ u_int16_t l3_proto;
+ int l4_offset;
+ u_int8_t l4_proto;
+ int l4_length;
+ int l5_offset;
+ int l5_length;
+
+ int is_l3_fragment;
+ int skip_l4_dissection;
+ int skip_payload_actions;
+
+ struct m_pkt *next;
+};
+struct pl7m_handle {
+ int datalink;
+
+ struct m_pkt *head;
+ struct m_pkt *tail;
+};
+
+
+/*
+ Dissection code: START
+*/
+
+static int __is_datalink_supported(int datalink_type)
+{
+ switch(datalink_type) {
+ case DLT_NULL:
+ case DLT_EN10MB:
+ case DLT_PPP:
+ case DLT_C_HDLC:
+ case DLT_RAW:
+ case DLT_LINUX_SLL:
+ case DLT_LINUX_SLL2:
+ case DLT_IPV4:
+ case DLT_IPV6:
+ case DLT_PPI:
+ return 1;
+ default:
+ return 0;
+ }
+}
+static int __is_l3_proto_supported(int proto)
+{
+ switch(proto) {
+ case ETH_P_IP:
+ case ETH_P_IPV6:
+ case ETH_P_ARP:
+ /* TODO: add other protocols */
+ return 1;
+ default:
+ return 0;
+ }
+}
+static int dissect_l2(int datalink_type, struct m_pkt *p)
+{
+ int l2_offset, l3_offset;
+ u_int16_t l3_proto = 0, next, header_length;
+ u_int32_t dlt;
+ unsigned char *data = p->raw_data;
+ int data_len = p->header.caplen;
+
+ if (data_len <= 0) {
+ derr("Invalid len %d\n", data_len);
+ return -1;
+ }
+
+ l2_offset = p->l2_offset;
+ assert(l2_offset >= 0 && l2_offset < (int)p->header.caplen);
+
+ switch(datalink_type) {
+ case DLT_NULL:
+ if (data_len < l2_offset + 5)
+ return -1;
+ l3_offset = l2_offset + 4;
+ if ((data[l3_offset] & 0xF0) == 0x40)
+ l3_proto = ETH_P_IP;
+ else if ((data[l3_offset] & 0xF0) == 0x60)
+ l3_proto = ETH_P_IPV6;
+ break;
+
+ case DLT_RAW:
+ if (data_len < l2_offset + 1)
+ return -1;
+ l3_offset = l2_offset + 0;
+ if ((data[l3_offset] & 0xF0) == 0x40)
+ l3_proto = ETH_P_IP;
+ else if ((data[l3_offset] & 0xF0) == 0x60)
+ l3_proto = ETH_P_IPV6;
+ break;
+
+ case DLT_IPV4:
+ l3_proto = ETH_P_IP;
+ l3_offset = l2_offset + 0;
+ break;
+
+ case DLT_IPV6:
+ l3_proto = ETH_P_IPV6;
+ l3_offset = l2_offset + 0;
+ break;
+
+ case DLT_LINUX_SLL:
+ if (data_len < l2_offset + 16)
+ return -1;
+ l3_proto = ntohs(*((u_int16_t *)&data[l2_offset + 14]));
+ l3_offset = 16;
+ break;
+
+ case DLT_LINUX_SLL2:
+ if (data_len < l2_offset + 20)
+ return -1;
+ l3_proto = ntohs(*((u_int16_t *)&data[l2_offset]));
+ l3_offset = 20;
+ break;
+
+ case DLT_PPI:
+ if (data_len < l2_offset + 4)
+ return -1;
+ header_length = le16toh(*(u_int16_t *)&data[l2_offset + 2]);
+ dlt = le32toh(*(u_int32_t *)&data[l2_offset + 4]);
+ if(dlt != DLT_EN10MB) /* Handle only standard ethernet, for the time being */
+ return -1;
+ p->l2_offset += header_length;
+ if (p->l2_offset >= (int)p->header.caplen)
+ return -1;
+ return dissect_l2(dlt, p);
+
+ case DLT_PPP:
+ case DLT_C_HDLC:
+ if (data[l2_offset + 0] == 0x0f || data[l2_offset + 0] == 0x8f) {
+ l3_offset = 4;
+ l3_proto = ntohs(*((u_int16_t *)&data[l2_offset + 2]));
+ } else {
+ l3_offset = l2_offset + 2;
+ next = ntohs(*((u_int16_t *)&data[l2_offset + 0]));
+ switch (next) {
+ case 0x0021:
+ l3_proto = ETH_P_IP;
+ break;
+ case 0x0057:
+ l3_proto = ETH_P_IPV6;
+ break;
+ default:
+ derr("Unknown next proto on ppp 0x%x\n", next);
+ return -1;
+ }
+ }
+ break;
+
+ case DLT_EN10MB:
+ if (data_len < l2_offset + 14)
+ return -1;
+ l3_offset = l2_offset + 14;
+ l3_proto = ntohs(*((u_int16_t *)&data[l3_offset - 2]));
+
+ /* VLAN */
+ while (l3_proto == 0x8100 && l3_offset + 4 < data_len) {
+ l3_offset += 4;
+ l3_proto = ntohs(*((u_int16_t *)&data[l3_offset - 2]));
+ }
+
+ /* PPPoES */
+ if (l3_proto == 0x8864) {
+ if (data_len < l3_offset + 8)
+ return -1;
+ l3_offset += 8;
+ next = ntohs(*((u_int16_t *)&data[l3_offset - 2]));
+ switch (next) {
+ case 0x0021:
+ l3_proto = ETH_P_IP;
+ break;
+ case 0x0057:
+ l3_proto = ETH_P_IPV6;
+ break;
+ default:
+ derr("Unknown next proto on pppoes 0x%x\n", next);
+ return -1;
+ }
+ }
+
+ break;
+
+ default:
+ derr("Unknown datalink %d\n", datalink_type);
+ return -1;
+ }
+
+ if (data_len < l3_offset) {
+ derr("Invalid length %d < %d\n", data_len, l3_offset);
+ return -1;
+ }
+ if (!__is_l3_proto_supported(l3_proto)) {
+ derr("Unsupported l3_proto 0x%x\n", l3_proto);
+ return -1;
+ }
+ p->l3_offset = l3_offset;
+ p->l3_proto = l3_proto;
+
+ return 0;
+}
+static int __is_l4_proto_supported(int proto)
+{
+ switch(proto) {
+ case IPPROTO_UDP:
+ case IPPROTO_TCP:
+ case IPPROTO_ICMP:
+ case IPPROTO_ICMPV6:
+ case IPPROTO_IGMP:
+ case IPPROTO_VRRP:
+ case IPPROTO_AH:
+ case IPPROTO_ESP:
+ case IPPROTO_SCTP:
+ case IPPROTO_PGM:
+ case IPPROTO_PIM:
+ case IPPROTO_IPV4:
+ case IPPROTO_IPV6:
+ case IPPROTO_GRE:
+ case IPPROTO_OSPF:
+ /* TODO: add other protocols */
+ return 1;
+ default:
+ return 0;
+ }
+}
+static int dissect_l3(struct m_pkt *p)
+{
+ struct ip *ip4;
+ struct ip6_hdr *ip6;
+ struct ip6_ext *ipv6_opt;
+ int num_eh, ip_hdr_len, l3_len;
+ unsigned char *data = p->raw_data + p->l3_offset;
+ int data_len = p->header.caplen - p->l3_offset;
+
+ ddbg("L3: l3_proto %d data_len %d\n", p->l3_proto, data_len);
+
+ if (data_len < 0)
+ return -1;
+
+ switch (p->l3_proto) {
+ case ETH_P_IP:
+ ip4 = (struct ip *)data;
+ if (ip4->ip_v != 4 ||
+ data_len < 20 /* min */ ||
+ ip4->ip_hl < 5 ||
+ data_len < ip4->ip_hl * 4 ||
+ ntohs(ip4->ip_len) < ip4->ip_hl * 4) {
+ derr("Wrong lengths %d %d %d\n", data_len, ip4->ip_hl,
+ ntohs(ip4->ip_len));
+ return -1;
+ }
+ /* TODO: properly handle fragments */
+ if ((ntohs(ip4->ip_off) & IP_MF) ||
+ (ntohs(ip4->ip_off) & IP_OFFMASK)) {
+ ddbg("Fragment\n");
+ p->is_l3_fragment = 1;
+ p->skip_payload_actions = 1;
+ }
+ if (!__is_l4_proto_supported(ip4->ip_p)) {
+ derr("Unsupported L4: %d\n", ip4->ip_p);
+ return -1;
+ }
+ p->l4_proto = ip4->ip_p;
+ p->l4_offset = p->l3_offset + ip4->ip_hl * 4;
+ p->l4_length = ntohs(ip4->ip_len) - ip4->ip_hl * 4;
+ break;
+
+ case ETH_P_IPV6:
+ ip6 = (struct ip6_hdr *)data;
+ if (data_len < (int)sizeof(struct ip6_hdr))
+ return -1;
+
+ /* It may be a IPv6 Jumbograms but it is probably a
+ malformed packet */
+ if (ip6->ip6_plen == 0) {
+ derr("Invalid ext len\n");
+ return -1;
+ }
+
+ ip_hdr_len = sizeof(struct ip6_hdr);
+ l3_len = ntohs(ip6->ip6_plen) + ip_hdr_len;
+ if (l3_len < ip_hdr_len || data_len < l3_len) {
+ derr("Invalid ipv6 lengths %d %d %d\n",
+ l3_len, ip_hdr_len, data_len);
+ return -1;
+ }
+
+ p->l4_proto = ip6->ip6_nxt;
+
+ /* Extension header */
+ num_eh = 0;
+ while (p->l4_proto == IPPROTO_HOPOPTS ||
+ p->l4_proto == IPPROTO_DSTOPTS ||
+ p->l4_proto == IPPROTO_ROUTING ||
+ p->l4_proto == IPPROTO_AH ||
+ p->l4_proto == IPPROTO_FRAGMENT) {
+
+ num_eh++;
+
+ if (data_len < ip_hdr_len + (int)sizeof(struct ip6_ext)) {
+ derr("Error ipv6 (a) %d %d\n", data_len, ip_hdr_len);
+ return -1;
+ }
+ if (ip_hdr_len >= l3_len) {
+ derr("Error ipv6 (b) %d %d\n", ip_hdr_len, l3_len);
+ return -1;
+ }
+ /* RFC2460 4.1 . Hop-by-Hop Options header [..] is
+ restricted to appear immediately after an IPv6 header only */
+ if (p->l4_proto == IPPROTO_HOPOPTS && num_eh != 1) {
+ derr("Hop-by-Hop Options not first header\n");
+ return -1;
+ }
+
+ ipv6_opt = (struct ip6_ext *)&data[ip_hdr_len];
+ ip_hdr_len += sizeof(struct ip6_ext);
+ ddbg("EH (%d) %d ip6e_len %d\n", num_eh, p->l4_proto,
+ ipv6_opt->ip6e_len);
+ if (p->l4_proto == IPPROTO_AH) {
+ /* RFC4302 2.2. Payload Length: This 8-bit
+ field specifies the length of AH in
+ 32-bit words (4-byte units), minus "2". */
+ ip_hdr_len += ipv6_opt->ip6e_len * 4;
+ } else if (p->l4_proto == IPPROTO_HOPOPTS) {
+ /* RFC2460 4.3. Hdr Ext Len: Length of the Hop-by-Hop
+ Options header in 8-octet units, not including
+ the first 8 octets */
+ ip_hdr_len += (8 + ipv6_opt->ip6e_len * 8);
+ } else if (p->l4_proto == IPPROTO_ROUTING) {
+ /* RFC8200 4.4. Hdr Ext Len: Length of the Routing
+ header in 8-octet units, not including the
+ first 8 octets. */
+ ip_hdr_len += (8 + ipv6_opt->ip6e_len * 8);
+ } else {
+ if (p->l4_proto != IPPROTO_FRAGMENT) {
+ ip_hdr_len += ipv6_opt->ip6e_len;
+ } else {
+ ip_hdr_len += 6;
+ ddbg("Fragment IPv6\n");
+ p->is_l3_fragment = 1;
+ p->skip_payload_actions = 1;
+ }
+ }
+ p->l4_proto = ipv6_opt->ip6e_nxt;
+
+ if (ip_hdr_len >= l3_len) {
+ derr("Error ipv6 (c) %d %d\n", ip_hdr_len, l3_len);
+ return -1;
+ }
+ }
+
+ if (!__is_l4_proto_supported(p->l4_proto)) {
+ derr("Unsupported L4: %d\n", p->l4_proto);
+ return -1;
+ }
+ p->l4_proto = ip6->ip6_nxt;
+ p->l4_offset = p->l3_offset + ip_hdr_len;
+ p->l4_length = ntohs(ip6->ip6_plen) - (ip_hdr_len - sizeof(struct ip6_hdr));
+ break;
+
+ case ETH_P_ARP:
+ p->skip_l4_dissection = 1;
+ p->skip_payload_actions = 1;
+ break;
+
+ default:
+ assert(0);
+ }
+ return 0;
+}
+static int dissect_l4(struct m_pkt *p)
+{
+ struct udphdr *udp_h;
+ struct tcphdr *tcp_h;
+ struct gre_header *gre_h;
+ unsigned char *data = p->raw_data + p->l4_offset;
+ int data_len = p->header.caplen - p->l4_offset;
+ int l4_hdr_len, rc;
+ unsigned char *ppp_h;
+ u_int16_t ppp_proto;
+
+ ddbg("L4: l4_proto %d data_len %d l4_length %d\n",
+ p->l4_proto, data_len, p->l4_length);
+
+ if (data_len < 0 || p->l4_length > data_len)
+ return -1;
+
+ if (p->is_l3_fragment) {
+ ddbg("Skip L4 dissection because it is a fragment\n");
+ return 0;
+ }
+ if (p->skip_l4_dissection) {
+ ddbg("Skip L4 dissection\n");
+ return 0;
+ }
+
+ switch(p->l4_proto) {
+ case IPPROTO_UDP:
+ udp_h = (struct udphdr *)data;
+ if (p->l4_length < (int)sizeof(struct udphdr) ||
+ ntohs(udp_h->len) > p->l4_length ||
+ ntohs(udp_h->len) < sizeof(struct udphdr)) {
+ derr("Unexpected udp len %u vs %u\n",
+ ntohs(udp_h->len), p->l4_length);
+ return -1;
+ }
+ p->l5_offset = p->l4_offset + sizeof(struct udphdr);
+ p->l5_length = ntohs(udp_h->len) - sizeof(struct udphdr);
+ break;
+ case IPPROTO_TCP:
+ tcp_h = (struct tcphdr *)data;
+ if (p->l4_length < (int)sizeof(struct tcphdr)) {
+ derr("Unexpected tcp len %d\n", p->l4_length);
+ return -1;
+ }
+ l4_hdr_len = tcp_h->doff << 2;
+ if (l4_hdr_len < (int)sizeof(struct tcphdr) ||
+ l4_hdr_len > p->l4_length) {
+ derr("Unexpected tcp len %u %u\n",
+ l4_hdr_len, p->l4_length);
+ return -1;
+ }
+ p->l5_offset = p->l4_offset + l4_hdr_len;
+ p->l5_length = p->l4_length - l4_hdr_len;
+ break;
+
+ case IPPROTO_IPV4: /* IP in IP tunnel */
+ case IPPROTO_IPV6: /* IP in IP tunnel */
+ if (p->prev_l3_proto == 0) {
+ assert(p->prev_l3_offset == 0);
+ p->prev_l3_proto = p->l3_proto;
+ p->prev_l3_offset = p->l3_offset;
+ } else {
+ derr("More than 2 ip headers. Unsupported\n");
+ return -1;
+ }
+ p->l3_offset = p->l4_offset;
+ p->l3_proto = (p->l4_proto == IPPROTO_IPV4) ? ETH_P_IP : ETH_P_IPV6;
+ rc = dissect_l3(p);
+ if (rc != 0) {
+ derr("Error dissect_l3 (second header)\n");
+ return -1;
+ }
+ return dissect_l4(p);
+
+ case IPPROTO_GRE:
+ gre_h = (struct gre_header *)data;
+ if (p->l4_length < (int)sizeof(struct gre_header)) {
+ derr("Unexpected gre len %d\n", p->l4_length);
+ return -1;
+ }
+ /* Check version. 0 = GRE, 1 = ENHANCED GRE (used for PPTP) */
+ if ((gre_h->version != 0 && gre_h->version != 1) ||
+ (gre_h->version == 1 && ntohs(gre_h->protocol) != 0x880b)) {
+ derr("Unexpected gre version %d\n", gre_h->version);
+ return -1;
+ }
+ l4_hdr_len = sizeof(struct gre_header);
+ if (gre_h->key)
+ l4_hdr_len += 4;
+ if (gre_h->seq)
+ l4_hdr_len += 4;
+ if (gre_h->csum || gre_h->routing)
+ l4_hdr_len += 4;
+ if (gre_h->ack)
+ l4_hdr_len += 4;
+ if (p->l4_length < l4_hdr_len) {
+ derr("Unexpected gre len %d/%d\n", l4_hdr_len, p->l4_length);
+ return -1;
+ }
+
+ if (p->prev_l3_proto == 0) {
+ assert(p->prev_l3_offset == 0);
+ p->prev_l3_proto = p->l3_proto;
+ p->prev_l3_offset = p->l3_offset;
+ } else {
+ derr("More than 2 ip headers. Unsupported\n");
+ return -1;
+ }
+
+ if (gre_h->version == 0) {
+ p->l3_proto = ntohs(gre_h->protocol);
+ if (p->l3_proto == 0 && l4_hdr_len == p->l4_length) {
+ derr("GRE keepalive\n");
+ return -1;
+ }
+ if (p->l3_proto != ETH_P_IP && p->l3_proto != ETH_P_IPV6) {
+ derr("Invalid L3 after GRE: 0x%x\n", p->l3_proto);
+ return -1;
+ }
+ } else {
+ ppp_h = &data[l4_hdr_len];
+
+ if (ppp_h[0] == 0xFF) { /* PPP HDLC encapsulation */
+ if(p->l4_length < l4_hdr_len + 4) {
+ derr("Unexpected gre ppp len %d/%d\n",
+ l4_hdr_len, p->l4_length);
+ return -1;
+ }
+ ppp_proto = ntohs(*(u_int16_t *)(ppp_h + 2));
+ l4_hdr_len += 4;
+ } else { /* Address and control are compressed */
+ ppp_proto = ppp_h[0];
+ l4_hdr_len += 1;
+ }
+
+ switch (ppp_proto) {
+ case PPP_IP:
+ p->l3_proto = ETH_P_IP;
+ break;
+ case PPP_IPV6:
+ p->l3_proto = ETH_P_IPV6;
+ break;
+ default:
+ derr("Unexpected ppp proto %d\n", ppp_proto);
+ return -1;
+ }
+ }
+ p->l3_offset = p->l4_offset + l4_hdr_len;
+ rc = dissect_l3(p);
+ if (rc != 0) {
+ derr("Error dissect_l3 (after gre)\n");
+ return -1;
+ }
+ return dissect_l4(p);
+
+ default:
+ /* Fuzz also the L4 header itself, but leave at least 1 byte:
+ this way, we will never have an empty ip4/6 header */
+ if (p->l4_length == 0)
+ return -1;
+ p->l5_offset = p->l4_offset + 1;
+ p->l5_length = p->l4_length - 1;
+ break;
+ }
+ return 0;
+}
+static int dissect_do(int datalink_type, struct m_pkt *p)
+{
+ int rc;
+
+ rc = dissect_l2(datalink_type, p);
+ if (rc != 0) {
+ derr("Error dissect_l2\n");
+ return -1;
+ }
+ rc = dissect_l3(p);
+ if (rc != 0) {
+ derr("Error dissect_l3\n");
+ return -1;
+ }
+ rc = dissect_l4(p);
+ if (rc != 0) {
+ derr("Error dissect_l4\n");
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ Dissection code: END
+*/
+
+
+#ifdef PL7M_USE_INTERNAL_FUZZER_MUTATE
+static size_t internal_FuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize)
+{
+ int r;
+ unsigned char rand_byte;
+ size_t new_len = Size, offset;
+
+ r = rand();
+ switch (r % 5) {
+ case 0:
+ ddbg("Payload action: unchange\n");
+ new_len = Size;
+ break;
+ case 1:
+ ddbg("Payload action: change one byte at a random location\n");
+ if (Size > 0) {
+ offset = rand() % Size;
+ rand_byte = rand() % 255;
+ Data[offset] = rand_byte;
+ }
+ break;
+ case 2:
+ ddbg("Payload action: append zero bytes\n");
+ new_len = rand() % MaxSize;
+ if (new_len > Size)
+ memset(&Data[Size], '\0', new_len - Size);
+ else
+ new_len = Size;
+ break;
+ case 3:
+ ddbg("Payload action: add one random byte at random location\n");
+ if (MaxSize >= Size + 1) {
+ offset = Size == 0 ? 0 : rand() % Size;
+ rand_byte = rand() % 255;
+ new_len = Size + 1;
+ memmove(Data + offset + 1, Data + offset, Size - offset);
+ Data[offset] = rand_byte;
+ }
+ break;
+ case 4:
+ ddbg("Payload action: remove one byte from a random location\n");
+ if (Size > 0) {
+ offset = rand() % Size;
+ new_len = Size - 1;
+ memmove(Data + offset, Data + offset + 1, Size - offset - 1);
+ }
+ break;
+ }
+ return new_len;
+}
+#endif
+
+
+static void update_do_l7(struct m_pkt *p)
+{
+ struct udphdr *udp_h;
+ struct tcphdr *tcp_h;
+ size_t new_l5_len;
+ int l4_header_len = 0;
+ int l5_len_diff;
+ struct ip *ip4;
+ struct ip6_hdr *ip6;
+
+ assert(p->l5_offset + p->l5_length <= (int)p->header.caplen);
+ assert(p->header.caplen <= MAX_PKT_LENGTH);
+#ifndef PL7M_USE_INTERNAL_FUZZER_MUTATE
+ new_l5_len = LLVMFuzzerMutate(p->raw_data + p->l5_offset,
+ p->l5_length, MAX_PKT_LENGTH - p->l5_offset);
+ /* It seems the MASAN returns false positives. The value from
+ LLVMFuzzerMutate needs to be treated as initialized.
+ See a similar report:
+ https://github.com/google/libprotobuf-mutator/pull/213/commits/51629aaf874b38c42f5dc8b970cdf9156895c7e3
+ */
+#if defined(__has_feature)
+#if __has_feature(memory_sanitizer)
+ __msan_unpoison(p->raw_data + p->l5_offset, new_l5_len);
+# endif
+#endif
+
+#else
+ new_l5_len = internal_FuzzerMutate(p->raw_data + p->l5_offset,
+ p->l5_length,
+ MAX_PKT_LENGTH - p->l5_offset);
+#endif
+ l5_len_diff = new_l5_len - p->l5_length;
+ ddbg("l5_len %u->%zu (%d)\n", p->l5_length, new_l5_len, l5_len_diff);
+
+ switch (p->l4_proto) {
+ case IPPROTO_UDP:
+ l4_header_len = sizeof(struct udphdr);
+ udp_h = (struct udphdr *)(p->raw_data + p->l4_offset);
+ udp_h->len = htons(l4_header_len + new_l5_len);
+ break;
+ case IPPROTO_TCP:
+ tcp_h = (struct tcphdr *)(p->raw_data + p->l4_offset);
+ l4_header_len = tcp_h->doff << 2;
+ /* TODO */
+ break;
+ default:
+ l4_header_len = 1;
+ break;
+ }
+
+ if (p->l3_proto == ETH_P_IP) {
+ ip4 = (struct ip *)(p->raw_data + p->l3_offset);
+ ip4->ip_len = htons(htons(ip4->ip_len) + l5_len_diff);
+ } else {
+ assert(p->l3_proto == ETH_P_IPV6);
+ ip6 = (struct ip6_hdr *)(p->raw_data + p->l3_offset);
+ ip6->ip6_plen = htons(ntohs(ip6->ip6_plen) + l5_len_diff);
+ }
+
+ /* Update previous ip header */
+ if(p->prev_l3_proto == ETH_P_IP) {
+ ip4 = (struct ip *)(p->raw_data + p->prev_l3_offset);
+ ip4->ip_len = htons(htons(ip4->ip_len) + l5_len_diff);
+
+ } else if(p->prev_l3_proto == ETH_P_IPV6) {
+ ip6 = (struct ip6_hdr *)(p->raw_data + p->prev_l3_offset);
+ ip6->ip6_plen = htons(ntohs(ip6->ip6_plen) + l5_len_diff);
+ }
+
+ p->l5_length = new_l5_len;
+ ddbg("cap_len %u->%zu\n", p->header.caplen, p->l5_offset + new_l5_len);
+ p->header.caplen = p->l5_offset + new_l5_len;
+ p->header.len = p->header.caplen;
+ assert(p->header.caplen <= MAX_PKT_LENGTH);
+}
+
+#ifdef PL7M_USE_SIMPLEST_MUTATOR
+static void update_do_simplest(struct m_pkt *p)
+{
+ size_t new_len;
+
+#ifndef PL7M_USE_INTERNAL_FUZZER_MUTATE
+ new_len = LLVMFuzzerMutate(p->raw_data, p->header.caplen, MAX_PKT_LENGTH);
+#else
+ new_len = internal_FuzzerMutate(p->raw_data, p->header.caplen, MAX_PKT_LENGTH);
+#endif
+
+ p->header.caplen = new_len;
+ p->header.len = p->header.caplen;
+ assert(p->header.caplen <= MAX_PKT_LENGTH);
+}
+#endif
+
+static void update_do(struct m_pkt *p)
+{
+#ifdef PL7M_USE_SIMPLEST_MUTATOR
+ update_do_simplest(p);
+#else
+ update_do_l7(p);
+#endif
+}
+
+static void swap_direction(struct m_pkt *p)
+{
+ struct udphdr *udp_h;
+ u_int16_t tmp_port;
+
+ /* Length fields don't change */
+
+ if (p->skip_l4_dissection == 1)
+ return;
+
+ switch (p->l4_proto) {
+ case IPPROTO_UDP:
+ udp_h = (struct udphdr *)(p->raw_data + p->l4_offset);
+ tmp_port = udp_h->source;
+ udp_h->source = udp_h->source;
+ udp_h->dest = tmp_port;
+ break;
+ case IPPROTO_TCP:
+ /* TODO */
+ break;
+ default:
+ /* Nothing to do */
+ break;
+ }
+
+ if (p->l3_proto == ETH_P_IP) {
+ struct ip *ip4;
+ struct in_addr tmp_ip4;
+
+ ip4 = (struct ip *)(p->raw_data + p->l3_offset);
+ tmp_ip4 = ip4->ip_src;
+ ip4->ip_src = ip4->ip_dst;
+ ip4->ip_dst = tmp_ip4;
+ } else {
+ struct ip6_hdr *ip6;
+ struct in6_addr tmp_ip6;
+
+ assert(p->l3_proto == ETH_P_IPV6);
+ ip6 = (struct ip6_hdr *)(p->raw_data + p->l3_offset);
+ tmp_ip6 = ip6->ip6_src;
+ ip6->ip6_src = ip6->ip6_dst;
+ ip6->ip6_dst = tmp_ip6;
+ }
+
+ /* We probably don't need to swap direction of previous
+ ip header (if any) */
+}
+
+static struct m_pkt *__dup_pkt(struct m_pkt *p)
+{
+ struct m_pkt *n;
+
+ n = (struct m_pkt *)malloc(sizeof(struct m_pkt));
+ if (!n)
+ return NULL;
+ n->raw_data = (unsigned char *)malloc(MAX_PKT_LENGTH);
+ if(!n->raw_data) {
+ free(n);
+ return NULL;
+ }
+ memcpy(n->raw_data, p->raw_data, p->header.caplen);
+ n->header = p->header;
+ n->l2_offset = p->l2_offset;
+ n->prev_l3_offset = p->prev_l3_offset;
+ n->prev_l3_proto = p->prev_l3_proto;
+ n->l4_offset = p->l4_offset;
+ n->l3_offset = p->l3_offset;
+ n->l3_proto = p->l3_proto;
+ n->l4_offset = p->l4_offset;
+ n->l4_proto = p->l4_proto;
+ n->l4_length = p->l4_length;
+ n->l5_offset = p->l5_offset;
+ n->l5_length = p->l5_length;
+ n->is_l3_fragment = p->is_l3_fragment;
+ n->skip_l4_dissection = p->skip_l4_dissection;
+ n->skip_payload_actions = p->skip_payload_actions;
+ n->next = NULL;
+ return n;
+}
+static void __free_pkt(struct m_pkt *p)
+{
+ free(p->raw_data);
+ free(p);
+}
+static void __add_pkt(struct pl7m_handle *h, struct m_pkt *p,
+ struct m_pkt *prev, struct m_pkt *next)
+{
+ if (prev) {
+ prev->next = p;
+ } else {
+ h->head = p;
+ }
+ p->next = next;
+ if(p->next == NULL)
+ h->tail = p;
+}
+static void __del_pkt(struct pl7m_handle *h, struct m_pkt *p, struct m_pkt *prev)
+{
+ if (prev) {
+ prev->next = p->next;
+ } else {
+ h->head = p->next;
+ }
+ __free_pkt(p);
+}
+static int __swap_pkt(struct pl7m_handle *h, struct m_pkt *p, struct m_pkt *prev,
+ struct m_pkt *next)
+{
+ struct timeval ts;
+
+ if (!next)
+ return 0;
+
+ /* Swap timestamps too. If the original pcap is ordered (in time),
+ the mutated one will be, too */
+ ts = next->header.ts;
+
+ if (prev) {
+ prev->next = next;
+ next->header.ts = p->header.ts;
+ } else {
+ h->head = next;
+ }
+ p->next = next->next;
+ next->next = p;
+ p->header.ts = ts;
+
+ /* TODO: update tail */
+
+ return 1;
+}
+
+static void __free_m_pkts(struct pl7m_handle *h)
+{
+ struct m_pkt *p, *n;
+
+ if (!h)
+ return;
+ p = h->head;
+ while (p) {
+ n = p->next;
+ __free_pkt(p);
+ p = n;
+ }
+ free(h);
+}
+
+static struct m_pkt *do_pkt_actions(struct pl7m_handle *h, struct m_pkt *p, struct m_pkt **prev)
+{
+ int r;
+ struct m_pkt *d, *tmp_prev;
+
+ r = rand();
+ switch (r % 4) {
+ case 0: /* Drop */
+ ddbg("Action drop\n");
+ __del_pkt(h, p, *prev);
+ break;
+ case 1: /* Duplicate */
+ ddbg("Action dup\n"); /* Both pkts don't trigger a payload action */
+ d = __dup_pkt(p);
+ __add_pkt(h, d, *prev, p);
+ *prev = p;
+ break;
+ case 2: /* Swap */
+ ddbg("Action swap\n"); /* Both pkts don't trigger a payload action */
+ tmp_prev = p->next;
+ if (__swap_pkt(h, p, *prev, p->next))
+ *prev = p;
+ else
+ *prev = tmp_prev;
+ break;
+ case 3: /* Swap direction */
+ ddbg("Action swap direction\n");
+ swap_direction(p);
+ *prev = p;
+ break;
+ }
+ if (*prev)
+ return (*prev)->next;
+ return h->head;
+}
+
+static void do_payload_actions(struct m_pkt *p)
+{
+ if (!p->skip_payload_actions)
+ update_do(p);
+ else
+ ddbg("Skip payload action\n");
+}
+
+static size_t __serialize_to_fd(struct pl7m_handle *h, FILE *fd_out,
+ size_t max_data_len)
+{
+ pcap_t *pcap_h;
+ pcap_dumper_t *pdumper;
+ struct m_pkt *p;
+ size_t written;
+
+ /* We must be sure to not write more than max_data_len bytes.
+ PCAP file format:
+ * 24 bytes for global header
+ * packets (with a 16 bytes header)
+ */
+
+ if (max_data_len < 24) {
+ derr("Buffer too small: %zu vs 24\n", max_data_len);
+ return 0;
+ }
+
+ pcap_h = pcap_open_dead(h->datalink, 65535 /* snaplen */);
+ if (!pcap_h) {
+ derr("Error pcap_open_dead\n");
+ return 0;
+ }
+
+ pdumper = pcap_dump_fopen(pcap_h, fd_out);
+ if (!pcap_h) {
+ derr("Error pcap_dump_open");
+ pcap_close(pcap_h);
+ return 0;
+ }
+ written = 24;
+
+ p = h->head;
+ while (p) {
+ if(written + 16 + p->header.caplen >= max_data_len) {
+ ddbg("Buffer too small: %zu %u %zu. Skipping packet(s)\n",
+ written, p->header.caplen, max_data_len);
+ break;
+ }
+ pcap_dump((u_char *)pdumper, &p->header, p->raw_data);
+ written += 16 + p->header.caplen;
+ p = p->next;
+ ddbg("dumping pkt\n");
+ }
+
+ assert(written <= max_data_len);
+
+ pcap_dump_close(pdumper);
+ pcap_close(pcap_h);
+ return written;
+}
+
+static size_t __serialize(struct pl7m_handle *h, const unsigned char *data,
+ size_t max_data_len)
+{
+ FILE *f_out;
+ size_t data_len = max_data_len;
+
+ f_out = fmemopen((void *)data, max_data_len, "w");
+ if (!f_out) {
+ derr("Error fmemopen\n");
+ return 0;
+ }
+
+ data_len = __serialize_to_fd(h, f_out, max_data_len);
+ return data_len;
+}
+
+static struct pl7m_handle *__deserialize_from_fd(FILE *fd_in)
+{
+ const u_char *data;
+ struct pcap_pkthdr *header;
+ struct pl7m_handle *h;
+ struct m_pkt *p;
+ pcap_t *pcap_h;
+ char errbuf[PCAP_ERRBUF_SIZE];
+ int rc;
+#ifdef PL7M_ENABLE_LOG
+ int pkt = 0;
+#endif
+
+ ddbg("Deserializing...\n");
+
+ pcap_h = pcap_fopen_offline(fd_in, errbuf);
+ if (pcap_h == NULL) {
+ derr("Error pcap_open_offline: %s\n", errbuf);
+ fclose(fd_in);
+ return NULL;
+ }
+
+ if (__is_datalink_supported(pcap_datalink(pcap_h)) == 0) {
+ derr("Datalink type %d not supported\n", pcap_datalink(pcap_h));
+ pcap_close(pcap_h);
+ return NULL;
+ }
+
+ h = (struct pl7m_handle *)calloc(1, sizeof(struct pl7m_handle));
+ if (!h) {
+ pcap_close(pcap_h);
+ return NULL;
+ }
+ h->datalink = pcap_datalink(pcap_h);
+
+ header = NULL;
+ while (pcap_next_ex(pcap_h, &header, &data) > 0) {
+ ddbg("Pkt %d\n", ++pkt);
+
+ if (header->caplen > MAX_PKT_LENGTH) {
+ derr("Pkt too big %i %i\n", header->caplen, MAX_PKT_LENGTH);
+ /* Ignore current pkt, but keep going */
+ continue;
+ }
+ p = (struct m_pkt *)calloc(sizeof(struct m_pkt), 1);
+ if (!p) {
+ __free_m_pkts(h);
+ pcap_close(pcap_h);
+ return NULL;
+ }
+ p->raw_data = (unsigned char *)malloc(MAX_PKT_LENGTH);
+ if (!p->raw_data) {
+ free(p);
+ __free_m_pkts(h);
+ pcap_close(pcap_h);
+ return NULL;
+ }
+ assert(header->caplen <= MAX_PKT_LENGTH);
+ memcpy(p->raw_data, data, header->caplen);
+ p->header = *header;
+ p->next = NULL;
+
+ rc = dissect_do(h->datalink, p);
+ if (rc != 0) {
+ derr("Error dissect_do\n");
+ /* Ignore current pkt, but keep going */
+ free(p->raw_data);
+ free(p);
+ continue;
+ }
+
+ __add_pkt(h, p, h->tail, NULL);
+ ddbg("Adding pkt (l5_len %d)\n", p->l5_length);
+ }
+
+ pcap_close(pcap_h);
+
+ return h;
+}
+
+static struct pl7m_handle *__deserialize(const unsigned char *data,
+ size_t data_len)
+{
+ FILE *f_in;
+
+ if (data_len == 0)
+ return NULL;
+ f_in = fmemopen((void *)data, data_len, "rw");
+ if (!f_in) {
+ derr("Error fmemopen\n");
+ return NULL;
+ }
+ return __deserialize_from_fd(f_in);
+}
+
+static void __mutate(struct pl7m_handle *h, unsigned int seed)
+{
+ int r;
+ struct m_pkt *p, *prev;
+
+ srand(seed);
+
+ p = h->head;
+ prev = NULL;
+ while (p) {
+ r = rand();
+ /* TODO: do these ratios [33%, 33%, 33%] make sense? */
+ switch (r % 3) {
+ case 0:
+ ddbg("Mutate: unchange\n");
+ prev = p;
+ p = p->next;
+ break;
+ case 1:
+ ddbg("Mutate: packet action\n");
+#ifndef PL7M_DISABLE_PACKET_MUTATION
+ p = do_pkt_actions(h, p, &prev);
+#else
+ prev = p;
+ p = p->next;
+#endif
+ break;
+ case 2:
+ ddbg("Mutate: payload action\n");
+#ifndef PL7M_DISABLE_PAYLOAD_MUTATION
+ do_payload_actions(p);
+#endif
+ prev = p;
+ p = p->next;
+ break;
+ }
+ }
+}
+
+/* Public functions */
+
+size_t pl7m_mutator(uint8_t *data, size_t size, size_t max_size,
+ unsigned int seed)
+{
+ struct pl7m_handle *h;
+ size_t new_size;
+ static const uint8_t empty_pcap_file [24] = {
+ 0xd4, 0xc3, 0xb2, 0xa1, 0x02, 0x00, 0x04, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 };
+
+
+ h = __deserialize(data, size);
+ if (!h) {
+ /* Return an empty, valid pcap file, if possible */
+ if (max_size >= 24) {
+ memcpy(data, empty_pcap_file, sizeof(empty_pcap_file));
+ return sizeof(empty_pcap_file);
+ }
+ return 0;
+ }
+ __mutate(h, seed);
+ new_size = __serialize(h, data, max_size);
+ __free_m_pkts(h);
+ return new_size;
+}
+
+size_t pl7m_mutator_fd(FILE *fd_in, FILE *fd_out, size_t max_size,
+ unsigned int seed)
+{
+ struct pl7m_handle *h;
+ size_t new_size;
+ static const uint8_t empty_pcap_file [24] = {
+ 0xd4, 0xc3, 0xb2, 0xa1, 0x02, 0x00, 0x04, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 };
+
+
+ h = __deserialize_from_fd(fd_in);
+ if (!h) {
+ /* Return an empty, valid pcap file, if possible */
+ if (max_size >= 24) {
+ fwrite(empty_pcap_file, sizeof(empty_pcap_file), 1, fd_out);
+ return sizeof(empty_pcap_file);
+ }
+ return 0;
+ }
+ __mutate(h, seed);
+ new_size = __serialize_to_fd(h, fd_out, max_size);
+ __free_m_pkts(h);
+ return new_size;
+}