Coverage Report

Created: 2026-08-31 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl_fuzzer/curl_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_tlv_mutator.h"
30
31
/**
32
 * Fuzzing entry point. This function is passed a buffer containing a test
33
 * case.  This test case should drive the CURL API into making a request.
34
 */
35
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
36
251k
{
37
251k
  int rc = 0;
38
251k
  int tlv_rc;
39
251k
  FUZZ_DATA fuzz;
40
251k
  TLV tlv;
41
42
  /* Ignore SIGPIPE errors. We'll handle the errors ourselves. */
43
251k
  signal(SIGPIPE, SIG_IGN);
44
45
  /* Have to set all fields to zero before getting to the terminate function */
46
251k
  memset(&fuzz, 0, sizeof(FUZZ_DATA));
47
48
251k
  if(size < sizeof(TLV_RAW)) {
49
    /* Not enough data for a single TLV - don't continue */
50
64
    goto EXIT_LABEL;
51
64
  }
52
53
  /* Try to initialize the fuzz data */
54
251k
  FTRY(fuzz_initialize_fuzz_data(&fuzz, data, size));
55
56
251k
  for(tlv_rc = fuzz_get_first_tlv(&fuzz, &tlv);
57
7.91M
      tlv_rc == 0;
58
7.69M
      tlv_rc = fuzz_get_next_tlv(&fuzz, &tlv)) {
59
60
    /* Have the TLV in hand. Parse the TLV. */
61
7.69M
    rc = fuzz_parse_tlv(&fuzz, &tlv);
62
63
7.69M
    if(rc != 0) {
64
      /* Failed to parse the TLV. Can't continue. */
65
32.5k
      goto EXIT_LABEL;
66
32.5k
    }
67
7.69M
  }
68
69
218k
  if(tlv_rc != TLV_RC_NO_MORE_TLVS) {
70
    /* A TLV call failed. Can't continue. */
71
3.36k
    goto EXIT_LABEL;
72
3.36k
  }
73
74
  /* Set up the standard easy options. */
75
215k
  FTRY(fuzz_set_easy_options(&fuzz));
76
77
  /**
78
   * Add in more curl options that have been accumulated over possibly
79
   * multiple TLVs.
80
   */
81
204k
  if(fuzz.header_list != NULL) {
82
11.4k
    curl_easy_setopt(fuzz.easy, CURLOPT_HTTPHEADER, fuzz.header_list);
83
11.4k
  }
84
85
204k
  if(fuzz.mail_recipients_list != NULL) {
86
1.47k
    curl_easy_setopt(fuzz.easy, CURLOPT_MAIL_RCPT, fuzz.mail_recipients_list);
87
1.47k
  }
88
89
204k
  if(fuzz.mime != NULL) {
90
5.40k
    curl_easy_setopt(fuzz.easy, CURLOPT_MIMEPOST, fuzz.mime);
91
5.40k
  }
92
93
204k
  if (fuzz.httppost != NULL) {
94
1.17k
    curl_easy_setopt(fuzz.easy, CURLOPT_HTTPPOST, fuzz.httppost);
95
1.17k
  }
96
97
  /* Run the transfer. */
98
204k
  fuzz_handle_transfer(&fuzz);
99
100
251k
EXIT_LABEL:
101
102
251k
  fuzz_terminate_fuzz_data(&fuzz);
103
104
  /* This function must always return 0. Non-zero codes are reserved. */
105
251k
  return 0;
106
204k
}
107
108
/**
109
 * Utility function to convert 4 bytes to a u32 predictably.
110
 */
111
uint32_t to_u32(const uint8_t b[4])
112
7.95M
{
113
7.95M
  uint32_t u;
114
  /* Promote into the unsigned result type before shifting. uint8_t otherwise
115
     promotes to signed int, and values with the high bit set make the
116
     left-shift undefined before curl ever observes the fuzzed boundary. */
117
7.95M
  u = (static_cast<uint32_t>(b[0]) << 24) |
118
7.95M
      (static_cast<uint32_t>(b[1]) << 16) |
119
7.95M
      (static_cast<uint32_t>(b[2]) << 8) |
120
7.95M
      static_cast<uint32_t>(b[3]);
121
7.95M
  return u;
122
7.95M
}
123
124
/**
125
 * Utility function to convert 2 bytes to a u16 predictably.
126
 */
127
uint16_t to_u16(const uint8_t b[2])
128
7.83M
{
129
7.83M
  uint16_t u;
130
7.83M
  u = (b[0] << 8) + b[1];
131
7.83M
  return u;
132
7.83M
}
133
134
/**
135
 * Initialize the local fuzz data structure.
136
 */
137
int fuzz_initialize_fuzz_data(FUZZ_DATA *fuzz,
138
                              const uint8_t *data,
139
                              size_t data_len)
