diff options
-rw-r--r-- | fuzzing/README.md | 51 | ||||
-rw-r--r-- | fuzzing/corpuses/generic-cache-dir | bin | 0 -> 1872 bytes | |||
-rw-r--r-- | fuzzing/driver.c | 45 | ||||
-rw-r--r-- | fuzzing/meson.build | 43 | ||||
-rw-r--r-- | fuzzing/setup.c | 133 | ||||
-rw-r--r-- | fuzzing/setup.h | 15 | ||||
-rw-r--r-- | meson.build | 8 | ||||
-rw-r--r-- | meson_options.txt | 4 | ||||
-rw-r--r-- | src/meson.build | 1 |
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 Binary files differnew file mode 100644 index 0000000..30f640f --- /dev/null +++ b/fuzzing/corpuses/generic-cache-dir 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) |