/src/r-oss-fuzz/harnesses/scan.c
Line | Count | Source |
1 | | /* |
2 | | * libFuzzer harness for R's scan / delimited-text parser. |
3 | | * |
4 | | * Feeds fuzzed input to scan() as text under one of several |
5 | | * configurations, exercising the C-level parsing state machine in |
6 | | * src/main/scan.c: field separation, quoting, escapes, comment chars, |
7 | | * NA-string matching, and the per-type converters (scanVector for an |
8 | | * atomic `what`, scanFrame for a list `what`). R's equivalent of a |
9 | | * csv.reader target. |
10 | | * |
11 | | * The first input byte selects the configuration; the rest is the text. |
12 | | * Running every configuration on every input stopped scaling once there |
13 | | * were more than a handful, and one-per-input keeps libFuzzer's |
14 | | * coverage signal attributable. |
15 | | * |
16 | | * Adapted from r-afl's scan harness. |
17 | | */ |
18 | | |
19 | | #include <stdint.h> |
20 | | #include <string.h> |
21 | | |
22 | | #include "common.h" |
23 | | |
24 | 5.47k | #define FUZZ_MAX_INPUT (1024 * 64) |
25 | 2.80k | #define N_CALLS 18 |
26 | | |
27 | | /* Each configuration is an R closure over the text so that named |
28 | | * arguments need no Rf_lang* plumbing. The first four match the |
29 | | * original harness; the rest reach converters and options it never |
30 | | * exercised (integer/complex/raw/logical readers, scanFrame, escapes, |
31 | | * quoting, NA strings, decimal marks, comments, fill, skip). */ |
32 | | static const char *const sources[N_CALLS] = { |
33 | | "function(x) scan(text = x, what = \"\", quiet = TRUE)", |
34 | | "function(x) scan(text = x, what = 0, quiet = TRUE)", |
35 | | "function(x) scan(text = x, what = \"\", sep = \",\", quiet = TRUE)", |
36 | | "function(x) scan(text = x, what = \"\", sep = \"\\t\", quiet = TRUE)", |
37 | | "function(x) scan(text = x, what = integer(), quiet = TRUE)", |
38 | | "function(x) scan(text = x, what = complex(), quiet = TRUE)", |
39 | | "function(x) scan(text = x, what = raw(), quiet = TRUE)", |
40 | | "function(x) scan(text = x, what = logical(), quiet = TRUE)", |
41 | | "function(x) scan(text = x, what = list(\"\", 0, \"\"), sep = \",\", " |
42 | | "quiet = TRUE)", |
43 | | "function(x) scan(text = x, what = \"\", allowEscapes = TRUE, quiet = TRUE)", |
44 | | "function(x) scan(text = x, what = \"\", sep = \",\", quote = \"\\\"'\", " |
45 | | "quiet = TRUE)", |
46 | | "function(x) scan(text = x, what = 0, na.strings = c(\"NA\", \"-\"), " |
47 | | "quiet = TRUE)", |
48 | | "function(x) scan(text = x, what = 0, dec = \",\", quiet = TRUE)", |
49 | | "function(x) scan(text = x, what = \"\", comment.char = \"#\", quiet = TRUE)", |
50 | | "function(x) scan(text = x, what = \"\", sep = \",\", strip.white = TRUE, " |
51 | | "quiet = TRUE)", |
52 | | "function(x) scan(text = x, what = list(\"\", 0, \"\"), sep = \",\", " |
53 | | "fill = TRUE, multi.line = FALSE, quiet = TRUE)", |
54 | | "function(x) scan(text = x, what = \"\", skip = 1, nlines = 4, quiet = TRUE)", |
55 | | "function(x) scan(text = x, what = \"\", sep = \",\", " |
56 | | "blank.lines.skip = FALSE, quiet = TRUE)", |
57 | | }; |
58 | | |
59 | | static SEXP x_text; |
60 | | static SEXP calls[N_CALLS]; |
61 | | |
62 | | int LLVMFuzzerInitialize(int *argc, char ***argv) |
63 | 2 | { |
64 | 2 | (void)argc; |
65 | 2 | (void)argv; |
66 | 2 | fuzz_init_r(); |
67 | | |
68 | | /* Reusable text container -- each iteration swaps its CHARSXP. */ |
69 | 2 | Rf_protect(x_text = Rf_allocVector(STRSXP, 1)); |
70 | | |
71 | 38 | for (int i = 0; i < N_CALLS; i++) { |
72 | 36 | SEXP wrapper; |
73 | 36 | Rf_protect(wrapper = fuzz_make_wrapper(sources[i])); |
74 | 36 | Rf_protect(calls[i] = Rf_lang2(wrapper, x_text)); |
75 | 36 | } |
76 | | |
77 | | /* Warmup: prime scan's type-dispatch and locale state. */ |
78 | 2 | SET_STRING_ELT(x_text, 0, Rf_mkChar("1,2,3\n4,5,6\n")); |
79 | 38 | for (int i = 0; i < N_CALLS; i++) |
80 | 36 | fuzz_eval_silent(calls[i], R_GlobalEnv); |
81 | | |
82 | 2 | return 0; |
83 | 2 | } |
84 | | |
85 | | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) |
86 | 2.74k | { |
87 | 2.74k | if (size < 2 || size > FUZZ_MAX_INPUT || memchr(data + 1, '\0', size - 1)) |
88 | 13 | return 0; |
89 | | |
90 | 2.72k | char buffer[FUZZ_MAX_INPUT]; |
91 | 2.72k | memcpy(buffer, data + 1, size - 1); |
92 | 2.72k | buffer[size - 1] = '\0'; |
93 | 2.72k | if (!fuzz_set_string(x_text, buffer)) |
94 | 0 | return 0; |
95 | | |
96 | 2.72k | fuzz_eval_silent(calls[data[0] % N_CALLS], R_GlobalEnv); |
97 | 2.72k | return 0; |
98 | 2.72k | } |