Coverage Report

Created: 2026-10-15 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl_fuzzer/full_coverage/legacy_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 "full_coverage/legacy_mutator.h"
8
9
#include "legacy_tlv_mutator.h"
10
11
#include <stdint.h>
12
13
#include <algorithm>
14
#include <array>
15
#include <cstring>
16
#include <vector>
17
18
extern "C" size_t LLVMFuzzerCustomMutator(uint8_t *data, size_t size,
19
                                          size_t max_size,
20
                                          unsigned int seed);
21
extern "C" size_t LLVMFuzzerCustomCrossOver(
22
    const uint8_t *data1, size_t size1, const uint8_t *data2, size_t size2,
23
    uint8_t *out, size_t max_out_size, unsigned int seed);
24
25
namespace full_coverage {
26
namespace {
27
28
constexpr size_t kCapacity = 8192;
29
constexpr unsigned int kSeedCount = 64;
30
using Input = std::vector<uint8_t>;
31
32
void AppendRecord(Input *input, uint16_t type, const uint8_t *value,
33
0
                  size_t size) {
34
0
  const uint32_t length = static_cast<uint32_t>(size);
35
0
  input->push_back(static_cast<uint8_t>(type >> 8));
36
0
  input->push_back(static_cast<uint8_t>(type));
37
0
  input->push_back(static_cast<uint8_t>(length >> 24));
38
0
  input->push_back(static_cast<uint8_t>(length >> 16));
39
0
  input->push_back(static_cast<uint8_t>(length >> 8));
40
0
  input->push_back(static_cast<uint8_t>(length));
41
0
  if(size)
42
0
    input->insert(input->end(), value, value + size);
43
0
}
44
45
0
void AppendString(Input *input, uint16_t type, const char *value) {
46
0
  AppendRecord(input, type, reinterpret_cast<const uint8_t *>(value),
47
0
               std::strlen(value));
48
0
}
49
50
0
std::vector<Input> MakeInputs() {
51
0
  Input transfer;
52
0
  AppendString(&transfer, 1, "http://127.0.0.1/");
53
0
  const Input url = transfer;
54
0
  AppendString(&transfer, 2,
55
0
               "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
56
57
  // One ordinary record from every value class, plus repeatable records and
58
  // canonical routing strings. These remain data for the mutator only.
59
0
  Input options = transfer;
60
0
  AppendString(&options, 6, "X-Coverage: 1");
61
0
  AppendString(&options, 6, "X-Coverage: 2");
62
0
  AppendString(&options, 8, "upload");
63
0
  AppendString(&options, 11, "coverage@example.test");
64
0
  Input mime;
65
0
  AppendString(&mime, 14, "field");
66
0
  AppendString(&mime, 15, "value");
67
0
  AppendRecord(&options, 13, mime.data(), mime.size());
68
0
  const uint8_t numeric[] = {0, 0, 0, 80};
69
0
  AppendRecord(&options, 200, numeric, sizeof(numeric));
70
0
  for(const uint16_t type : {53, 102, 105, 149})
71
0
    AppendString(&options, type,
72
0
                 legacy_tlv_mutator::CanonicalRoutingValue(type));
73
74
0
  Input header;
75
0
  AppendString(&header, 6, "X-Only: 1");
76
0
  Input response;
77
0
  AppendString(&response, 2, "HTTP/1.1 204 OK\r\n\r\n");
78
79
  // Short values exercise canonicalization with and without growth space.
80
0
  Input routing = transfer;
81
0
  for(const uint16_t type : {53, 102, 105, 149})
82
0
    AppendString(&routing, type, "");
83
84
0
  Input trailing = options;
85
0
  trailing.push_back(0);
86
87
  // Known framing and semantic rejection paths also enter prefix repair and
88
  // byte crossover. All storage is local; none of these records reaches curl.
89
0
  Input truncated = options;
90
0
  truncated.pop_back();
91
0
  Input duplicate = transfer;
92
0
  AppendString(&duplicate, 1, "http://127.0.0.1/");
93
0
  Input short_numeric = transfer;
94
0
  AppendRecord(&short_numeric, 200, numeric, 1);
95
0
  Input unknown = transfer;
96
0
  AppendString(&unknown, 0xffff, "");
97
0
  Input invalid_mime = transfer;
98
0
  AppendRecord(&invalid_mime, 13, header.data(), header.size());
99
0
  Input truncated_mime = transfer;
100
0
  const uint8_t short_part[] = {0, 15, 0, 0, 0, 2, 'v'};
101
0
  AppendRecord(&truncated_mime, 13, short_part, sizeof(short_part));
102
103
  // The parser has a 512-record work limit. Repeated empty headers exercise
104
  // acceptance at that limit and the bounded repair path immediately above it.
105
0
  Input at_record_limit;
106
0
  for(size_t index = 0; index < 512; ++index)
107
0
    AppendString(&at_record_limit, 6, "");
108
0
  Input above_record_limit = at_record_limit;
109
0
  AppendString(&above_record_limit, 6, "");
110
111
0
  return {{}, transfer, options, url, header, response, routing, trailing,
112
0
          truncated, duplicate, short_numeric, unknown, invalid_mime,
113
0
          truncated_mime, at_record_limit, above_record_limit};
114
0
}
115
116
// A volatile result keeps validation and immutable routing queries visible
117
// even when link-time optimization can see that this is a coverage exercise.
118
0
void ObserveValidation(const uint8_t *data, size_t size) {
119
0
  volatile bool valid = legacy_tlv_mutator::IsStructurallyValid(data, size);
120
0
  (void)valid;
121
0
}
122
123
}  // namespace
124
125
0
void ExerciseLegacyMutatorCoverage() {
126
0
  const std::vector<Input> inputs = MakeInputs();
127
0
  std::array<uint8_t, kCapacity> scratch = {};
128
0
  std::array<uint8_t, kCapacity> child = {};
129
130
  // Replay does not call libFuzzer's ABI adapters. Cover them explicitly as
131
  // well as their ordinary namespaced implementations below.
132
0
  const Input &first = inputs[1];
133
0
  const Input &second = inputs[2];
134
0
  std::copy(first.begin(), first.end(), scratch.begin());
135
0
  const size_t mutated_size = LLVMFuzzerCustomMutator(
136
0
      scratch.data(), first.size(), scratch.size(), 1);
137
0
  ObserveValidation(scratch.data(), mutated_size);
138
0
  const size_t crossed_size = LLVMFuzzerCustomCrossOver(
139
0
      first.data(), first.size(), second.data(), second.size(), child.data(),
140
0
      child.size(), 1);
141
0
  ObserveValidation(child.data(), crossed_size);
142
143
  // Empty parents still use real storage. Reversing the existing empty-first
144
  // case exercises the one-byte fallback from the first parent; two empty
145
  // parents have no byte to inherit even when output capacity is available.
146
0
  const Input &header = inputs[4];
147
0
  const size_t reversed_size = LLVMFuzzerCustomCrossOver(
148
0
      header.data(), header.size(), scratch.data(), 0, child.data(), 1, 7);
149
0
  ObserveValidation(child.data(), reversed_size);
150
0
  const size_t empty_size = LLVMFuzzerCustomCrossOver(
151
0
      scratch.data(), 0, scratch.data(), 0, child.data(), 1, 7);
152
0
  ObserveValidation(child.data(), empty_size);
153
154
  // These framed IDs are explicitly excluded by the top-level type policy.
155
0
  for(const uint16_t type : {15, 51, 212, 322}) {
156
0
    Input rejected;
157
0
    AppendRecord(&rejected, type, scratch.data(), 0);
158
0
    ObserveValidation(rejected.data(), rejected.size());
159
0
  }
160
161
  // With exact capacity, a nonempty short routing value must shrink to the
162
  // disabled empty value when its canonical spelling cannot fit. Seed 8
163
  // selects duplication; scalar-only records leave no room for insertion.
164
0
  Input short_routing = first;
165
0
  AppendString(&short_routing, 53, "1");
166
0
  std::copy(short_routing.begin(), short_routing.end(), scratch.begin());
167
0
  const size_t routing_size = legacy_tlv_mutator::Mutate(
168
0
      scratch.data(), short_routing.size(), short_routing.size(), 8);
169
0
  ObserveValidation(scratch.data(), routing_size);
170
171
0
  for(const uint16_t type : {53, 102, 105, 129, 149, 0xffff}) {
172
0
    const char *volatile routing =
173
0
        legacy_tlv_mutator::CanonicalRoutingValue(type);
174
0
    (void)routing;
175
0
  }
176
177
0
  for(size_t index = 0; index < inputs.size(); ++index) {
178
0
    const Input &input = inputs[index];
179
0
    const Input &other = inputs[(index + 1) % inputs.size()];
180
0
    ObserveValidation(input.data(), input.size());
181
182
    // Zero/tiny capacities exercise the documented no-op/fallback behavior;
183
    // exact capacity and spare capacity distinguish replacement from growth.
184
0
    const size_t capacities[] = {0, 1, 6, 96, input.size(), kCapacity};
185
0
    for(const size_t capacity : capacities) {
186
0
      for(unsigned int seed = 0; seed < kSeedCount; ++seed) {
187
0
        std::copy(input.begin(), input.end(), scratch.begin());
188
0
        const size_t size = legacy_tlv_mutator::Mutate(
189
0
            scratch.data(), input.size(), capacity, seed);
190
0
        ObserveValidation(scratch.data(), size);
191
192
0
        const size_t child_size = legacy_tlv_mutator::CrossOver(
193
0
            input.data(), input.size(), other.data(), other.size(),
194
0
            child.data(), capacity, seed);
195
0
        ObserveValidation(child.data(), child_size);
196
0
      }
197
0
    }
198
0
  }
199
0
}
200
201
}  // namespace full_coverage