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