Coverage Report

Created: 2026-10-22 06:56

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl_fuzzer/proto_fuzzer/scenario_runner.cc
Line
Count
Source
1
/*
2
 * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al.
3
 *
4
 * SPDX-License-Identifier: curl
5
 */
6
7
/// @file
8
/// @brief Implementation of RunScenario.
9
10
#include "proto_fuzzer/scenario_runner.h"
11
12
#include <curl/curl.h>
13
#include <curl/header.h>
14
15
#include <cstddef>
16
#include <memory>
17
#include <string>
18
19
#include "proto_fuzzer/api_lifecycle.h"
20
#include "proto_fuzzer/ftp_mock_server.h"
21
#include "proto_fuzzer/h2_cleartext_mock_server.h"
22
#include "proto_fuzzer/mock_server.h"
23
#include "proto_fuzzer/mock_server_base.h"
24
#include "proto_fuzzer/multi_transfer_runner.h"
25
#include "proto_fuzzer/option_apply.h"
26
#include "proto_fuzzer/request_data.h"
27
#include "proto_fuzzer/socks4_mock_server.h"
28
#include "proto_fuzzer/telnet_mock_server.h"
29
#include "proto_fuzzer/tftp_mock_server.h"
30
#include "proto_fuzzer/transfer_session.h"
31
#include "proto_fuzzer/websocket_mock_server.h"
32
33
#if defined(PROTO_FUZZER_HAS_TLS_MOCK_SERVER)
34
#include "proto_fuzzer/h2_origin_mock_server.h"
35
#include "proto_fuzzer/h2_proxy_mock_server.h"
36
#include "proto_fuzzer/tls_mock_server.h"
37
#include "proto_fuzzer/tls_websocket_mock_server.h"
38
#endif
39
#if defined(PROTO_FUZZER_HAS_HTTP3_MOCK_SERVER)
40
#include "proto_fuzzer/connect_udp_proxy_mock_server.h"
41
#include "proto_fuzzer/http3_mock_server.h"
42
#endif
43
44
namespace proto_fuzzer {
45
46
namespace {
47
48
constexpr unsigned int kAllHeaderOrigins = CURLH_HEADER | CURLH_TRAILER | CURLH_CONNECT | CURLH_1XX | CURLH_PSEUDO;
49
constexpr std::size_t kMaxResultHeaders = 16;
50
/// Probe each public getinfo return family and the response-header API after
51
/// curl has settled the transfer. Applications commonly inspect these APIs,
52
/// but a harness that only drives I/O leaves their type dispatch and
53
/// post-transfer state unexecuted even when the corresponding parser ran.
54
/// The chosen values are handle-owned or scalar: notably CERTINFO exercises
55
/// the pointer/slist dispatch family without materialising a separately-owned
56
/// cookie/engine list. Header iteration is capped independently of response
57
/// size so this unconditional coverage cannot dominate a fuzz iteration.
58
598k
void ProbeTransferResults(CURL* easy) {
59
598k
  char* string_result = nullptr;
60
598k
  long long_result = 0;
61
598k
  double double_result = 0;
62
598k
  curl_off_t offset_result = 0;
63
598k
  curl_socket_t socket_result = CURL_SOCKET_BAD;
64
598k
  struct curl_certinfo* certinfo_result = nullptr;
65
66
598k
  (void)curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL, &string_result);
67
598k
  (void)curl_easy_getinfo(easy, CURLINFO_RESPONSE_CODE, &long_result);
68
598k
  (void)curl_easy_getinfo(easy, CURLINFO_TOTAL_TIME, &double_result);
69
598k
  (void)curl_easy_getinfo(easy, CURLINFO_SIZE_DOWNLOAD_T, &offset_result);
70
598k
  (void)curl_easy_getinfo(easy, CURLINFO_ACTIVESOCKET, &socket_result);
71
598k
  (void)curl_easy_getinfo(easy, CURLINFO_CERTINFO, &certinfo_result);
72
73
598k
  struct curl_header* header = nullptr;
74
598k
  (void)curl_easy_header(easy, "Content-Type", 0, kAllHeaderOrigins, -1, &header);
75
598k
  header = nullptr;
76
1.30M
  for (std::size_t index = 0; index < kMaxResultHeaders; ++index) {
77
1.28M
    header = curl_easy_nextheader(easy, kAllHeaderOrigins, -1, header);
78
1.28M
    if (header == nullptr) {
79
581k
      break;
80
581k
    }
81
1.28M
  }
82
598k
}
scenario_runner.cc:proto_fuzzer::(anonymous namespace)::ProbeTransferResults(void*)
Line
Count
Source
58
299k
void ProbeTransferResults(CURL* easy) {
59
299k
  char* string_result = nullptr;
60
299k
  long long_result = 0;
61
299k
  double double_result = 0;
62
299k
  curl_off_t offset_result = 0;
63
299k
  curl_socket_t socket_result = CURL_SOCKET_BAD;
64
299k
  struct curl_certinfo* certinfo_result = nullptr;
65
66
299k
  (void)curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL, &string_result);
67
299k
  (void)curl_easy_getinfo(easy, CURLINFO_RESPONSE_CODE, &long_result);
68
299k
  (void)curl_easy_getinfo(easy, CURLINFO_TOTAL_TIME, &double_result);
69
299k
  (void)curl_easy_getinfo(easy, CURLINFO_SIZE_DOWNLOAD_T, &offset_result);
70
299k
  (void)curl_easy_getinfo(easy, CURLINFO_ACTIVESOCKET, &socket_result);
71
299k
  (void)curl_easy_getinfo(easy, CURLINFO_CERTINFO, &certinfo_result);
72
73
299k
  struct curl_header* header = nullptr;
74
299k
  (void)curl_easy_header(easy, "Content-Type", 0, kAllHeaderOrigins, -1, &header);
75
299k
  header = nullptr;
76
650k
  for (std::size_t index = 0; index < kMaxResultHeaders; ++index) {
77
641k
    header = curl_easy_nextheader(easy, kAllHeaderOrigins, -1, header);
78
641k
    if (header == nullptr) {
79
290k
      break;
80
290k
    }
81
641k
  }
82
299k
}
scenario_runner.cc:proto_fuzzer::(anonymous namespace)::ProbeTransferResults(void*)
Line
Count
Source
58
299k
void ProbeTransferResults(CURL* easy) {
59
299k
  char* string_result = nullptr;
60
299k
  long long_result = 0;
61
299k
  double double_result = 0;
62
299k
  curl_off_t offset_result = 0;
63
299k
  curl_socket_t socket_result = CURL_SOCKET_BAD;
64
299k
  struct curl_certinfo* certinfo_result = nullptr;
65
66
299k
  (void)curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL, &string_result);
