diff mbox series

[v3,16/22] fuzz: add fuzzer skeleton

Message ID 20190918231846.22538-17-alxndr@bu.edu
State New
Headers show
Series Add virtual device fuzzing support | expand

Commit Message

Alexander Bulekov Sept. 18, 2019, 11:19 p.m. UTC
tests/fuzz/fuzz.c serves as the entry point for the virtual-device
fuzzer. Namely, libfuzzer invokes the LLVMFuzzerInitialize and
LLVMFuzzerTestOneInput functions, both of which are defined in this
file. This change adds a "FuzzTarget" struct, along with the
fuzz_add_target function, which should be used to define new fuzz
targets.

Signed-off-by: Alexander Oleinik <alxndr@bu.edu>
---
 tests/fuzz/Makefile.include |   4 +-
 tests/fuzz/fuzz.c           | 179 ++++++++++++++++++++++++++++++++++++
 tests/fuzz/fuzz.h           |  30 ++++++
 3 files changed, 211 insertions(+), 2 deletions(-)
 create mode 100644 tests/fuzz/fuzz.c
 create mode 100644 tests/fuzz/fuzz.h

Comments

Stefan Hajnoczi Sept. 19, 2019, 12:48 p.m. UTC | #1
On Wed, Sep 18, 2019 at 11:19:43PM +0000, Oleinik, Alexander wrote:
> +void set_fuzz_target_args(int argc, char **argv)
> +{
> +    if (fuzz_target) {
> +        fuzz_target->main_argc = argc;
> +        fuzz_target->main_argv = argv;
> +    }
> +}

Why calls this and why?

> +
> +void reboot(QTestState *s)
> +{
> +    qemu_system_reset(SHUTDOWN_CAUSE_GUEST_RESET);
> +}

Why does reboot() take an unused argument?

