summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOneric <oneric@oneric.stub>2021-08-26 23:02:31 +0200
committerOneric <oneric@oneric.stub>2022-07-03 16:19:06 +0200
commit9987eef44384b368670d962fca57b5994b216499 (patch)
tree07c6e526a03faa2d02b3d437fc79720b01a4e43c
parentf1d19df9011ba5824bed137f4cc170d882514f33 (diff)
downloadlibass-9987eef44384b368670d962fca57b5994b216499.tar.bz2
libass-9987eef44384b368670d962fca57b5994b216499.tar.xz
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=...
-rw-r--r--.gitignore1
-rw-r--r--Makefile_util.am8
-rw-r--r--configure.ac9
-rw-r--r--fuzz/fuzz.c250
4 files changed, 268 insertions, 0 deletions
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 <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#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 <subtitle file>\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