From 9987eef44384b368670d962fca57b5994b216499 Mon Sep 17 00:00:00 2001 From: Oneric Date: Thu, 26 Aug 2021 23:02:31 +0200 Subject: Add fuzzing utility Parses its input and renders every event at its start, middle and end. By default it is built as a simple standalone program, similar to profile to consume a single already existing input. By setting ASS_FUZZMODE in FUZZ_CPPFLAGS alternative buildmodes offering integration into AFL++ or LLVM's libFuzzer can be selected. As libFuzzer links in its own main function, it cannot be used during configure else the tests fail. Instead 'clang -fsanitize=fuzzer-no-link' can be set together with FUZZ_LDFLAGS='-fsanitize=fuzzer' to only link in the main function into the desired binary. As an alternative to FUZZ_LDFLAGS, it is also possible to change the compiler after configure via make CC=... --- .gitignore | 1 + Makefile_util.am | 8 ++ configure.ac | 9 ++ fuzz/fuzz.c | 250 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 268 insertions(+) create mode 100644 fuzz/fuzz.c diff --git a/.gitignore b/.gitignore index 1af81c0..c285dd3 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ Makefile.in /test/test /compare/compare /profile/profile +/fuzz/fuzz # pkgconfig /libass.pc diff --git a/Makefile_util.am b/Makefile_util.am index b150de0..be1cf88 100644 --- a/Makefile_util.am +++ b/Makefile_util.am @@ -21,3 +21,11 @@ compare_compare_SOURCES = compare/image.h compare/image.c compare/compare.c compare_compare_CPPFLAGS = -I$(top_srcdir)/libass compare_compare_LDADD = libass/libass.la compare_compare_LDFLAGS = $(AM_LDFLAGS) $(LIBPNG_LIBS) -static + +if ENABLE_FUZZ +noinst_PROGRAMS += fuzz/fuzz +endif +fuzz_fuzz_SOURCES = fuzz/fuzz.c +fuzz_fuzz_CPPFLAGS = -I$(top_srcdir)/libass $(FUZZ_CPPFLAGS) +fuzz_fuzz_LDADD = libass/libass.la +fuzz_fuzz_LDFLAGS = $(AM_LDFLAGS) $(FUZZ_LDFLAGS) -static diff --git a/configure.ac b/configure.ac index 7bfd625..f87f855 100644 --- a/configure.ac +++ b/configure.ac @@ -22,6 +22,8 @@ AC_ARG_ENABLE([compare], AS_HELP_STRING([--enable-compare], [enable compare program (requires libpng) @<:@default=no@:>@])) AC_ARG_ENABLE([profile], AS_HELP_STRING([--enable-profile], [enable profiling program @<:@default=no@:>@])) +AC_ARG_ENABLE([fuzz], AS_HELP_STRING([--enable-fuzz], + [enable fuzzing consumer @<:@default=no@:>@])) AC_ARG_ENABLE([fontconfig], AS_HELP_STRING([--disable-fontconfig], [disable fontconfig support @<:@default=check@:>@])) AC_ARG_ENABLE([directwrite], AS_HELP_STRING([--disable-directwrite], @@ -35,6 +37,12 @@ AC_ARG_ENABLE([asm], AS_HELP_STRING([--disable-asm], AC_ARG_ENABLE([large-tiles], AS_HELP_STRING([--enable-large-tiles], [use larger tiles in the rasterizer (better performance, slightly worse quality) @<:@default=disabled@:>@])) +AC_ARG_VAR([FUZZ_LDFLAGS], + [Optional special linking flags only used for the fuzzer binary.]) +AC_ARG_VAR([FUZZ_CPPFLAGS], + [If fuzzing program is enabled, set this to select alternative modes; see fuzzer source for options.]) +FUZZ_CPPFLAGS="${FUZZ_CPPFLAGS:--DASS_FUZZMODE=0}" + # Checks for available libraries and define corresponding C Macros # Start with system libs, then check everything else via pkg-config AS_IF([test "x$ac_cv_header_iconv_h" = xyes], [ @@ -323,6 +331,7 @@ AM_CONDITIONAL([ENABLE_LARGE_TILES], [test "x$enable_large_tiles" = xyes]) AM_CONDITIONAL([ENABLE_COMPARE], [test "x$enable_compare" = xyes && test "x$libpng" = xtrue]) AM_CONDITIONAL([ENABLE_TEST], [test "x$enable_test" = xyes && test "x$libpng" = xtrue]) AM_CONDITIONAL([ENABLE_PROFILE], [test "x$enable_profile" = xyes]) +AM_CONDITIONAL([ENABLE_FUZZ], [test "x$enable_fuzz" = xyes]) AM_CONDITIONAL([FONTCONFIG], [test "x$fontconfig" = xtrue]) AM_CONDITIONAL([CORETEXT], [test "x$coretext" = xtrue]) diff --git a/fuzz/fuzz.c b/fuzz/fuzz.c new file mode 100644 index 0000000..3791397 --- /dev/null +++ b/fuzz/fuzz.c @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2022 libass contributors + * + * This file is part of libass. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include "ass.h" +#include "ass_types.h" + +#define FUZZMODE_STANDALONE 0 +#define FUZZMODE_AFLXX_SHAREDMEM 1 +#define FUZZMODE_LIBFUZZER 2 +#ifndef ASS_FUZZMODE + #define ASS_FUZZMODE FUZZMODE_STANDALONE +#endif + +ASS_Library *ass_library = NULL; +ASS_Renderer *ass_renderer = NULL; + +void msg_callback(int level, const char *fmt, va_list va, void *data) +{ +#if ASS_FUZZMODE == FUZZMODE_STANDALONE + if (level > 6) return; + printf("libass: "); + vprintf(fmt, va); + printf("\n"); +#endif +} + +static bool init_renderer(void) +{ + if (ass_renderer) + return true; + + ass_renderer = ass_renderer_init(ass_library); + if (!ass_renderer) + return false; + + ass_set_fonts(ass_renderer, NULL, "sans-serif", + ASS_FONTPROVIDER_AUTODETECT, NULL, 1); + ass_set_frame_size(ass_renderer, 854, 480); + ass_set_storage_size(ass_renderer, 854, 480); + + return true; +} + +static bool init(void) +{ + ass_library = ass_library_init(); + if (!ass_library) { + printf("ass_library_init failed!\n"); + return false; + } + + ass_set_message_cb(ass_library, msg_callback, NULL); + + if (!init_renderer()) { + ass_library_done(ass_library); + ass_library = NULL; + printf("ass_renderer_init failed!\n"); + return false; + } + + return true; +} + +static void consume_track(ASS_Renderer *renderer, ASS_Track *track) +{ + for (int n = 0; n < track->n_events; ++n) { + int change; + ASS_Event event = track->events[n]; + ass_render_frame(ass_renderer, track, event.Start, &change); + if (event.Duration > 1) { + ass_render_frame(ass_renderer, track, event.Start + event.Duration/2, &change); + ass_render_frame(ass_renderer, track, event.Start + event.Duration-1, &change); + } + } +} + +#if ASS_FUZZMODE == FUZZMODE_STANDALONE +static ASS_Track *read_track_from_stdin(void) +{ + size_t smax = 4096; + char* buf = malloc(smax); + if (!buf) + goto error; + size_t s = 0; + ssize_t read_b = 0; + do { + // AFL++ docs recommend using raw file descriptors + // to avoid buffering issues with stdin + read_b = read(STDIN_FILENO, buf + s, smax - s); + s += read_b > 0 ? read_b : 0; + if (s == smax) { + size_t new_smax = smax > SIZE_MAX / 2 ? SIZE_MAX : smax * 2; + char* new_buf = realloc(buf, new_smax); + if (!new_buf || new_smax <= smax) { + free(new_buf ? new_buf : buf); + goto error; + } + smax = new_smax; + buf = new_buf; + } + } while (read_b > 0); + buf[s] = '\0'; + ASS_Track *track = ass_read_memory(ass_library, buf, s, NULL); + free(buf); + return track; +error: + printf("Input too large!\n"); + return NULL; +} + +int main(int argc, char *argv[]) +{ + /* Default failure code of sanitisers is 1, unless + * changed via env (A|UB|..)SAN_OPTIONS=exitcode=21 + * Except, LLVM's UBSAN always exits with 0 (unless using + * -fsanitize-undefined-trap-on-error which will SIGILL without an + * error report being printed), see https://reviews.llvm.org/D35085 + */ + enum { + FUZZ_OK = 0, + //SANITISER_FAIL = 1, + // Invalid parameters passed etc + FUZZ_BAD_USAGE = 2, + // Error before rendering starts + FUZZ_INIT_ERR = 0 + }; + + ASS_Track *track = NULL; + int retval = FUZZ_OK; + + if (argc != 2) { + printf("usage: %s \n", argc ? argv[0] : "fuzz"); + return FUZZ_BAD_USAGE; + } + + if (!init()) { + printf("library init failed!\n"); + retval = FUZZ_INIT_ERR; + goto cleanup; + } + + if (strcmp(argv[1], "-")) + track = ass_read_file(ass_library, argv[1], NULL); + else + track = read_track_from_stdin(); + + if (!track) { + printf("track init failed!\n"); + retval = FUZZ_INIT_ERR; + goto cleanup; + } + + consume_track(ass_renderer, track); + +cleanup: + if (track) ass_free_track(track); + if (ass_renderer) ass_renderer_done(ass_renderer); + if (ass_library) ass_library_done(ass_library); + + return retval; +} +#elif ASS_FUZZMODE == FUZZMODE_AFLXX_SHAREDMEM +__AFL_FUZZ_INIT(); +/* + * AFL++ docs recommend to disable optimisation for the main function + * and GCC and Clang are the only AFL compilers. + */ +#pragma clang optimize off +#pragma GCC optimize("O0") +int main(int argc, char *argv[]) +{ + // AFLs buffer and length macros should not be used directly + ssize_t len; + unsigned char *buf; + + if (!init()) { + printf("library init failed!\n"); + return 1; + } + + __AFL_INIT(); + buf = __AFL_FUZZ_TESTCASE_BUF; + while (__AFL_LOOP(100000)) { + len = __AFL_FUZZ_TESTCASE_LEN; + + if (!init_renderer()) { + printf("Failing renderer init, skipping a sample!\n"); + continue; + } + + ASS_Track *track = ass_read_memory(ass_library, (char *)buf, len, NULL); + if (!track) + continue; + consume_track(ass_renderer, track); + + ass_free_track(track); + ass_renderer_done(ass_renderer); + ass_renderer = NULL; + ass_clear_fonts(ass_library); + } + + ass_renderer_done(ass_renderer); + ass_library_done(ass_library); + + return 0; +} +#elif ASS_FUZZMODE == FUZZMODE_LIBFUZZER +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ASS_Track *track = NULL; + + // All return values but zero are reserved + if (!init()) + return 0; + + track = ass_read_memory(ass_library, (char *)data, size, NULL); + if (track) { + consume_track(ass_renderer, track); + ass_free_track(track); + } + + ass_renderer_done(ass_renderer); + ass_library_done(ass_library); + + return 0; +} +#else + #error Unknown fuzzer mode selected! +#endif -- cgit v1.2.3