Start some fuzzing test

You can run the tests over the corpus with a "regular" build, then
$ fuzzing/fuzz-input ../fuzzing/IN/*

Or building with fuzzing enabled, and running:
$ CFLAGS="-fsanitize=fuzzer" CC=clang CXX=clang++ meson -Db_lundef=false
$ fuzzing/fuzz-input  ../fuzzing/IN

I have an initial corpus which was generated by running fuzz-input for
a few hours starting with qemu.pkt, which is the first packet sent by
qemu.  Sadly, it only covers 25%... I tried to increase the coverage
manually, see for example tftp-get-blah.pkt, but that's not so simple,
as multiple packets may be required to setup a session etc.

Neverthess, the fuzzing already found a few issues, so it might be
worth to add it in this current form.

fuzzing/oss-fuzz.sh is used by oss-fuzz, for Google fuzzing.
(see documentation if you want to reproduce the build locally)

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
diff --git a/fuzzing/IN/nc-ident.pcap b/fuzzing/IN/nc-ident.pcap
new file mode 100644
index 0000000..3d0421b
--- /dev/null
+++ b/fuzzing/IN/nc-ident.pcap
Binary files differ
diff --git a/fuzzing/IN/qemu.pkt b/fuzzing/IN/qemu.pkt
new file mode 100644
index 0000000..9200037
--- /dev/null
+++ b/fuzzing/IN/qemu.pkt
Binary files differ
diff --git a/fuzzing/IN/tftp-get-blah.pkt b/fuzzing/IN/tftp-get-blah.pkt
new file mode 100644
index 0000000..c540ccf
--- /dev/null
+++ b/fuzzing/IN/tftp-get-blah.pkt
Binary files differ
diff --git a/fuzzing/fuzz-input.c b/fuzzing/fuzz-input.c
new file mode 100644
index 0000000..0707919
--- /dev/null
+++ b/fuzzing/fuzz-input.c
@@ -0,0 +1,240 @@
+#ifdef _WIN32
+/* as defined in sdkddkver.h */
+#ifndef _WIN32_WINNT
+#define _WIN32_WINNT 0x0600 /* Vista */
+#endif
+#include <ws2tcpip.h>
+#endif
+
+#include <glib.h>
+#include "libslirp.h"
+
+int LLVMFuzzerTestOneInput(const unsigned char *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);
+    *addrlen = 0;
+    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;
+
+int LLVMFuzzerTestOneInput(const unsigned char *data, size_t size)
+{
+    Slirp *slirp;
+    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;
+    pcap_hdr_t *hdr = (void *)data;
+    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 = (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/fuzzing/fuzz-input.options b/fuzzing/fuzz-input.options
new file mode 100644
index 0000000..7948888
--- /dev/null
+++ b/fuzzing/fuzz-input.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 1024
diff --git a/fuzzing/fuzz-main.c b/fuzzing/fuzz-main.c
new file mode 100644
index 0000000..1de031c
--- /dev/null
+++ b/fuzzing/fuzz-main.c
@@ -0,0 +1,35 @@
+#include <glib.h>
+#include <stdlib.h>
+
+#define MIN_NUMBER_OF_RUNS 1
+#define EXIT_TEST_SKIP 77
+
+extern int LLVMFuzzerTestOneInput(const unsigned char *data, size_t size);
+
+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 (LLVMFuzzerTestOneInput((void *)buf, size) == EXIT_TEST_SKIP) {
+                return EXIT_TEST_SKIP;
+            }
+        }
+        g_free(buf);
+    }
+
+    return EXIT_SUCCESS;
+}
diff --git a/fuzzing/meson.build b/fuzzing/meson.build
new file mode 100644
index 0000000..50890f7
--- /dev/null
+++ b/fuzzing/meson.build
@@ -0,0 +1,25 @@
+extra_sources = []
+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')
+endif
+
+deps = [glib_dep, libslirp_dep, platform_deps]
+if fuzzer_build
+  deps += fuzzing_engine
+else
+  code = '''
+void main() {}
+'''
+  if cc.links(code, name : 'fuzzer driver check')
+    extra_sources += 'fuzz-main.c'
+  endif
+endif
+
+executable(
+  'fuzz-input', [extra_sources, 'fuzz-input.c'],
+  dependencies : deps,
+)
diff --git a/fuzzing/oss-fuzz.sh b/fuzzing/oss-fuzz.sh
new file mode 100755
index 0000000..b40f2a7
--- /dev/null
+++ b/fuzzing/oss-fuzz.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+set -ex
+
+export CC=${CC:-clang}
+export CXX=${CXX:-clang++}
+export WORK=${WORK:-$(pwd)}
+export OUT=${OUT:-$(pwd)/out}
+
+build=$WORK/build
+rm -rf $build
+mkdir -p $build
+
+fuzzflag="oss-fuzz=true"
+if [ -z "$FUZZING_ENGINE" ]; then
+    fuzzflag="llvm-fuzz=true"
+fi
+
+meson $build \
+      -D$fuzzflag \
+      -Db_lundef=false \
+      -Ddefault_library=static \
+      -Dstatic=true \
+      -Dbuildtype=debugoptimized
+
+ninja -C $build
+
+zip -jqr $OUT/fuzz-input_seed_corpus.zip "$(dirname "$0")/IN"
+
+find $build -type f -executable -name "fuzz-*" -exec mv {} $OUT \;
+find $build -type f -name "*.options" -exec mv {} $OUT \;
diff --git a/meson.build b/meson.build
index 7e7d818..b76b7d6 100644
--- a/meson.build
+++ b/meson.build
@@ -16,7 +16,19 @@
 conf.set('SLIRP_MINOR_VERSION', minor_version)
 conf.set('SLIRP_MICRO_VERSION', micro_version)
 
+want_ossfuzz = get_option('oss-fuzz')
+want_libfuzzer = get_option('llvm-fuzz')
+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
+
 cc = meson.get_compiler('c')
+add_languages('cpp', required : fuzzer_build)
+
+if get_option('static') == true
+  add_global_arguments('-static', language : 'c')
+endif
 
 if cc.get_argument_syntax() != 'msvc'
   r = run_command('build-aux/git-version-gen',
@@ -59,7 +71,7 @@
 
 host_system = host_machine.system()
 
-glib_dep = dependency('glib-2.0')
+glib_dep = dependency('glib-2.0', static : get_option('static'))
 
 add_project_arguments(cc.get_supported_arguments('-Wmissing-prototypes', '-Wstrict-prototypes',
                                                  '-Wredundant-decls', '-Wundef', '-Wwrite-strings'),
@@ -187,7 +199,11 @@
   if get_option('default_library') == 'both'
     lib = lib.get_static_lib()
   endif
-  libslirp_dep = declare_dependency(
-    include_directories: include_directories('.', 'src'),
-    link_with: lib)
 endif
+
+libslirp_dep = declare_dependency(
+  link_with : lib,
+  include_directories : [include_directories('src'), include_directories('.')],
+)
+
+subdir('fuzzing')
diff --git a/meson_options.txt b/meson_options.txt
index 27e7c80..72c2d58 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,2 +1,11 @@
 option('version_suffix', type: 'string', value: '',
        description: 'Suffix to append to SLIRP_VERSION_STRING')
+
+option('oss-fuzz', type : 'boolean', value : 'false',
+       description : 'build against oss-fuzz')
+
+option('llvm-fuzz', type : 'boolean', value : 'false',
+       description : 'build against LLVM libFuzzer')
+
+option('static', type : 'boolean', value : 'false',
+       description : 'build static binary')