diff --git a/fuzzing/README.md b/fuzzing/README.md new file mode 100644 index 000000000..bcf32b51a --- /dev/null +++ b/fuzzing/README.md @@ -0,0 +1,49 @@ +Fuzz targets used by [oss-fuzz](https://github.com/google/oss-fuzz/). + +## How to add new targets + +Add **fuzz_target_name.c** and edit `meson.build` accordingly. + +New targets are picked up by oss-fuzz automatically within a day. Targets must not be renamed once added. + +Add (optional) **fuzz_target_name.dict** containing keywords and magic bytes. + +Add (optional) **fuzz_target_name.corpus** with file names on separate lines. Wildcards `?`, `*` and `**` are supported. Examples below. + +```bash +glib/* # all files in directory glib +glib/** # all files in directory glib and sub-directories +**.xbel # all files ending with .xbel in the repository +``` + +Recommended reading: [Fuzz Target](https://llvm.org/docs/LibFuzzer.html#fuzz-target), [Dictionaries](https://llvm.org/docs/LibFuzzer.html#dictionaries), [Corpus](https://llvm.org/docs/LibFuzzer.html#corpus) + +## How to reproduce oss-fuzz bugs locally + +Build with at least the following flags, choosing a sanitizer as needed. A somewhat recent version of [clang](http://clang.llvm.org/) is recommended. + +```bash +$ CC=clang CXX=clang++ meson DIR -Db_sanitize= -Db_lundef=false +``` + +Afterwards run the affected target against the provided test case. + +```bash +$ DIR/fuzzing/fuzz_target_name FILE +``` + +#### FAQs + +###### What about Memory Sanitizer (MSAN)? + +Correct MSAN instrumentation is [difficult to achieve](https://clang.llvm.org/docs/MemorySanitizer.html#handling-external-code) locally, so false positives are very likely to mask the actual bug. + +If need be, [you can still reproduce](https://github.com/google/oss-fuzz/blob/master/docs/reproducing.md#building-using-docker) those bugs with the oss-fuzz provided docker images. + +###### There are no file/function names in the stack trace. + +`llvm-symbolizer` must be in `PATH`. + +###### UndefinedBehavior Sanitizer (UBSAN) doesn't provide a stack trace. + +Set environment variable `UBSAN_OPTIONS` to `print_stacktrace=1` prior to running the target. diff --git a/fuzzing/driver.c b/fuzzing/driver.c new file mode 100644 index 000000000..99e965ba6 --- /dev/null +++ b/fuzzing/driver.c @@ -0,0 +1,32 @@ +/* Simpler gnu89 version of StandaloneFuzzTargetMain.c from LLVM */ + +#include +#include +#include + +extern int LLVMFuzzerTestOneInput (const unsigned char *data, size_t size); + +int +main (int argc, char **argv) +{ + FILE *f; + size_t n_read, len; + unsigned char *buf; + + if (argc < 2) + return 1; + + f = fopen (argv[1], "r"); + assert (f); + fseek (f, 0, SEEK_END); + len = ftell (f); + fseek (f, 0, SEEK_SET); + buf = (unsigned char*) malloc (len); + n_read = fread (buf, 1, len, f); + assert (n_read == len); + LLVMFuzzerTestOneInput (buf, len); + + free (buf); + printf ("Done!\n"); + return 0; +} diff --git a/fuzzing/fuzz.h b/fuzzing/fuzz.h new file mode 100644 index 000000000..4a879984c --- /dev/null +++ b/fuzzing/fuzz.h @@ -0,0 +1,22 @@ +#include "gio/gio.h" +#include "glib/glib.h" + +int LLVMFuzzerTestOneInput (const unsigned char *data, size_t size); + +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +static GLogWriterOutput +empty_logging_func (GLogLevelFlags log_level, const GLogField *fields, + gsize n_fields, gpointer user_data) +{ + return G_LOG_WRITER_HANDLED; +} +#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_writer_func (empty_logging_func, NULL, NULL); +#endif +} diff --git a/fuzzing/fuzz_bookmark.c b/fuzzing/fuzz_bookmark.c new file mode 100644 index 000000000..4f257fd0c --- /dev/null +++ b/fuzzing/fuzz_bookmark.c @@ -0,0 +1,15 @@ +#include "fuzz.h" + +int +LLVMFuzzerTestOneInput (const unsigned char *data, size_t size) +{ + GBookmarkFile *bookmark = NULL; + + fuzz_set_logging_func (); + + bookmark = g_bookmark_file_new (); + g_bookmark_file_load_from_data (bookmark, (const gchar*) data, size, NULL); + + g_bookmark_file_free (bookmark); + return 0; +} diff --git a/fuzzing/fuzz_bookmark.corpus b/fuzzing/fuzz_bookmark.corpus new file mode 100644 index 000000000..5c78bc930 --- /dev/null +++ b/fuzzing/fuzz_bookmark.corpus @@ -0,0 +1 @@ +glib/tests/**.xbel diff --git a/fuzzing/fuzz_dbus_message.c b/fuzzing/fuzz_dbus_message.c new file mode 100644 index 000000000..bccb2ba3e --- /dev/null +++ b/fuzzing/fuzz_dbus_message.c @@ -0,0 +1,28 @@ +#include "fuzz.h" + +const static GDBusCapabilityFlags flags = G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING; + +int +LLVMFuzzerTestOneInput (const unsigned char *data, size_t size) +{ + gssize bytes; + GDBusMessage *msg = NULL; + guchar *blob = NULL; + gsize msg_size; + + fuzz_set_logging_func (); + + bytes = g_dbus_message_bytes_needed ((guchar*) data, size, NULL); + if (bytes <= 0) + return 0; + + msg = g_dbus_message_new_from_blob ((guchar*) data, size, flags, NULL); + if (msg == NULL) + return 0; + + blob = g_dbus_message_to_blob (msg, &msg_size, flags, NULL); + + g_free (blob); + g_object_unref (msg); + return 0; +} diff --git a/fuzzing/fuzz_key.c b/fuzzing/fuzz_key.c new file mode 100644 index 000000000..8d0edc5fa --- /dev/null +++ b/fuzzing/fuzz_key.c @@ -0,0 +1,16 @@ +#include "fuzz.h" + +int +LLVMFuzzerTestOneInput (const unsigned char *data, size_t size) +{ + GKeyFile *key = NULL; + + fuzz_set_logging_func (); + + key = g_key_file_new (); + g_key_file_load_from_data (key, (const gchar*) data, size, G_KEY_FILE_NONE, + NULL); + + g_key_file_free (key); + return 0; +} diff --git a/fuzzing/fuzz_key.corpus b/fuzzing/fuzz_key.corpus new file mode 100644 index 000000000..5a1cb4166 --- /dev/null +++ b/fuzzing/fuzz_key.corpus @@ -0,0 +1,2 @@ +glib/tests/**.ini +gio/tests/**.desktop diff --git a/fuzzing/fuzz_variant_binary.c b/fuzzing/fuzz_variant_binary.c new file mode 100644 index 000000000..995718c23 --- /dev/null +++ b/fuzzing/fuzz_variant_binary.c @@ -0,0 +1,21 @@ +#include "fuzz.h" + +int +LLVMFuzzerTestOneInput (const unsigned char *data, size_t size) +{ + GVariant *variant = NULL, *normal_variant = NULL; + + fuzz_set_logging_func (); + + variant = g_variant_new_from_data (G_VARIANT_TYPE_VARIANT, data, size, FALSE, + NULL, NULL); + if (variant == NULL) + return 0; + + normal_variant = g_variant_take_ref (g_variant_get_normal_form (variant)); + g_variant_get_data (variant); + + g_variant_unref (normal_variant); + g_variant_unref (variant); + return 0; +} diff --git a/fuzzing/fuzz_variant_text.c b/fuzzing/fuzz_variant_text.c new file mode 100644 index 000000000..a79790949 --- /dev/null +++ b/fuzzing/fuzz_variant_text.c @@ -0,0 +1,21 @@ +#include "fuzz.h" + +int +LLVMFuzzerTestOneInput (const unsigned char *data, size_t size) +{ + const gchar *gdata = (const gchar*) data; + GVariant *variant = NULL; + gchar *text = NULL; + + fuzz_set_logging_func (); + + variant = g_variant_parse (NULL, gdata, gdata + size, NULL, NULL); + if (variant == NULL) + return 0; + + text = g_variant_print (variant, TRUE); + + g_free (text); + g_variant_unref (variant); + return 0; +} diff --git a/fuzzing/fuzz_variant_text.dict b/fuzzing/fuzz_variant_text.dict new file mode 100644 index 000000000..a0e528400 --- /dev/null +++ b/fuzzing/fuzz_variant_text.dict @@ -0,0 +1,32 @@ +"'" +"\"" +"(" +")" +"<" +">" +"[" +"]" +"{" +"}" +"*" +"?" +"@" +"b'" +"b\"" +"boolean" +"byte" +"double" +"false" +"handle" +"int16" +"int32" +"int64" +"just" +"nothing" +"objectpath" +"signature" +"string" +"true" +"uint16" +"uint32" +"uint64" diff --git a/fuzzing/meson.build b/fuzzing/meson.build new file mode 100644 index 000000000..16878fedf --- /dev/null +++ b/fuzzing/meson.build @@ -0,0 +1,28 @@ +fuzz_targets = [ + 'fuzz_bookmark', + 'fuzz_dbus_message', + 'fuzz_key', + 'fuzz_variant_binary', + 'fuzz_variant_text', +] + +deps = [libgmodule_dep, libgio_dep, libglib_dep, libgobject_dep] + +extra_sources = [] +extra_c_args = cc.get_supported_arguments('-Werror=unused-function') + +# Links in a static library provided by oss-fuzz, else a standalone driver. +# https://github.com/google/oss-fuzz/blob/master/docs/new_project_guide.md#buildsh-script-environment +fuzzing_engine = cxx.find_library('FuzzingEngine', required : false) +if fuzzing_engine.found() + deps += fuzzing_engine +else + extra_sources += 'driver.c' +endif + +foreach target_name : fuzz_targets + exe = executable(target_name, [extra_sources, target_name + '.c'], + c_args : extra_c_args, + dependencies : deps, + ) +endforeach diff --git a/meson.build b/meson.build index dd5bd8f3f..d562e8e87 100644 --- a/meson.build +++ b/meson.build @@ -1980,6 +1980,7 @@ subdir('gio') if xgettext.found() subdir('po') endif +subdir('fuzzing') subdir('tests') # Install glib-gettextize executable, if a UNIX-style shell is found