/src/curl_fuzzer/legacy_tlv_mutator.cc
Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al. |
3 | | * |
4 | | * SPDX-License-Identifier: curl |
5 | | */ |
6 | | |
7 | | #include "legacy_tlv_mutator.h" |
8 | | |
9 | | #include <algorithm> |
10 | | #include <array> |
11 | | #include <cstring> |
12 | | #include <limits> |
13 | | |
14 | | #ifdef LEGACY_TLV_MUTATOR_CHECK_HARNESS_CONSTANTS |
15 | | #include "curl_fuzzer.h" |
16 | | |
17 | | static_assert(TLV_TYPE_URL == 1, "legacy TLV URL ID changed"); |
18 | | static_assert(TLV_TYPE_MIME_PART_NAME == 14, "legacy MIME TLV IDs changed"); |
19 | | static_assert(TLV_TYPE_MIME_PART_DATA == 15, "legacy MIME TLV IDs changed"); |
20 | | static_assert(TLV_TYPE_HSTS == 51, "legacy disabled TLV IDs changed"); |
21 | | static_assert(TLV_TYPE_PROXY == 53, "legacy routing TLV IDs changed"); |
22 | | static_assert(TLV_TYPE_FTPPORT == 102, "legacy routing TLV IDs changed"); |
23 | | static_assert(TLV_TYPE_INTERFACE == 105, "legacy routing TLV IDs changed"); |
24 | | static_assert(TLV_TYPE_DNS_INTERFACE == 129, |
25 | | "legacy routing audit TLV IDs changed"); |
26 | | static_assert(TLV_TYPE_PRE_PROXY == 149, |
27 | | "legacy routing TLV IDs changed"); |
28 | | static_assert(TLV_TYPE_ECH == 161, "legacy string TLV range changed"); |
29 | | static_assert(TLV_TYPE_PORT == 200, "legacy integer TLV range changed"); |
30 | | static_assert(TLV_TYPE_TCP_KEEPCNT == 293, |
31 | | "legacy integer TLV range changed"); |
32 | | static_assert(TLV_TYPE_SSLVERSION == 300, |
33 | | "legacy enum TLV range changed"); |
34 | | static_assert(TLV_TYPE_PROXY_SSLVERSION == 312, |
35 | | "legacy enum TLV range changed"); |
36 | | static_assert(TLV_TYPE_RESUME_FROM_LARGE == 320, |
37 | | "legacy large-value TLV range changed"); |
38 | | static_assert(TLV_TYPE_TIMEVALUE_LARGE == 325, |
39 | | "legacy large-value TLV range changed"); |
40 | | #endif |
41 | | |
42 | | #if defined(__clang__) || defined(__GNUC__) |
43 | | /** |
44 | | * Optional libFuzzer byte mutator supplied by the fuzzing runtime. |
45 | | * |
46 | | * Weak linkage keeps standalone replay and unit-test binaries linkable. The |
47 | | * wrapper below selects a small deterministic fallback when the symbol is not |
48 | | * present, so both build modes exercise the same higher-level policy. |
49 | | */ |
50 | | extern "C" size_t LLVMFuzzerMutate(uint8_t *data, size_t size, |
51 | | size_t max_size) __attribute__((weak)); |
52 | | #endif |
53 | | |
54 | | namespace legacy_tlv_mutator { |
55 | | namespace { |
56 | | |
57 | | /* The fast path is a policy pipeline: consult one ID policy, build a bounded |
58 | | * index over valid wire records, then perform one local edit. Mutate chooses |
59 | | * between structure preservation and a rare raw escape before parsing; |
60 | | * CrossOver uses the same index to splice only whole records. The shared parse |
61 | | * contract keeps repair, mutation, validation, and crossover from drifting |
62 | | * apart. */ |
63 | | |
64 | | /* All parser state is fixed-size and stack-resident. The record bound makes |
65 | | * work predictable for adversarial corpora, while the field/growth bounds |
66 | | * keep one large payload from dominating a mutation or one execution. */ |
67 | | constexpr size_t kTlvHeaderSize = 6; |
68 | | constexpr size_t kMaxParsedRecords = 512; |
69 | | constexpr size_t kMaxValueGrowth = 4096; |
70 | | constexpr size_t kMaxFieldToMutate = 64 * 1024; |
71 | | constexpr uint16_t kMaximumKnownType = 325; |
72 | | |
73 | | /** |
74 | | * Semantic representation classes that are safe to mutate interchangeably. |
75 | | * |
76 | | * In particular, numeric options must remain four bytes and MIME values need |
77 | | * nested framing; classifying them here prevents a type mutation from silently |
78 | | * producing a record that the harness rejects before curl is called. |
79 | | */ |
80 | | enum class ValueKind { |
81 | | kUnknown, |
82 | | kBytes, |
83 | | kString, |
84 | | kU32, |
85 | | kMime, |
86 | | }; |
87 | | |
88 | | /** |
89 | | * Mutation policy for one legacy option type. |
90 | | * |
91 | | * `kind` protects the value-shape invariant. `repeatable` is mutation policy: |
92 | | * list-like entries can contribute independently, whereas duplicate scalar or |
93 | | * last-write-wins records mostly bloat a child and obscure which value won. |
94 | | */ |
95 | | struct TypeInfo { |
96 | | ValueKind kind; |
97 | | bool repeatable; |
98 | | }; |
99 | | |
100 | | /** |
101 | | * Non-owning index for a validated top-level record. |
102 | | * |
103 | | * Storing offsets instead of pointers keeps the descriptor valid across array |
104 | | * relocation and avoids per-record allocation. A descriptor is intentionally |
105 | | * used for only one edit because an edit can invalidate later offsets. |
106 | | */ |
107 | | struct Record { |
108 | | size_t offset; |
109 | | uint32_t length; |
110 | | uint16_t type; |
111 | | |
112 | | /** Returns the payload boundary used by value-only mutations. */ |
113 | 0 | size_t value_offset() const { return offset + kTlvHeaderSize; } |
114 | | /** Returns the first byte after this record for bounded tail moves. */ |
115 | 0 | size_t end_offset() const { return value_offset() + length; } |
116 | | /** Returns the wire footprint used by insert, erase, and crossover. */ |
117 | 0 | size_t total_size() const { return kTlvHeaderSize + length; } |
118 | | }; |
119 | | |
120 | | /** |
121 | | * Allocation-free parse result shared by all structured operations. |
122 | | * |
123 | | * `records_size` always marks the end of the validated prefix, even on error; |
124 | | * that invariant lets mutation cheaply repair corrupt inputs. `valid` means |
125 | | * the whole input has acceptable legacy trailing-byte semantics, while the |
126 | | * limit flag distinguishes bounded-parser exhaustion for diagnostics/policy. |
127 | | */ |
128 | | struct ParsedInput { |
129 | | /** Fixed storage makes parse cost independent of allocator behavior. */ |
130 | | std::array<Record, kMaxParsedRecords> records; |
131 | | /** Descriptors in `[0, count)` are validated and safe to index. */ |
132 | | size_t count; |
133 | | /** Boundary after the last validated record, including on failure. */ |
134 | | size_t records_size; |
135 | | /** Bytes not represented by records; short tails are wire-compatible. */ |
136 | | size_t trailing_size; |
137 | | /** True only when the complete input satisfies structured preconditions. */ |
138 | | bool valid; |
139 | | /** Records resource-bound rejection separately from malformed framing. */ |
140 | | bool hit_record_limit; |
141 | | }; |
142 | | |
143 | | /** |
144 | | * Small deterministic generator isolated from the C library's global state. |
145 | | * |
146 | | * Reproducibility from libFuzzer's seed is required for minimizing failures, |
147 | | * and local state makes mutation safe when several fuzzing jobs share a |
148 | | * process. |
149 | | */ |
150 | | class Random { |
151 | | public: |
152 | | /** Decorrelates low-entropy seeds before the first SplitMix64 choice. */ |
153 | | explicit Random(uint64_t seed) |
154 | 0 | : state_(seed + UINT64_C(0x9e3779b97f4a7c15)) {} |
155 | | |
156 | | /** Advances a stable full-width stream used for all policy choices. */ |
157 | 0 | uint64_t Next() { |
158 | 0 | uint64_t z = (state_ += UINT64_C(0x9e3779b97f4a7c15)); |
159 | 0 | z = (z ^ (z >> 30)) * UINT64_C(0xbf58476d1ce4e5b9); |
160 | 0 | z = (z ^ (z >> 27)) * UINT64_C(0x94d049bb133111eb); |
161 | 0 | return z ^ (z >> 31); |
162 | 0 | } |
163 | | |
164 | | /** Selects a bounded index; zero keeps empty-case callers simple. */ |
165 | 0 | size_t Index(size_t count) { |
166 | 0 | return count ? static_cast<size_t>(Next() % count) : 0; |
167 | 0 | } |
168 | | |
169 | | /** Makes deliberately rare policy branches explicit and reproducible. */ |
170 | 0 | bool OneIn(unsigned int count) { |
171 | 0 | return count && (Next() % count) == 0; |
172 | 0 | } |
173 | | |
174 | | private: |
175 | | uint64_t state_; |
176 | | }; |
177 | | |
178 | | /** Reads a big-endian type without host alignment assumptions. */ |
179 | 0 | uint16_t ReadU16(const uint8_t *data) { |
180 | 0 | return static_cast<uint16_t>((static_cast<uint16_t>(data[0]) << 8) | |
181 | 0 | static_cast<uint16_t>(data[1])); |
182 | 0 | } |
183 | | |
184 | | /** Reads a length/value without host endianness or alignment assumptions. */ |
185 | 0 | uint32_t ReadU32(const uint8_t *data) { |
186 | 0 | return (static_cast<uint32_t>(data[0]) << 24) | |
187 | 0 | (static_cast<uint32_t>(data[1]) << 16) | |
188 | 0 | (static_cast<uint32_t>(data[2]) << 8) | |
189 | 0 | static_cast<uint32_t>(data[3]); |
190 | 0 | } |
191 | | |
192 | | /** Writes a type in the exact byte order consumed by the legacy harness. */ |
193 | 0 | void WriteU16(uint8_t *data, uint16_t value) { |
194 | 0 | data[0] = static_cast<uint8_t>(value >> 8); |
195 | 0 | data[1] = static_cast<uint8_t>(value); |
196 | 0 | } |
197 | | |
198 | | /** Writes a length/value in the byte order consumed by the legacy harness. */ |
199 | 0 | void WriteU32(uint8_t *data, uint32_t value) { |
200 | 0 | data[0] = static_cast<uint8_t>(value >> 24); |
201 | 0 | data[1] = static_cast<uint8_t>(value >> 16); |
202 | 0 | data[2] = static_cast<uint8_t>(value >> 8); |
203 | 0 | data[3] = static_cast<uint8_t>(value); |
204 | 0 | } |
205 | | |
206 | | /** Identifies payloads interpreted as server bytes rather than C strings. */ |
207 | 0 | bool IsResponseType(uint16_t type) { |
208 | 0 | return type == 2 || (type >= 17 && type <= 26) || type == 31 || |
209 | 0 | type == 32; |
210 | 0 | } |
211 | | |
212 | | /** |
213 | | * Returns the non-resolving endpoint used for a routing option. |
214 | | * |
215 | | * These four options can reinterpret an arbitrary string as a hostname outside |
216 | | * the harness's CONNECT_TO wildcard. Keeping the canonical spellings in one |
217 | | * function makes insertion and post-mutation repair enforce the same safety |
218 | | * policy. PRE_PROXY deliberately uses a SOCKS scheme so repair still reaches |
219 | | * its option parser instead of converting the option into an ordinary proxy. |
220 | | * |
221 | | * CURLOPT_DNS_INTERFACE (type 129) is intentionally absent. With c-ares it is |
222 | | * passed to ares_set_local_dev as a device name; it is not resolved as a host, |
223 | | * and builds without c-ares reject it at setopt time. Canonicalizing it would |
224 | | * therefore discard interface-binding coverage without removing a synchronous |
225 | | * resolver stall. |
226 | | */ |
227 | 52.0k | const char *CanonicalRoutingValueImpl(uint16_t type) { |
228 | 52.0k | switch(type) { |
229 | 38.5k | case 53: // CURLOPT_PROXY |
230 | 38.5k | return "http://127.0.0.1"; |
231 | 189 | case 102: // CURLOPT_FTPPORT |
232 | 5.65k | case 105: // CURLOPT_INTERFACE |
233 | 5.65k | return "127.0.0.1"; |
234 | 7.76k | case 149: // CURLOPT_PRE_PROXY |
235 | 7.76k | return "socks5://127.0.0.1"; |
236 | 0 | default: |
237 | 0 | return nullptr; |
238 | 52.0k | } |
239 | 52.0k | } |
240 | | |
241 | | /** |
242 | | * Identifies strings whose arbitrary values can escape the in-process peer. |
243 | | * Centralizing the predicate through CanonicalRoutingValue prevents the |
244 | | * mutation exclusions from drifting away from final output repair. |
245 | | */ |
246 | 0 | bool IsLatencySensitiveStringType(uint16_t type) { |
247 | 0 | return CanonicalRoutingValueImpl(type) != nullptr; |
248 | 0 | } |
249 | | |
250 | | /** Identifies non-range-based options whose payload is exactly one u32. */ |
251 | 0 | bool IsTopLevelU32Type(uint16_t type) { |
252 | 0 | switch(type) { |
253 | 0 | case 16: |
254 | 0 | case 27: |
255 | 0 | case 28: |
256 | 0 | case 29: |
257 | 0 | case 33: |
258 | 0 | case 34: |
259 | 0 | case 38: |
260 | 0 | case 40: |
261 | 0 | case 46: |
262 | 0 | case 48: |
263 | 0 | case 49: |
264 | 0 | case 50: |
265 | 0 | case 54: |
266 | 0 | return true; |
267 | 0 | default: |
268 | 0 | return false; |
269 | 0 | } |
270 | 0 | } |
271 | | |
272 | | /** |
273 | | * Centralizes the declarative legacy ID policy used by parsing and mutation. |
274 | | * Keeping disabled IDs and wire kinds in one mapping prevents generators from |
275 | | * drifting away from the harness switch table. |
276 | | */ |
277 | 0 | TypeInfo GetTypeInfo(uint16_t type) { |
278 | | /* IDs 14 and 15 are valid only inside a type-13 MIME record. ID 51 is |
279 | | * declared for corpus compatibility but intentionally disabled by the |
280 | | * harness. */ |
281 | 0 | if(type == 14 || type == 15 || type == 51) |
282 | 0 | return {ValueKind::kUnknown, false}; |
283 | | |
284 | 0 | if(type >= 1 && type <= 54) { |
285 | 0 | if(type == 13) |
286 | 0 | return {ValueKind::kMime, true}; |
287 | 0 | if(IsResponseType(type) || type == 8) |
288 | 0 | return {ValueKind::kBytes, false}; |
289 | 0 | if(IsTopLevelU32Type(type)) |
290 | 0 | return {ValueKind::kU32, false}; |
291 | 0 | return {ValueKind::kString, type == 6 || type == 11}; |
292 | 0 | } |
293 | | |
294 | | /* The legacy ID layout deliberately groups string and integer options. |
295 | | * Keep the two API-misuse-prone POSTFIELDSIZE IDs out of the mutator because |
296 | | * their parser cases are disabled. */ |
297 | 0 | if(type >= 100 && type <= 161) |
298 | 0 | return {ValueKind::kString, false}; |
299 | 0 | if(type >= 200 && type <= 293 && type != 212) |
300 | 0 | return {ValueKind::kU32, false}; |
301 | 0 | if(type >= 300 && type <= 312) |
302 | 0 | return {ValueKind::kU32, false}; |
303 | 0 | if(type >= 320 && type <= 325 && type != 322) |
304 | 0 | return {ValueKind::kU32, false}; |
305 | | |
306 | 0 | return {ValueKind::kUnknown, false}; |
307 | 0 | } |
308 | | |
309 | | /** |
310 | | * Validates the nested subset accepted inside a MIME record. |
311 | | * |
312 | | * MIME values are intentionally treated as atomic by mutation, so this one |
313 | | * linear check is enough to guarantee later top-level edits cannot expose a |
314 | | * malformed nested length or an illegal top-level/nested ID mix. |
315 | | */ |
316 | 0 | bool ValidateMimeValue(const uint8_t *data, size_t size) { |
317 | 0 | size_t offset = 0; |
318 | 0 | while(offset + kTlvHeaderSize <= size) { |
319 | 0 | const uint16_t type = ReadU16(data + offset); |
320 | 0 | const uint32_t length = ReadU32(data + offset + 2); |
321 | 0 | if(type != 14 && type != 15) |
322 | 0 | return false; |
323 | 0 | if(static_cast<size_t>(length) > size - offset - kTlvHeaderSize) |
324 | 0 | return false; |
325 | 0 | offset += kTlvHeaderSize + length; |
326 | 0 | } |
327 | | /* The harness treats fewer than six leftover bytes as an exhausted nested |
328 | | * TLV stream, so preserve that wire-compatible behavior. */ |
329 | 0 | return size - offset < kTlvHeaderSize; |
330 | 0 | } |
331 | | |
332 | | /** |
333 | | * Scans a legacy TLV stream into bounded, non-owning record descriptors. |
334 | | * |
335 | | * Parsing stops at the first semantic or framing failure and always publishes |
336 | | * the end of the valid prefix. The structured path can therefore repair an |
337 | | * input by truncation without rescanning or allocating. A fixed descriptor |
338 | | * array bounds latency for very large/adversarial inputs. Fewer than six final |
339 | | * bytes remain valid because the harness itself treats that suffix as EOF. |
340 | | */ |
341 | 0 | ParsedInput Parse(const uint8_t *data, size_t size) { |
342 | 0 | ParsedInput parsed = {}; |
343 | 0 | std::array<uint8_t, kMaximumKnownType + 1> seen = {}; |
344 | 0 | size_t offset = 0; |
345 | |
|
346 | 0 | while(offset + kTlvHeaderSize <= size) { |
347 | 0 | if(parsed.count == parsed.records.size()) { |
348 | 0 | parsed.records_size = offset; |
349 | 0 | parsed.trailing_size = size - offset; |
350 | 0 | parsed.hit_record_limit = true; |
351 | 0 | return parsed; |
352 | 0 | } |
353 | | |
354 | 0 | const uint16_t type = ReadU16(data + offset); |
355 | 0 | const uint32_t length = ReadU32(data + offset + 2); |
356 | 0 | const TypeInfo info = GetTypeInfo(type); |
357 | 0 | if(info.kind == ValueKind::kUnknown || |
358 | 0 | static_cast<size_t>(length) > size - offset - kTlvHeaderSize) { |
359 | 0 | parsed.records_size = offset; |
360 | 0 | parsed.trailing_size = size - offset; |
361 | 0 | return parsed; |
362 | 0 | } |
363 | 0 | if(info.kind == ValueKind::kU32 && length != 4) { |
364 | 0 | parsed.records_size = offset; |
365 | 0 | parsed.trailing_size = size - offset; |
366 | 0 | return parsed; |
367 | 0 | } |
368 | 0 | if(info.kind == ValueKind::kMime && |
369 | 0 | !ValidateMimeValue(data + offset + kTlvHeaderSize, length)) { |
370 | 0 | parsed.records_size = offset; |
371 | 0 | parsed.trailing_size = size - offset; |
372 | 0 | return parsed; |
373 | 0 | } |
374 | 0 | if(!info.repeatable && seen[type]) { |
375 | 0 | parsed.records_size = offset; |
376 | 0 | parsed.trailing_size = size - offset; |
377 | 0 | return parsed; |
378 | 0 | } |
379 | | |
380 | 0 | seen[type] = 1; |
381 | 0 | parsed.records[parsed.count++] = {offset, length, type}; |
382 | 0 | offset += kTlvHeaderSize + length; |
383 | 0 | parsed.records_size = offset; |
384 | 0 | } |
385 | | |
386 | 0 | parsed.trailing_size = size - offset; |
387 | 0 | parsed.valid = true; |
388 | 0 | return parsed; |
389 | 0 | } |
390 | | |
391 | | /** Tests singleton presence when choosing insertions or replacement types. */ |
392 | 0 | bool HasType(const ParsedInput &parsed, uint16_t type) { |
393 | 0 | for(size_t i = 0; i < parsed.count; ++i) { |
394 | 0 | if(parsed.records[i].type == type) |
395 | 0 | return true; |
396 | 0 | } |
397 | 0 | return false; |
398 | 0 | } |
399 | | |
400 | | /* The supported top-level IDs form compact ranges with five intentional |
401 | | * holes. Mapping an ordinal avoids a 224-entry table while keeping selection |
402 | | * uniform and O(1). */ |
403 | | constexpr size_t kKnownTopLevelTypeCount = 224; |
404 | | |
405 | | /** |
406 | | * Maps a dense selection ordinal to a legal top-level legacy type ID. |
407 | | * The arithmetic encoding excludes nested and disabled holes without a large |
408 | | * lookup table, keeping the hot selection path cache-small and uniform. |
409 | | */ |
410 | 0 | uint16_t TypeFromOrdinal(size_t ordinal) { |
411 | 0 | if(ordinal < 51) { |
412 | 0 | uint16_t type = static_cast<uint16_t>(ordinal + 1); |
413 | 0 | if(type >= 14) |
414 | 0 | type = static_cast<uint16_t>(type + 2); |
415 | 0 | if(type >= 51) |
416 | 0 | ++type; |
417 | 0 | return type; |
418 | 0 | } |
419 | 0 | ordinal -= 51; |
420 | |
|
421 | 0 | if(ordinal < 62) |
422 | 0 | return static_cast<uint16_t>(100 + ordinal); |
423 | 0 | ordinal -= 62; |
424 | |
|
425 | 0 | if(ordinal < 93) { |
426 | 0 | uint16_t type = static_cast<uint16_t>(200 + ordinal); |
427 | 0 | if(type >= 212) |
428 | 0 | ++type; |
429 | 0 | return type; |
430 | 0 | } |
431 | 0 | ordinal -= 93; |
432 | |
|
433 | 0 | if(ordinal < 13) |
434 | 0 | return static_cast<uint16_t>(300 + ordinal); |
435 | 0 | ordinal -= 13; |
436 | |
|
437 | 0 | uint16_t type = static_cast<uint16_t>(320 + ordinal); |
438 | 0 | if(type >= 322) |
439 | 0 | ++type; |
440 | 0 | return type; |
441 | 0 | } |
442 | | |
443 | | /** Selects uniformly from the legal top-level IDs encoded above. */ |
444 | 0 | uint16_t RandomKnownType(Random *random) { |
445 | 0 | return TypeFromOrdinal(random->Index(kKnownTopLevelTypeCount)); |
446 | 0 | } |
447 | | |
448 | | /** |
449 | | * Supplies a protocol-specific URL that reaches the selected legacy target. |
450 | | * Seed records must use the compiled harness's scheme or otherwise-valid |
451 | | * structured mutations still exit before exercising the intended protocol. |
452 | | */ |
453 | 0 | const char *DefaultUrl() { |
454 | | #if defined(FUZZ_PROTOCOLS_DICT) |
455 | | return "dict://127.0.0.1/"; |
456 | | #elif defined(FUZZ_PROTOCOLS_FILE) |
457 | | return "file:///dev/null"; |
458 | | #elif defined(FUZZ_PROTOCOLS_FTP) |
459 | | return "ftp://127.0.0.1/"; |
460 | | #elif defined(FUZZ_PROTOCOLS_GOPHER) |
461 | | return "gopher://127.0.0.1/"; |
462 | | #elif defined(FUZZ_PROTOCOLS_HTTPS) |
463 | | return "https://127.0.0.1/"; |
464 | | #elif defined(FUZZ_PROTOCOLS_IMAP) |
465 | | return "imap://127.0.0.1/"; |
466 | | #elif defined(FUZZ_PROTOCOLS_LDAP) |
467 | | return "ldap://127.0.0.1/"; |
468 | | #elif defined(FUZZ_PROTOCOLS_MQTT) |
469 | | return "mqtt://127.0.0.1/"; |
470 | | #elif defined(FUZZ_PROTOCOLS_POP3) |
471 | | return "pop3://127.0.0.1/"; |
472 | | #elif defined(FUZZ_PROTOCOLS_RTMP) |
473 | | return "rtmp://127.0.0.1/"; |
474 | | #elif defined(FUZZ_PROTOCOLS_RTSP) |
475 | | return "rtsp://127.0.0.1/"; |
476 | | #elif defined(FUZZ_PROTOCOLS_SCP) |
477 | | return "scp://127.0.0.1/"; |
478 | | #elif defined(FUZZ_PROTOCOLS_SFTP) |
479 | | return "sftp://127.0.0.1/"; |
480 | | #elif defined(FUZZ_PROTOCOLS_SMB) |
481 | | return "smb://127.0.0.1/share"; |
482 | | #elif defined(FUZZ_PROTOCOLS_SMTP) |
483 | | return "smtp://127.0.0.1/"; |
484 | | #elif defined(FUZZ_PROTOCOLS_TFTP) |
485 | | return "tftp://127.0.0.1/file"; |
486 | | #elif defined(FUZZ_PROTOCOLS_WS) |
487 | | return "ws://127.0.0.1/"; |
488 | | #else |
489 | | return "http://127.0.0.1/"; |
490 | | #endif |
491 | 0 | } |
492 | | |
493 | | /** |
494 | | * Supplies a small, coverage-oriented response placeholder for the target. |
495 | | * It is not universally parseable, but a plausible greeting/status advances |
496 | | * common state machines farther than an arbitrary byte on first insertion. |
497 | | */ |
498 | 0 | const char *DefaultResponse() { |
499 | | #if defined(FUZZ_PROTOCOLS_FTP) || defined(FUZZ_PROTOCOLS_SMTP) |
500 | | return "220 fuzz ready\r\n"; |
501 | | #elif defined(FUZZ_PROTOCOLS_IMAP) |
502 | | return "* OK fuzz ready\r\n"; |
503 | | #elif defined(FUZZ_PROTOCOLS_POP3) |
504 | | return "+OK fuzz ready\r\n"; |
505 | | #elif defined(FUZZ_PROTOCOLS_RTSP) |
506 | | return "RTSP/1.0 200 OK\r\nCSeq: 1\r\n\r\n"; |
507 | | #elif defined(FUZZ_PROTOCOLS_HTTP) || defined(FUZZ_PROTOCOLS_HTTPS) || \ |
508 | | defined(FUZZ_PROTOCOLS_WS) || defined(FUZZ_PROTOCOLS_ALL) |
509 | | return "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"; |
510 | | #else |
511 | 0 | return "fuzz\r\n"; |
512 | 0 | #endif |
513 | 0 | } |
514 | | |
515 | | /** |
516 | | * Distinguishes the file harness, where injecting a network response is dead |
517 | | * corpus weight, from targets whose transfer path needs peer input. |
518 | | */ |
519 | 0 | bool TargetNeedsResponse() { |
520 | | #if defined(FUZZ_PROTOCOLS_FILE) |
521 | | return false; |
522 | | #else |
523 | 0 | return true; |
524 | 0 | #endif |
525 | 0 | } |
526 | | |
527 | | /** |
528 | | * Marks the minimal records that keep structured network children executable. |
529 | | * Raw mutation may still remove or corrupt them to retain framing coverage. |
530 | | */ |
531 | 0 | bool IsRequiredType(uint16_t type) { |
532 | 0 | return type == 1 || (TargetNeedsResponse() && type == 2); |
533 | 0 | } |
534 | | |
535 | | /** |
536 | | * Provides a bounded deterministic byte mutator outside a libFuzzer binary. |
537 | | * It keeps unit tests, replay tools, and alternate fuzzing engines functional |
538 | | * without pretending to match libFuzzer's richer mutation distribution. |
539 | | */ |
540 | | size_t LocalByteMutate(uint8_t *data, size_t size, size_t max_size, |
541 | 0 | Random *random) { |
542 | 0 | size = std::min(size, max_size); |
543 | 0 | if(!max_size) |
544 | 0 | return 0; |
545 | 0 | if(!size) { |
546 | 0 | data[0] = static_cast<uint8_t>(random->Next()); |
547 | 0 | return 1; |
548 | 0 | } |
549 | | |
550 | 0 | switch(random->Index(4)) { |
551 | 0 | case 0: { |
552 | 0 | const size_t pos = random->Index(size); |
553 | 0 | data[pos] ^= static_cast<uint8_t>(1u << random->Index(8)); |
554 | 0 | break; |
555 | 0 | } |
556 | 0 | case 1: { |
557 | 0 | const size_t pos = random->Index(size); |
558 | 0 | data[pos] = static_cast<uint8_t>(random->Next()); |
559 | 0 | break; |
560 | 0 | } |
561 | 0 | case 2: |
562 | 0 | if(size < max_size) { |
563 | 0 | const size_t pos = random->Index(size + 1); |
564 | 0 | std::memmove(data + pos + 1, data + pos, size - pos); |
565 | 0 | data[pos] = static_cast<uint8_t>(random->Next()); |
566 | 0 | ++size; |
567 | 0 | } |
568 | 0 | else { |
569 | 0 | data[random->Index(size)] ^= 0x80; |
570 | 0 | } |
571 | 0 | break; |
572 | 0 | default: { |
573 | 0 | const size_t pos = random->Index(size); |
574 | 0 | std::memmove(data + pos, data + pos + 1, size - pos - 1); |
575 | 0 | --size; |
576 | 0 | break; |
577 | 0 | } |
578 | 0 | } |
579 | 0 | return size; |
580 | 0 | } |
581 | | |
582 | | /** |
583 | | * Uses libFuzzer's tuned byte mutations when available, with a local fallback. |
584 | | * Centralizing this choice gives both the periodic raw path and field mutation |
585 | | * identical size bounds and standalone behavior. |
586 | | */ |
587 | | size_t ByteMutate(uint8_t *data, size_t size, size_t max_size, |
588 | 0 | Random *random) { |
589 | 0 | #if defined(__clang__) || defined(__GNUC__) |
590 | 0 | if(LLVMFuzzerMutate) |
591 | 0 | return std::min(LLVMFuzzerMutate(data, size, max_size), max_size); |
592 | 0 | #endif |
593 | 0 | return LocalByteMutate(data, size, max_size, random); |
594 | 0 | } |
595 | | |
596 | | /** |
597 | | * Generates numeric payloads biased toward API and conversion boundaries. |
598 | | * Occasional random values retain breadth, while common powers, signed edges, |
599 | | * and maxima reach more useful curl branches than uniformly random u32s alone. |
600 | | */ |
601 | 0 | void WriteBoundaryU32(uint8_t *out, Random *random) { |
602 | 0 | static constexpr uint32_t kBoundaries[] = { |
603 | 0 | 0, 1, 2, 3, 4, 7, 8, 15, 16, 255, 256, 1024, |
604 | 0 | UINT32_C(0x7fffffff), UINT32_C(0x80000000), UINT32_MAX}; |
605 | 0 | uint32_t value; |
606 | 0 | if(random->OneIn(4)) |
607 | 0 | value = static_cast<uint32_t>(random->Next()); |
608 | 0 | else |
609 | 0 | value = kBoundaries[random->Index(sizeof(kBoundaries) / |
610 | 0 | sizeof(kBoundaries[0]))]; |
611 | 0 | WriteU32(out, value); |
612 | 0 | } |
613 | | |
614 | | /** |
615 | | * Lightweight immutable payload view used for synthetic record defaults. |
616 | | * Static backing avoids allocation and copying until insertion is committed. |
617 | | */ |
618 | | struct ByteView { |
619 | | const uint8_t *data; |
620 | | size_t size; |
621 | | }; |
622 | | |
623 | | /** |
624 | | * Chooses a small payload that satisfies each value class's framing needs. |
625 | | * These are coverage-oriented starting points, not fixed corpus templates. |
626 | | * Ordinary fields can evolve freely; latency-sensitive routing fields stay on |
627 | | * loopback because even valid raw mutations are repaired before execution. |
628 | | * Malformed routing records remain available through the raw framing lane. |
629 | | */ |
630 | 0 | ByteView DefaultValue(uint16_t type, ValueKind kind) { |
631 | 0 | static const uint8_t kOneByte[] = {'A'}; |
632 | 0 | static const uint8_t kHeader[] = "X-Fuzz: 1"; |
633 | 0 | static const uint8_t kRecipient[] = "a@b"; |
634 | 0 | static const uint8_t kMime[] = { |
635 | 0 | 0, 14, 0, 0, 0, 1, 'n', |
636 | 0 | 0, 15, 0, 0, 0, 1, 'v'}; |
637 | |
|
638 | 0 | if(type == 1) { |
639 | 0 | const char *url = DefaultUrl(); |
640 | 0 | return {reinterpret_cast<const uint8_t *>(url), std::strlen(url)}; |
641 | 0 | } |
642 | 0 | if(IsResponseType(type)) { |
643 | 0 | const char *response = DefaultResponse(); |
644 | 0 | return {reinterpret_cast<const uint8_t *>(response), |
645 | 0 | std::strlen(response)}; |
646 | 0 | } |
647 | 0 | if(type == 6) |
648 | 0 | return {kHeader, sizeof(kHeader) - 1}; |
649 | 0 | if(type == 11) |
650 | 0 | return {kRecipient, sizeof(kRecipient) - 1}; |
651 | 0 | const char *routing_value = CanonicalRoutingValueImpl(type); |
652 | 0 | if(routing_value) |
653 | 0 | return {reinterpret_cast<const uint8_t *>(routing_value), |
654 | 0 | std::strlen(routing_value)}; |
655 | 0 | if(kind == ValueKind::kMime) |
656 | 0 | return {kMime, sizeof(kMime)}; |
657 | 0 | return {kOneByte, sizeof(kOneByte)}; |
658 | 0 | } |
659 | | |
660 | | /** |
661 | | * Rewrites resolver-sensitive records after mutation or crossover. |
662 | | * |
663 | | * A valid raw edit can change only a proxy hostname and otherwise look |
664 | | * executable; canonicalizing that case closes the resolver-stall lane. |
665 | | * Structured edits and crossover also pass here because they can inherit an |
666 | | * unsafe value from an older corpus entry even though they never mutate that |
667 | | * value directly. Semantically malformed streams take the length-preserving |
668 | | * neutralization path below so their parser shape remains available. |
669 | | * |
670 | | * Records are processed from the end so resizing a value never invalidates an |
671 | | * offset that remains to be visited. If the preferred loopback spelling cannot |
672 | | * fit, an empty string is used as the safe fallback: curl treats it as disabled |
673 | | * proxy/interface/FTPPORT configuration, and shrinking always fits. Short |
674 | | * wire-compatible trailing bytes move with the tail and remain intact. |
675 | | */ |
676 | | size_t CanonicalizeRoutingValues(uint8_t *data, size_t size, |
677 | 0 | size_t max_size) { |
678 | 0 | const ParsedInput parsed = Parse(data, size); |
679 | 0 | if(!parsed.valid) { |
680 | | /* The legacy harness rejects most malformed streams before transfer, but |
681 | | * malformed MIME records are tolerated and an oversized record list can |
682 | | * exceed this mutator's descriptor bound. Scan only independently framed |
683 | | * top-level records and NUL their first routing byte. This preserves every |
684 | | * type, length, offset, and malformed suffix while making curl observe an |
685 | | * empty C string if the harness does continue to a transfer. A broken |
686 | | * length stops the scan at the same boundary that stops the harness. */ |
687 | 0 | size_t offset = 0; |
688 | 0 | while(offset + kTlvHeaderSize <= size) { |
689 | 0 | const uint32_t length = ReadU32(data + offset + 2); |
690 | 0 | if(static_cast<size_t>(length) > size - offset - kTlvHeaderSize) |
691 | 0 | break; |
692 | 0 | if(length && |
693 | 0 | IsLatencySensitiveStringType(ReadU16(data + offset))) |
694 | 0 | data[offset + kTlvHeaderSize] = 0; |
695 | 0 | offset += kTlvHeaderSize + length; |
696 | 0 | } |
697 | 0 | return size; |
698 | 0 | } |
699 | | |
700 | 0 | for(size_t index = parsed.count; index > 0; --index) { |
701 | 0 | const Record &record = parsed.records[index - 1]; |
702 | 0 | const char *canonical = CanonicalRoutingValueImpl(record.type); |
703 | 0 | if(!canonical) |
704 | 0 | continue; |
705 | | |
706 | 0 | size_t canonical_size = std::strlen(canonical); |
707 | 0 | const size_t fixed_size = size - record.length; |
708 | 0 | if(canonical_size > max_size - fixed_size) |
709 | 0 | canonical_size = 0; |
710 | |
|
711 | 0 | const uint8_t *value = data + record.value_offset(); |
712 | 0 | if(record.length == canonical_size && |
713 | 0 | (!canonical_size || |
714 | 0 | std::memcmp(value, canonical, canonical_size) == 0)) |
715 | 0 | continue; |
716 | | |
717 | 0 | const size_t tail_size = size - record.end_offset(); |
718 | 0 | std::memmove(data + record.value_offset() + canonical_size, |
719 | 0 | data + record.end_offset(), tail_size); |
720 | 0 | WriteU32(data + record.offset + 2, |
721 | 0 | static_cast<uint32_t>(canonical_size)); |
722 | 0 | if(canonical_size) |
723 | 0 | std::memcpy(data + record.value_offset(), canonical, canonical_size); |
724 | 0 | size = fixed_size + canonical_size; |
725 | 0 | } |
726 | 0 | return size; |
727 | 0 | } |
728 | | |
729 | | /** |
730 | | * Chooses an insertable type while prioritizing transfer-enabling core records. |
731 | | * URL and response prerequisites are filled first; later choices mix a focused |
732 | | * high-value set with the complete ID space. Bounded retries enforce singleton |
733 | | * cardinality without turning mutation time into corpus-dependent searching. |
734 | | */ |
735 | 0 | uint16_t PickInsertType(const ParsedInput &parsed, Random *random) { |
736 | 0 | if(!HasType(parsed, 1)) |
737 | 0 | return 1; |
738 | 0 | if(TargetNeedsResponse() && !HasType(parsed, 2)) |
739 | 0 | return 2; |
740 | | |
741 | 0 | static constexpr uint16_t kCoreTypes[] = { |
742 | 0 | 1, 2, 17, 6, 8, 11, 13, 16, 27, 28, 29, 30, 40, 48, 49, |
743 | 0 | 52, 53, 54, 200, 214, 222, 227, 240, 258, 262, 280}; |
744 | |
|
745 | 0 | for(size_t attempt = 0; attempt < 64; ++attempt) { |
746 | 0 | const uint16_t type = random->OneIn(2) |
747 | 0 | ? RandomKnownType(random) |
748 | 0 | : kCoreTypes[random->Index( |
749 | 0 | sizeof(kCoreTypes) / |
750 | 0 | sizeof(kCoreTypes[0]))]; |
751 | 0 | const TypeInfo info = GetTypeInfo(type); |
752 | 0 | if(info.repeatable || !HasType(parsed, type)) |
753 | 0 | return type; |
754 | 0 | } |
755 | 0 | return 6; // Headers are repeatable and useful for every network protocol. |
756 | 0 | } |
757 | | |
758 | | /** |
759 | | * Inserts one self-consistent record at an existing record boundary. |
760 | | * |
761 | | * Choosing the type and default before moving bytes makes capacity failure a |
762 | | * no-op. Boundary insertion and big-endian header repair preserve every |
763 | | * existing record while introducing a new curl option in one mutation step. |
764 | | */ |
765 | | size_t InsertRecord(uint8_t *data, size_t size, size_t max_size, |
766 | 0 | const ParsedInput &parsed, Random *random) { |
767 | 0 | const uint16_t type = PickInsertType(parsed, random); |
768 | 0 | const TypeInfo info = GetTypeInfo(type); |
769 | 0 | uint8_t numeric_value[4]; |
770 | 0 | ByteView value = DefaultValue(type, info.kind); |
771 | 0 | if(info.kind == ValueKind::kU32) { |
772 | 0 | WriteBoundaryU32(numeric_value, random); |
773 | 0 | value = {numeric_value, sizeof(numeric_value)}; |
774 | 0 | } |
775 | |
|
776 | 0 | if(value.size > std::numeric_limits<uint32_t>::max() || |
777 | 0 | kTlvHeaderSize + value.size > max_size - std::min(size, max_size)) |
778 | 0 | return size; |
779 | | |
780 | 0 | const size_t boundary_index = random->Index(parsed.count + 1); |
781 | 0 | const size_t insert_at = boundary_index == parsed.count |
782 | 0 | ? parsed.records_size |
783 | 0 | : parsed.records[boundary_index].offset; |
784 | 0 | const size_t added = kTlvHeaderSize + value.size; |
785 | 0 | std::memmove(data + insert_at + added, data + insert_at, size - insert_at); |
786 | 0 | WriteU16(data + insert_at, type); |
787 | 0 | WriteU32(data + insert_at + 2, static_cast<uint32_t>(value.size)); |
788 | 0 | if(value.size) |
789 | 0 | std::memcpy(data + insert_at + kTlvHeaderSize, value.data, value.size); |
790 | 0 | return size + added; |
791 | 0 | } |
792 | | |
793 | | /** |
794 | | * Erases one optional record while preserving framing and transfer scaffolding. |
795 | | * Whole-record deletion explores option interactions without producing broken |
796 | | * lengths; retaining URL/initial-response records avoids systematic fast-path |
797 | | * loss and socket stalls in otherwise-structured children. |
798 | | */ |
799 | | size_t EraseRecord(uint8_t *data, size_t size, const ParsedInput &parsed, |
800 | 0 | Random *random) { |
801 | 0 | if(!parsed.count) |
802 | 0 | return size; |
803 | 0 | const size_t start = random->Index(parsed.count); |
804 | 0 | for(size_t n = 0; n < parsed.count; ++n) { |
805 | 0 | const Record &record = parsed.records[(start + n) % parsed.count]; |
806 | 0 | if(IsRequiredType(record.type)) |
807 | 0 | continue; |
808 | 0 | std::memmove(data + record.offset, data + record.end_offset(), |
809 | 0 | size - record.end_offset()); |
810 | 0 | return size - record.total_size(); |
811 | 0 | } |
812 | 0 | return size; |
813 | 0 | } |
814 | | |
815 | | /** |
816 | | * Duplicates only records whose harness semantics permit repetition. |
817 | | * This targets lists such as headers/MIME parts while avoiding inert duplicate |
818 | | * singletons and performs no edit when capacity cannot hold the whole record. |
819 | | */ |
820 | | size_t DuplicateRecord(uint8_t *data, size_t size, size_t max_size, |
821 | 0 | const ParsedInput &parsed, Random *random) { |
822 | 0 | if(!parsed.count) |
823 | 0 | return size; |
824 | 0 | const size_t start = random->Index(parsed.count); |
825 | 0 | for(size_t n = 0; n < parsed.count; ++n) { |
826 | 0 | const Record &record = parsed.records[(start + n) % parsed.count]; |
827 | 0 | if(!GetTypeInfo(record.type).repeatable) |
828 | 0 | continue; |
829 | 0 | if(record.total_size() > max_size - size) |
830 | 0 | return size; |
831 | 0 | const size_t at = record.end_offset(); |
832 | 0 | std::memmove(data + at + record.total_size(), data + at, size - at); |
833 | | /* memmove above can move the source only when it is after `at`; this |
834 | | * record ends exactly at `at`, so its bytes remain available. */ |
835 | 0 | std::memmove(data + at, data + record.offset, record.total_size()); |
836 | 0 | return size + record.total_size(); |
837 | 0 | } |
838 | 0 | return size; |
839 | 0 | } |
840 | | |
841 | | /** |
842 | | * Swaps neighboring complete records to explore order-sensitive option setup. |
843 | | * Restricting the permutation to one adjacent pair keeps evolutionary locality |
844 | | * and preserves every record's bytes and framing. |
845 | | */ |
846 | | size_t SwapAdjacentRecords(uint8_t *data, size_t size, |
847 | 0 | const ParsedInput &parsed, Random *random) { |
848 | 0 | if(parsed.count < 2) |
849 | 0 | return size; |
850 | 0 | const size_t index = random->Index(parsed.count - 1); |
851 | 0 | const Record &first = parsed.records[index]; |
852 | 0 | const Record &second = parsed.records[index + 1]; |
853 | 0 | std::rotate(data + first.offset, data + second.offset, |
854 | 0 | data + second.end_offset()); |
855 | 0 | return size; |
856 | 0 | } |
857 | | |
858 | | /** |
859 | | * Rebinds an optional record to another option with the same representation. |
860 | | * Required transfer records are retained. Matching kinds protect u32/MIME |
861 | | * shapes, and routing fields are never created from arbitrary string payloads; |
862 | | * their loopback defaults remain available through direct insertion. |
863 | | */ |
864 | | size_t ChangeRecordType(uint8_t *data, size_t size, |
865 | 0 | const ParsedInput &parsed, Random *random) { |
866 | 0 | if(!parsed.count) |
867 | 0 | return size; |
868 | 0 | const size_t start = random->Index(parsed.count); |
869 | 0 | for(size_t n = 0; n < parsed.count; ++n) { |
870 | 0 | const Record &record = parsed.records[(start + n) % parsed.count]; |
871 | 0 | if(IsRequiredType(record.type)) |
872 | 0 | continue; |
873 | 0 | const TypeInfo original = GetTypeInfo(record.type); |
874 | |
|
875 | 0 | for(size_t attempt = 0; attempt < kKnownTopLevelTypeCount; ++attempt) { |
876 | 0 | const uint16_t replacement = RandomKnownType(random); |
877 | 0 | const TypeInfo candidate = GetTypeInfo(replacement); |
878 | 0 | if(replacement != record.type && candidate.kind == original.kind && |
879 | 0 | !IsLatencySensitiveStringType(replacement) && |
880 | 0 | (candidate.repeatable || !HasType(parsed, replacement))) { |
881 | 0 | WriteU16(data + record.offset, replacement); |
882 | 0 | return size; |
883 | 0 | } |
884 | 0 | } |
885 | 0 | } |
886 | 0 | return size; |
887 | 0 | } |
888 | | |
889 | | /** |
890 | | * Mutates one payload while shielding the rest of the TLV stream. |
891 | | * |
892 | | * Numeric fields use boundary values; nested MIME and connection-routing |
893 | | * fields remain atomic so an ordinary field edit cannot manufacture a routing |
894 | | * endpoint. The raw lane still explores surrounding type/length corruption, |
895 | | * but its independently framed routing strings are canonicalized before curl |
896 | | * can resolve them. For other byte/string fields, the untouched suffix is |
897 | | * temporarily parked at the high end of the selected field's capacity. This |
898 | | * gives LLVMFuzzerMutate a contiguous resizable buffer without letting it |
899 | | * consume following headers; the suffix is then relocated behind the new value |
900 | | * and the length repaired. Per-field size/growth caps keep this hot operation |
901 | | * bounded. |
902 | | */ |
903 | | size_t MutateRecordValue(uint8_t *data, size_t size, size_t max_size, |
904 | 0 | const ParsedInput &parsed, Random *random) { |
905 | 0 | if(!parsed.count) |
906 | 0 | return size; |
907 | 0 | const size_t start = random->Index(parsed.count); |
908 | |
|
909 | 0 | for(size_t n = 0; n < parsed.count; ++n) { |
910 | 0 | const Record &record = parsed.records[(start + n) % parsed.count]; |
911 | 0 | const TypeInfo info = GetTypeInfo(record.type); |
912 | 0 | if(info.kind == ValueKind::kMime || |
913 | 0 | IsLatencySensitiveStringType(record.type) || |
914 | 0 | record.length > kMaxFieldToMutate) |
915 | 0 | continue; |
916 | | |
917 | 0 | if(info.kind == ValueKind::kU32) { |
918 | 0 | WriteBoundaryU32(data + record.value_offset(), random); |
919 | 0 | return size; |
920 | 0 | } |
921 | | |
922 | 0 | const size_t fixed_size = size - record.length; |
923 | 0 | if(fixed_size > max_size) |
924 | 0 | return size; |
925 | 0 | size_t capacity = max_size - fixed_size; |
926 | 0 | capacity = std::min(capacity, |
927 | 0 | static_cast<size_t>(std::numeric_limits<uint32_t>::max())); |
928 | 0 | if(capacity > record.length + kMaxValueGrowth) |
929 | 0 | capacity = record.length + kMaxValueGrowth; |
930 | | |
931 | | /* Relocate the tail before exposing the value to a general byte mutator. |
932 | | * `capacity` is bounded above and at least the old length, so the temporary |
933 | | * destination cannot overlap the active value or exceed `max_size`. */ |
934 | 0 | const size_t tail_size = size - record.end_offset(); |
935 | 0 | uint8_t *value = data + record.value_offset(); |
936 | 0 | std::memmove(value + capacity, data + record.end_offset(), tail_size); |
937 | 0 | const size_t new_length = |
938 | 0 | ByteMutate(value, record.length, capacity, random); |
939 | 0 | std::memmove(value + new_length, value + capacity, tail_size); |
940 | 0 | WriteU32(data + record.offset + 2, static_cast<uint32_t>(new_length)); |
941 | 0 | return fixed_size + new_length; |
942 | 0 | } |
943 | 0 | return size; |
944 | 0 | } |
945 | | |
946 | | /** |
947 | | * Bounded byte-level crossover for parents that cannot support record splicing. |
948 | | * Keeping malformed parents alive is important for framing coverage, while |
949 | | * limiting both slices guarantees the libFuzzer output contract. |
950 | | */ |
951 | | size_t LocalByteCrossOver(const uint8_t *data1, size_t size1, |
952 | | const uint8_t *data2, size_t size2, |
953 | | uint8_t *out, size_t max_out_size, |
954 | 0 | Random *random) { |
955 | 0 | if(!max_out_size) |
956 | 0 | return 0; |
957 | | |
958 | 0 | const size_t first_take = |
959 | 0 | std::min(random->Index(size1 + 1), max_out_size); |
960 | 0 | if(first_take) |
961 | 0 | std::memcpy(out, data1, first_take); |
962 | |
|
963 | 0 | const size_t second_start = random->Index(size2 + 1); |
964 | 0 | const size_t second_take = |
965 | 0 | std::min(size2 - second_start, max_out_size - first_take); |
966 | 0 | if(second_take) |
967 | 0 | std::memcpy(out + first_take, data2 + second_start, second_take); |
968 | |
|
969 | 0 | size_t out_size = first_take + second_take; |
970 | 0 | if(!out_size) { |
971 | 0 | if(size1) |
972 | 0 | out[out_size++] = data1[random->Index(size1)]; |
973 | 0 | else if(size2) |
974 | 0 | out[out_size++] = data2[random->Index(size2)]; |
975 | 0 | } |
976 | 0 | return out_size; |
977 | 0 | } |
978 | | |
979 | | /** |
980 | | * Appends one complete record under the mutator's duplicate policy. |
981 | | * Returning false only for capacity exhaustion lets crossover stop cleanly; |
982 | | * skipped last-write-wins duplicates do not prevent useful later records from |
983 | | * being inherited. |
984 | | */ |
985 | | bool AppendRecord(const uint8_t *source, const Record &record, |
986 | | uint8_t *out, size_t max_out_size, size_t *out_size, |
987 | 0 | std::array<uint8_t, kMaximumKnownType + 1> *seen) { |
988 | 0 | const TypeInfo info = GetTypeInfo(record.type); |
989 | 0 | if(!info.repeatable && (*seen)[record.type]) |
990 | 0 | return true; |
991 | 0 | if(record.total_size() > max_out_size - *out_size) |
992 | 0 | return false; |
993 | | |
994 | 0 | std::memcpy(out + *out_size, source + record.offset, record.total_size()); |
995 | 0 | *out_size += record.total_size(); |
996 | 0 | (*seen)[record.type] = 1; |
997 | 0 | return true; |
998 | 0 | } |
999 | | |
1000 | | /** Finds a validated record without reparsing or allocating. */ |
1001 | 0 | const Record *FindRecord(const ParsedInput &parsed, uint16_t type) { |
1002 | 0 | for(size_t i = 0; i < parsed.count; ++i) { |
1003 | 0 | if(parsed.records[i].type == type) |
1004 | 0 | return &parsed.records[i]; |
1005 | 0 | } |
1006 | 0 | return nullptr; |
1007 | 0 | } |
1008 | | |
1009 | | /** |
1010 | | * Appends a coverage-oriented default when neither parent has a prerequisite. |
1011 | | * This makes a structured child executable without weakening invalid-parent |
1012 | | * fallback or manufacturing arbitrary connection endpoints. |
1013 | | */ |
1014 | | bool AppendDefaultRecord(uint16_t type, uint8_t *out, size_t max_out_size, |
1015 | | size_t *out_size, |
1016 | 0 | std::array<uint8_t, kMaximumKnownType + 1> *seen) { |
1017 | 0 | const TypeInfo info = GetTypeInfo(type); |
1018 | 0 | const ByteView value = DefaultValue(type, info.kind); |
1019 | 0 | if(value.size > std::numeric_limits<uint32_t>::max() || |
1020 | 0 | kTlvHeaderSize + value.size > max_out_size - *out_size) |
1021 | 0 | return false; |
1022 | | |
1023 | 0 | WriteU16(out + *out_size, type); |
1024 | 0 | WriteU32(out + *out_size + 2, static_cast<uint32_t>(value.size)); |
1025 | 0 | if(value.size) |
1026 | 0 | std::memcpy(out + *out_size + kTlvHeaderSize, value.data, value.size); |
1027 | 0 | *out_size += kTlvHeaderSize + value.size; |
1028 | 0 | (*seen)[type] = 1; |
1029 | 0 | return true; |
1030 | 0 | } |
1031 | | |
1032 | | /** |
1033 | | * Inherits a required record from either parent, or synthesizes its default. |
1034 | | * Random parent choice retains genetic diversity when both provide the record; |
1035 | | * appending prerequisites first ensures optional records cannot consume their |
1036 | | * output capacity. |
1037 | | */ |
1038 | | bool AppendRequiredRecord( |
1039 | | uint16_t type, const uint8_t *data1, const ParsedInput &first, |
1040 | | const uint8_t *data2, const ParsedInput &second, uint8_t *out, |
1041 | | size_t max_out_size, size_t *out_size, |
1042 | 0 | std::array<uint8_t, kMaximumKnownType + 1> *seen, Random *random) { |
1043 | 0 | const Record *first_record = FindRecord(first, type); |
1044 | 0 | const Record *second_record = FindRecord(second, type); |
1045 | 0 | if(first_record && second_record && random->OneIn(2)) { |
1046 | 0 | std::swap(first_record, second_record); |
1047 | 0 | std::swap(data1, data2); |
1048 | 0 | } |
1049 | 0 | if(first_record) |
1050 | 0 | return AppendRecord(data1, *first_record, out, max_out_size, out_size, |
1051 | 0 | seen); |
1052 | 0 | if(second_record) |
1053 | 0 | return AppendRecord(data2, *second_record, out, max_out_size, out_size, |
1054 | 0 | seen); |
1055 | 0 | return AppendDefaultRecord(type, out, max_out_size, out_size, seen); |
1056 | 0 | } |
1057 | | |
1058 | | } // namespace |
1059 | | |
1060 | | /** Shares the routing policy with the execution-time legacy harness guard. */ |
1061 | 52.0k | const char *CanonicalRoutingValue(uint16_t type) { |
1062 | 52.0k | return CanonicalRoutingValueImpl(type); |
1063 | 52.0k | } |
1064 | | |
1065 | | /** |
1066 | | * Applies one reproducible mutation under the public structural/raw policy. |
1067 | | * One record-aware edit per structured call preserves locality for libFuzzer's |
1068 | | * evolutionary search while repairing malformed ancestors back into inputs |
1069 | | * that reach curl quickly. |
1070 | | */ |
1071 | | size_t Mutate(uint8_t *data, size_t size, size_t max_size, |
1072 | 0 | unsigned int seed) { |
1073 | 0 | size = std::min(size, max_size); |
1074 | 0 | Random random(static_cast<uint64_t>(seed) ^ |
1075 | 0 | (static_cast<uint64_t>(size) << 32)); |
1076 | 0 | size_t result = size; |
1077 | | |
1078 | | /* Structure-aware edits dominate because valid options drive curl coverage. |
1079 | | * A scheduled raw escape hatch still explores unknown IDs, corrupt lengths, |
1080 | | * and trailing-byte states that a validity-preserving mutator cannot make. |
1081 | | * Every lane rejoins below so a raw edit that happens to remain executable |
1082 | | * cannot smuggle a blocking hostname past routing repair. */ |
1083 | 0 | if(seed % kRawMutationPeriod == 0) { |
1084 | 0 | result = ByteMutate(data, size, max_size, &random); |
1085 | 0 | } |
1086 | 0 | else { |
1087 | 0 | ParsedInput parsed = Parse(data, size); |
1088 | 0 | if(!parsed.valid) { |
1089 | | /* Parse exposes the last trustworthy boundary. Retain that prefix, |
1090 | | * discard the corrupt record/tail, then insert a valid option so repair |
1091 | | * is itself a productive mutation. Oversized record lists are compacted |
1092 | | * as well; the periodic raw path separately preserves stress-input |
1093 | | * exploration. */ |
1094 | 0 | size = parsed.records_size; |
1095 | 0 | parsed = Parse(data, size); |
1096 | 0 | result = InsertRecord(data, size, max_size, parsed, &random); |
1097 | 0 | } |
1098 | 0 | else if(!HasType(parsed, 1) || |
1099 | 0 | (TargetNeedsResponse() && !HasType(parsed, 2)) || |
1100 | 0 | !parsed.count) { |
1101 | | /* A structurally valid stream may still lack enough transfer scaffolding |
1102 | | * to reach curl or feed its fake peer. Repair one missing prerequisite |
1103 | | * before dispatch so structured mutation converges on executable inputs. */ |
1104 | 0 | result = InsertRecord(data, size, max_size, parsed, &random); |
1105 | 0 | } |
1106 | 0 | else { |
1107 | | /* Dispatch exactly one local structural operation. Equal-ish weighting |
1108 | | * keeps growth, shrinkage, order, type, and payload exploration in |
1109 | | * circulation; duplicate falls back to insertion when no repeatable |
1110 | | * record is available, avoiding a wasted no-op mutation. */ |
1111 | 0 | switch(random.Index(7)) { |
1112 | 0 | case 0: |
1113 | 0 | result = MutateRecordValue(data, size, max_size, parsed, &random); |
1114 | 0 | break; |
1115 | 0 | case 1: |
1116 | 0 | result = InsertRecord(data, size, max_size, parsed, &random); |
1117 | 0 | break; |
1118 | 0 | case 2: |
1119 | 0 | result = EraseRecord(data, size, parsed, &random); |
1120 | 0 | break; |
1121 | 0 | case 3: |
1122 | 0 | result = DuplicateRecord(data, size, max_size, parsed, &random); |
1123 | 0 | if(result == size) |
1124 | 0 | result = InsertRecord(data, size, max_size, parsed, &random); |
1125 | 0 | break; |
1126 | 0 | case 4: |
1127 | 0 | result = ChangeRecordType(data, size, parsed, &random); |
1128 | 0 | break; |
1129 | 0 | case 5: |
1130 | 0 | result = SwapAdjacentRecords(data, size, parsed, &random); |
1131 | 0 | break; |
1132 | 0 | default: |
1133 | 0 | result = MutateRecordValue(data, size, max_size, parsed, &random); |
1134 | 0 | break; |
1135 | 0 | } |
1136 | 0 | } |
1137 | 0 | } |
1138 | | |
1139 | | /* Older corpus records and valid raw edits can both carry routing hostnames. |
1140 | | * Repair them only after the chosen mutation so no return path bypasses the |
1141 | | * resolver-stall policy. Malformed raw results retain their framing and |
1142 | | * continue to exercise the legacy TLV parser. */ |
1143 | 0 | return CanonicalizeRoutingValues(data, result, max_size); |
1144 | 0 | } |
1145 | | |
1146 | | /** |
1147 | | * Builds a useful child from record-aligned pieces of two valid parents. |
1148 | | * A prefix/suffix splice preserves coherent option groups better than arbitrary |
1149 | | * bytes, while the duplicate policy and required transfer records keep the |
1150 | | * child executable. Invalid parents deliberately retain byte crossover so |
1151 | | * malformed framing survives rather than being repaired away. |
1152 | | */ |
1153 | | size_t CrossOver(const uint8_t *data1, size_t size1, |
1154 | | const uint8_t *data2, size_t size2, |
1155 | | uint8_t *out, size_t max_out_size, |
1156 | 0 | unsigned int seed) { |
1157 | 0 | Random random(static_cast<uint64_t>(seed) ^ |
1158 | 0 | (static_cast<uint64_t>(size1) << 32) ^ size2); |
1159 | 0 | const ParsedInput first = Parse(data1, size1); |
1160 | 0 | const ParsedInput second = Parse(data2, size2); |
1161 | | /* Invalid inputs still have a valid prefix, but repairing them here would |
1162 | | * erase the malformed framing that made those parents interesting. Empty |
1163 | | * inputs have no record genetics, so retain bounded byte crossover. The |
1164 | | * common routing finalizer changes only a byte child that happens to become a |
1165 | | * complete executable TLV stream. */ |
1166 | 0 | if(!first.valid || !second.valid || !first.count || !second.count) |
1167 | 0 | return CanonicalizeRoutingValues( |
1168 | 0 | out, LocalByteCrossOver(data1, size1, data2, size2, out, |
1169 | 0 | max_out_size, &random), |
1170 | 0 | max_out_size); |
1171 | | |
1172 | 0 | std::array<uint8_t, kMaximumKnownType + 1> seen = {}; |
1173 | 0 | size_t out_size = 0; |
1174 | | |
1175 | | /* Reserve the transfer scaffold before optional inherited records. This |
1176 | | * prevents a splice from producing a syntactically valid child that stalls |
1177 | | * waiting for a missing fake-peer response. */ |
1178 | 0 | if(!AppendRequiredRecord(1, data1, first, data2, second, out, |
1179 | 0 | max_out_size, &out_size, &seen, &random) || |
1180 | 0 | (TargetNeedsResponse() && |
1181 | 0 | !AppendRequiredRecord(2, data1, first, data2, second, out, |
1182 | 0 | max_out_size, &out_size, &seen, &random))) |
1183 | 0 | return CanonicalizeRoutingValues( |
1184 | 0 | out, LocalByteCrossOver(data1, size1, data2, size2, out, |
1185 | 0 | max_out_size, &random), |
1186 | 0 | max_out_size); |
1187 | | |
1188 | 0 | const size_t first_end = 1 + random.Index(first.count); |
1189 | 0 | const size_t second_start = random.Index(second.count); |
1190 | | |
1191 | | /* Take a non-empty prefix from parent one and a suffix from parent two. |
1192 | | * AppendRecord copies whole records, suppresses duplicate scalar entries, |
1193 | | * and respects capacity, so each structured result validates. */ |
1194 | 0 | for(size_t i = 0; i < first_end; ++i) { |
1195 | 0 | if(!AppendRecord(data1, first.records[i], out, max_out_size, |
1196 | 0 | &out_size, &seen)) |
1197 | 0 | break; |
1198 | 0 | } |
1199 | 0 | for(size_t i = second_start; i < second.count; ++i) { |
1200 | 0 | if(!AppendRecord(data2, second.records[i], out, max_out_size, |
1201 | 0 | &out_size, &seen)) |
1202 | 0 | break; |
1203 | 0 | } |
1204 | |
|
1205 | 0 | if(!out_size) |
1206 | 0 | out_size = LocalByteCrossOver(data1, size1, data2, size2, out, |
1207 | 0 | max_out_size, &random); |
1208 | | |
1209 | | /* Crossover inherits payloads verbatim, so route safety must be restored |
1210 | | * after genetic selection rather than while copying one parent. */ |
1211 | 0 | return CanonicalizeRoutingValues(out, out_size, max_out_size); |
1212 | 0 | } |
1213 | | |
1214 | | /** Reuses Parse so validation cannot drift from edit preconditions. */ |
1215 | 0 | bool IsStructurallyValid(const uint8_t *data, size_t size) { |
1216 | 0 | return Parse(data, size).valid; |
1217 | 0 | } |
1218 | | |
1219 | | } // namespace legacy_tlv_mutator |
1220 | | |
1221 | | /** LibFuzzer ABI adapter; all policy remains testable in the namespaced API. */ |
1222 | | extern "C" size_t LLVMFuzzerCustomMutator(uint8_t *data, size_t size, |
1223 | | size_t max_size, |
1224 | 0 | unsigned int seed) { |
1225 | 0 | return legacy_tlv_mutator::Mutate(data, size, max_size, seed); |
1226 | 0 | } |
1227 | | |
1228 | | /** LibFuzzer ABI adapter for the record-aware crossover implementation. */ |
1229 | | extern "C" size_t LLVMFuzzerCustomCrossOver( |
1230 | | const uint8_t *data1, size_t size1, const uint8_t *data2, size_t size2, |
1231 | 0 | uint8_t *out, size_t max_out_size, unsigned int seed) { |
1232 | 0 | return legacy_tlv_mutator::CrossOver(data1, size1, data2, size2, out, |
1233 | 0 | max_out_size, seed); |
1234 | 0 | } |