140
251k
{
141
251k
  int rc = 0;
142
251k
  int ii;
143
144
  /* Initialize the fuzz data. */
145
251k
  memset(fuzz, 0, sizeof(FUZZ_DATA));
146
147
  /* Create an easy handle. This will have all of the settings configured on
148
     it. */
149
251k
  fuzz->easy = curl_easy_init();
150
251k
  FCHECK(fuzz->easy != NULL);
151
152
  /* Set up the state parser */
153
251k
  fuzz->state.data = data;
154
251k
  fuzz->state.data_len = data_len;
155
156
  /* Set up the state of the server sockets. */
157
753k
  for(ii = 0; ii < FUZZ_NUM_CONNECTIONS; ii++) {
158
502k
    fuzz->sockman[ii].index = ii;
159
502k
    fuzz->sockman[ii].fd_state = FUZZ_SOCK_CLOSED;
160
502k
  }
161
162
  /* Check for verbose mode. */
163
251k
  fuzz->verbose = (getenv("FUZZ_VERBOSE") != NULL);
164
165
251k
  FCHECK(setenv("CURL_HSTS_HTTP", "1", 0) == 0);
166
251k
  FCHECK(setenv("CURL_ALTSVC_HTTP", "1", 0) == 0);
167
168
251k
EXIT_LABEL:
169
170
251k
  return rc;
171
251k
}
172
173
/**
174
 * Reapply resolver-sensitive string options with their canonical loopback
175
 * values before starting a transfer.
176
 *
177
 * Custom mutation and crossover already finalize generated buffers, but an
178
 * initial corpus entry or standalone reproducer is executed without passing
179
 * through either callback. The option tracker identifies only values that the
180
 * TLV parser successfully applied, so this does not enable routing options that
181
 * were absent from the input. DNS_INTERFACE is deliberately excluded: with
182
 * c-ares it is a device name passed to ares_set_local_dev(), not a hostname.
183
 */
184
static int fuzz_finalize_routing_options(FUZZ_DATA *fuzz)
185
215k
{
186
215k
  int rc = 0;
187
188
215k
#define FFINALIZE_ROUTING_OPTION(TLVTYPE, CURLOPTNAME)                         \
189
860k
  if(fuzz->options[(CURLOPTNAME) % 1000]) {                                   \
190
52.0k
    const char *canonical =                                                   \
191
52.0k
      legacy_tlv_mutator::CanonicalRoutingValue((TLVTYPE));                   \
192
52.0k
    FCHECK(canonical != NULL);                                                 \
193
52.0k
    FTRY(curl_easy_setopt(fuzz->easy, (CURLOPTNAME), canonical));              \
194
52.0k
  }
195
196
215k
  FFINALIZE_ROUTING_OPTION(TLV_TYPE_PROXY, CURLOPT_PROXY);
197
215k
  FFINALIZE_ROUTING_OPTION(TLV_TYPE_FTPPORT, CURLOPT_FTPPORT);
198
215k
  FFINALIZE_ROUTING_OPTION(TLV_TYPE_INTERFACE, CURLOPT_INTERFACE);
199
215k
  FFINALIZE_ROUTING_OPTION(TLV_TYPE_PRE_PROXY, CURLOPT_PRE_PROXY);
200
201
215k
#undef FFINALIZE_ROUTING_OPTION
202
203
215k
EXIT_LABEL:
204
215k
  return rc;
205
215k
}
206
207
/**
208
 * Set standard options on the curl easy.
209
 */
210
int fuzz_set_easy_options(FUZZ_DATA *fuzz)
211
215k
{
212
215k
  int rc = 0;
213
214
  /* Existing seeds and direct reproducers bypass the custom mutator. Close
215
     that path before any transfer can resolve a corpus-provided endpoint. */
216
215k
  FTRY(fuzz_finalize_routing_options(fuzz));
217
218
  /* Set some standard options on the CURL easy handle. We need to override the
219
     socket function so that we create our own sockets to present to CURL. */
220
215k
  FTRY(curl_easy_setopt(fuzz->easy,
221
215k
                        CURLOPT_OPENSOCKETFUNCTION,
222
215k
                        fuzz_open_socket));
223
215k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_OPENSOCKETDATA, fuzz));
224
225
  /* In case something tries to set a socket option, intercept this. */
226
215k
  FTRY(curl_easy_setopt(fuzz->easy,
227
215k
                        CURLOPT_SOCKOPTFUNCTION,
228
215k
                        fuzz_sockopt_callback));
229
230
  /* Set the standard read function callback. */
231
215k
  FTRY(curl_easy_setopt(fuzz->easy,
232
215k
                        CURLOPT_READFUNCTION,
233
215k
                        fuzz_read_callback));
234
215k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_READDATA, fuzz));
235
236
  /* Set the standard write function callback. */
237
215k
  FTRY(curl_easy_setopt(fuzz->easy,
238
215k
                        CURLOPT_WRITEFUNCTION,
239
215k
                        fuzz_write_callback));
240
215k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_WRITEDATA, fuzz));
241
242
  /* Set the writable cookie jar path so cookies are tested. */
243
215k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_COOKIEJAR, FUZZ_COOKIE_JAR_PATH));
244
245
  /* Set the RO cookie file path so cookies are tested. */
246
215k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_COOKIEFILE, FUZZ_RO_COOKIE_FILE_PATH));
247
248
  /* Set altsvc header cache filepath so that it can be fuzzed. */
249
215k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_ALTSVC, FUZZ_ALT_SVC_HEADER_CACHE_PATH));
250
251
  /* Set the hsts header cache filepath so that it can be fuzzed. */
252
215k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_HSTS, FUZZ_HSTS_HEADER_CACHE_PATH));
253
254
  /* Set the Certificate Revocation List file path so it can be fuzzed */
