Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (C) 2022 libass contributors |
3 | | * |
4 | | * This file is part of libass. |
5 | | * |
6 | | * Permission to use, copy, modify, and distribute this software for any |
7 | | * purpose with or without fee is hereby granted, provided that the above |
8 | | * copyright notice and this permission notice appear in all copies. |
9 | | * |
10 | | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 | | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 | | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 | | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 | | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
15 | | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
16 | | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 | | */ |
18 | | |
19 | | #include <assert.h> |
20 | | #include <stdarg.h> |
21 | | #include <stdbool.h> |
22 | | #include <stdint.h> |
23 | | #include <stdio.h> |
24 | | #include <stdlib.h> |
25 | | #include <string.h> |
26 | | #ifdef _WIN32 |
27 | | #include <io.h> |
28 | | #else |
29 | | #include <unistd.h> |
30 | | #endif |
31 | | #include "ass.h" |
32 | | #include "ass_types.h" |
33 | | |
34 | | #define FUZZMODE_STANDALONE 0 |
35 | | #define FUZZMODE_AFLXX_SHAREDMEM 1 |
36 | | #define FUZZMODE_LIBFUZZER 2 |
37 | | #ifndef ASS_FUZZMODE |
38 | | #define ASS_FUZZMODE FUZZMODE_STANDALONE |
39 | | #endif |
40 | | |
41 | | // Limit maximal processed size in continuous fuzzing; |
42 | | // to be used when the fuzzing setup doesn't expose its own max size control. |
43 | | // Has no effect in standalone builds |
44 | | #ifdef ASSFUZZ_MAX_LEN |
45 | 11.1k | #define LEN_IN_RANGE(s) ((s) <= (ASSFUZZ_MAX_LEN)) |
46 | | #else |
47 | | #define LEN_IN_RANGE(s) true |
48 | | #endif |
49 | | |
50 | | #define STR_(x) #x |
51 | | #define STR(x) STR_(x) |
52 | | |
53 | | // MSAN: will trigger MSAN if any pixel in bitmap not written to (costly) |
54 | | #ifndef ASSFUZZ_HASH_WHOLEBITMAP |
55 | | #define ASSFUZZ_HASH_WHOLEBITMAP 0 |
56 | | #endif |
57 | | |
58 | | ASS_Library *ass_library = NULL; |
59 | | ASS_Renderer *ass_renderer = NULL; |
60 | | bool quiet = false; |
61 | | |
62 | | uint8_t hval = 0; |
63 | | |
64 | | #if ASSFUZZ_HASH_WHOLEBITMAP |
65 | | static inline void hash(const void *buf, size_t len) |
66 | | { |
67 | | const uint8_t *ptr = buf; |
68 | | const uint8_t *end = ptr + len; |
69 | | while (ptr < end) |
70 | | hval ^= *ptr++; |
71 | | // MSAN doesn't trigger on the XORs, but will on conditional branches |
72 | | if (hval) |
73 | | hval ^= 57; |
74 | | } |
75 | | #endif |
76 | | |
77 | | void msg_callback(int level, const char *fmt, va_list va, void *data) |
78 | 322k | { |
79 | | #if ASS_FUZZMODE == FUZZMODE_STANDALONE |
80 | | if (level > 6 || quiet) return; |
81 | | printf("libass: "); |
82 | | vprintf(fmt, va); |
83 | | printf("\n"); |
84 | | #else |
85 | | // still check for type-mismatches even when not printing |
86 | | // (seems to be cheap enough from some simple perormance tests) |
87 | 322k | char msg[2048]; |
88 | 322k | int l = vsnprintf(msg, sizeof(msg), fmt, va) - 1; |
89 | 322k | l = l >= sizeof(msg) ? sizeof(msg) - 1 : l; |
90 | 322k | l = l < 0 ? 0 : l; |
91 | 322k | hval ^= *(msg + l); |
92 | 322k | #endif |
93 | 322k | } |
94 | | |
95 | | static const int RWIDTH = 854; |
96 | | static const int RHEIGHT = 480; |
97 | | |
98 | | static bool init_renderer(void) |
99 | 11.1k | { |
100 | 11.1k | if (ass_renderer) |
101 | 0 | return true; |
102 | | |
103 | 11.1k | ass_renderer = ass_renderer_init(ass_library); |
104 | 11.1k | if (!ass_renderer) |
105 | 0 | return false; |
106 | | |
107 | 11.1k | ass_set_fonts(ass_renderer, NULL, "sans-serif", |
108 | 11.1k | ASS_FONTPROVIDER_AUTODETECT, NULL, 1); |
109 | 11.1k | ass_set_frame_size(ass_renderer, RWIDTH, RHEIGHT); |
110 | 11.1k | ass_set_storage_size(ass_renderer, RWIDTH, RHEIGHT); |
111 | | |
112 | 11.1k | return true; |
113 | 11.1k | } |
114 | | |
115 | | static bool init(void) |
116 | 11.1k | { |
117 | 11.1k | ass_library = ass_library_init(); |
118 | 11.1k | if (!ass_library) { |
119 | 0 | printf("ass_library_init failed!\n"); |
120 | 0 | return false; |
121 | 0 | } |
122 | | |
123 | 11.1k | ass_set_message_cb(ass_library, msg_callback, NULL); |
124 | | |
125 | 11.1k | if (!init_renderer()) { |
126 | 0 | ass_library_done(ass_library); |
127 | 0 | ass_library = NULL; |
128 | 0 | printf("ass_renderer_init failed!\n"); |
129 | 0 | return false; |
130 | 0 | } |
131 | | |
132 | 11.1k | return true; |
133 | 11.1k | } |
134 | | |
135 | | |
136 | | static inline void process_image(ASS_Image *imgs) |
137 | 52.8k | { |
138 | 537k | for (; imgs; imgs = imgs->next) { |
139 | 484k | assert(imgs->w >= 0 && imgs->h >= 0 && |
140 | 484k | imgs->dst_x >= 0 && imgs->dst_y >= 0 && |
141 | 484k | imgs->dst_x + imgs->w <= RWIDTH && |
142 | 484k | imgs->dst_y + imgs->h <= RHEIGHT && |
143 | 484k | imgs->stride >= imgs->w); |
144 | 484k | #if !ASSFUZZ_HASH_WHOLEBITMAP |
145 | | // Check last pixel to probe for out-of-bounds errors |
146 | 484k | if (imgs->w && imgs->h) |
147 | 429k | hval ^= *(imgs->bitmap + imgs->stride * (imgs->h - 1) + imgs->w - 1); |
148 | | #else |
149 | | unsigned char *src = imgs->bitmap; |
150 | | for (int y = 0; y < imgs->h; ++y) { |
151 | | hash(src, imgs->w); |
152 | | src += imgs->stride; |
153 | | } |
154 | | #endif |
155 | 484k | } |
156 | 52.8k | } |
157 | | |
158 | | static void consume_track(ASS_Renderer *renderer, ASS_Track *track) |
159 | 7.94k | { |
160 | 28.3k | for (int n = 0; n < track->n_events; ++n) { |
161 | 20.4k | int change; |
162 | 20.4k | ASS_Event event = track->events[n]; |
163 | 20.4k | process_image(ass_render_frame(ass_renderer, track, event.Start, &change)); |
164 | 20.4k | if (event.Duration > 1) { |
165 | 16.1k | process_image(ass_render_frame(ass_renderer, track, event.Start + event.Duration/2, &change)); |
166 | 16.1k | process_image(ass_render_frame(ass_renderer, track, event.Start + event.Duration-1, &change)); |
167 | 16.1k | } |
168 | 20.4k | } |
169 | 7.94k | } |
170 | | |
171 | | #if ASS_FUZZMODE == FUZZMODE_STANDALONE |
172 | | #include "writeout.h" |
173 | | |
174 | | struct settings { |
175 | | enum { |
176 | | CONSUME_INPUT, |
177 | | WRITEOUT_TRACK |
178 | | } mode; |
179 | | const char *input; // path or "-" for stdin |
180 | | const char *output; // path or NULL for tmp file |
181 | | }; |
182 | | |
183 | | #ifdef _WIN32 |
184 | | #define READ_RET int |
185 | | #else |
186 | | #define READ_RET ssize_t |
187 | | #endif |
188 | | |
189 | | static ASS_Track *read_track_from_stdin(void) |
190 | | { |
191 | | size_t smax = 4096; |
192 | | char *buf = malloc(smax); |
193 | | if (!buf) |
194 | | goto error; |
195 | | size_t s = 0; |
196 | | READ_RET read_b = 0; |
197 | | do { |
198 | | // AFL++ docs recommend using raw file descriptors |
199 | | // to avoid buffering issues with stdin |
200 | | read_b = read(fileno(stdin), buf + s, smax - s); |
201 | | s += read_b > 0 ? read_b : 0; |
202 | | if (s == smax) { |
203 | | size_t new_smax = smax > SIZE_MAX / 2 ? SIZE_MAX : smax * 2; |
204 | | char *new_buf = realloc(buf, new_smax); |
205 | | if (!new_buf || new_smax <= smax) { |
206 | | free(new_buf ? new_buf : buf); |
207 | | goto error; |
208 | | } |
209 | | smax = new_smax; |
210 | | buf = new_buf; |
211 | | } |
212 | | } while (read_b > 0); |
213 | | buf[s] = '\0'; |
214 | | ASS_Track *track = ass_read_memory(ass_library, buf, s, NULL); |
215 | | free(buf); |
216 | | return track; |
217 | | error: |
218 | | printf("Input too large!\n"); |
219 | | return NULL; |
220 | | } |
221 | | |
222 | | /** |
223 | | * \param argc |
224 | | * \param argv |
225 | | * \param settings will be filled according to parsed args or defaults |
226 | | * \return whether CLI args could be parsed successfully |
227 | | */ |
228 | | static bool parse_cmdline(int argc, char *argv[], struct settings *settings) |
229 | | { |
230 | | // defaults |
231 | | settings->mode = CONSUME_INPUT; |
232 | | settings->input = NULL; |
233 | | settings->output = NULL; |
234 | | |
235 | | int i; |
236 | | for (i = 1; i < argc; i++) { |
237 | | const char *param = argv[i]; |
238 | | if (!param || param[0] != '-' || !param[1]) |
239 | | goto no_more_args; |
240 | | |
241 | | switch (param[1]) { |
242 | | case 'q': |
243 | | quiet = true; |
244 | | break; |
245 | | |
246 | | case 'o': |
247 | | settings->mode = WRITEOUT_TRACK; |
248 | | // optional argument |
249 | | if (argc - i > 1 && argv[i + 1][0] != '-') { |
250 | | settings->output = argv[i + 1]; |
251 | | i++; |
252 | | } |
253 | | break; |
254 | | |
255 | | case '-': |
256 | | if (param[2]) { |
257 | | return false; |
258 | | } else { |
259 | | i++; |
260 | | goto no_more_args; |
261 | | } |
262 | | |
263 | | default: |
264 | | return false; |
265 | | } |
266 | | |
267 | | continue; |
268 | | |
269 | | no_more_args: |
270 | | break; |
271 | | } |
272 | | |
273 | | if (argc < 2 || argc - i > 1 || argc == i) |
274 | | return false; |
275 | | |
276 | | settings->input = argv[argc - 1]; |
277 | | return !!settings->input; |
278 | | } |
279 | | |
280 | | int main(int argc, char *argv[]) |
281 | | { |
282 | | /* Default failure code of sanitisers is 1, unless |
283 | | * changed via env (A|UB|..)SAN_OPTIONS=exitcode=21 |
284 | | * Except, LLVM's UBSAN always exits with 0 (unless using |
285 | | * -fsanitize-undefined-trap-on-error which will SIGILL without an |
286 | | * error report being printed), see https://reviews.llvm.org/D35085 |
287 | | */ |
288 | | enum { |
289 | | FUZZ_OK = 0, |
290 | | //SANITISER_FAIL = 1, |
291 | | // Invalid parameters passed etc |
292 | | FUZZ_BAD_USAGE = 2, |
293 | | // Error before rendering starts |
294 | | FUZZ_INIT_ERR = 0 |
295 | | }; |
296 | | |
297 | | ASS_Track *track = NULL; |
298 | | int retval = FUZZ_OK; |
299 | | |
300 | | struct settings settings; |
301 | | if (!parse_cmdline(argc, argv, &settings)) { |
302 | | printf("usage: %s [-q] [-o [output_file]] [--] <subtitle file>\n" |
303 | | " -q:\n" |
304 | | " Hide libass log messages\n" |
305 | | "\n" |
306 | | " -o [FILE]:\n" |
307 | | " Write out parsed file content in a standardized form\n" |
308 | | " into FILE or if omitted a generated temporary file.\n" |
309 | | " If used the input file will not be processed, only parsed.\n", |
310 | | argc ? argv[0] : "fuzz"); |
311 | | return FUZZ_BAD_USAGE; |
312 | | } |
313 | | |
314 | | if (!init()) { |
315 | | printf("library init failed!\n"); |
316 | | retval = FUZZ_INIT_ERR; |
317 | | goto cleanup; |
318 | | } |
319 | | |
320 | | if (strcmp(settings.input, "-")) |
321 | | track = ass_read_file(ass_library, settings.input, NULL); |
322 | | else |
323 | | track = read_track_from_stdin(); |
324 | | |
325 | | if (!track) { |
326 | | printf("track init failed!\n"); |
327 | | retval = FUZZ_INIT_ERR; |
328 | | goto cleanup; |
329 | | } |
330 | | |
331 | | switch (settings.mode) { |
332 | | case CONSUME_INPUT: |
333 | | consume_track(ass_renderer, track); |
334 | | break; |
335 | | |
336 | | case WRITEOUT_TRACK: |
337 | | write_out_track(track, settings.output); |
338 | | break; |
339 | | } |
340 | | |
341 | | cleanup: |
342 | | if (track) ass_free_track(track); |
343 | | if (ass_renderer) ass_renderer_done(ass_renderer); |
344 | | if (ass_library) ass_library_done(ass_library); |
345 | | |
346 | | return retval; |
347 | | } |
348 | | #elif ASS_FUZZMODE == FUZZMODE_AFLXX_SHAREDMEM |
349 | | __AFL_FUZZ_INIT(); |
350 | | /* |
351 | | * AFL++ docs recommend to disable optimisation for the main function |
352 | | * and GCC and Clang are the only AFL compilers. |
353 | | */ |
354 | | #pragma clang optimize off |
355 | | #pragma GCC optimize("O0") |
356 | | int main(int argc, char *argv[]) |
357 | | { |
358 | | // AFLs buffer and length macros should not be used directly |
359 | | ssize_t len; |
360 | | unsigned char *buf; |
361 | | |
362 | | #ifdef ASSFUZZ_FONTCONFIG_SYSROOT |
363 | | setenv("FONTCONFIG_SYSROOT", STR(ASSFUZZ_FONTCONFIG_SYSROOT), 1); |
364 | | #endif |
365 | | |
366 | | if (!init()) { |
367 | | printf("library init failed!\n"); |
368 | | return 1; |
369 | | } |
370 | | |
371 | | __AFL_INIT(); |
372 | | buf = __AFL_FUZZ_TESTCASE_BUF; |
373 | | while (__AFL_LOOP(100000)) { |
374 | | len = __AFL_FUZZ_TESTCASE_LEN; |
375 | | if (!LEN_IN_RANGE(len)) |
376 | | continue; |
377 | | |
378 | | if (!init_renderer()) { |
379 | | printf("Failing renderer init, skipping a sample!\n"); |
380 | | continue; |
381 | | } |
382 | | |
383 | | ASS_Track *track = ass_read_memory(ass_library, (char *)buf, len, NULL); |
384 | | if (!track) |
385 | | continue; |
386 | | consume_track(ass_renderer, track); |
387 | | |
388 | | ass_free_track(track); |
389 | | ass_renderer_done(ass_renderer); |
390 | | ass_renderer = NULL; |
391 | | ass_clear_fonts(ass_library); |
392 | | } |
393 | | |
394 | | ass_renderer_done(ass_renderer); |
395 | | ass_library_done(ass_library); |
396 | | |
397 | | return 0; |
398 | | } |
399 | | #elif ASS_FUZZMODE == FUZZMODE_LIBFUZZER |
400 | | int LLVMFuzzerInitialize(int *argc, char ***argv) |
401 | 2 | { |
402 | | #ifdef ASSFUZZ_FONTCONFIG_SYSROOT |
403 | | setenv("FONTCONFIG_SYSROOT", STR(ASSFUZZ_FONTCONFIG_SYSROOT), 1); |
404 | | #endif |
405 | 2 | return 0; |
406 | 2 | } |
407 | | |
408 | | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) |
409 | 11.1k | { |
410 | | // OSS Fuzz docs recommend just returning 0 on too large input |
411 | | // libFuzzer docs tell us to use -1 to prevent addition to corpus |
412 | | // BUT HongFuzz' LLVMFuzzerTestOneInput hook can't handle it and |
413 | | // neither does OSS Fuzz convert this in their wrapper, see: |
414 | | // https://github.com/google/oss-fuzz/issues/11983 |
415 | 11.1k | if (!LEN_IN_RANGE(size)) |
416 | 11 | return 0; |
417 | | |
418 | 11.1k | ASS_Track *track = NULL; |
419 | | |
420 | 11.1k | if (!init()) |
421 | 0 | exit(1); |
422 | | |
423 | 11.1k | track = ass_read_memory(ass_library, (char *)data, size, NULL); |
424 | 11.1k | if (track) { |
425 | 7.94k | consume_track(ass_renderer, track); |
426 | 7.94k | ass_free_track(track); |
427 | 7.94k | } |
428 | | |
429 | 11.1k | ass_renderer_done(ass_renderer); |
430 | 11.1k | ass_library_done(ass_library); |
431 | 11.1k | ass_renderer = NULL; |
432 | 11.1k | ass_library = NULL; |
433 | | |
434 | 11.1k | return 0; |
435 | 11.1k | } |
436 | | #else |
437 | | #error Unknown fuzzer mode selected! |
438 | | #endif |