/src/curl_fuzzer/legacy_fuzzer.cc
Line | Count | Source |
1 | | /*************************************************************************** |
2 | | * _ _ ____ _ |
3 | | * Project ___| | | | _ \| | |
4 | | * / __| | | | |_) | | |
5 | | * | (__| |_| | _ <| |___ |
6 | | * \___|\___/|_| \_\_____| |
7 | | * |
8 | | * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al. |
9 | | * |
10 | | * This software is licensed as described in the file COPYING, which |
11 | | * you should have received as part of this distribution. The terms |
12 | | * are also available at https://curl.se/docs/copyright.html. |
13 | | * |
14 | | * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
15 | | * copies of the Software, and permit persons to whom the Software is |
16 | | * furnished to do so, under the terms of the COPYING file. |
17 | | * |
18 | | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
19 | | * KIND, either express or implied. |
20 | | * |
21 | | ***************************************************************************/ |
22 | | |
23 | | #include <stdlib.h> |
24 | | #include <signal.h> |
25 | | #include <string.h> |
26 | | #include <unistd.h> |
27 | | #include <curl/curl.h> |
28 | | #include "curl_fuzzer.h" |
29 | | #include "legacy_fuzzer.h" |
30 | | #include "legacy_protocol_allowlist.h" |
31 | | #include "legacy_tlv_mutator.h" |
32 | | |
33 | | /** |
34 | | * Consume libcurl verbose records without emitting per-input diagnostics. |
35 | | * Enabling CURLOPT_VERBOSE is what makes curl evaluate its trace format |
36 | | * arguments; the callback keeps that work off stderr during fuzzing. |
37 | | */ |
38 | | int fuzz_silent_debug_callback(CURL *handle, |
39 | | curl_infotype type, |
40 | | char *data, |
41 | | size_t size, |
42 | | void *userptr) |
43 | 53.0M | { |
44 | 53.0M | (void)handle; |
45 | 53.0M | (void)type; |
46 | 53.0M | (void)data; |
47 | 53.0M | (void)size; |
48 | 53.0M | (void)userptr; |
49 | 53.0M | return 0; |
50 | 53.0M | } |
51 | | |
52 | | /** |
53 | | * Open /dev/null once for the no-callback verbose lane. When no debug |
54 | | * callback is installed libcurl writes its trace directly to this stream. |
55 | | */ |
56 | | static FILE *fuzz_devnull(void) |
57 | 3.14k | { |
58 | 3.14k | static FILE *devnull = NULL; |
59 | | |
60 | 3.14k | if(devnull == NULL) |
61 | 13 | devnull = fopen("/dev/null", "wb"); |
62 | 3.14k | return devnull; |
63 | 3.14k | } |
64 | | |
65 | | /** |
66 | | * Run one legacy TLV input independently of the exported libFuzzer symbol. |
67 | | * Keeping the implementation behind a normal C++ function lets every |
68 | | * protocol binary expose its own same-named source entrypoint, which is how |
69 | | * Fuzz Introspector attributes a binary's runtime coverage to its call tree. |
70 | | */ |
71 | | int LegacyFuzzerTestOneInput(const uint8_t *data, size_t size) |
72 | 227k | { |
73 | 227k | int rc = 0; |
74 | 227k | int tlv_rc; |
75 | 227k | FUZZ_DATA fuzz; |
76 | 227k | TLV tlv; |
77 | | |
78 | | /* Ignore SIGPIPE errors. We'll handle the errors ourselves. */ |
79 | 227k | signal(SIGPIPE, SIG_IGN); |
80 | | |
81 | | /* Have to set all fields to zero before getting to the terminate function */ |
82 | 227k | memset(&fuzz, 0, sizeof(FUZZ_DATA)); |
83 | 227k | fuzz_doh_server_init(&fuzz); |
84 | | |
85 | 227k | if(size < sizeof(TLV_RAW)) { |
86 | | /* Not enough data for a single TLV - don't continue */ |
87 | 52 | goto EXIT_LABEL; |
88 | 52 | } |
89 | | |
90 | | /* Try to initialize the fuzz data */ |
91 | 227k | FTRY(fuzz_initialize_fuzz_data(&fuzz, data, size)); |
92 | | |
93 | 227k | for(tlv_rc = fuzz_get_first_tlv(&fuzz, &tlv); |
94 | 4.23M | tlv_rc == 0; |
95 | 4.02M | tlv_rc = fuzz_get_next_tlv(&fuzz, &tlv)) { |
96 | | |
97 | | /* Have the TLV in hand. Parse the TLV. */ |
98 | 4.02M | rc = fuzz_parse_tlv(&fuzz, &tlv); |
99 | | |
100 | 4.02M | if(rc != 0) { |
101 | | /* Failed to parse the TLV. Can't continue. */ |
102 | 16.8k | goto EXIT_LABEL; |
103 | 16.8k | } |
104 | 4.02M | } |
105 | | |
106 | 210k | if(tlv_rc != TLV_RC_NO_MORE_TLVS) { |
107 | | /* A TLV call failed. Can't continue. */ |
108 | 1.07k | goto EXIT_LABEL; |
109 | 1.07k | } |
110 | | |
111 | | /* Set up the standard easy options. */ |
112 | 209k | FTRY(fuzz_set_easy_options(&fuzz)); |
113 | | |
114 | | /** |
115 | | * Add in more curl options that have been accumulated over possibly |
116 | | * multiple TLVs. |
117 | | */ |
118 | 209k | if(fuzz.header_list != NULL) { |
119 | 17.8k | curl_easy_setopt(fuzz.easy, CURLOPT_HTTPHEADER, fuzz.header_list); |
120 | 17.8k | } |
121 | | |
122 | 209k | if(fuzz.mail_recipients_list != NULL) { |
123 | 3.41k | curl_easy_setopt(fuzz.easy, CURLOPT_MAIL_RCPT, fuzz.mail_recipients_list); |
124 | 3.41k | } |
125 | | |
126 | 209k | if(fuzz.mime != NULL) { |
127 | 11.1k | curl_easy_setopt(fuzz.easy, CURLOPT_MIMEPOST, fuzz.mime); |
128 | 11.1k | } |
129 | | |
130 | 209k | if (fuzz.httppost != NULL) { |
131 | 2.73k | curl_easy_setopt(fuzz.easy, CURLOPT_HTTPPOST, fuzz.httppost); |
132 | 2.73k | } |
133 | | |
134 | | /* Run the transfer. */ |
135 | 209k | fuzz_handle_transfer(&fuzz); |
136 | 209k | FV_PRINTF(&fuzz, "FUZZ: transfer done, wrote %zu bytes\n", |
137 | 209k | fuzz.written_data); |
138 | | |
139 | 227k | EXIT_LABEL: |
140 | | |
141 | 227k | fuzz_terminate_fuzz_data(&fuzz); |
142 | | |
143 | | /* This function must always return 0. Non-zero codes are reserved. */ |
144 | 227k | return 0; |
145 | 209k | } |
146 | | |
147 | | /** |
148 | | * Utility function to convert 4 bytes to a u32 predictably. |
149 | | */ |
150 | | uint32_t to_u32(const uint8_t b[4]) |
151 | 4.37M | { |
152 | 4.37M | uint32_t u; |
153 | | /* Promote into the unsigned result type before shifting. uint8_t otherwise |
154 | | promotes to signed int, and values with the high bit set make the |
155 | | left-shift undefined before curl ever observes the fuzzed boundary. */ |
156 | 4.37M | u = (static_cast<uint32_t>(b[0]) << 24) | |
157 | 4.37M | (static_cast<uint32_t>(b[1]) << 16) | |
158 | 4.37M | (static_cast<uint32_t>(b[2]) << 8) | |
159 | 4.37M | static_cast<uint32_t>(b[3]); |
160 | 4.37M | return u; |
161 | 4.37M | } |
162 | | |
163 | | /** |
164 | | * Utility function to convert 2 bytes to a u16 predictably. |
165 | | */ |
166 | | uint16_t to_u16(const uint8_t b[2]) |
167 | 4.23M | { |
168 | 4.23M | uint16_t u; |
169 | 4.23M | u = (b[0] << 8) + b[1]; |
170 | 4.23M | return u; |
171 | 4.23M | } |
172 | | |
173 | | /** |
174 | | * Initialize the local fuzz data structure. |
175 | | */ |
176 | | int fuzz_initialize_fuzz_data(FUZZ_DATA *fuzz, |
177 | | const uint8_t *data, |
178 | | size_t data_len) |
179 | 227k | { |
180 | 227k | int rc = 0; |
181 | 227k | int ii; |
182 | | |
183 | | /* Initialize the fuzz data. */ |
184 | 227k | memset(fuzz, 0, sizeof(FUZZ_DATA)); |
185 | 227k | fuzz_doh_server_init(fuzz); |
186 | | |
187 | | /* Create an easy handle. This will have all of the settings configured on |
188 | | it. */ |
189 | 227k | fuzz->easy = curl_easy_init(); |
190 | 227k | FCHECK(fuzz->easy != NULL); |
191 | | |
192 | | /* Set up the state parser */ |
193 | 227k | fuzz->state.data = data; |
194 | 227k | fuzz->state.data_len = data_len; |
195 | | |
196 | | /* Set up the state of the server sockets. */ |
197 | 681k | for(ii = 0; ii < FUZZ_NUM_CONNECTIONS; ii++) { |
198 | 454k | fuzz->sockman[ii].index = ii; |
199 | 454k | fuzz->sockman[ii].fd_state = FUZZ_SOCK_CLOSED; |
200 | 454k | } |
201 | | |
202 | | /* Check for verbose mode. */ |
203 | 227k | fuzz->verbose = (getenv("FUZZ_VERBOSE") != NULL); |
204 | | |
205 | 227k | FCHECK(setenv("CURL_HSTS_HTTP", "1", 0) == 0); |
206 | 227k | FCHECK(setenv("CURL_ALTSVC_HTTP", "1", 0) == 0); |
207 | | /* Current curl permits plaintext DoH under DEBUGBUILD directly. Set the |
208 | | analogous override as well so a future curl that gates it on an |
209 | | environment check keeps the loopback lane reachable. */ |
210 | 227k | FCHECK(setenv("CURL_DOH_HTTP", "1", 0) == 0); |
211 | | |
212 | 227k | EXIT_LABEL: |
213 | | |
214 | 227k | return rc; |
215 | 227k | } |
216 | | |
217 | | /** |
218 | | * Reapply resolver-sensitive string options with their canonical loopback |
219 | | * values before starting a transfer. |
220 | | * |
221 | | * Custom mutation and crossover already finalize generated buffers, but an |
222 | | * initial corpus entry or standalone reproducer is executed without passing |
223 | | * through either callback. The option tracker identifies only values that the |
224 | | * TLV parser successfully applied, so this does not enable routing options that |
225 | | * were absent from the input. DNS_INTERFACE is deliberately excluded: with |
226 | | * c-ares it is a device name passed to ares_set_local_dev(), not a hostname. |
227 | | */ |
228 | | static int fuzz_finalize_routing_options(FUZZ_DATA *fuzz) |
229 | 209k | { |
230 | 209k | int rc = 0; |
231 | | |
232 | 209k | #define FFINALIZE_ROUTING_OPTION(TLVTYPE, CURLOPTNAME) \ |
233 | 836k | if(fuzz->options[(CURLOPTNAME) % 1000]) { \ |
234 | 35.6k | const char *canonical = \ |
235 | 35.6k | legacy_tlv_mutator::CanonicalRoutingValue((TLVTYPE)); \ |
236 | 35.6k | FCHECK(canonical != NULL); \ |
237 | 35.6k | FTRY(curl_easy_setopt(fuzz->easy, (CURLOPTNAME), canonical)); \ |
238 | 35.6k | } |
239 | | |
240 | 209k | FFINALIZE_ROUTING_OPTION(TLV_TYPE_PROXY, CURLOPT_PROXY); |
241 | 209k | FFINALIZE_ROUTING_OPTION(TLV_TYPE_FTPPORT, CURLOPT_FTPPORT); |
242 | 209k | FFINALIZE_ROUTING_OPTION(TLV_TYPE_INTERFACE, CURLOPT_INTERFACE); |
243 | 209k | FFINALIZE_ROUTING_OPTION(TLV_TYPE_PRE_PROXY, CURLOPT_PRE_PROXY); |
244 | | |
245 | 209k | #undef FFINALIZE_ROUTING_OPTION |
246 | | |
247 | 209k | EXIT_LABEL: |
248 | 209k | return rc; |
249 | 209k | } |
250 | | |
251 | | /** |
252 | | * Set standard options on the curl easy. |
253 | | */ |
254 | | int fuzz_set_easy_options(FUZZ_DATA *fuzz) |
255 | 209k | { |
256 | 209k | int rc = 0; |
257 | | |
258 | | /* Reset curl's global trace configuration so every input starts from a known |
259 | | off baseline. curl_global_trace() only changes the components it names, so |
260 | | without this a prior input's "all" leaks into later inputs that request a |
261 | | subset, making coverage depend on corpus order. */ |
262 | 209k | (void)curl_global_trace("-all"); |
263 | | |
264 | | /* Existing seeds and direct reproducers bypass the custom mutator. Close |
265 | | that path before any transfer can resolve a corpus-provided endpoint. */ |
266 | 209k | FTRY(fuzz_finalize_routing_options(fuzz)); |
267 | | |
268 | | /* Set some standard options on the CURL easy handle. We need to override the |
269 | | socket function so that we create our own sockets to present to CURL. */ |
270 | 209k | FTRY(curl_easy_setopt(fuzz->easy, |
271 | 209k | CURLOPT_OPENSOCKETFUNCTION, |
272 | 209k | fuzz_open_socket)); |
273 | 209k | FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_OPENSOCKETDATA, fuzz)); |
274 | | |
275 | | /* In case something tries to set a socket option, intercept this. */ |
276 | 209k | FTRY(curl_easy_setopt(fuzz->easy, |
277 | 209k | CURLOPT_SOCKOPTFUNCTION, |
278 | 209k | fuzz_sockopt_callback)); |
279 | | |
280 | | /* Set the standard read function callback. */ |
281 | 209k | FTRY(curl_easy_setopt(fuzz->easy, |
282 | 209k | CURLOPT_READFUNCTION, |
283 | 209k | fuzz_read_callback)); |
284 | 209k | FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_READDATA, fuzz)); |
285 | | |
286 | | /* Set the standard write function callback. */ |
287 | 209k | FTRY(curl_easy_setopt(fuzz->easy, |
288 | 209k | CURLOPT_WRITEFUNCTION, |
289 | 209k | fuzz_write_callback)); |
290 | 209k | FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_WRITEDATA, fuzz)); |
291 | | |
292 | | /* Set the writable cookie jar path so cookies are tested. */ |
293 | 209k | FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_COOKIEJAR, FUZZ_COOKIE_JAR_PATH)); |
294 | | |
295 | | /* Set the RO cookie file path so cookies are tested. */ |
296 | 209k | FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_COOKIEFILE, FUZZ_RO_COOKIE_FILE_PATH)); |
297 | | |
298 | | /* Set altsvc header cache filepath so that it can be fuzzed. */ |
299 | 209k | FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_ALTSVC, FUZZ_ALT_SVC_HEADER_CACHE_PATH)); |
300 | | |
301 | | /* Set the hsts header cache filepath so that it can be fuzzed. */ |
302 | 209k | FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_HSTS, FUZZ_HSTS_HEADER_CACHE_PATH)); |
303 | | |
304 | | /* Set the Certificate Revocation List file path so it can be fuzzed */ |
305 | 209k | FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_CRLFILE, FUZZ_CRL_FILE_PATH)); |
306 | | |
307 | | /* Set the .netrc file path so it can be fuzzed */ |
308 | 209k | FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_NETRC_FILE, FUZZ_NETRC_FILE_PATH)); |
309 | | |
310 | | /* Time out requests quickly. A loopback DoH probe needs a little more room |
311 | | for the harness listener to run, but the transfer loop's stall detection |
312 | | still bounds inputs that make no progress. */ |
313 | 209k | FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_TIMEOUT_MS, |
314 | 209k | fuzz->doh.enabled ? 2000L : 200L)); |
315 | 209k | FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_SERVER_RESPONSE_TIMEOUT, 1L)); |
316 | | |
317 | | /* Can enable verbose mode by having the environment variable FUZZ_VERBOSE. */ |
318 | 209k | if(fuzz->verbose) { |
319 | 0 | FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_VERBOSE, 1L)); |
320 | 0 | } |
321 | | |
322 | | /* The verbose DoH lane enables curl's DoH feature tracing globally and |
323 | | installs a no-op debug callback. That makes infof() evaluate its |
324 | | arguments, exercising doh_type2name()/doh_strerror(), while keeping the |
325 | | trace off stderr. The callback and debug data are inherited by the |
326 | | internal DoH handle. */ |
327 | 209k | if(fuzz->doh_verbose) { |
328 | 870 | (void)curl_global_trace("doh"); |
329 | 870 | FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_VERBOSE, 1L)); |
330 | 870 | FTRY(curl_easy_setopt(fuzz->easy, |
331 | 870 | CURLOPT_DEBUGFUNCTION, |
332 | 870 | fuzz_doh_debug_callback)); |
333 | 870 | FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_DEBUGDATA, fuzz)); |
334 | 870 | } |
335 | | |
336 | | /* Fuzzer-controlled trace configuration. curl_global_trace() selects the |
337 | | per-feature trace formatters and the silent callback keeps them off |
338 | | stderr. The no-callback lane below deliberately takes precedence if both |
339 | | flags appear in one input. */ |
340 | 209k | if(fuzz->trace_verbose && !fuzz->trace_stderr_verbose) { |
341 | 8.11k | const char *trace_config = |
342 | 8.11k | fuzz->trace_config[0] ? fuzz->trace_config : "all"; |
343 | | |
344 | 8.11k | (void)curl_global_trace(trace_config); |
345 | 8.11k | FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_VERBOSE, 1L)); |
346 | 8.11k | FTRY(curl_easy_setopt(fuzz->easy, |
347 | 8.11k | CURLOPT_DEBUGFUNCTION, |
348 | 8.11k | fuzz_silent_debug_callback)); |
349 | 8.11k | FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_DEBUGDATA, fuzz)); |
350 | 8.11k | FTRY(curl_easy_setopt(fuzz->easy, |
351 | 8.11k | CURLOPT_ERRORBUFFER, |
352 | 8.11k | fuzz->error_buffer)); |
353 | 8.11k | } |
354 | | |
355 | | /* With no debug callback libcurl's trace helpers write to CURLOPT_STDERR |
356 | | directly. Point that at /dev/null so the fwrite() paths run without |
357 | | flooding the coverage log. */ |
358 | 209k | if(fuzz->trace_stderr_verbose) { |
359 | 3.14k | FILE *devnull = fuzz_devnull(); |
360 | 3.14k | const char *trace_config = |
361 | 3.14k | fuzz->trace_config[0] ? fuzz->trace_config : "all"; |
362 | | |
363 | 3.14k | (void)curl_global_trace(trace_config); |
364 | 3.14k | FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_VERBOSE, 1L)); |
365 | | /* This lane must reach the direct-write path, so drop any debug callback a |
366 | | DoH (TLV 165) or trace (TLV 166) record may have installed earlier. */ |
367 | 3.14k | FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_DEBUGFUNCTION, NULL)); |
368 | 3.14k | FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_DEBUGDATA, NULL)); |
369 | 3.14k | if(devnull != NULL) { |
370 | 3.14k | FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_STDERR, devnull)); |
371 | 3.14k | } |
372 | 3.14k | FTRY(curl_easy_setopt(fuzz->easy, |
373 | 3.14k | CURLOPT_ERRORBUFFER, |
374 | 3.14k | fuzz->error_buffer)); |
375 | 3.14k | } |
376 | | |
377 | | /* Force resolution of all addresses to a specific IP address. DoH loopback |
378 | | inputs must leave the hostname unresolved so the probe actually runs; |
379 | | the resolver answer then supplies the loopback address. */ |
380 | 209k | if(!fuzz->doh.enabled) { |
381 | 205k | fuzz->connect_to_list = curl_slist_append(NULL, "::127.0.1.127:"); |
382 | 205k | FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_CONNECT_TO, fuzz->connect_to_list)); |
383 | 205k | } |
384 | | |
385 | | /* Point CURLOPT_DOH_URL at the harness-owned loopback responder. The |
386 | | internal DoH easy handle opens a real socket, so this is the only way to |
387 | | let a probe complete without changing curl. */ |
388 | 209k | if(fuzz->doh.enabled) { |
389 | 3.53k | if(fuzz_doh_server_start(fuzz) == 0) { |
390 | 3.53k | FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_DOH_URL, fuzz->doh.url)); |
391 | 3.53k | fuzz_doh_proxy_bypass_begin(fuzz); |
392 | 3.53k | } |
393 | 0 | else { |
394 | | /* Loopback is unavailable in this environment; run without DoH rather |
395 | | than failing an input for an infrastructure limitation. Restore the |
396 | | CONNECT_TO guard so a hostname cannot fall through to system DNS. */ |
397 | 0 | fuzz->doh.enabled = 0; |
398 | 0 | fuzz->connect_to_list = curl_slist_append(NULL, "::127.0.1.127:"); |
399 | 0 | if(fuzz->connect_to_list) |
400 | 0 | FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_CONNECT_TO, |
401 | 0 | fuzz->connect_to_list)); |
402 | 0 | } |
403 | 3.53k | } |
404 | | |
405 | | /* Limit the protocols in use by this fuzzer. */ |
406 | 209k | FTRY(fuzz_set_allowed_protocols(fuzz)); |
407 | | |
408 | 209k | EXIT_LABEL: |
409 | | |
410 | 209k | return rc; |
411 | 209k | } |
412 | | |
413 | | /** |
414 | | * Terminate the fuzz data structure, including freeing any allocated memory. |
415 | | */ |
416 | | void fuzz_terminate_fuzz_data(FUZZ_DATA *fuzz) |
417 | 227k | { |
418 | 227k | int ii; |
419 | | |
420 | 227k | fuzz_doh_server_stop(fuzz); |
421 | | |
422 | 227k | fuzz_free((void **)&fuzz->postfields); |
423 | | |
424 | 681k | for(ii = 0; ii < FUZZ_NUM_CONNECTIONS; ii++) { |
425 | 454k | if(fuzz->sockman[ii].fd_state != FUZZ_SOCK_CLOSED) { |
426 | 147k | close(fuzz->sockman[ii].fd); |
427 | 147k | fuzz->sockman[ii].fd_state = FUZZ_SOCK_CLOSED; |
428 | 147k | } |
429 | 454k | } |
430 | | |
431 | 227k | if(fuzz->connect_to_list != NULL) { |
432 | 205k | curl_slist_free_all(fuzz->connect_to_list); |
433 | 205k | fuzz->connect_to_list = NULL; |
434 | 205k | } |
435 | | |
436 | 227k | if(fuzz->header_list != NULL) { |
437 | 17.8k | curl_slist_free_all(fuzz->header_list); |
438 | 17.8k | fuzz->header_list = NULL; |
439 | 17.8k | } |
440 | | |
441 | 227k | if(fuzz->mail_recipients_list != NULL) { |
442 | 3.46k | curl_slist_free_all(fuzz->mail_recipients_list); |
443 | 3.46k | fuzz->mail_recipients_list = NULL; |
444 | 3.46k | } |
445 | | |
446 | 227k | if(fuzz->mime != NULL) { |
447 | 11.3k | curl_mime_free(fuzz->mime); |
448 | 11.3k | fuzz->mime = NULL; |
449 | 11.3k | } |
450 | | |
451 | 227k | if(fuzz->easy != NULL) { |
452 | 227k | curl_easy_cleanup(fuzz->easy); |
453 | 227k | fuzz->easy = NULL; |
454 | 227k | } |
455 | | |
456 | | /* When you have passed the struct curl_httppost pointer to curl_easy_setopt |
457 | | * (using the CURLOPT_HTTPPOST option), you must not free the list until after |
458 | | * you have called curl_easy_cleanup for the curl handle. |
459 | | * https://curl.se/libcurl/c/curl_formadd.html */ |
460 | 227k | if (fuzz->httppost != NULL) { |
461 | 2.77k | curl_formfree(fuzz->httppost); |
462 | 2.77k | fuzz->httppost = NULL; |
463 | 2.77k | } |
464 | | |
465 | | // free after httppost and last_post_part. |
466 | 227k | if (fuzz->post_body != NULL) { |
467 | 2.77k | fuzz_free((void **)&fuzz->post_body); |
468 | 2.77k | } |
469 | 227k | } |
470 | | |
471 | | /** |
472 | | * If a pointer has been allocated, free that pointer. |
473 | | */ |
474 | | void fuzz_free(void **ptr) |
475 | 4.31M | { |
476 | 4.31M | if(*ptr != NULL) { |
477 | 817k | free(*ptr); |
478 | 817k | *ptr = NULL; |
479 | 817k | } |
480 | 4.31M | } |
481 | | |
482 | | /** |
483 | | * Function for handling the fuzz transfer, including sending responses to |
484 | | * requests. |
485 | | */ |
486 | | int fuzz_handle_transfer(FUZZ_DATA *fuzz) |
487 | 209k | { |
488 | 209k | int rc = 0; |
489 | 209k | CURLM *multi_handle; |
490 | 209k | int still_running; /* keep number of running handles */ |
491 | 209k | CURLMsg *msg; /* for picking up messages with the transfer status */ |
492 | 209k | int msgs_left; /* how many messages are left */ |
493 | 209k | int double_timeout = 0; |
494 | 209k | fd_set fdread; |
495 | 209k | fd_set fdwrite; |
496 | 209k | fd_set fdexcep; |
497 | 209k | struct timeval timeout; |
498 | 209k | int select_rc; |
499 | 209k | CURLMcode mc; |
500 | 209k | int maxfd = -1; |
501 | 209k | long curl_timeo = -1; |
502 | 209k | int ii; |
503 | 209k | FUZZ_SOCKET_MANAGER *sman[FUZZ_NUM_CONNECTIONS]; |
504 | | |
505 | 627k | for(ii = 0; ii < FUZZ_NUM_CONNECTIONS; ii++) { |
506 | 418k | sman[ii] = &fuzz->sockman[ii]; |
507 | | |
508 | | /* Set up the starting index for responses. */ |
509 | 418k | sman[ii]->response_index = 1; |
510 | 418k | } |
511 | | |
512 | | /* init a multi stack */ |
513 | 209k | multi_handle = curl_multi_init(); |
514 | | |
515 | | /* add the individual transfers */ |
516 | 209k | curl_multi_add_handle(multi_handle, fuzz->easy); |
517 | | |
518 | | /* Do an initial process. This might end the transfer immediately. */ |
519 | 209k | curl_multi_perform(multi_handle, &still_running); |
520 | 209k | FV_PRINTF(fuzz, |
521 | 209k | "FUZZ: Initial perform; still running? %d \n", |
522 | 209k | still_running); |
523 | | |
524 | 289k | while(still_running) { |
525 | | /* Reset the sets of file descriptors. */ |
526 | 86.8k | FD_ZERO(&fdread); |
527 | 86.8k | FD_ZERO(&fdwrite); |
528 | 86.8k | FD_ZERO(&fdexcep); |
529 | | |
530 | | /* Set a timeout of 10ms. This is lower than recommended by the multi guide |
531 | | but we're not going to any remote servers, so everything should complete |
532 | | very quickly. */ |
533 | 86.8k | timeout.tv_sec = 0; |
534 | 86.8k | timeout.tv_usec = 10000; |
535 | | |
536 | | /* get file descriptors from the transfers */ |
537 | 86.8k | mc = curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd); |
538 | 86.8k | if(mc != CURLM_OK) { |
539 | 0 | fprintf(stderr, "curl_multi_fdset() failed, code %d.\n", mc); |
540 | 0 | rc = -1; |
541 | 0 | break; |
542 | 0 | } |
543 | | |
544 | 260k | for(ii = 0; ii < FUZZ_NUM_CONNECTIONS; ii++) { |
545 | | /* Add the socket FD into the readable set if connected. */ |
546 | 173k | if(sman[ii]->fd_state == FUZZ_SOCK_OPEN) { |
547 | 66.9k | FD_SET(sman[ii]->fd, &fdread); |
548 | | |
549 | | /* Work out the maximum FD between the cURL file descriptors and the |
550 | | server FD. */ |
551 | 66.9k | maxfd = FUZZ_MAX(sman[ii]->fd, maxfd); |
552 | 66.9k | } |
553 | 173k | } |
554 | | |
555 | 86.8k | fuzz_doh_server_add_fds(fuzz, &fdread, &fdwrite, &maxfd); |
556 | | |
557 | | /* Work out what file descriptors need work. */ |
558 | 86.8k | rc = fuzz_select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout); |
559 | | |
560 | 86.8k | if(rc == -1) { |
561 | | /* Had an issue while selecting a file descriptor. Let's just exit. */ |
562 | 0 | FV_PRINTF(fuzz, "FUZZ: select failed, exiting \n"); |
563 | 0 | break; |
564 | 0 | } |
565 | | |
566 | 86.8k | int server_data_sent = 0; |
567 | 86.8k | if(rc > 0 && fuzz_doh_server_service(fuzz, &fdread, &fdwrite)) |
568 | 4.89k | server_data_sent = 1; |
569 | | |
570 | | /* Check to see if a server file descriptor is readable. If it is, |
571 | | then send the next response from the fuzzing data. */ |
572 | 259k | for(ii = 0; ii < FUZZ_NUM_CONNECTIONS; ii++) { |
573 | 173k | if(sman[ii]->fd_state == FUZZ_SOCK_OPEN && |
574 | 173k | FD_ISSET(sman[ii]->fd, &fdread)) { |
575 | 61.0k | rc = fuzz_send_next_response(fuzz, sman[ii]); |
576 | 61.0k | if(rc != 0) { |
577 | | /* Failed to send a response. Break out here. */ |
578 | 581 | break; |
579 | 581 | } |
580 | 60.4k | server_data_sent = 1; |
581 | 60.4k | } |
582 | 173k | } |
583 | | |
584 | | /* Stall detection: exit after two consecutive iterations where no new |
585 | | data was provided to curl. This handles both select() timeouts and |
586 | | cases where curl registers a writable fd but cannot make progress |
587 | | (e.g. HTTP/2 egress stuck with no real peer to drain to). */ |
588 | 86.8k | if(!server_data_sent) { |
589 | 21.5k | FV_PRINTF(fuzz, "FUZZ: No data sent; stall count %d \n", double_timeout); |
590 | 21.5k | if(double_timeout == 1) { |
591 | 6.08k | break; |
592 | 6.08k | } |
593 | 15.5k | double_timeout = 1; |
594 | 15.5k | } |
595 | 65.2k | else { |
596 | 65.2k | double_timeout = 0; |
597 | 65.2k | } |
598 | | |
599 | 80.7k | curl_multi_perform(multi_handle, &still_running); |
600 | 80.7k | } |
601 | | |
602 | | /* Remove the easy handle from the multi stack. */ |
603 | 209k | curl_multi_remove_handle(multi_handle, fuzz->easy); |
604 | | |
605 | | /* Clean up the multi handle - the top level function will handle the easy |
606 | | handle. */ |
607 | 209k | curl_multi_cleanup(multi_handle); |
608 | | |
609 | 209k | return rc; |
610 | 209k | } |
611 | | |
612 | | /** |
613 | | * Sends the next fuzzing response to the server file descriptor. |
614 | | */ |
615 | | int fuzz_send_next_response(FUZZ_DATA *fuzz, FUZZ_SOCKET_MANAGER *sman) |
616 | 61.0k | { |
617 | 61.0k | int rc = 0; |
618 | 61.0k | ssize_t ret_in; |
619 | 61.0k | ssize_t ret_out; |
620 | 61.0k | char buffer[8192]; |
621 | 61.0k | const uint8_t *data; |
622 | 61.0k | size_t data_len; |
623 | | |
624 | | /* Need to read all data sent by the client so the file descriptor becomes |
625 | | unreadable. Because the file descriptor is non-blocking we won't just |
626 | | hang here. */ |
627 | 194k | do { |
628 | 194k | ret_in = read(sman->fd, buffer, sizeof(buffer)); |
629 | 194k | if(fuzz->verbose && ret_in > 0) { |
630 | 0 | printf("FUZZ[%d]: Received %zu bytes \n==>\n", sman->index, ret_in); |
631 | 0 | fwrite(buffer, ret_in, 1, stdout); |
632 | 0 | printf("\n<==\n"); |
633 | 0 | } |
634 | 194k | } while (ret_in > 0); |
635 | | |
636 | | /* Now send a response to the request that the client just made. */ |
637 | 61.0k | FV_PRINTF(fuzz, |
638 | 61.0k | "FUZZ[%d]: Sending next response: %d \n", |
639 | 61.0k | sman->index, |
640 | 61.0k | sman->response_index); |
641 | 61.0k | data = sman->responses[sman->response_index].data; |
642 | 61.0k | data_len = sman->responses[sman->response_index].data_len; |
643 | | |
644 | 61.0k | if(data != NULL) { |
645 | 61.0k | if(write(sman->fd, data, data_len) != (ssize_t)data_len) { |
646 | | /* Failed to write the data back to the client. Prevent any further |
647 | | testing. */ |
648 | 581 | rc = -1; |
649 | 581 | } |
650 | 61.0k | } |
651 | | |
652 | | /* Work out if there are any more responses. If not, then shut down the |
653 | | server. */ |
654 | 61.0k | sman->response_index++; |
655 | | |
656 | 61.0k | if(sman->response_index >= TLV_MAX_NUM_RESPONSES || |
657 | 60.8k | sman->responses[sman->response_index].data == NULL) { |
658 | 34.6k | FV_PRINTF(fuzz, |
659 | 34.6k | "FUZZ[%d]: Shutting down server socket: %d \n", |
660 | 34.6k | sman->index, |
661 | 34.6k | sman->fd); |
662 | 34.6k | shutdown(sman->fd, SHUT_WR); |
663 | 34.6k | sman->fd_state = FUZZ_SOCK_SHUTDOWN; |
664 | 34.6k | } |
665 | | |
666 | 61.0k | return rc; |
667 | 61.0k | } |
668 | | |
669 | | /** |
670 | | * Wrapper for select() so profiling can track it. |
671 | | */ |
672 | | int fuzz_select(int nfds, |
673 | | fd_set *readfds, |
674 | | fd_set *writefds, |
675 | | fd_set *exceptfds, |
676 | 86.8k | struct timeval *timeout) { |
677 | 86.8k | return select(nfds, readfds, writefds, exceptfds, timeout); |
678 | 86.8k | } |
679 | | |
680 | | /** |
681 | | * Set allowed protocols based on the compile options. |
682 | | * |
683 | | * Note that it can only use ONE of the FUZZ_PROTOCOLS_* defines. |
684 | | */ |
685 | | int fuzz_set_allowed_protocols(FUZZ_DATA *fuzz) |
686 | 209k | { |
687 | 209k | int rc = 0; |
688 | 209k | const char *allowed_protocols = ""; |
689 | | |
690 | | #ifdef FUZZ_PROTOCOLS_ALL |
691 | | /* CURLOPT_PROTOCOLS_STR rejects the complete value if even one requested |
692 | | protocol was compiled out. Derive the generic target's stable safety |
693 | | policy from this libcurl build so optional RTMP and SSH backends cannot |
694 | | prevent every transfer from starting. */ |
695 | | allowed_protocols = legacy_protocol_allowlist::ForCurrentCurl().c_str(); |
696 | | #endif |
697 | | #ifdef FUZZ_PROTOCOLS_FILE |
698 | | allowed_protocols = "file"; |
699 | | #endif |
700 | | #ifdef FUZZ_PROTOCOLS_FTP |
701 | | allowed_protocols = "ftp,ftps"; |
702 | | #endif |
703 | | #ifdef FUZZ_PROTOCOLS_GOPHER |
704 | | allowed_protocols = "gopher,gophers"; |
705 | | #endif |
706 | | #ifdef FUZZ_PROTOCOLS_HTTP |
707 | | allowed_protocols = "http"; |
708 | | #endif |
709 | | #ifdef FUZZ_PROTOCOLS_HTTPS |
710 | | allowed_protocols = "https"; |
711 | | #endif |
712 | | #ifdef FUZZ_PROTOCOLS_IMAP |
713 | | allowed_protocols = "imap,imaps"; |
714 | | #endif |
715 | 209k | #ifdef FUZZ_PROTOCOLS_LDAP |
716 | 209k | allowed_protocols = "ldap,ldaps"; |
717 | 209k | #endif |
718 | | #ifdef FUZZ_PROTOCOLS_MQTT |
719 | | allowed_protocols = "mqtt"; |
720 | | #endif |
721 | | #ifdef FUZZ_PROTOCOLS_POP3 |
722 | | allowed_protocols = "pop3,pop3s"; |
723 | | #endif |
724 | | #ifdef FUZZ_PROTOCOLS_RTMP |
725 | | allowed_protocols = "rtmp,rtmpe,rtmps,rtmpt,rtmpte,rtmpts"; |
726 | | #endif |
727 | | #ifdef FUZZ_PROTOCOLS_RTSP |
728 | | allowed_protocols = "rtsp"; |
729 | | #endif |
730 | | #ifdef FUZZ_PROTOCOLS_SCP |
731 | | allowed_protocols = "scp"; |
732 | | #endif |
733 | | #ifdef FUZZ_PROTOCOLS_SFTP |
734 | | allowed_protocols = "sftp"; |
735 | | #endif |
736 | | #ifdef FUZZ_PROTOCOLS_SMTP |
737 | | allowed_protocols = "smtp,smtps"; |
738 | | #endif |
739 | | #ifdef FUZZ_PROTOCOLS_TFTP |
740 | | allowed_protocols = "tftp"; |
741 | | #endif |
742 | 209k | FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_PROTOCOLS_STR, allowed_protocols)); |
743 | | |
744 | 209k | EXIT_LABEL: |
745 | | |
746 | 209k | return rc; |
747 | 209k | } |