255
215k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_CRLFILE, FUZZ_CRL_FILE_PATH));
256
257
  /* Loading the host trust store for every WSS mutation dominates the
258
     WebSocket target even when the in-process mock immediately ends the TLS
259
     handshake. Keep this exception local to that target: the other legacy
260
     fuzzers should retain libcurl's verification default so their ordinary
261
     mutations continue to cover certificate setup. An explicit
262
     SSL_VERIFYPEER TLV still restores verification in the WebSocket target. */
263
#ifdef FUZZ_PROTOCOLS_WS
264
20.0k
  if(!fuzz->options[CURLOPT_SSL_VERIFYPEER % 1000]) {
265
19.8k
    FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_SSL_VERIFYPEER, 0L));
266
19.8k
  }
267
20.0k
#endif
268
269
  /* Set the .netrc file path so it can be fuzzed */
270
215k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_NETRC_FILE, FUZZ_NETRC_FILE_PATH));
271
272
  /* Time out requests quickly. */
273
215k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_TIMEOUT_MS, 200L));
274
215k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_SERVER_RESPONSE_TIMEOUT, 1L));
275
276
  /* Can enable verbose mode by having the environment variable FUZZ_VERBOSE. */
277
215k
  if(fuzz->verbose) {
278
0
    FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_VERBOSE, 1L));
279
0
  }
280
281
  /* Force resolution of all addresses to a specific IP address. */
282
215k
  fuzz->connect_to_list = curl_slist_append(NULL, "::127.0.1.127:");
283
215k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_CONNECT_TO, fuzz->connect_to_list));
284
285
  /* Limit the protocols in use by this fuzzer. */
286
215k
  FTRY(fuzz_set_allowed_protocols(fuzz));
287
288
215k
EXIT_LABEL:
289
290
215k
  return rc;
291
204k
}
fuzz_set_easy_options(fuzz_data*)
Line
Count
Source
211
195k
{
212
195k
  int rc = 0;
213
214
  /* Existing seeds and direct reproducers bypass the custom mutator. Close
215
     that path before any transfer can resolve a corpus-provided endpoint. */
216
195k
  FTRY(fuzz_finalize_routing_options(fuzz));
217
218
  /* Set some standard options on the CURL easy handle. We need to override the
219
     socket function so that we create our own sockets to present to CURL. */
220
195k
  FTRY(curl_easy_setopt(fuzz->easy,
221
195k
                        CURLOPT_OPENSOCKETFUNCTION,
222
195k
                        fuzz_open_socket));
223
195k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_OPENSOCKETDATA, fuzz));
224
225
  /* In case something tries to set a socket option, intercept this. */
226
195k
  FTRY(curl_easy_setopt(fuzz->easy,
227
195k
                        CURLOPT_SOCKOPTFUNCTION,
228
195k
                        fuzz_sockopt_callback));
229
230
  /* Set the standard read function callback. */
231
195k
  FTRY(curl_easy_setopt(fuzz->easy,
232
195k
                        CURLOPT_READFUNCTION,
233
195k
                        fuzz_read_callback));
234
195k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_READDATA, fuzz));
235
236
  /* Set the standard write function callback. */
237
195k
  FTRY(curl_easy_setopt(fuzz->easy,
238
195k
                        CURLOPT_WRITEFUNCTION,
239
195k
                        fuzz_write_callback));
240
195k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_WRITEDATA, fuzz));
241
242
  /* Set the writable cookie jar path so cookies are tested. */
243
195k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_COOKIEJAR, FUZZ_COOKIE_JAR_PATH));
244
245
  /* Set the RO cookie file path so cookies are tested. */
246
195k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_COOKIEFILE, FUZZ_RO_COOKIE_FILE_PATH));
247
248
  /* Set altsvc header cache filepath so that it can be fuzzed. */
249
195k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_ALTSVC, FUZZ_ALT_SVC_HEADER_CACHE_PATH));
250
251
  /* Set the hsts header cache filepath so that it can be fuzzed. */
252
195k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_HSTS, FUZZ_HSTS_HEADER_CACHE_PATH));
253
254
  /* Set the Certificate Revocation List file path so it can be fuzzed */
255
195k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_CRLFILE, FUZZ_CRL_FILE_PATH));
256
257
  /* Loading the host trust store for every WSS mutation dominates the
258
     WebSocket target even when the in-process mock immediately ends the TLS
259
     handshake. Keep this exception local to that target: the other legacy
260
     fuzzers should retain libcurl's verification default so their ordinary
261
     mutations continue to cover certificate setup. An explicit
262
     SSL_VERIFYPEER TLV still restores verification in the WebSocket target. */
263
#ifdef FUZZ_PROTOCOLS_WS
264
  if(!fuzz->options[CURLOPT_SSL_VERIFYPEER % 1000]) {
265
    FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_SSL_VERIFYPEER, 0L));
266
  }
267
#endif
268
269
  /* Set the .netrc file path so it can be fuzzed */
270
195k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_NETRC_FILE, FUZZ_NETRC_FILE_PATH));
271
272
  /* Time out requests quickly. */
273
195k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_TIMEOUT_MS, 200L));
274
195k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_SERVER_RESPONSE_TIMEOUT, 1L));
275
276
  /* Can enable verbose mode by having the environment variable FUZZ_VERBOSE. */
277
195k
  if(fuzz->verbose) {
278
0
    FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_VERBOSE, 1L));
279
0
  }
280
281
  /* Force resolution of all addresses to a specific IP address. */
