First attempt at fuzzing with libFuzzer based on @elmarco work
The slirp_fuzz_ip_header harness should be working and is a basic
example of a custom mutator focusing on part of the input.
The slirp_fuzz_udp harness needs a bit of work to calculate the checksum
properly.
The code can be built using `meson build` followed by `ninja -C build`,
the current meson.build file is not suitable with a general usage.
To run the fuzzing code just run `build/fuzzing/fuzz-ip-header
fuzzing/IN -detect_leaks=0`, crash will be sent to current folder and
new input will go directly in the `IN` folder.
The main point to focus on to improve the fuzzing should be generating
a better corpus.
diff --git a/fuzzing/IN/DNS_freedesktop_1-1-1-1.pcap b/fuzzing/IN/DNS_freedesktop_1-1-1-1.pcap
new file mode 100644
index 0000000..9950156
--- /dev/null
+++ b/fuzzing/IN/DNS_freedesktop_1-1-1-1.pcap
Binary files differ
diff --git a/fuzzing/IN/ping_10-0-2-2.pcap b/fuzzing/IN/ping_10-0-2-2.pcap
new file mode 100644
index 0000000..69b60fb
--- /dev/null
+++ b/fuzzing/IN/ping_10-0-2-2.pcap
Binary files differ
diff --git a/fuzzing/IN/tftp_get_libslirp-txt.pcap b/fuzzing/IN/tftp_get_libslirp-txt.pcap
new file mode 100644
index 0000000..18defe4
--- /dev/null
+++ b/fuzzing/IN/tftp_get_libslirp-txt.pcap
Binary files differ
diff --git a/fuzzing/README.md b/fuzzing/README.md
new file mode 100644
index 0000000..205d4e0
--- /dev/null
+++ b/fuzzing/README.md
@@ -0,0 +1,48 @@
+# Fuzzing libslirp state and instructions
+
+## Current state
+We chose to use libFuzzer because of its custom mutator feature, which allows to keep coherent informations inside the packets being sent to libslirp. This ease the process of fuzzing as packets are less likely to be rejected early during processing them.
+
+In the current state, the `meson.build` file is not compatible with the original one used by libSlirp main repository but it should be easy to merge them in a clean way. Also **in the current state, it seems that there is a memory leak inside the fuzzing code**, which make it run out of memory. The current goal is to find and get rid of this leak to allow fuzzing for longer without the process being interrupted because of it.
+
+Two harness are currently available, more are to be added later to focus on other parts of the code :
+
+- **fuzz-ip-header** : the mutator focuses on the ip header field informations,
+- **fuzz-udp** : the mutator only work on udp packets, mutating the udp header and content,
+
+These harness should be good starting examples on how to fuzz libslirp using libFuzzer.
+
+## Running the fuzzer
+
+Building the fuzzers/harness requires the use of clang as libFuzzer is part of LLVM.
+You can build it running :
+
+`CC=clang meson build && ninja -C build`
+
+It will build the fuzzer in the ./build/fuzzing/ directory.
+
+A script named `fuzzing/coverage.py` is available to generate coverage informations. **It makes a lot of assumptions on the directory structure** and should be read before use.
+
+To run the fuzzer, simply run `build/fuzzing/fuzz-ip-header
+fuzzing/IN -detect_leaks=0`.
+Your current directory should be a separate directory as crashes to it. New inputs found by the fuzzer will go directly in the `IN` folder.
+
+# Adding new files to the corpus
+
+In its current state, the fuzzing code is taking pcap files as input, we produced some using `tcpdump` on linux inside qemu with default settings.
+Those files should be captured using the `EN10MB (Ethernet)` data link type, this can be set with the flag `-y` but it seems this can't be done while listening on all interfaces (`-i any`).
+New files should give new coverage, to ensure a new file is usefull the `coverage.py` script (see next section) can be used to compare the coverage with and without that new file.
+
+# Coverage
+
+The `coverage.py` script allows to see coverage informations about the corpus. It makes a lot of assumptions on the directory structure so it should be read and probably modified before running it.
+To generate coverage informations, the following flags are passed to the fuzzer and libslirp :
+
+- g
+- fsanitize-coverage=edge,indirect-calls,trace-cmp
+- fprofile-instr-generate
+- fcoverage-mapping
+
+The last 2 arguments should also be passed to the linker.
+
+Then the `llvm-profdata` and `llvm-cov` tools can be used to generate a report and a fancy set of HTML files with line-coverage informations.
diff --git a/fuzzing/coverage.py b/fuzzing/coverage.py
new file mode 100755
index 0000000..383ecd8
--- /dev/null
+++ b/fuzzing/coverage.py
@@ -0,0 +1,32 @@
+from os import chdir,listdir,environ
+from os.path import isfile,join
+from subprocess import DEVNULL, run
+import sys
+
+ignored_files = "-ignore-filename-regex=glib -ignore-filename-regex=fuzz -ignore-filename-regex=helper -ignore-filename-regex=h$"
+
+if __name__ == "__main__":
+ chdir("build/fuzzing/out")
+ available_targets = [exe for exe in listdir("../") if isfile(join("..", exe))]
+ if len(sys.argv) != 3 :
+ print("usage : python coverage.py fuzz_target result_type")
+ print("available targets : ")
+ print(available_targets)
+ print("available result types : \n export \n show \n report (default)")
+ exit(0)
+ fuzzing_target = sys.argv[1]
+ result_type = sys.argv[2]
+ if fuzzing_target in available_targets:
+ environ["LLVM_PROFILE_FILE"] = fuzzing_target + "_%p.profraw"
+ corpus_path = "../../../fuzzing/IN/"
+ corpus = listdir(corpus_path)
+ for f in corpus:
+ #print(corpus_path+f)
+ run(["../" + fuzzing_target, corpus_path+f,"-detect_leaks=0"], stdin=DEVNULL, stdout=DEVNULL, stderr=DEVNULL)
+ run(["llvm-profdata merge -sparse " + fuzzing_target + "_*.profraw -o " + fuzzing_target + ".profdata"], shell=True)
+ if result_type == "export" :
+ run(["llvm-cov show ../" + fuzzing_target + " -format=html -output-dir=../report -instr-profile=" + fuzzing_target + ".profdata " + ignored_files], shell=True)
+ elif result_type == "show" :
+ run(["llvm-cov show ../" + fuzzing_target + " -instr-profile=" + fuzzing_target + ".profdata " + ignored_files], shell=True)
+ else:
+ run(["llvm-cov report ../" + fuzzing_target + " -instr-profile=" + fuzzing_target + ".profdata " + ignored_files], shell=True)
diff --git a/fuzzing/helper.c b/fuzzing/helper.c
new file mode 100644
index 0000000..5c975c6
--- /dev/null
+++ b/fuzzing/helper.c
@@ -0,0 +1,21 @@
+#include "helper.h"
+
+/// Function to compute the checksum of the ip header, should be compatible with
+/// TCP and UDP checksum calculation too.
+uint16_t compute_checksum(uint8_t *Data, size_t Size)
+{
+ uint32_t sum = 0;
+ uint16_t *Data_as_u16 = (uint16_t *)Data;
+
+ for (size_t i = 0; i < Size/2; i++)
+ {
+ uint16_t val = ntohs(*(Data_as_u16 + i));
+ sum += val;
+ }
+ if (Size % 2 == 1) sum += Data[Size-1] << 8;
+
+ uint16_t carry = sum >> 16;
+ uint32_t sum_val = carry + (sum & 0xFFFF);
+ uint16_t result = (sum_val >> 16) + (sum_val & 0xFFFF);
+ return ~result;
+}
diff --git a/fuzzing/helper.h b/fuzzing/helper.h
new file mode 100644
index 0000000..2c9a59e
--- /dev/null
+++ b/fuzzing/helper.h
@@ -0,0 +1,5 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <netinet/in.h>
+
+uint16_t compute_checksum(uint8_t *Data, size_t Size);
\ No newline at end of file
diff --git a/fuzzing/meson.build b/fuzzing/meson.build
index 50890f7..fdbba1a 100644
--- a/fuzzing/meson.build
+++ b/fuzzing/meson.build
@@ -1,25 +1,42 @@
extra_sources = []
+extra_cargs = []
+extra_ldargs = []
fuzzing_engine = []
-if want_libfuzzer
- fuzzing_engine = meson.get_compiler('cpp').find_library('Fuzzer')
-elif want_ossfuzz
- fuzzing_engine = meson.get_compiler('cpp').find_library('FuzzingEngine')
+
+extra_cargs += '-g'
+if fuzzer_build
+ extra_cargs += '-fsanitize=fuzzer,address'
+ extra_cargs += '-fsanitize-coverage=edge,indirect-calls,trace-cmp'
+ extra_cargs += '-DCUSTOM_MUTATOR'
+ extra_cargs += '-fprofile-instr-generate'
+ extra_cargs += '-fcoverage-mapping'
+
+ extra_ldargs += '-fsanitize=fuzzer,address'
+ extra_ldargs += '-fprofile-instr-generate'
+ extra_ldargs += '-fcoverage-mapping'
endif
deps = [glib_dep, libslirp_dep, platform_deps]
+
+exes = [['fuzz-ip-header', ['slirp_fuzz_ip_header.c', 'helper.c']],
+ ['fuzz-udp', ['slirp_fuzz_udp.c', 'helper.c']]]
+
if fuzzer_build
- deps += fuzzing_engine
-else
- code = '''
-void main() {}
-'''
- if cc.links(code, name : 'fuzzer driver check')
- extra_sources += 'fuzz-main.c'
- endif
+ foreach exe : exes
+ executable(
+ exe[0], exe[1],
+ dependencies : deps,
+ c_args: extra_cargs,
+ link_args: extra_ldargs,
+ )
+ endforeach
endif
-executable(
- 'fuzz-input', [extra_sources, 'fuzz-input.c'],
- dependencies : deps,
-)
+if fuzz_reproduce
+ executable(['reproducer', ['reproducer.c']],
+ dependencies: deps,
+ c_args: extra_cargs,
+ link_args: extra_ldargs,
+ )
+endif
diff --git a/fuzzing/oss-fuzz.sh b/fuzzing/oss-fuzz.sh
index b40f2a7..0163741 100755
--- a/fuzzing/oss-fuzz.sh
+++ b/fuzzing/oss-fuzz.sh
@@ -10,6 +10,7 @@
build=$WORK/build
rm -rf $build
mkdir -p $build
+mkdir -p $OUT
fuzzflag="oss-fuzz=true"
if [ -z "$FUZZING_ENGINE" ]; then
diff --git a/fuzzing/fuzz-input.c b/fuzzing/reproducer.c
similarity index 83%
rename from fuzzing/fuzz-input.c
rename to fuzzing/reproducer.c
index 0707919..3c5d8fd 100644
--- a/fuzzing/fuzz-input.c
+++ b/fuzzing/reproducer.c
@@ -7,9 +7,12 @@
#endif
#include <glib.h>
-#include "libslirp.h"
+#include <stdlib.h>
+#include "../src/libslirp.h"
-int LLVMFuzzerTestOneInput(const unsigned char *data, size_t size);
+#define MIN_NUMBER_OF_RUNS 1
+#define EXIT_TEST_SKIP 77
+
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen)
@@ -53,7 +56,7 @@
struct sockaddr *src_addr, socklen_t *addrlen)
{
memset(buf, 0, len);
- *addrlen = 0;
+ memset(src_addr,0,*addrlen);
return len / 2;
}
@@ -63,21 +66,6 @@
return 0;
}
-#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
-static void empty_logging_func(const gchar *log_domain,
- GLogLevelFlags log_level, const gchar *message,
- gpointer user_data)
-{
-}
-#endif
-
-/* Disables logging for oss-fuzz. Must be used with each target. */
-static void fuzz_set_logging_func(void)
-{
-#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
- g_log_set_default_handler(empty_logging_func, NULL);
-#endif
-}
static ssize_t send_packet(const void *pkt, size_t pkt_len, void *opaque)
{
@@ -162,9 +150,10 @@
guint32 orig_len; /* actual length of packet */
} pcaprec_hdr_t;
-int LLVMFuzzerTestOneInput(const unsigned char *data, size_t size)
+
+static int reproduce_fuzz_case(const uint8_t *data, size_t size)
{
- Slirp *slirp;
+ Slirp *slirp = NULL;
struct in_addr net = { .s_addr = htonl(0x0a000200) }; /* 10.0.2.0 */
struct in_addr mask = { .s_addr = htonl(0xffffff00) }; /* 255.255.255.0 */
struct in_addr host = { .s_addr = htonl(0x0a000202) }; /* 10.0.2.2 */
@@ -180,8 +169,8 @@
const char *bootfile = NULL;
const char **dnssearch = NULL;
const char *vdomainname = NULL;
- pcap_hdr_t *hdr = (void *)data;
- pcaprec_hdr_t *rec = NULL;
+ const pcap_hdr_t *hdr = (const void *)data;
+ const pcaprec_hdr_t *rec = NULL;
uint32_t timeout = 0;
if (size < sizeof(pcap_hdr_t)) {
@@ -194,13 +183,10 @@
g_debug("FIXME: byteswap fields");
return 0;
} /* else assume native pcap file */
-
if (hdr->network != 1) {
return 0;
}
- fuzz_set_logging_func();
-
ret = inet_pton(AF_INET6, "fec0::", &ip6_prefix);
g_assert_cmpint(ret, ==, 1);
@@ -215,7 +201,7 @@
dhcp, dns, ip6_dns, dnssearch, vdomainname, &slirp_cb, NULL);
while (size > sizeof(*rec)) {
- rec = (void *)data;
+ rec = (const void *)data;
data += sizeof(*rec);
size -= sizeof(*rec);
if (rec->incl_len != rec->orig_len) {
@@ -229,7 +215,7 @@
slirp_input(slirp, data, rec->incl_len);
slirp_pollfds_fill(slirp, &timeout, add_poll_cb, NULL);
slirp_pollfds_poll(slirp, 0, get_revents_cb, NULL);
-
+
data += rec->incl_len;
size -= rec->incl_len;
}
@@ -238,3 +224,32 @@
return 0;
}
+
+
+int main(int argc, char **argv)
+{
+ int i, j;
+
+ for (i = 1; i < argc; i++) {
+ GError *err = NULL;
+ char *name = argv[i];
+ char *buf;
+ size_t size;
+
+ if (!g_file_get_contents(name, &buf, &size, &err)) {
+ g_warning("Failed to read '%s': %s", name, err->message);
+ g_clear_error(&err);
+ return EXIT_FAILURE;
+ }
+
+ g_print("%s...\n", name);
+ for (j = 0; j < MIN_NUMBER_OF_RUNS; j++) {
+ if (reproduce_fuzz_case((void *)buf, size) == EXIT_TEST_SKIP) {
+ return EXIT_TEST_SKIP;
+ }
+ }
+ g_free(buf);
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/fuzzing/fuzz-input.c b/fuzzing/slirp_fuzz_ip_header.c
similarity index 61%
copy from fuzzing/fuzz-input.c
copy to fuzzing/slirp_fuzz_ip_header.c
index 0707919..e011b61 100644
--- a/fuzzing/fuzz-input.c
+++ b/fuzzing/slirp_fuzz_ip_header.c
@@ -7,9 +7,13 @@
#endif
#include <glib.h>
-#include "libslirp.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include "../src/libslirp.h"
+#include "helper.h"
-int LLVMFuzzerTestOneInput(const unsigned char *data, size_t size);
+size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed);
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen)
@@ -53,7 +57,7 @@
struct sockaddr *src_addr, socklen_t *addrlen)
{
memset(buf, 0, len);
- *addrlen = 0;
+ memset(src_addr,0,*addrlen);
return len / 2;
}
@@ -162,9 +166,99 @@
guint32 orig_len; /* actual length of packet */
} pcaprec_hdr_t;
-int LLVMFuzzerTestOneInput(const unsigned char *data, size_t size)
+
+#ifdef CUSTOM_MUTATOR
+extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize);
+
+/// This is a custom mutator, this allows us to mutate only specific parts of
+/// the input and fix the checksum so the packet isn't rejected for bad reasons.
+size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed)
{
- Slirp *slirp;
+ uint8_t *Data_ptr = Data;
+ uint8_t *ip_data;
+
+ pcap_hdr_t *hdr = (void *)Data_ptr;
+ pcaprec_hdr_t *rec = NULL;
+
+ if (Size < sizeof(pcap_hdr_t)) {
+ return 0;
+ }
+ Data_ptr += sizeof(*hdr);
+ Size -= sizeof(*hdr);
+
+ if (hdr->magic_number == 0xd4c3b2a1) {
+ g_debug("FIXME: byteswap fields");
+ return 0;
+ } /* else assume native pcap file */
+ if (hdr->network != 1) {
+ return 0;
+ }
+
+ while (Size > sizeof(*rec)) {
+ rec = (void *)Data_ptr;
+ Data_ptr += sizeof(*rec);
+ Size -= sizeof(*rec);
+ if (rec->incl_len != rec->orig_len) {
+ break;
+ }
+ if (rec->incl_len > Size) {
+ break;
+ }
+ ip_data = Data_ptr + 14;
+ uint8_t Data_to_mutate[MaxSize];
+ uint8_t ip_hl = (ip_data[0] & 0xF);
+ uint8_t ip_hl_in_bytes = ip_hl * 4;
+
+ // Copy interesting data to the `Data_to_mutate` array
+ // here we want to fuzz everything in the ip header, maybe the IPs or total
+ // length should be excluded ?
+ memset(Data_to_mutate,0,MaxSize);
+ memcpy(Data_to_mutate, ip_data, ip_hl_in_bytes);
+
+ // Call to libfuzzer's mutation function.
+ // For now we dont want to change the header size as it would require to
+ // resize the `Data` array to include the new bytes inside the whole
+ // packet.
+ // This should be easy as LibFuzzer probably does it by itself or
+ // reserved enough space in Data beforehand, needs some research to
+ // confirm.
+ // FIXME: allow up to grow header size to 60 bytes,
+ // requires to update the `header length` before calculating checksum
+ LLVMFuzzerMutate(Data_to_mutate, ip_hl_in_bytes, ip_hl_in_bytes);
+
+ // Set the `checksum` field to 0 and calculate the new checksum
+ Data_to_mutate[10] = 0;
+ Data_to_mutate[11] = 0;
+ uint16_t new_checksum = compute_checksum(Data_to_mutate, ip_hl_in_bytes);
+
+ // Copy the mutated data back to the `Data` array and fix the checksum value
+ memcpy(ip_data,Data_to_mutate,ip_hl_in_bytes);
+ *((uint16_t*)ip_data + 5) = new_checksum;
+
+ Data_ptr += rec->incl_len;
+ Size -= rec->incl_len;
+ }
+
+ return Size;
+}
+#endif //CUSTOM_MUTATOR
+
+
+// Fuzzing strategy is the following :
+// The custom mutator :
+// - extract the packets from the pcap one by one,
+// - mutates the ip header and put it back inside the pcap
+// this is required because we need the pcap structure to separate them
+// before we send them to slirp.
+// LLVMFuzzerTestOneInput :
+// - build a slirp instance,
+// - extract the packets from the pcap one by one,
+// - send the data to `slirp_input`
+// - call `slirp_pollfds_fill` and `slirp_pollfds_poll` to advance slirp
+// - cleanup slirp when the whole pcap has been unwrapped.
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ Slirp *slirp = NULL;
struct in_addr net = { .s_addr = htonl(0x0a000200) }; /* 10.0.2.0 */
struct in_addr mask = { .s_addr = htonl(0xffffff00) }; /* 255.255.255.0 */
struct in_addr host = { .s_addr = htonl(0x0a000202) }; /* 10.0.2.2 */
@@ -180,8 +274,8 @@
const char *bootfile = NULL;
const char **dnssearch = NULL;
const char *vdomainname = NULL;
- pcap_hdr_t *hdr = (void *)data;
- pcaprec_hdr_t *rec = NULL;
+ const pcap_hdr_t *hdr = (const void *)data;
+ const pcaprec_hdr_t *rec = NULL;
uint32_t timeout = 0;
if (size < sizeof(pcap_hdr_t)) {
@@ -194,7 +288,6 @@
g_debug("FIXME: byteswap fields");
return 0;
} /* else assume native pcap file */
-
if (hdr->network != 1) {
return 0;
}
@@ -215,7 +308,7 @@
dhcp, dns, ip6_dns, dnssearch, vdomainname, &slirp_cb, NULL);
while (size > sizeof(*rec)) {
- rec = (void *)data;
+ rec = (const void *)data;
data += sizeof(*rec);
size -= sizeof(*rec);
if (rec->incl_len != rec->orig_len) {
diff --git a/fuzzing/slirp_fuzz_udp.c b/fuzzing/slirp_fuzz_udp.c
new file mode 100644
index 0000000..b73841d
--- /dev/null
+++ b/fuzzing/slirp_fuzz_udp.c
@@ -0,0 +1,373 @@
+#ifdef _WIN32
+/* as defined in sdkddkver.h */
+#ifndef _WIN32_WINNT
+#define _WIN32_WINNT 0x0600 /* Vista */
+#endif
+#include <ws2tcpip.h>
+#endif
+
+#include <glib.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "../src/libslirp.h"
+#include "helper.h"
+
+size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed);
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+int connect(int sockfd, const struct sockaddr *addr,
+ socklen_t addrlen)
+{
+ /* FIXME: fail on some addr? */
+ return 0;
+}
+
+int listen(int sockfd, int backlog)
+{
+ return 0;
+}
+
+int bind(int sockfd, const struct sockaddr *addr,
+ socklen_t addrlen)
+{
+ /* FIXME: fail on some addr? */
+ return 0;
+}
+
+ssize_t send(int sockfd, const void *buf, size_t len, int flags)
+{
+ /* FIXME: partial send? */
+ return len;
+}
+
+ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
+ const struct sockaddr *dest_addr, socklen_t addrlen)
+{
+ /* FIXME: partial send? */
+ return len;
+}
+
+ssize_t recv(int sockfd, void *buf, size_t len, int flags)
+{
+ memset(buf, 0, len);
+ return len / 2;
+}
+
+ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
+ struct sockaddr *src_addr, socklen_t *addrlen)
+{
+ memset(buf, 0, len);
+ memset(src_addr,0,*addrlen);
+ return len / 2;
+}
+
+int setsockopt(int sockfd, int level, int optname,
+ const void *optval, socklen_t optlen)
+{
+ return 0;
+}
+
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+static void empty_logging_func(const gchar *log_domain,
+ GLogLevelFlags log_level, const gchar *message,
+ gpointer user_data)
+{
+}
+#endif
+
+/* Disables logging for oss-fuzz. Must be used with each target. */
+static void fuzz_set_logging_func(void)
+{
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+ g_log_set_default_handler(empty_logging_func, NULL);
+#endif
+}
+
+static ssize_t send_packet(const void *pkt, size_t pkt_len, void *opaque)
+{
+ return pkt_len;
+}
+
+static int64_t clock_get_ns(void *opaque)
+{
+ return 0;
+}
+
+static void *timer_new(SlirpTimerCb cb, void *cb_opaque, void *opaque)
+{
+ return NULL;
+}
+
+static void timer_mod(void *timer, int64_t expire_timer, void *opaque)
+{
+}
+
+static void timer_free(void *timer, void *opaque)
+{
+}
+
+static void guest_error(const char *msg, void *opaque)
+{
+}
+
+static void register_poll_fd(int fd, void *opaque)
+{
+}
+
+static void unregister_poll_fd(int fd, void *opaque)
+{
+}
+
+static void notify(void *opaque)
+{
+}
+
+static const SlirpCb slirp_cb = {
+ .send_packet = send_packet,
+ .guest_error = guest_error,
+ .clock_get_ns = clock_get_ns,
+ .timer_new = timer_new,
+ .timer_mod = timer_mod,
+ .timer_free = timer_free,
+ .register_poll_fd = register_poll_fd,
+ .unregister_poll_fd = unregister_poll_fd,
+ .notify = notify,
+};
+
+#define MAX_EVID 1024
+static int fake_events[MAX_EVID];
+
+static int add_poll_cb(int fd, int events, void *opaque)
+{
+ g_assert(fd < G_N_ELEMENTS(fake_events));
+ fake_events[fd] = events;
+ return fd;
+}
+
+static int get_revents_cb(int idx, void *opaque)
+{
+ return fake_events[idx] & ~(SLIRP_POLL_ERR|SLIRP_POLL_HUP);
+}
+
+typedef struct pcap_hdr_s {
+ guint32 magic_number; /* magic number */
+ guint16 version_major; /* major version number */
+ guint16 version_minor; /* minor version number */
+ gint32 thiszone; /* GMT to local correction */
+ guint32 sigfigs; /* accuracy of timestamps */
+ guint32 snaplen; /* max length of captured packets, in octets */
+ guint32 network; /* data link type */
+} pcap_hdr_t;
+
+typedef struct pcaprec_hdr_s {
+ guint32 ts_sec; /* timestamp seconds */
+ guint32 ts_usec; /* timestamp microseconds */
+ guint32 incl_len; /* number of octets of packet saved in file */
+ guint32 orig_len; /* actual length of packet */
+} pcaprec_hdr_t;
+
+
+#ifdef CUSTOM_MUTATOR
+extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize);
+
+/// This is a custom mutator, this allows us to mutate only specific parts of
+/// the input and fix the checksum so the packet isn't rejected for bad reasons.
+size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed)
+{
+ size_t i, current_size = Size;
+ uint8_t *Data_ptr = Data;
+ uint8_t *ip_data;
+
+ pcap_hdr_t *hdr = (void *)Data_ptr;
+ pcaprec_hdr_t *rec = NULL;
+
+ if (current_size < sizeof(pcap_hdr_t)) {
+ return 0;
+ }
+
+ Data_ptr += sizeof(*hdr);
+ current_size -= sizeof(*hdr);
+
+ if (hdr->magic_number == 0xd4c3b2a1) {
+ g_debug("FIXME: byteswap fields");
+ return 0;
+ } /* else assume native pcap file */
+ if (hdr->network != 1) {
+ return 0;
+ }
+
+ while (current_size > sizeof(*rec)) {
+ rec = (void *)Data_ptr;
+ Data_ptr += sizeof(*rec);
+ current_size -= sizeof(*rec);
+
+ if (rec->incl_len != rec->orig_len) {
+ break;
+ }
+ if (rec->incl_len > current_size) {
+ break;
+ }
+ ip_data = Data_ptr + 14;
+
+ // Exclude packets that are not UDP from the mutation strategy
+ if (ip_data[9] != IPPROTO_UDP) {
+ Data_ptr += rec->incl_len;
+ current_size -= rec->incl_len;
+ continue;
+ }
+ // Allocate a bit more than needed, this is useful for
+ // checksum calculation.
+ uint8_t Data_to_mutate[MaxSize+12];
+ uint8_t ip_hl = (ip_data[0] & 0xF);
+ uint8_t ip_hl_in_bytes = ip_hl * 4;
+
+ uint8_t *start_of_udp = ip_data + ip_hl_in_bytes;
+ uint16_t udp_size = ntohs(*((uint16_t *)start_of_udp + 2));
+
+ // The size inside the packet can't be trusted, if it is too big it can
+ // lead to heap overflows in the fuzzing code.
+ // Fixme : don't use udp_size inside the fuzzing code, maybe use the
+ // rec->incl_len and manually calculate the size.
+ if (udp_size >= MaxSize || udp_size >= rec->incl_len) {
+ Data_ptr += rec->incl_len;
+ current_size -= rec->incl_len;
+ continue;
+ }
+
+ // Copy interesting data to the `Data_to_mutate` array
+ // here we want to fuzz everything in the udp packet
+ memset(Data_to_mutate,0,MaxSize+12);
+ memcpy(Data_to_mutate,start_of_udp,udp_size);
+
+ // Call to libfuzzer's mutation function.
+ // Pass the whole UDP packet, mutate it and then fix checksum value
+ // so the packet isn't rejected.
+ // The new size of the data is returned by LLVMFuzzerMutate.
+ // Fixme: allow to change the size of the UDP packet, this will require
+ // to fix the size before calculating the new checksum and change
+ // how the Data_ptr is advanced.
+ // Most offsets bellow should be good for when the switch will be
+ // done to avoid overwriting new/mutated data.
+ size_t mutated_size = LLVMFuzzerMutate(Data_to_mutate, udp_size, udp_size);
+
+ // Set the `checksum` field to 0 to calculate the new checksum
+ *((uint16_t *)Data_to_mutate + 3) = (uint16_t)0;
+ // Copy the source and destination IP addresses, the UDP length and
+ // protocol number at the end of the `Data_to_mutate` array to calculate
+ // the new checksum.
+ for (i = 0; i < 4; i++)
+ {
+ *(Data_to_mutate + mutated_size + i) = *(ip_data + 12 + i);
+ }
+ for (i = 0; i < 4; i++)
+ {
+ *(Data_to_mutate + mutated_size + 4 + i) = *(ip_data + 16 + i);
+ }
+
+ *(Data_to_mutate + mutated_size + 8) = *(start_of_udp + 4);
+ *(Data_to_mutate + mutated_size + 9) = *(start_of_udp + 5);
+ // The protocol is a uint8_t, it follows a 0uint8_t for checksum
+ // calculation.
+ *(Data_to_mutate + mutated_size + 11) = IPPROTO_UDP;
+
+ uint16_t new_checksum = compute_checksum(Data_to_mutate, mutated_size + 12);
+ *((uint16_t *)Data_to_mutate + 3) = new_checksum;
+
+ // Copy the mutated data back to the `Data` array
+ memcpy(start_of_udp,Data_to_mutate,mutated_size);
+
+ Data_ptr += rec->incl_len;
+ current_size -= rec->incl_len;
+ }
+ return Size;
+}
+#endif //CUSTOM_MUTATOR
+
+
+// Fuzzing strategy is the following :
+// The custom mutator :
+// - extract the packets from the pcap one by one,
+// - mutates the ip header and put it back inside the pcap
+// this is required because we need the pcap structure to separate them
+// before we send them to slirp.
+// LLVMFuzzerTestOneInput :
+// - build a slirp instance,
+// - extract the packets from the pcap one by one,
+// - send the data to `slirp_input`
+// - call `slirp_pollfds_fill` and `slirp_pollfds_poll` to advance slirp
+// - cleanup slirp when the whole pcap has been unwrapped.
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ Slirp *slirp = NULL;
+ struct in_addr net = { .s_addr = htonl(0x0a000200) }; /* 10.0.2.0 */
+ struct in_addr mask = { .s_addr = htonl(0xffffff00) }; /* 255.255.255.0 */
+ struct in_addr host = { .s_addr = htonl(0x0a000202) }; /* 10.0.2.2 */
+ struct in_addr dhcp = { .s_addr = htonl(0x0a00020f) }; /* 10.0.2.15 */
+ struct in_addr dns = { .s_addr = htonl(0x0a000203) }; /* 10.0.2.3 */
+ struct in6_addr ip6_prefix;
+ struct in6_addr ip6_host;
+ struct in6_addr ip6_dns;
+ int ret, vprefix6_len = 64;
+ const char *vhostname = NULL;
+ const char *tftp_server_name = NULL;
+ const char *tftp_export = NULL;
+ const char *bootfile = NULL;
+ const char **dnssearch = NULL;
+ const char *vdomainname = NULL;
+ const pcap_hdr_t *hdr = (const void *)data;
+ const pcaprec_hdr_t *rec = NULL;
+ uint32_t timeout = 0;
+
+ if (size < sizeof(pcap_hdr_t)) {
+ return 0;
+ }
+ data += sizeof(*hdr);
+ size -= sizeof(*hdr);
+
+ if (hdr->magic_number == 0xd4c3b2a1) {
+ g_debug("FIXME: byteswap fields");
+ return 0;
+ } /* else assume native pcap file */
+ if (hdr->network != 1) {
+ return 0;
+ }
+
+ fuzz_set_logging_func();
+
+ ret = inet_pton(AF_INET6, "fec0::", &ip6_prefix);
+ g_assert_cmpint(ret, ==, 1);
+
+ ip6_host = ip6_prefix;
+ ip6_host.s6_addr[15] |= 2;
+ ip6_dns = ip6_prefix;
+ ip6_dns.s6_addr[15] |= 3;
+
+ slirp =
+ slirp_init(false, true, net, mask, host, true, ip6_prefix, vprefix6_len,
+ ip6_host, vhostname, tftp_server_name, tftp_export, bootfile,
+ dhcp, dns, ip6_dns, dnssearch, vdomainname, &slirp_cb, NULL);
+
+ while (size > sizeof(*rec)) {
+ rec = (const void *)data;
+ data += sizeof(*rec);
+ size -= sizeof(*rec);
+ if (rec->incl_len != rec->orig_len) {
+ g_debug("unsupported rec->incl_len != rec->orig_len");
+ break;
+ }
+ if (rec->incl_len > size) {
+ break;
+ }
+
+ slirp_input(slirp, data, rec->incl_len);
+ slirp_pollfds_fill(slirp, &timeout, add_poll_cb, NULL);
+ slirp_pollfds_poll(slirp, 0, get_revents_cb, NULL);
+
+ data += rec->incl_len;
+ size -= rec->incl_len;
+ }
+
+ slirp_cleanup(slirp);
+
+ return 0;
+}
diff --git a/meson.build b/meson.build
index b76b7d6..7a3da0e 100644
--- a/meson.build
+++ b/meson.build
@@ -18,10 +18,14 @@
want_ossfuzz = get_option('oss-fuzz')
want_libfuzzer = get_option('llvm-fuzz')
+fuzz_reproduce = get_option('fuzz-reproduce')
if want_ossfuzz and want_libfuzzer
error('only one of oss-fuzz and llvm-fuzz can be specified')
endif
fuzzer_build = want_ossfuzz or want_libfuzzer
+if fuzzer_build and fuzz_reproduce
+ error('fuzzer build and reproducer build are mutually exclusive')
+endif
cc = meson.get_compiler('c')
add_languages('cpp', required : fuzzer_build)
@@ -144,6 +148,23 @@
vflag += vflag_test
endif
+if fuzzer_build
+ cargs += '-fsanitize-coverage=edge,indirect-calls,trace-cmp'
+ cargs += '-fsanitize=fuzzer-no-link,address'
+ cargs += '-fprofile-instr-generate'
+ cargs += '-fcoverage-mapping'
+ cargs += '-g'
+ cargs += '-DSLIRP_DEBUG'
+ vflag += '-fsanitize=fuzzer-no-link,address'
+ vflag += '-fsanitize-coverage=edge,indirect-calls,trace-cmp'
+ vflag += '-fprofile-instr-generate'
+ vflag += '-fcoverage-mapping'
+endif
+if fuzz_reproduce
+ cargs += '-DSLIRP_DEBUG'
+ cargs += '-g'
+endif
+
install_devel = not meson.is_subproject()
configure_file(
@@ -154,17 +175,28 @@
configuration : conf
)
-lib = library('slirp', sources,
- version : lt_version,
- c_args : cargs,
- link_args : vflag,
- link_depends : mapfile,
- dependencies : [glib_dep, platform_deps],
- install : install_devel or get_option('default_library') == 'shared',
-)
+if fuzzer_build or fuzz_reproduce
+ lib = static_library('slirp', sources,
+ c_args : cargs,
+ link_args : vflag,
+ link_depends : mapfile,
+ dependencies : [glib_dep, platform_deps],
+ )
+else
+ lib = library('slirp', sources,
+ version : lt_version,
+ c_args : cargs,
+ link_args : vflag,
+ link_depends : mapfile,
+ dependencies : [glib_dep, platform_deps],
+ install : install_devel or get_option('default_library') == 'shared',
+ )
+endif
pingtest = executable('pingtest', 'test/pingtest.c',
link_with: [ lib ],
+ c_args : cargs,
+ link_args : vflag,
include_directories: [ 'src' ],
dependencies : [ platform_deps ]
)
@@ -173,6 +205,8 @@
ncsitest = executable('ncsitest', 'test/ncsitest.c',
link_with: [lib],
+ c_args : cargs,
+ link_args : vflag,
include_directories: ['src'],
dependencies: [glib_dep, platform_deps]
)
diff --git a/meson_options.txt b/meson_options.txt
index 72c2d58..1dad4b9 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -7,5 +7,8 @@
option('llvm-fuzz', type : 'boolean', value : 'false',
description : 'build against LLVM libFuzzer')
+option('fuzz-reproduce', type : 'boolean', value : 'false',
+ description : 'build a standalone executable to reproduce fuzz cases')
+
option('static', type : 'boolean', value : 'false',
description : 'build static binary')