Coverage Report

Created: 2026-10-15 06:25

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
591k
void ProbeTransferResults(CURL* easy) {
59
591k
  char* string_result = nullptr;
60
591k
  long long_result = 0;
61
591k
  double double_result = 0;
62
591k
  curl_off_t offset_result = 0;
63
591k
  curl_socket_t socket_result = CURL_SOCKET_BAD;
64
591k
  struct curl_certinfo* certinfo_result = nullptr;
65
66
591k
  (void)curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL, &string_result);
67
591k
  (void)curl_easy_getinfo(easy, CURLINFO_RESPONSE_CODE, &long_result);
68
591k
  (void)curl_easy_getinfo(easy, CURLINFO_TOTAL_TIME, &double_result);
69
591k
  (void)curl_easy_getinfo(easy, CURLINFO_SIZE_DOWNLOAD_T, &offset_result);
70
591k
  (void)curl_easy_getinfo(easy, CURLINFO_ACTIVESOCKET, &socket_result);
71
591k
  (void)curl_easy_getinfo(easy, CURLINFO_CERTINFO, &certinfo_result);
72
73
591k
  struct curl_header* header = nullptr;
74
591k
  (void)curl_easy_header(easy, "Content-Type", 0, kAllHeaderOrigins, -1, &header);
75
591k
  header = nullptr;
76
1.28M
  for (std::size_t index = 0; index < kMaxResultHeaders; ++index) {
77
1.26M
    header = curl_easy_nextheader(easy, kAllHeaderOrigins, -1, header);
78
1.26M
    if (header == nullptr) {
79
573k
      break;
80
573k
    }
81
1.26M
  }
82
591k
}
scenario_runner.cc:proto_fuzzer::(anonymous namespace)::ProbeTransferResults(void*)
Line
Count
Source
58
295k
void ProbeTransferResults(CURL* easy) {
59
295k
  char* string_result = nullptr;
60
295k
  long long_result = 0;
61
295k
  double double_result = 0;
62
295k
  curl_off_t offset_result = 0;
63
295k
  curl_socket_t socket_result = CURL_SOCKET_BAD;
64
295k
  struct curl_certinfo* certinfo_result = nullptr;
65
66
295k
  (void)curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL, &string_result);
67
295k
  (void)curl_easy_getinfo(easy, CURLINFO_RESPONSE_CODE, &long_result);
68
295k
  (void)curl_easy_getinfo(easy, CURLINFO_TOTAL_TIME, &double_result);
69
295k
  (void)curl_easy_getinfo(easy, CURLINFO_SIZE_DOWNLOAD_T, &offset_result);
70
295k
  (void)curl_easy_getinfo(easy, CURLINFO_ACTIVESOCKET, &socket_result);
71
295k
  (void)curl_easy_getinfo(easy, CURLINFO_CERTINFO, &certinfo_result);
72
73
295k
  struct curl_header* header = nullptr;
74
295k
  (void)curl_easy_header(easy, "Content-Type", 0, kAllHeaderOrigins, -1, &header);
75
295k
  header = nullptr;
76
643k
  for (std::size_t index = 0; index < kMaxResultHeaders; ++index) {
77
634k
    header = curl_easy_nextheader(easy, kAllHeaderOrigins, -1, header);
78
634k
    if (header == nullptr) {
79
286k
      break;
80
286k
    }
81
634k
  }
82
295k
}
scenario_runner.cc:proto_fuzzer::(anonymous namespace)::ProbeTransferResults(void*)
Line
Count
Source
58
295k
void ProbeTransferResults(CURL* easy) {
59
295k
  char* string_result = nullptr;
60
295k
  long long_result = 0;
61
295k
  double double_result = 0;
62
295k
  curl_off_t offset_result = 0;
63
295k
  curl_socket_t socket_result = CURL_SOCKET_BAD;
64
295k
  struct curl_certinfo* certinfo_result = nullptr;
65
66
295k
  (void)curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL, &string_result);
67
295k
  (void)curl_easy_getinfo(easy, CURLINFO_RESPONSE_CODE, &long_result);
