/src/r-oss-fuzz/harnesses/parse.c
Line | Count | Source |
1 | | /* |
2 | | * libFuzzer harness for R's parser. |
3 | | * |
4 | | * Feeds input to the lexer and parser without evaluating the result. |
5 | | * Targets parser bugs: crashes, OOB reads, infinite loops, stack |
6 | | * overflows in deeply nested input. |
7 | | * |
8 | | * The first input byte selects the route; the rest is the source text. |
9 | | * Slot 0 is the bare R_ParseVector of the original harness. The others |
10 | | * go through R-level parse() so the srcref machinery runs, then push the |
11 | | * parsed expressions through deparse() and back, str2lang(), and the |
12 | | * deparse control options -- deparse.c is the parser's inverse and had |
13 | | * only ever been reached incidentally. Nothing here evaluates what was |
14 | | * parsed. |
15 | | * |
16 | | * Adapted from r-afl's parse harness. |
17 | | */ |
18 | | |
19 | | #include <stdint.h> |
20 | | #include <string.h> |
21 | | |
22 | | #include "common.h" |
23 | | |
24 | 53.8k | #define FUZZ_MAX_INPUT (1024 * 16) |
25 | 26.9k | #define N_CALLS 7 |
26 | | |
27 | | /* Slot 0 is handled in C; sources[0] is a placeholder so slot numbers |
28 | | * line up with the table. */ |
29 | | static const char *const sources[N_CALLS] = { |
30 | | NULL, |
31 | | "function(x) { e <- parse(text = x, keep.source = TRUE); " |
32 | | "getParseData(e) }", |
33 | | "function(x) { e <- parse(text = x, keep.source = FALSE); " |
34 | | "d <- vapply(as.list(e), function(ex) " |
35 | | "paste(deparse(ex, control = \"all\"), collapse = \"\\n\"), \"\"); " |
36 | | "identical(e, parse(text = d, keep.source = FALSE)) }", |
37 | | "function(x) str2lang(x)", |
38 | | "function(x) str2expression(x)", |
39 | | "function(x) deparse(parse(text = x, keep.source = FALSE), " |
40 | | "width.cutoff = 20L, backtick = TRUE, control = c(\"keepInteger\", " |
41 | | "\"quoteExpressions\", \"showAttributes\", \"digits17\"))", |
42 | | "function(x) deparse(parse(text = x, keep.source = TRUE), " |
43 | | "control = c(\"useSource\", \"keepNA\", \"niceNames\"))", |
44 | | }; |
45 | | |
46 | | static SEXP x_str; |
47 | | static SEXP calls[N_CALLS]; |
48 | | |
49 | | typedef struct { |
50 | | SEXP str; |
51 | | ParseStatus status; |
52 | | } parse_data_t; |
53 | | |
54 | | static void do_parse(void *data) |
55 | 970 | { |
56 | 970 | parse_data_t *pd = (parse_data_t *)data; |
57 | 970 | SEXP parsed; |
58 | 970 | Rf_protect(parsed = R_ParseVector(pd->str, -1, &pd->status, R_NilValue)); |
59 | 970 | Rf_unprotect(1); |
60 | 970 | } |
61 | | |
62 | | static void run_slot(int selected) |
63 | 5.62k | { |
64 | 5.62k | if (selected == 0) { |
65 | 970 | parse_data_t pd; |
66 | 970 | pd.status = PARSE_NULL; |
67 | 970 | pd.str = x_str; |
68 | 970 | R_ToplevelExec(do_parse, &pd); |
69 | 970 | return; |
70 | 970 | } |
71 | | |
72 | 4.65k | fuzz_eval_silent(calls[selected], R_GlobalEnv); |
73 | 4.65k | } |
74 | | |
75 | | int LLVMFuzzerInitialize(int *argc, char ***argv) |
76 | 2 | { |
77 | 2 | fuzz_init_r(); |
78 | | |
79 | | /* Enable pipe bind (=>) syntax so the fuzzer exercises that path. */ |
80 | 2 | setenv("_R_USE_PIPEBIND_", "true", 0); |
81 | | |
82 | | /* Pre-allocate reusable string container. Each iteration just |
83 | | * swaps the CHARSXP inside via SET_STRING_ELT. */ |
84 | 2 | Rf_protect(x_str = Rf_allocVector(STRSXP, 1)); |
85 | | |
86 | 14 | for (int i = 1; i < N_CALLS; i++) { |
87 | 12 | SEXP wrapper; |
88 | 12 | Rf_protect(wrapper = fuzz_make_wrapper(sources[i])); |
89 | 12 | Rf_protect(calls[i] = Rf_lang2(wrapper, x_str)); |
90 | 12 | } |
91 | | |
92 | | /* Warmup: prime the parser's internal state so early iterations |
93 | | * don't diverge from later ones. */ |
94 | 2 | { |
95 | 2 | static const char *warmup[] = { |
96 | 2 | "1+1", |
97 | 2 | "x <- function(a, b) a + b", |
98 | 2 | "if (TRUE) 'yes' else 'no'", |
99 | 2 | "for (i in 1:10) i", |
100 | 2 | "list(a=1, b=\"hello\", c=NULL, d=NA)", |
101 | 2 | "\\(x) x + 1", |
102 | 2 | "1:10 |> rev()", |
103 | 2 | NULL |
104 | 2 | }; |
105 | 16 | for (int i = 0; warmup[i] != NULL; i++) { |
106 | 14 | SET_STRING_ELT(x_str, 0, Rf_mkChar(warmup[i])); |
107 | 112 | for (int slot = 0; slot < N_CALLS; slot++) |
108 | 98 | run_slot(slot); |
109 | 14 | } |
110 | 2 | R_gc(); |
111 | 2 | } |
112 | | |
113 | 2 | return 0; |
114 | 2 | } |
115 | | |
116 | | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) |
117 | 26.9k | { |
118 | 26.9k | if (size < 2 || size > FUZZ_MAX_INPUT || memchr(data + 1, '\0', size - 1)) |
119 | 97 | return 0; |
120 | | |
121 | | /* Null-terminate the input for R's string API. */ |
122 | 26.8k | char buf[FUZZ_MAX_INPUT]; |
123 | 26.8k | memcpy(buf, data + 1, size - 1); |
124 | 26.8k | buf[size - 1] = '\0'; |
125 | | |
126 | 26.8k | if (!fuzz_set_string(x_str, buf)) |
127 | 0 | return 0; |
128 | | |
129 | 26.8k | run_slot(data[0] % N_CALLS); |
130 | 26.8k | return 0; |
131 | 26.8k | } |