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')