Coverage Report

Created: 2026-10-28 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/r-oss-fuzz/harnesses/common.h
Line
Count
Source
1
/*
2
 * Shared helpers for R fuzzing harnesses (libFuzzer).
3
 *
4
 * Provides:
5
 *   fuzz_suppress_warnings() - Suppress R warnings to avoid buffer overflow
6
 *   fuzz_run_on_r_thread()   - Run a function on the R thread (1 GiB stack)
7
 *   fuzz_init_r()            - Full R initialization sequence
8
 *   fuzz_set_string()        - Stage a string input under a toplevel context
9
 *   fuzz_set_string_ce()     - Stage a string with an explicit encoding mark
10
 *   fuzz_set_raw_arg()       - Stage a raw-vector input likewise
11
 *   fuzz_make_wrapper()      - Parse an R closure that implements a call slot
12
 *   fuzz_scratch_file()      - Per-process temp file for path-only entry points
13
 *   fuzz_write_scratch()     - Rewrite that file with the current input
14
 *   fuzz_repeat_product_excessive() - Guard against TRE repeat blowup
15
 *   LLVMFuzzerInitialize / LLVMFuzzerTestOneInput - forward to the
16
 *       harness's fuzz_target_init / fuzz_target_run on the R thread
17
 *
18
 * Adapted from r-afl's fuzz.h for use with libFuzzer instead of AFL++.
19
 */
20
21
#ifndef FUZZ_COMMON_H
22
#define FUZZ_COMMON_H
23
24
#include <limits.h>
25
#include <pthread.h>
26
#include <signal.h>
27
#include <stdint.h>
28
#include <stdio.h>
29
#include <stdlib.h>
30
#include <string.h>
31
#include <unistd.h>
32
33
#define R_NO_REMAP 1
34
#include <Rembedded.h>
35
#include <Rinternals.h>
36
#define CSTACK_DEFNS 1    /* R_CStackStart, R_CStackLimit */
37
#define HAVE_UINTPTR_T 1
38
#include <Rinterface.h>   /* R_SignalHandlers */
39
#include <R_ext/Parse.h>
40
41
/*
42
 * Suppress R warnings globally.
43
 *
44
 * Many R functions emit warnings on invalid input (e.g., "NAs introduced
45
 * by coercion").  In persistent fuzzing, these accumulate and can overflow
46
 * R's warning buffer, triggering fatal errors.  Setting warn = -1
47
 * suppresses all warnings.
48
 */
49
static void fuzz_suppress_warnings(void)
50
2
{
51
2
    int error = 0;
52
2
    SEXP warn_call;
53
2
    Rf_protect(warn_call = Rf_lang2(Rf_install("options"),
54
2
                                    Rf_ScalarInteger(-1)));
55
2
    SET_TAG(CDR(warn_call), Rf_install("warn"));
56
2
    R_tryEval(warn_call, R_GlobalEnv, &error);
57
2
    Rf_unprotect(1);
58
2
}
59
60
/*
61
 * Set R_HOME based on the fuzzer binary's location.
62
 *
63
 * OSS-Fuzz places everything in $OUT.  The build.sh installs R to
64
 * $OUT/r-install, so R_HOME is at $OUT/r-install/lib/R.  We derive
65
 * this from /proc/self/exe so it works regardless of the mount path.
66
 */
