/src/r-oss-fuzz/harnesses/connections.c
Line | Count | Source |
1 | | /* |
2 | | * Exercise R's connection layer (src/main/connections.c) as a reader of |
3 | | * untrusted files. |
4 | | * |
5 | | * memDecompress, readBin and readLines(textConnection) already fuzz their |
6 | | * narrow entry points; what they never reach is the machinery around |
7 | | * them: the encoding= re-encoding layer (dummy_fgetc), the compressed-file |
8 | | * readers (gzfile/bzfile/xzfile/zstdfile and gzcon), rawConnection, the |
9 | | * line buffering behind readLines/readChar/scan, seek, pushBack, and the |
10 | | * write side (writeLines/writeChar/rawConnectionValue). Each input is |
11 | | * staged in a scratch file and read back through one of those paths. |
12 | | * |
13 | | * Every slot is an R closure that closes its connection on exit: R caps |
14 | | * the connection table at 128, so a leak would quietly turn every later |
15 | | * iteration into the same "all connections are in use" error. |
16 | | */ |
17 | | |
18 | | #include <stdint.h> |
19 | | #include <string.h> |
20 | | |
21 | | #include "common.h" |
22 | | |
23 | 2.51k | #define FUZZ_MAX_INPUT (1024 * 64) |
24 | | |
25 | | /* Encodings handed to file(encoding=). "" means no re-encoding, the |
26 | | * others go through iconv in dummy_fgetc; UTF-8-BOM and UCS-2LE take the |
27 | | * byte-order-mark branches. */ |
28 | 2.65k | #define N_ENCODINGS 9 |
29 | | static const char *const encodings[N_ENCODINGS] = { |
30 | | "", "UTF-8", "latin1", "UTF-16LE", "UTF-16BE", "UTF-16", "UCS-2LE", |
31 | | "CP1252", "UTF-8-BOM" |
32 | | }; |
33 | | |
34 | 2.54k | #define N_OPENERS 4 |
35 | | static const char *const openers[N_OPENERS] = { |
36 | | "gzfile", "bzfile", "xzfile", "zstdfile" |
37 | | }; |
38 | | |
39 | | /* Slot layout, in the order the calls are built below: |
40 | | * [0,9) readLines(file(path, encoding), skipNul = FALSE) |
41 | | * [9,18) readLines(..., skipNul = TRUE) |
42 | | * [18,27) readChar(file(path, "r", encoding), useBytes = TRUE) |
43 | | * [27,36) readChar(..., useBytes = FALSE) |
44 | | * [36,45) scan(file(path, encoding), what = "") |
45 | | * [45,54) open(con, "rt") then chunked readLines/seek/readChar |
46 | | * [54,58) <opener>(path, "rb") + readBin |
47 | | * [58,62) <opener>(path, "rt") + readLines |
48 | | * 62 gzcon(file(path, "rb")) + readBin |
49 | | * 63 rawConnection(x) reads, seek, readChar |
50 | | * 64 pushBack over a textConnection of the lines |
51 | | * 65 write side: rawConnection/textConnection/gzcon writers |
52 | | */ |
53 | 2.51k | #define N_CALLS (6 * N_ENCODINGS + 2 * N_OPENERS + 4) |
54 | | |
55 | | static SEXP calls[N_CALLS]; |
56 | | static char *scratch_path; |
57 | | |
58 | | /* Every wrapper takes (x, path, ...) so that fuzz_set_raw_arg can swap the |
59 | | * raw vector in at CADR regardless of which slot runs. */ |
60 | | static const char wrapper_lines[] = |
61 | | "function(x, path, enc, skip) {" |
62 | | " con <- file(path, encoding = enc); on.exit(close(con));" |
63 | | " readLines(con, n = 1000L, warn = FALSE, skipNul = skip) }"; |
64 | | |
65 | | static const char wrapper_chars[] = |
66 | | "function(x, path, enc, ub) {" |
67 | | " con <- file(path, 'r', encoding = enc); on.exit(close(con));" |
68 | | " readChar(con, c(1L, 10L, 100L, 1000L), useBytes = ub) }"; |
69 | | |
70 | | static const char wrapper_scan[] = |
71 | | "function(x, path, enc) {" |
72 | | " con <- file(path, encoding = enc); on.exit(close(con));" |
73 | | " scan(con, what = '', quiet = TRUE, nmax = 1000L) }"; |
74 | | |
75 | | static const char wrapper_chunks[] = |
76 | | "function(x, path, enc) {" |
77 | | " con <- file(path, encoding = enc); open(con, 'rt'); on.exit(close(con));" |
78 | | " a <- readLines(con, n = 2L, warn = FALSE); isIncomplete(con);" |
79 | | " b <- readLines(con, n = 500L, warn = FALSE);" |
80 | | " if (isSeekable(con)) seek(con, 0L);" |
81 | | " list(a, b, readChar(con, 10L, useBytes = TRUE)) }"; |
82 | | |
83 | | static const char wrapper_compressed_bin[] = |
84 | | "function(x, path, opener) {" |
85 | | " con <- opener(path, 'rb'); on.exit(close(con));" |
86 | | " readBin(con, 'raw', 65536L) }"; |
87 | | |
88 | | static const char wrapper_compressed_text[] = |
89 | | "function(x, path, opener) {" |
90 | | " con <- opener(path, 'rt'); on.exit(close(con));" |
91 | | " readLines(con, n = 1000L, warn = FALSE) }"; |
92 | | |
93 | | static const char wrapper_gzcon[] = |
94 | | "function(x, path) {" |
95 | | " con <- gzcon(file(path, 'rb')); on.exit(close(con));" |
96 | | " readBin(con, 'raw', 65536L) }"; |
97 | | |
98 | | static const char wrapper_raw[] = |
99 | | "function(x, path) {" |
100 | | " con <- rawConnection(x); on.exit(close(con));" |
101 | | " a <- readLines(con, n = 1000L, warn = FALSE);" |
102 | | " isSeekable(con); seek(con, 3L);" |
103 | | " b <- readChar(con, c(5L, 50L), useBytes = TRUE);" |
104 | | " seek(con, 0L, 'start');" |
105 | | " list(a, b, readBin(con, 'integer', 100L, size = 2L)) }"; |
106 | | |
107 | | static const char wrapper_pushback[] = |
108 | | "function(x, path) {" |
109 | | " con <- rawConnection(x); on.exit(close(con));" |
110 | | " l <- readLines(con, n = 20L, warn = FALSE);" |
111 | | " tc <- textConnection(l); on.exit(close(tc), add = TRUE);" |
112 | | " pushBack(rev(l), tc, newLine = FALSE); pushBack(l, tc);" |
113 | | " pushBackLength(tc); a <- readLines(tc, n = 5L, warn = FALSE);" |
114 | | " clearPushBack(tc); list(a, readLines(tc, warn = FALSE)) }"; |
115 | | |
116 | | static const char wrapper_write[] = |
117 | | "function(x, path) {" |
118 | | " l <- readLines(rc <- rawConnection(x), n = 50L, warn = FALSE); close(rc);" |
119 | | " out <- rawConnection(raw(0), 'w'); on.exit(close(out));" |
120 | | " writeLines(l, out); writeChar(l, out, eos = NULL); writeBin(x, out);" |
121 | | " seek(out, 0L); truncate(out); writeLines(l, out, sep = '\\r\\n');" |
122 | | " v <- rawConnectionValue(out);" |
123 | | " tc <- textConnection(NULL, 'w'); on.exit(close(tc), add = TRUE);" |
124 | | " writeLines(l, tc); cat(l, file = tc, sep = '|'); t <- textConnectionValue(tc);" |
125 | | " gz <- gzcon(rawConnection(raw(0), 'w')); writeBin(x, gz); close(gz);" |
126 | | " list(v, t) }"; |
127 | | |
128 | | int LLVMFuzzerInitialize(int *argc, char ***argv) |
129 | 2 | { |
130 | 2 | (void)argc; |
131 | 2 | (void)argv; |
132 | 2 | fuzz_init_r(); |
133 | | |
134 | 2 | scratch_path = fuzz_scratch_file("connections"); |
135 | 2 | if (scratch_path == NULL) |
136 | 0 | abort(); |
137 | | |
138 | 2 | SEXP placeholder, path, true_value, false_value; |
139 | 2 | Rf_protect(placeholder = Rf_allocVector(RAWSXP, 1)); |
140 | 2 | Rf_protect(path = Rf_mkString(scratch_path)); |
141 | 2 | Rf_protect(true_value = Rf_ScalarLogical(TRUE)); |
142 | 2 | Rf_protect(false_value = Rf_ScalarLogical(FALSE)); |
143 | | |
144 | 2 | SEXP w_lines, w_chars, w_scan, w_chunks, w_cbin, w_ctext; |
145 | 2 | Rf_protect(w_lines = fuzz_make_wrapper(wrapper_lines)); |
146 | 2 | Rf_protect(w_chars = fuzz_make_wrapper(wrapper_chars)); |
147 | 2 | Rf_protect(w_scan = fuzz_make_wrapper(wrapper_scan)); |
148 | 2 | Rf_protect(w_chunks = fuzz_make_wrapper(wrapper_chunks)); |
149 | 2 | Rf_protect(w_cbin = fuzz_make_wrapper(wrapper_compressed_bin)); |
150 | 2 | Rf_protect(w_ctext = fuzz_make_wrapper(wrapper_compressed_text)); |
151 | | |
152 | 2 | int n = 0; |
153 | 2 | SEXP enc[N_ENCODINGS]; |
154 | 20 | for (int i = 0; i < N_ENCODINGS; i++) |
155 | 18 | Rf_protect(enc[i] = Rf_mkString(encodings[i])); |
156 | | |
157 | 20 | for (int i = 0; i < N_ENCODINGS; i++) |
158 | 18 | Rf_protect(calls[n++] = Rf_lang5(w_lines, placeholder, path, enc[i], |
159 | 18 | false_value)); |
160 | 20 | for (int i = 0; i < N_ENCODINGS; i++) |
161 | 18 | Rf_protect(calls[n++] = Rf_lang5(w_lines, placeholder, path, enc[i], |
162 | 18 | true_value)); |
163 | 20 | for (int i = 0; i < N_ENCODINGS; i++) |
164 | 18 | Rf_protect(calls[n++] = Rf_lang5(w_chars, placeholder, path, enc[i], |
165 | 18 | true_value)); |
166 | 20 | for (int i = 0; i < N_ENCODINGS; i++) |
167 | 18 | Rf_protect(calls[n++] = Rf_lang5(w_chars, placeholder, path, enc[i], |
168 | 18 | false_value)); |
169 | 20 | for (int i = 0; i < N_ENCODINGS; i++) |
170 | 18 | Rf_protect(calls[n++] = Rf_lang4(w_scan, placeholder, path, enc[i])); |
171 | 20 | for (int i = 0; i < N_ENCODINGS; i++) |
172 | 18 | Rf_protect(calls[n++] = Rf_lang4(w_chunks, placeholder, path, enc[i])); |
173 | | |
174 | | /* The openers are base functions looked up once; passing the closure |
175 | | * itself keeps the wrapper free of any eval of a name. */ |
176 | 2 | SEXP opener[N_OPENERS]; |
177 | 10 | for (int i = 0; i < N_OPENERS; i++) |
178 | 8 | Rf_protect(opener[i] = Rf_findFun(Rf_install(openers[i]), R_BaseEnv)); |
179 | 10 | for (int i = 0; i < N_OPENERS; i++) |
180 | 8 | Rf_protect(calls[n++] = Rf_lang4(w_cbin, placeholder, path, opener[i])); |
181 | 10 | for (int i = 0; i < N_OPENERS; i++) |
182 | 8 | Rf_protect(calls[n++] = Rf_lang4(w_ctext, placeholder, path, opener[i])); |
183 | | |
184 | 2 | SEXP w_gzcon, w_raw, w_pushback, w_write; |
185 | 2 | Rf_protect(w_gzcon = fuzz_make_wrapper(wrapper_gzcon)); |
186 | 2 | Rf_protect(w_raw = fuzz_make_wrapper(wrapper_raw)); |
187 | 2 | Rf_protect(w_pushback = fuzz_make_wrapper(wrapper_pushback)); |
188 | 2 | Rf_protect(w_write = fuzz_make_wrapper(wrapper_write)); |
189 | 2 | Rf_protect(calls[n++] = Rf_lang3(w_gzcon, placeholder, path)); |
190 | 2 | Rf_protect(calls[n++] = Rf_lang3(w_raw, placeholder, path)); |
191 | 2 | Rf_protect(calls[n++] = Rf_lang3(w_pushback, placeholder, path)); |
192 | 2 | Rf_protect(calls[n++] = Rf_lang3(w_write, placeholder, path)); |
193 | 2 | if (n != N_CALLS) |
194 | 0 | abort(); |
195 | 2 | return 0; |
196 | 2 | } |
197 | | |
198 | | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) |
199 | 2.51k | { |
200 | 2.51k | if (size < 2 || size > FUZZ_MAX_INPUT) |
201 | 2 | return 0; |
202 | | |
203 | 2.51k | int selected = data[0] % N_CALLS; |
204 | 2.51k | if (!fuzz_write_scratch(scratch_path, data + 1, size - 1)) |
205 | 0 | return 0; |
206 | 2.51k | if (!fuzz_set_raw_arg(calls[selected], data + 1, size - 1)) |
207 | 0 | return 0; |
208 | | |
209 | 2.51k | fuzz_eval_silent(calls[selected], R_GlobalEnv); |
210 | 2.51k | return 0; |
211 | 2.51k | } |