> +static void usage(char *path)
> +{
> +    printf("Usage: %s --FUZZ_TARGET [LIBFUZZER ARGUMENTS]\n", path);
> +    printf("where --FUZZ_TARGET is one of:\n");

Is the "--" prefix a libfuzzer requirement?  I would have expected
either FUZZ_TARGET by itself or --fuzz-target=FUZZ_TARGET (a properly
formatted long option) so that collisions with other command-line
options are not possible.

> +typedef struct FuzzTarget {
> +    GString *name;
> +    GString *description;
> +    void(*pre_main)(void);
> +    void(*pre_fuzz)(QTestState *);
> +    void(*fuzz)(QTestState *, const unsigned char *, size_t);
> +    int main_argc;
> +    char **main_argv;
> +} FuzzTarget;
> +
> +void set_fuzz_target_args(int argc, char **argv);
> +void reboot(QTestState *);
> +void fuzz_add_target(const char *name, const char *description, FuzzTarget
> +        *target);

This is a strange API.  I can't make sense of the struct,
fuzz_add_target(), and set_fuzz_target_args() without reading the
implementation:

1. set_fuzz_target_args() implies that there is global state but then
   FuzzTarget also has main_argc and main_argv fields.  I'm not sure
   what the relationship is.

2. fuzz_add_target() takes name and description as arguments but expects
   the caller to populate the struct arg's pre_main(), pre_fuzz(),
   fuzz() function pointers.  This is inconsistent and undocumented.

A cleaner API:

  /* Each fuzz target implements the following interface: */
  typedef struct {
      const char *name;        /* command-line option for this target */
      const char *description; /* human-readable help text */

      /* TODO documentation */
      void (*pre_main)(void);

      /* TODO documentation */
      void (*pre_fuzz)(QTestState *);

      /* TODO documentation */
      void (*fuzz)(QTestState *, const unsigned char *, size_t);
  } FuzzTarget;

  void fuzz_register_target(const FuzzTarget *target);
Alexander Bulekov Sept. 19, 2019, 1:49 p.m. UTC | #2
On Thu, 2019-09-19 at 13:48 +0100, Stefan Hajnoczi wrote:

> > +
> > +void reboot(QTestState *s)
> > +{
> > +    qemu_system_reset(SHUTDOWN_CAUSE_GUEST_RESET);
> > +}
> 
> Why does reboot() take an unused argument?
It was needed when I had a reset_state(s) pointer which was separate
from fuzz(). Since fuzz() is responsible for initializing and resetting
state now, I'll remove it.

> 
> > +static void usage(char *path)
> > +{
> > +    printf("Usage: %s --FUZZ_TARGET [LIBFUZZER ARGUMENTS]\n",
> > path);
> > +    printf("where --FUZZ_TARGET is one of:\n");
> 
> Is the "--" prefix a libfuzzer requirement?  I would have expected
> either FUZZ_TARGET by itself or --fuzz-target=FUZZ_TARGET (a properly
> formatted long option) so that collisions with other command-line
> options are not possible.
Yes libfuzzer will only pass arguments that start with "--". I can
replace it with --fuzz-target=FUZZ_TARGET. Alternatively, I can try to
build separate binaries for each target. It might waste disk space, but
we wouldn't need arguments (--trace could be replace with TRACE=1 in
ENV). With this design, I'm not sure what to do with code such as
i440fx_fuzz.c which re-purposes some functions for multiple different
fuzz targets.

> > +typedef struct FuzzTarget {
> > +    GString *name;
> > +    GString *description;
> > +    void(*pre_main)(void);
> > +    void(*pre_fuzz)(QTestState *);
> > +    void(*fuzz)(QTestState *, const unsigned char *, size_t);
> > +    int main_argc;
> > +    char **main_argv;
> > +} FuzzTarget;
> > +
> > +void set_fuzz_target_args(int argc, char **argv);
> > +void reboot(QTestState *);
> > +void fuzz_add_target(const char *name, const char *description,
> > FuzzTarget
> > +        *target);
> 
> This is a strange API.  I can't make sense of the struct,
> fuzz_add_target(), and set_fuzz_target_args() without reading the
> implementation:
> 
> 1. set_fuzz_target_args() implies that there is global state but then
>    FuzzTarget also has main_argc and main_argv fields.  I'm not sure
>    what the relationship is.
This is essentially there for the QOS support. For QOS targets, when
running fuzz_add_qos_target(), we don't yet know argc and argv, since
that requires walking the QOSGraph. When we have populated the
FuzzTargetList, QOSGraph and parsed the --FUZZ_TARGET, we set global
FuzzTarget and check against it while walking the QOSGraph. When we
find the matching path, we then know the argc/argv and can set them for
the global fuzz_target. Since qos_fuzz.c still needs major work due to
all of the duplicated code, I'll take another stab at this. Maybe we
can identify the argc/argv immediately when we add the fuzz target node
to the QOSGraph. This is another case for "each target has its own
binary", since that could avoid much of this complexity, since we
wouldn't need the fuzz_target_list.

> 2. fuzz_add_target() takes name and description as arguments but
> expects
>    the caller to populate the struct arg's pre_main(), pre_fuzz(),
>    fuzz() function pointers.  This is inconsistent and undocumented.
> 
> A cleaner API:
> 
>   /* Each fuzz target implements the following interface: */
>   typedef struct {
>       const char *name;        /* command-line option for this target
> */
>       const char *description; /* human-readable help text */
> 
>       /* TODO documentation */
>       void (*pre_main)(void);
> 
>       /* TODO documentation */
>       void (*pre_fuzz)(QTestState *);
> 
>       /* TODO documentation */
>       void (*fuzz)(QTestState *, const unsigned char *, size_t);
>   } FuzzTarget;

Sounds good. Should there also be argc and argv here? 

>   void fuzz_register_target(const FuzzTarget *target);
Stefan Hajnoczi Sept. 20, 2019, 9:30 a.m. UTC | #3
On Thu, Sep 19, 2019 at 01:49:09PM +0000, Oleinik, Alexander wrote:
> On Thu, 2019-09-19 at 13:48 +0100, Stefan Hajnoczi wrote:
> > > +static void usage(char *path)
> > > +{
> > > +    printf("Usage: %s --FUZZ_TARGET [LIBFUZZER ARGUMENTS]\n",
> > > path);
> > > +    printf("where --FUZZ_TARGET is one of:\n");
> > 
> > Is the "--" prefix a libfuzzer requirement?  I would have expected
> > either FUZZ_TARGET by itself or --fuzz-target=FUZZ_TARGET (a properly
> > formatted long option) so that collisions with other command-line
> > options are not possible.
> Yes libfuzzer will only pass arguments that start with "--". I can
> replace it with --fuzz-target=FUZZ_TARGET. Alternatively, I can try to
> build separate binaries for each target. It might waste disk space, but
> we wouldn't need arguments (--trace could be replace with TRACE=1 in
> ENV). With this design, I'm not sure what to do with code such as
> i440fx_fuzz.c which re-purposes some functions for multiple different
> fuzz targets.

Building a single fuzzing binary with all targets feels natural.  Please
support the --fuzz-target=TARGET syntax though.

> > A cleaner API:
> > 
> >   /* Each fuzz target implements the following interface: */
> >   typedef struct {
> >       const char *name;        /* command-line option for this target
> > */
> >       const char *description; /* human-readable help text */
> > 
> >       /* TODO documentation */
> >       void (*pre_main)(void);
> > 
> >       /* TODO documentation */
> >       void (*pre_fuzz)(QTestState *);
> > 
> >       /* TODO documentation */
> >       void (*fuzz)(QTestState *, const unsigned char *, size_t);
> >   } FuzzTarget;
> 
> Sounds good. Should there also be argc and argv here? 

If they are read-only and provided by the FuzzTarget, then yes.  The
reason I consider this "cleaner" is because the FuzzTarget struct is
stateless and just captures the information about the fuzz target
instead of mixing it with runtime state.  But like I said, I didn't
really understand the design of the struct so maybe I don't understand
the full problem :).
Darren Kenny Sept. 23, 2019, 2:55 p.m. UTC | #4
On Wed, Sep 18, 2019 at 11:19:43PM +0000, Oleinik, Alexander wrote:
>tests/fuzz/fuzz.c serves as the entry point for the virtual-device
>fuzzer. Namely, libfuzzer invokes the LLVMFuzzerInitialize and
>LLVMFuzzerTestOneInput functions, both of which are defined in this
>file. This change adds a "FuzzTarget" struct, along with the
>fuzz_add_target function, which should be used to define new fuzz
>targets.
>
>Signed-off-by: Alexander Oleinik <alxndr@bu.edu>
>---
> tests/fuzz/Makefile.include |   4 +-
> tests/fuzz/fuzz.c           | 179 ++++++++++++++++++++++++++++++++++++
> tests/fuzz/fuzz.h           |  30 ++++++
> 3 files changed, 211 insertions(+), 2 deletions(-)
> create mode 100644 tests/fuzz/fuzz.c
> create mode 100644 tests/fuzz/fuzz.h
>
>diff --git a/tests/fuzz/Makefile.include b/tests/fuzz/Makefile.include
>index 324e6c1433..b415b056b0 100644
>--- a/tests/fuzz/Makefile.include
>+++ b/tests/fuzz/Makefile.include
>@@ -1,4 +1,4 @@
>-# QEMU_PROG_FUZZ=qemu-fuzz-$(TARGET_NAME)$(EXESUF)
>+QEMU_PROG_FUZZ=qemu-fuzz-$(TARGET_NAME)$(EXESUF)
> fuzz-obj-y = $(libqos-obj-y)
> fuzz-obj-y += tests/libqtest.o
>-
>+fuzz-obj-y += tests/fuzz/fuzz.o
>diff --git a/tests/fuzz/fuzz.c b/tests/fuzz/fuzz.c
>new file mode 100644
>index 0000000000..833f436731
>--- /dev/null
>+++ b/tests/fuzz/fuzz.c
>@@ -0,0 +1,179 @@
>+#include "qemu/osdep.h"
>+
>+#include <stdio.h>
>+#include <stdlib.h>
>+
>+
>+#include "tests/libqtest.h"
>+#include "sysemu/qtest.h"
>+#include "fuzz.h"
>+#include "tests/libqos/qgraph.h"
>+#include "sysemu/runstate.h"
>+#include "sysemu/sysemu.h"
>+
>+typedef struct FuzzTargetState {
>+        FuzzTarget *target;
>+        QSLIST_ENTRY(FuzzTargetState) target_list;
>+} FuzzTargetState;
>+
>+typedef QSLIST_HEAD(, FuzzTargetState) FuzzTargetList;
>+
>+static const char *fuzz_arch = TARGET_NAME;
>+
>+static FuzzTargetList *fuzz_target_list;
>+static FuzzTarget *fuzz_target;
>+static QTestState *fuzz_qts;
>+static bool trace;
>+
>+
>+void set_fuzz_target_args(int argc, char **argv)
>+{
>+    if (fuzz_target) {
>+        fuzz_target->main_argc = argc;
>+        fuzz_target->main_argv = argv;
>+    }
>+}
>+
>+void reboot(QTestState *s)
>+{
>+    qemu_system_reset(SHUTDOWN_CAUSE_GUEST_RESET);
>+}
>+
>+static QTestState *qtest_setup(void)
>+{
>+    qtest_server_set_tx_handler(&qtest_client_inproc_recv, NULL);
>+    return qtest_inproc_init(trace, fuzz_arch, &qtest_server_inproc_recv);
>+}
>+
>+void fuzz_add_target(const char *name, const char *description,
>+        FuzzTarget *target)
>+{
>+    FuzzTargetState *tmp;
>+    FuzzTargetState *target_state;
>+    if (!fuzz_target_list) {
>+        fuzz_target_list = g_new0(FuzzTargetList, 1);
>+    }
>+
>+    QSLIST_FOREACH(tmp, fuzz_target_list, target_list) {
>+        if (g_strcmp0(tmp->target->name->str, name) == 0) {
>+            fprintf(stderr, "Error: Fuzz target name %s already in use\n",
>+                    name);
>+            abort();
>+        }
>+    }
>+    target_state = g_new0(FuzzTargetState, 1);
>+    target_state->target = g_new0(FuzzTarget, 1);
>+    *(target_state->target) = *target;
>+    target_state->target->name = g_string_new(name);
>+    target_state->target->description = g_string_new(description);
>+    QSLIST_INSERT_HEAD(fuzz_target_list, target_state, target_list);
>+}
>+
>+
>+static FuzzTarget *fuzz_get_target(char* name)
>+{
>+    FuzzTargetState *tmp;
>+    if (!fuzz_target_list) {
>+        fprintf(stderr, "Fuzz target list not initialized\n");
>+        abort();
>+    }
>+
>+    QSLIST_FOREACH(tmp, fuzz_target_list, target_list) {
>+        if (g_strcmp0(tmp->target->name->str, name) == 0) {
>+            break;
>+        }
>+    }
>+    return tmp->target;
>+}
>+
>+
>+static void usage(char *path)
>+{
>+    printf("Usage: %s --FUZZ_TARGET [LIBFUZZER ARGUMENTS]\n", path);
>+    printf("where --FUZZ_TARGET is one of:\n");
>+    FuzzTargetState *tmp;
>+    if (!fuzz_target_list) {
>+        fprintf(stderr, "Fuzz target list not initialized\n");
>+        abort();
>+    }
>+    QSLIST_FOREACH(tmp, fuzz_target_list, target_list) {
>+        printf(" --%s  : %s\n", tmp->target->name->str,
>+                tmp->target->description->str);
>+    }
>+    exit(0);
>+}
>+
>+
>+/* Executed for each fuzzing-input */
>+int LLVMFuzzerTestOneInput(const unsigned char *Data, size_t Size)
>+{
>+    if (fuzz_target->fuzz) {
>+        fuzz_target->fuzz(fuzz_qts, Data, Size);
>+    }
>+    return 0;
>+}
>+
>+/* Executed once, prior to fuzzing */
>+int LLVMFuzzerInitialize(int *argc, char ***argv, char ***envp)
>+{
>+
>+    char *target_name, *trace_qtest;
>+
>+    /* --trace is useful for outputting a log of qtest commands that trigger
>+     * a crash. The log can can then be replayed with a simple qtest script. */
>+    if (*argc > 2) {
>+        trace_qtest = (*argv)[2];
>+        if (strcmp(trace_qtest, "--trace") == 0) {
>+            trace = true;
>+        }
>+    }
>+
>+    /* Initialize qgraph and modules */
>+    qos_graph_init();
>+    module_call_init(MODULE_INIT_FUZZ_TARGET);
>+    module_call_init(MODULE_INIT_QOM);
>+    module_call_init(MODULE_INIT_LIBQOS);
>+
>+    if (*argc <= 1) {
>+        usage(**argv);
>+    }
>+
>+    /* Identify the fuzz target */
>+    target_name = (*argv)[1];
>+    target_name += 2;
>+    fuzz_target = fuzz_get_target(target_name);
>+
>+    fuzz_qts = qtest_setup(void);

Shouldn't be passing void as an argument here, causes a build
failure for me.

Thanks,

Darren.
diff mbox series

Patch

diff --git a/tests/fuzz/Makefile.include b/tests/fuzz/Makefile.include
index 324e6c1433..b415b056b0 100644
--- a/tests/fuzz/Makefile.include
+++ b/tests/fuzz/Makefile.include
@@ -1,4 +1,4 @@ 
-# QEMU_PROG_FUZZ=qemu-fuzz-$(TARGET_NAME)$(EXESUF)
+QEMU_PROG_FUZZ=qemu-fuzz-$(TARGET_NAME)$(EXESUF)
 fuzz-obj-y = $(libqos-obj-y)
 fuzz-obj-y += tests/libqtest.o
-
+fuzz-obj-y += tests/fuzz/fuzz.o
diff --git a/tests/fuzz/fuzz.c b/tests/fuzz/fuzz.c
new file mode 100644
index 0000000000..833f436731
--- /dev/null
+++ b/tests/fuzz/fuzz.c
@@ -0,0 +1,179 @@ 
+#include "qemu/osdep.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+
+#include "tests/libqtest.h"
+#include "sysemu/qtest.h"
+#include "fuzz.h"
+#include "tests/libqos/qgraph.h"
+#include "sysemu/runstate.h"
+#include "sysemu/sysemu.h"
+
+typedef struct FuzzTargetState {
+        FuzzTarget *target;
+        QSLIST_ENTRY(FuzzTargetState) target_list;
+} FuzzTargetState;
+
+typedef QSLIST_HEAD(, FuzzTargetState) FuzzTargetList;
+
+static const char *fuzz_arch = TARGET_NAME;
+
+static FuzzTargetList *fuzz_target_list;
+static FuzzTarget *fuzz_target;
+static QTestState *fuzz_qts;
+static bool trace;
+
+
+void set_fuzz_target_args(int argc, char **argv)
+{
+    if (fuzz_target) {
+        fuzz_target->main_argc = argc;
+        fuzz_target->main_argv = argv;
+    }
+}
+
+void reboot(QTestState *s)
+{
+    qemu_system_reset(SHUTDOWN_CAUSE_GUEST_RESET);
+}
+
+static QTestState *qtest_setup(void)
+{
+    qtest_server_set_tx_handler(&qtest_client_inproc_recv, NULL);
+    return qtest_inproc_init(trace, fuzz_arch, &qtest_server_inproc_recv);
+}
+
+void fuzz_add_target(const char *name, const char *description,
+        FuzzTarget *target)
+{
+    FuzzTargetState *tmp;
+    FuzzTargetState *target_state;
+    if (!fuzz_target_list) {
+        fuzz_target_list = g_new0(FuzzTargetList, 1);
+    }
+
+    QSLIST_FOREACH(tmp, fuzz_target_list, target_list) {
+        if (g_strcmp0(tmp->target->name->str, name) == 0) {
+            fprintf(stderr, "Error: Fuzz target name %s already in use\n",
+                    name);
+            abort();
+        }
+    }
+    target_state = g_new0(FuzzTargetState, 1);
+    target_state->target = g_new0(FuzzTarget, 1);
+    *(target_state->target) = *target;
+    target_state->target->name = g_string_new(name);
+    target_state->target->description = g_string_new(description);
+    QSLIST_INSERT_HEAD(fuzz_target_list, target_state, target_list);
+}
+
+
+static FuzzTarget *fuzz_get_target(char* name)
+{
+    FuzzTargetState *tmp;
+    if (!fuzz_target_list) {
+        fprintf(stderr, "Fuzz target list not initialized\n");
+        abort();
+    }
+
+    QSLIST_FOREACH(tmp, fuzz_target_list, target_list) {
+        if (g_strcmp0(tmp->target->name->str, name) == 0) {
+            break;
+        }
+    }
+    return tmp->target;
+}
+
+
+static void usage(char *path)
+{
+    printf("Usage: %s --FUZZ_TARGET [LIBFUZZER ARGUMENTS]\n", path);
+    printf("where --FUZZ_TARGET is one of:\n");
+    FuzzTargetState *tmp;
+    if (!fuzz_target_list) {
+        fprintf(stderr, "Fuzz target list not initialized\n");
+        abort();
+    }
+    QSLIST_FOREACH(tmp, fuzz_target_list, target_list) {
+        printf(" --%s  : %s\n", tmp->target->name->str,
+                tmp->target->description->str);
+    }
+    exit(0);
+}
+
+
+/* Executed for each fuzzing-input */
+int LLVMFuzzerTestOneInput(const unsigned char *Data, size_t Size)
+{
+    if (fuzz_target->fuzz) {
+        fuzz_target->fuzz(fuzz_qts, Data, Size);
+    }
+    return 0;
+}
+
+/* Executed once, prior to fuzzing */
+int LLVMFuzzerInitialize(int *argc, char ***argv, char ***envp)
+{
+
+    char *target_name, *trace_qtest;
+
+    /* --trace is useful for outputting a log of qtest commands that trigger
+     * a crash. The log can can then be replayed with a simple qtest script. */
+    if (*argc > 2) {
+        trace_qtest = (*argv)[2];
+        if (strcmp(trace_qtest, "--trace") == 0) {
+            trace = true;
+        }
+    }
+
+    /* Initialize qgraph and modules */
+    qos_graph_init();
+    module_call_init(MODULE_INIT_FUZZ_TARGET);
+    module_call_init(MODULE_INIT_QOM);
+    module_call_init(MODULE_INIT_LIBQOS);
+
+    if (*argc <= 1) {
+        usage(**argv);
+    }
+
+    /* Identify the fuzz target */
+    target_name = (*argv)[1];
+    target_name += 2;
+    fuzz_target = fuzz_get_target(target_name);
+
+    fuzz_qts = qtest_setup(void);
+
+    if (!fuzz_target) {
+        fprintf(stderr, "Error: Fuzz fuzz_target name %s not found\n",
+                target_name);
+        usage(**argv);
+    }
+
+    if (fuzz_target->pre_main) {
+        fuzz_target->pre_main();
+    }
+
+    if (trace) {
+        printf("### cmd_line: ");
+        for (int i = 0; i < (fuzz_target->main_argc); i++) {
+            printf("%s ", ((fuzz_target->main_argv))[i]);
+        }
+        printf("\n");
+    }
+
+    /* Run QEMU's softmmu main with the calculated arguments*/
+    qemu_init(fuzz_target->main_argc, fuzz_target->main_argv, NULL);
+
+    /* If configured, this is a good spot to set up snapshotting */
+    if (fuzz_target->pre_fuzz) {
+        fuzz_target->pre_fuzz(fuzz_qts);
+    }
+
+    if (trace) {
+        printf("### END INITIALIZATION\n");
+    }
+
+    return 0;
+}
diff --git a/tests/fuzz/fuzz.h b/tests/fuzz/fuzz.h
new file mode 100644
index 0000000000..73af029c82
--- /dev/null
+++ b/tests/fuzz/fuzz.h
@@ -0,0 +1,30 @@ 
+#ifndef FUZZER_H_
+#define FUZZER_H_
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qapi/error.h"
+#include "exec/memory.h"
+#include "tests/libqtest.h"
+
+
+typedef struct FuzzTarget {
+    GString *name;
+    GString *description;
+    void(*pre_main)(void);
+    void(*pre_fuzz)(QTestState *);
+    void(*fuzz)(QTestState *, const unsigned char *, size_t);
+    int main_argc;
+    char **main_argv;
+} FuzzTarget;
+
+void set_fuzz_target_args(int argc, char **argv);
+void reboot(QTestState *);
+void fuzz_add_target(const char *name, const char *description, FuzzTarget
+        *target);
+
+int LLVMFuzzerTestOneInput(const unsigned char *Data, size_t Size);
+int LLVMFuzzerInitialize(int *argc, char ***argv, char ***envp);
+
+#endif
+