Coverage Report

Created: 2023-06-07 06:14

/src/libass/fuzz/fuzz.c
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