Coverage Report

Created: 2026-09-14 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}