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 | | #include <unistd.h> |
27 | | #include "ass.h" |
28 | | #include "ass_types.h" |
29 | | |
30 | | #define FUZZMODE_STANDALONE 0 |
31 | | #define FUZZMODE_AFLXX_SHAREDMEM 1 |
32 | | #define FUZZMODE_LIBFUZZER 2 |
33 | | #ifndef ASS_FUZZMODE |
34 | | #define ASS_FUZZMODE FUZZMODE_STANDALONE |
35 | | #endif |
36 | | |
37 | | // MSAN: will trigger MSAN if any pixel in bitmap not written to (costly) |
38 | | #ifndef ASSFUZZ_HASH_WHOLEBITMAP |
39 | | #define ASSFUZZ_HASH_WHOLEBITMAP 0 |
40 | | #endif |
41 | | |
42 | | ASS_Library *ass_library = NULL; |
43 | | ASS_Renderer *ass_renderer = NULL; |
44 | | |
45 | | uint8_t hval = 0; |
46 | | |
47 | | #if ASSFUZZ_HASH_WHOLEBITMAP |
48 | | static inline void hash(const void *buf, size_t len) |
49 | | { |
50 | | const uint8_t *ptr = buf; |
51 | | const uint8_t *end = ptr + len; |
52 | | while (ptr < end) |
53 | | hval ^= *ptr++; |
54 | | // MSAN doesn't trigger on the XORs, but will on conditional branches |
55 | | if (hval) |
56 | | hval ^= 57; |
57 | | } |
58 | | #endif |
59 | | |
60 | | void msg_callback(int level, const char *fmt, va_list va, void *data) |
61 | 215k | { |
62 | | #if ASS_FUZZMODE == FUZZMODE_STANDALONE |
63 | | if (level > 6) return; |
64 | | printf("libass: "); |
65 | | vprintf(fmt, va); |
66 | | printf("\n"); |
67 | | #else |
68 | | // still check for type-mismatches even when not printing |
69 | | // (seems to be cheap enough from some simple perormance tests) |
70 | 215k | char msg[2048]; |
71 | 215k | int l = vsnprintf(msg, sizeof(msg), fmt, va) - 1; |
72 | 215k | l = l >= sizeof(msg) ? sizeof(msg) - 1 : l; |
73 | 215k | l = l < 0 ? 0 : l; |
74 | 215k | hval ^= *(msg + l); |
75 | 215k | #endif |
76 | 215k | } |
77 | | |
78 | | static const int RWIDTH = 854; |
79 | | static const int RHEIGHT = 480; |
80 | | |
81 | | static bool init_renderer(void) |
82 | 34 | { |
83 | 34 | if (ass_renderer) |
84 | 0 | return true; |
85 | | |
86 | 34 | ass_renderer = ass_renderer_init(ass_library); |
87 | 34 | if (!ass_renderer) |
88 | 0 | return false; |
89 | | |
90 | 34 | ass_set_fonts(ass_renderer, NULL, "sans-serif", |
91 | 34 | ASS_FONTPROVIDER_AUTODETECT, NULL, 1); |
92 | 34 | ass_set_frame_size(ass_renderer, RWIDTH, RHEIGHT); |
93 | 34 | ass_set_storage_size(ass_renderer, RWIDTH, RHEIGHT); |
94 | | |
95 | 34 | return true; |
96 | 34 | } |
97 | | |
98 | | static bool init(void) |
99 | 34 | { |
100 | 34 | ass_library = ass_library_init(); |
101 | 34 | if (!ass_library) { |
102 | 0 | printf("ass_library_init failed!\n"); |
103 | 0 | return false; |
104 | 0 | } |
105 | | |
106 | 34 | ass_set_message_cb(ass_library, msg_callback, NULL); |
107 | | |
108 | 34 | if (!init_renderer()) { |
109 | 0 | ass_library_done(ass_library); |
110 | 0 | ass_library = NULL; |
111 | 0 | printf("ass_renderer_init failed!\n"); |
112 | 0 | return false; |
113 | 0 | } |
114 | | |
115 | 34 | return true; |
116 | 34 | } |
117 | | |
118 | | |
119 | | static inline void process_image(ASS_Image* imgs) |
120 | 51.2k | { |
121 | 358k | for (; imgs; imgs = imgs->next) { |
122 | 307k | assert(imgs->w >= 0 && imgs->h >= 0 && |
123 | 307k | imgs->dst_x >= 0 && imgs->dst_y >= 0 && |
124 | 307k | imgs->dst_x + imgs->w <= RWIDTH && |
125 | 307k | imgs->dst_y + imgs->h <= RHEIGHT && |
126 | 307k | imgs->stride >= imgs->w); |
127 | 307k | #if !ASSFUZZ_HASH_WHOLEBITMAP |
128 | | // Check last pixel to probe for out-of-bounds errors |
129 | 307k | if (imgs->w && imgs->h) |
130 | 307k | hval ^= *(imgs->bitmap + imgs->stride * (imgs->h - 1) + imgs->w - 1); |
131 | | #else |
132 | | unsigned char *src = imgs->bitmap; |
133 | | for (int y = 0; y < imgs->h; ++y) { |
134 | | hash(src, imgs->w); |
135 | | src += imgs->stride; |
136 | | } |
137 | | #endif |
138 | 307k | } |
139 | 51.2k | } |
140 | | |
141 | | static void consume_track(ASS_Renderer *renderer, ASS_Track *track) |
142 | 15 | { |
143 | 51.1k | for (int n = 0; n < track->n_events; ++n) { |
144 | 51.1k | int change; |
145 | 51.1k | ASS_Event event = track->events[n]; |
146 | 51.1k | process_image(ass_render_frame(ass_renderer, track, event.Start, &change)); |
147 | 51.1k | if (event.Duration > 1) { |
148 | 35 | process_image(ass_render_frame(ass_renderer, track, event.Start + event.Duration/2, &change)); |
149 | 35 | process_image(ass_render_frame(ass_renderer, track, event.Start + event.Duration-1, &change)); |
150 | 35 | } |
151 | 51.1k | } |
152 | 15 | } |
153 | | |
154 | | #if ASS_FUZZMODE == FUZZMODE_STANDALONE |
155 | | static ASS_Track *read_track_from_stdin(void) |
156 | | { |
157 | | size_t smax = 4096; |
158 | | char* buf = malloc(smax); |
159 | | if (!buf) |
160 | | goto error; |
161 | | size_t s = 0; |
162 | | ssize_t read_b = 0; |
163 | | do { |
164 | | // AFL++ docs recommend using raw file descriptors |
165 | | // to avoid buffering issues with stdin |
166 | | read_b = read(STDIN_FILENO, buf + s, smax - s); |
167 | | s += read_b > 0 ? read_b : 0; |
168 | | if (s == smax) { |
169 | | size_t new_smax = smax > SIZE_MAX / 2 ? SIZE_MAX : smax * 2; |
170 | | char* new_buf = realloc(buf, new_smax); |
171 | | if (!new_buf || new_smax <= smax) { |
172 | | free(new_buf ? new_buf : buf); |
173 | | goto error; |
174 | | } |
175 | | smax = new_smax; |
176 | | buf = new_buf; |
177 | | } |
178 | | } while (read_b > 0); |
179 | | buf[s] = '\0'; |
180 | | ASS_Track *track = ass_read_memory(ass_library, buf, s, NULL); |
181 | | free(buf); |
182 | | return track; |
183 | | error: |
184 | | printf("Input too large!\n"); |
185 | | return NULL; |
186 | | } |
187 | | |
188 | | int main(int argc, char *argv[]) |
189 | | { |
190 | | /* Default failure code of sanitisers is 1, unless |
191 | | * changed via env (A|UB|..)SAN_OPTIONS=exitcode=21 |
192 | | * Except, LLVM's UBSAN always exits with 0 (unless using |
193 | | * -fsanitize-undefined-trap-on-error which will SIGILL without an |
194 | | * error report being printed), see https://reviews.llvm.org/D35085 |
195 | | */ |
196 | | enum { |
197 | | FUZZ_OK = 0, |
198 | | //SANITISER_FAIL = 1, |
199 | | // Invalid parameters passed etc |
200 | | FUZZ_BAD_USAGE = 2, |
201 | | // Error before rendering starts |
202 | | FUZZ_INIT_ERR = 0 |
203 | | }; |
204 | | |
205 | | ASS_Track *track = NULL; |
206 | | int retval = FUZZ_OK; |
207 | | |
208 | | if (argc != 2) { |
209 | | printf("usage: %s <subtitle file>\n", argc ? argv[0] : "fuzz"); |
210 | | return FUZZ_BAD_USAGE; |
211 | | } |
212 | | |
213 | | if (!init()) { |
214 | | printf("library init failed!\n"); |
215 | | retval = FUZZ_INIT_ERR; |
216 | | goto cleanup; |
217 | | } |
218 | | |
219 | | if (strcmp(argv[1], "-")) |
220 | | track = ass_read_file(ass_library, argv[1], NULL); |
221 | | else |
222 | | track = read_track_from_stdin(); |
223 | | |
224 | | if (!track) { |
225 | | printf("track init failed!\n"); |
226 | | retval = FUZZ_INIT_ERR; |
227 | | goto cleanup; |
228 | | } |
229 | | |
230 | | consume_track(ass_renderer, track); |
231 | | |
232 | | cleanup: |
233 | | if (track) ass_free_track(track); |
234 | | if (ass_renderer) ass_renderer_done(ass_renderer); |
235 | | if (ass_library) ass_library_done(ass_library); |
236 | | |
237 | | return retval; |
238 | | } |
239 | | #elif ASS_FUZZMODE == FUZZMODE_AFLXX_SHAREDMEM |
240 | | __AFL_FUZZ_INIT(); |
241 | | /* |
242 | | * AFL++ docs recommend to disable optimisation for the main function |
243 | | * and GCC and Clang are the only AFL compilers. |
244 | | */ |
245 | | #pragma clang optimize off |
246 | | #pragma GCC optimize("O0") |
247 | | int main(int argc, char *argv[]) |
248 | | { |
249 | | // AFLs buffer and length macros should not be used directly |
250 | | ssize_t len; |
251 | | unsigned char *buf; |
252 | | |
253 | | if (!init()) { |
254 | | printf("library init failed!\n"); |
255 | | return 1; |
256 | | } |
257 | | |
258 | | __AFL_INIT(); |
259 | | buf = __AFL_FUZZ_TESTCASE_BUF; |
260 | | while (__AFL_LOOP(100000)) { |
261 | | len = __AFL_FUZZ_TESTCASE_LEN; |
262 | | |
263 | | if (!init_renderer()) { |
264 | | printf("Failing renderer init, skipping a sample!\n"); |
265 | | continue; |
266 | | } |
267 | | |
268 | | ASS_Track *track = ass_read_memory(ass_library, (char *)buf, len, NULL); |
269 | | if (!track) |
270 | | continue; |
271 | | consume_track(ass_renderer, track); |
272 | | |
273 | | ass_free_track(track); |
274 | | ass_renderer_done(ass_renderer); |
275 | | ass_renderer = NULL; |
276 | | ass_clear_fonts(ass_library); |
277 | | } |
278 | | |
279 | | ass_renderer_done(ass_renderer); |
280 | | ass_library_done(ass_library); |
281 | | |
282 | | return 0; |
283 | | } |
284 | | #elif ASS_FUZZMODE == FUZZMODE_LIBFUZZER |
285 | | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) |
286 | 34 | { |
287 | 34 | ASS_Track *track = NULL; |
288 | | |
289 | | // All return values but zero are reserved |
290 | 34 | if (!init()) |
291 | 0 | return 0; |
292 | | |
293 | 34 | track = ass_read_memory(ass_library, (char *)data, size, NULL); |
294 | 34 | if (track) { |
295 | 15 | consume_track(ass_renderer, track); |
296 | 15 | ass_free_track(track); |
297 | 15 | } |
298 | | |
299 | 34 | ass_renderer_done(ass_renderer); |
300 | 34 | ass_library_done(ass_library); |
301 | 34 | ass_renderer = NULL; |
302 | 34 | ass_library = NULL; |
303 | | |
304 | 34 | return 0; |
305 | 34 | } |
306 | | #else |
307 | | #error Unknown fuzzer mode selected! |
308 | | #endif |