/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 | 1.81k | for (;;) { |
209 | 3.63k | while (fuzz_r_thread.job == NULL) |
210 | 1.81k | pthread_cond_wait(&fuzz_r_thread.cond, &fuzz_r_thread.mutex); |
211 | 1.81k | fuzz_job_t *job = fuzz_r_thread.job; |
212 | 1.81k | pthread_mutex_unlock(&fuzz_r_thread.mutex); |
213 | | |
214 | 1.81k | job->fn(job->arg); |
215 | | |
216 | 1.81k | pthread_mutex_lock(&fuzz_r_thread.mutex); |
217 | 1.81k | fuzz_r_thread.job = NULL; |
218 | 1.81k | pthread_cond_broadcast(&fuzz_r_thread.cond); |
219 | 1.81k | } |
220 | 2 | return NULL; |
221 | 2 | } |
222 | | |
223 | | static int fuzz_on_r_thread(void) |
224 | 1.81k | { |
225 | 1.81k | return fuzz_r_thread.started && |
226 | 1.81k | pthread_equal(pthread_self(), fuzz_r_thread.thread); |
227 | 1.81k | } |
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 | 1.81k | { |
246 | 1.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 | 1.81k | if (fuzz_on_r_thread()) { |
269 | 0 | fn(arg); |
270 | 0 | return; |
271 | 0 | } |
272 | | |
273 | 1.81k | fuzz_job_t job = { fn, arg }; |
274 | 1.81k | pthread_mutex_lock(&fuzz_r_thread.mutex); |
275 | 1.81k | fuzz_r_thread.job = &job; |
276 | 1.81k | pthread_cond_broadcast(&fuzz_r_thread.cond); |
277 | 3.63k | while (fuzz_r_thread.job != NULL) |
278 | 1.81k | pthread_cond_wait(&fuzz_r_thread.cond, &fuzz_r_thread.mutex); |
279 | 1.81k | pthread_mutex_unlock(&fuzz_r_thread.mutex); |
280 | 1.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 | 1.80k | { |
364 | 1.80k | int error = 0; |
365 | 1.80k | R_tryEvalSilent(call, env, &error); |
366 | 1.80k | } |
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 | 0 | { |
384 | 0 | fuzz_string_data_t *sd = (fuzz_string_data_t *)data; |
385 | 0 | SET_STRING_ELT(sd->vec, 0, Rf_mkChar(sd->str)); |
386 | 0 | } |
387 | | |
388 | | static Rboolean fuzz_set_string(SEXP vec, const char *str) |
389 | 0 | { |
390 | 0 | fuzz_string_data_t sd = { vec, str }; |
391 | 0 | return R_ToplevelExec(fuzz_do_set_string, &sd); |
392 | 0 | } |
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 | 8 | { |
405 | 8 | ParseStatus status; |
406 | 8 | SEXP text, parsed, wrapper; |
407 | 8 | Rf_protect(text = Rf_mkString(source)); |
408 | 8 | Rf_protect(parsed = R_ParseVector(text, -1, &status, R_NilValue)); |
409 | 8 | if (status != PARSE_OK || XLENGTH(parsed) != 1) |
410 | 0 | abort(); |
411 | | |
412 | 8 | wrapper = Rf_eval(VECTOR_ELT(parsed, 0), R_BaseEnv); |
413 | 8 | Rf_unprotect(2); |
414 | 8 | return wrapper; |
415 | 8 | } |
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 | 2 | { |
479 | 2 | const char *tmpdir = getenv("TMPDIR"); |
480 | 2 | if (tmpdir == NULL || *tmpdir == '\0') |
481 | 2 | tmpdir = "/tmp"; |
482 | | |
483 | 2 | char *path = malloc(PATH_MAX); |
484 | 2 | if (path == NULL) |
485 | 0 | return NULL; |
486 | 2 | snprintf(path, PATH_MAX, "%s/fuzz-%s-XXXXXX", tmpdir, name); |
487 | | |
488 | 2 | int fd = mkstemp(path); |
489 | 2 | if (fd < 0) { |
490 | 0 | free(path); |
491 | 0 | return NULL; |
492 | 0 | } |
493 | 2 | close(fd); |
494 | 2 | return path; |
495 | 2 | } |
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 | 1.80k | { |
502 | 1.80k | FILE *fp = fopen(path, "wb"); |
503 | 1.80k | if (fp == NULL) |
504 | 0 | return FALSE; |
505 | | |
506 | 1.80k | size_t written = size > 0 ? fwrite(data, 1, size, fp) : 0; |
507 | 1.80k | int rc = fclose(fp); |
508 | 1.80k | return written == size && rc == 0; |
509 | 1.80k | } |
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 | 1.81k | { |
601 | 1.81k | fuzz_run_job_t *job = data; |
602 | 1.81k | job->result = fuzz_target_run(job->data, job->size); |
603 | 1.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 | 1.81k | { |
614 | 1.81k | fuzz_run_job_t job = { data, size, 0 }; |
615 | 1.81k | fuzz_run_on_r_thread(fuzz_do_target_run, &job); |
616 | 1.81k | return job.result; |
617 | 1.81k | } |
618 | | |
619 | | #endif /* FUZZ_COMMON_H */ |