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