curl-fuzzer C++ reference
Loading...
Searching...
No Matches
target_policy.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al.
3 *
4 * SPDX-License-Identifier: curl
5 */
6
9
11
12#include <curl/curl.h>
13
14#include <algorithm>
15#include <array>
16#include <cstddef>
17#include <cstdint>
18#include <limits>
19#include <string>
20
24
25namespace proto_fuzzer {
26
27namespace {
28
29// Linux raises smaller socket-buffer requests to an implementation minimum,
30// so 2048 is both cheap and reliably small enough to exercise short writes.
31constexpr std::uint32_t kDefaultBackpressureBufferBytes = 2048;
32
33// Values outside these ranges do not create useful new socket behavior for
34// the harness's bounded 4-16 KiB writes. Keeping them small also prevents a
35// mutated uint32 recv size from overflowing the int accepted by setsockopt.
36constexpr std::uint32_t kMinBackpressureBufferBytes = 2048;
37constexpr std::uint32_t kMaxBackpressureBufferBytes = 4096;
38constexpr std::uint32_t kMaxDrainBytesPerIteration = 1024;
39constexpr std::uint32_t kHaproxyConnectBufferBytes = 2048;
40constexpr std::uint32_t kHaproxyConnectDrainBytes = 48;
41constexpr std::uint32_t kMqttProbeBufferBytes = 2048;
42constexpr std::uint32_t kMqttProbeDrainBytes = 1024;
43constexpr std::uint32_t kSmtpProbeBufferBytes = 2048;
44constexpr std::uint32_t kSmtpProbeDrainBytes = 1024;
45
46// CURLOPT_LOCALPORTRANGE performs one synchronous bind attempt per selected
47// port. Retain a useful retry window without letting one mutation request tens
48// of thousands of syscalls from a single transfer.
49constexpr std::uint64_t kMaxLocalPortRange = 16;
50constexpr std::size_t kGopherTlsPeerKeyHostBytes = 10163;
51
52// A default, bounded identifier keeps newly generated inputs on the gateway's
53// /ipfs/ or /ipns/ namespace. The path, query, and fragment after it stay
54// mutation-controlled.
55constexpr char kDefaultIpfsCid[] = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi";
56constexpr char kDefaultIpnsName[] = "k51qzi5uqu5dlvj2baxnqndepeb86cbk3ng7n3i46uzyxzyqj2xjonzllnv0v8";
57
61template <typename RepeatedField>
62void TrimRepeated(RepeatedField* field, std::size_t limit) {
63 const std::size_t size = static_cast<std::size_t>(field->size());
64 if (size > limit) {
65 field->DeleteSubrange(static_cast<int>(limit), static_cast<int>(size - limit));
66 }
67}
68
71void TrimMetadata(std::string* value) {
72 if (value->size() > scenario_limits::kMaxMetadataBytes) {
74 }
75}
76
82void CanonicalizeTlsAuthority(curl::fuzzer::proto::Scenario* scenario) {
83 const std::string& host_path = scenario->host_path();
84 const std::size_t suffix_start = host_path.find_first_of("/?#");
85 if (suffix_start == std::string::npos) {
86 scenario->set_host_path("tls.test/");
87 return;
88 }
89 scenario->set_host_path("tls.test" + host_path.substr(suffix_start));
90}
91
95void CanonicalizeHttp3Authority(curl::fuzzer::proto::Scenario* scenario) {
96 const std::string& host_path = scenario->host_path();
97 if (!scenario->http3_plan().use_h1_connect_udp_proxy() || host_path.empty() || host_path.front() != '[') {
98 CanonicalizeTlsAuthority(scenario);
99 return;
100 }
101 const std::size_t suffix_start = host_path.find_first_of("/?#");
102 scenario->set_host_path(std::string("[::1]") +
103 (suffix_start == std::string::npos ? "/" : host_path.substr(suffix_start)));
104}
105
110void CanonicalizeAltSvcAuthority(curl::fuzzer::proto::Scenario* scenario) {
111 const std::string& host_path = scenario->host_path();
112 const std::size_t suffix_start = host_path.find_first_of("/?#");
113 if (suffix_start == std::string::npos) {
114 scenario->set_host_path("altsvc-origin.test/");
115 return;
116 }
117 scenario->set_host_path("altsvc-origin.test" + host_path.substr(suffix_start));
118}
119
122void CanonicalizeTlsCertificateChain(curl::fuzzer::proto::Scenario* scenario) {
123 switch (scenario->tls_certificate_chain()) {
124 case curl::fuzzer::proto::TLS_CERTIFICATE_CHAIN_DEFAULT_EC:
125 case curl::fuzzer::proto::TLS_CERTIFICATE_CHAIN_ALL_KEY_TYPES:
126 return;
127 default:
128 scenario->clear_tls_certificate_chain();
129 return;
130 }
131}
132
135void CanonicalizeTlsGroupProfile(curl::fuzzer::proto::Scenario* scenario) {
136 switch (scenario->tls_group_profile()) {
137 case curl::fuzzer::proto::TLS_GROUP_X25519:
138 case curl::fuzzer::proto::TLS_GROUP_PROVIDER_DEFAULT:
139 return;
140 default:
141 scenario->clear_tls_group_profile();
142 return;
143 }
144}
145
148void CanonicalizeTlsClientCredentialProfile(curl::fuzzer::proto::Scenario* scenario) {
149 switch (scenario->tls_client_credential_profile()) {
150 case curl::fuzzer::proto::TLS_CLIENT_CREDENTIAL_NONE:
151 case curl::fuzzer::proto::TLS_CLIENT_CREDENTIAL_MALFORMED_PKCS12:
152 case curl::fuzzer::proto::TLS_CLIENT_CREDENTIAL_ENGINE_DEV_NULL:
153 case curl::fuzzer::proto::TLS_CLIENT_CREDENTIAL_DEFAULT_PROVIDER_DEV_NULL:
154 case curl::fuzzer::proto::TLS_CLIENT_CREDENTIAL_UNLOADED_PROVIDER_DEV_NULL:
155 return;
156 default:
157 scenario->clear_tls_client_credential_profile();
158 return;
159 }
160}
161
163void CanonicalizeTlsHandshakeProfile(curl::fuzzer::proto::Scenario* scenario) {
164 switch (scenario->tls_handshake_profile()) {
165 case curl::fuzzer::proto::TLS_HANDSHAKE_DEFAULT:
166 case curl::fuzzer::proto::TLS_HANDSHAKE_OPENSSL_CLOSE_NOTIFY:
167 case curl::fuzzer::proto::TLS_HANDSHAKE_OPENSSL_HELLO_REQUEST:
168 return;
169 default:
170 scenario->clear_tls_handshake_profile();
171 return;
172 }
173}
174
178void CanonicalizeGopherProbeMode(curl::fuzzer::proto::Scenario* scenario) {
179 switch (scenario->gopher_probe_mode()) {
180 case curl::fuzzer::proto::GOPHER_PROBE_DEFAULT:
181 case curl::fuzzer::proto::GOPHER_PROBE_PARTIAL_WRITE_NO_TIMEOUT:
182 case curl::fuzzer::proto::GOPHER_PROBE_SEND_TIMEOUT:
183 case curl::fuzzer::proto::GOPHER_PROBE_TLS_SESSION_KEY_LIMIT:
184 return;
185 default:
186 scenario->clear_gopher_probe_mode();
187 return;
188 }
189}
190
193void CanonicalizeRtspProbeMode(curl::fuzzer::proto::Scenario* scenario) {
194 switch (scenario->rtsp_probe_mode()) {
195 case curl::fuzzer::proto::RTSP_PROBE_DEFAULT:
196 case curl::fuzzer::proto::RTSP_PROBE_CROSS_ORIGIN_REUSE:
197 case curl::fuzzer::proto::RTSP_PROBE_SPLIT_INVALID_RTP_WRITE_FAILURE:
198 return;
199 default:
200 scenario->clear_rtsp_probe_mode();
201 return;
202 }
203}
204
208void CanonicalizeMqttProbeMode(curl::fuzzer::proto::Scenario* scenario) {
209 switch (scenario->mqtt_probe_mode()) {
210 case curl::fuzzer::proto::MQTT_PROBE_DEFAULT:
211 case curl::fuzzer::proto::MQTT_PROBE_SEND_BACKPRESSURE:
212 case curl::fuzzer::proto::MQTT_PROBE_OVERSIZED_USERNAME:
213 case curl::fuzzer::proto::MQTT_PROBE_OVERSIZED_PASSWORD:
214 return;
215 default:
216 scenario->clear_mqtt_probe_mode();
217 return;
218 }
219}
220
223void CanonicalizeSmtpProbeMode(curl::fuzzer::proto::Scenario* scenario) {
224 switch (scenario->smtp_probe_mode()) {
225 case curl::fuzzer::proto::SMTP_PROBE_DEFAULT:
226 case curl::fuzzer::proto::SMTP_PROBE_STARTTLS_TRY:
227 case curl::fuzzer::proto::SMTP_PROBE_STARTTLS_REQUIRE:
228 case curl::fuzzer::proto::SMTP_PROBE_STARTTLS_FILTER_ALLOCATION_FAILURE:
229 case curl::fuzzer::proto::SMTP_PROBE_SEND_BACKPRESSURE:
230 return;
231 default:
232 scenario->clear_smtp_probe_mode();
233 return;
234 }
235}
236
241void CanonicalizeH2ProxyOriginAuthority(curl::fuzzer::proto::Scenario* scenario) {
242 const std::string& host_path = scenario->host_path();
243 const std::size_t suffix_start = host_path.find_first_of("/?#");
244 if (suffix_start == std::string::npos) {
245 scenario->set_host_path("origin.test/");
246 return;
247 }
248 scenario->set_host_path("origin.test" + host_path.substr(suffix_start));
249}
250
254void CanonicalizeSocksAuthority(curl::fuzzer::proto::Scenario* scenario) {
255 const std::string& host_path = scenario->host_path();
256 const std::size_t suffix_start = host_path.find_first_of("/?#");
257 const auto mode = scenario->socks_proxy_mode();
258 const bool resolve_at_proxy =
259 mode == curl::fuzzer::proto::SOCKS_PROXY_SOCKS4A || mode == curl::fuzzer::proto::SOCKS_PROXY_SOCKS5_HOSTNAME;
260 const char* host = resolve_at_proxy ? "socks.test" : "localhost";
261 if (suffix_start == std::string::npos) {
262 scenario->set_host_path(std::string(host) + "/");
263 return;
264 }
265 scenario->set_host_path(std::string(host) + host_path.substr(suffix_start));
266}
267
273void CanonicalizeHttpTransportAuthority(curl::fuzzer::proto::Scenario* scenario) {
274 const std::string& host_path = scenario->host_path();
275 const std::size_t suffix_start = host_path.find_first_of("/?#");
276 if (suffix_start == std::string::npos) {
277 scenario->set_host_path("transport.test/");
278 return;
279 }
280 scenario->set_host_path("transport.test" + host_path.substr(suffix_start));
281}
282
285void CanonicalizeProxySocketOptionAuthority(curl::fuzzer::proto::Scenario* scenario) {
286 const char* host;
287 switch (scenario->proxy_socket_option_mode()) {
288 case curl::fuzzer::proto::PROXY_SOCKET_OPTION_NOPROXY_HOSTNAME:
289 host = "service.proxy.test";
290 break;
291 case curl::fuzzer::proto::PROXY_SOCKET_OPTION_NOPROXY_IPV4_CIDR:
292 host = "127.0.0.2";
293 break;
294 case curl::fuzzer::proto::PROXY_SOCKET_OPTION_NOPROXY_IPV6_CIDR:
295 host = "[::1]";
296 break;
297 case curl::fuzzer::proto::PROXY_SOCKET_OPTION_ENV_HTTP_PROXY:
298 host = "proxy-origin.test";
299 break;
300 case curl::fuzzer::proto::PROXY_SOCKET_OPTION_LOOPBACK_INTERFACE_KEEPALIVE:
301 host = "resolve.test";
302 break;
303 case curl::fuzzer::proto::PROXY_SOCKET_OPTION_DEFAULT:
304 default:
305 return;
306 }
307
308 const std::string& host_path = scenario->host_path();
309 const std::size_t suffix_start = host_path.find_first_of("/?#");
310 scenario->set_host_path(std::string(host) +
311 (suffix_start == std::string::npos ? "/" : host_path.substr(suffix_start)));
312}
313
319void CanonicalizeResolverAuthority(curl::fuzzer::proto::Scenario* scenario) {
320 const std::string& host_path = scenario->host_path();
321 const std::size_t suffix_start = host_path.find_first_of("/?#");
322 const char* host = "localhost";
323 switch (scenario->resolver_mode()) {
324 case curl::fuzzer::proto::RESOLVER_MODE_THREADED_POSITIVE:
325 host = "resolver-positive.test";
326 break;
327 case curl::fuzzer::proto::RESOLVER_MODE_THREADED_NEGATIVE:
328 host = "resolver-negative.test";
329 break;
330 case curl::fuzzer::proto::RESOLVER_MODE_THREADED_LINK_LOCAL:
331 host = "resolver-link-local.test";
332 break;
333 case curl::fuzzer::proto::RESOLVER_MODE_THREADED_PARTIAL:
334 host = "resolver-partial.test";
335 break;
336 case curl::fuzzer::proto::RESOLVER_MODE_THREADED_QUEUED_CANCEL:
337 host = "resolver-queued-cancel.test";
338 break;
339 case curl::fuzzer::proto::RESOLVER_MODE_UNIX_SOCKET:
340 host = "resolver-unix.test";
341 break;
342 case curl::fuzzer::proto::RESOLVER_MODE_GLOBAL_LIFECYCLE:
343 host = "localhost";
344 break;
345 case curl::fuzzer::proto::RESOLVER_MODE_DEFAULT:
346 default:
347 host =
348 scenario->has_doh()
349 ? "tls.test"
350 : ((scenario->happy_eyeballs() || !scenario->resolve_entries().empty()) ? "resolve.test" : "localhost");
351 break;
352 }
353 if (suffix_start == std::string::npos) {
354 scenario->set_host_path(std::string(host) + "/");
355 return;
356 }
357 scenario->set_host_path(std::string(host) + host_path.substr(suffix_start));
358}
359
360bool IsThreadedResolverMode(curl::fuzzer::proto::ResolverMode mode) {
361 return mode == curl::fuzzer::proto::RESOLVER_MODE_THREADED_POSITIVE ||
362 mode == curl::fuzzer::proto::RESOLVER_MODE_THREADED_NEGATIVE ||
363 mode == curl::fuzzer::proto::RESOLVER_MODE_THREADED_LINK_LOCAL ||
364 mode == curl::fuzzer::proto::RESOLVER_MODE_THREADED_PARTIAL ||
365 mode == curl::fuzzer::proto::RESOLVER_MODE_THREADED_QUEUED_CANCEL;
366}
367
374void CanonicalizeTftpAuthority(curl::fuzzer::proto::Scenario* scenario) {
375 const std::string& host_path = scenario->host_path();
376 const std::size_t suffix_start = host_path.find_first_of("/?#");
377 if (suffix_start == std::string::npos) {
378 scenario->set_host_path("tftp.test/file");
379 return;
380 }
381 scenario->set_host_path("tftp.test" + host_path.substr(suffix_start));
382}
383
390void CanonicalizeFtpAuthority(curl::fuzzer::proto::Scenario* scenario) {
391 const std::string& host_path = scenario->host_path();
392 const std::size_t suffix_start = host_path.find_first_of("/?#");
393 const char* host = scenario->scheme() == curl::fuzzer::proto::SCHEME_FTPS ? "tls.test" : "ftp.test";
394 if (suffix_start == std::string::npos) {
395 scenario->set_host_path(std::string(host) + "/file");
396 return;
397 }
398 scenario->set_host_path(std::string(host) + host_path.substr(suffix_start));
399}
400
409void CanonicalizeSmtpAuthority(curl::fuzzer::proto::Scenario* scenario) {
410 const std::string& host_path = scenario->host_path();
411 const std::size_t suffix_start = host_path.find_first_of("/?#");
412 const char* host = scenario->scheme() == curl::fuzzer::proto::SCHEME_SMTPS ? "tls.test" : "smtp.test";
413 if (suffix_start == std::string::npos) {
414 scenario->set_host_path(std::string(host) + "/client.test");
415 return;
416 }
417 scenario->set_host_path(std::string(host) + host_path.substr(suffix_start));
418}
419
430void CanonicalizeImapAuthority(curl::fuzzer::proto::Scenario* scenario) {
431 const std::string& host_path = scenario->host_path();
432 const std::size_t suffix_start = host_path.find_first_of("/?#");
433 const char* host = scenario->scheme() == curl::fuzzer::proto::SCHEME_IMAPS ? "tls.test" : "imap.test";
434 const std::size_t authority_end = suffix_start == std::string::npos ? host_path.size() : suffix_start;
435 const bool retain_default_port = scenario->scheme() == curl::fuzzer::proto::SCHEME_IMAP && authority_end >= 3 &&
436 host_path.compare(authority_end - 3, 3, ":80") == 0;
437 const std::string authority = std::string(host) + (retain_default_port ? ":80" : "");
438 if (suffix_start == std::string::npos) {
439 scenario->set_host_path(authority + "/INBOX");
440 return;
441 }
442 scenario->set_host_path(authority + host_path.substr(suffix_start));
443}
444
450void CanonicalizePop3Authority(curl::fuzzer::proto::Scenario* scenario) {
451 const std::string& host_path = scenario->host_path();
452 const std::size_t suffix_start = host_path.find_first_of("/?#");
453 const char* host = scenario->scheme() == curl::fuzzer::proto::SCHEME_POP3S ? "tls.test" : "pop3.test";
454 if (suffix_start == std::string::npos) {
455 scenario->set_host_path(std::string(host) + "/1");
456 return;
457 }
458 scenario->set_host_path(std::string(host) + host_path.substr(suffix_start));
459}
460
468void CanonicalizeMqttAuthority(curl::fuzzer::proto::Scenario* scenario) {
469 const std::string& host_path = scenario->host_path();
470 const std::size_t suffix_start = host_path.find_first_of("/?#");
471 const char* host = scenario->scheme() == curl::fuzzer::proto::SCHEME_MQTTS ? "tls.test" : "mqtt.test";
472 if (suffix_start == std::string::npos) {
473 scenario->set_host_path(std::string(host) + "/topic");
474 return;
475 }
476 scenario->set_host_path(std::string(host) + host_path.substr(suffix_start));
477}
478
485void CanonicalizeDictAuthority(curl::fuzzer::proto::Scenario* scenario) {
486 const std::string& host_path = scenario->host_path();
487 const std::size_t suffix_start = host_path.find_first_of("/?#");
488 if (suffix_start == std::string::npos) {
489 scenario->set_host_path("dict.test/d:word");
490 return;
491 }
492 std::string canonical = "dict.test" + host_path.substr(suffix_start);
493 if (canonical.size() > scenario_limits::kMaxDictHostPathBytes) {
495 }
496 scenario->set_host_path(canonical);
497}
498
507void CanonicalizeLdapAuthority(curl::fuzzer::proto::Scenario* scenario) {
508 const std::string& host_path = scenario->host_path();
509 const std::size_t suffix_start = host_path.find_first_of("/?#");
510 const char* host = scenario->scheme() == curl::fuzzer::proto::SCHEME_LDAPS ? "tls.test" : "ldap.test";
511 if (suffix_start == std::string::npos) {
512 scenario->set_host_path(std::string(host) + "/dc=example,dc=com");
513 return;
514 }
515 scenario->set_host_path(std::string(host) + host_path.substr(suffix_start));
516}
517
523void CanonicalizeRtspAuthority(curl::fuzzer::proto::Scenario* scenario) {
524 const std::string& host_path = scenario->host_path();
525 const std::size_t suffix_start = host_path.find_first_of("/?#");
526 if (suffix_start == std::string::npos) {
527 scenario->set_host_path("rtsp.test/stream");
528 return;
529 }
530 scenario->set_host_path("rtsp.test" + host_path.substr(suffix_start));
531}
532
540void CanonicalizeIpfsAuthority(curl::fuzzer::proto::Scenario* scenario) {
541 const std::string& host_path = scenario->host_path();
542 const std::size_t suffix_start = host_path.find_first_of("/?#");
543 std::string canonical = scenario->scheme() == curl::fuzzer::proto::SCHEME_IPNS ? kDefaultIpnsName : kDefaultIpfsCid;
544 if (suffix_start != std::string::npos) {
545 canonical += host_path.substr(suffix_start);
546 }
547 if (canonical.size() > scenario_limits::kMaxIpfsHostPathBytes) {
549 }
550 scenario->set_host_path(canonical);
551}
552
556void CanonicalizeMultiAuthority(curl::fuzzer::proto::Scenario* scenario) {
557 const std::string& host_path = scenario->host_path();
558 const std::size_t suffix_start = host_path.find_first_of("/?#");
559 if (suffix_start == std::string::npos) {
560 scenario->set_host_path("multi.test/");
561 return;
562 }
563 scenario->set_host_path("multi.test" + host_path.substr(suffix_start));
564}
565
566template <typename RepeatedBytes>
567void BoundStringValues(RepeatedBytes* values, std::size_t count_limit, std::size_t value_limit) {
568 TrimRepeated(values, count_limit);
569 for (std::string& value : *values) {
570 if (value.size() > value_limit) {
571 value.resize(value_limit);
572 }
573 }
574}
575
576template <typename RepeatedBytes>
577void BoundHeaderValues(RepeatedBytes* headers, std::size_t limit) {
578 BoundStringValues(headers, limit, scenario_limits::kMaxMetadataBytes);
579}
580
584void BoundConnectionShape(curl::fuzzer::proto::Connection* connection) {
585 TrimRepeated(connection->mutable_on_readable(), scenario_limits::kMaxResponseChunks);
586 const std::size_t raw_count = static_cast<std::size_t>(connection->on_readable_size());
587 TrimRepeated(connection->mutable_server_frames(), scenario_limits::kMaxResponseChunks - raw_count);
588}
589
592void BoundFileInput(std::string* input, std::size_t* remaining_bytes) {
593 const std::size_t limit = std::min(scenario_limits::kMaxFileInputBytes, *remaining_bytes);
594 if (input->size() > limit) {
595 input->resize(limit);
596 }
597 *remaining_bytes -= input->size();
598}
599
600void BoundFileInputs(curl::fuzzer::proto::Scenario* scenario) {
601 std::size_t remaining_bytes = scenario_limits::kMaxFileInputTotalBytes;
602 BoundFileInput(scenario->mutable_cookie_file(), &remaining_bytes);
603 BoundFileInput(scenario->mutable_altsvc_file(), &remaining_bytes);
604 BoundFileInput(scenario->mutable_hsts_file(), &remaining_bytes);
605 BoundFileInput(scenario->mutable_netrc_file(), &remaining_bytes);
606 BoundFileInput(scenario->mutable_crl_file(), &remaining_bytes);
607 BoundFileInput(scenario->mutable_pinned_public_key_file(), &remaining_bytes);
608}
609
611template <typename Part>
612void BoundMimePartMetadata(Part* part) {
613 TrimMetadata(part->mutable_name());
614 TrimMetadata(part->mutable_filename());
615 TrimMetadata(part->mutable_content_type());
616 BoundHeaderValues(part->mutable_headers(), scenario_limits::kMaxMimeHeadersPerPart);
617}
618
619void BoundGeneratedMimeData(curl::fuzzer::proto::GeneratedBytes* generated, std::size_t* remaining_bytes) {
620 if (generated->pattern().size() > scenario_limits::kMaxGeneratedMimePatternBytes) {
621 generated->mutable_pattern()->resize(scenario_limits::kMaxGeneratedMimePatternBytes);
622 }
623
624 const std::size_t pattern_size = generated->pattern().size();
625 if (pattern_size == 0 || *remaining_bytes == 0) {
626 generated->clear_repeat_count();
627 return;
628 }
629
630 // Keep every exact in-range size, including 16/32/64 KiB and their +/-1
631 // neighborhoods. Counts beyond the shared budget all have identical runtime
632 // behavior, so fold them onto the largest observable repetition count.
633 const std::size_t max_repeat_count = *remaining_bytes / pattern_size;
634 const std::size_t repeat_count =
635 std::min<std::size_t>(static_cast<std::size_t>(generated->repeat_count()), max_repeat_count);
636 generated->set_repeat_count(static_cast<std::uint32_t>(repeat_count));
637 *remaining_bytes -= repeat_count * pattern_size;
638}
639
640void BoundMimeLeaf(curl::fuzzer::proto::MimeDataPart* part, std::size_t* remaining_generated_bytes) {
641 BoundMimePartMetadata(part);
642 if (part->content_case() == curl::fuzzer::proto::MimeDataPart::kGeneratedData) {
643 BoundGeneratedMimeData(part->mutable_generated_data(), remaining_generated_bytes);
644 } else if (part->data().size() > scenario_limits::kMaxMimeDataBytes) {
645 part->mutable_data()->resize(scenario_limits::kMaxMimeDataBytes);
646 }
647}
648
652void BoundMimeShape(curl::fuzzer::proto::MimePost* post) {
653 TrimRepeated(post->mutable_parts(), scenario_limits::kMaxTopLevelMimeParts);
654 std::size_t remaining = scenario_limits::kMaxTotalMimeParts;
655 std::size_t remaining_generated_bytes = scenario_limits::kMaxGeneratedMimeDataBytes;
656 std::size_t retained_top_parts = 0;
657
658 while (retained_top_parts < static_cast<std::size_t>(post->parts_size()) && remaining != 0) {
659 auto* part = post->mutable_parts(static_cast<int>(retained_top_parts));
660 ++retained_top_parts;
661 --remaining;
662 BoundMimePartMetadata(part);
663
664 if (part->content_case() == curl::fuzzer::proto::MimePart::kData) {
665 if (part->data().size() > scenario_limits::kMaxMimeDataBytes) {
666 part->mutable_data()->resize(scenario_limits::kMaxMimeDataBytes);
667 }
668 continue;
669 }
670 if (part->content_case() == curl::fuzzer::proto::MimePart::kGeneratedData) {
671 BoundGeneratedMimeData(part->mutable_generated_data(), &remaining_generated_bytes);
672 continue;
673 }
674 if (part->content_case() != curl::fuzzer::proto::MimePart::kSubparts) {
675 continue;
676 }
677
678 auto* children = part->mutable_subparts()->mutable_parts();
679 TrimRepeated(children, std::min(scenario_limits::kMaxNestedMimeParts, remaining));
680 for (auto& child : *children) {
681 BoundMimeLeaf(&child, &remaining_generated_bytes);
682 --remaining;
683 }
684 }
685
686 TrimRepeated(post->mutable_parts(), retained_top_parts);
687}
688
692void BoundUploadShape(curl::fuzzer::proto::UploadScript* upload, std::size_t data_limit, std::size_t read_step_limit,
693 std::size_t read_size_limit) {
694 if (upload->data().size() > data_limit) {
695 upload->mutable_data()->resize(data_limit);
696 }
697 // RepeatedField<uint32_t> lacks RepeatedPtrField's DeleteSubrange helper;
698 // removing the ignored suffix from the end is constant-time per element and
699 // preserves the mutation-significant prefix exactly.
700 while (static_cast<std::size_t>(upload->read_sizes_size()) > read_step_limit) {
701 upload->mutable_read_sizes()->RemoveLast();
702 }
703 for (int i = 0; i < upload->read_sizes_size(); ++i) {
704 if (upload->read_sizes(i) > read_size_limit) {
705 upload->set_read_sizes(i, static_cast<std::uint32_t>(read_size_limit));
706 }
707 }
708}
709
713template <typename RepeatedScalar>
714void TrimRepeatedScalar(RepeatedScalar* values, std::size_t limit) {
715 while (static_cast<std::size_t>(values->size()) > limit) {
716 values->RemoveLast();
717 }
718}
719
724void CanonicalizeAllocationFailures(curl::fuzzer::proto::Scenario* scenario) {
725 std::array<std::uint32_t, scenario_limits::kMaxAllocationFailures> retained{};
726 std::size_t retained_count = 0;
727 const std::uint32_t maximum = static_cast<std::uint32_t>(scenario_limits::kMaxAllocationOrdinal);
728
729 for (const std::uint32_t raw : scenario->fail_allocations()) {
730 if (raw == 0) {
731 continue;
732 }
733 const std::uint32_t ordinal = 1U + ((raw - 1U) % maximum);
734 const auto begin = retained.begin();
735 const auto end = begin + static_cast<std::ptrdiff_t>(retained_count);
736 const auto position = std::lower_bound(begin, end, ordinal);
737 if (position != end && *position == ordinal) {
738 continue;
739 }
740 const std::size_t index = static_cast<std::size_t>(position - begin);
741 if (index >= retained.size()) {
742 continue;
743 }
744 if (retained_count < retained.size()) {
745 ++retained_count;
746 }
747 for (std::size_t destination = retained_count - 1; destination > index; --destination) {
748 retained[destination] = retained[destination - 1];
749 }
750 retained[index] = ordinal;
751 }
752
753 auto* values = scenario->mutable_fail_allocations();
754 values->Clear();
755 for (std::size_t index = 0; index < retained_count; ++index) {
756 values->Add(retained[index]);
757 }
758}
759
765void CanonicalizeAllocationFailureDrive(curl::fuzzer::proto::Scenario* scenario) {
766 if (!scenario->fail_allocations().empty() && scenario->has_api_plan() &&
767 scenario->api_plan().drive_mode() == curl::fuzzer::proto::API_DRIVE_EASY_EVENTS) {
768 scenario->mutable_api_plan()->set_drive_mode(curl::fuzzer::proto::API_DRIVE_EASY_PERFORM);
769 }
770}
771
774void CanonicalizeApiDriveMode(curl::fuzzer::proto::ApiPlan* plan) {
775 switch (plan->drive_mode()) {
776 case curl::fuzzer::proto::API_DRIVE_MULTI_PERFORM:
777 case curl::fuzzer::proto::API_DRIVE_MULTI_SOCKET:
778 case curl::fuzzer::proto::API_DRIVE_EASY_PERFORM:
779 case curl::fuzzer::proto::API_DRIVE_EASY_EVENTS:
780 case curl::fuzzer::proto::API_DRIVE_CONNECT_ONLY:
781 return;
782 default:
783 plan->set_drive_mode(curl::fuzzer::proto::API_DRIVE_MULTI_PERFORM);
784 return;
785 }
786}
787
792void BoundApiPlanShape(curl::fuzzer::proto::ApiPlan* plan) {
793 TrimRepeatedScalar(plan->mutable_share_data_selectors(), scenario_limits::kMaxApiShareDataSelectors);
794 TrimRepeatedScalar(plan->mutable_easy_info_selectors(), scenario_limits::kMaxApiInfoSelectors);
795 TrimRepeatedScalar(plan->mutable_reentrant_probe_selectors(), scenario_limits::kMaxApiReentrantSelectors);
796
797 CanonicalizeApiDriveMode(plan);
798 switch (plan->drive_mode()) {
799 case curl::fuzzer::proto::API_DRIVE_MULTI_PERFORM:
800 case curl::fuzzer::proto::API_DRIVE_MULTI_SOCKET:
801 break;
802 case curl::fuzzer::proto::API_DRIVE_EASY_PERFORM:
803 case curl::fuzzer::proto::API_DRIVE_EASY_EVENTS:
804 case curl::fuzzer::proto::API_DRIVE_CONNECT_ONLY:
805 // Wakeup is a multi-handle API and has no live object in easy mode.
806 // Likewise, a body callback cannot be resumed while one of these
807 // blocking entrypoints owns the thread. Clearing both mutations keeps
808 // every retained API-plan value observable and bounded.
809 plan->set_wake_multi(false);
810 plan->set_pause_response_once(false);
811 break;
812 default:
813 break;
814 }
815}
816
821void CanonicalizeApiMimeRepeat(curl::fuzzer::proto::Scenario* scenario) {
822 if (!scenario->has_api_plan() || !scenario->api_plan().repeat_mime_post()) {
823 return;
824 }
825
826 bool has_callback_part = false;
827 if (scenario->has_mime_post()) {
828 for (const auto& part : scenario->mime_post().parts()) {
829 if (part.content_case() == curl::fuzzer::proto::MimePart::kUploadCallback) {
830 has_callback_part = true;
831 break;
832 }
833 }
834 }
835 if (!scenario->has_upload() || !has_callback_part) {
836 scenario->mutable_api_plan()->clear_repeat_mime_post();
837 return;
838 }
839
840 auto* plan = scenario->mutable_api_plan();
841 plan->set_drive_mode(curl::fuzzer::proto::API_DRIVE_MULTI_PERFORM);
842 plan->set_wake_multi(false);
843 plan->set_pause_response_once(false);
844}
845
849void BoundMultiPlanShape(curl::fuzzer::proto::MultiPlan* plan) {
850 const std::uint32_t minimum = static_cast<std::uint32_t>(scenario_limits::kMinMultiTransfers);
851 const std::uint32_t maximum = static_cast<std::uint32_t>(scenario_limits::kMaxMultiTransfers);
852 const std::uint32_t transfer_count = std::max(minimum, std::min(plan->transfer_count(), maximum));
853 plan->set_transfer_count(transfer_count);
854 plan->set_max_host_connections(std::min(plan->max_host_connections(), transfer_count));
855 plan->set_max_total_connections(std::min(plan->max_total_connections(), transfer_count));
856 plan->set_connection_cache_size(std::min(plan->connection_cache_size(), maximum * 2U));
857 TrimRepeated(plan->mutable_actions(), scenario_limits::kMaxMultiActions);
858
859 switch (plan->drive_mode()) {
860 case curl::fuzzer::proto::MULTI_DRIVE_PERFORM:
861 case curl::fuzzer::proto::MULTI_DRIVE_SOCKET:
862 break;
863 default:
864 plan->set_drive_mode(curl::fuzzer::proto::MULTI_DRIVE_PERFORM);
865 break;
866 }
867
868 switch (plan->state_probe_profile()) {
869 case curl::fuzzer::proto::MULTI_STATE_PROBE_NONE:
870 case curl::fuzzer::proto::MULTI_STATE_PROBE_PROGRESS_RATE_LIMIT:
871 break;
872 default:
873 plan->set_state_probe_profile(curl::fuzzer::proto::MULTI_STATE_PROBE_NONE);
874 break;
875 }
876
877 for (auto& action : *plan->mutable_actions()) {
878 action.set_transfer_selector(action.transfer_selector() % transfer_count);
879 switch (action.kind()) {
880 case curl::fuzzer::proto::MULTI_ACTION_NONE:
881 case curl::fuzzer::proto::MULTI_ACTION_PAUSE_RECV:
882 case curl::fuzzer::proto::MULTI_ACTION_PAUSE_SEND:
883 case curl::fuzzer::proto::MULTI_ACTION_PAUSE_ALL:
884 case curl::fuzzer::proto::MULTI_ACTION_RESUME:
885 case curl::fuzzer::proto::MULTI_ACTION_REMOVE:
886 case curl::fuzzer::proto::MULTI_ACTION_READD:
887 break;
888 default:
889 action.set_kind(curl::fuzzer::proto::MULTI_ACTION_NONE);
890 break;
891 }
892 }
893}
894
898void CanonicalizeHttp3HeaderName(std::string* name) {
901 }
902 if (name->empty()) {
903 *name = "x-fuzz";
904 return;
905 }
906
907 for (char& byte : *name) {
908 const unsigned char value = static_cast<unsigned char>(byte);
909 const bool alpha = (value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z');
910 const bool digit = value >= '0' && value <= '9';
911 const bool punctuation = value == '!' || value == '#' || value == '$' || value == '%' || value == '&' ||
912 value == '\'' || value == '*' || value == '+' || value == '-' || value == '.' ||
913 value == '^' || value == '_' || value == '`' || value == '|' || value == '~';
914 if (value >= 'A' && value <= 'Z') {
915 byte = static_cast<char>(value - 'A' + 'a');
916 } else if (!alpha && !digit && !punctuation) {
917 byte = '-';
918 }
919 }
920}
921
925void CanonicalizeHttp3HeaderValue(std::string* value) {
926 if (value->size() > scenario_limits::kMaxHttp3HeaderValueBytes) {
928 }
929 for (char& byte : *value) {
930 const unsigned char character = static_cast<unsigned char>(byte);
931 if ((character < 0x20U && character != '\t') || character == 0x7fU) {
932 byte = ' ';
933 }
934 }
935}
936
940template <typename RepeatedHeaders>
941void BoundHttp3Headers(RepeatedHeaders* headers, std::size_t count_limit, std::size_t* remaining_bytes) {
942 TrimRepeated(headers, count_limit);
943 std::size_t retained = 0;
944 while (retained < static_cast<std::size_t>(headers->size()) && *remaining_bytes != 0) {
945 auto* header = headers->Mutable(static_cast<int>(retained));
946 CanonicalizeHttp3HeaderName(header->mutable_name());
947 CanonicalizeHttp3HeaderValue(header->mutable_value());
948
949 if (header->name().size() > *remaining_bytes) {
950 header->mutable_name()->resize(*remaining_bytes);
951 header->clear_value();
952 } else if (header->value().size() > *remaining_bytes - header->name().size()) {
953 header->mutable_value()->resize(*remaining_bytes - header->name().size());
954 }
955 *remaining_bytes -= header->name().size() + header->value().size();
956 ++retained;
957 }
958 TrimRepeated(headers, retained);
959}
960
961void CanonicalizeHttp3StreamRole(curl::fuzzer::proto::Http3StreamRole* role) {
962 switch (*role) {
963 case curl::fuzzer::proto::HTTP3_STREAM_RESPONSE:
964 case curl::fuzzer::proto::HTTP3_STREAM_CONTROL:
965 case curl::fuzzer::proto::HTTP3_STREAM_QPACK_ENCODER:
966 case curl::fuzzer::proto::HTTP3_STREAM_QPACK_DECODER:
967 return;
968 default:
969 *role = curl::fuzzer::proto::HTTP3_STREAM_RESPONSE;
970 return;
971 }
972}
973
974void BoundHttp3RawData(std::string* data, std::size_t* remaining_raw_bytes) {
975 const std::size_t limit = std::min(scenario_limits::kMaxHttp3RawWriteBytes, *remaining_raw_bytes);
976 if (data->size() > limit) {
977 data->resize(limit);
978 }
979 *remaining_raw_bytes -= data->size();
980}
981
982void BoundHttp2StreamRef(curl::fuzzer::proto::Http2StreamRef* stream) {
983 switch (stream->target_case()) {
984 case curl::fuzzer::proto::Http2StreamRef::kRequestIndex:
985 stream->set_request_index(stream->request_index() % 16U);
986 break;
987 case curl::fuzzer::proto::Http2StreamRef::kPushIndex:
988 stream->set_push_index(stream->push_index() % 16U);
989 break;
990 case curl::fuzzer::proto::Http2StreamRef::kExplicitId:
991 stream->set_explicit_id(stream->explicit_id() & 0x7fffffffU);
992 break;
993 case curl::fuzzer::proto::Http2StreamRef::kConnection:
994 case curl::fuzzer::proto::Http2StreamRef::kLatestRequest:
995 case curl::fuzzer::proto::Http2StreamRef::TARGET_NOT_SET:
996 default:
997 break;
998 }
999}
1000
1001void BoundHttp2Settings(curl::fuzzer::proto::Http2Settings* settings) {
1002 TrimRepeated(settings->mutable_entries(), scenario_limits::kMaxHttp2Settings);
1003 for (auto& entry : *settings->mutable_entries()) {
1004 // SETTINGS identifiers occupy 16 bits. Preserve unknown extensions,
1005 // which receivers ignore when they do not understand them.
1006 entry.set_identifier(entry.identifier() & 0xffffU);
1007 switch (entry.identifier()) {
1008 case 2U: // SETTINGS_ENABLE_PUSH
1009 case 8U: // SETTINGS_ENABLE_CONNECT_PROTOCOL
1010 case 9U: // SETTINGS_NO_RFC7540_PRIORITIES
1011 entry.set_value(entry.value() & 1U);
1012 break;
1013 case 4U: // SETTINGS_INITIAL_WINDOW_SIZE
1014 entry.set_value(entry.value() & 0x7fffffffU);
1015 break;
1016 case 5U: // SETTINGS_MAX_FRAME_SIZE
1017 if (entry.value() < 16384U || entry.value() > 0x00ffffffU) {
1018 entry.set_value(16384U + entry.value() % (0x00ffffffU - 16384U + 1U));
1019 }
1020 break;
1021 case 1U: // SETTINGS_HEADER_TABLE_SIZE
1022 case 3U: // SETTINGS_MAX_CONCURRENT_STREAMS
1023 case 6U: // SETTINGS_MAX_HEADER_LIST_SIZE
1024 default:
1025 break;
1026 }
1027 }
1028}
1029
1030void BoundHttp2RawData(std::string* data, std::size_t* remaining_raw_bytes) {
1031 const std::size_t limit = std::min(scenario_limits::kMaxHttp2RawFrameBytes, *remaining_raw_bytes);
1032 if (data->size() > limit) {
1033 data->resize(limit);
1034 }
1035 *remaining_raw_bytes -= data->size();
1036}
1037
1041void BoundHttp2PlanShape(curl::fuzzer::proto::Http2Plan* plan) {
1042 TrimRepeated(plan->mutable_actions(), scenario_limits::kMaxHttp2Actions);
1043 BoundHttp2Settings(plan->mutable_initial_settings());
1044 plan->mutable_initial_settings()->set_ack(false);
1045 if (plan->stream_weight_after_headers() > 256U) {
1046 plan->set_stream_weight_after_headers(1U + (plan->stream_weight_after_headers() - 1U) % 256U);
1047 }
1048 if (plan->actions().empty()) {
1049 auto* response = plan->add_actions()->mutable_headers();
1050 response->set_status_code(200);
1051 response->set_end_stream(true);
1052 }
1053
1054 std::size_t remaining_header_bytes = scenario_limits::kMaxHttp2HeaderBytes;
1055 std::size_t remaining_data_bytes = scenario_limits::kMaxHttp2DataBytes;
1056 std::size_t remaining_raw_bytes = scenario_limits::kMaxHttp2RawBytes;
1057 for (auto& action : *plan->mutable_actions()) {
1058 switch (action.action_case()) {
1059 case curl::fuzzer::proto::Http2Action::kSettings:
1060 BoundHttp2Settings(action.mutable_settings());
1061 break;
1062 case curl::fuzzer::proto::Http2Action::kHeaders: {
1063 auto* headers = action.mutable_headers();
1064 BoundHttp2StreamRef(headers->mutable_stream());
1065 if (!headers->trailers()) {
1066 if (headers->status_code() == 0U) {
1067 headers->set_status_code(200U);
1068 } else if (headers->status_code() < 100U || headers->status_code() > 599U) {
1069 headers->set_status_code(100U + headers->status_code() % 500U);
1070 }
1071 }
1072 BoundHttp3Headers(headers->mutable_fields(), scenario_limits::kMaxHttp2Headers, &remaining_header_bytes);
1073 break;
1074 }
1075 case curl::fuzzer::proto::Http2Action::kData: {
1076 auto* data = action.mutable_data();
1077 BoundHttp2StreamRef(data->mutable_stream());
1078 if (data->data().size() > remaining_data_bytes) {
1079 data->mutable_data()->resize(remaining_data_bytes);
1080 }
1081 remaining_data_bytes -= data->data().size();
1082 break;
1083 }
1084 case curl::fuzzer::proto::Http2Action::kWindowUpdate: {
1085 auto* update = action.mutable_window_update();
1086 BoundHttp2StreamRef(update->mutable_stream());
1087 update->set_increment(std::max(1U, update->increment() & 0x7fffffffU));
1088 break;
1089 }
1090 case curl::fuzzer::proto::Http2Action::kRstStream:
1091 BoundHttp2StreamRef(action.mutable_rst_stream()->mutable_stream());
1092 break;
1093 case curl::fuzzer::proto::Http2Action::kPing:
1094 if (action.ping().opaque_data().size() > 8U) {
1095 action.mutable_ping()->mutable_opaque_data()->resize(8U);
1096 }
1097 break;
1098 case curl::fuzzer::proto::Http2Action::kGoaway:
1099 BoundHttp2StreamRef(action.mutable_goaway()->mutable_last_stream());
1100 BoundHttp2RawData(action.mutable_goaway()->mutable_debug_data(), &remaining_raw_bytes);
1101 break;
1102 case curl::fuzzer::proto::Http2Action::kPushPromise:
1103 BoundHttp2StreamRef(action.mutable_push_promise()->mutable_parent_stream());
1104 BoundHttp2StreamRef(action.mutable_push_promise()->mutable_promised_stream());
1105 BoundHttp3Headers(action.mutable_push_promise()->mutable_request_headers(), scenario_limits::kMaxHttp2Headers,
1106 &remaining_header_bytes);
1107 break;
1108 case curl::fuzzer::proto::Http2Action::kWait: {
1109 auto* wait = action.mutable_wait();
1110 BoundHttp2StreamRef(wait->mutable_stream());
1111 if (wait->event() < curl::fuzzer::proto::HTTP2_CLIENT_EVENT_SETTINGS ||
1112 wait->event() > curl::fuzzer::proto::HTTP2_CLIENT_EVENT_END_STREAM) {
1113 wait->set_event(curl::fuzzer::proto::HTTP2_CLIENT_EVENT_SETTINGS);
1114 }
1115 wait->set_count(std::max(1U, std::min(wait->count(), 16U)));
1116 break;
1117 }
1118 case curl::fuzzer::proto::Http2Action::kYieldTurns:
1119 action.set_yield_turns(std::min<std::uint32_t>(action.yield_turns(), scenario_limits::kMaxHttp2YieldTurns));
1120 break;
1121 case curl::fuzzer::proto::Http2Action::kWireFrame:
1122 action.mutable_wire_frame()->set_type(action.wire_frame().type() & 0xffU);
1123 action.mutable_wire_frame()->set_flags(action.wire_frame().flags() & 0xffU);
1124 action.mutable_wire_frame()->set_stream_id(action.wire_frame().stream_id() & 0x7fffffffU);
1125 action.mutable_wire_frame()->set_declared_length(action.wire_frame().declared_length() & 0x00ffffffU);
1126 BoundHttp2RawData(action.mutable_wire_frame()->mutable_payload(), &remaining_raw_bytes);
1127 break;
1128 case curl::fuzzer::proto::Http2Action::kRawBytes:
1129 BoundHttp2RawData(action.mutable_raw_bytes(), &remaining_raw_bytes);
1130 break;
1131 case curl::fuzzer::proto::Http2Action::ACTION_NOT_SET: {
1132 auto* response = action.mutable_headers();
1133 response->set_status_code(200);
1134 response->set_end_stream(true);
1135 break;
1136 }
1137 }
1138 }
1139}
1140
1144void BoundHttp3PlanShape(curl::fuzzer::proto::Http3Plan* plan) {
1145 if (plan->use_h1_connect_udp_proxy()) {
1146 plan->clear_actions();
1147 return;
1148 }
1149 TrimRepeated(plan->mutable_actions(), scenario_limits::kMaxHttp3Actions);
1150 if (plan->actions().empty()) {
1151 auto* response = plan->add_actions()->mutable_structured_response();
1152 response->set_status_code(200);
1153 response->set_finish_stream(true);
1154 }
1155
1156 std::size_t remaining_header_bytes = scenario_limits::kMaxHttp3HeaderBytes;
1157 std::size_t remaining_body_bytes = scenario_limits::kMaxHttp3BodyBytes;
1158 std::size_t remaining_raw_bytes = scenario_limits::kMaxHttp3RawBytes;
1159 std::size_t retained_actions = 0;
1160 bool connection_closed = false;
1161 for (auto& action : *plan->mutable_actions()) {
1162 if (connection_closed) {
1163 break;
1164 }
1165 ++retained_actions;
1166 switch (action.action_case()) {
1167 case curl::fuzzer::proto::Http3Action::kStructuredResponse: {
1168 auto* response = action.mutable_structured_response();
1169 if (response->status_code() == 0U) {
1170 response->set_status_code(200U);
1171 } else if (response->status_code() < 100U || response->status_code() > 599U) {
1172 response->set_status_code(100U + response->status_code() % 500U);
1173 }
1174 BoundHttp3Headers(response->mutable_response_headers(), scenario_limits::kMaxHttp3Headers,
1175 &remaining_header_bytes);
1176 BoundHttp3Headers(response->mutable_response_trailers(), scenario_limits::kMaxHttp3Trailers,
1177 &remaining_header_bytes);
1178 TrimRepeated(response->mutable_body_chunks(), scenario_limits::kMaxHttp3BodyChunks);
1179 for (std::string& chunk : *response->mutable_body_chunks()) {
1180 if (chunk.size() > remaining_body_bytes) {
1181 chunk.resize(remaining_body_bytes);
1182 }
1183 remaining_body_bytes -= chunk.size();
1184 }
1185 break;
1186 }
1187
1188 case curl::fuzzer::proto::Http3Action::kStreamWrite: {
1189 auto* write = action.mutable_stream_write();
1190 auto role = write->role();
1191 CanonicalizeHttp3StreamRole(&role);
1192 write->set_role(role);
1193 BoundHttp3RawData(write->mutable_data(), &remaining_raw_bytes);
1194 break;
1195 }
1196
1197 case curl::fuzzer::proto::Http3Action::kOpenUnidirectionalStream: {
1198 auto* stream = action.mutable_open_unidirectional_stream();
1199 BoundHttp3RawData(stream->mutable_data(), &remaining_raw_bytes);
1200 break;
1201 }
1202
1203 case curl::fuzzer::proto::Http3Action::kStreamReset: {
1204 auto* reset = action.mutable_stream_reset();
1205 auto role = reset->role();
1206 CanonicalizeHttp3StreamRole(&role);
1207 reset->set_role(role);
1208 reset->set_application_error_code(reset->application_error_code() & scenario_limits::kMaxQuicVarint);
1209 break;
1210 }
1211
1212 case curl::fuzzer::proto::Http3Action::kGoaway:
1213 action.mutable_goaway()->set_id(action.goaway().id() & scenario_limits::kMaxQuicVarint & ~std::uint64_t{3});
1214 break;
1215
1216 case curl::fuzzer::proto::Http3Action::kConnectionClose:
1217 action.mutable_connection_close()->set_application_error_code(
1218 action.connection_close().application_error_code() & scenario_limits::kMaxQuicVarint);
1219 connection_closed = true;
1220 break;
1221
1222 case curl::fuzzer::proto::Http3Action::ACTION_NOT_SET: {
1223 auto* response = action.mutable_structured_response();
1224 response->set_status_code(200);
1225 response->set_finish_stream(true);
1226 break;
1227 }
1228 }
1229 }
1230 TrimRepeated(plan->mutable_actions(), retained_actions);
1231}
1232
1236void BoundScenarioShape(curl::fuzzer::proto::Scenario* scenario) {
1237 TrimRepeated(scenario->mutable_options(), scenario_limits::kMaxOptions);
1238
1239 BoundHeaderValues(scenario->mutable_request_headers(), scenario_limits::kMaxRequestHeaders);
1240 BoundStringValues(scenario->mutable_telnet_options(), scenario_limits::kMaxTelnetOptions,
1242 if (scenario->has_mime_post()) {
1243 BoundMimeShape(scenario->mutable_mime_post());
1244 }
1245 if (scenario->has_upload()) {
1246 const bool telnet = scenario->scheme() == curl::fuzzer::proto::SCHEME_TELNET;
1247 const std::size_t data_limit = telnet ? scenario_limits::kMaxTelnetUploadBytes : scenario_limits::kMaxUploadBytes;
1248 const std::size_t read_step_limit =
1250 const std::size_t read_size_limit =
1252 BoundUploadShape(scenario->mutable_upload(), data_limit, read_step_limit, read_size_limit);
1253 }
1254
1255 BoundConnectionShape(scenario->mutable_connection());
1256 TrimRepeated(scenario->mutable_subsequent_connections(), scenario_limits::kMaxConnections - 1);
1257 for (auto& connection : *scenario->mutable_subsequent_connections()) {
1258 BoundConnectionShape(&connection);
1259 }
1260 BoundStringValues(scenario->mutable_mail_rcpt(), scenario_limits::kMaxMailRecipients,
1262 BoundFileInputs(scenario);
1263}
1264
1268void PruneAndBoundScenarioShape(curl::fuzzer::proto::Scenario* scenario, TargetProfile profile,
1269 ConditionalEligibilityPruner prune_conditional_eligibility) {
1270 if (prune_conditional_eligibility != nullptr) {
1271 prune_conditional_eligibility(scenario, profile);
1272 }
1273 BoundScenarioShape(scenario);
1274}
1275
1278void CanonicalizeDeepHttpSelectors(curl::fuzzer::proto::Scenario* scenario) {
1279 switch (scenario->http_transport_mode()) {
1280 case curl::fuzzer::proto::HTTP_TRANSPORT_DIRECT:
1281 case curl::fuzzer::proto::HTTP_TRANSPORT_FORWARD_PROXY:
1282 case curl::fuzzer::proto::HTTP_TRANSPORT_CONNECT_PROXY:
1283 case curl::fuzzer::proto::HTTP_TRANSPORT_UNIX_SOCKET:
1284 break;
1285 default:
1286 scenario->clear_http_transport_mode();
1287 break;
1288 }
1289 switch (scenario->proxy_socket_option_mode()) {
1290 case curl::fuzzer::proto::PROXY_SOCKET_OPTION_DEFAULT:
1291 case curl::fuzzer::proto::PROXY_SOCKET_OPTION_NOPROXY_HOSTNAME:
1292 case curl::fuzzer::proto::PROXY_SOCKET_OPTION_NOPROXY_IPV4_CIDR:
1293 case curl::fuzzer::proto::PROXY_SOCKET_OPTION_NOPROXY_IPV6_CIDR:
1294 case curl::fuzzer::proto::PROXY_SOCKET_OPTION_ENV_HTTP_PROXY:
1295 case curl::fuzzer::proto::PROXY_SOCKET_OPTION_LOOPBACK_INTERFACE_KEEPALIVE:
1296 break;
1297 default:
1298 scenario->clear_proxy_socket_option_mode();
1299 break;
1300 }
1301}
1302
1307std::uint64_t IntegralMutationValue(const curl::fuzzer::proto::SetOption& option) {
1308 switch (option.value_case()) {
1309 case curl::fuzzer::proto::SetOption::kUintValue:
1310 return option.uint_value();
1311 case curl::fuzzer::proto::SetOption::kBoolValue:
1312 return option.bool_value() ? 1U : 0U;
1313 case curl::fuzzer::proto::SetOption::kStringValue:
1314 case curl::fuzzer::proto::SetOption::VALUE_NOT_SET:
1315 return 0;
1316 }
1317 return 0;
1318}
1319
1322void CanonicalizeLocalPortRange(curl::fuzzer::proto::Scenario* scenario) {
1323 for (auto& option : *scenario->mutable_options()) {
1324 if (option.option_id() == curl::fuzzer::proto::CURLOPT_LOCALPORTRANGE) {
1325 option.set_uint_value(std::min<std::uint64_t>(IntegralMutationValue(option), kMaxLocalPortRange));
1326 }
1327 }
1328}
1329
1334void CanonicalizeFastHttpVersion(curl::fuzzer::proto::Scenario* scenario) {
1335 for (auto& option : *scenario->mutable_options()) {
1336 if (option.option_id() != curl::fuzzer::proto::CURLOPT_HTTP_VERSION) {
1337 continue;
1338 }
1339 const std::uint64_t version = IntegralMutationValue(option);
1340 // 2TLS remains HTTP/1.1 because this target fixes the scheme to HTTP.
1341 if (version <= CURL_HTTP_VERSION_2TLS) {
1342 option.set_uint_value(version);
1343 } else {
1344 option.set_uint_value(CURL_HTTP_VERSION_NONE);
1345 }
1346 }
1347}
1348
1351void CanonicalizeFtpOptionModes(curl::fuzzer::proto::Scenario* scenario) {
1352 for (auto& option : *scenario->mutable_options()) {
1353 switch (option.option_id()) {
1354 case curl::fuzzer::proto::CURLOPT_FTP_CREATE_MISSING_DIRS:
1355 option.set_uint_value(IntegralMutationValue(option) % 3U);
1356 break;
1357 case curl::fuzzer::proto::CURLOPT_FTP_FILEMETHOD:
1358 option.set_uint_value(IntegralMutationValue(option) % 4U);
1359 break;
1360 case curl::fuzzer::proto::CURLOPT_FTPPORT:
1361 // Never let a mutated active-mode address resolve or bind outside the
1362 // process. Empty remains reachable only by omitting the option.
1363 option.set_string_value("127.0.0.1");
1364 break;
1365 case curl::fuzzer::proto::CURLOPT_FTP_USE_EPRT:
1366 option.set_bool_value(IntegralMutationValue(option) != 0U);
1367 break;
1368 default:
1369 break;
1370 }
1371 }
1372}
1373
1377void CanonicalizeTftpOptionModes(curl::fuzzer::proto::Scenario* scenario) {
1378 for (auto& option : *scenario->mutable_options()) {
1379 if (option.option_id() != curl::fuzzer::proto::CURLOPT_TFTP_BLKSIZE) {
1380 continue;
1381 }
1382 std::uint64_t value = IntegralMutationValue(option);
1383 if (value == 0) {
1384 value = 512;
1385 }
1386 option.set_uint_value(std::max<std::uint64_t>(8, std::min<std::uint64_t>(value, 65464)));
1387 }
1388}
1389
1395void CanonicalizeRtspOptionModes(curl::fuzzer::proto::Scenario* scenario) {
1396 for (auto& option : *scenario->mutable_options()) {
1397 switch (option.option_id()) {
1398 case curl::fuzzer::proto::CURLOPT_RTSP_REQUEST: {
1399 const std::uint64_t value = IntegralMutationValue(option);
1400 // RECEIVE sends no request and instead waits for server-initiated
1401 // stream data, which the request-aligned peer never emits. Fold it
1402 // onto the default OPTIONS like zero and out-of-range values so every
1403 // retained request still issues a scripted exchange.
1404 const bool supported =
1405 value >= CURL_RTSPREQ_OPTIONS && value < CURL_RTSPREQ_LAST && value != CURL_RTSPREQ_RECEIVE;
1406 option.set_uint_value(supported ? value : CURL_RTSPREQ_OPTIONS);
1407 break;
1408 }
1409 case curl::fuzzer::proto::CURLOPT_RTSP_CLIENT_CSEQ:
1410 case curl::fuzzer::proto::CURLOPT_RTSP_SERVER_CSEQ:
1411 option.set_uint_value(std::min<std::uint64_t>(IntegralMutationValue(option),
1412 static_cast<std::uint64_t>(std::numeric_limits<int>::max())));
1413 break;
1414 default:
1415 break;
1416 }
1417 }
1418}
1419
1425void RemoveDeepHttpShape(curl::fuzzer::proto::Scenario* scenario) {
1426 scenario->clear_mime_post();
1427 scenario->clear_upload();
1428 scenario->clear_subsequent_connections();
1429
1430 auto* connection = scenario->mutable_connection();
1431 connection->clear_server_frames();
1432 connection->clear_manual_probes();
1433 connection->clear_backpressure();
1434}
1435
1440void RemoveIgnoredH2ProxyShape(curl::fuzzer::proto::Scenario* scenario) {
1441 scenario->clear_subsequent_connections();
1442 auto* connection = scenario->mutable_connection();
1443 connection->clear_server_frames();
1444 connection->clear_manual_probes();
1445 connection->clear_backpressure();
1446}
1447
1453void RemoveIgnoredWebSocketShape(curl::fuzzer::proto::Scenario* scenario) {
1454 scenario->clear_subsequent_connections();
1455 scenario->clear_mime_post();
1456}
1457
1463void RemoveNonTelnetShape(curl::fuzzer::proto::Scenario* scenario) {
1464 scenario->clear_subsequent_connections();
1465 scenario->clear_request_headers();
1466 scenario->clear_mime_post();
1467
1468 auto* connection = scenario->mutable_connection();
1469 connection->clear_server_frames();
1470 connection->clear_manual_probes();
1471 connection->clear_backpressure();
1472}
1473
1477void RemoveTelnetOnlyShape(curl::fuzzer::proto::Scenario* scenario) {
1478 scenario->clear_telnet_options();
1479 if (scenario->has_upload() && scenario->upload().terminal() == curl::fuzzer::proto::UPLOAD_TERMINAL_PAUSE) {
1480 scenario->mutable_upload()->set_terminal(curl::fuzzer::proto::UPLOAD_TERMINAL_EOF);
1481 }
1482}
1483
1487void RemoveApiOnlyShape(curl::fuzzer::proto::Scenario* scenario) { scenario->clear_api_plan(); }
1488
1491void RemoveMultiOnlyShape(curl::fuzzer::proto::Scenario* scenario) { scenario->clear_multi_plan(); }
1492
1496void RemoveIgnoredHttp3Shape(curl::fuzzer::proto::Scenario* scenario) {
1497 if (!scenario->http3_plan().use_h1_connect_udp_proxy()) {
1498 scenario->clear_connection();
1499 }
1500 scenario->clear_subsequent_connections();
1501 RemoveTelnetOnlyShape(scenario);
1502 RemoveApiOnlyShape(scenario);
1503 RemoveMultiOnlyShape(scenario);
1504}
1505
1510void RemoveUnusedFileTransferConnectionShape(curl::fuzzer::proto::Connection* connection,
1511 bool retain_backpressure = false) {
1512 connection->clear_server_frames();
1513 connection->clear_manual_probes();
1514 if (!retain_backpressure) {
1515 connection->clear_backpressure();
1516 }
1517}
1518
1523void RemoveIgnoredFtpShape(curl::fuzzer::proto::Scenario* scenario) {
1524 scenario->clear_request_headers();
1525 scenario->clear_mime_post();
1526 RemoveTelnetOnlyShape(scenario);
1527 RemoveApiOnlyShape(scenario);
1528 RemoveMultiOnlyShape(scenario);
1529
1530 RemoveUnusedFileTransferConnectionShape(scenario->mutable_connection());
1531 TrimRepeated(scenario->mutable_subsequent_connections(), scenario_limits::kMaxConnections - 1);
1532 for (auto& connection : *scenario->mutable_subsequent_connections()) {
1533 RemoveUnusedFileTransferConnectionShape(&connection);
1534 }
1535}
1536
1540void RemoveIgnoredTftpShape(curl::fuzzer::proto::Scenario* scenario) {
1541 scenario->clear_subsequent_connections();
1542 scenario->clear_request_headers();
1543 scenario->clear_mime_post();
1544 RemoveTelnetOnlyShape(scenario);
1545 RemoveApiOnlyShape(scenario);
1546 RemoveMultiOnlyShape(scenario);
1547 RemoveUnusedFileTransferConnectionShape(scenario->mutable_connection());
1548}
1549
1555void RemoveIgnoredSmtpShape(curl::fuzzer::proto::Scenario* scenario) {
1556 scenario->clear_subsequent_connections();
1557 scenario->clear_request_headers();
1558 RemoveTelnetOnlyShape(scenario);
1559 RemoveApiOnlyShape(scenario);
1560 RemoveMultiOnlyShape(scenario);
1561 RemoveUnusedFileTransferConnectionShape(scenario->mutable_connection());
1562}
1563
1569void RemoveIgnoredImapShape(curl::fuzzer::proto::Scenario* scenario) {
1570 scenario->clear_subsequent_connections();
1571 scenario->clear_request_headers();
1572 RemoveTelnetOnlyShape(scenario);
1573 RemoveApiOnlyShape(scenario);
1574 RemoveMultiOnlyShape(scenario);
1575 RemoveUnusedFileTransferConnectionShape(scenario->mutable_connection(), true);
1576}
1577
1581void RemoveIgnoredPop3Shape(curl::fuzzer::proto::Scenario* scenario) {
1582 scenario->clear_subsequent_connections();
1583 scenario->clear_request_headers();
1584 scenario->clear_mime_post();
1585 scenario->clear_upload();
1586 RemoveTelnetOnlyShape(scenario);
1587 RemoveApiOnlyShape(scenario);
1588 RemoveMultiOnlyShape(scenario);
1589 RemoveUnusedFileTransferConnectionShape(scenario->mutable_connection(), true);
1590}
1591
1592void RemoveIgnoredGopherShape(curl::fuzzer::proto::Scenario* scenario) {
1593 scenario->clear_subsequent_connections();
1594 scenario->clear_request_headers();
1595 scenario->clear_mime_post();
1596 scenario->clear_upload();
1597 RemoveTelnetOnlyShape(scenario);
1598 RemoveApiOnlyShape(scenario);
1599 RemoveMultiOnlyShape(scenario);
1600 RemoveUnusedFileTransferConnectionShape(scenario->mutable_connection());
1601}
1602
1606void RemoveIgnoredDictShape(curl::fuzzer::proto::Scenario* scenario) {
1607 scenario->clear_subsequent_connections();
1608 scenario->clear_request_headers();
1609 scenario->clear_mime_post();
1610 scenario->clear_upload();
1611 RemoveTelnetOnlyShape(scenario);
1612 RemoveApiOnlyShape(scenario);
1613 RemoveMultiOnlyShape(scenario);
1614 RemoveUnusedFileTransferConnectionShape(scenario->mutable_connection());
1615 // curl writes CLIENT, one lookup command, and QUIT in a single send, then
1616 // reads an EOF-framed body. The peer consumes only the first non-CLIENT
1617 // reply chunk and half-closes, so any later chunk is permanently dead work
1618 // the mutator keeps rediscovering.
1619 TrimRepeated(scenario->mutable_connection()->mutable_on_readable(), 1);
1620}
1621
1627void RemoveIgnoredMqttShape(curl::fuzzer::proto::Scenario* scenario) {
1628 scenario->clear_subsequent_connections();
1629 scenario->clear_request_headers();
1630 scenario->clear_mime_post();
1631 scenario->clear_upload();
1632 RemoveTelnetOnlyShape(scenario);
1633 RemoveApiOnlyShape(scenario);
1634 RemoveMultiOnlyShape(scenario);
1635 RemoveUnusedFileTransferConnectionShape(scenario->mutable_connection());
1636}
1637
1642void RemoveIgnoredFileShape(curl::fuzzer::proto::Scenario* scenario) {
1643 scenario->clear_connection();
1644 scenario->clear_subsequent_connections();
1645 scenario->clear_request_headers();
1646 scenario->clear_mime_post();
1647 RemoveTelnetOnlyShape(scenario);
1648 RemoveApiOnlyShape(scenario);
1649 RemoveMultiOnlyShape(scenario);
1650}
1651
1657void RemoveIgnoredLdapShape(curl::fuzzer::proto::Scenario* scenario) {
1658 scenario->clear_subsequent_connections();
1659 scenario->clear_request_headers();
1660 scenario->clear_mime_post();
1661 scenario->clear_upload();
1662 RemoveTelnetOnlyShape(scenario);
1663 RemoveApiOnlyShape(scenario);
1664 RemoveMultiOnlyShape(scenario);
1665 RemoveUnusedFileTransferConnectionShape(scenario->mutable_connection());
1666}
1667
1673void RemoveIgnoredRtspShape(curl::fuzzer::proto::Scenario* scenario) {
1674 scenario->clear_subsequent_connections();
1675 scenario->clear_mime_post();
1676 RemoveTelnetOnlyShape(scenario);
1677 RemoveApiOnlyShape(scenario);
1678 RemoveMultiOnlyShape(scenario);
1679
1680 auto* connection = scenario->mutable_connection();
1681 connection->clear_initial_response();
1682 RemoveUnusedFileTransferConnectionShape(connection);
1683}
1684
1689void RemoveIgnoredIpfsShape(curl::fuzzer::proto::Scenario* scenario) {
1690 RemoveApiOnlyShape(scenario);
1691 RemoveMultiOnlyShape(scenario);
1692 RemoveTelnetOnlyShape(scenario);
1693 RemoveDeepHttpShape(scenario);
1694}
1695
1699std::uint32_t CanonicalizeNonZero(std::uint32_t value, std::uint32_t minimum, std::uint32_t maximum) {
1700 if (value == 0) {
1701 return 0;
1702 }
1703 return std::max(minimum, std::min(value, maximum));
1704}
1705
1714curl::fuzzer::proto::Scheme PlaintextScheme(curl::fuzzer::proto::Scheme scheme) {
1715 switch (scheme) {
1716 case curl::fuzzer::proto::SCHEME_WS:
1717 case curl::fuzzer::proto::SCHEME_WSS:
1718 return curl::fuzzer::proto::SCHEME_WS;
1719 case curl::fuzzer::proto::SCHEME_HTTP:
1720 case curl::fuzzer::proto::SCHEME_HTTPS:
1721 case curl::fuzzer::proto::SCHEME_TELNET:
1722 case curl::fuzzer::proto::SCHEME_FTP:
1723 case curl::fuzzer::proto::SCHEME_TFTP:
1724 case curl::fuzzer::proto::SCHEME_DICT:
1725 case curl::fuzzer::proto::SCHEME_FILE:
1726 case curl::fuzzer::proto::SCHEME_UNSPECIFIED:
1727 default:
1728 return curl::fuzzer::proto::SCHEME_HTTP;
1729 }
1730}
1731
1735void ClearAllBackpressure(curl::fuzzer::proto::Scenario* scenario) {
1736 if (scenario->has_connection()) {
1737 scenario->mutable_connection()->clear_backpressure();
1738 }
1739 for (auto& connection : *scenario->mutable_subsequent_connections()) {
1740 connection.clear_backpressure();
1741 }
1742}
1743
1747void CanonicalizeOptionalBackpressure(curl::fuzzer::proto::Connection* connection) {
1748 if (!connection->has_backpressure()) {
1749 return;
1750 }
1751 auto* backpressure = connection->mutable_backpressure();
1752 if (backpressure->recv_buf_bytes() == 0 && backpressure->drain_limit() != 0) {
1753 backpressure->set_recv_buf_bytes(kDefaultBackpressureBufferBytes);
1754 } else {
1755 backpressure->set_recv_buf_bytes(
1756 CanonicalizeNonZero(backpressure->recv_buf_bytes(), kMinBackpressureBufferBytes, kMaxBackpressureBufferBytes));
1757 }
1758 backpressure->set_drain_limit(CanonicalizeNonZero(backpressure->drain_limit(), 1, kMaxDrainBytesPerIteration));
1759}
1760
1764void CanonicalizeMailBackpressure(curl::fuzzer::proto::Connection* connection) {
1765 if (!connection->has_backpressure()) {
1766 return;
1767 }
1768 auto* backpressure = connection->mutable_backpressure();
1769 if (backpressure->recv_buf_bytes() == 0) {
1770 connection->clear_backpressure();
1771 return;
1772 }
1773 backpressure->set_recv_buf_bytes(
1774 CanonicalizeNonZero(backpressure->recv_buf_bytes(), kMinBackpressureBufferBytes, kMaxBackpressureBufferBytes));
1775 backpressure->clear_drain_limit();
1776}
1777
1783void ApplyLanePolicy(curl::fuzzer::proto::Scenario* scenario, TargetProfile profile,
1784 ConditionalEligibilityPruner prune_conditional_eligibility) {
1785 if (scenario == nullptr) {
1786 return;
1787 }
1788
1789 if (profile == TargetProfile::kCompatibility) {
1790 // The original target's existing corpus predates profile splitting. A
1791 // no-op here makes the type safe to pass around while its binary continues
1792 // to omit postprocessor registration altogether. The append-only FTP/FTPS
1793 // and TFTP scheme values do not justify rewriting historical mixed-lane
1794 // inputs.
1795 return;
1796 }
1797
1798 // Only FastFile may turn protobuf bytes into a local descriptor. Generated
1799 // profiles already enforce this ownership; repeat it for direct callers of
1800 // the full runtime Scenario before any generic bounds inspect the field.
1801 if (profile != TargetProfile::kFastFile) {
1802 scenario->clear_file_source();
1803 }
1804
1805 // The nested selector belongs exclusively to the HTTPS CONNECT proxy lane.
1806 // Compatibility returned above and therefore preserves accumulated inputs;
1807 // every unrelated fixed lane drops work its peer cannot observe.
1808 if (profile != TargetProfile::kH2Proxy) {
1809 scenario->mutable_connection()->clear_proxy_protocol();
1810 for (auto& connection : *scenario->mutable_subsequent_connections()) {
1811 connection.clear_proxy_protocol();
1812 }
1813 }
1814
1815 // MQTT is append-only as well. No other fixed lane may reinterpret one of
1816 // its packet scripts, so clear the selector before the lane-specific early
1817 // paths install their own scheme. Compatibility returned above and keeps
1818 // historical unknown-field semantics.
1819 if (profile != TargetProfile::kFastMqtt && scenario->scheme() == curl::fuzzer::proto::SCHEME_MQTT) {
1820 scenario->clear_scheme();
1821 }
1822
1823 // DICT is append-only too. Only the dedicated lane may reinterpret a lookup
1824 // script; every other fixed lane clears the selector before installing its
1825 // own scheme.
1826 if (profile != TargetProfile::kFastDict && scenario->scheme() == curl::fuzzer::proto::SCHEME_DICT) {
1827 scenario->clear_scheme();
1828 }
1829
1830 // FILE is append-only as well. Other fixed lanes must not inherit a local
1831 // filesystem selector from a corpus input; only the dedicated lane may run
1832 // the peerless file:// path.
1833 if (profile != TargetProfile::kFastFile && scenario->scheme() == curl::fuzzer::proto::SCHEME_FILE) {
1834 scenario->clear_scheme();
1835 }
1836
1837 // LDAP and LDAPS are append-only too. Only the fixed BER lane may turn one
1838 // of their scripted bind/search exchanges into a live OpenLDAP session.
1839 if (profile != TargetProfile::kFastLdap && (scenario->scheme() == curl::fuzzer::proto::SCHEME_LDAP ||
1840 scenario->scheme() == curl::fuzzer::proto::SCHEME_LDAPS)) {
1841 scenario->clear_scheme();
1842 }
1843
1844 // RTSP is append-only too. Keep its selector away from every fixed lane
1845 // that cannot service a request-aligned RTSP exchange. Static option
1846 // membership is carried by each generated profile descriptor.
1847 if (profile != TargetProfile::kFastRtsp && scenario->scheme() == curl::fuzzer::proto::SCHEME_RTSP) {
1848 scenario->clear_scheme();
1849 }
1850 // FTPS is append-only too. Only the fixed FTP lane may turn one of its
1851 // scenarios into a TLS-wrapped control/data exchange.
1852 if (profile != TargetProfile::kFastFtp && scenario->scheme() == curl::fuzzer::proto::SCHEME_FTPS) {
1853 scenario->clear_scheme();
1854 }
1855
1856 // IPFS/IPNS are append-only as well. Their identifiers and gateway paths
1857 // would be inert in every other fixed lane, so clear the selector before
1858 // the lane-specific early paths install their own scheme.
1859 if (profile != TargetProfile::kFastIpfs && (scenario->scheme() == curl::fuzzer::proto::SCHEME_IPFS ||
1860 scenario->scheme() == curl::fuzzer::proto::SCHEME_IPNS)) {
1861 scenario->clear_scheme();
1862 }
1863
1864 // Accepted server push adds a harness-owned easy handle and therefore
1865 // belongs only in the fixed H2 origin lanes. Compatibility remains a no-op
1866 // above so accumulated mixed corpus entries keep their wire meaning.
1867 if (profile != TargetProfile::kHttpsH2 && profile != TargetProfile::kFastHttp2) {
1868 scenario->clear_accept_h2_push();
1869 }
1870
1871 // Only the dedicated TLS and QUIC peers consume a certificate-chain
1872 // selector. Remove it before protocol-specific early returns so other fixed
1873 // targets do not spend mutations on inert TLS server state.
1874 if (profile != TargetProfile::kFastHttps && profile != TargetProfile::kHttpsH2 &&
1876 scenario->clear_tls_certificate_chain();
1877 }
1878
1879 // Only the dedicated TLS HTTP/2 origin consumes the group selector. The
1880 // compatibility profile returned above so unknown fields remain stable.
1881 if (profile != TargetProfile::kHttpsH2) {
1882 scenario->clear_tls_group_profile();
1883 }
1884
1885 // Field 13 is append-only so the compatibility target can round-trip it,
1886 // but every other fixed lane must discard work its peer cannot consume.
1887 if (profile != TargetProfile::kFastHttp3) {
1888 scenario->clear_http3_plan();
1889 }
1890
1891 const bool resolver_uses_doh = profile == TargetProfile::kResolver && scenario->has_doh();
1892 if (profile != TargetProfile::kHttpsH2 && profile != TargetProfile::kFastHttp2 && !resolver_uses_doh) {
1893 scenario->clear_http2_plan();
1894 }
1895
1896 const bool deep_http_haproxy = profile == TargetProfile::kDeepHttp &&
1897 scenario->http_transport_mode() == curl::fuzzer::proto::HTTP_TRANSPORT_CONNECT_PROXY;
1898 if (profile != TargetProfile::kProxyTransport) {
1899 scenario->clear_socks_proxy_mode();
1900 if (!deep_http_haproxy) {
1901 scenario->clear_haproxy_protocol();
1902 }
1903 }
1904
1905 // StartTLS is a correlated LDAP transport mode rather than a transferable
1906 // scalar option. Other fixed lanes discard it before their early returns.
1907 if (profile != TargetProfile::kFastLdap) {
1908 scenario->clear_ldap_starttls();
1909 }
1910
1911 if (profile != TargetProfile::kFastPop3) {
1912 scenario->clear_pop3_starttls_filter_allocation_failure();
1913 }
1914
1915 if (profile != TargetProfile::kFastFtp) {
1916 scenario->clear_ftp_auth_tls_filter_allocation_failure();
1917 }
1918
1919 if (profile != TargetProfile::kFastGopher) {
1920 scenario->clear_gopher_probe_mode();
1921 }
1922
1923 if (profile != TargetProfile::kFastRtsp) {
1924 scenario->clear_rtsp_probe_mode();
1925 }
1926
1927 if (profile != TargetProfile::kFastMqtt) {
1928 scenario->clear_mqtt_probe_mode();
1929 }
1930
1931 if (profile != TargetProfile::kFastSmtp) {
1932 scenario->clear_smtp_probe_mode();
1933 }
1934
1935 if (profile != TargetProfile::kFastHttp2) {
1936 scenario->clear_http2_connect_flush_probe();
1937 }
1938
1939 if (profile != TargetProfile::kDeepHttp) {
1940 scenario->clear_http_transport_mode();
1941 scenario->clear_proxy_socket_option_mode();
1942 }
1943
1944 if (profile != TargetProfile::kResolver) {
1945 scenario->clear_resolve_entries();
1946 scenario->clear_happy_eyeballs();
1947 scenario->clear_doh();
1948 scenario->clear_resolver_mode();
1949 }
1950
1951 // SMTP's schema-native recipient list has no meaning in another protocol's
1952 // exchange. Clearing it before early returns keeps other fixed lanes from
1953 // carrying inert envelope work.
1954 if (profile != TargetProfile::kFastSmtp) {
1955 scenario->clear_mail_rcpt();
1956 }
1957
1958 // Only these protocol peers currently interleave zero-timeout public multi
1959 // polling with immediate service of their live connection.
1960 // Keep the opt-in out of fixed lanes whose driver cannot honor that timing
1961 // contract; compatibility returned above and preserves its wire input.
1962 if (profile != TargetProfile::kFastSmtp && profile != TargetProfile::kFastImap &&
1963 profile != TargetProfile::kFastPop3 && profile != TargetProfile::kFastFtp &&
1965 profile != TargetProfile::kDeepHttp) {
1966 scenario->clear_probe_multi_pollset();
1967 }
1968
1969 // Direct forwarding changes the H2 proxy stream from CONNECT authority-form
1970 // to an absolute-form origin request. No other peer implements that shape.
1971 if (profile != TargetProfile::kH2Proxy) {
1972 scenario->clear_h2_forward_proxy();
1973 }
1974
1975 // Anonymous parser files add syscalls and line parsing that belong only in
1976 // the deep HTTP lane. Clear them before every protocol-specific early path
1977 // so fast targets never pay to normalize content they cannot consume.
1978 if (profile != TargetProfile::kDeepHttp) {
1979 scenario->clear_cookie_file();
1980 scenario->clear_altsvc_file();
1981 scenario->clear_hsts_file();
1982 scenario->clear_netrc_file();
1983 }
1984
1985 // CRLs and public-key pin files are useful only when the target completes a
1986 // real origin TLS setup. Keep malformed or oversized inputs from turning
1987 // every other lane into an early TLS-option failure.
1988 if (profile != TargetProfile::kFastHttps) {
1989 scenario->clear_crl_file();
1990 scenario->clear_pinned_public_key_file();
1991 scenario->clear_tls_client_credential_profile();
1992 scenario->clear_tls_handshake_profile();
1993 }
1994
1995 if (profile == TargetProfile::kFastHttp3) {
1996 scenario->set_scheme(curl::fuzzer::proto::SCHEME_HTTPS);
1997 RemoveIgnoredHttp3Shape(scenario);
1998 PruneAndBoundScenarioShape(scenario, profile, prune_conditional_eligibility);
1999 // The ordinary QUIC peer consumes Http3Plan and has no use for a stream
2000 // response. CONNECT-UDP proxy mode instead feeds the retained Connection
2001 // bytes to curl's HTTP/1.1 proxy state machine and capsule filter.
2002 if (!scenario->http3_plan().use_h1_connect_udp_proxy()) {
2003 scenario->clear_connection();
2004 }
2005 BoundHttp3PlanShape(scenario->mutable_http3_plan());
2006 CanonicalizeHttp3Authority(scenario);
2007 CanonicalizeTlsCertificateChain(scenario);
2008 return;
2009 }
2010
2011 if (profile == TargetProfile::kFastTelnet) {
2012 // Set the scheme before general bounds so the TELNET-specific upload and
2013 // PAUSE budgets are selected rather than event-driven compatibility ones.
2014 scenario->set_scheme(curl::fuzzer::proto::SCHEME_TELNET);
2015 RemoveApiOnlyShape(scenario);
2016 RemoveMultiOnlyShape(scenario);
2017 RemoveNonTelnetShape(scenario);
2018 PruneAndBoundScenarioShape(scenario, profile, prune_conditional_eligibility);
2019 BoundTelnetResponse(scenario->mutable_connection());
2020 return;
2021 }
2022
2023 if (profile == TargetProfile::kFastHttp) {
2024 scenario->set_scheme(curl::fuzzer::proto::SCHEME_HTTP);
2025 RemoveApiOnlyShape(scenario);
2026 RemoveMultiOnlyShape(scenario);
2027 RemoveTelnetOnlyShape(scenario);
2028 RemoveDeepHttpShape(scenario);
2029 CanonicalizeFastHttpVersion(scenario);
2030 PruneAndBoundScenarioShape(scenario, profile, prune_conditional_eligibility);
2031 return;
2032 }
2033
2034 if (profile == TargetProfile::kH2Proxy) {
2035 scenario->set_scheme(curl::fuzzer::proto::SCHEME_HTTP);
2036 RemoveApiOnlyShape(scenario);
2037 RemoveMultiOnlyShape(scenario);
2038 RemoveTelnetOnlyShape(scenario);
2039 RemoveIgnoredH2ProxyShape(scenario);
2040 switch (scenario->connection().proxy_protocol()) {
2041 case curl::fuzzer::proto::PROXY_PROTOCOL_HTTP2:
2042 case curl::fuzzer::proto::PROXY_PROTOCOL_HTTP1:
2043 break;
2044 default:
2045 scenario->mutable_connection()->set_proxy_protocol(curl::fuzzer::proto::PROXY_PROTOCOL_HTTP2);
2046 break;
2047 }
2048 if (scenario->connection().proxy_protocol() == curl::fuzzer::proto::PROXY_PROTOCOL_HTTP1) {
2049 scenario->clear_h2_forward_proxy();
2050 }
2051 PruneAndBoundScenarioShape(scenario, profile, prune_conditional_eligibility);
2052 CanonicalizeH2ProxyOriginAuthority(scenario);
2053 return;
2054 }
2055
2056 if (profile == TargetProfile::kHttpsH2 || profile == TargetProfile::kFastHttp2) {
2057 scenario->set_scheme(profile == TargetProfile::kHttpsH2 ? curl::fuzzer::proto::SCHEME_HTTPS
2058 : curl::fuzzer::proto::SCHEME_HTTP);
2059 RemoveApiOnlyShape(scenario);
2060 RemoveMultiOnlyShape(scenario);
2061 RemoveTelnetOnlyShape(scenario);
2062 RemoveIgnoredH2ProxyShape(scenario);
2063 PruneAndBoundScenarioShape(scenario, profile, prune_conditional_eligibility);
2064 if (scenario->http2_connect_flush_probe()) {
2065 // CONNECT_ONLY supplies no HTTP response plan. Clear competing peer
2066 // bytes so mutations stay focused on the connection-filter lifecycle.
2067 scenario->clear_http2_plan();
2068 scenario->clear_connection();
2069 scenario->clear_subsequent_connections();
2070 }
2071 if (scenario->has_http2_plan()) {
2072 auto* connection = scenario->mutable_connection();
2073 connection->clear_initial_response();
2074 connection->clear_on_readable();
2075 connection->clear_server_frames();
2076 BoundHttp2PlanShape(scenario->mutable_http2_plan());
2077 }
2078 CanonicalizeTlsAuthority(scenario);
2079 if (profile == TargetProfile::kHttpsH2) {
2080 CanonicalizeTlsCertificateChain(scenario);
2081 CanonicalizeTlsGroupProfile(scenario);
2082 }
2083 return;
2084 }
2085
2086 if (profile == TargetProfile::kProxyTransport) {
2087 scenario->set_scheme(curl::fuzzer::proto::SCHEME_HTTP);
2088 switch (scenario->socks_proxy_mode()) {
2089 case curl::fuzzer::proto::SOCKS_PROXY_SOCKS4:
2090 case curl::fuzzer::proto::SOCKS_PROXY_SOCKS4A:
2091 case curl::fuzzer::proto::SOCKS_PROXY_SOCKS5:
2092 case curl::fuzzer::proto::SOCKS_PROXY_SOCKS5_HOSTNAME:
2093 break;
2094 default:
2095 scenario->set_socks_proxy_mode(curl::fuzzer::proto::SOCKS_PROXY_SOCKS4);
2096 break;
2097 }
2098 RemoveApiOnlyShape(scenario);
2099 RemoveMultiOnlyShape(scenario);
2100 RemoveTelnetOnlyShape(scenario);
2101 RemoveDeepHttpShape(scenario);
2102 scenario->mutable_connection()->clear_initial_response();
2103 PruneAndBoundScenarioShape(scenario, profile, prune_conditional_eligibility);
2104 CanonicalizeSocksAuthority(scenario);
2105 return;
2106 }
2107
2108 if (profile == TargetProfile::kResolver) {
2109 // The DoH variant owns the outer HTTPS exchange through a verified
2110 // TLS/ALPN h2 peer, so its response is an Http2Plan rather than a raw
2111 // stream script. The ordinary resolver lanes keep plaintext HTTP.
2112 switch (scenario->resolver_mode()) {
2113 case curl::fuzzer::proto::RESOLVER_MODE_DEFAULT:
2114 case curl::fuzzer::proto::RESOLVER_MODE_THREADED_POSITIVE:
2115 case curl::fuzzer::proto::RESOLVER_MODE_THREADED_NEGATIVE:
2116 case curl::fuzzer::proto::RESOLVER_MODE_THREADED_LINK_LOCAL:
2117 case curl::fuzzer::proto::RESOLVER_MODE_UNIX_SOCKET:
2118 case curl::fuzzer::proto::RESOLVER_MODE_THREADED_PARTIAL:
2119 case curl::fuzzer::proto::RESOLVER_MODE_GLOBAL_LIFECYCLE:
2120 case curl::fuzzer::proto::RESOLVER_MODE_THREADED_QUEUED_CANCEL:
2121 break;
2122 default:
2123 scenario->clear_resolver_mode();
2124 break;
2125 }
2126 if (scenario->has_doh()) {
2127 if (!curl::fuzzer::proto::DohResponseMode_IsValid(scenario->doh().response_mode())) {
2128 scenario->mutable_doh()->set_response_mode(curl::fuzzer::proto::DOH_RESPONSE_MODE_NORMAL);
2129 }
2130 if (!curl::fuzzer::proto::DohTransportMode_IsValid(scenario->doh().transport_mode())) {
2131 scenario->mutable_doh()->set_transport_mode(curl::fuzzer::proto::DOH_TRANSPORT_HTTP);
2132 }
2133 if (!curl::fuzzer::proto::DohOptionProbeMode_IsValid(scenario->doh().option_probe_mode())) {
2134 scenario->mutable_doh()->set_option_probe_mode(curl::fuzzer::proto::DOH_OPTION_PROBE_DEFAULT);
2135 }
2136 if (scenario->doh().option_probe_mode() != curl::fuzzer::proto::DOH_OPTION_PROBE_DEFAULT) {
2137 scenario->mutable_doh()->set_transport_mode(curl::fuzzer::proto::DOH_TRANSPORT_HTTPS);
2138 }
2139 }
2140 const bool fixed_resolver_mode = scenario->resolver_mode() != curl::fuzzer::proto::RESOLVER_MODE_DEFAULT;
2141 if (fixed_resolver_mode) {
2142 scenario->clear_resolve_entries();
2143 scenario->clear_happy_eyeballs();
2144 scenario->clear_doh();
2145 }
2146 const bool uses_doh = scenario->has_doh();
2147 scenario->set_scheme(uses_doh ? curl::fuzzer::proto::SCHEME_HTTPS : curl::fuzzer::proto::SCHEME_HTTP);
2148 RemoveApiOnlyShape(scenario);
2149 RemoveMultiOnlyShape(scenario);
2150 RemoveTelnetOnlyShape(scenario);
2151 RemoveDeepHttpShape(scenario);
2152 // The generic fast-lane pruning above removes fuzz-controlled timing
2153 // knobs. Threaded resolver modes add back one fixed marker so their
2154 // bounded drive performs a pollset probe and short fdset waits while the
2155 // target-local resolver work is outstanding.
2156 if (IsThreadedResolverMode(scenario->resolver_mode())) {
2157 scenario->mutable_connection()->mutable_backpressure()->set_drain_limit(1);
2158 }
2159 if (scenario->resolver_mode() == curl::fuzzer::proto::RESOLVER_MODE_THREADED_QUEUED_CANCEL) {
2160 // The runner observes the pool's public THREADS trace after releasing
2161 // its lookup gate. Seeing an idle worker proves it entered the configured
2162 // timed condition wait; mutated trace controls must not remove that
2163 // lifecycle contract.
2164 scenario->set_trace_config("+THREADS");
2165 }
2166 if (uses_doh) {
2167 // CURLOPT_RESOLVE cannot carry an HTTPS record, and the responder owns
2168 // both the address and HTTPS answers. Clearing the address entries keeps
2169 // the DoH query on the only path that can reach the ALPN selector.
2170 scenario->clear_resolve_entries();
2171 scenario->clear_happy_eyeballs();
2172 if (scenario->has_http2_plan()) {
2173 auto* connection = scenario->mutable_connection();
2174 connection->clear_initial_response();
2175 connection->clear_on_readable();
2176 connection->clear_server_frames();
2177 BoundHttp2PlanShape(scenario->mutable_http2_plan());
2178 }
2179 } else {
2180 scenario->clear_http2_plan();
2181 BoundStringValues(scenario->mutable_resolve_entries(), scenario_limits::kMaxResolveEntries,
2183 }
2184 PruneAndBoundScenarioShape(scenario, profile, prune_conditional_eligibility);
2185 CanonicalizeResolverAuthority(scenario);
2186 return;
2187 }
2188
2189 if (profile == TargetProfile::kFastFtp) {
2190 // The same lane services plaintext FTP and implicit-TLS FTPS through one
2191 // command/data parser. Preserve an explicitly supplied FTPS scheme so the
2192 // seed and mutations can select the TLS peer; every other scheme falls
2193 // back to the historical plaintext lane.
2194 const auto scheme = scenario->scheme();
2195 const bool implicit_tls = scheme == curl::fuzzer::proto::SCHEME_FTPS;
2196 scenario->set_scheme(implicit_tls ? curl::fuzzer::proto::SCHEME_FTPS : curl::fuzzer::proto::SCHEME_FTP);
2197 if (implicit_tls) {
2198 scenario->clear_ftp_auth_tls_filter_allocation_failure();
2199 }
2200 if (scenario->ftp_auth_tls_filter_allocation_failure()) {
2201 scenario->clear_fail_allocations();
2202 }
2203 RemoveIgnoredFtpShape(scenario);
2204 CanonicalizeFtpOptionModes(scenario);
2205 PruneAndBoundScenarioShape(scenario, profile, prune_conditional_eligibility);
2206 CanonicalizeFtpAuthority(scenario);
2207 return;
2208 }
2209
2210 if (profile == TargetProfile::kFastTftp) {
2211 scenario->set_scheme(curl::fuzzer::proto::SCHEME_TFTP);
2212 RemoveIgnoredTftpShape(scenario);
2213 CanonicalizeTftpOptionModes(scenario);
2214 PruneAndBoundScenarioShape(scenario, profile, prune_conditional_eligibility);
2215 CanonicalizeTftpAuthority(scenario);
2216 return;
2217 }
2218
2219 if (profile == TargetProfile::kFastSmtp) {
2220 // One command/reply parser services plaintext SMTP and implicit-TLS SMTPS.
2221 // Preserve an explicitly supplied SMTPS scheme so the seed and mutations
2222 // can select the TLS peer; every other scheme falls back to the historical
2223 // plaintext lane.
2224 const auto scheme = scenario->scheme();
2225 scenario->set_scheme(scheme == curl::fuzzer::proto::SCHEME_SMTPS ? curl::fuzzer::proto::SCHEME_SMTPS
2226 : curl::fuzzer::proto::SCHEME_SMTP);
2227 CanonicalizeSmtpProbeMode(scenario);
2228 if (scenario->scheme() == curl::fuzzer::proto::SCHEME_SMTPS) {
2229 scenario->clear_smtp_probe_mode();
2230 }
2231 if (scenario->smtp_probe_mode() == curl::fuzzer::proto::SMTP_PROBE_STARTTLS_FILTER_ALLOCATION_FAILURE) {
2232 scenario->clear_fail_allocations();
2233 }
2234 if (scenario->smtp_probe_mode() == curl::fuzzer::proto::SMTP_PROBE_SEND_BACKPRESSURE) {
2235 scenario->clear_mail_rcpt();
2236 scenario->clear_upload();
2237 scenario->clear_mime_post();
2238 }
2239 RemoveIgnoredSmtpShape(scenario);
2240 PruneAndBoundScenarioShape(scenario, profile, prune_conditional_eligibility);
2241 if (scenario->smtp_probe_mode() == curl::fuzzer::proto::SMTP_PROBE_SEND_BACKPRESSURE) {
2242 scenario->set_probe_multi_pollset(true);
2243 auto* backpressure = scenario->mutable_connection()->mutable_backpressure();
2244 backpressure->set_recv_buf_bytes(kSmtpProbeBufferBytes);
2245 backpressure->set_drain_limit(kSmtpProbeDrainBytes);
2246 }
2247 CanonicalizeSmtpAuthority(scenario);
2248 return;
2249 }
2250
2251 if (profile == TargetProfile::kFastImap) {
2252 // Preserve an explicitly supplied IMAPS scheme so seeds and mutations can
2253 // select the implicit-TLS peer; every other scheme falls back to the
2254 // historical plaintext lane. Both values reuse this tag-aware parser.
2255 const auto scheme = scenario->scheme();
2256 scenario->set_scheme(scheme == curl::fuzzer::proto::SCHEME_IMAPS ? curl::fuzzer::proto::SCHEME_IMAPS
2257 : curl::fuzzer::proto::SCHEME_IMAP);
2258 RemoveIgnoredImapShape(scenario);
2259 PruneAndBoundScenarioShape(scenario, profile, prune_conditional_eligibility);
2260 CanonicalizeMailBackpressure(scenario->mutable_connection());
2261 CanonicalizeImapAuthority(scenario);
2262 return;
2263 }
2264
2265 if (profile == TargetProfile::kFastPop3) {
2266 // Preserve an explicitly supplied POP3S scheme so seeds and mutations can
2267 // select the implicit-TLS peer; every other scheme falls back to the
2268 // historical plaintext lane. Both values reuse this status-line parser.
2269 const auto scheme = scenario->scheme();
2270 scenario->set_scheme(scheme == curl::fuzzer::proto::SCHEME_POP3S ? curl::fuzzer::proto::SCHEME_POP3S
2271 : curl::fuzzer::proto::SCHEME_POP3);
2272 if (scenario->scheme() == curl::fuzzer::proto::SCHEME_POP3S) {
2273 scenario->clear_pop3_starttls_filter_allocation_failure();
2274 }
2275 if (scenario->pop3_starttls_filter_allocation_failure()) {
2276 scenario->clear_fail_allocations();
2277 }
2278 RemoveIgnoredPop3Shape(scenario);
2279 PruneAndBoundScenarioShape(scenario, profile, prune_conditional_eligibility);
2280 CanonicalizeMailBackpressure(scenario->mutable_connection());
2281 CanonicalizePop3Authority(scenario);
2282 return;
2283 }
2284
2285 if (profile == TargetProfile::kFastMqtt) {
2286 // The dedicated MQTT lane services both the plaintext and implicit-TLS
2287 // variants through one packet-aware parser. Preserve an explicitly
2288 // supplied MQTTS scheme so the seed and mutations can select the TLS peer;
2289 // every other scheme falls back to the historical plaintext lane.
2290 CanonicalizeMqttProbeMode(scenario);
2291 const auto probe = scenario->mqtt_probe_mode();
2292 const auto scheme = scenario->scheme();
2293 scenario->set_scheme(probe == curl::fuzzer::proto::MQTT_PROBE_DEFAULT && scheme == curl::fuzzer::proto::SCHEME_MQTTS
2294 ? curl::fuzzer::proto::SCHEME_MQTTS
2295 : curl::fuzzer::proto::SCHEME_MQTT);
2296 RemoveIgnoredMqttShape(scenario);
2297 PruneAndBoundScenarioShape(scenario, profile, prune_conditional_eligibility);
2298 if (probe == curl::fuzzer::proto::MQTT_PROBE_SEND_BACKPRESSURE) {
2299 auto* backpressure = scenario->mutable_connection()->mutable_backpressure();
2300 backpressure->set_recv_buf_bytes(kMqttProbeBufferBytes);
2301 backpressure->set_drain_limit(kMqttProbeDrainBytes);
2302 scenario->set_probe_multi_pollset(true);
2303 }
2304 CanonicalizeMqttAuthority(scenario);
2305 return;
2306 }
2307
2308 if (profile == TargetProfile::kFastDict) {
2309 scenario->set_scheme(curl::fuzzer::proto::SCHEME_DICT);
2310 RemoveIgnoredDictShape(scenario);
2311 PruneAndBoundScenarioShape(scenario, profile, prune_conditional_eligibility);
2312 CanonicalizeDictAuthority(scenario);
2313 return;
2314 }
2315
2316 if (profile == TargetProfile::kFastGopher) {
2317 CanonicalizeGopherProbeMode(scenario);
2318 const auto probe = scenario->gopher_probe_mode();
2319 if (probe == curl::fuzzer::proto::GOPHER_PROBE_TLS_SESSION_KEY_LIMIT) {
2320 scenario->set_scheme(curl::fuzzer::proto::SCHEME_GOPHERS);
2321 } else if (probe != curl::fuzzer::proto::GOPHER_PROBE_DEFAULT) {
2322 scenario->set_scheme(curl::fuzzer::proto::SCHEME_GOPHER);
2323 } else {
2324 scenario->set_scheme(scenario->scheme() == curl::fuzzer::proto::SCHEME_GOPHERS
2325 ? curl::fuzzer::proto::SCHEME_GOPHERS
2326 : curl::fuzzer::proto::SCHEME_GOPHER);
2327 }
2328 RemoveIgnoredGopherShape(scenario);
2329 PruneAndBoundScenarioShape(scenario, profile, prune_conditional_eligibility);
2330 if (probe == curl::fuzzer::proto::GOPHER_PROBE_TLS_SESSION_KEY_LIMIT) {
2331 scenario->set_host_path(std::string(kGopherTlsPeerKeyHostBytes, 'g') + "/1/session-key-limit");
2332 } else {
2333 CanonicalizeTlsAuthority(scenario);
2334 }
2335 return;
2336 }
2337
2338 if (profile == TargetProfile::kFastFile) {
2339 scenario->set_scheme(curl::fuzzer::proto::SCHEME_FILE);
2340 scenario->clear_host_path();
2341 RemoveIgnoredFileShape(scenario);
2342 PruneAndBoundScenarioShape(scenario, profile, prune_conditional_eligibility);
2343 if (scenario->file_source().size() > scenario_limits::kMaxFileInputBytes) {
2344 scenario->mutable_file_source()->resize(scenario_limits::kMaxFileInputBytes);
2345 }
2346 return;
2347 }
2348
2349 if (profile == TargetProfile::kFastLdap) {
2350 // The single LDAP target services plaintext LDAP and implicit-TLS LDAPS
2351 // through one BER parser. Preserve an explicitly supplied LDAPS scheme so
2352 // the seed and mutations can select the TLS peer; every other scheme falls
2353 // back to the historical plaintext lane.
2354 const auto scheme = scenario->scheme();
2355 scenario->set_scheme(scheme == curl::fuzzer::proto::SCHEME_LDAPS ? curl::fuzzer::proto::SCHEME_LDAPS
2356 : curl::fuzzer::proto::SCHEME_LDAP);
2357 if (scenario->scheme() == curl::fuzzer::proto::SCHEME_LDAPS) {
2358 scenario->clear_ldap_starttls();
2359 }
2360 RemoveIgnoredLdapShape(scenario);
2361 PruneAndBoundScenarioShape(scenario, profile, prune_conditional_eligibility);
2362 CanonicalizeLdapAuthority(scenario);
2363 return;
2364 }
2365
2366 if (profile == TargetProfile::kFastRtsp) {
2367 scenario->set_scheme(curl::fuzzer::proto::SCHEME_RTSP);
2368 RemoveIgnoredRtspShape(scenario);
2369 CanonicalizeRtspOptionModes(scenario);
2370 CanonicalizeRtspProbeMode(scenario);
2371 PruneAndBoundScenarioShape(scenario, profile, prune_conditional_eligibility);
2372 CanonicalizeRtspAuthority(scenario);
2373 return;
2374 }
2375
2376 if (profile == TargetProfile::kFastIpfs) {
2377 // Preserve an explicitly selected IPNS name; every other pre-policy value
2378 // falls back to the historical IPFS lane so one binary covers both.
2379 scenario->set_scheme(scenario->scheme() == curl::fuzzer::proto::SCHEME_IPNS ? curl::fuzzer::proto::SCHEME_IPNS
2380 : curl::fuzzer::proto::SCHEME_IPFS);
2381 RemoveIgnoredIpfsShape(scenario);
2382 CanonicalizeFastHttpVersion(scenario);
2383 PruneAndBoundScenarioShape(scenario, profile, prune_conditional_eligibility);
2384 CanonicalizeIpfsAuthority(scenario);
2385 return;
2386 }
2387
2388 // Select the lane's scheme before applying scheme-sensitive upload bounds.
2389 // The scheme field is itself mutable, so bounding first could accidentally
2390 // give an HTTP/WS case TELNET's smaller payload budget merely because that
2391 // was the input's pre-policy value.
2392 switch (profile) {
2394 return;
2396 scenario->set_scheme(curl::fuzzer::proto::SCHEME_HTTP);
2397 break;
2399 scenario->set_scheme(curl::fuzzer::proto::SCHEME_HTTP);
2400 break;
2402 scenario->set_scheme(curl::fuzzer::proto::SCHEME_HTTP);
2403 break;
2405 scenario->set_scheme(curl::fuzzer::proto::SCHEME_HTTPS);
2406 break;
2408 // The protocol-specific early path fixes ALPN and prunes incompatible
2409 // response shapes while retaining the raw frame script.
2410 return;
2412 // The protocol-specific early path fixes prior-knowledge H2 and prunes
2413 // incompatible response shapes while retaining its frame script.
2414 return;
2416 // The protocol-specific early path owns the QUIC response plan.
2417 return;
2419 // The early path removes proxy-incompatible fields before general
2420 // bounds, keeping raw frame mutation dense.
2421 return;
2423 // The early path fixes proxy routing and request-triggered replies.
2424 return;
2426 // The early path fixes safe DNS/cache routing and bounds its slist.
2427 return;
2429 scenario->set_scheme(curl::fuzzer::proto::SCHEME_WS);
2430 break;
2432 scenario->set_scheme(curl::fuzzer::proto::SCHEME_WSS);
2433 break;
2435 scenario->set_scheme(PlaintextScheme(scenario->scheme()));
2436 break;
2451 // Protocol-specific early-return paths selected their scheme above.
2452 return;
2453 }
2454
2455 // TELNET's retained slist and synchronous pause have no observable, safe
2456 // meaning in the fixed event-driven lanes. Clear them before walking the
2457 // general shape so only the compatibility and TELNET targets can retain
2458 // those values.
2459 RemoveTelnetOnlyShape(scenario);
2460 if (profile != TargetProfile::kApi) {
2461 RemoveApiOnlyShape(scenario);
2462 }
2463 if (profile != TargetProfile::kMulti) {
2464 RemoveMultiOnlyShape(scenario);
2465 }
2466 if (profile == TargetProfile::kDeepHttp) {
2467 CanonicalizeDeepHttpSelectors(scenario);
2468 }
2469 if (profile == TargetProfile::kApi && scenario->has_api_plan()) {
2470 CanonicalizeApiDriveMode(scenario->mutable_api_plan());
2471 }
2472 PruneAndBoundScenarioShape(scenario, profile, prune_conditional_eligibility);
2473
2474 switch (profile) {
2476 return;
2478 // Handled before the general bounds so discarded deep shapes are never
2479 // traversed on the fast path.
2480 return;
2481
2483 if (scenario->proxy_socket_option_mode() != curl::fuzzer::proto::PROXY_SOCKET_OPTION_DEFAULT) {
2484 // A scenario describes one owned route. Prefer the closed socket-option
2485 // profile when a mutation selects both independent fields.
2486 scenario->clear_http_transport_mode();
2487 CanonicalizeProxySocketOptionAuthority(scenario);
2488 // Alt-Svc or HSTS can replace this mode's fixed plaintext authority
2489 // before its option path runs. Keep the focused route authoritative.
2490 scenario->clear_altsvc_file();
2491 scenario->clear_hsts_file();
2492 } else if (scenario->http_transport_mode() != curl::fuzzer::proto::HTTP_TRANSPORT_DIRECT) {
2493 CanonicalizeHttpTransportAuthority(scenario);
2494 // The selected transport owns the route. Alt-Svc and HSTS can rewrite
2495 // its fixed plaintext authority into a different proxy or TLS path,
2496 // defeating the mode the scenario is meant to exercise.
2497 scenario->clear_altsvc_file();
2498 scenario->clear_hsts_file();
2499 } else if (!scenario->altsvc_file().empty()) {
2500 CanonicalizeAltSvcAuthority(scenario);
2501 }
2502 if (scenario->http_transport_mode() == curl::fuzzer::proto::HTTP_TRANSPORT_CONNECT_PROXY &&
2503 scenario->haproxy_protocol()) {
2504 // This closed combination makes the pending HAProxy setup state
2505 // reachable after CONNECT. A large checked-in request fills the
2506 // tightened socket, while bounded peer drains and repeated public
2507 // multi polling expose the filter's write-only pollset.
2508 auto* backpressure = scenario->mutable_connection()->mutable_backpressure();
2509 backpressure->set_recv_buf_bytes(kHaproxyConnectBufferBytes);
2510 backpressure->set_drain_limit(kHaproxyConnectDrainBytes);
2511 for (auto& connection : *scenario->mutable_subsequent_connections()) {
2512 connection.clear_backpressure();
2513 }
2514 } else {
2515 scenario->clear_haproxy_protocol();
2516 ClearAllBackpressure(scenario);
2517 }
2518 if (scenario->http_transport_mode() != curl::fuzzer::proto::HTTP_TRANSPORT_CONNECT_PROXY) {
2519 scenario->clear_probe_multi_pollset();
2520 }
2521 return;
2522
2524 ClearAllBackpressure(scenario);
2525 if (scenario->host_path().size() > scenario_limits::kMaxApiStringBytes) {
2526 scenario->mutable_host_path()->resize(scenario_limits::kMaxApiStringBytes);
2527 }
2528 if (scenario->has_api_plan()) {
2529 BoundApiPlanShape(scenario->mutable_api_plan());
2530 CanonicalizeApiMimeRepeat(scenario);
2531 }
2532 return;
2533
2534 case TargetProfile::kMulti: {
2535 ClearAllBackpressure(scenario);
2536 CanonicalizeMultiAuthority(scenario);
2537 // The multi runner is HTTP-only (it drives every handle through one
2538 // plaintext HTTP peer), so pin the scheme instead of letting a mutation
2539 // select a protocol the runner cannot service.
2540 scenario->set_scheme(curl::fuzzer::proto::SCHEME_HTTP);
2541 auto* plan = scenario->mutable_multi_plan();
2542 BoundMultiPlanShape(plan);
2543 TrimRepeated(scenario->mutable_subsequent_connections(), plan->transfer_count() - 1U);
2544 return;
2545 }
2546
2548 ClearAllBackpressure(scenario);
2549 CanonicalizeTlsAuthority(scenario);
2550 CanonicalizeTlsCertificateChain(scenario);
2551 CanonicalizeTlsClientCredentialProfile(scenario);
2552 CanonicalizeTlsHandshakeProfile(scenario);
2553 return;
2554
2556 // Handled by the raw HTTP/2 protocol-specific early path above.
2557 return;
2558
2560 // Handled by the plaintext HTTP/2 protocol-specific early path above.
2561 return;
2562
2564 // Handled before generic connection bounding because Http3Plan replaces
2565 // the stream-socket response script.
2566 return;
2567
2569 // Handled by the protocol-specific early path above.
2570 return;
2571
2573 // Handled by the protocol-specific early path above.
2574 return;
2575
2577 // Handled by the resolver-specific early path above.
2578 return;
2579
2581 ClearAllBackpressure(scenario);
2582 RemoveIgnoredWebSocketShape(scenario);
2583 return;
2584
2586 ClearAllBackpressure(scenario);
2587 RemoveIgnoredWebSocketShape(scenario);
2588 CanonicalizeTlsAuthority(scenario);
2589 CanonicalizeTlsCertificateChain(scenario);
2590 return;
2591
2593 // Handled before the general bounds so its protocol-specific limits are
2594 // selected from the start.
2595 return;
2596
2609 // Their peers consume narrower raw-script shapes, pruned before general
2610 // bounds so ignored fields never tax these fast paths.
2611 return;
2612
2614 if (scenario->scheme() == curl::fuzzer::proto::SCHEME_WS) {
2615 RemoveIgnoredWebSocketShape(scenario);
2616 }
2617 auto* backpressure = scenario->mutable_connection()->mutable_backpressure();
2618 if (backpressure->recv_buf_bytes() == 0) {
2619 // A drain limit alone cannot fill the default AF_UNIX buffer with the
2620 // harness's bounded upload. Always tighten the socket so this lane
2621 // represents real pressure, not merely selection of the timed loop.
2622 backpressure->set_recv_buf_bytes(kDefaultBackpressureBufferBytes);
2623 } else {
2624 backpressure->set_recv_buf_bytes(CanonicalizeNonZero(backpressure->recv_buf_bytes(),
2625 kMinBackpressureBufferBytes, kMaxBackpressureBufferBytes));
2626 }
2627 backpressure->set_drain_limit(CanonicalizeNonZero(backpressure->drain_limit(), 1, kMaxDrainBytesPerIteration));
2628 for (auto& connection : *scenario->mutable_subsequent_connections()) {
2629 CanonicalizeOptionalBackpressure(&connection);
2630 }
2631 return;
2632 }
2633 }
2634}
2635
2636} // namespace
2637
2643void NormalizeScenarioForExecution(curl::fuzzer::proto::Scenario* scenario) {
2644 if (scenario == nullptr) {
2645 return;
2646 }
2647 CanonicalizeAllocationFailures(scenario);
2648 CanonicalizeAllocationFailureDrive(scenario);
2649 CanonicalizeLocalPortRange(scenario);
2650}
2651
2664void NormalizeScenarioForTarget(curl::fuzzer::proto::Scenario* scenario, TargetProfile profile,
2665 ConditionalEligibilityPruner prune_conditional_eligibility) {
2666 // Compatibility has no LPM postprocessor. Preserve the complete wire input,
2667 // including mismatched option oneofs and newly added, otherwise inert fields.
2668 if (scenario == nullptr || profile == TargetProfile::kCompatibility) {
2669 return;
2670 }
2671
2672 ApplyLanePolicy(scenario, profile, prune_conditional_eligibility);
2675 // Canonicalization can materialize string values or prepend pin syntax.
2676 // Charge the final representation so normalization remains a fixed point.
2677 for (auto& option : *scenario->mutable_options()) {
2678 if (option.value_case() == curl::fuzzer::proto::SetOption::kStringValue) {
2679 TrimMetadata(option.mutable_string_value());
2680 }
2681 }
2682}
2683
2684} // namespace proto_fuzzer
void CanonicalizeOptionValueCases(curl::fuzzer::proto::Scenario *scenario)
Restore each recognized option's consumed oneof member without calling libcurl.
Pure interpretation and normalization of structured curl options.
Shared complexity limits for structured scenarios.
constexpr std::size_t kMaxUploadReadSize
Larger read requests are indistinguishable once the whole body fits.
constexpr std::size_t kMaxHttp3Trailers
Trailer lists need fewer entries while retaining duplicate-name behavior.
constexpr std::size_t kMaxHttp2Settings
Bound each SETTINGS payload while retaining every standard identifier and several unknown extension v...
constexpr std::size_t kMaxHttp3BodyChunks
Bound the number of DATA-frame boundaries independently of total bytes.
constexpr std::size_t kMaxTelnetOptionBytes
Preserve curl's explicit over-1000-byte TTYPE/XDISPLOC rejection paths without giving arbitrary optio...
constexpr std::size_t kMaxHttp2Actions
Ordered H2 work can express startup, barriers, informational responses, flow-control changes,...
constexpr std::size_t kMaxHttp2YieldTurns
Sixteen turns preserve meaningful scheduler boundaries without letting a single scalar consume the co...
constexpr std::size_t kMinMultiTransfers
Two handles are the minimum that can enter shared-multi scheduling, while four align with the mock's ...
constexpr std::size_t kMaxHttp3Headers
Keep each structured header block small enough for cheap QPACK encoding.
constexpr std::size_t kMaxTelnetUploadReadSize
The TELNET peer drains immediately before each callback result.
constexpr std::uint64_t kMaxQuicVarint
QUIC variable-length integers reserve their two high bits for encoding.
constexpr std::size_t kMaxTelnetOptions
TELNET has only a handful of meaningful negotiated preferences.
constexpr std::size_t kMaxHttp3RawWriteBytes
Raw plaintext is primarily for malformed frame/QPACK prefixes; a compact per-action cap preserves tha...
constexpr std::size_t kMaxUploadReadSteps
Short-read scripts need several transitions, not mutation-sized sequences.
constexpr std::size_t kMaxApiReentrantSelectors
Each reentrancy selector fires one public API call from inside a callback.
constexpr std::size_t kMaxHttp2RawFrameBytes
Raw frame payloads target malformed parsing rather than bulk-body work.
constexpr std::size_t kMaxHttp3Actions
Ordered H3 work needs enough slots to interleave response fragments with stream and connection state ...
constexpr std::size_t kMaxMultiActions
Ordered lifecycle transitions remain useful in pairs (pause/resume and remove/re-add); sixteen permit...
constexpr std::size_t kMaxMimeDataBytes
MIME payloads remain large enough to cross encoder buffer boundaries.
constexpr std::size_t kMaxHttp2RawBytes
Bound aggregate raw escape data across an action sequence.
constexpr std::size_t kMaxResponseChunks
Enough response boundaries to exercise incremental parsers while keeping per-perform delivery work fi...
constexpr std::size_t kMaxHttp2HeaderBytes
Share one metadata ceiling across all H2 header blocks in a scenario.
constexpr std::size_t kMaxHttp2Headers
Keep structured HPACK work proportional to the action budget.
constexpr std::size_t kMaxMultiTransfers
Maximum easy handles configured in one shared-multi fuzz iteration.
constexpr std::size_t kMaxMailRecipientBytes
Keep recipient mailbox bytes within curl's normal address-processing range; oversized suffixes add no...
constexpr std::size_t kMaxMailRecipients
SMTP envelope recipients are schema-native because CURLOPT_MAIL_RCPT is a curl_slist.
constexpr std::size_t kMaxTelnetUploadReadSteps
OSS-Fuzz's local AF_UNIX peer normally fills curl's 4 KiB TELNET reads, so the bounded 8 KiB response...
constexpr std::size_t kMaxApiShareDataSelectors
A share handle has only a small fixed set of shareable data families.
constexpr std::size_t kMaxRequestHeaders
Request headers retain useful combinatorial interactions at this size.
constexpr std::size_t kMaxNestedMimeParts
One bounded child layer is sufficient to enter recursive MIME code.
constexpr std::size_t kMaxTotalMimeParts
Share a total budget so many parents cannot multiply their child caps.
constexpr std::size_t kMaxIpfsHostPathBytes
IPFS and IPNS inputs carry one CID/name plus a gateway path.
constexpr std::size_t kMaxUploadBytes
Maximum callback-backed body bytes visible in one scenario.
constexpr std::size_t kMaxMimeHeadersPerPart
Per-part headers are bounded separately from the part count.
constexpr std::size_t kMaxConnections
Four sockets cover redirect/auth flows without permitting mutation-sized connection chains to dominat...
constexpr std::size_t kMaxApiStringBytes
URL escaping can expand input threefold.
constexpr std::size_t kMaxFileInputBytes
One filename-only parser input can span many lines and cross curl's normal transfer buffer boundary w...
constexpr std::size_t kMaxResolveEntries
Host-cache mutations need only a compact sequence of add/remove/wildcard entries.
constexpr std::size_t kMaxApiInfoSelectors
One API iteration can cheaply inspect several result families, but an unbounded list would repeatedly...
constexpr std::size_t kMaxAllocationOrdinal
Fold mutated ordinals into a range large enough for the bounded transfer drivers while keeping most s...
constexpr std::size_t kMaxHttp2DataBytes
Structured DATA remains large enough to cross curl's callback boundary.
constexpr std::size_t kMaxHttp3HeaderBytes
Share one metadata budget across every structured header and trailer block so many actions cannot mul...
constexpr std::size_t kMaxResolveEntryBytes
CURLOPT_RESOLVE consumes NUL-terminated host:port:address records.
constexpr std::size_t kMaxHttp3HeaderValueBytes
Values use the shared metadata ceiling used by other header-bearing APIs.
constexpr std::size_t kMaxHttp3HeaderNameBytes
Header names are tokens rather than general metadata.
constexpr std::size_t kMaxHttp3BodyBytes
A response body can still cross curl's normal 16 KiB callback boundary.
constexpr std::size_t kMaxOptions
Scalar options are cheap, but a finite list prevents protobuf allocation from growing independently o...
constexpr std::size_t kMaxGeneratedMimeDataBytes
Retain both sides of curl's 64 KiB send-buffer boundary.
constexpr std::size_t kMaxTelnetUploadBytes
TELNET writes upload bytes while its blocking driver owns the thread, so keep total callback work sma...
constexpr std::size_t kMaxTopLevelMimeParts
Bound top-level MIME traversal before curl recursively serializes it.
constexpr std::size_t kMaxMetadataBytes
Metadata reaches NUL-terminated APIs, where giant suffixes add no coverage.
constexpr std::size_t kMaxGeneratedMimePatternBytes
Longer repeated patterns are better expressed as ordinary MIME data and make the compact source less ...
constexpr std::size_t kMaxDictHostPathBytes
DICT builds its complete command synchronously before the outer mock loop can drain curl's socket.
constexpr std::size_t kMaxHttp3RawBytes
Share one ceiling across all raw actions so sixteen maximum-sized writes cannot make one fuzzer itera...
constexpr std::size_t kMaxFileInputTotalBytes
Share a ceiling across filename-backed fixtures so populating several in one scenario cannot multiply...
void NormalizeScenarioForExecution(curl::fuzzer::proto::Scenario *scenario)
Apply execution-only allocation-failure safety rules.
void NormalizeScenarioForTarget(curl::fuzzer::proto::Scenario *scenario, TargetProfile profile, ConditionalEligibilityPruner prune_conditional_eligibility)
Complete normalization for a loaded or mutated fixed-target Scenario.
Mutation policies that keep each proto fuzzer in one intentional performance and protocol lane.
void(*)(curl::fuzzer::proto::Scenario *, TargetProfile) ConditionalEligibilityPruner
Generated callback that removes conditionally ineligible fields and options.
TargetProfile
Identifies one compiled proto-fuzzer lane.
@ kFastSmtp
Exercise single-connection SMTP and implicit-TLS SMTPS mail transfers.
@ kMulti
Exercise several easy handles through one shared multi handle.
@ kFastWebSocket
Exercise plaintext WebSocket framing without wall-clock waits.
@ kProxyTransport
Exercise HTTP through an in-process SOCKS proxy, optionally preceded by a HAProxy protocol-v1 prefix ...
@ kFastGopher
Exercise Gopher selectors through the bounded stream peer.
@ kApi
Exercise easy, share, multi, URL, and result API lifecycles.
@ kFastMqtt
Exercise binary MQTT and implicit-TLS MQTTS CONNECT/PUBLISH/SUBSCRIBE exchanges through the packet-aw...
@ kFastDict
Exercise line-based DICT lookups through the raw stream peer.
@ kFastHttp
Keep ordinary HTTP iterations cheap enough for a high mutation rate.
@ kFastFtp
Exercise FTP control and passive data connections, including implicit-TLS FTPS, without external I/O.
@ kFastRtsp
Exercise RTSP request/response exchanges and interleaved RTP delivery through the request-aligned str...
@ kResolver
Exercise localhost lookup and structured CURLOPT_RESOLVE host-cache work.
@ kFastHttp2
Exercise HTTP/2 origin frames over plaintext prior knowledge.
@ kH2Proxy
Exercise an HTTP/1.1 origin through an HTTPS/HTTP2 CONNECT proxy.
@ kTiming
Isolate deterministic backpressure and timed-wait behavior.
@ kFastSecureWebSocket
Exercise WebSocket framing through a verified TLS connection.
@ kFastFile
Exercise file:// URL parsing against a fixed read-only local target.
@ kHttpsH2
Exercise raw HTTP/2 origin frames after a verified TLS/ALPN handshake.
@ kFastTftp
Exercise packet-preserving TFTP exchanges over the loopback UDP peer.
@ kFastImap
Exercise single-connection IMAP mailbox transfers through the control peer.
@ kFastHttp3
Exercise HTTP/3 over a valid in-process QUIC/TLS connection.
@ kFastTelnet
Exercise bounded TELNET negotiation and callback-backed input.
@ kDeepHttp
Retain HTTP's complete structured request and response surface.
@ kCompatibility
Preserve the original mixed target and its accumulated corpus exactly.
@ kFastHttps
Exercise a complete HTTPS exchange against the in-process TLS peer.
@ kFastIpfs
Exercise curl's ipfs:// and ipns:// gateway rewrite as an ordinary HTTP GET against the in-process HT...
@ kFastLdap
Exercise binary BER LDAP bind and search exchanges through the OpenLDAP-backed single-socket peer.
@ kFastPop3
Exercise single-connection POP3 mailbox transfers through the control peer.
void BoundTelnetResponse(curl::fuzzer::proto::Connection *connection)
Retain the response prefix that TelnetMockServer can preload safely.
Shared TELNET response normalization.