Coverage Report

Created: 2026-08-31 06:49

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