67
static void fuzz_set_r_home(void)
68
2
{
69
    /* Skip if R_HOME is already set (e.g., for local testing) */
70
2
    if (getenv("R_HOME") != NULL)
71
1
        return;
72
73
1
    char exe[PATH_MAX];
74
1
    ssize_t len = readlink("/proc/self/exe", exe, sizeof(exe) - 1);
75
1
    if (len <= 0)
76
0
        return;
77
1
    exe[len] = '\0';
78
79
    /* Find the directory containing the binary */
80
1
    char *slash = strrchr(exe, '/');
81
1
    if (slash == NULL)
82
0
        return;
83
1
    *slash = '\0';
84
85
1
    char r_home[PATH_MAX];
86
1
    snprintf(r_home, sizeof(r_home), "%s/r-install/lib/R", exe);
87
1
    setenv("R_HOME", r_home, 1);
88
1
}
89
90
/*
91
 * Cap R's vector heap.
92
 *
93
 * Hostile input reaches R's allocator directly -- a serialized vector can
94
 * declare any length it likes, and coercion or scanning can be talked into
95
 * asking for one -- so a 64KB input can request gigabytes.
96
 *
97
 * R enforces R_MAX_VSIZE in allocVector and signals an ordinary R error
98
 * ("vector memory limit of N reached").  When it fires inside R_ToplevelExec
99
 * the iteration is discarded and fuzzing carries on; every per-iteration
100
 * allocation must therefore go through fuzz_set_string / fuzz_set_raw_arg
101
 * below, which run under such a context.  That catchability is the reason
102
 * to prefer this over a sanitizer-level RSS limit: ASAN's soft_rss_limit_mb
103
 * either aborts the process or starts handing NULL back to allocators that
104
 * never expected it, and both look like crashes.
105
 *
106
 * Caveats:
107
 *   - The cap bounds R's cumulative LIVE vector heap, not one input's
108
 *     allocations.  Interned symbols are never collected, so the parse and
109
 *     unserialize targets ratchet toward the cap over a long run.
110
 *   - It bounds R's vector heap ONLY.  malloc-based allocations, like TRE
111
 *     compiling a pattern, bypass it entirely; see
112
 *     fuzz_repeat_product_excessive for that guard.
113
 *   - It does cost coverage of code paths that need larger results.
114
 *     Targets with legitimately large outputs (decompress: a 64KB bzip2
115
 *     stream of zeros expands past 1GB) raise the default by defining
116
 *     FUZZ_R_MAX_VSIZE before including this header.
117
 *   - R's suffix parsing is case-sensitive: "4Gb" works, "4gb" silently
118
 *     leaves the heap uncapped (R only warns on stderr at startup).  The
119
 *     env override is for local runs; the CI runner does not forward
120
 *     arbitrary environment variables.
121
 *
122
 * Local reproduction of vector-limit findings under plain Rscript needs
123
 * the same value exported; see README.md.
124
 */
125
#ifndef FUZZ_R_MAX_VSIZE
126
1
# define FUZZ_R_MAX_VSIZE "1Gb"
127
#endif
128
129
static void fuzz_set_vsize_limit(void)
130
2
{
131
2
    if (getenv("R_MAX_VSIZE") == NULL)
132
1
        setenv("R_MAX_VSIZE", FUZZ_R_MAX_VSIZE, 1);
133
2
}
134
135
/*
136
 * Run R on its own thread with a large stack.
137
 *
138
 * Several of R's recursive C walkers never call R_CheckStack(): bind.c's
139
 * AnswerType behind c(recursive = TRUE), deparse.c behind
140
 * as.character(<list>), and the coerce, duplicate, identical and subassign
141
 * walkers.  A list some tens of thousands deep, which an input can build
142
 * at run time, overflows the 8 MiB main-thread stack in them.  Plain R
143
 * reports "segfault from C stack overflow" and recovers, long-standing
144
 * behaviour rather than a defect to report; under the sanitizers it is a
145
 * stack-overflow crash that every target walking a nested value would
146
 * keep rediscovering.
147
 *
148
 * The main thread's stack is fixed before the process starts, so R runs
149
 * on a worker thread created with FUZZ_R_STACK_SIZE of stack instead:
150
 * deep enough that any recursion an input can afford within the per-input
151
 * limits completes, and virtual until touched, so it costs an idle target
152
 * nothing.
153
 *
154
 * Everything that touches R -- initialization, every input, the checks
155
 * under tests/ -- must run on that thread: R records its stack bounds
156
 * once, and R_CheckStack() measures against them.  The libFuzzer entry
157
 * points at the end of this header forward to fuzz_target_init and
158
 * fuzz_target_run there, and fuzz_init_r() refuses to run anywhere else.
159
 *
160
 * The thread does not survive a fork, and AFL++'s driver forks: its
161
 * forkserver starts after LLVMFuzzerInitialize, so every input runs in a
162
 * child that inherited the initialized R but no thread to run it on.
163
 * An atfork handler forgets the thread in the child, and the next job
164
 * starts a fresh one, which points R's stack check at its own stack.
165
 */