282
195k
  fuzz->connect_to_list = curl_slist_append(NULL, "::127.0.1.127:");
283
195k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_CONNECT_TO, fuzz->connect_to_list));
284
285
  /* Limit the protocols in use by this fuzzer. */
286
195k
  FTRY(fuzz_set_allowed_protocols(fuzz));
287
288
195k
EXIT_LABEL:
289
290
195k
  return rc;
291
184k
}
fuzz_set_easy_options(fuzz_data*)
Line
Count
Source
211
20.0k
{
212
20.0k
  int rc = 0;
213
214
  /* Existing seeds and direct reproducers bypass the custom mutator. Close
215
     that path before any transfer can resolve a corpus-provided endpoint. */
216
20.0k
  FTRY(fuzz_finalize_routing_options(fuzz));
217
218
  /* Set some standard options on the CURL easy handle. We need to override the
219
     socket function so that we create our own sockets to present to CURL. */
220
20.0k
  FTRY(curl_easy_setopt(fuzz->easy,
221
20.0k
                        CURLOPT_OPENSOCKETFUNCTION,
222
20.0k
                        fuzz_open_socket));
223
20.0k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_OPENSOCKETDATA, fuzz));
224
225
  /* In case something tries to set a socket option, intercept this. */
226
20.0k
  FTRY(curl_easy_setopt(fuzz->easy,
227
20.0k
                        CURLOPT_SOCKOPTFUNCTION,
228
20.0k
                        fuzz_sockopt_callback));
229
230
  /* Set the standard read function callback. */
231
20.0k
  FTRY(curl_easy_setopt(fuzz->easy,
232
20.0k
                        CURLOPT_READFUNCTION,
233
20.0k
                        fuzz_read_callback));
234
20.0k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_READDATA, fuzz));
235
236
  /* Set the standard write function callback. */
237
20.0k
  FTRY(curl_easy_setopt(fuzz->easy,
238
20.0k
                        CURLOPT_WRITEFUNCTION,
239
20.0k
                        fuzz_write_callback));
240
20.0k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_WRITEDATA, fuzz));
241
242
  /* Set the writable cookie jar path so cookies are tested. */
243
20.0k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_COOKIEJAR, FUZZ_COOKIE_JAR_PATH));
244
245
  /* Set the RO cookie file path so cookies are tested. */
246
20.0k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_COOKIEFILE, FUZZ_RO_COOKIE_FILE_PATH));
247
248
  /* Set altsvc header cache filepath so that it can be fuzzed. */
249
20.0k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_ALTSVC, FUZZ_ALT_SVC_HEADER_CACHE_PATH));
250
251
  /* Set the hsts header cache filepath so that it can be fuzzed. */
252
20.0k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_HSTS, FUZZ_HSTS_HEADER_CACHE_PATH));
253
254
  /* Set the Certificate Revocation List file path so it can be fuzzed */
255
20.0k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_CRLFILE, FUZZ_CRL_FILE_PATH));
256
257
  /* Loading the host trust store for every WSS mutation dominates the
258
     WebSocket target even when the in-process mock immediately ends the TLS
259
     handshake. Keep this exception local to that target: the other legacy
260
     fuzzers should retain libcurl's verification default so their ordinary
261
     mutations continue to cover certificate setup. An explicit
262
     SSL_VERIFYPEER TLV still restores verification in the WebSocket target. */
263
20.0k
#ifdef FUZZ_PROTOCOLS_WS
264
20.0k
  if(!fuzz->options[CURLOPT_SSL_VERIFYPEER % 1000]) {
265
19.8k
    FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_SSL_VERIFYPEER, 0L));
266
19.8k
  }
267
20.0k
#endif
268
269
  /* Set the .netrc file path so it can be fuzzed */
270
20.0k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_NETRC_FILE, FUZZ_NETRC_FILE_PATH));
271
272
  /* Time out requests quickly. */
273
20.0k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_TIMEOUT_MS, 200L));
274
20.0k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_SERVER_RESPONSE_TIMEOUT, 1L));
275
276
  /* Can enable verbose mode by having the environment variable FUZZ_VERBOSE. */
277
20.0k
  if(fuzz->verbose) {
278
0
    FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_VERBOSE, 1L));
279
0
  }
280
281
  /* Force resolution of all addresses to a specific IP address. */
282
20.0k
  fuzz->connect_to_list = curl_slist_append(NULL, "::127.0.1.127:");
283
20.0k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_CONNECT_TO, fuzz->connect_to_list));
284
285
  /* Limit the protocols in use by this fuzzer. */
286
20.0k
  FTRY(fuzz_set_allowed_protocols(fuzz));
287
288
20.0k
EXIT_LABEL:
289
290
20.0k
  return rc;
291
20.0k
}
292
293
/**
294
 * Terminate the fuzz data structure, including freeing any allocated memory.
295
 */
