/src/r-oss-fuzz/harnesses/parse.c
Line | Count | Source |
1 | | /* |
2 | | * libFuzzer harness for R's parser. |
3 | | * |
4 | | * Feeds input to R_ParseVector -- exercising the lexer and parser |
5 | | * without evaluating the result. Targets parser bugs: crashes, |
6 | | * OOB reads, infinite loops, stack overflows in deeply nested input. |
7 | | * |
8 | | * Adapted from r-afl's parse harness. |
9 | | */ |
10 | | |
11 | | #include <stdint.h> |
12 | | #include <string.h> |
13 | | |
14 | | #include "common.h" |
15 | | |
16 | 10.8k | #define FUZZ_MAX_INPUT (1024 * 16) |
17 | | |
18 | | static SEXP x_str; |
19 | | |
20 | | typedef struct { |
21 | | SEXP str; |
22 | | ParseStatus status; |
23 | | } parse_data_t; |
24 | | |
25 | | static void do_parse(void *data) |
26 | 6.50k | { |
27 | 6.50k | parse_data_t *pd = (parse_data_t *)data; |
28 | 6.50k | SEXP parsed; |
29 | 6.50k | Rf_protect(parsed = R_ParseVector(pd->str, -1, &pd->status, R_NilValue)); |
30 | 6.50k | Rf_unprotect(1); |
31 | 6.50k | } |
32 | | |
33 | | int LLVMFuzzerInitialize(int *argc, char ***argv) |
34 | 2 | { |
35 | 2 | fuzz_init_r(); |
36 | | |
37 | | /* Enable pipe bind (=>) syntax so the fuzzer exercises that path. */ |
38 | 2 | setenv("_R_USE_PIPEBIND_", "true", 0); |
39 | | |
40 | | /* Pre-allocate reusable string container. Each iteration just |
41 | | * swaps the CHARSXP inside via SET_STRING_ELT. */ |
42 | 2 | Rf_protect(x_str = Rf_allocVector(STRSXP, 1)); |
43 | | |
44 | | /* Warmup: prime the parser's internal state so early iterations |
45 | | * don't diverge from later ones. */ |
46 | 2 | { |
47 | 2 | static const char *warmup[] = { |
48 | 2 | "1+1", |
49 | 2 | "x <- function(a, b) a + b", |
50 | 2 | "if (TRUE) 'yes' else 'no'", |
51 | 2 | "for (i in 1:10) i", |
52 | 2 | "list(a=1, b=\"hello\", c=NULL, d=NA)", |
53 | 2 | "\\(x) x + 1", |
54 | 2 | "1:10 |> rev()", |
55 | 2 | NULL |
56 | 2 | }; |
57 | 2 | parse_data_t pd; |
58 | 16 | for (int i = 0; warmup[i] != NULL; i++) { |
59 | 14 | SET_STRING_ELT(x_str, 0, Rf_mkChar(warmup[i])); |
60 | 14 | pd.str = x_str; |
61 | 14 | pd.status = PARSE_NULL; |
62 | 14 | R_ToplevelExec(do_parse, &pd); |
63 | 14 | } |
64 | 2 | R_gc(); |
65 | 2 | } |
66 | | |
67 | 2 | return 0; |
68 | 2 | } |
69 | | |
70 | | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) |
71 | 10.8k | { |
72 | 10.8k | if (size == 0 || size > FUZZ_MAX_INPUT) |
73 | 16 | return 0; |
74 | | |
75 | | /* Null-terminate the input for R's string API. */ |
76 | 10.8k | char buf[FUZZ_MAX_INPUT + 1]; |
77 | 10.8k | memcpy(buf, data, size); |
78 | 10.8k | buf[size] = '\0'; |
79 | | |
80 | 10.8k | if (!fuzz_set_string(x_str, buf)) |
81 | 0 | return 0; |
82 | | |
83 | 10.8k | parse_data_t pd; |
84 | 10.8k | pd.status = PARSE_NULL; |
85 | 10.8k | pd.str = x_str; |
86 | 10.8k | R_ToplevelExec(do_parse, &pd); |
87 | | |
88 | 10.8k | return 0; |
89 | 10.8k | } |