summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilip Withnall <pwithnall@gnome.org>2023-12-04 16:21:35 +0000
committerPhilip Withnall <pwithnall@gnome.org>2023-12-05 11:27:17 +0000
commitd9a91e56fe77329709410ee1eb2fdad6c147284e (patch)
tree008220e1c8c6fcb384ecd112db21d5ba8c2681a1
parent1e7b848bab113136cf45061d8351a23c78ab015d (diff)
fuzzing: Add basic fuzzing framework using oss-fuzz
See fuzzing/README.md for details. oss-fuzz (https://github.com/google/oss-fuzz) is a project run by Google which automatically runs fuzz testing on open source projects and files issues if it finds bugs. It requires some tests to run, each of which needs to take an arbitrary binary blob as input. It also requires some configuration for how to build the project — that will live in the oss-fuzz project on GitHub as `projects/xdgmime/`. Signed-off-by: Philip Withnall <pwithnall@gnome.org>
-rw-r--r--fuzzing/README.md51
-rw-r--r--fuzzing/corpuses/generic-cache-dirbin0 -> 1872 bytes
-rw-r--r--fuzzing/driver.c45
-rw-r--r--fuzzing/meson.build43
-rw-r--r--fuzzing/setup.c133
-rw-r--r--fuzzing/setup.h15
-rw-r--r--meson.build8
-rw-r--r--meson_options.txt4
-rw-r--r--src/meson.build1
9 files changed, 300 insertions, 0 deletions
diff --git a/fuzzing/README.md b/fuzzing/README.md
new file mode 100644
index 0000000..49119cc
--- /dev/null
+++ b/fuzzing/README.md
@@ -0,0 +1,51 @@
+Fuzz targets used by [oss-fuzz](https://github.com/google/oss-fuzz/).
+
+Useful links: [Dashboard](https://oss-fuzz.com/) _(requires access)_, [Build logs](https://oss-fuzz-build-logs.storage.googleapis.com/index.html), [Coverage](https://oss-fuzz.com/coverage-report/job/libfuzzer_asan_xdgmime/latest)
+
+## 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
+xdgmime/* # all files in directory xdgmime
+xdgmime/** # all files in directory xdgmime 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=<address|undefined> -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://google.github.io/oss-fuzz/advanced-topics/reproducing/#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/corpuses/generic-cache-dir b/fuzzing/corpuses/generic-cache-dir
new file mode 100644
index 0000000..30f640f
--- /dev/null
+++ b/fuzzing/corpuses/generic-cache-dir
Binary files differ
diff --git a/fuzzing/driver.c b/fuzzing/driver.c
new file mode 100644
index 0000000..d895007
--- /dev/null
+++ b/fuzzing/driver.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2018 LLVM contributors
+ *
+ * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+ *
+ * Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+ * See https://llvm.org/LICENSE.txt for license information.
+ */
+
+/* Simpler gnu89 version of StandaloneFuzzTargetMain.c from LLVM */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+extern int LLVMFuzzerTestOneInput (const unsigned char *data, size_t size);
+
+int
+main (int argc, char **argv)
+{
+ FILE *f;
+ long tell_result;
+ 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);
+ tell_result = ftell (f);
+ assert (tell_result >= 0);
+ len = (size_t) tell_result;
+ 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);
+ fclose (f);
+ printf ("Done!\n");
+ return 0;
+}
diff --git a/fuzzing/meson.build b/fuzzing/meson.build
new file mode 100644
index 0000000..7b29e6a
--- /dev/null
+++ b/fuzzing/meson.build
@@ -0,0 +1,43 @@
+# Copyright 2018 pdknsk
+# Copyright 2020, 2021, 2022 Endless OS Foundation, LLC
+# Copyright 2023 GNOME Foundation Inc.
+#
+# SPDX-License-Identifier: LGPL-2.1-or-later or AFL-2.0
+
+fuzz_targets = [
+]
+
+deps = [libxdgmime_dep]
+
+extra_sources = ['setup.c']
+extra_c_args = cc.get_supported_arguments('-Werror=unused-function')
+
+# Links in a static library provided by oss-fuzz, else a standalone driver.
+# https://google.github.io/oss-fuzz/getting-started/new-project-guide/#buildsh-script-environment
+have_fuzzing_engine = false
+if have_cxx
+ fuzzing_engine = cxx.find_library('FuzzingEngine', required : get_option('oss_fuzz'))
+ have_fuzzing_engine = fuzzing_engine.found()
+endif
+if have_fuzzing_engine
+ 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,
+ )
+
+ # If the FuzzingEngine isn’t available, build some unit tests to check that
+ # the fuzzing files do basically work. This doesn’t do any actual fuzzing though.
+ # Pass in the README as an arbitrary fuzzing input, just so we have something.
+ if not have_fuzzing_engine
+ test(target_name, exe,
+ args : files('README.md'),
+ suite : 'fuzzing',
+ )
+ endif
+endforeach \ No newline at end of file
diff --git a/fuzzing/setup.c b/fuzzing/setup.c
new file mode 100644
index 0000000..d9b2065
--- /dev/null
+++ b/fuzzing/setup.c
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2023 GNOME Foundation Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later or AFL-2.0
+ */
+
+/* for TEMP_FAILURE_RETRY */
+#define _GNU_SOURCE 1
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "setup.h"
+
+static const char *mime_dir_filenames[] =
+ {
+ "mime.cache",
+ "globs2",
+ "globs",
+ "magic",
+ "aliases",
+ "subclasses",
+ "icons",
+ "generic-icons",
+ };
+
+void
+fuzz_teardown (int working_dir_fd)
+{
+ size_t i;
+
+ for (i = 0; i < sizeof (mime_dir_filenames) / sizeof (*mime_dir_filenames); i++)
+ unlinkat (working_dir_fd, mime_dir_filenames[i], 0);
+
+ unlinkat (working_dir_fd, ".", AT_REMOVEDIR);
+
+ close (working_dir_fd);
+}
+
+int
+fuzz_setup (const unsigned char *data,
+ size_t data_len,
+ int *working_dir_fd_out)
+{
+ const char *blob_separator = "~~ fuzz separator ~~";
+ const size_t blob_separator_len = strlen (blob_separator);
+ const unsigned char *separator;
+ size_t i;
+ char tmp_path[] = "/tmp/fuzz_xdgmime_XXXXXX";
+ int dirfd = -1;
+
+ /* libxdgmime loads its inputs from disk, rather than from memory, so we need
+ * to split the fuzz input and save it to disk as multiple files.
+ *
+ * See https://github.com/google/fuzzing/blob/master/docs/split-inputs.md#magic-separator
+ *
+ * You can build a sample input file in this format using:
+ * ```sh
+ * $ pushd ~/.local/share/mime/
+ * $ cat ./mime.cache ^C
+ * $ echo -n "~~ fuzz separator ~~" > separator
+ * $ cat mime.cache separator globs2 separator globs separator magic separator aliases separator subclasses separator icons separator generic-icons > test.corpus
+ * $ rm separator
+ * $ popd
+ * ```
+ */
+
+ *working_dir_fd_out = -1;
+
+ if (mkdtemp (tmp_path) == NULL)
+ return 0;
+
+ dirfd = TEMP_FAILURE_RETRY (open (tmp_path, O_PATH | O_CLOEXEC | O_DIRECTORY));
+ if (dirfd < 0)
+ {
+ rmdir (tmp_path);
+ return 0;
+ }
+
+ for (i = 0; i < sizeof (mime_dir_filenames) / sizeof (*mime_dir_filenames); i++)
+ {
+ const unsigned char *file_data;
+ size_t file_data_len;
+ int fd = -1;
+
+ file_data = data;
+ separator = memmem (data, data_len, blob_separator, blob_separator_len);
+ file_data_len = (separator != NULL) ? (size_t) (separator - data) : data_len;
+
+ if (separator != NULL)
+ {
+ data = separator + blob_separator_len;
+ data_len -= file_data_len + blob_separator_len;
+ }
+
+ fd = TEMP_FAILURE_RETRY (openat (dirfd, mime_dir_filenames[i], O_CREAT | O_WRONLY | O_CLOEXEC));
+ if (fd < 0)
+ {
+ fuzz_teardown (dirfd);
+ return 0;
+ }
+
+ while (file_data_len > 0)
+ {
+ ssize_t s;
+
+ s = TEMP_FAILURE_RETRY (write (fd, file_data, file_data_len));
+ if (s < 0)
+ {
+ close (fd);
+ fuzz_teardown (dirfd);
+ return 0;
+ }
+
+ file_data += s;
+ file_data_len -= s;
+ }
+
+ close (fd);
+
+ /* Skip the rest of the files if this was the last separator. */
+ if (separator == NULL)
+ break;
+ }
+
+ *working_dir_fd_out = dirfd;
+
+ return 1;
+}
diff --git a/fuzzing/setup.h b/fuzzing/setup.h
new file mode 100644
index 0000000..e67a336
--- /dev/null
+++ b/fuzzing/setup.h
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2023 GNOME Foundation Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later or AFL-2.0
+ */
+
+#include <stddef.h>
+
+void fuzz_teardown (int working_dir_fd);
+int fuzz_setup (const unsigned char *data,
+ size_t data_len,
+ int *working_dir_fd_out);
+
+int LLVMFuzzerTestOneInput (const unsigned char *data,
+ size_t size);
diff --git a/meson.build b/meson.build
index 9b71f5f..ef9a4cf 100644
--- a/meson.build
+++ b/meson.build
@@ -27,4 +27,12 @@ add_project_arguments(
language: 'c',
)
+cc = meson.get_compiler('c')
+
+have_cxx = add_languages('cpp', native: false, required: get_option('oss_fuzz').enabled())
+if have_cxx
+ cxx = meson.get_compiler('cpp')
+endif
+
subdir('src')
+subdir('fuzzing') \ No newline at end of file
diff --git a/meson_options.txt b/meson_options.txt
new file mode 100644
index 0000000..c2d3b0f
--- /dev/null
+++ b/meson_options.txt
@@ -0,0 +1,4 @@
+option('oss_fuzz',
+ type : 'feature',
+ value : 'disabled',
+ description : 'Indicate oss-fuzz build environment')
diff --git a/src/meson.build b/src/meson.build
index d8e4777..6273c96 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -15,6 +15,7 @@ libxdgmime = static_library('xdgmime',
libxdgmime_dep = declare_dependency(
link_with : libxdgmime,
+ include_directories : ['.'],
)
meson.override_dependency('xdgmime', libxdgmime_dep)