68
295k
  (void)curl_easy_getinfo(easy, CURLINFO_TOTAL_TIME, &double_result);
69
295k
  (void)curl_easy_getinfo(easy, CURLINFO_SIZE_DOWNLOAD_T, &offset_result);
70
295k
  (void)curl_easy_getinfo(easy, CURLINFO_ACTIVESOCKET, &socket_result);
71
295k
  (void)curl_easy_getinfo(easy, CURLINFO_CERTINFO, &certinfo_result);
72
73
295k
  struct curl_header* header = nullptr;
74
295k
  (void)curl_easy_header(easy, "Content-Type", 0, kAllHeaderOrigins, -1, &header);
75
295k
  header = nullptr;
76
643k
  for (std::size_t index = 0; index < kMaxResultHeaders; ++index) {
77
634k
    header = curl_easy_nextheader(easy, kAllHeaderOrigins, -1, header);
78
634k
    if (header == nullptr) {
79
286k
      break;
80
286k
    }
81
634k
  }
82
295k
}
83
84
/// Map a Scheme enum to the URL scheme literal.
85
328k
const char* SchemePrefix(curl::fuzzer::proto::Scheme scheme) {
86
328k
  switch (scheme) {
87
175k
    case curl::fuzzer::proto::SCHEME_HTTP:
88
175k
      return "http";
89
91.8k
    case curl::fuzzer::proto::SCHEME_HTTPS:
90
91.8k
      return "https";
91
20.3k
    case curl::fuzzer::proto::SCHEME_WS:
92
20.3k
      return "ws";
93
12.8k
    case curl::fuzzer::proto::SCHEME_WSS:
94
12.8k
      return "wss";
95
5.82k
    case curl::fuzzer::proto::SCHEME_TELNET:
96
5.82k
      return "telnet";
97
8.91k
    case curl::fuzzer::proto::SCHEME_FTP:
98
8.91k
      return "ftp";
99
2.51k
    case curl::fuzzer::proto::SCHEME_TFTP:
100
2.51k
      return "tftp";
101
5.77k
    case curl::fuzzer::proto::SCHEME_GOPHER:
102
5.77k
      return "gopher";
103
4.43k
    case curl::fuzzer::proto::SCHEME_GOPHERS:
104
4.43k
      return "gophers";
105
276
    case curl::fuzzer::proto::SCHEME_UNSPECIFIED:
106
277
    default:
107
277
      return nullptr;
108
328k
  }
109
328k
}
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
315k
                                                          ScenarioRunMode mode) {
118
315k
  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
315k
  if (mode == ScenarioRunMode::kH2ProxyCoverage) {
130
13.0k
#if defined(PROTO_FUZZER_HAS_TLS_MOCK_SERVER)
131
13.0k
    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
13.0k
  }
139
140
302k
  if (mode == ScenarioRunMode::kHttp2Coverage) {
141
19.6k
    return std::make_unique<H2CleartextMockServer>();
142
19.6k
  }
143
144
282k
  if (mode == ScenarioRunMode::kTlsHttp2Coverage) {
145
16.5k
#if defined(PROTO_FUZZER_HAS_TLS_MOCK_SERVER)
146
16.5k
    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.5k
  }
153
154
265k
  if (mode == ScenarioRunMode::kSocks4Coverage) {
155
16.9k
    return std::make_unique<Socks4MockServer>(scenario.socks_proxy_mode());
156
16.9k
  }
157
158
249k
  switch (scenario.scheme()) {
159
124k
    case curl::fuzzer::proto::SCHEME_HTTP:
160
124k
      if (mode == ScenarioRunMode::kApiLifecycle) {
161
23.8k
        return std::make_unique<MockServer>(MultiDrivePolicy::kFromApiPlan);
162
23.8k
      }
163
100k
      return std::make_unique<MockServer>();
164
64.8k
    case curl::fuzzer::proto::SCHEME_HTTPS:
165
64.8k
#if defined(PROTO_FUZZER_HAS_TLS_MOCK_SERVER)
166
64.8k
      if (mode == ScenarioRunMode::kTlsCoverage) {
167
63.2k
        return std::make_unique<TlsMockServer>(scenario.tls_certificate_chain());
168
63.2k
      }
169
#else
170
      (void)mode;
171
#endif
172
1.60k
      return std::make_unique<MockServer>();
173
5.77k
    case curl::fuzzer::proto::SCHEME_GOPHER:
174
5.77k
      return mode == ScenarioRunMode::kGopherCoverage ? std::make_unique<MockServer>() : nullptr;
175
4.43k
    case curl::fuzzer::proto::SCHEME_GOPHERS:
176
4.43k
#if defined(PROTO_FUZZER_HAS_TLS_MOCK_SERVER)
177
4.43k
      if (mode == ScenarioRunMode::kGopherCoverage) {
178
4.42k
        return std::make_unique<TlsMockServer>(scenario.tls_certificate_chain());
179
4.42k
      }
180
4
      return nullptr;
181
#else
182
      return nullptr;
183
#endif
184
19.6k
    case curl::fuzzer::proto::SCHEME_WS:
185
19.6k
      return std::make_unique<WebSocketMockServer>();
186
12.8k
    case curl::fuzzer::proto::SCHEME_WSS:
187
12.8k
      if (mode == ScenarioRunMode::kTlsWebSocketCoverage) {
188
11.6k
#if defined(PROTO_FUZZER_HAS_TLS_MOCK_SERVER)
189
11.6k
        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
11.6k
      }
196
      // Preserve the compatibility target's historical interpretation of WSS
197
      // inputs, which exposed TLS bytes directly to the plaintext WS peer.
198
1.18k
      return std::make_unique<WebSocketMockServer>();
199
5.43k
    case curl::fuzzer::proto::SCHEME_TELNET:
200
5.43k
      return std::make_unique<TelnetMockServer>();
201
8.91k
    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.91k
      if (mode == ScenarioRunMode::kFtpCoverage) {
206
8.90k
        return std::make_unique<FtpMockServer>();
207
8.90k
      }
208
5
      return nullptr;
209
2.51k
    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.51k
      if (mode == ScenarioRunMode::kTftpCoverage) {
214
2.50k
        return std::make_unique<TftpMockServer>();
215
2.50k
      }
216
4
      return nullptr;
217
0
    case curl::fuzzer::proto::SCHEME_UNSPECIFIED:
218
0
    default:
219
0
      return nullptr;
220
249k
  }
221
249k
}
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
314k
void ObserveProtocolPeerState(MockServerBase* mock) {
229
314k
  if (auto* ftp = dynamic_cast<FtpMockServer*>(mock)) {
230
8.83k
    const volatile std::size_t ftp_observation[] = {ftp->control_transcript().size(), ftp->uploaded_data().size(),
231
8.83k
                                                    ftp->opened_data_connection_count()};
232
8.83k
    (void)ftp_observation;
233
8.83k
  }
234
235
314k
  if (auto* tftp = dynamic_cast<TftpMockServer*>(mock)) {
236
2.47k
    const volatile std::size_t tftp_observation[] = {tftp->received_datagrams().size(), tftp->request_port(),
237
2.47k
                                                     tftp->transfer_port()};
238
2.47k
    (void)tftp_observation;
239
2.47k
  }
240
241
314k
  if (auto* websocket = dynamic_cast<WebSocketMockServer*>(mock)) {
242
32.4k
    const volatile std::size_t websocket_observation = websocket->manual_received_bytes();
243
32.4k
    (void)websocket_observation;
244
32.4k
  }
245
246
314k
#if defined(PROTO_FUZZER_HAS_TLS_MOCK_SERVER)
247
314k
  if (auto* websocket = dynamic_cast<TlsWebSocketMockServer*>(mock)) {
248
11.5k
    const volatile std::size_t tls_websocket_observation = websocket->completed_handshake_count();
249
11.5k
    (void)tls_websocket_observation;
250
11.5k
  }
251
314k
#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
314k
}
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
701k
int RunScenario(const curl::fuzzer::proto::Scenario& scenario, ScenarioRunMode mode) {
272
701k
  if (mode == ScenarioRunMode::kMultiTransfer) {
273
45.4k
    (void)proto_fuzzer::RunMultiTransferScenario(scenario);
274
45.4k
    return 0;
275
45.4k
  }
276
277
656k
  const char* prefix = SchemePrefix(scenario.scheme());
278
656k
  if (prefix == nullptr || scenario.host_path().empty()) {
279
5.25k
    return 0;
280
5.25k
  }
281
282
651k
  std::unique_ptr<MockServerBase> mock = MakeMockServerForScenario(scenario, mode);
283
651k
  if (!mock) {
284
32
    return 0;
285
32
  }
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
651k
  TransferSession transfer;
290
651k
  (void)transfer.PrepareInputFiles(scenario, mode);
291
651k
  if (!transfer.Initialize()) {
292
2.13k
    return 0;
293
2.13k
  }
294
649k
  CURL* easy = transfer.easy();
295
649k
  const std::string url = std::string(prefix) + "://" + scenario.host_path();
296
652k
  const auto configure_easy = [&] {
297
652k
    transfer.ApplyBaseline(scenario.scheme(), scenario.trace_ids());
298
652k
    (void)curl_easy_setopt(easy, CURLOPT_URL, url.c_str());
299
652k
    mock->Install(easy);
300
652k
    transfer.ApplyInputFiles();
301
    // Repeat the option-count bound for compatibility and direct callers.
302
652k
    (void)ApplyScenarioOptions(easy, scenario);
303
652k
  };
scenario_runner.cc:proto_fuzzer::RunScenario(curl::fuzzer::proto::Scenario const&, proto_fuzzer::ScenarioRunMode)::$_1::operator()() const
Line
Count
Source
296
326k
  const auto configure_easy = [&] {
297
326k
    transfer.ApplyBaseline(scenario.scheme(), scenario.trace_ids());
298
326k
    (void)curl_easy_setopt(easy, CURLOPT_URL, url.c_str());
299
326k
    mock->Install(easy);
300
326k
    transfer.ApplyInputFiles();
301
    // Repeat the option-count bound for compatibility and direct callers.
302
326k
    (void)ApplyScenarioOptions(easy, scenario);
303
326k
  };
scenario_runner.cc:proto_fuzzer::RunScenario(curl::fuzzer::proto::Scenario const&, proto_fuzzer::ScenarioRunMode)::$_1::operator()() const
Line
Count
Source
296
326k
  const auto configure_easy = [&] {
297
326k
    transfer.ApplyBaseline(scenario.scheme(), scenario.trace_ids());
298
326k
    (void)curl_easy_setopt(easy, CURLOPT_URL, url.c_str());
299
326k
    mock->Install(easy);
300
326k
    transfer.ApplyInputFiles();
301
    // Repeat the option-count bound for compatibility and direct callers.
302
326k
    (void)ApplyScenarioOptions(easy, scenario);
303
326k
  };
304
649k
  configure_easy();
305
649k
  transfer.ConfigureAltSvcRouting(scenario.host_path());
306
307
649k
  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.1k
    (void)curl_easy_setopt(easy, CURLOPT_CONNECT_TO, nullptr);
312
30.1k
  }