166
#ifndef FUZZ_R_STACK_SIZE
167
4
# define FUZZ_R_STACK_SIZE ((size_t)1 << 30)
168
#endif
169
170
typedef struct {
171
    void (*fn)(void *);
172
    void *arg;
173
} fuzz_job_t;
174
175
static struct {
176
    pthread_t thread;
177
    int started;
178
    pthread_mutex_t mutex;
179
    pthread_cond_t cond;
180
    fuzz_job_t *job;        /* set by the caller, cleared by the thread */
181
    uintptr_t stack_top;    /* where the thread's frames begin */
182
} fuzz_r_thread = {
183
    .mutex = PTHREAD_MUTEX_INITIALIZER,
184
    .cond = PTHREAD_COND_INITIALIZER
185
};
186
187
static void *fuzz_r_thread_main(void *unused)
188
2
{
189
2
    (void)unused;
190
    /* The frame address rather than a local's: under ASAN an address-taken
191
     * local may sit on the fake stack, nowhere near this thread's stack.
192
     * R_CStackStart matters only once R is initialized; fuzz_init_r()
193
     * sets it again after Rf_initialize_R has overwritten it. */
194
2
    fuzz_r_thread.stack_top = (uintptr_t)__builtin_frame_address(0);
195
2
    R_CStackStart = fuzz_r_thread.stack_top;
196
197
    /* libFuzzer's -timeout alarm is acted on only by the thread running
198
     * its loop (the main thread); delivered here it would be ignored and
199
     * the input would run on.  Block it so the kernel cannot pick this
200
     * thread.  A timeout report therefore shows the main thread waiting
201
     * rather than where R is; the testcase it writes still reproduces. */
202
2
    sigset_t alarm;
203
2
    sigemptyset(&alarm);
204
2
    sigaddset(&alarm, SIGALRM);
205
2
    pthread_sigmask(SIG_BLOCK, &alarm, NULL);
206
207
2
    pthread_mutex_lock(&fuzz_r_thread.mutex);
208
2.82k
    for (;;) {
209
5.64k
        while (fuzz_r_thread.job == NULL)
210
2.81k
            pthread_cond_wait(&fuzz_r_thread.cond, &fuzz_r_thread.mutex);
211
2.82k
        fuzz_job_t *job = fuzz_r_thread.job;
212
2.82k
        pthread_mutex_unlock(&fuzz_r_thread.mutex);
213
214
2.82k
        job->fn(job->arg);
215
216
2.82k
        pthread_mutex_lock(&fuzz_r_thread.mutex);
217
2.82k
        fuzz_r_thread.job = NULL;
218
2.82k
        pthread_cond_broadcast(&fuzz_r_thread.cond);
219
2.82k
    }
220
2
    return NULL;
221
2
}
222
223
static int fuzz_on_r_thread(void)
224
2.82k
{
225
2.82k
    return fuzz_r_thread.started &&
226
2.82k
           pthread_equal(pthread_self(), fuzz_r_thread.thread);
227
2.82k
}
228
229
/* Child side of fork: only the forking thread survives, so the R thread
230
 * is gone, and if it was the one waiting in pthread_cond_wait the
231
 * condition variable still counts it.  Nothing holds the mutex during a
232
 * fork from either side of the handoff, so resetting both is safe;
233
 * pthread_cond_destroy would wait for the phantom waiter instead. */
234
static void fuzz_r_thread_forget(void)
235
0
{
236
0
    fuzz_r_thread.started = 0;
237
0
    fuzz_r_thread.job = NULL;
238
0
    fuzz_r_thread.mutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
239
0
    fuzz_r_thread.cond = (pthread_cond_t)PTHREAD_COND_INITIALIZER;
240
0
}
241
242
/* Run fn(arg) on the R thread and wait for it to return, starting the
243
 * thread on first use.  From the R thread itself this is a plain call. */
