/src/r-oss-fuzz/harnesses/readtable.c
Line | Count | Source |
1 | | /* |
2 | | * libFuzzer harness for read.table() and friends -- the tabular readers |
3 | | * most untrusted CSV enters R through. |
4 | | * |
5 | | * The scan target covers scan.c, but read.table() wraps it in C code of |
6 | | * its own in src/library/utils/src/io.c that scan never reaches: |
7 | | * readtablehead (the header/line pre-reader that tracks quotes and |
8 | | * comments across physical lines), countfields (count.fields), and |
9 | | * writetable (the quoting and encoding of write.table()). Each input is |
10 | | * handed to one of those paths as the text of a file, under a |
11 | | * configuration chosen by the first byte; the readers that succeed hand |
12 | | * their data frame to write.table() so the writer sees real, fuzz-shaped |
13 | | * columns rather than only hand-built ones. |
14 | | * |
15 | | * Writers go to nullfile(): the C code runs in full, nothing hits the |
16 | | * fuzzer's stdout. |
17 | | */ |
18 | | |
19 | | #include <stdint.h> |
20 | | #include <string.h> |
21 | | |
22 | | #include "common.h" |
23 | | |
24 | 693 | #define FUZZ_MAX_INPUT (1024 * 64) |
25 | 414 | #define N_CALLS 20 |
26 | | |
27 | | /* Slot layout: |
28 | | * [0,12) read.table() and its csv/csv2/delim presets under assorted |
29 | | * separators, quoting, comments, NA strings, fill/flush, |
30 | | * escapes, encodings, colClasses and row.names handling |
31 | | * [12,15) count.fields() on a textConnection of the input |
32 | | * [15,20) readers chained into write.table()/write.csv(): quoting |
33 | | * methods, factor levels, NA and decimal marks, line endings, |
34 | | * and a re-encoding fileEncoding |
35 | | * Every connection a slot opens is closed before it returns: R caps the |
36 | | * connection table at 128 slots. */ |
37 | | static const char *const sources[N_CALLS] = { |
38 | | "function(x) read.table(text = x)", |
39 | | "function(x) read.csv(text = x)", |
40 | | "function(x) read.csv2(text = x)", |
41 | | "function(x) read.delim(text = x)", |
42 | | "function(x) read.table(text = x, header = TRUE, sep = \",\", " |
43 | | "quote = \"\\\"'\", comment.char = \"#\", fill = TRUE)", |
44 | | "function(x) read.table(text = x, sep = \",\", allowEscapes = TRUE, " |
45 | | "strip.white = TRUE, blank.lines.skip = FALSE)", |
46 | | "function(x) read.table(text = x, header = TRUE, row.names = 1, " |
47 | | "check.names = TRUE)", |
48 | | "function(x) read.table(text = x, colClasses = \"character\", " |
49 | | "nrows = 100, skip = 1, fill = TRUE)", |
50 | | "function(x) read.table(text = x, header = TRUE, " |
51 | | "na.strings = c(\"NA\", \"\", \"-\", \"?\"), stringsAsFactors = TRUE)", |
52 | | "function(x) read.table(text = x, flush = TRUE, numerals = \"no.loss\", " |
53 | | "dec = \",\")", |
54 | | "function(x) read.csv(text = x, encoding = \"UTF-8\", skipNul = TRUE)", |
55 | | "function(x) read.csv(text = x, encoding = \"latin1\", header = FALSE, " |
56 | | "col.names = paste0(\"V\", 1:3), fill = TRUE)", |
57 | | "function(x) { con <- textConnection(x); on.exit(close(con)); " |
58 | | "count.fields(con, sep = \",\", quote = \"\\\"\", comment.char = \"#\") }", |
59 | | "function(x) { con <- textConnection(x); on.exit(close(con)); " |
60 | | "count.fields(con, sep = \"\", blank.lines.skip = FALSE, skip = 1) }", |
61 | | "function(x) { con <- textConnection(x); on.exit(close(con)); " |
62 | | "count.fields(con, sep = \";\", quote = \"'\", comment.char = \"\") }", |
63 | | "function(x) { d <- read.csv(text = x, stringsAsFactors = TRUE); " |
64 | | "write.table(d, nullfile(), sep = \",\", qmethod = \"double\") }", |
65 | | "function(x) { d <- read.table(text = x, fill = TRUE); " |
66 | | "write.csv(d, nullfile(), row.names = FALSE, na = \"\") }", |
67 | | "function(x) { d <- read.csv2(text = x); " |
68 | | "write.table(d, nullfile(), quote = FALSE, dec = \",\", eol = \"\\r\\n\", " |
69 | | "col.names = NA) }", |
70 | | "function(x) { d <- read.delim(text = x); " |
71 | | "write.table(d, nullfile(), qmethod = \"escape\", quote = seq_along(d)) }", |
72 | | "function(x) { d <- read.csv(text = x, encoding = \"UTF-8\"); " |
73 | | "write.csv(d, nullfile(), fileEncoding = \"UTF-16LE\") }", |
74 | | }; |
75 | | |
76 | | static SEXP x_text; |
77 | | static SEXP calls[N_CALLS]; |
78 | | |
79 | | int fuzz_target_init(int *argc, char ***argv) |
80 | 2 | { |
81 | 2 | (void)argc; |
82 | 2 | (void)argv; |
83 | 2 | fuzz_init_r(); |
84 | | |
85 | | /* Reusable text container -- each iteration swaps its CHARSXP. */ |
86 | 2 | Rf_protect(x_text = Rf_allocVector(STRSXP, 1)); |
87 | | |
88 | 42 | for (int i = 0; i < N_CALLS; i++) { |
89 | 40 | SEXP wrapper; |
90 | 40 | Rf_protect(wrapper = fuzz_make_wrapper(sources[i])); |
91 | 40 | Rf_protect(calls[i] = Rf_lang2(wrapper, x_text)); |
92 | 40 | } |
93 | | |
94 | | /* Warmup: load utils and prime read.table's method dispatch. */ |
95 | 2 | SET_STRING_ELT(x_text, 0, Rf_mkChar("a,b,c\n1,2,x\n4,NA,\"y\"\n")); |
96 | 42 | for (int i = 0; i < N_CALLS; i++) |
97 | 40 | fuzz_eval_silent(calls[i], R_GlobalEnv); |
98 | | |
99 | 2 | return 0; |
100 | 2 | } |
101 | | |
102 | | int fuzz_target_run(const uint8_t *data, size_t size) |
103 | 347 | { |
104 | 347 | if (size < 2 || size > FUZZ_MAX_INPUT || memchr(data + 1, '\0', size - 1)) |
105 | 17 | return 0; |
106 | | |
107 | 330 | char buffer[FUZZ_MAX_INPUT]; |
108 | 330 | memcpy(buffer, data + 1, size - 1); |
109 | 330 | buffer[size - 1] = '\0'; |
110 | 330 | if (!fuzz_set_string(x_text, buffer)) |
111 | 0 | return 0; |
112 | | |
113 | 330 | fuzz_eval_silent(calls[data[0] % N_CALLS], R_GlobalEnv); |
114 | 330 | return 0; |
115 | 330 | } |