313
314
649k
  const curl::fuzzer::proto::ApiPlan* api_plan =
315
649k
      mode == ScenarioRunMode::kApiLifecycle && scenario.has_api_plan() ? &scenario.api_plan() : nullptr;
316
649k
  if (api_plan != nullptr && api_plan->reset_easy() && transfer.ResetConfiguration()) {
317
3.79k
    configure_easy();
318
3.79k
  }
319
320
649k
  ApiLifecycle* api_lifecycle = nullptr;
321
649k
  if (api_plan != nullptr) {
322
11.5k
    api_lifecycle = transfer.InstallApiLifecycle(*api_plan, url);
323
11.5k
    mock->SetMultiObserver([api_lifecycle](CURLM* multi) { api_lifecycle->SetActiveMulti(multi); });
324
11.5k
  }
325
326
649k
  ScenarioRequestData* request_data = transfer.InstallRequestData(scenario, mode == ScenarioRunMode::kResolverCoverage);
327
649k
  if (mode == ScenarioRunMode::kResolverCoverage && !request_data->resolve_entries_ready()) {
328
42
    return 0;
329
42
  }
330
648k
  mock->ConfigureRequestData(request_data);
331
648k
  const auto drive_mode = api_plan == nullptr ? curl::fuzzer::proto::API_DRIVE_MULTI_PERFORM : api_plan->drive_mode();