244
static void fuzz_run_on_r_thread(void (*fn)(void *), void *arg)
245
2.81k
{
246
2.81k
    if (!fuzz_r_thread.started) {
247
2
        static int atfork_registered = 0;
248
2
        if (!atfork_registered) {
249
2
            if (pthread_atfork(NULL, NULL, fuzz_r_thread_forget) != 0) {
250
0
                fprintf(stderr, "fuzz: cannot register the fork handler\n");
251
0
                abort();
252
0
            }
253
2
            atfork_registered = 1;
254
2
        }
255
256
2
        pthread_attr_t attr;
257
2
        if (pthread_attr_init(&attr) != 0 ||
258
2
            pthread_attr_setstacksize(&attr, FUZZ_R_STACK_SIZE) != 0 ||
259
2
            pthread_create(&fuzz_r_thread.thread, &attr,
260
2
                           fuzz_r_thread_main, NULL) != 0) {
261
0
            fprintf(stderr, "fuzz: cannot start the R thread\n");
262
0
            abort();
263
0
        }
264
2
        pthread_attr_destroy(&attr);
265
2
        fuzz_r_thread.started = 1;
266
2
    }
267
268
2.81k
    if (fuzz_on_r_thread()) {
269
0
        fn(arg);
270
0
        return;
271
0
    }
272
273
2.81k
    fuzz_job_t job = { fn, arg };
274
2.81k
    pthread_mutex_lock(&fuzz_r_thread.mutex);
275
2.81k
    fuzz_r_thread.job = &job;
276
2.81k
    pthread_cond_broadcast(&fuzz_r_thread.cond);
277
5.63k
    while (fuzz_r_thread.job != NULL)
278
2.81k
        pthread_cond_wait(&fuzz_r_thread.cond, &fuzz_r_thread.mutex);
279
2.81k
    pthread_mutex_unlock(&fuzz_r_thread.mutex);
280
2.81k
}
281
282
/*
283
 * Initialize R for fuzzing.  Call once from fuzz_target_init.
284
 *
285
 * Performs:
286
 *   1. Set R_HOME from binary location
287
 *   2. Cap the vector heap (must precede R's startup, which reads it)
288
 *   3. Initialize embedded R, without R's signal handlers
289
 *   4. Point R's C stack check at the R thread's stack
290
 *   5. Suppress warnings
291
 */
292
static void fuzz_init_r(void)
293
2
{
294
2
    if (!fuzz_on_r_thread()) {
295
0
        fprintf(stderr, "fuzz_init_r: must run on the R thread "
296
0
                        "(define fuzz_target_init, not LLVMFuzzerInitialize)\n");
297
0
        abort();
298
0
    }
299
300
2
    fuzz_set_r_home();
301
2
    fuzz_set_vsize_limit();
302
303
    /* GC consistency failures ("untraced old-to-new reference", a node
304
     * with a bad type) are memory corruption, not input errors.  R's
305
     * default is to report and carry on; this makes gc_error() fatal
306
     * (through R_Suicide, which instrumentation/0004 turns into abort)
307
     * so they surface as crashes at the collection that found them. */
308
2
    if (getenv("_R_GC_FAIL_ON_ERROR_") == NULL)
309
1
        setenv("_R_GC_FAIL_ON_ERROR_", "yes", 1);
310
311
    /* R's SIGSEGV/SIGILL/etc. handlers enter an interactive recovery
312
     * prompt instead of crashing, which would hang the fuzzer.  Telling
313
     * R not to install them (rather than resetting to SIG_DFL after
314
     * init) keeps the sanitizers' own handlers in place, so wild-pointer
315
     * faults still get full ASAN reports and clean deduplication. */
316
2
    R_SignalHandlers = 0;
317
318
    /* The two halves of Rf_initEmbeddedR, with the C stack check fixed
319
     * up in between.  On Linux Rf_initialize_R takes R_CStackStart from
320
     * __libc_stack_end, the main thread's stack, so from this thread
321
     * every R_CheckStack() while setup_Rmainloop loads base would see
322
     * terabytes of usage and R would abort ("unable to initialize the
323
     * JIT").  macOS never sets it when embedded, which hides this
324
     * locally.  Switch the check off for startup, then measure against
325
     * this thread with R's own 5% of headroom for handling the overflow
326
     * error: R_CheckStack() fires as an ordinary R error at
327
     * 0.95 * FUZZ_R_STACK_SIZE rather than the sanitizer catching a fault.
328
     * setup_Rmainloop itself would turn any limit above 100 MB into
329
     * "unlimited", hence setting it after. */
330
2
    char *r_argv[] = {"R", "--vanilla", "--no-echo", "--no-restore"};
331
2
    int r_argc = sizeof(r_argv) / sizeof(r_argv[0]);
332
2
    Rf_initialize_R(r_argc, r_argv);
333
2
    R_CStackStart = fuzz_r_thread.stack_top;
334
2
    R_CStackLimit = (uintptr_t)-1;
335
2
    R_Interactive = TRUE;   /* as Rf_initEmbeddedR */
336
2
    setup_Rmainloop();
337
2
    R_CStackLimit = (uintptr_t)(0.95 * FUZZ_R_STACK_SIZE);
338
339
2
    fuzz_suppress_warnings();
340
2
}
341
342
/*
343
 * Helper for protected evaluation via R_ToplevelExec.
344
 *
345
 * R API calls that touch R objects can longjmp on error.  Without a
346
 * top-level context, R's error handler crashes.  Wrapping calls in
347
 * R_ToplevelExec catches the longjmp cleanly.
348
 */
