/src/curl_fuzzer/proto_fuzzer/option_apply.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 option-translation helpers declared in |
9 | | /// option_apply.h. |
10 | | |
11 | | #include "proto_fuzzer/option_apply.h" |
12 | | |
13 | | #include <algorithm> |
14 | | #include <cstddef> |
15 | | #include <cstdint> |
16 | | #include <cstdio> |
17 | | #include <cstdlib> |
18 | | #include <string> |
19 | | |
20 | | #include "proto_fuzzer/allocation_fault_injection.h" |
21 | | #include "proto_fuzzer/scenario_limits.h" |
22 | | |
23 | | namespace proto_fuzzer { |
24 | | |
25 | | namespace { |
26 | | |
27 | | constexpr char kEventDrivenProtocolsAllowed[] = "http,https,ws,wss"; |
28 | | constexpr char kTelnetProtocolAllowed[] = "telnet"; |
29 | | constexpr char kFtpProtocolAllowed[] = "ftp,ftps"; |
30 | | constexpr char kTftpProtocolAllowed[] = "tftp"; |
31 | | constexpr char kSmtpProtocolAllowed[] = "smtp,smtps"; |
32 | | constexpr char kImapProtocolAllowed[] = "imap,imaps"; |
33 | | constexpr char kPop3ProtocolAllowed[] = "pop3,pop3s"; |
34 | | constexpr char kMqttProtocolAllowed[] = "mqtt,mqtts"; |
35 | | constexpr char kDictProtocolAllowed[] = "dict"; |
36 | | constexpr char kFileProtocolAllowed[] = "file"; |
37 | | constexpr char kLdapProtocolAllowed[] = "ldap,ldaps"; |
38 | | constexpr char kRtspProtocolAllowed[] = "rtsp"; |
39 | | constexpr char kConnectToOverride[] = "::127.0.1.127:"; |
40 | | constexpr char kDevNull[] = "/dev/null"; |
41 | | constexpr char kDefaultProvider[] = "default"; |
42 | | constexpr char kEngineCertificateType[] = "ENG"; |
43 | | constexpr char kPkcs12CertificateType[] = "P12"; |
44 | | constexpr char kProviderCertificateType[] = "PROV"; |
45 | | constexpr unsigned char kMalformedPkcs12[] = {0x30, 0x00}; |
46 | | constexpr char kVerboseEnvVar[] = "FUZZ_VERBOSE"; |
47 | | constexpr char kAltSvcHttpEnvVar[] = "CURL_ALTSVC_HTTP"; |
48 | | constexpr char kHstsHttpEnvVar[] = "CURL_HSTS_HTTP"; |
49 | | constexpr long kConnectTimeoutMs = 200; |
50 | | constexpr long kTimeoutMs = 200; |
51 | | |
52 | | /// Baseline write callback for both CURLOPT_WRITEFUNCTION and |
53 | | /// CURLOPT_HEADERFUNCTION. Consumes every byte so transfers don't stall on |
54 | | /// backpressure and emits nothing. Protocol-specific mocks may install their |
55 | | /// own WRITEFUNCTION afterwards if they need to poke protocol APIs while |
56 | | /// inside a curl callback. |
57 | 4.09M | size_t SilentWriteCallback(void* /*contents*/, size_t size, size_t nmemb, void* /*userdata*/) { return size * nmemb; } |
58 | | |
59 | | /// Consume libcurl's verbose records without emitting per-input diagnostics. |
60 | | /// TELNET's debug build keeps its negotiation/suboption formatters behind the |
61 | | /// verbose switch, so the protocol lane uses this sink to make that reachable |
62 | | /// code fuzzable without turning millions of iterations into log traffic. |
63 | 7.91M | int SilentDebugCallback(CURL* /*handle*/, curl_infotype /*type*/, char* /*data*/, size_t /*size*/, void* /*userdata*/) { |
64 | 7.91M | return 0; |
65 | 7.91M | } |
66 | | |
67 | | /// Let curl's debug build accept transport-security response headers over the |
68 | | /// plaintext HTTP mock. The HTTPS lane now provides a verified peer, but making |
69 | | /// HSTS and Alt-Svc parser coverage depend on a cryptographic handshake would |
70 | | /// needlessly remove those parsers from the high-throughput HTTP lane. |
71 | 266k | void EnableDebugHttpTransportMetadata() { |
72 | 266k | static const bool configured = [] { |
73 | | // CMake builds the fuzzing copy of curl with ENABLE_DEBUG specifically so |
74 | | // these curl-provided test hooks are available. Do not overwrite values a |
75 | | // reproducer deliberately supplied in its environment. |
76 | 30 | (void)setenv(kAltSvcHttpEnvVar, "1", 0); |
77 | 30 | (void)setenv(kHstsHttpEnvVar, "1", 0); |
78 | 30 | return true; |
79 | 30 | }(); |
80 | 266k | (void)configured; |
81 | 266k | } |
82 | | |
83 | 34.3k | void EnableTraceIds() { (void)curl_global_trace("+LIB-IDS"); } |
84 | | |
85 | | } // namespace |
86 | | |
87 | | /// Apply deterministic routing, timeout, output, and persistence defaults. |
88 | | /// Call before applying scenario options. `scheme` identifies the dedicated |
89 | | /// in-process mock that will service the transfer and selects the safe direct- |
90 | | /// protocol allowlist. |
91 | | /// @param easy The curl easy handle to configure. |
92 | | /// @param scheme Protocol whose dedicated in-process mock will service it. |
93 | | /// @param trace_ids Enable verbose LIB-IDS tracing for this scenario. |
94 | | /// @param trace_config Optional curl_global_trace() configuration. |
95 | | /// @return Caller-owned CURLOPT_CONNECT_TO list, which must outlive the easy |
96 | | /// handle and be freed with curl_slist_free_all after curl_easy_cleanup. |
97 | | struct curl_slist* ApplyBaselineOptions(CURL* easy, curl::fuzzer::proto::Scheme scheme, bool trace_ids, |
98 | 266k | const std::string& trace_config) { |
99 | 266k | EnableDebugHttpTransportMetadata(); |
100 | | |
101 | 266k | curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, &SilentWriteCallback); |
102 | 266k | curl_easy_setopt(easy, CURLOPT_HEADERFUNCTION, &SilentWriteCallback); |
103 | | |
104 | 266k | const bool user_requested_verbose = std::getenv(kVerboseEnvVar) != nullptr; |
105 | 266k | if (!trace_config.empty()) { |
106 | | // Apply the fuzzer-selected categories before any explicit trace-ID |
107 | | // request so a scenario cannot accidentally disable its own ID prefixes. |
108 | 44.6k | (void)curl_global_trace(trace_config.c_str()); |
109 | 44.6k | } |
110 | 266k | if (trace_ids) { |
111 | 34.3k | EnableTraceIds(); |
112 | 34.3k | } |
113 | 266k | if (trace_ids || !trace_config.empty()) { |
114 | | // Keep trace coverage enabled during fuzzing without flooding the process |
115 | | // log. The callback still executes curl_trc.c's formatting path. |
116 | 68.0k | curl_easy_setopt(easy, CURLOPT_DEBUGFUNCTION, &SilentDebugCallback); |
117 | 68.0k | curl_easy_setopt(easy, CURLOPT_VERBOSE, 1L); |
118 | 198k | } else if (scheme == curl::fuzzer::proto::SCHEME_TELNET && !user_requested_verbose) { |
119 | | // printoption() and printsub() contain a substantial part of curl's TELNET |
120 | | // parser diagnostics but run only in verbose mode. Keep those paths in the |
121 | | // ordinary TELNET coverage lane while suppressing their high-volume text. |
122 | | // An explicit FUZZ_VERBOSE still skips the sink so reproductions remain |
123 | | // inspectable from the terminal. |
124 | 4.60k | curl_easy_setopt(easy, CURLOPT_DEBUGFUNCTION, &SilentDebugCallback); |
125 | 4.60k | curl_easy_setopt(easy, CURLOPT_VERBOSE, 1L); |
126 | 4.60k | } |
127 | | |
128 | | // Each non-HTTP protocol must use its dedicated peer invariants. Keep them |
129 | | // out of the redirect allowlist so an HTTP response cannot switch a stream |
130 | | // mock into synchronous TELNET, two-channel FTP, or datagram TFTP semantics. |
131 | | // CURLOPT_PROTOCOLS_STR arrived in 7.85.0. |
132 | 266k | const char* direct_protocols = kEventDrivenProtocolsAllowed; |
133 | 266k | switch (scheme) { |
134 | 5.05k | case curl::fuzzer::proto::SCHEME_TELNET: |
135 | 5.05k | direct_protocols = kTelnetProtocolAllowed; |
136 | 5.05k | break; |
137 | 4.12k | case curl::fuzzer::proto::SCHEME_FTP: |
138 | 6.07k | case curl::fuzzer::proto::SCHEME_FTPS: |
139 | 6.07k | direct_protocols = kFtpProtocolAllowed; |
140 | 6.07k | break; |
141 | 2.22k | case curl::fuzzer::proto::SCHEME_TFTP: |
142 | 2.22k | direct_protocols = kTftpProtocolAllowed; |
143 | 2.22k | break; |
144 | 2.74k | case curl::fuzzer::proto::SCHEME_SMTP: |
145 | 4.82k | case curl::fuzzer::proto::SCHEME_SMTPS: |
146 | 4.82k | direct_protocols = kSmtpProtocolAllowed; |
147 | 4.82k | break; |
148 | 2.40k | case curl::fuzzer::proto::SCHEME_IMAP: |
149 | 5.12k | case curl::fuzzer::proto::SCHEME_IMAPS: |
150 | 5.12k | direct_protocols = kImapProtocolAllowed; |
151 | 5.12k | break; |
152 | 1.10k | case curl::fuzzer::proto::SCHEME_POP3: |
153 | 3.78k | case curl::fuzzer::proto::SCHEME_POP3S: |
154 | 3.78k | direct_protocols = kPop3ProtocolAllowed; |
155 | 3.78k | break; |
156 | 1.01k | case curl::fuzzer::proto::SCHEME_MQTT: |
157 | 4.02k | case curl::fuzzer::proto::SCHEME_MQTTS: |
158 | 4.02k | direct_protocols = kMqttProtocolAllowed; |
159 | 4.02k | break; |
160 | 3.17k | case curl::fuzzer::proto::SCHEME_DICT: |
161 | 3.17k | direct_protocols = kDictProtocolAllowed; |
162 | 3.17k | break; |
163 | 4.13k | case curl::fuzzer::proto::SCHEME_GOPHER: |
164 | 4.13k | direct_protocols = "gopher"; |
165 | 4.13k | break; |
166 | 3.39k | case curl::fuzzer::proto::SCHEME_GOPHERS: |
167 | 3.39k | direct_protocols = "gophers"; |
168 | 3.39k | break; |
169 | 3.51k | case curl::fuzzer::proto::SCHEME_FILE: |
170 | 3.51k | direct_protocols = kFileProtocolAllowed; |
171 | 3.51k | break; |
172 | 2.81k | case curl::fuzzer::proto::SCHEME_LDAP: |
173 | 5.73k | case curl::fuzzer::proto::SCHEME_LDAPS: |
174 | 5.73k | direct_protocols = kLdapProtocolAllowed; |
175 | 5.73k | break; |
176 | 6.27k | case curl::fuzzer::proto::SCHEME_RTSP: |
177 | 6.27k | direct_protocols = kRtspProtocolAllowed; |
178 | 6.27k | break; |
179 | 131k | case curl::fuzzer::proto::SCHEME_HTTP: |
180 | 179k | case curl::fuzzer::proto::SCHEME_HTTPS: |
181 | 191k | case curl::fuzzer::proto::SCHEME_WS: |
182 | 199k | case curl::fuzzer::proto::SCHEME_WSS: |
183 | 208k | case curl::fuzzer::proto::SCHEME_IPFS: |
184 | 208k | case curl::fuzzer::proto::SCHEME_IPNS: |
185 | 208k | case curl::fuzzer::proto::SCHEME_UNSPECIFIED: |
186 | 208k | default: |
187 | 208k | break; |
188 | 266k | } |
189 | 266k | curl_easy_setopt(easy, CURLOPT_PROTOCOLS_STR, direct_protocols); |
190 | 266k | curl_easy_setopt(easy, CURLOPT_REDIR_PROTOCOLS_STR, kEventDrivenProtocolsAllowed); |
191 | | |
192 | | // CONNECT_TO confines direct connections, but an ambient http_proxy or |
193 | | // ALL_PROXY can select a proxy before curl asks the harness for a socket. |
194 | | // An explicit empty proxy keeps every transfer inside the socketpair and |
195 | | // makes replay independent of the machine running the fuzzer. |
196 | 266k | curl_easy_setopt(easy, CURLOPT_PROXY, ""); |
197 | | |
198 | | // Keep raw secure-scheme inputs independent of the host trust store, |
199 | | // matching the legacy harness. The dedicated TLS mock installs its own |
200 | | // in-memory trust anchor after this baseline; scenario options still run |
201 | | // last and can deliberately select verification failures. |
202 | 266k | curl_easy_setopt(easy, CURLOPT_SSL_VERIFYPEER, 0L); |
203 | | |
204 | | // Force every name lookup to the fuzzer's in-process mock peer. The caller |
205 | | // owns the returned slist and must free it after curl_easy_cleanup. |
206 | 266k | struct curl_slist* connect_to = curl_slist_append(nullptr, kConnectToOverride); |
207 | 266k | curl_easy_setopt(easy, CURLOPT_CONNECT_TO, connect_to); |
208 | | |
209 | | // Short bounds: fuzzing should never sit waiting on real I/O. Response |
210 | | // volume is already bounded by libFuzzer's input-size limit, so do not rate |
211 | | // limit receive traffic here: a global bytes-per-second throttle turns every |
212 | | // otherwise-complete large response into wall-clock sleep. |
213 | 266k | curl_easy_setopt(easy, CURLOPT_CONNECTTIMEOUT_MS, kConnectTimeoutMs); |
214 | 266k | curl_easy_setopt(easy, CURLOPT_TIMEOUT_MS, kTimeoutMs); |
215 | | |
216 | | // Keep every persistence/read path deterministic and prevent scenarios from |
217 | | // leaking state onto the filesystem. COOKIEFILE also makes the in-memory |
218 | | // engine's RELOAD command traverse its loader against a harmless empty |
219 | | // source. These path options deliberately remain absent from the generated |
220 | | // mutation manifest, so a proto cannot replace /dev/null. |
221 | 266k | curl_easy_setopt(easy, CURLOPT_COOKIEJAR, kDevNull); |
222 | 266k | curl_easy_setopt(easy, CURLOPT_COOKIEFILE, kDevNull); |
223 | 266k | curl_easy_setopt(easy, CURLOPT_ALTSVC, kDevNull); |
224 | 266k | curl_easy_setopt(easy, CURLOPT_HSTS, kDevNull); |
225 | 266k | curl_easy_setopt(easy, CURLOPT_NETRC_FILE, kDevNull); |
226 | | // Do not set CRLFILE merely to mirror the legacy harness. An empty CRL is |
227 | | // not a harmless sink: when a scenario restores certificate verification, |
228 | | // OpenSSL rejects /dev/null before it can exercise useful handshake and |
229 | | // verification paths. The option is absent from the mutation manifest, so |
230 | | // leaving it unset introduces neither filesystem writes nor external input. |
231 | | |
232 | | // Match the legacy TLV fuzzer: FUZZ_VERBOSE in the environment flips curl's |
233 | | // own verbose logging on. Useful when reproducing a crashing corpus entry. |
234 | 266k | if (user_requested_verbose) { |
235 | 0 | curl_easy_setopt(easy, CURLOPT_VERBOSE, 1L); |
236 | 0 | } |
237 | 266k | return connect_to; |
238 | 266k | } |
239 | | |
240 | | /// Apply one closed, harness-owned TLS client-credential configuration. |
241 | | /// No protobuf bytes become a path, provider, or engine identifier. |
242 | | /// @param easy Handle receiving the fixed credential options. |
243 | | /// @param profile Safe credential setup selected by the HTTPS lane. |
244 | | /// @return The first curl_easy_setopt error, or CURLE_OK. |
245 | 27.1k | CURLcode ApplyTlsClientCredentialProfile(CURL* easy, curl::fuzzer::proto::TlsClientCredentialProfile profile) { |
246 | 27.1k | if (easy == nullptr) { |
247 | 0 | return CURLE_BAD_FUNCTION_ARGUMENT; |
248 | 0 | } |
249 | | |
250 | 27.1k | const auto set_string = [easy](CURLoption option, const char* value) { |
251 | 1.34k | return curl_easy_setopt(easy, option, value); |
252 | 1.34k | }; |
253 | 27.1k | switch (profile) { |
254 | 26.2k | case curl::fuzzer::proto::TLS_CLIENT_CREDENTIAL_NONE: |
255 | 26.2k | return CURLE_OK; |
256 | | |
257 | 306 | case curl::fuzzer::proto::TLS_CLIENT_CREDENTIAL_MALFORMED_PKCS12: { |
258 | 306 | CURLcode result = set_string(CURLOPT_SSLCERTTYPE, kPkcs12CertificateType); |
259 | 306 | if (result != CURLE_OK) { |
260 | 3 | return result; |
261 | 3 | } |
262 | 303 | const curl_blob blob = {const_cast<unsigned char*>(kMalformedPkcs12), sizeof(kMalformedPkcs12), CURL_BLOB_COPY}; |
263 | 303 | return curl_easy_setopt(easy, CURLOPT_SSLCERT_BLOB, &blob); |
264 | 306 | } |
265 | | |
266 | 128 | case curl::fuzzer::proto::TLS_CLIENT_CREDENTIAL_ENGINE_DEV_NULL: { |
267 | 128 | CURLcode result = set_string(CURLOPT_SSLCERTTYPE, kEngineCertificateType); |
268 | 128 | if (result != CURLE_OK) { |
269 | 6 | return result; |
270 | 6 | } |
271 | 122 | return set_string(CURLOPT_SSLCERT, kDevNull); |
272 | 128 | } |
273 | | |
274 | 356 | case curl::fuzzer::proto::TLS_CLIENT_CREDENTIAL_DEFAULT_PROVIDER_DEV_NULL: { |
275 | 356 | CURLcode result = set_string(CURLOPT_SSLENGINE, kDefaultProvider); |
276 | 356 | if (result != CURLE_OK) { |
277 | 238 | return result; |
278 | 238 | } |
279 | 118 | result = set_string(CURLOPT_SSLCERTTYPE, kProviderCertificateType); |
280 | 118 | if (result != CURLE_OK) { |
281 | 9 | return result; |
282 | 9 | } |
283 | 109 | return set_string(CURLOPT_SSLCERT, kDevNull); |
284 | 118 | } |
285 | | |
286 | 105 | case curl::fuzzer::proto::TLS_CLIENT_CREDENTIAL_UNLOADED_PROVIDER_DEV_NULL: { |
287 | 105 | CURLcode result = set_string(CURLOPT_SSLCERTTYPE, kProviderCertificateType); |
288 | 105 | if (result != CURLE_OK) { |
289 | 4 | return result; |
290 | 4 | } |
291 | 101 | return set_string(CURLOPT_SSLCERT, kDevNull); |
292 | 105 | } |
293 | | |
294 | 0 | default: |
295 | 0 | return CURLE_BAD_FUNCTION_ARGUMENT; |
296 | 27.1k | } |
297 | 27.1k | } |
298 | | |
299 | | /// Translate and apply one generated scalar/string SetOption to the easy handle. |
300 | | /// Pointer-valued options borrow the SetOption's protobuf-owned string storage, |
301 | | /// so its containing Scenario must remain alive and unmodified until the |
302 | | /// transfer has stopped and the easy handle has been cleaned up. Borrowing |
303 | | /// avoids a duplicate backing allocation on every iteration; the lifetime is |
304 | | /// especially important for CURLOPT_POSTFIELDS, which curl does not copy. |
305 | | /// @param easy The curl easy handle to configure. |
306 | | /// @param option The SetOption proto describing which option and value to set. |
307 | | /// @return curl's setopt result, or CURLE_UNKNOWN_OPTION for an unknown id. |
308 | 759k | CURLcode ApplySetOption(CURL* easy, const curl::fuzzer::proto::SetOption& option) { |
309 | 759k | const OptionDescriptor* desc = LookupOptionDescriptor(option.option_id()); |
310 | 759k | if (desc == nullptr) { |
311 | 23.7k | return CURLE_UNKNOWN_OPTION; |
312 | 23.7k | } |
313 | 735k | const CURLoption curlopt = static_cast<CURLoption>(desc->curlopt); |
314 | | |
315 | 735k | switch (desc->kind) { |
316 | 364k | case OptionValueKind::kString: { |
317 | 364k | const std::string& value = option.string_value(); |
318 | | |
319 | | // COPYPOSTFIELDS allocates after reading the separately configured |
320 | | // POSTFIELDSIZE. First install the same protobuf-owned bytes as a |
321 | | // no-allocation borrowed fallback. A genuine OOM then still leaves a |
322 | | // coherent pointer and length for later consumers such as AWS SigV4. |
323 | 364k | if (desc->curlopt == CURLOPT_COPYPOSTFIELDS) { |
324 | 5.39k | CURLcode result = curl_easy_setopt(easy, CURLOPT_POSTFIELDS, value.c_str()); |
325 | 5.39k | if (result != CURLE_OK) { |
326 | 0 | return result; |
327 | 0 | } |
328 | 5.39k | } |
329 | | |
330 | | // POSTFIELDS borrows its pointer and COPYPOSTFIELDS copies exactly the |
331 | | // previously configured size. Apply the correlated byte length first so |
332 | | // either option accepts embedded NULs without strlen semantics and the |
333 | | // copying variant can never read beyond the protobuf-owned buffer. |
334 | 364k | if (desc->curlopt == CURLOPT_POSTFIELDS || desc->curlopt == CURLOPT_COPYPOSTFIELDS) { |
335 | 10.0k | CURLcode result = curl_easy_setopt(easy, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(value.size())); |
336 | 10.0k | if (result != CURLE_OK) { |
337 | 0 | return result; |
338 | 0 | } |
339 | 10.0k | } |
340 | | |
341 | 364k | if (desc->curlopt == CURLOPT_COPYPOSTFIELDS) { |
342 | | // Copy ownership is a harness invariant rather than an allocation- |
343 | | // failure target. Preserve pending ordinals for later injectable work. |
344 | 5.39k | allocation_fault_injection::ScopedFailureSuppression suppress_failures; |
345 | 5.39k | return curl_easy_setopt(easy, CURLOPT_COPYPOSTFIELDS, value.c_str()); |
346 | 5.39k | } |
347 | | |
348 | | // Curl otherwise treats this option as a filename during the TLS |
349 | | // handshake. Fixed-policy inputs normally arrive pre-constrained by the |
350 | | // postprocessor; this runtime check also protects compatibility inputs, |
351 | | // which intentionally bypass it. Curl copies this option in setopt, so |
352 | | // the temporary remains valid for the call's full ownership contract. |
353 | 359k | if (desc->curlopt == CURLOPT_PINNEDPUBLICKEY && !UsesInMemoryPublicKeyPin(value)) { |
354 | 143 | std::string constrained = value; |
355 | 143 | ConstrainPinnedPublicKeyValue(&constrained); |
356 | 143 | return curl_easy_setopt(easy, curlopt, constrained.c_str()); |
357 | 143 | } |
358 | | |
359 | 358k | return curl_easy_setopt(easy, curlopt, value.c_str()); |
360 | 359k | } |
361 | | |
362 | | // Decode the uint_value and pass it as either a long or a curl_off_t depending on the option. |
363 | 177k | case OptionValueKind::kUint: { |
364 | 177k | const std::uint64_t raw = DecodeIntegralOptionValue(*desc, option); |
365 | | // CURLOPTTYPE_OFF_T options start at 30000. Everything below takes a |
366 | | // long; everything at/above takes a curl_off_t. |
367 | 177k | if (static_cast<int>(desc->curlopt) >= 30000) { |
368 | 27.3k | return curl_easy_setopt(easy, curlopt, static_cast<curl_off_t>(raw)); |
369 | 27.3k | } |
370 | 150k | return curl_easy_setopt(easy, curlopt, static_cast<long>(raw)); |
371 | 177k | } |
372 | | |
373 | | // Decode the bool_value and pass it as a long flag (0 or 1). |
374 | 193k | case OptionValueKind::kBool: { |
375 | 193k | const long flag = static_cast<long>(DecodeIntegralOptionValue(*desc, option)); |
376 | 193k | return curl_easy_setopt(easy, curlopt, flag); |
377 | 177k | } |
378 | 735k | } |
379 | 0 | return CURLE_UNKNOWN_OPTION; |
380 | 735k | } |
381 | | |
382 | | /// Return the runtime-visible option prefix length. Fixed targets normally |
383 | | /// trim the protobuf in their postprocessor, but the compatibility target must |
384 | | /// preserve its historical message unchanged; applying the same bound here |
385 | | /// prevents that lane from doing mutation-sized setopt work while exposing the |
386 | | /// same option prefix as postprocessed fixed lanes. |
387 | | /// @param scenario Structured input whose option prefix will be consumed. |
388 | | /// @return Number of options visible to the runtime. |
389 | 519k | std::size_t RuntimeOptionCount(const curl::fuzzer::proto::Scenario& scenario) { |
390 | 519k | return std::min<std::size_t>(static_cast<std::size_t>(scenario.options_size()), scenario_limits::kMaxOptions); |
391 | 519k | } |
392 | | |
393 | | /// Apply the bounded runtime-visible prefix of Scenario.options. Individual |
394 | | /// CURLcode values remain intentionally ignored, matching the fuzzer runner's |
395 | | /// historical behavior. Bounding here also covers standalone compatibility |
396 | | /// seeds that reach RunScenario without normalization. |
397 | | /// @param easy Easy handle receiving each supported option. |
398 | | /// @param scenario Structured input that owns all borrowed option strings. |
399 | | /// @return Number of option entries attempted. |
400 | 266k | std::size_t ApplyScenarioOptions(CURL* easy, const curl::fuzzer::proto::Scenario& scenario) { |
401 | 266k | const std::size_t option_count = RuntimeOptionCount(scenario); |
402 | 1.02M | for (std::size_t index = 0; index < option_count; ++index) { |
403 | 759k | (void)ApplySetOption(easy, scenario.options(static_cast<int>(index))); |
404 | 759k | } |
405 | 266k | return option_count; |
406 | 266k | } |
407 | | |
408 | | } // namespace proto_fuzzer |