332
648k
  CURLcode transfer_result = CURLE_FAILED_INIT;
333
648k
  if (drive_mode == curl::fuzzer::proto::API_DRIVE_EASY_PERFORM) {
334
1.74k
    transfer_result = mock->DriveEasyScenario(easy, scenario);
335
647k
  } else if (drive_mode == curl::fuzzer::proto::API_DRIVE_EASY_EVENTS) {
336
700
    transfer_result = mock->DriveEasyScenario(easy, scenario, true);
337
646k
  } else if (drive_mode == curl::fuzzer::proto::API_DRIVE_CONNECT_ONLY) {
338
178
    transfer_result = mock->DriveConnectOnlyScenario(easy, scenario).connect_result;
339
646k
  } else {
340
646k
    transfer_result = mock->DriveScenario(easy, scenario);
341
646k
  }
342
648k
  if (api_lifecycle != nullptr) {
343
11.5k
    const bool retains_internal_multi = drive_mode == curl::fuzzer::proto::API_DRIVE_EASY_PERFORM ||
344
9.84k
                                        drive_mode == curl::fuzzer::proto::API_DRIVE_EASY_EVENTS ||
345
9.14k
                                        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.5k
    const bool probe_upkeep =
350
11.5k
        retains_internal_multi && transfer_result != CURLE_OUT_OF_MEMORY && transfer_result != CURLE_FAILED_INIT;
351
11.5k
    api_lifecycle->ProbeTransferResults(probe_upkeep);
352
11.5k
    api_lifecycle->ProbeEasyDuplication();
353
11.5k
#if defined(PROTO_FUZZER_OBSERVE_STATE)
354
11.5k
    const volatile std::size_t api_observation[] = {
355
11.5k
        api_lifecycle->response_pause_returned(), api_lifecycle->response_bytes_received(),
356
11.5k
        api_lifecycle->reentrant_probes_run(), api_lifecycle->reentrant_recursive_rejections()};
357
11.5k
    (void)api_observation;
358
11.5k
#endif
359
637k
  } else if (mode != ScenarioRunMode::kFastProtocol) {
360
591k
    ProbeTransferResults(easy);
361
591k
  }