349
typedef struct {
350
    SEXP call;
351
    SEXP env;
352
} fuzz_eval_data_t;
353
354
static void fuzz_do_eval(void *data)
355
0
{
356
0
    fuzz_eval_data_t *ed = (fuzz_eval_data_t *)data;
357
0
    Rf_eval(ed->call, ed->env);
358
0
}
359
360
/* Evaluate malformed-input-heavy targets without printing each expected
361
 * R error. R_tryEvalSilent installs its own top-level error context. */
362
static void fuzz_eval_silent(SEXP call, SEXP env)
363
2.84k
{
364
2.84k
    int error = 0;
365
2.84k
    R_tryEvalSilent(call, env, &error);
366
2.84k
}
367
368
/*
369
 * Stage per-iteration inputs under a toplevel context.
370
 *
371
 * Rf_mkChar and Rf_allocVector can themselves signal the vector-limit
372
 * error.  Outside a toplevel context that error longjmps into a stack
373
 * frame that returned during Rf_initEmbeddedR -- undefined behaviour --
374
 * so input staging must be wrapped just like evaluation.  Both helpers
375
 * return FALSE when the allocation failed; skip the iteration then.
376
 */
377
typedef struct {
378
    SEXP vec;
379
    const char *str;
380
} fuzz_string_data_t;
381
382
static void fuzz_do_set_string(void *data)
383
2.80k
{
384
2.80k
    fuzz_string_data_t *sd = (fuzz_string_data_t *)data;
385
2.80k
    SET_STRING_ELT(sd->vec, 0, Rf_mkChar(sd->str));
386
2.80k
}
387
388
static Rboolean fuzz_set_string(SEXP vec, const char *str)
389
2.80k
{
390
2.80k
    fuzz_string_data_t sd = { vec, str };
391
2.80k
    return R_ToplevelExec(fuzz_do_set_string, &sd);
392
2.80k
}
393
394
/*
395
 * Parse and evaluate one R function literal, once, at initialization.
396
 *
397
 * Harnesses whose calls need named arguments, on.exit(close(con)) or a
398
 * few R-level steps around the entry point express each slot as an R
399
 * closure and call it with Rf_lang*; that keeps the C side free of
400
 * argument plumbing.  The source is harness-authored (never fuzz
401
 * input), so a parse or evaluation failure is a harness bug: abort.
402
 */
403
static SEXP fuzz_make_wrapper(const char *source)
404
36
{
405
36
    ParseStatus status;
406
36
    SEXP text, parsed, wrapper;
407
36
    Rf_protect(text = Rf_mkString(source));
408
36
    Rf_protect(parsed = R_ParseVector(text, -1, &status, R_NilValue));
409
36
    if (status != PARSE_OK || XLENGTH(parsed) != 1)
410
0
        abort();
411
412
36
    wrapper = Rf_eval(VECTOR_ELT(parsed, 0), R_BaseEnv);
413
36
    Rf_unprotect(2);
414
36
    return wrapper;
415
36
}
416
417
/*
418
 * fuzz_set_string with an explicit encoding mark.
419
 *
420
 * R does not validate the mark (Encoding<- lets users set it on any
421
 * bytes), so handing the same payload over as UTF-8 or latin1 reaches
422
 * the multibyte branches and their handling of invalid sequences
423
 * without touching the process locale.
424
 */
