/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 |