362
363
648k
#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
648k
  const RequestBuildStats& stats = request_data->stats();
368
648k
  const UploadScriptState& upload = request_data->upload_state();
369
648k
  const volatile std::size_t request_observation[] = {stats.request_headers,
370
648k
                                                      stats.resolve_entries,
371
648k
                                                      stats.telnet_options,
372
648k
                                                      stats.mime_parts,
373
648k
                                                      stats.mime_headers,
374
648k
                                                      stats.generated_mime_bytes,
375
648k
                                                      request_data->upload_callbacks_installed(),
376
648k
                                                      upload.data_size(),
377
648k
                                                      upload.read_step_count(),
378
648k
                                                      upload.offset(),
379
648k
                                                      upload.scripted()};
380
648k
  (void)request_observation;
381
648k
  ObserveProtocolPeerState(mock.get());
382
648k
#endif
383
648k
  return 0;
384
649k
}
proto_fuzzer::RunScenario(curl::fuzzer::proto::Scenario const&, proto_fuzzer::ScenarioRunMode)
Line
Count
Source
271
350k
int RunScenario(const curl::fuzzer::proto::Scenario& scenario, ScenarioRunMode mode) {
272
350k
  if (mode == ScenarioRunMode::kMultiTransfer) {
273
22.7k
    (void)proto_fuzzer::RunMultiTransferScenario(scenario);
274
22.7k
    return 0;
275
22.7k
  }
276
277
328k
  const char* prefix = SchemePrefix(scenario.scheme());
278
328k
  if (prefix == nullptr || scenario.host_path().empty()) {
279
2.62k
    return 0;
280
2.62k
  }
281
282
325k
  std::unique_ptr<MockServerBase> mock = MakeMockServerForScenario(scenario, mode);
283
325k
  if (!mock) {
284
16
    return 0;
285
16
  }
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
325k
  TransferSession transfer;
290
325k
  (void)transfer.PrepareInputFiles(scenario, mode);
291
325k
  if (!transfer.Initialize()) {
292
1.06k
    return 0;
293
1.06k
  }
294
324k
  CURL* easy = transfer.easy();
295
324k
  const std::string url = std::string(prefix) + "://" + scenario.host_path();
296
324k
  const auto configure_easy = [&] {
297
324k
    transfer.ApplyBaseline(scenario.scheme(), scenario.trace_ids());
298
324k
    (void)curl_easy_setopt(easy, CURLOPT_URL, url.c_str());
299
324k
    mock->Install(easy);
300
324k
    transfer.ApplyInputFiles();
301
    // Repeat the option-count bound for compatibility and direct callers.
302
324k
    (void)ApplyScenarioOptions(easy, scenario);
303
324k
  };
304
324k
  configure_easy();
305
324k
  transfer.ConfigureAltSvcRouting(scenario.host_path());
306
307
324k
  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
324k
  const curl::fuzzer::proto::ApiPlan* api_plan =
315
324k
      mode == ScenarioRunMode::kApiLifecycle && scenario.has_api_plan() ? &scenario.api_plan() : nullptr;
316
324k
  if (api_plan != nullptr && api_plan->reset_easy() && transfer.ResetConfiguration()) {
317
1.89k
    configure_easy();
318
1.89k
  }
319
320
324k
  ApiLifecycle* api_lifecycle = nullptr;
321
324k
  if (api_plan != nullptr) {
322
5.79k
    api_lifecycle = transfer.InstallApiLifecycle(*api_plan, url);
323
5.79k
    mock->SetMultiObserver([api_lifecycle](CURLM* multi) { api_lifecycle->SetActiveMulti(multi); });
324
5.79k
  }
325
326
324k
  ScenarioRequestData* request_data = transfer.InstallRequestData(scenario, mode == ScenarioRunMode::kResolverCoverage);
327
324k
  if (mode == ScenarioRunMode::kResolverCoverage && !request_data->resolve_entries_ready()) {
328
21
    return 0;
329
21
  }
330
324k
  mock->ConfigureRequestData(request_data);
331
324k
  const auto drive_mode = api_plan == nullptr ? curl::fuzzer::proto::API_DRIVE_MULTI_PERFORM : api_plan->drive_mode();
332
324k
  CURLcode transfer_result = CURLE_FAILED_INIT;
333
324k
  if (drive_mode == curl::fuzzer::proto::API_DRIVE_EASY_PERFORM) {
334
871
    transfer_result = mock->DriveEasyScenario(easy, scenario);
335
323k
  } else if (drive_mode == curl::fuzzer::proto::API_DRIVE_EASY_EVENTS) {
336
350
    transfer_result = mock->DriveEasyScenario(easy, scenario, true);
337
323k
  } else if (drive_mode == curl::fuzzer::proto::API_DRIVE_CONNECT_ONLY) {
338
89
    transfer_result = mock->DriveConnectOnlyScenario(easy, scenario).connect_result;
339
323k
  } else {
340
323k
    transfer_result = mock->DriveScenario(easy, scenario);
341
323k
  }
342
324k
  if (api_lifecycle != nullptr) {
343
5.79k
    const bool retains_internal_multi = drive_mode == curl::fuzzer::proto::API_DRIVE_EASY_PERFORM ||
344
4.92k
                                        drive_mode == curl::fuzzer::proto::API_DRIVE_EASY_EVENTS ||
345
4.57k
                                        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.79k
    const bool probe_upkeep =
350
5.79k
        retains_internal_multi && transfer_result != CURLE_OUT_OF_MEMORY && transfer_result != CURLE_FAILED_INIT;
351
5.79k
    api_lifecycle->ProbeTransferResults(probe_upkeep);
352
5.79k
    api_lifecycle->ProbeEasyDuplication();
353
5.79k
#if defined(PROTO_FUZZER_OBSERVE_STATE)
354
5.79k
    const volatile std::size_t api_observation[] = {
355
5.79k
        api_lifecycle->response_pause_returned(), api_lifecycle->response_bytes_received(),
356
5.79k
        api_lifecycle->reentrant_probes_run(), api_lifecycle->reentrant_recursive_rejections()};
357
5.79k
    (void)api_observation;
358
5.79k
#endif
359
318k
  } else if (mode != ScenarioRunMode::kFastProtocol) {
360
295k
    ProbeTransferResults(easy);
361
295k
  }
362
363
324k
#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
324k
  const RequestBuildStats& stats = request_data->stats();
368
324k
  const UploadScriptState& upload = request_data->upload_state();
369
324k
  const volatile std::size_t request_observation[] = {stats.request_headers,
370
324k
                                                      stats.resolve_entries,
371
324k
                                                      stats.telnet_options,
372
324k
                                                      stats.mime_parts,
373
324k
                                                      stats.mime_headers,
374
324k
                                                      stats.generated_mime_bytes,
375
324k
                                                      request_data->upload_callbacks_installed(),
376
324k
                                                      upload.data_size(),
377
324k
                                                      upload.read_step_count(),
378
324k
                                                      upload.offset(),
379
324k
                                                      upload.scripted()};
380
324k
  (void)request_observation;
381
324k
  ObserveProtocolPeerState(mock.get());
382
324k
#endif
383
324k
  return 0;
384
324k
}
proto_fuzzer::RunScenario(curl::fuzzer::proto::Scenario const&, proto_fuzzer::ScenarioRunMode)
Line
Count
Source
271
350k
int RunScenario(const curl::fuzzer::proto::Scenario& scenario, ScenarioRunMode mode) {
272
350k
  if (mode == ScenarioRunMode::kMultiTransfer) {
273
22.7k
    (void)proto_fuzzer::RunMultiTransferScenario(scenario);
274
22.7k
    return 0;
275
22.7k
  }
276
277
328k
  const char* prefix = SchemePrefix(scenario.scheme());
278
328k
  if (prefix == nullptr || scenario.host_path().empty()) {
279
2.62k
    return 0;
280
2.62k
  }
281
282
325k
  std::unique_ptr<MockServerBase> mock = MakeMockServerForScenario(scenario, mode);
283
325k
  if (!mock) {
284
16
    return 0;
285
16
  }
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
325k
  TransferSession transfer;
290
325k
  (void)transfer.PrepareInputFiles(scenario, mode);
291
325k
  if (!transfer.Initialize()) {
292
1.06k
    return 0;
293
1.06k
  }
294
324k
  CURL* easy = transfer.easy();
295
324k
  const std::string url = std::string(prefix) + "://" + scenario.host_path();
296
324k
  const auto configure_easy = [&] {
297
324k
    transfer.ApplyBaseline(scenario.scheme(), scenario.trace_ids());
298
324k
    (void)curl_easy_setopt(easy, CURLOPT_URL, url.c_str());
299
324k
    mock->Install(easy);
300
324k
    transfer.ApplyInputFiles();
301
    // Repeat the option-count bound for compatibility and direct callers.
302
324k
    (void)ApplyScenarioOptions(easy, scenario);
303
324k
  };
304
324k
  configure_easy();
305
324k
  transfer.ConfigureAltSvcRouting(scenario.host_path());
306
307
324k
  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
324k
  const curl::fuzzer::proto::ApiPlan* api_plan =
315
324k
      mode == ScenarioRunMode::kApiLifecycle && scenario.has_api_plan() ? &scenario.api_plan() : nullptr;
316
324k
  if (api_plan != nullptr && api_plan->reset_easy() && transfer.ResetConfiguration()) {
317
1.89k
    configure_easy();
318
1.89k
  }
319
320
324k
  ApiLifecycle* api_lifecycle = nullptr;
321
324k
  if (api_plan != nullptr) {
322
5.79k
    api_lifecycle = transfer.InstallApiLifecycle(*api_plan, url);
323
5.79k
    mock->SetMultiObserver([api_lifecycle](CURLM* multi) { api_lifecycle->SetActiveMulti(multi); });
324
5.79k
  }
325
326
324k
  ScenarioRequestData* request_data = transfer.InstallRequestData(scenario, mode == ScenarioRunMode::kResolverCoverage);
327
324k
  if (mode == ScenarioRunMode::kResolverCoverage && !request_data->resolve_entries_ready()) {
328
21
    return 0;
329
21
  }
330
324k
  mock->ConfigureRequestData(request_data);
331
324k
  const auto drive_mode = api_plan == nullptr ? curl::fuzzer::proto::API_DRIVE_MULTI_PERFORM : api_plan->drive_mode();
332
324k
  CURLcode transfer_result = CURLE_FAILED_INIT;
333
324k
  if (drive_mode == curl::fuzzer::proto::API_DRIVE_EASY_PERFORM) {
334
871
    transfer_result = mock->DriveEasyScenario(easy, scenario);
335
323k
  } else if (drive_mode == curl::fuzzer::proto::API_DRIVE_EASY_EVENTS) {
336
350
    transfer_result = mock->DriveEasyScenario(easy, scenario, true);
337
323k
  } else if (drive_mode == curl::fuzzer::proto::API_DRIVE_CONNECT_ONLY) {
338
89
    transfer_result = mock->DriveConnectOnlyScenario(easy, scenario).connect_result;
339
323k
  } else {
340
323k
    transfer_result = mock->DriveScenario(easy, scenario);
341
323k
  }
342
324k
  if (api_lifecycle != nullptr) {
343
5.79k
    const bool retains_internal_multi = drive_mode == curl::fuzzer::proto::API_DRIVE_EASY_PERFORM ||
344
4.92k
                                        drive_mode == curl::fuzzer::proto::API_DRIVE_EASY_EVENTS ||
345
4.57k
                                        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.79k
    const bool probe_upkeep =
350
5.79k
        retains_internal_multi && transfer_result != CURLE_OUT_OF_MEMORY && transfer_result != CURLE_FAILED_INIT;
351
5.79k
    api_lifecycle->ProbeTransferResults(probe_upkeep);
352
5.79k
    api_lifecycle->ProbeEasyDuplication();
353
5.79k
#if defined(PROTO_FUZZER_OBSERVE_STATE)
354
5.79k
    const volatile std::size_t api_observation[] = {
355
5.79k
        api_lifecycle->response_pause_returned(), api_lifecycle->response_bytes_received(),
356
5.79k
        api_lifecycle->reentrant_probes_run(), api_lifecycle->reentrant_recursive_rejections()};
357
5.79k
    (void)api_observation;
358
5.79k
#endif
359
318k
  } else if (mode != ScenarioRunMode::kFastProtocol) {
360
295k
    ProbeTransferResults(easy);
361
295k
  }
362
363
324k
#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
324k
  const RequestBuildStats& stats = request_data->stats();
368
324k
  const UploadScriptState& upload = request_data->upload_state();
369
324k
  const volatile std::size_t request_observation[] = {stats.request_headers,
370
324k
                                                      stats.resolve_entries,
371
324k
                                                      stats.telnet_options,
372
324k
                                                      stats.mime_parts,
373
324k
                                                      stats.mime_headers,
374
324k
                                                      stats.generated_mime_bytes,
375
324k
                                                      request_data->upload_callbacks_installed(),
376
324k
                                                      upload.data_size(),
377
324k
                                                      upload.read_step_count(),
378
324k
                                                      upload.offset(),
379
324k
                                                      upload.scripted()};
380
324k
  (void)request_observation;
381
324k
  ObserveProtocolPeerState(mock.get());
382
324k
#endif
383
324k
  return 0;
384
324k
}
385
386
}  // namespace proto_fuzzer