425
typedef struct {
426
    SEXP vec;
427
    const char *str;
428
    cetype_t ce;
429
} fuzz_ce_string_data_t;
430
431
static void fuzz_do_set_string_ce(void *data)
432
0
{
433
0
    fuzz_ce_string_data_t *sd = (fuzz_ce_string_data_t *)data;
434
0
    SET_STRING_ELT(sd->vec, 0, Rf_mkCharCE(sd->str, sd->ce));
435
0
}
436
437
static Rboolean fuzz_set_string_ce(SEXP vec, const char *str, cetype_t ce)
438
0
{
439
0
    fuzz_ce_string_data_t sd = { vec, str, ce };
440
0
    return R_ToplevelExec(fuzz_do_set_string_ce, &sd);
441
0
}
442
443
typedef struct {
444
    SEXP call;
445
    const uint8_t *data;
446
    size_t size;
447
} fuzz_raw_data_t;
448
449
static void fuzz_do_set_raw(void *data)
450
0
{
451
0
    fuzz_raw_data_t *rd = (fuzz_raw_data_t *)data;
452
0
453
0
    /* The raw vector is reachable from the (protected) call as soon as
454
0
     * SETCADR runs, and nothing allocates before the memcpy completes,
455
0
     * so no extra protection is needed. */
456
0
    SEXP raw = Rf_allocVector(RAWSXP, (R_xlen_t)rd->size);
457
0
    SETCADR(rd->call, raw);
458
0
    memcpy(RAW(raw), rd->data, rd->size);
459
0
}
460
461
static Rboolean fuzz_set_raw_arg(SEXP call, const uint8_t *data, size_t size)
462
0
{
463
0
    fuzz_raw_data_t rd = { call, data, size };
464
0
    return R_ToplevelExec(fuzz_do_set_raw, &rd);
465
0
}
466
467
/*
468
 * Stage the input in a scratch file.
469
 *
470
 * Some R entry points only take a path (load() of a pre-1.4 save file,
471
 * unzip(), file(encoding=)), so those targets rewrite one per-process
472
 * file each iteration instead of handing R a raw vector.  The file lives
473
 * in the system temp directory, which OSS-Fuzz runners allow writes to,
474
 * and is created once in fuzz_target_init so fuzzing never pays a
475
 * mkstemp per input.  Returns a malloc'd path, or NULL on failure.
476
 */
477
static char *fuzz_scratch_file(const char *name)
478
0
{
479
0
    const char *tmpdir = getenv("TMPDIR");
480
0
    if (tmpdir == NULL || *tmpdir == '\0')
481
0
        tmpdir = "/tmp";
482
0
483
0
    char *path = malloc(PATH_MAX);
484
0
    if (path == NULL)
485
0
        return NULL;
486
0
    snprintf(path, PATH_MAX, "%s/fuzz-%s-XXXXXX", tmpdir, name);
487
0
488
0
    int fd = mkstemp(path);
489
0
    if (fd < 0) {
490
0
        free(path);
491
0
        return NULL;
492
0
    }
493
0
    close(fd);
494
0
    return path;
495
0
}
496
497
/* Replace the scratch file's contents.  Returns FALSE on any I/O error so
498
 * the iteration can be skipped rather than fuzzing a stale file. */
499
static Rboolean fuzz_write_scratch(const char *path, const uint8_t *data,
500
                                   size_t size)