296
void fuzz_terminate_fuzz_data(FUZZ_DATA *fuzz)
297
251k
{
298
251k
  int ii;
299
300
251k
  fuzz_free((void **)&fuzz->postfields);
301
302
753k
  for(ii = 0; ii < FUZZ_NUM_CONNECTIONS; ii++) {
303
502k
    if(fuzz->sockman[ii].fd_state != FUZZ_SOCK_CLOSED) {
304
125k
      close(fuzz->sockman[ii].fd);
305
125k
      fuzz->sockman[ii].fd_state = FUZZ_SOCK_CLOSED;
306
125k
    }
307
502k
  }
308
309
251k
  if(fuzz->connect_to_list != NULL) {
310
215k
    curl_slist_free_all(fuzz->connect_to_list);
311
215k
    fuzz->connect_to_list = NULL;
312
215k
  }
313
314
251k
  if(fuzz->header_list != NULL) {
315
11.6k
    curl_slist_free_all(fuzz->header_list);
316
11.6k
    fuzz->header_list = NULL;
317
11.6k
  }
318
319
251k
  if(fuzz->mail_recipients_list != NULL) {
320
1.63k
    curl_slist_free_all(fuzz->mail_recipients_list);
321
1.63k
    fuzz->mail_recipients_list = NULL;
322
1.63k
  }
323
324
251k
  if(fuzz->mime != NULL) {
325
6.13k
    curl_mime_free(fuzz->mime);
326
6.13k
    fuzz->mime = NULL;
327
6.13k
  }
328
329
251k
  if(fuzz->easy != NULL) {
330
251k
    curl_easy_cleanup(fuzz->easy);
331
251k
    fuzz->easy = NULL;
332
251k
  }
333
334
  /* When you have passed the struct curl_httppost pointer to curl_easy_setopt
335
   * (using the CURLOPT_HTTPPOST option), you must not free the list until after
336
   *  you have called curl_easy_cleanup for the curl handle.
337
   *  https://curl.se/libcurl/c/curl_formadd.html */
338
251k
  if (fuzz->httppost != NULL) {
339
1.27k
    curl_formfree(fuzz->httppost);
340
1.27k
    fuzz->httppost = NULL;
341
1.27k
  }
342
343
  // free after httppost and last_post_part.
344
251k
  if (fuzz->post_body != NULL) {
345
1.27k
    fuzz_free((void **)&fuzz->post_body);
346
1.27k
  }
347
251k
}
348
349
/**
350
 * If a pointer has been allocated, free that pointer.
351
 */
352
void fuzz_free(void **ptr)
353
7.96M
{
354
7.96M
  if(*ptr != NULL) {
355
866k
    free(*ptr);
356
866k
    *ptr = NULL;
357
866k
  }
358
7.96M
}
359
360
/**
361
 * Function for handling the fuzz transfer, including sending responses to
362
 * requests.
363
 */
364
int fuzz_handle_transfer(FUZZ_DATA *fuzz)
365
204k
{
366
204k
  int rc = 0;
367
204k
  CURLM *multi_handle;
368
204k
  int still_running; /* keep number of running handles */
369
204k
  CURLMsg *msg; /* for picking up messages with the transfer status */
370
204k
  int msgs_left; /* how many messages are left */
371
204k
  int double_timeout = 0;
372
204k
  fd_set fdread;
373
204k
  fd_set fdwrite;
374
204k
  fd_set fdexcep;
375
204k
  struct timeval timeout;
376
204k
  int select_rc;
377
204k
  CURLMcode mc;
378
204k
  int maxfd = -1;
379
204k
  long curl_timeo = -1;
380
204k
  int ii;
381
204k
  FUZZ_SOCKET_MANAGER *sman[FUZZ_NUM_CONNECTIONS];
382
383
614k
  for(ii = 0; ii < FUZZ_NUM_CONNECTIONS; ii++) {
384
409k
    sman[ii] = &fuzz->sockman[ii];
385
386
    /* Set up the starting index for responses. */
387
409k
    sman[ii]->response_index = 1;
388
409k
  }
389
390
  /* init a multi stack */
391
204k
  multi_handle = curl_multi_init();
392
393
  /* add the individual transfers */
394
204k
  curl_multi_add_handle(multi_handle, fuzz->easy);
395
396
  /* Do an initial process. This might end the transfer immediately. */
397
204k
  curl_multi_perform(multi_handle, &still_running);
398
204k
  FV_PRINTF(fuzz,
399
204k
            "FUZZ: Initial perform; still running? %d \n",
400
204k
            still_running);
401
402
239k
  while(still_running) {
403
    /* Reset the sets of file descriptors. */
404
37.6k
    FD_ZERO(&fdread);
405
37.6k
    FD_ZERO(&fdwrite);
406
37.6k
    FD_ZERO(&fdexcep);
407
408
    /* Set a timeout of 10ms. This is lower than recommended by the multi guide
409
       but we're not going to any remote servers, so everything should complete
410
       very quickly. */
411
37.6k
    timeout.tv_sec = 0;
412
37.6k
    timeout.tv_usec = 10000;
413
414
    /* get file descriptors from the transfers */
415
37.6k
    mc = curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);
416
37.6k
    if(mc != CURLM_OK) {
417
0
      fprintf(stderr, "curl_multi_fdset() failed, code %d.\n", mc);
418
0
      rc = -1;
419
0
      break;
420
0
    }
421
422
113k
    for(ii = 0; ii < FUZZ_NUM_CONNECTIONS; ii++) {
423
      /* Add the socket FD into the readable set if connected. */
424
75.3k
      if(sman[ii]->fd_state == FUZZ_SOCK_OPEN) {
425
32.1k
        FD_SET(sman[ii]->fd, &fdread);
426
427
        /* Work out the maximum FD between the cURL file descriptors and the
428
           server FD. */
429
32.1k
        maxfd = FUZZ_MAX(sman[ii]->fd, maxfd);
430
32.1k
      }
431
75.3k
    }
432
433
    /* Work out what file descriptors need work. */
434
37.6k
    rc = fuzz_select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout);
