/src/curl_fuzzer/proto_fuzzer/target_policy.cc
Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al. |
3 | | * |
4 | | * SPDX-License-Identifier: curl |
5 | | */ |
6 | | |
7 | | /// @file |
8 | | /// @brief Implementation of the per-binary proto mutation policies. |
9 | | |
10 | | #include "proto_fuzzer/target_policy.h" |
11 | | |
12 | | #include <algorithm> |
13 | | #include <cstdint> |
14 | | #include <string> |
15 | | |
16 | | #include "proto_fuzzer/scenario_limits.h" |
17 | | #include "proto_fuzzer/telnet_scenario.h" |
18 | | |
19 | | namespace proto_fuzzer { |
20 | | |
21 | | namespace { |
22 | | |
23 | | // Linux raises smaller socket-buffer requests to an implementation minimum, |
24 | | // so 2048 is both cheap and reliably small enough to exercise short writes. |
25 | | constexpr std::uint32_t kDefaultBackpressureBufferBytes = 2048; |
26 | | |
27 | | // Values outside these ranges do not create useful new socket behavior for |
28 | | // the harness's bounded 4-16 KiB writes. Keeping them small also prevents a |
29 | | // mutated uint32 recv size from overflowing the int accepted by setsockopt. |
30 | | constexpr std::uint32_t kMinBackpressureBufferBytes = 2048; |
31 | | constexpr std::uint32_t kMaxBackpressureBufferBytes = 4096; |
32 | | constexpr std::uint32_t kMaxDrainBytesPerIteration = 1024; |
33 | | |
34 | | /// Remove a repeated-field suffix that the runtime would ignore. Doing this |
35 | | /// in LPM's postprocessor matters for speed as well as memory: otherwise later |
36 | | /// mutations keep rediscovering and editing objects that cannot reach curl. |
37 | | template <typename RepeatedField> |
38 | 896k | void TrimRepeated(RepeatedField* field, std::size_t limit) { |
39 | 896k | const std::size_t size = static_cast<std::size_t>(field->size()); |
40 | 896k | if (size > limit) { |
41 | 1.31k | field->DeleteSubrange(static_cast<int>(limit), static_cast<int>(size - limit)); |
42 | 1.31k | } |
43 | 896k | } target_policy.cc:void proto_fuzzer::(anonymous namespace)::TrimRepeated<google::protobuf::RepeatedPtrField<curl::fuzzer::proto::SetOption> >(google::protobuf::RepeatedPtrField<curl::fuzzer::proto::SetOption>*, unsigned long) Line | Count | Source | 38 | 116k | void TrimRepeated(RepeatedField* field, std::size_t limit) { | 39 | 116k | const std::size_t size = static_cast<std::size_t>(field->size()); | 40 | 116k | if (size > limit) { | 41 | 474 | field->DeleteSubrange(static_cast<int>(limit), static_cast<int>(size - limit)); | 42 | 474 | } | 43 | 116k | } |
target_policy.cc:void proto_fuzzer::(anonymous namespace)::TrimRepeated<google::protobuf::RepeatedPtrField<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >(google::protobuf::RepeatedPtrField<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >*, unsigned long) Line | Count | Source | 38 | 456k | void TrimRepeated(RepeatedField* field, std::size_t limit) { | 39 | 456k | const std::size_t size = static_cast<std::size_t>(field->size()); | 40 | 456k | if (size > limit) { | 41 | 366 | field->DeleteSubrange(static_cast<int>(limit), static_cast<int>(size - limit)); | 42 | 366 | } | 43 | 456k | } |
target_policy.cc:void proto_fuzzer::(anonymous namespace)::TrimRepeated<google::protobuf::RepeatedPtrField<curl::fuzzer::proto::MimePart> >(google::protobuf::RepeatedPtrField<curl::fuzzer::proto::MimePart>*, unsigned long) Line | Count | Source | 38 | 33.2k | void TrimRepeated(RepeatedField* field, std::size_t limit) { | 39 | 33.2k | const std::size_t size = static_cast<std::size_t>(field->size()); | 40 | 33.2k | if (size > limit) { | 41 | 119 | field->DeleteSubrange(static_cast<int>(limit), static_cast<int>(size - limit)); | 42 | 119 | } | 43 | 33.2k | } |
target_policy.cc:void proto_fuzzer::(anonymous namespace)::TrimRepeated<google::protobuf::RepeatedPtrField<curl::fuzzer::proto::MimeDataPart> >(google::protobuf::RepeatedPtrField<curl::fuzzer::proto::MimeDataPart>*, unsigned long) Line | Count | Source | 38 | 17.0k | void TrimRepeated(RepeatedField* field, std::size_t limit) { | 39 | 17.0k | const std::size_t size = static_cast<std::size_t>(field->size()); | 40 | 17.0k | if (size > limit) { | 41 | 36 | field->DeleteSubrange(static_cast<int>(limit), static_cast<int>(size - limit)); | 42 | 36 | } | 43 | 17.0k | } |
target_policy.cc:void proto_fuzzer::(anonymous namespace)::TrimRepeated<google::protobuf::RepeatedPtrField<curl::fuzzer::proto::WebSocketFrame> >(google::protobuf::RepeatedPtrField<curl::fuzzer::proto::WebSocketFrame>*, unsigned long) Line | Count | Source | 38 | 148k | void TrimRepeated(RepeatedField* field, std::size_t limit) { | 39 | 148k | const std::size_t size = static_cast<std::size_t>(field->size()); | 40 | 148k | if (size > limit) { | 41 | 100 | field->DeleteSubrange(static_cast<int>(limit), static_cast<int>(size - limit)); | 42 | 100 | } | 43 | 148k | } |
target_policy.cc:void proto_fuzzer::(anonymous namespace)::TrimRepeated<google::protobuf::RepeatedPtrField<curl::fuzzer::proto::MultiAction> >(google::protobuf::RepeatedPtrField<curl::fuzzer::proto::MultiAction>*, unsigned long) Line | Count | Source | 38 | 502 | void TrimRepeated(RepeatedField* field, std::size_t limit) { | 39 | 502 | const std::size_t size = static_cast<std::size_t>(field->size()); | 40 | 502 | if (size > limit) { | 41 | 0 | field->DeleteSubrange(static_cast<int>(limit), static_cast<int>(size - limit)); | 42 | 0 | } | 43 | 502 | } |
target_policy.cc:void proto_fuzzer::(anonymous namespace)::TrimRepeated<google::protobuf::RepeatedPtrField<curl::fuzzer::proto::Connection> >(google::protobuf::RepeatedPtrField<curl::fuzzer::proto::Connection>*, unsigned long) Line | Count | Source | 38 | 124k | void TrimRepeated(RepeatedField* field, std::size_t limit) { | 39 | 124k | const std::size_t size = static_cast<std::size_t>(field->size()); | 40 | 124k | if (size > limit) { | 41 | 221 | field->DeleteSubrange(static_cast<int>(limit), static_cast<int>(size - limit)); | 42 | 221 | } | 43 | 124k | } |
|
44 | | |
45 | | /// Bound strings passed to NUL-terminated metadata APIs. The runtime applies |
46 | | /// the same prefix, so deleting the invisible suffix increases useful |
47 | | /// mutation density without removing any behavior curl could observe. |
48 | 349k | void TrimMetadata(std::string* value) { |
49 | 349k | if (value->size() > scenario_limits::kMaxMetadataBytes) { |
50 | 32 | value->resize(scenario_limits::kMaxMetadataBytes); |
51 | 32 | } |
52 | 349k | } |
53 | | |
54 | | /// Give the successful-TLS lane a hostname its fixed certificate can verify |
55 | | /// while retaining the fuzz-controlled path, query, and fragment. Arbitrary |
56 | | /// authorities remain covered by the compatibility and legacy HTTPS lanes; |
57 | | /// spending this lane's mutations on URL failures would keep curl's peer-cert |
58 | | /// and encrypted application-data paths dark. |
59 | 17.8k | void CanonicalizeTlsAuthority(curl::fuzzer::proto::Scenario* scenario) { |
60 | 17.8k | const std::string& host_path = scenario->host_path(); |
61 | 17.8k | const std::size_t suffix_start = host_path.find_first_of("/?#"); |
62 | 17.8k | if (suffix_start == std::string::npos) { |
63 | 2.87k | scenario->set_host_path("tls.test/"); |
64 | 2.87k | return; |
65 | 2.87k | } |
66 | 14.9k | scenario->set_host_path("tls.test" + host_path.substr(suffix_start)); |
67 | 14.9k | } |
68 | | |
69 | | /// Keep the tunneled origin parseable while retaining every path, query, and |
70 | | /// fragment byte. The fixed numeric proxy endpoint handles routing separately; |
71 | | /// mutating the origin authority would therefore buy only early URL failures, |
72 | | /// not additional HTTP/2 proxy behavior. |
73 | 5.54k | void CanonicalizeH2ProxyOriginAuthority(curl::fuzzer::proto::Scenario* scenario) { |
74 | 5.54k | const std::string& host_path = scenario->host_path(); |
75 | 5.54k | const std::size_t suffix_start = host_path.find_first_of("/?#"); |
76 | 5.54k | if (suffix_start == std::string::npos) { |
77 | 185 | scenario->set_host_path("origin.test/"); |
78 | 185 | return; |
79 | 185 | } |
80 | 5.36k | scenario->set_host_path("origin.test" + host_path.substr(suffix_start)); |
81 | 5.36k | } |
82 | | |
83 | | /// Give the TFTP lane a parseable filename-bearing URL while retaining the |
84 | | /// fuzz-controlled path, query, and fragment. The UDP peer rewrites curl's |
85 | | /// destination after URL parsing, so authority mutations cannot reach another |
86 | | /// host; canonicalizing them here avoids spending most iterations on failures |
87 | | /// before curl constructs a TFTP request. An explicit slash is preserved so |
88 | | /// the missing-filename error remains reachable. |
89 | 2.01k | void CanonicalizeTftpAuthority(curl::fuzzer::proto::Scenario* scenario) { |
90 | 2.01k | const std::string& host_path = scenario->host_path(); |
91 | 2.01k | const std::size_t suffix_start = host_path.find_first_of("/?#"); |
92 | 2.01k | if (suffix_start == std::string::npos) { |
93 | 196 | scenario->set_host_path("tftp.test/file"); |
94 | 196 | return; |
95 | 196 | } |
96 | 1.81k | scenario->set_host_path("tftp.test" + host_path.substr(suffix_start)); |
97 | 1.81k | } |
98 | | |
99 | | /// Keep the FTP lane inside the same parseable authority while leaving every |
100 | | /// path segment and wildcard under mutation control. CONNECT_TO already |
101 | | /// confines networking, but rejecting malformed authorities before USER/PWD |
102 | | /// would waste the control/data peer this target uniquely provides. |
103 | 7.19k | void CanonicalizeFtpAuthority(curl::fuzzer::proto::Scenario* scenario) { |
104 | 7.19k | const std::string& host_path = scenario->host_path(); |
105 | 7.19k | const std::size_t suffix_start = host_path.find_first_of("/?#"); |
106 | 7.19k | if (suffix_start == std::string::npos) { |
107 | 147 | scenario->set_host_path("ftp.test/file"); |
108 | 147 | return; |
109 | 147 | } |
110 | 7.04k | scenario->set_host_path("ftp.test" + host_path.substr(suffix_start)); |
111 | 7.04k | } |
112 | | |
113 | | /// Put every handle in the multi lane on one origin so connection limits, |
114 | | /// queueing, and reuse affect real transfers instead of independent hosts. |
115 | | /// Path/query/fragment bytes remain mutation-controlled. |
116 | 502 | void CanonicalizeMultiAuthority(curl::fuzzer::proto::Scenario* scenario) { |
117 | 502 | const std::string& host_path = scenario->host_path(); |
118 | 502 | const std::size_t suffix_start = host_path.find_first_of("/?#"); |
119 | 502 | if (suffix_start == std::string::npos) { |
120 | 0 | scenario->set_host_path("multi.test/"); |
121 | 0 | return; |
122 | 0 | } |
123 | 502 | scenario->set_host_path("multi.test" + host_path.substr(suffix_start)); |
124 | 502 | } |
125 | | |
126 | | template <typename RepeatedBytes> |
127 | 308k | void BoundStringValues(RepeatedBytes* values, std::size_t count_limit, std::size_t value_limit) { |
128 | 308k | TrimRepeated(values, count_limit); |
129 | 308k | for (std::string& value : *values) { |
130 | 121k | if (value.size() > value_limit) { |
131 | 42 | value.resize(value_limit); |
132 | 42 | } |
133 | 121k | } |
134 | 308k | } |
135 | | |
136 | | template <typename RepeatedBytes> |
137 | 191k | void BoundHeaderValues(RepeatedBytes* headers, std::size_t limit) { |
138 | 191k | BoundStringValues(headers, limit, scenario_limits::kMaxMetadataBytes); |
139 | 191k | } |
140 | | |
141 | | /// Keep one response script identical to the prefix MockServer and |
142 | | /// WebSocketMockServer can deliver. Raw chunks take precedence over structured |
143 | | /// frames, matching both runtime serializers. |
144 | 148k | void BoundConnectionShape(curl::fuzzer::proto::Connection* connection) { |
145 | 148k | TrimRepeated(connection->mutable_on_readable(), scenario_limits::kMaxResponseChunks); |
146 | 148k | const std::size_t raw_count = static_cast<std::size_t>(connection->on_readable_size()); |
147 | 148k | TrimRepeated(connection->mutable_server_frames(), scenario_limits::kMaxResponseChunks - raw_count); |
148 | 148k | } |
149 | | |
150 | | /// Apply the metadata/header limits shared by both MIME part message types. |
151 | | template <typename Part> |
152 | 74.6k | void BoundMimePartMetadata(Part* part) { |
153 | 74.6k | TrimMetadata(part->mutable_name()); |
154 | 74.6k | TrimMetadata(part->mutable_filename()); |
155 | 74.6k | TrimMetadata(part->mutable_content_type()); |
156 | 74.6k | BoundHeaderValues(part->mutable_headers(), scenario_limits::kMaxMimeHeadersPerPart); |
157 | 74.6k | } target_policy.cc:void proto_fuzzer::(anonymous namespace)::BoundMimePartMetadata<curl::fuzzer::proto::MimePart>(curl::fuzzer::proto::MimePart*) Line | Count | Source | 152 | 31.7k | void BoundMimePartMetadata(Part* part) { | 153 | 31.7k | TrimMetadata(part->mutable_name()); | 154 | 31.7k | TrimMetadata(part->mutable_filename()); | 155 | 31.7k | TrimMetadata(part->mutable_content_type()); | 156 | 31.7k | BoundHeaderValues(part->mutable_headers(), scenario_limits::kMaxMimeHeadersPerPart); | 157 | 31.7k | } |
target_policy.cc:void proto_fuzzer::(anonymous namespace)::BoundMimePartMetadata<curl::fuzzer::proto::MimeDataPart>(curl::fuzzer::proto::MimeDataPart*) Line | Count | Source | 152 | 42.9k | void BoundMimePartMetadata(Part* part) { | 153 | 42.9k | TrimMetadata(part->mutable_name()); | 154 | 42.9k | TrimMetadata(part->mutable_filename()); | 155 | 42.9k | TrimMetadata(part->mutable_content_type()); | 156 | 42.9k | BoundHeaderValues(part->mutable_headers(), scenario_limits::kMaxMimeHeadersPerPart); | 157 | 42.9k | } |
|
158 | | |
159 | 42.9k | void BoundMimeLeaf(curl::fuzzer::proto::MimeDataPart* part) { |
160 | 42.9k | BoundMimePartMetadata(part); |
161 | 42.9k | if (part->data().size() > scenario_limits::kMaxMimeDataBytes) { |
162 | 0 | part->mutable_data()->resize(scenario_limits::kMaxMimeDataBytes); |
163 | 0 | } |
164 | 42.9k | } |
165 | | |
166 | | /// Mirror the runtime's shared top-level/nested part budget in the protobuf |
167 | | /// itself. A simple per-list cap is insufficient because many bounded child |
168 | | /// lists could still leave most of the message semantically dead. |
169 | 16.6k | void BoundMimeShape(curl::fuzzer::proto::MimePost* post) { |
170 | 16.6k | TrimRepeated(post->mutable_parts(), scenario_limits::kMaxTopLevelMimeParts); |
171 | 16.6k | std::size_t remaining = scenario_limits::kMaxTotalMimeParts; |
172 | 16.6k | std::size_t retained_top_parts = 0; |
173 | | |
174 | 48.3k | while (retained_top_parts < static_cast<std::size_t>(post->parts_size()) && remaining != 0) { |
175 | 31.7k | auto* part = post->mutable_parts(static_cast<int>(retained_top_parts)); |
176 | 31.7k | ++retained_top_parts; |
177 | 31.7k | --remaining; |
178 | 31.7k | BoundMimePartMetadata(part); |
179 | | |
180 | 31.7k | if (part->content_case() == curl::fuzzer::proto::MimePart::kData) { |
181 | 5.13k | if (part->data().size() > scenario_limits::kMaxMimeDataBytes) { |
182 | 0 | part->mutable_data()->resize(scenario_limits::kMaxMimeDataBytes); |
183 | 0 | } |
184 | 5.13k | continue; |
185 | 5.13k | } |
186 | 26.5k | if (part->content_case() != curl::fuzzer::proto::MimePart::kSubparts) { |
187 | 9.57k | continue; |
188 | 9.57k | } |
189 | | |
190 | 17.0k | auto* children = part->mutable_subparts()->mutable_parts(); |
191 | 17.0k | TrimRepeated(children, std::min(scenario_limits::kMaxNestedMimeParts, remaining)); |
192 | 42.9k | for (auto& child : *children) { |
193 | 42.9k | BoundMimeLeaf(&child); |
194 | 42.9k | --remaining; |
195 | 42.9k | } |
196 | 17.0k | } |
197 | | |
198 | 16.6k | TrimRepeated(post->mutable_parts(), retained_top_parts); |
199 | 16.6k | } |
200 | | |
201 | | /// Remove upload bytes and read steps the callback cannot observe. Clamping |
202 | | /// individual limits also keeps mutations concentrated on short reads instead |
203 | | /// of many distinct uint32 values that all collapse to the same 16 KiB cap. |
204 | | void BoundUploadShape(curl::fuzzer::proto::UploadScript* upload, std::size_t data_limit, std::size_t read_step_limit, |
205 | 13.2k | std::size_t read_size_limit) { |
206 | 13.2k | if (upload->data().size() > data_limit) { |
207 | 6 | upload->mutable_data()->resize(data_limit); |
208 | 6 | } |
209 | | // RepeatedField<uint32_t> lacks RepeatedPtrField's DeleteSubrange helper; |
210 | | // removing the ignored suffix from the end is constant-time per element and |
211 | | // preserves the mutation-significant prefix exactly. |
212 | 51.8k | while (static_cast<std::size_t>(upload->read_sizes_size()) > read_step_limit) { |
213 | 38.6k | upload->mutable_read_sizes()->RemoveLast(); |
214 | 38.6k | } |
215 | 34.6k | for (int i = 0; i < upload->read_sizes_size(); ++i) { |
216 | 21.4k | if (upload->read_sizes(i) > read_size_limit) { |
217 | 370 | upload->set_read_sizes(i, static_cast<std::uint32_t>(read_size_limit)); |
218 | 370 | } |
219 | 21.4k | } |
220 | 13.2k | } |
221 | | |
222 | | /// Trim a protobuf repeated scalar without depending on the container's |
223 | | /// pointer-field-only DeleteSubrange API. Keeping the mutation-significant |
224 | | /// prefix matches every runtime selector loop. |
225 | | template <typename RepeatedScalar> |
226 | 9.70k | void TrimRepeatedScalar(RepeatedScalar* values, std::size_t limit) { |
227 | 19.8k | while (static_cast<std::size_t>(values->size()) > limit) { |
228 | 10.1k | values->RemoveLast(); |
229 | 10.1k | } |
230 | 9.70k | } |
231 | | |
232 | | /// Keep API work proportional to the fixed descriptor tables used by the |
233 | | /// runtime. Selector magnitudes stay mutation-controlled because the runtime |
234 | | /// folds them into the relevant typed table; only suffixes it cannot execute |
235 | | /// are dead and therefore removed here. |
236 | 4.85k | void BoundApiPlanShape(curl::fuzzer::proto::ApiPlan* plan) { |
237 | 4.85k | TrimRepeatedScalar(plan->mutable_share_data_selectors(), scenario_limits::kMaxApiShareDataSelectors); |
238 | 4.85k | TrimRepeatedScalar(plan->mutable_easy_info_selectors(), scenario_limits::kMaxApiInfoSelectors); |
239 | | |
240 | 4.85k | switch (plan->drive_mode()) { |
241 | 3.02k | case curl::fuzzer::proto::API_DRIVE_MULTI_PERFORM: |
242 | 3.90k | case curl::fuzzer::proto::API_DRIVE_MULTI_SOCKET: |
243 | 3.90k | break; |
244 | 950 | case curl::fuzzer::proto::API_DRIVE_EASY_PERFORM: |
245 | | // Wakeup is a multi-handle API and has no live object in easy mode. |
246 | | // Clearing it keeps every retained mutation observable. |
247 | 950 | plan->set_wake_multi(false); |
248 | 950 | break; |
249 | 3 | default: |
250 | 3 | plan->set_drive_mode(curl::fuzzer::proto::API_DRIVE_MULTI_PERFORM); |
251 | 3 | break; |
252 | 4.85k | } |
253 | 4.85k | } |
254 | | |
255 | | /// Keep concurrent-handle work within the mock's fixed socket and operation |
256 | | /// budgets. Values are canonicalized here rather than only at runtime so LPM |
257 | | /// mutates state that the target can actually distinguish. |
258 | 502 | void BoundMultiPlanShape(curl::fuzzer::proto::MultiPlan* plan) { |
259 | 502 | const std::uint32_t minimum = static_cast<std::uint32_t>(scenario_limits::kMinMultiTransfers); |
260 | 502 | const std::uint32_t maximum = static_cast<std::uint32_t>(scenario_limits::kMaxMultiTransfers); |
261 | 502 | const std::uint32_t transfer_count = std::max(minimum, std::min(plan->transfer_count(), maximum)); |
262 | 502 | plan->set_transfer_count(transfer_count); |
263 | 502 | plan->set_max_host_connections(std::min(plan->max_host_connections(), transfer_count)); |
264 | 502 | plan->set_max_total_connections(std::min(plan->max_total_connections(), transfer_count)); |
265 | 502 | plan->set_connection_cache_size(std::min(plan->connection_cache_size(), maximum * 2U)); |
266 | 502 | TrimRepeated(plan->mutable_actions(), scenario_limits::kMaxMultiActions); |
267 | | |
268 | 502 | switch (plan->drive_mode()) { |
269 | 319 | case curl::fuzzer::proto::MULTI_DRIVE_PERFORM: |
270 | 502 | case curl::fuzzer::proto::MULTI_DRIVE_SOCKET: |
271 | 502 | break; |
272 | 0 | default: |
273 | 0 | plan->set_drive_mode(curl::fuzzer::proto::MULTI_DRIVE_PERFORM); |
274 | 0 | break; |
275 | 502 | } |
276 | | |
277 | 1.42k | for (auto& action : *plan->mutable_actions()) { |
278 | 1.42k | action.set_transfer_selector(action.transfer_selector() % transfer_count); |
279 | 1.42k | switch (action.kind()) { |
280 | 220 | case curl::fuzzer::proto::MULTI_ACTION_NONE: |
281 | 369 | case curl::fuzzer::proto::MULTI_ACTION_PAUSE_RECV: |
282 | 472 | case curl::fuzzer::proto::MULTI_ACTION_PAUSE_SEND: |
283 | 697 | case curl::fuzzer::proto::MULTI_ACTION_PAUSE_ALL: |
284 | 823 | case curl::fuzzer::proto::MULTI_ACTION_RESUME: |
285 | 1.18k | case curl::fuzzer::proto::MULTI_ACTION_REMOVE: |
286 | 1.42k | case curl::fuzzer::proto::MULTI_ACTION_READD: |
287 | 1.42k | break; |
288 | 0 | default: |
289 | 0 | action.set_kind(curl::fuzzer::proto::MULTI_ACTION_NONE); |
290 | 0 | break; |
291 | 1.42k | } |
292 | 1.42k | } |
293 | 502 | } |
294 | | |
295 | | /// Canonicalize all shape limits enforced by the runtime. This runs only in |
296 | | /// fixed policy targets; the compatibility binary deliberately retains its |
297 | | /// historical no-postprocessor semantics for existing OSS-Fuzz reproducers. |
298 | 116k | void BoundScenarioShape(curl::fuzzer::proto::Scenario* scenario) { |
299 | 116k | TrimRepeated(scenario->mutable_options(), scenario_limits::kMaxOptions); |
300 | 363k | for (auto& option : *scenario->mutable_options()) { |
301 | 363k | if (option.value_case() == curl::fuzzer::proto::SetOption::kStringValue) { |
302 | 125k | TrimMetadata(option.mutable_string_value()); |
303 | 125k | } |
304 | 363k | } |
305 | | |
306 | 116k | BoundHeaderValues(scenario->mutable_request_headers(), scenario_limits::kMaxRequestHeaders); |
307 | 116k | BoundStringValues(scenario->mutable_telnet_options(), scenario_limits::kMaxTelnetOptions, |
308 | 116k | scenario_limits::kMaxTelnetOptionBytes); |
309 | 116k | if (scenario->has_mime_post()) { |
310 | 16.6k | BoundMimeShape(scenario->mutable_mime_post()); |
311 | 16.6k | } |
312 | 116k | if (scenario->has_upload()) { |
313 | 13.2k | const bool telnet = scenario->scheme() == curl::fuzzer::proto::SCHEME_TELNET; |
314 | 13.2k | const std::size_t data_limit = telnet ? scenario_limits::kMaxTelnetUploadBytes : scenario_limits::kMaxUploadBytes; |
315 | 13.2k | const std::size_t read_step_limit = |
316 | 13.2k | telnet ? scenario_limits::kMaxTelnetUploadReadSteps : scenario_limits::kMaxUploadReadSteps; |
317 | 13.2k | const std::size_t read_size_limit = |
318 | 13.2k | telnet ? scenario_limits::kMaxTelnetUploadReadSize : scenario_limits::kMaxUploadReadSize; |
319 | 13.2k | BoundUploadShape(scenario->mutable_upload(), data_limit, read_step_limit, read_size_limit); |
320 | 13.2k | } |
321 | | |
322 | 116k | BoundConnectionShape(scenario->mutable_connection()); |
323 | 116k | TrimRepeated(scenario->mutable_subsequent_connections(), scenario_limits::kMaxConnections - 1); |
324 | 116k | for (auto& connection : *scenario->mutable_subsequent_connections()) { |
325 | 31.3k | BoundConnectionShape(&connection); |
326 | 31.3k | } |
327 | 116k | } |
328 | | |
329 | | /// Return whether an option belongs in the high-throughput HTTP lane. This is |
330 | | /// deliberately an allowlist rather than a denylist: adding a new structured |
331 | | /// option should expand deep coverage first, not silently make the fast lane |
332 | | /// slower before its cost has been measured. |
333 | 25.3k | bool IsCheapHttpOption(curl::fuzzer::proto::CurlOptionId option_id) { |
334 | 25.3k | switch (option_id) { |
335 | 7.01k | case curl::fuzzer::proto::CURLOPT_ACCEPT_ENCODING: |
336 | 7.87k | case curl::fuzzer::proto::CURLOPT_BUFFERSIZE: |
337 | 9.10k | case curl::fuzzer::proto::CURLOPT_CUSTOMREQUEST: |
338 | 9.28k | case curl::fuzzer::proto::CURLOPT_DISALLOW_USERNAME_IN_URL: |
339 | 10.6k | case curl::fuzzer::proto::CURLOPT_FAILONERROR: |
340 | 11.2k | case curl::fuzzer::proto::CURLOPT_FILETIME: |
341 | 11.2k | case curl::fuzzer::proto::CURLOPT_HEADER: |
342 | 11.3k | case curl::fuzzer::proto::CURLOPT_HTTP09_ALLOWED: |
343 | 11.4k | case curl::fuzzer::proto::CURLOPT_HTTP_CONTENT_DECODING: |
344 | 11.6k | case curl::fuzzer::proto::CURLOPT_HTTP_TRANSFER_DECODING: |
345 | 18.0k | case curl::fuzzer::proto::CURLOPT_HTTP_VERSION: |
346 | 20.2k | case curl::fuzzer::proto::CURLOPT_HTTPGET: |
347 | 20.3k | case curl::fuzzer::proto::CURLOPT_IGNORE_CONTENT_LENGTH: |
348 | 21.2k | case curl::fuzzer::proto::CURLOPT_MAXFILESIZE_LARGE: |
349 | 22.1k | case curl::fuzzer::proto::CURLOPT_NOBODY: |
350 | 22.4k | case curl::fuzzer::proto::CURLOPT_PATH_AS_IS: |
351 | 22.5k | case curl::fuzzer::proto::CURLOPT_RANGE: |
352 | 23.4k | case curl::fuzzer::proto::CURLOPT_REQUEST_TARGET: |
353 | 24.1k | case curl::fuzzer::proto::CURLOPT_RESUME_FROM_LARGE: |
354 | 24.8k | case curl::fuzzer::proto::CURLOPT_TRANSFER_ENCODING: |
355 | 24.9k | case curl::fuzzer::proto::CURLOPT_USERAGENT: |
356 | 24.9k | return true; |
357 | | |
358 | 178 | case curl::fuzzer::proto::CURL_OPTION_UNSPECIFIED: |
359 | 342 | default: |
360 | 342 | return false; |
361 | 25.3k | } |
362 | 25.3k | } |
363 | | |
364 | | /// Return whether an option can affect the HTTP/1.1 request carried inside the |
365 | | /// fixed HTTP/2 CONNECT tunnel. Proxy routing, ALPN, and TLS verification are |
366 | | /// owned by H2ProxyMockServer and must not be mutation-controlled; HTTP/2 as an |
367 | | /// inner origin protocol is also excluded because it would require a second |
368 | | /// frame script and obscure coverage of the outer proxy filter. Stateful HTTP |
369 | | /// options remain useful here because their wire effects traverse cf-h2-proxy. |
370 | 36.1k | bool IsH2ProxyOriginOption(curl::fuzzer::proto::CurlOptionId option_id) { |
371 | 36.1k | switch (option_id) { |
372 | 1.19k | case curl::fuzzer::proto::CURLOPT_ACCEPT_ENCODING: |
373 | 1.82k | case curl::fuzzer::proto::CURLOPT_ALTSVC_CTRL: |
374 | 1.88k | case curl::fuzzer::proto::CURLOPT_AUTOREFERER: |
375 | 2.28k | case curl::fuzzer::proto::CURLOPT_AWS_SIGV4: |
376 | 2.80k | case curl::fuzzer::proto::CURLOPT_BUFFERSIZE: |
377 | 2.84k | case curl::fuzzer::proto::CURLOPT_COOKIE: |
378 | 7.40k | case curl::fuzzer::proto::CURLOPT_COOKIELIST: |
379 | 8.76k | case curl::fuzzer::proto::CURLOPT_COOKIESESSION: |
380 | 8.81k | case curl::fuzzer::proto::CURLOPT_CUSTOMREQUEST: |
381 | 9.47k | case curl::fuzzer::proto::CURLOPT_DISALLOW_USERNAME_IN_URL: |
382 | 10.7k | case curl::fuzzer::proto::CURLOPT_EXPECT_100_TIMEOUT_MS: |
383 | 11.4k | case curl::fuzzer::proto::CURLOPT_FAILONERROR: |
384 | 12.1k | case curl::fuzzer::proto::CURLOPT_FILETIME: |
385 | 12.7k | case curl::fuzzer::proto::CURLOPT_FOLLOWLOCATION: |
386 | 12.8k | case curl::fuzzer::proto::CURLOPT_FORBID_REUSE: |
387 | 13.1k | case curl::fuzzer::proto::CURLOPT_FRESH_CONNECT: |
388 | 15.1k | case curl::fuzzer::proto::CURLOPT_HEADER: |
389 | 15.8k | case curl::fuzzer::proto::CURLOPT_HSTS_CTRL: |
390 | 15.9k | case curl::fuzzer::proto::CURLOPT_HTTP09_ALLOWED: |
391 | 17.0k | case curl::fuzzer::proto::CURLOPT_HTTPAUTH: |
392 | 21.4k | case curl::fuzzer::proto::CURLOPT_HTTPGET: |
393 | 21.4k | case curl::fuzzer::proto::CURLOPT_HTTP_CONTENT_DECODING: |
394 | 21.5k | case curl::fuzzer::proto::CURLOPT_HTTP_TRANSFER_DECODING: |
395 | 21.7k | case curl::fuzzer::proto::CURLOPT_IGNORE_CONTENT_LENGTH: |
396 | 22.0k | case curl::fuzzer::proto::CURLOPT_INFILESIZE_LARGE: |
397 | 22.3k | case curl::fuzzer::proto::CURLOPT_KEEP_SENDING_ON_ERROR: |
398 | 22.8k | case curl::fuzzer::proto::CURLOPT_MAXAGE_CONN: |
399 | 23.1k | case curl::fuzzer::proto::CURLOPT_MAXFILESIZE_LARGE: |
400 | 24.4k | case curl::fuzzer::proto::CURLOPT_MAXLIFETIME_CONN: |
401 | 25.1k | case curl::fuzzer::proto::CURLOPT_MAXREDIRS: |
402 | 25.2k | case curl::fuzzer::proto::CURLOPT_MIME_OPTIONS: |
403 | 25.7k | case curl::fuzzer::proto::CURLOPT_NOBODY: |
404 | 25.9k | case curl::fuzzer::proto::CURLOPT_PASSWORD: |
405 | 26.2k | case curl::fuzzer::proto::CURLOPT_PATH_AS_IS: |
406 | 26.8k | case curl::fuzzer::proto::CURLOPT_POST: |
407 | 26.9k | case curl::fuzzer::proto::CURLOPT_POSTFIELDS: |
408 | 27.0k | case curl::fuzzer::proto::CURLOPT_POSTREDIR: |
409 | 27.3k | case curl::fuzzer::proto::CURLOPT_RANGE: |
410 | 27.3k | case curl::fuzzer::proto::CURLOPT_REFERER: |
411 | 28.2k | case curl::fuzzer::proto::CURLOPT_REQUEST_TARGET: |
412 | 29.4k | case curl::fuzzer::proto::CURLOPT_RESUME_FROM_LARGE: |
413 | 30.4k | case curl::fuzzer::proto::CURLOPT_TIMECONDITION: |
414 | 30.5k | case curl::fuzzer::proto::CURLOPT_TIMEVALUE_LARGE: |
415 | 31.0k | case curl::fuzzer::proto::CURLOPT_TRANSFER_ENCODING: |
416 | 31.3k | case curl::fuzzer::proto::CURLOPT_UNRESTRICTED_AUTH: |
417 | 32.1k | case curl::fuzzer::proto::CURLOPT_UPLOAD: |
418 | 32.5k | case curl::fuzzer::proto::CURLOPT_UPLOAD_BUFFERSIZE: |
419 | 33.0k | case curl::fuzzer::proto::CURLOPT_USERAGENT: |
420 | 33.1k | case curl::fuzzer::proto::CURLOPT_USERNAME: |
421 | 34.4k | case curl::fuzzer::proto::CURLOPT_USERPWD: |
422 | 35.5k | case curl::fuzzer::proto::CURLOPT_XOAUTH2_BEARER: |
423 | 35.5k | return true; |
424 | | |
425 | 405 | case curl::fuzzer::proto::CURL_OPTION_UNSPECIFIED: |
426 | 606 | default: |
427 | 606 | return false; |
428 | 36.1k | } |
429 | 36.1k | } |
430 | | |
431 | | /// Compact an option list before applying the general option-count bound. |
432 | | /// Keeping a relevant option that appears after a long rejected prefix is |
433 | | /// important for mutation density: bounding first would let unrelated options |
434 | | /// crowd useful ones out of a protocol-specific lane. |
435 | | template <typename Predicate> |
436 | 116k | void RetainMatchingOptions(curl::fuzzer::proto::Scenario* scenario, Predicate predicate) { |
437 | 116k | auto* options = scenario->mutable_options(); |
438 | 116k | int retained = 0; |
439 | 598k | for (int index = 0; index < options->size(); ++index) { |
440 | 482k | if (!predicate(options->Get(index).option_id())) { |
441 | 14.6k | continue; |
442 | 14.6k | } |
443 | 467k | if (retained != index) { |
444 | 38.7k | options->SwapElements(retained, index); |
445 | 38.7k | } |
446 | 467k | ++retained; |
447 | 467k | } |
448 | 116k | options->DeleteSubrange(retained, options->size() - retained); |
449 | 116k | } target_policy.cc:void proto_fuzzer::(anonymous namespace)::RetainMatchingOptions<bool (*)(curl::fuzzer::proto::CurlOptionId)>(curl::fuzzer::proto::Scenario*, bool (*)(curl::fuzzer::proto::CurlOptionId)) Line | Count | Source | 436 | 33.6k | void RetainMatchingOptions(curl::fuzzer::proto::Scenario* scenario, Predicate predicate) { | 437 | 33.6k | auto* options = scenario->mutable_options(); | 438 | 33.6k | int retained = 0; | 439 | 169k | for (int index = 0; index < options->size(); ++index) { | 440 | 135k | if (!predicate(options->Get(index).option_id())) { | 441 | 12.2k | continue; | 442 | 12.2k | } | 443 | 123k | if (retained != index) { | 444 | 31.1k | options->SwapElements(retained, index); | 445 | 31.1k | } | 446 | 123k | ++retained; | 447 | 123k | } | 448 | 33.6k | options->DeleteSubrange(retained, options->size() - retained); | 449 | 33.6k | } |
target_policy.cc:void proto_fuzzer::(anonymous namespace)::RetainMatchingOptions<proto_fuzzer::(anonymous namespace)::RemoveFileTransferOnlyOptions(curl::fuzzer::proto::Scenario*)::$_0>(curl::fuzzer::proto::Scenario*, proto_fuzzer::(anonymous namespace)::RemoveFileTransferOnlyOptions(curl::fuzzer::proto::Scenario*)::$_0) Line | Count | Source | 436 | 83.1k | void RetainMatchingOptions(curl::fuzzer::proto::Scenario* scenario, Predicate predicate) { | 437 | 83.1k | auto* options = scenario->mutable_options(); | 438 | 83.1k | int retained = 0; | 439 | 429k | for (int index = 0; index < options->size(); ++index) { | 440 | 346k | if (!predicate(options->Get(index).option_id())) { | 441 | 2.43k | continue; | 442 | 2.43k | } | 443 | 344k | if (retained != index) { | 444 | 7.59k | options->SwapElements(retained, index); | 445 | 7.59k | } | 446 | 344k | ++retained; | 447 | 344k | } | 448 | 83.1k | options->DeleteSubrange(retained, options->size() - retained); | 449 | 83.1k | } |
|
450 | | |
451 | | /// Keep the high-throughput HTTP lane free of options whose setup or state is |
452 | | /// assigned to a deeper or protocol-specific target. |
453 | 14.6k | void RetainCheapHttpOptions(curl::fuzzer::proto::Scenario* scenario) { |
454 | 14.6k | RetainMatchingOptions(scenario, &IsCheapHttpOption); |
455 | 14.6k | } |
456 | | |
457 | | /// Compact the option list before its shared cap so irrelevant TLS, WebSocket, |
458 | | /// and file-transfer entries cannot crowd out origin traffic mutations. |
459 | 5.54k | void RetainH2ProxyOriginOptions(curl::fuzzer::proto::Scenario* scenario) { |
460 | 5.54k | RetainMatchingOptions(scenario, &IsH2ProxyOriginOption); |
461 | 5.54k | } |
462 | | |
463 | | /// Return whether a scalar option can influence TELNET without selecting an |
464 | | /// incompatible transfer mode or introducing external state. Protocol- |
465 | | /// specific negotiation preferences use Scenario.telnet_options instead. |
466 | 23.2k | bool IsCheapTelnetOption(curl::fuzzer::proto::CurlOptionId option_id) { |
467 | 23.2k | switch (option_id) { |
468 | 6.95k | case curl::fuzzer::proto::CURLOPT_CRLF: |
469 | 13.0k | case curl::fuzzer::proto::CURLOPT_USERPWD: |
470 | 14.4k | case curl::fuzzer::proto::CURLOPT_USERNAME: |
471 | 16.3k | case curl::fuzzer::proto::CURLOPT_PASSWORD: |
472 | 17.4k | case curl::fuzzer::proto::CURLOPT_MAXFILESIZE_LARGE: |
473 | 17.4k | return true; |
474 | | |
475 | 2.83k | case curl::fuzzer::proto::CURL_OPTION_UNSPECIFIED: |
476 | 5.78k | default: |
477 | 5.78k | return false; |
478 | 23.2k | } |
479 | 23.2k | } |
480 | | |
481 | | /// Compact the TELNET option prefix so unrelated HTTP mutations cannot crowd |
482 | | /// useful credentials, CRLF handling, and transfer-size controls out of the |
483 | | /// fixed target's general option budget. |
484 | 4.20k | void RetainCheapTelnetOptions(curl::fuzzer::proto::Scenario* scenario) { |
485 | 4.20k | RetainMatchingOptions(scenario, &IsCheapTelnetOption); |
486 | 4.20k | } |
487 | | |
488 | | /// Return whether an option can change a plaintext FTP transfer serviced by |
489 | | /// the bounded control/data peer. Active mode and FTPS settings are omitted: |
490 | | /// retaining them would select socket and TLS behavior this target does not |
491 | | /// provide, turning otherwise-useful mutations into early setup failures. |
492 | 29.6k | bool IsFtpOption(curl::fuzzer::proto::CurlOptionId option_id) { |
493 | 29.6k | switch (option_id) { |
494 | 270 | case curl::fuzzer::proto::CURLOPT_APPEND: |
495 | 1.38k | case curl::fuzzer::proto::CURLOPT_BUFFERSIZE: |
496 | 2.54k | case curl::fuzzer::proto::CURLOPT_CRLF: |
497 | 2.93k | case curl::fuzzer::proto::CURLOPT_CUSTOMREQUEST: |
498 | 3.74k | case curl::fuzzer::proto::CURLOPT_DIRLISTONLY: |
499 | 4.21k | case curl::fuzzer::proto::CURLOPT_FILETIME: |
500 | 4.98k | case curl::fuzzer::proto::CURLOPT_FTP_ACCOUNT: |
501 | 8.53k | case curl::fuzzer::proto::CURLOPT_FTP_ALTERNATIVE_TO_USER: |
502 | 10.1k | case curl::fuzzer::proto::CURLOPT_FTP_CREATE_MISSING_DIRS: |
503 | 12.2k | case curl::fuzzer::proto::CURLOPT_FTP_FILEMETHOD: |
504 | 13.5k | case curl::fuzzer::proto::CURLOPT_FTP_SKIP_PASV_IP: |
505 | 14.0k | case curl::fuzzer::proto::CURLOPT_FTP_USE_EPSV: |
506 | 14.7k | case curl::fuzzer::proto::CURLOPT_FTP_USE_PRET: |
507 | 15.8k | case curl::fuzzer::proto::CURLOPT_INFILESIZE_LARGE: |
508 | 16.6k | case curl::fuzzer::proto::CURLOPT_MAXFILESIZE_LARGE: |
509 | 17.5k | case curl::fuzzer::proto::CURLOPT_NOBODY: |
510 | 18.4k | case curl::fuzzer::proto::CURLOPT_PASSWORD: |
511 | 18.6k | case curl::fuzzer::proto::CURLOPT_RANGE: |
512 | 19.8k | case curl::fuzzer::proto::CURLOPT_RESUME_FROM_LARGE: |
513 | 21.0k | case curl::fuzzer::proto::CURLOPT_TIMECONDITION: |
514 | 21.2k | case curl::fuzzer::proto::CURLOPT_TIMEVALUE_LARGE: |
515 | 22.0k | case curl::fuzzer::proto::CURLOPT_TRANSFERTEXT: |
516 | 23.9k | case curl::fuzzer::proto::CURLOPT_UPLOAD: |
517 | 24.7k | case curl::fuzzer::proto::CURLOPT_UPLOAD_BUFFERSIZE: |
518 | 25.3k | case curl::fuzzer::proto::CURLOPT_USERNAME: |
519 | 26.7k | case curl::fuzzer::proto::CURLOPT_USERPWD: |
520 | 28.3k | case curl::fuzzer::proto::CURLOPT_WILDCARDMATCH: |
521 | 28.3k | return true; |
522 | | |
523 | 584 | case curl::fuzzer::proto::CURL_OPTION_UNSPECIFIED: |
524 | 1.29k | default: |
525 | 1.29k | return false; |
526 | 29.6k | } |
527 | 29.6k | } |
528 | | |
529 | | /// Keep the FTP target's general option budget focused on states its passive |
530 | | /// peer can actually advance. |
531 | 7.19k | void RetainFtpOptions(curl::fuzzer::proto::Scenario* scenario) { RetainMatchingOptions(scenario, &IsFtpOption); } |
532 | | |
533 | | /// Return whether an option affects TFTP request construction, option |
534 | | /// negotiation, transfer direction, or bounded body delivery. TFTP has no |
535 | | /// connection reuse or stream-level controls, so retaining those settings |
536 | | /// would add protobuf work without another state-machine edge in curl. |
537 | 20.9k | bool IsTftpOption(curl::fuzzer::proto::CurlOptionId option_id) { |
538 | 20.9k | switch (option_id) { |
539 | 2.66k | case curl::fuzzer::proto::CURLOPT_CRLF: |
540 | 3.59k | case curl::fuzzer::proto::CURLOPT_INFILESIZE_LARGE: |
541 | 4.16k | case curl::fuzzer::proto::CURLOPT_MAXFILESIZE_LARGE: |
542 | 11.1k | case curl::fuzzer::proto::CURLOPT_NOBODY: |
543 | 12.4k | case curl::fuzzer::proto::CURLOPT_TFTP_BLKSIZE: |
544 | 14.7k | case curl::fuzzer::proto::CURLOPT_TFTP_NO_OPTIONS: |
545 | 15.0k | case curl::fuzzer::proto::CURLOPT_TRANSFERTEXT: |
546 | 16.7k | case curl::fuzzer::proto::CURLOPT_UPLOAD: |
547 | 16.7k | return true; |
548 | | |
549 | 2.34k | case curl::fuzzer::proto::CURL_OPTION_UNSPECIFIED: |
550 | 4.23k | default: |
551 | 4.23k | return false; |
552 | 20.9k | } |
553 | 20.9k | } |
554 | | |
555 | | /// Keep the datagram lane from spending mutations on stream-only options. |
556 | 2.01k | void RetainTftpOptions(curl::fuzzer::proto::Scenario* scenario) { RetainMatchingOptions(scenario, &IsTftpOption); } |
557 | | |
558 | | /// Decode an integral oneof locally before the generated option canonicalizer |
559 | | /// runs. Protocol mode bounds are policy, not setopt mechanics: folding them |
560 | | /// here keeps nearly every mutation on a real FTP/TFTP state while the shared |
561 | | /// option layer remains unaware of protocol-specific numeric ranges. |
562 | 5.07k | std::uint64_t IntegralMutationValue(const curl::fuzzer::proto::SetOption& option) { |
563 | 5.07k | switch (option.value_case()) { |
564 | 2.97k | case curl::fuzzer::proto::SetOption::kUintValue: |
565 | 2.97k | return option.uint_value(); |
566 | 445 | case curl::fuzzer::proto::SetOption::kBoolValue: |
567 | 445 | return option.bool_value() ? 1U : 0U; |
568 | 499 | case curl::fuzzer::proto::SetOption::kStringValue: |
569 | 1.65k | case curl::fuzzer::proto::SetOption::VALUE_NOT_SET: |
570 | 1.65k | return 0; |
571 | 5.07k | } |
572 | 0 | return 0; |
573 | 5.07k | } |
574 | | |
575 | | /// Fold small FTP enums onto curl's documented domains so random uint64 values |
576 | | /// do not overwhelmingly stop at setopt validation before issuing a command. |
577 | 7.19k | void CanonicalizeFtpOptionModes(curl::fuzzer::proto::Scenario* scenario) { |
578 | 28.3k | for (auto& option : *scenario->mutable_options()) { |
579 | 28.3k | switch (option.option_id()) { |
580 | 1.59k | case curl::fuzzer::proto::CURLOPT_FTP_CREATE_MISSING_DIRS: |
581 | 1.59k | option.set_uint_value(IntegralMutationValue(option) % 3U); |
582 | 1.59k | break; |
583 | 2.14k | case curl::fuzzer::proto::CURLOPT_FTP_FILEMETHOD: |
584 | 2.14k | option.set_uint_value(IntegralMutationValue(option) % 4U); |
585 | 2.14k | break; |
586 | 24.6k | default: |
587 | 24.6k | break; |
588 | 28.3k | } |
589 | 28.3k | } |
590 | 7.19k | } |
591 | | |
592 | | /// TFTP accepts block sizes from 8 through 65464. Mapping zero or a mismatched |
593 | | /// oneof to the default 512 preserves a common valid request, while saturating |
594 | | /// other values retains both lower/upper parser boundaries under mutation. |
595 | 2.01k | void CanonicalizeTftpOptionModes(curl::fuzzer::proto::Scenario* scenario) { |
596 | 16.7k | for (auto& option : *scenario->mutable_options()) { |
597 | 16.7k | if (option.option_id() != curl::fuzzer::proto::CURLOPT_TFTP_BLKSIZE) { |
598 | 15.4k | continue; |
599 | 15.4k | } |
600 | 1.34k | std::uint64_t value = IntegralMutationValue(option); |
601 | 1.34k | if (value == 0) { |
602 | 365 | value = 512; |
603 | 365 | } |
604 | 1.34k | option.set_uint_value(std::max<std::uint64_t>(8, std::min<std::uint64_t>(value, 65464))); |
605 | 1.34k | } |
606 | 2.01k | } |
607 | | |
608 | | /// Identify options introduced for FTP/TFTP so existing fixed lanes do not |
609 | | /// silently inherit dead mutations when the shared generated manifest grows. |
610 | | /// Generic options retained by the FTP/TFTP allowlists are deliberately absent |
611 | | /// here because they remain useful to HTTP, WebSocket, API, or timing targets. |
612 | 346k | bool IsFileTransferOnlyOption(curl::fuzzer::proto::CurlOptionId option_id) { |
613 | 346k | switch (option_id) { |
614 | 287 | case curl::fuzzer::proto::CURLOPT_APPEND: |
615 | 460 | case curl::fuzzer::proto::CURLOPT_DIRLISTONLY: |
616 | 524 | case curl::fuzzer::proto::CURLOPT_FTP_ACCOUNT: |
617 | 584 | case curl::fuzzer::proto::CURLOPT_FTP_ALTERNATIVE_TO_USER: |
618 | 858 | case curl::fuzzer::proto::CURLOPT_FTP_CREATE_MISSING_DIRS: |
619 | 947 | case curl::fuzzer::proto::CURLOPT_FTP_FILEMETHOD: |
620 | 1.06k | case curl::fuzzer::proto::CURLOPT_FTP_SKIP_PASV_IP: |
621 | 1.36k | case curl::fuzzer::proto::CURLOPT_FTP_USE_EPSV: |
622 | 1.69k | case curl::fuzzer::proto::CURLOPT_FTP_USE_PRET: |
623 | 1.82k | case curl::fuzzer::proto::CURLOPT_TFTP_BLKSIZE: |
624 | 2.10k | case curl::fuzzer::proto::CURLOPT_TFTP_NO_OPTIONS: |
625 | 2.31k | case curl::fuzzer::proto::CURLOPT_TRANSFERTEXT: |
626 | 2.43k | case curl::fuzzer::proto::CURLOPT_WILDCARDMATCH: |
627 | 2.43k | return true; |
628 | | |
629 | 19.9k | case curl::fuzzer::proto::CURL_OPTION_UNSPECIFIED: |
630 | 344k | default: |
631 | 344k | return false; |
632 | 346k | } |
633 | 346k | } |
634 | | |
635 | | /// Remove FTP/TFTP-only options while preserving the relative order of every |
636 | | /// generic option an existing fixed target already consumed. |
637 | 83.1k | void RemoveFileTransferOnlyOptions(curl::fuzzer::proto::Scenario* scenario) { |
638 | 83.1k | RetainMatchingOptions( |
639 | 346k | scenario, [](curl::fuzzer::proto::CurlOptionId option_id) { return !IsFileTransferOnlyOption(option_id); }); |
640 | 83.1k | } |
641 | | |
642 | | /// Remove the stateful shapes assigned to the deep HTTP target. This happens |
643 | | /// before BoundScenarioShape so a fast iteration never walks or normalizes a |
644 | | /// MIME tree, upload script, or follow-on connection that it will discard. |
645 | | /// Raw response chunks and request headers stay intact because they reach the |
646 | | /// core HTTP parser cheaply and provide much of the legacy fuzzer's coverage. |
647 | 14.6k | void RemoveDeepHttpShape(curl::fuzzer::proto::Scenario* scenario) { |
648 | 14.6k | scenario->clear_mime_post(); |
649 | 14.6k | scenario->clear_upload(); |
650 | 14.6k | scenario->clear_subsequent_connections(); |
651 | | |
652 | 14.6k | auto* connection = scenario->mutable_connection(); |
653 | 14.6k | connection->clear_server_frames(); |
654 | 14.6k | connection->clear_manual_probes(); |
655 | 14.6k | connection->clear_backpressure(); |
656 | 14.6k | } |
657 | | |
658 | | /// Remove response forms the proxy peer cannot interpret. Raw chunks are the |
659 | | /// HTTP/2 frame stream; WebSocket frames would merely add a second unrelated |
660 | | /// binary grammar, and follow-on Connection messages cannot describe later |
661 | | /// streams multiplexed on the already-open proxy socket. |
662 | 5.54k | void RemoveIgnoredH2ProxyShape(curl::fuzzer::proto::Scenario* scenario) { |
663 | 5.54k | scenario->clear_subsequent_connections(); |
664 | 5.54k | auto* connection = scenario->mutable_connection(); |
665 | 5.54k | connection->clear_server_frames(); |
666 | 5.54k | connection->clear_manual_probes(); |
667 | 5.54k | connection->clear_backpressure(); |
668 | 5.54k | } |
669 | | |
670 | | /// Remove fields the single-socket WebSocket driver cannot consume. MIME also |
671 | | /// changes the HTTP request away from a useful Upgrade handshake, so retaining |
672 | | /// either shape in fixed WS lanes gives LPM mutation work with no WS coverage |
673 | | /// payoff. The mixed compatibility target has no postprocessor and keeps its |
674 | | /// historical behavior. |
675 | 22.8k | void RemoveIgnoredWebSocketShape(curl::fuzzer::proto::Scenario* scenario) { |
676 | 22.8k | scenario->clear_subsequent_connections(); |
677 | 22.8k | scenario->clear_mime_post(); |
678 | 22.8k | } |
679 | | |
680 | | /// Remove fields whose only effect in a TELNET lane would be protobuf work or |
681 | | /// unsafe socket timing. TELNET's curl driver owns the thread until the peer |
682 | | /// closes, so response backpressure and follow-on sockets cannot be serviced |
683 | | /// by the outer event loop. Raw response fragments and the bounded upload stay |
684 | | /// mutation-controlled because the dedicated mock can preload and drain them. |
685 | 4.20k | void RemoveNonTelnetShape(curl::fuzzer::proto::Scenario* scenario) { |
686 | 4.20k | scenario->clear_subsequent_connections(); |
687 | 4.20k | scenario->clear_request_headers(); |
688 | 4.20k | scenario->clear_mime_post(); |
689 | | |
690 | 4.20k | auto* connection = scenario->mutable_connection(); |
691 | 4.20k | connection->clear_server_frames(); |
692 | 4.20k | connection->clear_manual_probes(); |
693 | 4.20k | connection->clear_backpressure(); |
694 | 4.20k | } |
695 | | |
696 | | /// Remove the TELNET-only list and pause outcome from fixed event-driven |
697 | | /// targets. The compatibility target skips postprocessing, so the runtime |
698 | | /// repeats the pause-to-EOF guard before installing callbacks. |
699 | 112k | void RemoveTelnetOnlyShape(curl::fuzzer::proto::Scenario* scenario) { |
700 | 112k | scenario->clear_telnet_options(); |
701 | 112k | if (scenario->has_upload() && scenario->upload().terminal() == curl::fuzzer::proto::UPLOAD_TERMINAL_PAUSE) { |
702 | 10 | scenario->mutable_upload()->set_terminal(curl::fuzzer::proto::UPLOAD_TERMINAL_EOF); |
703 | 10 | } |
704 | 112k | } |
705 | | |
706 | | /// Keep lifecycle work out of protocol-focused lanes. The API binary retains |
707 | | /// this message explicitly; compatibility inputs have no postprocessor so |
708 | | /// existing reproducers keep their historical serialized meaning. |
709 | 104k | void RemoveApiOnlyShape(curl::fuzzer::proto::Scenario* scenario) { scenario->clear_api_plan(); } |
710 | | |
711 | | /// Keep concurrent multi-handle work out of every other fixed lane. The |
712 | | /// compatibility binary deliberately preserves newly-added unknown fields. |
713 | 116k | void RemoveMultiOnlyShape(curl::fuzzer::proto::Scenario* scenario) { scenario->clear_multi_plan(); } |
714 | | |
715 | | /// Remove stream-driver controls that neither file-transfer peer interprets. |
716 | | /// FTP consumes raw byte chunks as control/data replies, while TFTP preserves |
717 | | /// them as individual datagrams; structured WebSocket frames, manual probes, |
718 | | /// and event-loop backpressure therefore cannot affect either curl protocol. |
719 | 13.2k | void RemoveUnusedFileTransferConnectionShape(curl::fuzzer::proto::Connection* connection) { |
720 | 13.2k | connection->clear_server_frames(); |
721 | 13.2k | connection->clear_manual_probes(); |
722 | 13.2k | connection->clear_backpressure(); |
723 | 13.2k | } |
724 | | |
725 | | /// Retain only the reusable shapes consumed by the FTP peer: one raw control |
726 | | /// script, a bounded sequence of passive-data scripts, and optional upload |
727 | | /// input. HTTP, TELNET, WebSocket, and public-API fields would otherwise absorb |
728 | | /// mutations despite having no representation in an FTP exchange. |
729 | 7.19k | void RemoveIgnoredFtpShape(curl::fuzzer::proto::Scenario* scenario) { |
730 | 7.19k | scenario->clear_request_headers(); |
731 | 7.19k | scenario->clear_mime_post(); |
732 | 7.19k | RemoveTelnetOnlyShape(scenario); |
733 | 7.19k | RemoveApiOnlyShape(scenario); |
734 | 7.19k | RemoveMultiOnlyShape(scenario); |
735 | | |
736 | 7.19k | RemoveUnusedFileTransferConnectionShape(scenario->mutable_connection()); |
737 | 7.19k | TrimRepeated(scenario->mutable_subsequent_connections(), scenario_limits::kMaxConnections - 1); |
738 | 7.19k | for (auto& connection : *scenario->mutable_subsequent_connections()) { |
739 | 4.05k | RemoveUnusedFileTransferConnectionShape(&connection); |
740 | 4.05k | } |
741 | 7.19k | } |
742 | | |
743 | | /// Retain the primary raw response script because its entries are the ordered |
744 | | /// UDP datagrams seen by curl, plus optional upload input for WRQ. TFTP cannot |
745 | | /// consume follow-on stream connections or any higher-level protocol shape. |
746 | 2.01k | void RemoveIgnoredTftpShape(curl::fuzzer::proto::Scenario* scenario) { |
747 | 2.01k | scenario->clear_subsequent_connections(); |
748 | 2.01k | scenario->clear_request_headers(); |
749 | 2.01k | scenario->clear_mime_post(); |
750 | 2.01k | RemoveTelnetOnlyShape(scenario); |
751 | 2.01k | RemoveApiOnlyShape(scenario); |
752 | 2.01k | RemoveMultiOnlyShape(scenario); |
753 | 2.01k | RemoveUnusedFileTransferConnectionShape(scenario->mutable_connection()); |
754 | 2.01k | } |
755 | | |
756 | | /// Preserve useful in-range mutations while folding ineffective extremes onto |
757 | | /// meaningful boundaries. Zero remains special: it disables that individual |
758 | | /// control and lets the other control provide the timing target's pressure. |
759 | 31.0k | std::uint32_t CanonicalizeNonZero(std::uint32_t value, std::uint32_t minimum, std::uint32_t maximum) { |
760 | 31.0k | if (value == 0) { |
761 | 12.9k | return 0; |
762 | 12.9k | } |
763 | 18.0k | return std::max(minimum, std::min(value, maximum)); |
764 | 31.0k | } |
765 | | |
766 | | /// Keep the timing target on the plaintext member of the protocol family. |
767 | | /// TLS setup has its own cost profile and would obscure whether backpressure |
768 | | /// mutations are exploring curl's send/receive state machines effectively. |
769 | 13.3k | curl::fuzzer::proto::Scheme PlaintextScheme(curl::fuzzer::proto::Scheme scheme) { |
770 | 13.3k | switch (scheme) { |
771 | 3.37k | case curl::fuzzer::proto::SCHEME_WS: |
772 | 3.37k | case curl::fuzzer::proto::SCHEME_WSS: |
773 | 3.37k | return curl::fuzzer::proto::SCHEME_WS; |
774 | 9.81k | case curl::fuzzer::proto::SCHEME_HTTP: |
775 | 9.81k | case curl::fuzzer::proto::SCHEME_HTTPS: |
776 | 9.81k | case curl::fuzzer::proto::SCHEME_TELNET: |
777 | 9.81k | case curl::fuzzer::proto::SCHEME_FTP: |
778 | 9.81k | case curl::fuzzer::proto::SCHEME_TFTP: |
779 | 9.83k | case curl::fuzzer::proto::SCHEME_UNSPECIFIED: |
780 | 9.95k | default: |
781 | 9.95k | return curl::fuzzer::proto::SCHEME_HTTP; |
782 | 13.3k | } |
783 | 13.3k | } |
784 | | |
785 | | /// Remove timing controls from every connection the structured message can |
786 | | /// carry. Clearing only the primary script would let a mutated redirect turn a |
787 | | /// fixed fast lane into the timed drive loop after its second socket opens. |
788 | 69.8k | void ClearAllBackpressure(curl::fuzzer::proto::Scenario* scenario) { |
789 | 69.8k | if (scenario->has_connection()) { |
790 | 69.8k | scenario->mutable_connection()->clear_backpressure(); |
791 | 69.8k | } |
792 | 69.8k | for (auto& connection : *scenario->mutable_subsequent_connections()) { |
793 | 23.2k | connection.clear_backpressure(); |
794 | 23.2k | } |
795 | 69.8k | } |
796 | | |
797 | | /// Clamp one explicitly pressure-bearing follow-on script to the same useful |
798 | | /// ranges as the timing lane's primary connection. An absent configuration is |
799 | | /// left absent so merely adding a redirect response does not add waits. |
800 | 4.11k | void CanonicalizeOptionalBackpressure(curl::fuzzer::proto::Connection* connection) { |
801 | 4.11k | if (!connection->has_backpressure()) { |
802 | 1.81k | return; |
803 | 1.81k | } |
804 | 2.29k | auto* backpressure = connection->mutable_backpressure(); |
805 | 2.29k | if (backpressure->recv_buf_bytes() == 0 && backpressure->drain_limit() != 0) { |
806 | 7 | backpressure->set_recv_buf_bytes(kDefaultBackpressureBufferBytes); |
807 | 2.28k | } else { |
808 | 2.28k | backpressure->set_recv_buf_bytes( |
809 | 2.28k | CanonicalizeNonZero(backpressure->recv_buf_bytes(), kMinBackpressureBufferBytes, kMaxBackpressureBufferBytes)); |
810 | 2.28k | } |
811 | 2.29k | backpressure->set_drain_limit(CanonicalizeNonZero(backpressure->drain_limit(), 1, kMaxDrainBytesPerIteration)); |
812 | 2.29k | } |
813 | | |
814 | | } // namespace |
815 | | |
816 | | /// Canonicalize the fields that determine which server and drive-loop policy |
817 | | /// execute. Fast targets discard backpressure because one mutated non-zero |
818 | | /// scalar otherwise opts an ordinary input into hundreds of timed waits. The |
819 | | /// timing target does the inverse: it guarantees a non-default buffer setting |
820 | | /// so its CPU allocation remains focused on the intentionally slower paths. |
821 | 116k | void ApplyTargetPolicy(curl::fuzzer::proto::Scenario* scenario, TargetProfile profile) { |
822 | 116k | if (scenario == nullptr) { |
823 | 0 | return; |
824 | 0 | } |
825 | | |
826 | 116k | if (profile == TargetProfile::kCompatibility) { |
827 | | // The original target's existing corpus predates profile splitting. A |
828 | | // no-op here makes the type safe to pass around while its binary continues |
829 | | // to omit postprocessor registration altogether. The append-only FTP/TFTP |
830 | | // scheme values do not justify rewriting historical mixed-lane inputs. |
831 | 0 | return; |
832 | 0 | } |
833 | | |
834 | | // Only the dedicated HTTPS peer consumes a certificate-chain selector. |
835 | | // Remove it before protocol-specific early returns so other fixed targets |
836 | | // do not spend mutations on inert TLS server state. |
837 | 116k | if (profile != TargetProfile::kFastHttps) { |
838 | 98.9k | scenario->clear_tls_certificate_chain(); |
839 | 98.9k | } |
840 | | |
841 | 116k | if (profile == TargetProfile::kFastTelnet) { |
842 | | // Set the scheme before general bounds so the TELNET-specific upload and |
843 | | // PAUSE budgets are selected rather than event-driven compatibility ones. |
844 | 4.20k | scenario->set_scheme(curl::fuzzer::proto::SCHEME_TELNET); |
845 | 4.20k | RemoveApiOnlyShape(scenario); |
846 | 4.20k | RemoveMultiOnlyShape(scenario); |
847 | 4.20k | RemoveNonTelnetShape(scenario); |
848 | 4.20k | RetainCheapTelnetOptions(scenario); |
849 | 4.20k | BoundScenarioShape(scenario); |
850 | 4.20k | BoundTelnetResponse(scenario->mutable_connection()); |
851 | 4.20k | return; |
852 | 4.20k | } |
853 | | |
854 | 112k | if (profile == TargetProfile::kFastHttp) { |
855 | 14.6k | scenario->set_scheme(curl::fuzzer::proto::SCHEME_HTTP); |
856 | 14.6k | RemoveApiOnlyShape(scenario); |
857 | 14.6k | RemoveMultiOnlyShape(scenario); |
858 | 14.6k | RemoveTelnetOnlyShape(scenario); |
859 | 14.6k | RemoveDeepHttpShape(scenario); |
860 | 14.6k | RetainCheapHttpOptions(scenario); |
861 | 14.6k | BoundScenarioShape(scenario); |
862 | 14.6k | return; |
863 | 14.6k | } |
864 | | |
865 | 97.8k | if (profile == TargetProfile::kH2Proxy) { |
866 | 5.54k | scenario->set_scheme(curl::fuzzer::proto::SCHEME_HTTP); |
867 | 5.54k | RemoveApiOnlyShape(scenario); |
868 | 5.54k | RemoveMultiOnlyShape(scenario); |
869 | 5.54k | RemoveTelnetOnlyShape(scenario); |
870 | 5.54k | RemoveIgnoredH2ProxyShape(scenario); |
871 | 5.54k | RetainH2ProxyOriginOptions(scenario); |
872 | 5.54k | BoundScenarioShape(scenario); |
873 | 5.54k | CanonicalizeH2ProxyOriginAuthority(scenario); |
874 | 5.54k | return; |
875 | 5.54k | } |
876 | | |
877 | 92.3k | if (profile == TargetProfile::kFastFtp) { |
878 | 7.19k | scenario->set_scheme(curl::fuzzer::proto::SCHEME_FTP); |
879 | 7.19k | RemoveIgnoredFtpShape(scenario); |
880 | 7.19k | RetainFtpOptions(scenario); |
881 | 7.19k | CanonicalizeFtpOptionModes(scenario); |
882 | 7.19k | BoundScenarioShape(scenario); |
883 | 7.19k | CanonicalizeFtpAuthority(scenario); |
884 | 7.19k | return; |
885 | 7.19k | } |
886 | | |
887 | 85.1k | if (profile == TargetProfile::kFastTftp) { |
888 | 2.01k | scenario->set_scheme(curl::fuzzer::proto::SCHEME_TFTP); |
889 | 2.01k | RemoveIgnoredTftpShape(scenario); |
890 | 2.01k | RetainTftpOptions(scenario); |
891 | 2.01k | CanonicalizeTftpOptionModes(scenario); |
892 | 2.01k | BoundScenarioShape(scenario); |
893 | 2.01k | CanonicalizeTftpAuthority(scenario); |
894 | 2.01k | return; |
895 | 2.01k | } |
896 | | |
897 | | // Select the lane's scheme before applying scheme-sensitive upload bounds. |
898 | | // The scheme field is itself mutable, so bounding first could accidentally |
899 | | // give an HTTP/WS case TELNET's smaller payload budget merely because that |
900 | | // was the input's pre-policy value. |
901 | 83.1k | switch (profile) { |
902 | 0 | case TargetProfile::kCompatibility: |
903 | 0 | return; |
904 | 20.0k | case TargetProfile::kDeepHttp: |
905 | 20.0k | scenario->set_scheme(curl::fuzzer::proto::SCHEME_HTTP); |
906 | 20.0k | break; |
907 | 11.9k | case TargetProfile::kApi: |
908 | 11.9k | scenario->set_scheme(curl::fuzzer::proto::SCHEME_HTTP); |
909 | 11.9k | break; |
910 | 502 | case TargetProfile::kMulti: |
911 | 502 | scenario->set_scheme(curl::fuzzer::proto::SCHEME_HTTP); |
912 | 502 | break; |
913 | 17.8k | case TargetProfile::kFastHttps: |
914 | 17.8k | scenario->set_scheme(curl::fuzzer::proto::SCHEME_HTTPS); |
915 | 17.8k | break; |
916 | 0 | case TargetProfile::kH2Proxy: |
917 | | // The early path removes proxy-incompatible fields before general |
918 | | // bounds, keeping raw frame mutation dense. |
919 | 0 | return; |
920 | 10.6k | case TargetProfile::kFastWebSocket: |
921 | 10.6k | scenario->set_scheme(curl::fuzzer::proto::SCHEME_WS); |
922 | 10.6k | break; |
923 | 8.74k | case TargetProfile::kFastSecureWebSocket: |
924 | 8.74k | scenario->set_scheme(curl::fuzzer::proto::SCHEME_WSS); |
925 | 8.74k | break; |
926 | 13.3k | case TargetProfile::kTiming: |
927 | 13.3k | scenario->set_scheme(PlaintextScheme(scenario->scheme())); |
928 | 13.3k | break; |
929 | 0 | case TargetProfile::kFastHttp: |
930 | 0 | case TargetProfile::kFastTelnet: |
931 | 0 | case TargetProfile::kFastFtp: |
932 | 0 | case TargetProfile::kFastTftp: |
933 | | // Protocol-specific early-return paths selected their scheme above. |
934 | 0 | return; |
935 | 83.1k | } |
936 | | |
937 | | // TELNET's retained slist and synchronous pause have no observable, safe |
938 | | // meaning in the fixed event-driven lanes. Clear them before walking the |
939 | | // general shape so only the compatibility and TELNET targets can retain |
940 | | // those values. |
941 | 83.1k | RemoveTelnetOnlyShape(scenario); |
942 | 83.1k | if (profile != TargetProfile::kApi) { |
943 | 71.1k | RemoveApiOnlyShape(scenario); |
944 | 71.1k | } |
945 | 83.1k | if (profile != TargetProfile::kMulti) { |
946 | 82.6k | RemoveMultiOnlyShape(scenario); |
947 | 82.6k | } |
948 | 83.1k | RemoveFileTransferOnlyOptions(scenario); |
949 | 83.1k | BoundScenarioShape(scenario); |
950 | | |
951 | 83.1k | switch (profile) { |
952 | 0 | case TargetProfile::kCompatibility: |
953 | 0 | return; |
954 | 0 | case TargetProfile::kFastHttp: |
955 | | // Handled before the general bounds so discarded deep shapes are never |
956 | | // traversed on the fast path. |
957 | 0 | return; |
958 | | |
959 | 20.0k | case TargetProfile::kDeepHttp: |
960 | 20.0k | ClearAllBackpressure(scenario); |
961 | 20.0k | return; |
962 | | |
963 | 11.9k | case TargetProfile::kApi: |
964 | 11.9k | ClearAllBackpressure(scenario); |
965 | 11.9k | if (scenario->host_path().size() > scenario_limits::kMaxApiStringBytes) { |
966 | 11 | scenario->mutable_host_path()->resize(scenario_limits::kMaxApiStringBytes); |
967 | 11 | } |
968 | 11.9k | if (scenario->has_api_plan()) { |
969 | 4.85k | BoundApiPlanShape(scenario->mutable_api_plan()); |
970 | 4.85k | } |
971 | 11.9k | return; |
972 | | |
973 | 502 | case TargetProfile::kMulti: { |
974 | 502 | ClearAllBackpressure(scenario); |
975 | 502 | CanonicalizeMultiAuthority(scenario); |
976 | 502 | auto* plan = scenario->mutable_multi_plan(); |
977 | 502 | BoundMultiPlanShape(plan); |
978 | 502 | TrimRepeated(scenario->mutable_subsequent_connections(), plan->transfer_count() - 1U); |
979 | 502 | return; |
980 | 0 | } |
981 | | |
982 | 17.8k | case TargetProfile::kFastHttps: |
983 | 17.8k | ClearAllBackpressure(scenario); |
984 | 17.8k | CanonicalizeTlsAuthority(scenario); |
985 | 17.8k | switch (scenario->tls_certificate_chain()) { |
986 | 17.7k | case curl::fuzzer::proto::TLS_CERTIFICATE_CHAIN_DEFAULT_EC: |
987 | 17.8k | case curl::fuzzer::proto::TLS_CERTIFICATE_CHAIN_ALL_KEY_TYPES: |
988 | 17.8k | break; |
989 | 4 | default: |
990 | 4 | scenario->clear_tls_certificate_chain(); |
991 | 4 | break; |
992 | 17.8k | } |
993 | 17.8k | return; |
994 | | |
995 | 17.8k | case TargetProfile::kH2Proxy: |
996 | | // Handled by the protocol-specific early path above. |
997 | 0 | return; |
998 | | |
999 | 10.6k | case TargetProfile::kFastWebSocket: |
1000 | 10.6k | ClearAllBackpressure(scenario); |
1001 | 10.6k | RemoveIgnoredWebSocketShape(scenario); |
1002 | 10.6k | return; |
1003 | | |
1004 | 8.74k | case TargetProfile::kFastSecureWebSocket: |
1005 | 8.74k | ClearAllBackpressure(scenario); |
1006 | 8.74k | RemoveIgnoredWebSocketShape(scenario); |
1007 | 8.74k | return; |
1008 | | |
1009 | 0 | case TargetProfile::kFastTelnet: |
1010 | | // Handled before the general bounds so its protocol-specific limits are |
1011 | | // selected from the start. |
1012 | 0 | return; |
1013 | | |
1014 | 0 | case TargetProfile::kFastFtp: |
1015 | 0 | case TargetProfile::kFastTftp: |
1016 | | // Their peers consume narrower raw-script shapes, pruned before general |
1017 | | // bounds so ignored fields never tax these fast paths. |
1018 | 0 | return; |
1019 | | |
1020 | 13.3k | case TargetProfile::kTiming: { |
1021 | 13.3k | if (scenario->scheme() == curl::fuzzer::proto::SCHEME_WS) { |
1022 | 3.37k | RemoveIgnoredWebSocketShape(scenario); |
1023 | 3.37k | } |
1024 | 13.3k | auto* backpressure = scenario->mutable_connection()->mutable_backpressure(); |
1025 | 13.3k | if (backpressure->recv_buf_bytes() == 0) { |
1026 | | // A drain limit alone cannot fill the default AF_UNIX buffer with the |
1027 | | // harness's bounded upload. Always tighten the socket so this lane |
1028 | | // represents real pressure, not merely selection of the timed loop. |
1029 | 205 | backpressure->set_recv_buf_bytes(kDefaultBackpressureBufferBytes); |
1030 | 13.1k | } else { |
1031 | 13.1k | backpressure->set_recv_buf_bytes(CanonicalizeNonZero(backpressure->recv_buf_bytes(), |
1032 | 13.1k | kMinBackpressureBufferBytes, kMaxBackpressureBufferBytes)); |
1033 | 13.1k | } |
1034 | 13.3k | backpressure->set_drain_limit(CanonicalizeNonZero(backpressure->drain_limit(), 1, kMaxDrainBytesPerIteration)); |
1035 | 13.3k | for (auto& connection : *scenario->mutable_subsequent_connections()) { |
1036 | 4.11k | CanonicalizeOptionalBackpressure(&connection); |
1037 | 4.11k | } |
1038 | 13.3k | return; |
1039 | 0 | } |
1040 | 83.1k | } |
1041 | 83.1k | } |
1042 | | |
1043 | | } // namespace proto_fuzzer |