Coverage Report

Created: 2026-10-28 07:17

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
 * 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
}