435
436
37.6k
    if(rc == -1) {
437
      /* Had an issue while selecting a file descriptor. Let's just exit. */
438
0
      FV_PRINTF(fuzz, "FUZZ: select failed, exiting \n");
439
0
      break;
440
0
    }
441
442
    /* Check to see if a server file descriptor is readable. If it is,
443
       then send the next response from the fuzzing data. */
444
37.6k
    int server_data_sent = 0;
445
112k
    for(ii = 0; ii < FUZZ_NUM_CONNECTIONS; ii++) {
446
75.2k
      if(sman[ii]->fd_state == FUZZ_SOCK_OPEN &&
447
75.2k
         FD_ISSET(sman[ii]->fd, &fdread)) {
448
28.9k
        rc = fuzz_send_next_response(fuzz, sman[ii]);
449
28.9k
        if(rc != 0) {
450
          /* Failed to send a response. Break out here. */
451
87
          break;
452
87
        }
453
28.8k
        server_data_sent = 1;
454
28.8k
      }
455
75.2k
    }
456
457
    /* Stall detection: exit after two consecutive iterations where no new
458
       data was provided to curl. This handles both select() timeouts and
459
       cases where curl registers a writable fd but cannot make progress
460
       (e.g. HTTP/2 egress stuck with no real peer to drain to). */
461
37.6k
    if(!server_data_sent) {
462
8.82k
      FV_PRINTF(fuzz, "FUZZ: No data sent; stall count %d \n", double_timeout);
463
8.82k
      if(double_timeout == 1) {
464
3.03k
        break;
465
3.03k
      }
466
5.79k
      double_timeout = 1;
467
5.79k
    }
468
28.8k
    else {
469
28.8k
      double_timeout = 0;
470
28.8k
    }
471
472
34.6k
    curl_multi_perform(multi_handle, &still_running);
473
34.6k
  }
474
475
  /* Remove the easy handle from the multi stack. */
476
204k
  curl_multi_remove_handle(multi_handle, fuzz->easy);
477
478
  /* Clean up the multi handle - the top level function will handle the easy
479
     handle. */
480
204k
  curl_multi_cleanup(multi_handle);
481
482
204k
  return rc;
483
204k
}
484
485
/**
486
 * Sends the next fuzzing response to the server file descriptor.
487
 */
488
int fuzz_send_next_response(FUZZ_DATA *fuzz, FUZZ_SOCKET_MANAGER *sman)
489
28.9k
{
490
28.9k
  int rc = 0;
491
28.9k
  ssize_t ret_in;
492
28.9k
  ssize_t ret_out;
493
28.9k
  char buffer[8192];
494
28.9k
  const uint8_t *data;
495
28.9k
  size_t data_len;
496
497
  /* Need to read all data sent by the client so the file descriptor becomes
498
     unreadable. Because the file descriptor is non-blocking we won't just
499
     hang here. */
500
78.7k
  do {
501
78.7k
    ret_in = read(sman->fd, buffer, sizeof(buffer));
502
78.7k
    if(fuzz->verbose && ret_in > 0) {
503
0
      printf("FUZZ[%d]: Received %zu bytes \n==>\n", sman->index, ret_in);
504
0
      fwrite(buffer, ret_in, 1, stdout);
505
0
      printf("\n<==\n");
506
0
    }
507
78.7k
  } while (ret_in > 0);
508
509
  /* Now send a response to the request that the client just made. */
510
28.9k
  FV_PRINTF(fuzz,
511
28.9k
            "FUZZ[%d]: Sending next response: %d \n",
512
28.9k
            sman->index,
513
28.9k
            sman->response_index);
514
28.9k
  data = sman->responses[sman->response_index].data;
515
28.9k
  data_len = sman->responses[sman->response_index].data_len;
516
517
28.9k
  if(data != NULL) {
518
28.9k
    if(write(sman->fd, data, data_len) != (ssize_t)data_len) {
519
      /* Failed to write the data back to the client. Prevent any further
520
         testing. */
521
87
      rc = -1;
522
87
    }
523
28.9k
  }
524
525
  /* Work out if there are any more responses. If not, then shut down the
526
     server. */
527
28.9k
  sman->response_index++;
528
529
28.9k
  if(sman->response_index >= TLV_MAX_NUM_RESPONSES ||
530
28.9k
     sman->responses[sman->response_index].data == NULL) {
531
23.5k
    FV_PRINTF(fuzz,
532
23.5k
              "FUZZ[%d]: Shutting down server socket: %d \n",
533
23.5k
              sman->index,
534
23.5k
              sman->fd);
535
23.5k
    shutdown(sman->fd, SHUT_WR);
536
23.5k
    sman->fd_state = FUZZ_SOCK_SHUTDOWN;
537
23.5k
  }
538
539
28.9k
  return rc;
540
28.9k
}
541
542
/**
543
 * Wrapper for select() so profiling can track it.
544
 */
545
int fuzz_select(int nfds,
546
                fd_set *readfds,
547
                fd_set *writefds,
548
                fd_set *exceptfds,
549
37.6k
                struct timeval *timeout) {
550
37.6k
  return select(nfds, readfds, writefds, exceptfds, timeout);
551
37.6k
}
552
553
/**
554
 * Set allowed protocols based on the compile options.
555
 *
556
 * Note that it can only use ONE of the FUZZ_PROTOCOLS_* defines.
557
 */
