Coverage Report

Created: 2026-10-28 07:17

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