Coverage Report

Created: 2026-10-15 06:25

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