558
int fuzz_set_allowed_protocols(FUZZ_DATA *fuzz)
559
215k
{
560
215k
  int rc = 0;
561
215k
  const char *allowed_protocols = "";
562
563
#ifdef FUZZ_PROTOCOLS_ALL
564
  /* Do not allow telnet currently as it accepts input from stdin. */
565
  allowed_protocols =
566
    "dict,file,ftp,ftps,gopher,gophers,http,https,imap,imaps,"
567
    "mqtt,pop3,pop3s,"
568
    "ldap,ldaps,"
569
    "rtmp,rtmpe,rtmps,rtmpt,rtmpte,rtmpts,"
570
    "scp,"
571
    "sftp,"
572
    "rtsp,smb,smbs,smtp,smtps,tftp,"
573
    "ws,wss";
574
#endif
575
#ifdef FUZZ_PROTOCOLS_DICT
576
  allowed_protocols = "dict";
577
#endif
578
#ifdef FUZZ_PROTOCOLS_FILE
579
  allowed_protocols = "file";
580
#endif
581
#ifdef FUZZ_PROTOCOLS_FTP
582
  allowed_protocols = "ftp,ftps";
583
#endif
584
#ifdef FUZZ_PROTOCOLS_GOPHER
585
  allowed_protocols = "gopher,gophers";
586
#endif
587
#ifdef FUZZ_PROTOCOLS_HTTP
588
  allowed_protocols = "http";
589
#endif
590
#ifdef FUZZ_PROTOCOLS_HTTPS
591
  allowed_protocols = "https";
592
#endif
593
#ifdef FUZZ_PROTOCOLS_IMAP
594
  allowed_protocols = "imap,imaps";
595
#endif
596
#ifdef FUZZ_PROTOCOLS_LDAP
597
  allowed_protocols = "ldap,ldaps";
598
#endif
599
#ifdef FUZZ_PROTOCOLS_MQTT
600
  allowed_protocols = "mqtt";
601
#endif
602
#ifdef FUZZ_PROTOCOLS_POP3
603
  allowed_protocols = "pop3,pop3s";
604
#endif
605
#ifdef FUZZ_PROTOCOLS_RTMP
606
  allowed_protocols = "rtmp,rtmpe,rtmps,rtmpt,rtmpte,rtmpts";
607
#endif
608
#ifdef FUZZ_PROTOCOLS_RTSP
609
  allowed_protocols = "rtsp";
610
#endif
611
#ifdef FUZZ_PROTOCOLS_SCP
612
  allowed_protocols = "scp";
613
#endif
614
#ifdef FUZZ_PROTOCOLS_SFTP
615
  allowed_protocols = "sftp";
616
#endif
617
#ifdef FUZZ_PROTOCOLS_SMB
618
  allowed_protocols = "smb,smbs";
619
#endif
620
#ifdef FUZZ_PROTOCOLS_SMTP
621
  allowed_protocols = "smtp,smtps";
622
#endif
623
#ifdef FUZZ_PROTOCOLS_TFTP
624
  allowed_protocols = "tftp";
625
#endif
626
#ifdef FUZZ_PROTOCOLS_WS
627
  // http is required by websockets
628
  allowed_protocols = "http,ws,wss";
629
20.0k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_CONNECT_ONLY, 2L));
630
20.0k
#endif
631
632
215k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_PROTOCOLS_STR, allowed_protocols));
633
634
215k
EXIT_LABEL:
635
636
215k
  return rc;