67
299k
  (void)curl_easy_getinfo(easy, CURLINFO_RESPONSE_CODE, &long_result);
68
299k
  (void)curl_easy_getinfo(easy, CURLINFO_TOTAL_TIME, &double_result);
69
299k
  (void)curl_easy_getinfo(easy, CURLINFO_SIZE_DOWNLOAD_T, &offset_result);
70
299k
  (void)curl_easy_getinfo(easy, CURLINFO_ACTIVESOCKET, &socket_result);
71
299k
  (void)curl_easy_getinfo(easy, CURLINFO_CERTINFO, &certinfo_result);
72
73
299k
  struct curl_header* header = nullptr;
74
299k
  (void)curl_easy_header(easy, "Content-Type", 0, kAllHeaderOrigins, -1, &header);
75
299k
  header = nullptr;
76
650k
  for (std::size_t index = 0; index < kMaxResultHeaders; ++index) {
77
641k
    header = curl_easy_nextheader(easy, kAllHeaderOrigins, -1, header);
78
641k
    if (header == nullptr) {
79
290k
      break;
80
290k
    }
81
641k
  }
82
299k
}
83
84
/// Map a Scheme enum to the URL scheme literal.
85
333k
const char* SchemePrefix(curl::fuzzer::proto::Scheme scheme) {
86
333k
  switch (scheme) {
87
179k
    case curl::fuzzer::proto::SCHEME_HTTP:
88
179k
      return "http";
89
92.9k
    case curl::fuzzer::proto::SCHEME_HTTPS:
90
92.9k
      return "https";
91
20.4k
    case curl::fuzzer::proto::SCHEME_WS:
92
20.4k
      return "ws";
93
13.2k
    case curl::fuzzer::proto::SCHEME_WSS:
94
13.2k
      return "wss";
95
5.91k
    case curl::fuzzer::proto::SCHEME_TELNET:
96
5.91k
      return "telnet";
97
8.89k
    case curl::fuzzer::proto::SCHEME_FTP:
98
8.89k
      return "ftp";
99
2.47k
    case curl::fuzzer::proto::SCHEME_TFTP:
100
2.47k
      return "tftp";
101
5.74k
    case curl::fuzzer::proto::SCHEME_GOPHER:
102
5.74k
      return "gopher";
103
4.32k
    case curl::fuzzer::proto::SCHEME_GOPHERS:
104
4.32k
      return "gophers";
105
317
    case curl::fuzzer::proto::SCHEME_UNSPECIFIED:
106
317
    default:
107
317
      return nullptr;
108
333k
  }
109
333k
}
110
111
/// Pick the peer implementation authorized by both protocol and target mode.
112
/// The compatibility target must keep its historical raw-wire interpretation
113
/// of secure schemes, while dedicated TLS lanes expose decrypted application
114
/// protocols. Keeping that semantic boundary in the closed run-mode enum
115
/// prevents a new peer from silently changing old OSS-Fuzz reproducers.
116
std::unique_ptr<MockServerBase> MakeMockServerForScenario(const curl::fuzzer::proto::Scenario& scenario,
117
320k
                                                          ScenarioRunMode mode) {
118
320k
  if (mode == ScenarioRunMode::kHttp3Coverage) {
119
#if defined(PROTO_FUZZER_HAS_HTTP3_MOCK_SERVER)
120
    if (scenario.http3_plan().use_h1_connect_udp_proxy()) {
121
      return std::make_unique<ConnectUdpProxyMockServer>();
122
    }
123
    return std::make_unique<Http3MockServer>(scenario.tls_certificate_chain());
124
#else
125
0
    return nullptr;
126
0
#endif
127
0
  }
128
129
320k
  if (mode == ScenarioRunMode::kH2ProxyCoverage) {
130
12.8k
#if defined(PROTO_FUZZER_HAS_TLS_MOCK_SERVER)
131
12.8k
    return std::make_unique<H2ProxyMockServer>();
132
#else
133
    // MemorySanitizer builds deliberately omit OpenSSL. Keep the target
134
    // binary available to OSS-Fuzz, but do not pretend a plaintext mock can
135
    // negotiate the ALPN gate required to enter cf-h2-proxy.
136
    return nullptr;
137
#endif
138
12.8k
  }
139
140
307k
  if (mode == ScenarioRunMode::kHttp2Coverage) {
141
19.4k
    return std::make_unique<H2CleartextMockServer>();
142
19.4k
  }
143
144
288k
  if (mode == ScenarioRunMode::kTlsHttp2Coverage) {
145
16.9k
#if defined(PROTO_FUZZER_HAS_TLS_MOCK_SERVER)
146
16.9k
    return std::make_unique<H2OriginMockServer>(scenario.tls_certificate_chain(), scenario.tls_group_profile());
147
#else
148
    // This target remains buildable under MemorySanitizer, whose curl build
149
    // omits the OpenSSL server dependency required for TLS/ALPN h2.
150
    return nullptr;
151
#endif
152
16.9k
  }
153
154
271k
  if (mode == ScenarioRunMode::kSocks4Coverage) {
155
16.9k
    return std::make_unique<Socks4MockServer>(scenario.socks_proxy_mode());
156
16.9k
  }
157
158
254k
  switch (scenario.scheme()) {
159
128k
    case curl::fuzzer::proto::SCHEME_HTTP:
160
128k
      if (mode == ScenarioRunMode::kApiLifecycle) {
161
24.1k
        return std::make_unique<MockServer>(MultiDrivePolicy::kFromApiPlan);
162
24.1k
      }
163
104k
      return std::make_unique<MockServer>();
164
65.5k
    case curl::fuzzer::proto::SCHEME_HTTPS:
165
65.5k
#if defined(PROTO_FUZZER_HAS_TLS_MOCK_SERVER)
166
65.5k
      if (mode == ScenarioRunMode::kTlsCoverage) {
167
63.7k
        return std::make_unique<TlsMockServer>(scenario.tls_certificate_chain());
168
63.7k
      }
169
#else
170
      (void)mode;
171
#endif
172
1.74k
      return std::make_unique<MockServer>();
173
5.74k
    case curl::fuzzer::proto::SCHEME_GOPHER:
174
5.74k
      return mode == ScenarioRunMode::kGopherCoverage ? std::make_unique<MockServer>() : nullptr;
175
4.32k
    case curl::fuzzer::proto::SCHEME_GOPHERS:
176
4.32k
#if defined(PROTO_FUZZER_HAS_TLS_MOCK_SERVER)
177
4.32k
      if (mode == ScenarioRunMode::kGopherCoverage) {
178
4.31k
        return std::make_unique<TlsMockServer>(scenario.tls_certificate_chain());
179
4.31k
      }
180
2
      return nullptr;
181
#else
182
      return nullptr;
183
#endif
184
19.8k
    case curl::fuzzer::proto::SCHEME_WS:
185
19.8k
      return std::make_unique<WebSocketMockServer>();
186
13.2k
    case curl::fuzzer::proto::SCHEME_WSS:
187
13.2k
      if (mode == ScenarioRunMode::kTlsWebSocketCoverage) {
188
12.0k
#if defined(PROTO_FUZZER_HAS_TLS_MOCK_SERVER)
189
12.0k
        return std::make_unique<TlsWebSocketMockServer>(scenario.tls_certificate_chain());
190
#else
191
        // MemorySanitizer builds deliberately omit OpenSSL. Keep the target
192
        // available without pretending plaintext can carry a WSS exchange.
193
        return nullptr;
194
#endif
195
12.0k
      }
196
      // Preserve the compatibility target's historical interpretation of WSS
197
      // inputs, which exposed TLS bytes directly to the plaintext WS peer.
198
1.17k
      return std::make_unique<WebSocketMockServer>();
199
5.49k
    case curl::fuzzer::proto::SCHEME_TELNET:
200
5.49k
      return std::make_unique<TelnetMockServer>();
201
8.89k
    case curl::fuzzer::proto::SCHEME_FTP:
202
      // New numeric enum values may already occur in the historical mixed
203
      // corpus as unknown fields. Only the fixed FTP profile may reinterpret
204
      // one as a live two-channel protocol exchange.
205
8.89k
      if (mode == ScenarioRunMode::kFtpCoverage) {
206
8.88k
        return std::make_unique<FtpMockServer>();
207
8.88k
      }
208
4
      return nullptr;
209
2.46k
    case curl::fuzzer::proto::SCHEME_TFTP:
210
      // TFTP changes the callback transport from a preconnected stream to a
211
      // real UDP endpoint, so compatibility inputs must not opt into it merely
212
      // because this build learned a new enum value.
213
2.46k
      if (mode == ScenarioRunMode::kTftpCoverage) {
214
2.46k
        return std::make_unique<TftpMockServer>();
215
2.46k
      }
216
2
      return nullptr;
217
0
    case curl::fuzzer::proto::SCHEME_UNSPECIFIED:
218
0
    default:
219
0
      return nullptr;
220
254k
  }
221
254k
}
222
223
#if defined(PROTO_FUZZER_OBSERVE_STATE)
224
/// Retain protocol-specific outcomes in coverage and introspector builds.
225
/// These peers already collect bounded state for focused tests; reading it
226
/// after the transfer gives profiling builds visibility into the completed
227
/// exchange without changing the protocol work performed by ordinary fuzzers.
228
319k
void ObserveProtocolPeerState(MockServerBase* mock) {
229
319k
  if (auto* ftp = dynamic_cast<FtpMockServer*>(mock)) {
230
8.81k
    const volatile std::size_t ftp_observation[] = {ftp->control_transcript().size(), ftp->uploaded_data().size(),
231
8.81k
                                                    ftp->opened_data_connection_count()};
232
8.81k
    (void)ftp_observation;
233
8.81k
  }
234
235
319k
  if (auto* tftp = dynamic_cast<TftpMockServer*>(mock)) {
236
2.43k
    const volatile std::size_t tftp_observation[] = {tftp->received_datagrams().size(), tftp->request_port(),
237
2.43k
                                                     tftp->transfer_port()};
238
2.43k
    (void)tftp_observation;
239
2.43k
  }
240
241
319k
  if (auto* websocket = dynamic_cast<WebSocketMockServer*>(mock)) {
242
32.8k
    const volatile std::size_t websocket_observation = websocket->manual_received_bytes();
243
32.8k
    (void)websocket_observation;
244
32.8k
  }
245
246
319k
#if defined(PROTO_FUZZER_HAS_TLS_MOCK_SERVER)
247
319k
  if (auto* websocket = dynamic_cast<TlsWebSocketMockServer*>(mock)) {
248
11.8k
    const volatile std::size_t tls_websocket_observation = websocket->completed_handshake_count();
249
11.8k
    (void)tls_websocket_observation;
250
11.8k
  }
251
319k
#endif
252
253
#if defined(PROTO_FUZZER_HAS_HTTP3_MOCK_SERVER)
254
  if (auto* http3 = dynamic_cast<Http3MockServer*>(mock)) {
255
    const volatile std::size_t http3_observation[] = {http3->handshake_complete(), http3->request_headers_received(),
256
                                                      http3->executed_action_count(), http3->server_port()};
257
    (void)http3_observation;
258
  }
259
#endif
260
319k
}
261
#endif
262
263
}  // namespace
264
265
/// Execute one scenario under a complete target behaviour. An enum makes the
266
/// supported fast, coverage, and API paths explicit and prevents callers from
267
/// constructing meaningless combinations of independent switches.
268
/// @param scenario Structured transfer and optional API plan.
269
/// @param mode Runtime coverage and lifecycle policy for this invocation.
270
/// @return zero after either a bounded run or an ignored invalid scenario.
271
713k
int RunScenario(const curl::fuzzer::proto::Scenario& scenario, ScenarioRunMode mode) {
272
713k
  if (mode == ScenarioRunMode::kMultiTransfer) {
273
45.9k
    (void)proto_fuzzer::RunMultiTransferScenario(scenario);
274
45.9k
    return 0;
275
45.9k
  }
276
277
667k
  const char* prefix = SchemePrefix(scenario.scheme());
278
667k
  if (prefix == nullptr || scenario.host_path().empty()) {
279
5.69k
    return 0;
280
5.69k
  }
281
282
661k
  std::unique_ptr<MockServerBase> mock = MakeMockServerForScenario(scenario, mode);
283
661k
  if (!mock) {
284
26
    return 0;
285
26
  }
286
287
  // The peer survives every easy option/callback, including cleanup of a
288
  // bounded incomplete transfer. TransferSession owns the rest of that graph.
289
661k
  TransferSession transfer;
290
661k
  (void)transfer.PrepareInputFiles(scenario, mode);
291
661k
  if (!transfer.Initialize()) {
292
2.89k
    return 0;
293
2.89k
  }
294
658k
  CURL* easy = transfer.easy();
295
658k
  const std::string url = std::string(prefix) + "://" + scenario.host_path();
296
662k
  const auto configure_easy = [&] {
297
662k
    transfer.ApplyBaseline(scenario.scheme(), scenario.trace_ids());
298
662k
    (void)curl_easy_setopt(easy, CURLOPT_URL, url.c_str());
299
662k
    mock->Install(easy);
300
662k
    transfer.ApplyInputFiles();
301
    // Repeat the option-count bound for compatibility and direct callers.
302
662k
    (void)ApplyScenarioOptions(easy, scenario);
303
662k
  };
scenario_runner.cc:proto_fuzzer::RunScenario(curl::fuzzer::proto::Scenario const&, proto_fuzzer::ScenarioRunMode)::$_1::operator()() const
Line
Count
Source
296
331k
  const auto configure_easy = [&] {
297
331k
    transfer.ApplyBaseline(scenario.scheme(), scenario.trace_ids());
298
331k
    (void)curl_easy_setopt(easy, CURLOPT_URL, url.c_str());
299
331k
    mock->Install(easy);
300
331k
    transfer.ApplyInputFiles();
301
    // Repeat the option-count bound for compatibility and direct callers.
302
331k
    (void)ApplyScenarioOptions(easy, scenario);
303
331k
  };
scenario_runner.cc:proto_fuzzer::RunScenario(curl::fuzzer::proto::Scenario const&, proto_fuzzer::ScenarioRunMode)::$_1::operator()() const
Line
Count
Source
296
331k
  const auto configure_easy = [&] {
297
331k
    transfer.ApplyBaseline(scenario.scheme(), scenario.trace_ids());
298
331k
    (void)curl_easy_setopt(easy, CURLOPT_URL, url.c_str());
299
331k
    mock->Install(easy);
300
331k
    transfer.ApplyInputFiles();
301
    // Repeat the option-count bound for compatibility and direct callers.
302
331k
    (void)ApplyScenarioOptions(easy, scenario);
303
331k
  };
304
658k
  configure_easy();
305
658k
  transfer.ConfigureAltSvcRouting(scenario.host_path());
306
307
658k
  if (mode == ScenarioRunMode::kResolverCoverage) {
308
    // The normal CONNECT_TO baseline deliberately bypasses DNS. This lane
309
    // removes only that override; OPENSOCKET still returns the in-process
310
    // socketpair, so no resolved address can receive network traffic.
311
30.0k
    (void)curl_easy_setopt(easy, CURLOPT_CONNECT_TO, nullptr);
312
30.0k
  }
313
314
658k
  const curl::fuzzer::proto::ApiPlan* api_plan =
315
658k
      mode == ScenarioRunMode::kApiLifecycle && scenario.has_api_plan() ? &scenario.api_plan() : nullptr;
316
658k
  if (api_plan != nullptr && api_plan->reset_easy() && transfer.ResetConfiguration()) {
317
3.84k
    configure_easy();
318
3.84k
  }
319
320
658k
  ApiLifecycle* api_lifecycle = nullptr;
321
658k
  if (api_plan != nullptr) {
322
11.9k
    api_lifecycle = transfer.InstallApiLifecycle(*api_plan, url);
323
11.9k
    mock->SetMultiObserver([api_lifecycle](CURLM* multi) { api_lifecycle->SetActiveMulti(multi); });
324
11.9k
  }
325
326
658k
  ScenarioRequestData* request_data = transfer.InstallRequestData(scenario, mode == ScenarioRunMode::kResolverCoverage);
327
658k
  if (mode == ScenarioRunMode::kResolverCoverage && !request_data->resolve_entries_ready()) {
328
40
    return 0;
329
40
  }
330
658k
  mock->ConfigureRequestData(request_data);
331
658k
  const auto drive_mode = api_plan == nullptr ? curl::fuzzer::proto::API_DRIVE_MULTI_PERFORM : api_plan->drive_mode();
332
658k
  CURLcode transfer_result = CURLE_FAILED_INIT;
333
658k
  if (drive_mode == curl::fuzzer::proto::API_DRIVE_EASY_PERFORM) {
334
1.76k
    transfer_result = mock->DriveEasyScenario(easy, scenario);
335
657k
  } else if (drive_mode == curl::fuzzer::proto::API_DRIVE_EASY_EVENTS) {
336
738
    transfer_result = mock->DriveEasyScenario(easy, scenario, true);
337
656k
  } else if (drive_mode == curl::fuzzer::proto::API_DRIVE_CONNECT_ONLY) {
338
206
    transfer_result = mock->DriveConnectOnlyScenario(easy, scenario).connect_result;
339
656k
  } else {
340
656k
    transfer_result = mock->DriveScenario(easy, scenario);
341
656k
  }
342
658k
  if (api_lifecycle != nullptr) {
343
11.9k
    const bool retains_internal_multi = drive_mode == curl::fuzzer::proto::API_DRIVE_EASY_PERFORM ||
344
10.1k
                                        drive_mode == curl::fuzzer::proto::API_DRIVE_EASY_EVENTS ||
345
9.45k
                                        drive_mode == curl::fuzzer::proto::API_DRIVE_CONNECT_ONLY;
346
    // An easy entrypoint can fail before attaching its private multi. The
347
    // drive mode alone therefore cannot establish curl_easy_upkeep's state
348
    // precondition. These are the two pre-attachment failure results.
349
11.9k
    const bool probe_upkeep =
350
11.9k
        retains_internal_multi && transfer_result != CURLE_OUT_OF_MEMORY && transfer_result != CURLE_FAILED_INIT;
351
11.9k
    api_lifecycle->ProbeTransferResults(probe_upkeep);
352
11.9k
    api_lifecycle->ProbeEasyDuplication();
353
11.9k
#if defined(PROTO_FUZZER_OBSERVE_STATE)
354
11.9k
    const volatile std::size_t api_observation[] = {
355
11.9k
        api_lifecycle->response_pause_returned(), api_lifecycle->response_bytes_received(),
356
11.9k
        api_lifecycle->reentrant_probes_run(), api_lifecycle->reentrant_recursive_rejections()};
357
11.9k
    (void)api_observation;
358
11.9k
#endif
359
646k
  } else if (mode != ScenarioRunMode::kFastProtocol) {
360
598k
    ProbeTransferResults(easy);
361
598k
  }
362
363
658k
#if defined(PROTO_FUZZER_OBSERVE_STATE)
364
  // TransferSession still owns these completed callback states. Local volatile
365
  // stores retain their getter calls during profiling without adding work to
366
  // ordinary fuzz builds or changing the transfer's callback configuration.
367
658k
  const RequestBuildStats& stats = request_data->stats();
368
658k
  const UploadScriptState& upload = request_data->upload_state();
369
658k
  const volatile std::size_t request_observation[] = {stats.request_headers,
370
658k
                                                      stats.resolve_entries,
371
658k
                                                      stats.telnet_options,
372
658k
                                                      stats.mime_parts,
373
658k
                                                      stats.mime_headers,
374
658k
                                                      stats.generated_mime_bytes,
375
658k
                                                      request_data->upload_callbacks_installed(),
376
658k
                                                      upload.data_size(),
377
658k
                                                      upload.read_step_count(),
378
658k
                                                      upload.offset(),
379
658k
                                                      upload.scripted()};
380
658k
  (void)request_observation;
381
658k
  ObserveProtocolPeerState(mock.get());
382
658k
#endif
383
658k
  return 0;
384
658k
}
proto_fuzzer::RunScenario(curl::fuzzer::proto::Scenario const&, proto_fuzzer::ScenarioRunMode)
Line
Count
Source
271
356k
int RunScenario(const curl::fuzzer::proto::Scenario& scenario, ScenarioRunMode mode) {
272
356k
  if (mode == ScenarioRunMode::kMultiTransfer) {
273
22.9k
    (void)proto_fuzzer::RunMultiTransferScenario(scenario);
274
22.9k
    return 0;
275
22.9k
  }
276
277
333k
  const char* prefix = SchemePrefix(scenario.scheme());
278
333k
  if (prefix == nullptr || scenario.host_path().empty()) {
279
2.84k
    return 0;
280
2.84k
  }
281
282
330k
  std::unique_ptr<MockServerBase> mock = MakeMockServerForScenario(scenario, mode);
283
330k
  if (!mock) {
284
13
    return 0;
285
13
  }
286
287
  // The peer survives every easy option/callback, including cleanup of a
288
  // bounded incomplete transfer. TransferSession owns the rest of that graph.
289
330k
  TransferSession transfer;
290
330k
  (void)transfer.PrepareInputFiles(scenario, mode);
291
330k
  if (!transfer.Initialize()) {
292
1.44k
    return 0;
293
1.44k
  }
294
329k
  CURL* easy = transfer.easy();
295
329k
  const std::string url = std::string(prefix) + "://" + scenario.host_path();
296
329k
  const auto configure_easy = [&] {
297
329k
    transfer.ApplyBaseline(scenario.scheme(), scenario.trace_ids());
298
329k
    (void)curl_easy_setopt(easy, CURLOPT_URL, url.c_str());
299
329k
    mock->Install(easy);
300
329k
    transfer.ApplyInputFiles();
301
    // Repeat the option-count bound for compatibility and direct callers.
302
329k
    (void)ApplyScenarioOptions(easy, scenario);
303
329k
  };
304
329k
  configure_easy();
305
329k
  transfer.ConfigureAltSvcRouting(scenario.host_path());
306
307
329k
  if (mode == ScenarioRunMode::kResolverCoverage) {
308
    // The normal CONNECT_TO baseline deliberately bypasses DNS. This lane
309
    // removes only that override; OPENSOCKET still returns the in-process
310
    // socketpair, so no resolved address can receive network traffic.
311
15.0k
    (void)curl_easy_setopt(easy, CURLOPT_CONNECT_TO, nullptr);
312
15.0k
  }
313
314
329k
  const curl::fuzzer::proto::ApiPlan* api_plan =
315
329k
      mode == ScenarioRunMode::kApiLifecycle && scenario.has_api_plan() ? &scenario.api_plan() : nullptr;
316
329k
  if (api_plan != nullptr && api_plan->reset_easy() && transfer.ResetConfiguration()) {
317
1.92k
    configure_easy();
318
1.92k
  }
319
320
329k
  ApiLifecycle* api_lifecycle = nullptr;
321
329k
  if (api_plan != nullptr) {
322
5.97k
    api_lifecycle = transfer.InstallApiLifecycle(*api_plan, url);
323
5.97k
    mock->SetMultiObserver([api_lifecycle](CURLM* multi) { api_lifecycle->SetActiveMulti(multi); });
324
5.97k
  }
325
326
329k
  ScenarioRequestData* request_data = transfer.InstallRequestData(scenario, mode == ScenarioRunMode::kResolverCoverage);
327
329k
  if (mode == ScenarioRunMode::kResolverCoverage && !request_data->resolve_entries_ready()) {
328
20
    return 0;
329
20
  }
330
329k
  mock->ConfigureRequestData(request_data);
331
329k
  const auto drive_mode = api_plan == nullptr ? curl::fuzzer::proto::API_DRIVE_MULTI_PERFORM : api_plan->drive_mode();
332
329k
  CURLcode transfer_result = CURLE_FAILED_INIT;
333
329k
  if (drive_mode == curl::fuzzer::proto::API_DRIVE_EASY_PERFORM) {
334
883
    transfer_result = mock->DriveEasyScenario(easy, scenario);
335
328k
  } else if (drive_mode == curl::fuzzer::proto::API_DRIVE_EASY_EVENTS) {
336
369
    transfer_result = mock->DriveEasyScenario(easy, scenario, true);
337
328k
  } else if (drive_mode == curl::fuzzer::proto::API_DRIVE_CONNECT_ONLY) {
338
103
    transfer_result = mock->DriveConnectOnlyScenario(easy, scenario).connect_result;
339
328k
  } else {
340
328k
    transfer_result = mock->DriveScenario(easy, scenario);
341
328k
  }
342
329k
  if (api_lifecycle != nullptr) {
343
5.97k
    const bool retains_internal_multi = drive_mode == curl::fuzzer::proto::API_DRIVE_EASY_PERFORM ||
344
5.09k
                                        drive_mode == curl::fuzzer::proto::API_DRIVE_EASY_EVENTS ||
345
4.72k
                                        drive_mode == curl::fuzzer::proto::API_DRIVE_CONNECT_ONLY;
346
    // An easy entrypoint can fail before attaching its private multi. The
347
    // drive mode alone therefore cannot establish curl_easy_upkeep's state
348
    // precondition. These are the two pre-attachment failure results.
349
5.97k
    const bool probe_upkeep =
350
5.97k
        retains_internal_multi && transfer_result != CURLE_OUT_OF_MEMORY && transfer_result != CURLE_FAILED_INIT;
351
5.97k
    api_lifecycle->ProbeTransferResults(probe_upkeep);
352
5.97k
    api_lifecycle->ProbeEasyDuplication();
353
5.97k
#if defined(PROTO_FUZZER_OBSERVE_STATE)
354
5.97k
    const volatile std::size_t api_observation[] = {
355
5.97k
        api_lifecycle->response_pause_returned(), api_lifecycle->response_bytes_received(),
356
5.97k
        api_lifecycle->reentrant_probes_run(), api_lifecycle->reentrant_recursive_rejections()};
357
5.97k
    (void)api_observation;
358
5.97k
#endif
359
323k
  } else if (mode != ScenarioRunMode::kFastProtocol) {
360
299k
    ProbeTransferResults(easy);
361
299k
  }
362
363
329k
#if defined(PROTO_FUZZER_OBSERVE_STATE)
364
  // TransferSession still owns these completed callback states. Local volatile
365
  // stores retain their getter calls during profiling without adding work to
366
  // ordinary fuzz builds or changing the transfer's callback configuration.
367
329k
  const RequestBuildStats& stats = request_data->stats();
368
329k
  const UploadScriptState& upload = request_data->upload_state();
369
329k
  const volatile std::size_t request_observation[] = {stats.request_headers,
370
329k
                                                      stats.resolve_entries,
371
329k
                                                      stats.telnet_options,
372
329k
                                                      stats.mime_parts,
373
329k
                                                      stats.mime_headers,
374
329k
                                                      stats.generated_mime_bytes,
375
329k
                                                      request_data->upload_callbacks_installed(),
376
329k
                                                      upload.data_size(),
377
329k
                                                      upload.read_step_count(),
378
329k
                                                      upload.offset(),
379
329k
                                                      upload.scripted()};
380
329k
  (void)request_observation;
381
329k
  ObserveProtocolPeerState(mock.get());
382
329k
#endif
383
329k
  return 0;
384
329k
}
proto_fuzzer::RunScenario(curl::fuzzer::proto::Scenario const&, proto_fuzzer::ScenarioRunMode)
Line
Count
Source
271
356k
int RunScenario(const curl::fuzzer::proto::Scenario& scenario, ScenarioRunMode mode) {
272
356k
  if (mode == ScenarioRunMode::kMultiTransfer) {
273
22.9k
    (void)proto_fuzzer::RunMultiTransferScenario(scenario);
274
22.9k
    return 0;
275
22.9k
  }
276
277
333k
  const char* prefix = SchemePrefix(scenario.scheme());
278
333k
  if (prefix == nullptr || scenario.host_path().empty()) {
279
2.84k
    return 0;
280
2.84k
  }
281
282
330k
  std::unique_ptr<MockServerBase> mock = MakeMockServerForScenario(scenario, mode);
283
330k
  if (!mock) {
284
13
    return 0;
285
13
  }
286
287
  // The peer survives every easy option/callback, including cleanup of a
288
  // bounded incomplete transfer. TransferSession owns the rest of that graph.
289
330k
  TransferSession transfer;
290
330k
  (void)transfer.PrepareInputFiles(scenario, mode);
291
330k
  if (!transfer.Initialize()) {
292
1.44k
    return 0;
293
1.44k
  }
294
329k
  CURL* easy = transfer.easy();
295
329k
  const std::string url = std::string(prefix) + "://" + scenario.host_path();
296
329k
  const auto configure_easy = [&] {
297
329k
    transfer.ApplyBaseline(scenario.scheme(), scenario.trace_ids());
298
329k
    (void)curl_easy_setopt(easy, CURLOPT_URL, url.c_str());
299
329k
    mock->Install(easy);
300
329k
    transfer.ApplyInputFiles();
301
    // Repeat the option-count bound for compatibility and direct callers.
302
329k
    (void)ApplyScenarioOptions(easy, scenario);
303
329k
  };
304
329k
  configure_easy();
305
329k
  transfer.ConfigureAltSvcRouting(scenario.host_path());
306
307
329k
  if (mode == ScenarioRunMode::kResolverCoverage) {
308
    // The normal CONNECT_TO baseline deliberately bypasses DNS. This lane
309
    // removes only that override; OPENSOCKET still returns the in-process
310
    // socketpair, so no resolved address can receive network traffic.
311
15.0k
    (void)curl_easy_setopt(easy, CURLOPT_CONNECT_TO, nullptr);
312
15.0k
  }
313
314
329k
  const curl::fuzzer::proto::ApiPlan* api_plan =
315
329k
      mode == ScenarioRunMode::kApiLifecycle && scenario.has_api_plan() ? &scenario.api_plan() : nullptr;
316
329k
  if (api_plan != nullptr && api_plan->reset_easy() && transfer.ResetConfiguration()) {
317
1.92k
    configure_easy();
318
1.92k
  }
319
320
329k
  ApiLifecycle* api_lifecycle = nullptr;
321
329k
  if (api_plan != nullptr) {
322
5.97k
    api_lifecycle = transfer.InstallApiLifecycle(*api_plan, url);
323
5.97k
    mock->SetMultiObserver([api_lifecycle](CURLM* multi) { api_lifecycle->SetActiveMulti(multi); });
324
5.97k
  }
325
326
329k
  ScenarioRequestData* request_data = transfer.InstallRequestData(scenario, mode == ScenarioRunMode::kResolverCoverage);
327
329k
  if (mode == ScenarioRunMode::kResolverCoverage && !request_data->resolve_entries_ready()) {
328
20
    return 0;
329
20
  }
330
329k
  mock->ConfigureRequestData(request_data);
331
329k
  const auto drive_mode = api_plan == nullptr ? curl::fuzzer::proto::API_DRIVE_MULTI_PERFORM : api_plan->drive_mode();
332
329k
  CURLcode transfer_result = CURLE_FAILED_INIT;
333
329k
  if (drive_mode == curl::fuzzer::proto::API_DRIVE_EASY_PERFORM) {
334
883
    transfer_result = mock->DriveEasyScenario(easy, scenario);
335
328k
  } else if (drive_mode == curl::fuzzer::proto::API_DRIVE_EASY_EVENTS) {
336
369
    transfer_result = mock->DriveEasyScenario(easy, scenario, true);
337
328k
  } else if (drive_mode == curl::fuzzer::proto::API_DRIVE_CONNECT_ONLY) {
338
103
    transfer_result = mock->DriveConnectOnlyScenario(easy, scenario).connect_result;
339
328k
  } else {
340
328k
    transfer_result = mock->DriveScenario(easy, scenario);
341
328k
  }
342
329k
  if (api_lifecycle != nullptr) {
343
5.97k
    const bool retains_internal_multi = drive_mode == curl::fuzzer::proto::API_DRIVE_EASY_PERFORM ||
344
5.09k
                                        drive_mode == curl::fuzzer::proto::API_DRIVE_EASY_EVENTS ||
345
4.72k
                                        drive_mode == curl::fuzzer::proto::API_DRIVE_CONNECT_ONLY;
346
    // An easy entrypoint can fail before attaching its private multi. The
347
    // drive mode alone therefore cannot establish curl_easy_upkeep's state
348
    // precondition. These are the two pre-attachment failure results.
349
5.97k
    const bool probe_upkeep =
350
5.97k
        retains_internal_multi && transfer_result != CURLE_OUT_OF_MEMORY && transfer_result != CURLE_FAILED_INIT;
351
5.97k
    api_lifecycle->ProbeTransferResults(probe_upkeep);
352
5.97k
    api_lifecycle->ProbeEasyDuplication();
353
5.97k
#if defined(PROTO_FUZZER_OBSERVE_STATE)
354
5.97k
    const volatile std::size_t api_observation[] = {
355
5.97k
        api_lifecycle->response_pause_returned(), api_lifecycle->response_bytes_received(),
356
5.97k
        api_lifecycle->reentrant_probes_run(), api_lifecycle->reentrant_recursive_rejections()};
357
5.97k
    (void)api_observation;
358
5.97k
#endif
359
323k
  } else if (mode != ScenarioRunMode::kFastProtocol) {
360
299k
    ProbeTransferResults(easy);
361
299k
  }
362
363
329k
#if defined(PROTO_FUZZER_OBSERVE_STATE)
364
  // TransferSession still owns these completed callback states. Local volatile
365
  // stores retain their getter calls during profiling without adding work to
366
  // ordinary fuzz builds or changing the transfer's callback configuration.
367
329k
  const RequestBuildStats& stats = request_data->stats();
368
329k
  const UploadScriptState& upload = request_data->upload_state();
369
329k
  const volatile std::size_t request_observation[] = {stats.request_headers,
370
329k
                                                      stats.resolve_entries,
371
329k
                                                      stats.telnet_options,
372
329k
                                                      stats.mime_parts,
373
329k
                                                      stats.mime_headers,
374
329k
                                                      stats.generated_mime_bytes,
375
329k
                                                      request_data->upload_callbacks_installed(),
376
329k
                                                      upload.data_size(),
377
329k
                                                      upload.read_step_count(),
378
329k
                                                      upload.offset(),
379
329k
                                                      upload.scripted()};
380
329k
  (void)request_observation;
381
329k
  ObserveProtocolPeerState(mock.get());
382
329k
#endif
383
329k
  return 0;
384
329k
}
385
386
}  // namespace proto_fuzzer