501
0
{
502
0
    FILE *fp = fopen(path, "wb");
503
0
    if (fp == NULL)
504
0
        return FALSE;
505
0
506
0
    size_t written = size > 0 ? fwrite(data, 1, size, fp) : 0;
507
0
    int rc = fclose(fp);
508
0
    return written == size && rc == 0;
509
0
}
510
511
/*
512
 * Guard against TRE's bounded-repeat blowup.
513
 *
514
 * TRE compiles bounded repeats by duplicating the pattern AST, so nested
515
 * counted repeats multiply: a 36-byte pattern like
516
 * (?:a{2,101,})(?:a{2,101,}){100}{100} expands to ~10^8 nodes and >8GB of
517
 * allocations at compile time.  Those allocations are malloc, not R's
518
 * vector heap, so R_MAX_VSIZE cannot bound them; reject the pattern
519
 * before it reaches R instead.
520
 *
521
 * The product of all repeat bounds is a deliberate over-estimate --
522
 * sequential (non-nested) repeats add rather than multiply -- trading a
523
 * little coverage of repeat-heavy patterns for a hard bound on compile
524
 * cost.
525
 */
526
#define FUZZ_MAX_REPEAT_PRODUCT 1000000.0
527
528
static int fuzz_repeat_product_excessive(const char *pattern)
529
0
{
530
0
    double product = 1.0;
531
0
532
0
    for (const char *p = pattern; *p != '\0'; p++) {
533
0
        if (*p != '{')
534
0
            continue;
535
0
536
0
        /* The largest number inside the braces bounds this repeat. */
537
0
        unsigned long bound = 0, cur = 0;
538
0
        int counted = 0;
539
0
        const char *q = p + 1;
540
0
        for (; *q != '\0' && *q != '}'; q++) {
541
0
            if (*q >= '0' && *q <= '9') {
542
0
                cur = cur * 10 + (unsigned long)(*q - '0');
543
0
                if (cur > 10000000)
544
0
                    cur = 10000000;  /* saturate; already over any budget */
545
0
                counted = 1;
546
0
            } else if (*q == ',') {
547
0
                if (cur > bound)
548
0
                    bound = cur;
549
0
                cur = 0;
550
0
            } else {
551
0
                counted = 0;  /* not a counted repeat, e.g. "{a}" */
552
0
                break;
553
0
            }
554
0
        }
555
0
        if (!counted || *q != '}')
556
0
            continue;
557
0
558
0
        if (cur > bound)
559
0
            bound = cur;
560
0
        if (bound > 1)
561
0
            product *= (double)bound;
562
0
        if (product > FUZZ_MAX_REPEAT_PRODUCT)
563
0
            return 1;
564
0
565
0
        p = q;
566
0
    }
567
0
568
0
    return 0;
569
0
}
570
571
/*
572
 * libFuzzer entry points.
573
 *
574
 * A harness defines fuzz_target_init and fuzz_target_run with the
575
 * LLVMFuzzerInitialize / LLVMFuzzerTestOneInput signatures; these run
576
 * them on the R thread, one condition-variable handoff each way per input.
577
 */
578
int fuzz_target_init(int *argc, char ***argv);
579
int fuzz_target_run(const uint8_t *data, size_t size);
580
581
typedef struct {
582
    int *argc;
583
    char ***argv;
584
    int result;
585
} fuzz_init_job_t;
586
587
static void fuzz_do_target_init(void *data)
588
2
{
589
2
    fuzz_init_job_t *job = data;
590
2
    job->result = fuzz_target_init(job->argc, job->argv);
591
2
}
592
593
typedef struct {
594
    const uint8_t *data;
595
    size_t size;
596
    int result;
597
} fuzz_run_job_t;
598
599
static void fuzz_do_target_run(void *data)
600
2.81k
{
601
2.81k
    fuzz_run_job_t *job = data;
602
2.81k
    job->result = fuzz_target_run(job->data, job->size);
603
2.81k
}
604
605
int LLVMFuzzerInitialize(int *argc, char ***argv)
606
2
{
607
2
    fuzz_init_job_t job = { argc, argv, 0 };
608
2
    fuzz_run_on_r_thread(fuzz_do_target_init, &job);
609
2
    return job.result;
610
2
}
611
612
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
613
2.81k
{
614
2.81k
    fuzz_run_job_t job = { data, size, 0 };
615
2.81k
    fuzz_run_on_r_thread(fuzz_do_target_run, &job);
616
2.81k
    return job.result;
617
2.81k
}
618
619
#endif /* FUZZ_COMMON_H */