637
204k
}
fuzz_set_allowed_protocols(fuzz_data*)
Line
Count
Source
559
195k
{
560
195k
  int rc = 0;
561
195k
  const char *allowed_protocols = "";
562
563
#ifdef FUZZ_PROTOCOLS_ALL
564
  /* Do not allow telnet currently as it accepts input from stdin. */
565
  allowed_protocols =
566
    "dict,file,ftp,ftps,gopher,gophers,http,https,imap,imaps,"
567
    "mqtt,pop3,pop3s,"
568
    "ldap,ldaps,"
569
    "rtmp,rtmpe,rtmps,rtmpt,rtmpte,rtmpts,"
570
    "scp,"
571
    "sftp,"
572
    "rtsp,smb,smbs,smtp,smtps,tftp,"
573
    "ws,wss";
574
#endif
575
#ifdef FUZZ_PROTOCOLS_DICT
576
  allowed_protocols = "dict";
577
#endif
578
#ifdef FUZZ_PROTOCOLS_FILE
579
  allowed_protocols = "file";
580
#endif
581
#ifdef FUZZ_PROTOCOLS_FTP
582
  allowed_protocols = "ftp,ftps";
583
#endif
584
#ifdef FUZZ_PROTOCOLS_GOPHER
585
  allowed_protocols = "gopher,gophers";
586
#endif
587
#ifdef FUZZ_PROTOCOLS_HTTP
588
  allowed_protocols = "http";
589
#endif
590
#ifdef FUZZ_PROTOCOLS_HTTPS
591
  allowed_protocols = "https";
592
#endif
593
#ifdef FUZZ_PROTOCOLS_IMAP
594
  allowed_protocols = "imap,imaps";
595
#endif
596
195k
#ifdef FUZZ_PROTOCOLS_LDAP
597
195k
  allowed_protocols = "ldap,ldaps";
598
195k
#endif
599
#ifdef FUZZ_PROTOCOLS_MQTT
600
  allowed_protocols = "mqtt";
601
#endif
602
#ifdef FUZZ_PROTOCOLS_POP3
603
  allowed_protocols = "pop3,pop3s";
604
#endif
605
#ifdef FUZZ_PROTOCOLS_RTMP
606
  allowed_protocols = "rtmp,rtmpe,rtmps,rtmpt,rtmpte,rtmpts";
607
#endif
608
#ifdef FUZZ_PROTOCOLS_RTSP
609
  allowed_protocols = "rtsp";
610
#endif
611
#ifdef FUZZ_PROTOCOLS_SCP
612
  allowed_protocols = "scp";
613
#endif
614
#ifdef FUZZ_PROTOCOLS_SFTP
615
  allowed_protocols = "sftp";
616
#endif
617
#ifdef FUZZ_PROTOCOLS_SMB
618
  allowed_protocols = "smb,smbs";
619
#endif
620
#ifdef FUZZ_PROTOCOLS_SMTP
621
  allowed_protocols = "smtp,smtps";
622
#endif
623
#ifdef FUZZ_PROTOCOLS_TFTP
624
  allowed_protocols = "tftp";
625
#endif
626
#ifdef FUZZ_PROTOCOLS_WS
627
  // http is required by websockets
628
  allowed_protocols = "http,ws,wss";
629
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_CONNECT_ONLY, 2L));
630
#endif
631
632
195k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_PROTOCOLS_STR, allowed_protocols));
633
634
195k
EXIT_LABEL:
635
636
195k
  return rc;
637
184k
}
fuzz_set_allowed_protocols(fuzz_data*)
Line
Count
Source
559
20.0k
{
560
20.0k
  int rc = 0;
561
20.0k
  const char *allowed_protocols = "";
562
563
#ifdef FUZZ_PROTOCOLS_ALL
564
  /* Do not allow telnet currently as it accepts input from stdin. */
565
  allowed_protocols =
566
    "dict,file,ftp,ftps,gopher,gophers,http,https,imap,imaps,"
567
    "mqtt,pop3,pop3s,"
568
    "ldap,ldaps,"
569
    "rtmp,rtmpe,rtmps,rtmpt,rtmpte,rtmpts,"
570
    "scp,"
571
    "sftp,"
572
    "rtsp,smb,smbs,smtp,smtps,tftp,"
573
    "ws,wss";
574
#endif
575
#ifdef FUZZ_PROTOCOLS_DICT
576
  allowed_protocols = "dict";
577
#endif
578
#ifdef FUZZ_PROTOCOLS_FILE
579
  allowed_protocols = "file";
580
#endif
581
#ifdef FUZZ_PROTOCOLS_FTP
582
  allowed_protocols = "ftp,ftps";
583
#endif
584
#ifdef FUZZ_PROTOCOLS_GOPHER
585
  allowed_protocols = "gopher,gophers";
586
#endif
587
#ifdef FUZZ_PROTOCOLS_HTTP
588
  allowed_protocols = "http";
589
#endif
590
#ifdef FUZZ_PROTOCOLS_HTTPS
591
  allowed_protocols = "https";
592
#endif
593
#ifdef FUZZ_PROTOCOLS_IMAP
594
  allowed_protocols = "imap,imaps";
595
#endif
596
#ifdef FUZZ_PROTOCOLS_LDAP
597
  allowed_protocols = "ldap,ldaps";
598
#endif
599
#ifdef FUZZ_PROTOCOLS_MQTT
600
  allowed_protocols = "mqtt";
601
#endif
602
#ifdef FUZZ_PROTOCOLS_POP3
603
  allowed_protocols = "pop3,pop3s";
604
#endif
605
#ifdef FUZZ_PROTOCOLS_RTMP
606
  allowed_protocols = "rtmp,rtmpe,rtmps,rtmpt,rtmpte,rtmpts";
607
#endif
608
#ifdef FUZZ_PROTOCOLS_RTSP
609
  allowed_protocols = "rtsp";
610
#endif
611
#ifdef FUZZ_PROTOCOLS_SCP
612
  allowed_protocols = "scp";
613
#endif
614
#ifdef FUZZ_PROTOCOLS_SFTP
615
  allowed_protocols = "sftp";
616
#endif
617
#ifdef FUZZ_PROTOCOLS_SMB
618
  allowed_protocols = "smb,smbs";
619
#endif
620
#ifdef FUZZ_PROTOCOLS_SMTP
621
  allowed_protocols = "smtp,smtps";
622
#endif
623
#ifdef FUZZ_PROTOCOLS_TFTP
624
  allowed_protocols = "tftp";
625
#endif
626
20.0k
#ifdef FUZZ_PROTOCOLS_WS
627
  // http is required by websockets
628
20.0k
  allowed_protocols = "http,ws,wss";
629
20.0k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_CONNECT_ONLY, 2L));
630
20.0k
#endif
631
632
20.0k
  FTRY(curl_easy_setopt(fuzz->easy, CURLOPT_PROTOCOLS_STR, allowed_protocols));
633
634
20.0k
EXIT_LABEL:
635
636
20.0k
  return rc;
637
20.0k
}