Coverage Report

Created: 2025-07-11 07:06

/src/nss/fuzz/targets/lib/tls/mutators.cc
Line
Count
Source (jump to first uncovered line)
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this
3
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
#include "mutators.h"
6
7
#include <algorithm>
8
#include <cassert>
9
#include <cstddef>
10
#include <cstdint>
11
#include <random>
12
#include <vector>
13
14
#include "tls_parser.h"
15
16
// Helper class to simplify TLS record manipulation.
17
class Record {
18
 public:
19
  static std::unique_ptr<Record> Create(const uint8_t *data, size_t size,
20
0
                                        size_t remaining) {
21
0
    return std::unique_ptr<Record>(new Record(data, size, remaining));
22
0
  }
23
24
0
  void insert_before(const std::unique_ptr<Record> &other) {
25
0
    assert(data_ && size_ > 0);
26
27
    // Copy data in case other == this.
28
0
    std::vector<uint8_t> buf(size_);
29
0
    memcpy(buf.data(), data_, size_);
30
31
0
    uint8_t *dest = const_cast<uint8_t *>(other->data());
32
    // Make room for the record we want to insert.
33
0
    memmove(dest + size_, other->data(), other->size() + other->remaining());
34
    // Insert the record.
35
0
    memcpy(dest, buf.data(), size_);
36
0
  }
37
38
0
  void truncate(size_t length) {
39
0
    assert(length >= 5 + EXTRA_HEADER_BYTES);
40
0
    uint8_t *dest = const_cast<uint8_t *>(data_);
41
0
    size_t l = length - (5 + EXTRA_HEADER_BYTES);
42
0
    dest[3] = (l >> 8) & 0xff;
43
0
    dest[4] = l & 0xff;
44
0
    memmove(dest + length, data_ + size_, remaining_);
45
0
  }
46
47
0
  void drop() {
48
0
    uint8_t *dest = const_cast<uint8_t *>(data_);
49
0
    memmove(dest, data_ + size_, remaining_);
50
0
  }
51
52
0
  const uint8_t *data() { return data_; }
53
0
  size_t remaining() { return remaining_; }
54
0
  size_t size() { return size_; }
55
56
 private:
57
  Record(const uint8_t *data, size_t size, size_t remaining)
58
0
      : data_(data), remaining_(remaining), size_(size) {}
59
60
  const uint8_t *data_;
61
  size_t remaining_;
62
  size_t size_;
63
};
64
65
// Parse records contained in a given TLS transcript.
66
std::vector<std::unique_ptr<Record>> ParseRecords(const uint8_t *data,
67
0
                                                  size_t size) {
68
0
  std::vector<std::unique_ptr<Record>> records;
69
0
  nss_test::TlsParser parser(data, size);
70
71
0
  while (parser.remaining()) {
72
0
    size_t offset = parser.consumed();
73
74
    // Skip type, version, and DTLS seqnums.
75
0
    if (!parser.Skip(3 + EXTRA_HEADER_BYTES)) {
76
0
      break;
77
0
    }
78
79
0
    nss_test::DataBuffer fragment;
80
0
    if (!parser.ReadVariable(&fragment, 2)) {
81
0
      break;
82
0
    }
83
84
0
    records.push_back(Record::Create(data + offset,
85
0
                                     fragment.len() + 5 + EXTRA_HEADER_BYTES,
86
0
                                     parser.remaining()));
87
0
  }
88
89
0
  return records;
90
0
}
91
92
namespace TlsMutators {
93
94
// Mutator that drops whole TLS records.
95
size_t DropRecord(uint8_t *data, size_t size, size_t maxSize,
96
0
                  unsigned int seed) {
97
0
  std::mt19937 rng(seed);
98
99
  // Find TLS records in the corpus.
100
0
  auto records = ParseRecords(data, size);
101
0
  if (records.empty()) {
102
0
    return 0;
103
0
  }
104
105
  // Pick a record to drop at random.
106
0
  std::uniform_int_distribution<size_t> dist(0, records.size() - 1);
107
0
  auto &rec = records.at(dist(rng));
108
109
  // Drop the record.
110
0
  rec->drop();
111
112
  // Return the new final size.
113
0
  return size - rec->size();
114
0
}
115
116
// Mutator that shuffles TLS records in a transcript.
117
size_t ShuffleRecords(uint8_t *data, size_t size, size_t maxSize,
118
0
                      unsigned int seed) {
119
0
  std::mt19937 rng(seed);
120
121
  // Find TLS records in the corpus.
122
0
  auto records = ParseRecords(data, size);
123
0
  if (records.empty()) {
124
0
    return 0;
125
0
  }
126
127
  // Store the original corpus.
128
0
  std::vector<uint8_t> buf(size);
129
0
  memcpy(buf.data(), data, size);
130
131
  // Find offset of first record in target buffer.
132
0
  uint8_t *dest = const_cast<uint8_t *>(records.at(0)->data());
133
134
  // Shuffle record order.
135
0
  std::shuffle(records.begin(), records.end(), rng);
136
137
  // Write records to their new positions.
138
0
  for (auto &rec : records) {
139
0
    memcpy(dest, buf.data() + (rec->data() - data), rec->size());
140
0
    dest += rec->size();
141
0
  }
142
143
  // Final size hasn't changed.
144
0
  return size;
145
0
}
146
147
// Mutator that duplicates a single TLS record and randomly inserts it.
148
size_t DuplicateRecord(uint8_t *data, size_t size, size_t maxSize,
149
0
                       unsigned int seed) {
150
0
  std::mt19937 rng(seed);
151
152
  // Find TLS records in the corpus.
153
0
  const auto records = ParseRecords(data, size);
154
0
  if (records.empty()) {
155
0
    return 0;
156
0
  }
157
158
  // Pick a record to duplicate at random.
159
0
  std::uniform_int_distribution<size_t> dist(0, records.size() - 1);
160
0
  auto &rec = records.at(dist(rng));
161
0
  if (size + rec->size() > maxSize) {
162
0
    return 0;
163
0
  }
164
165
  // Insert before random record.
166
0
  rec->insert_before(records.at(dist(rng)));
167
168
  // Return the new final size.
169
0
  return size + rec->size();
170
0
}
171
172
// Mutator that truncates a TLS record.
173
size_t TruncateRecord(uint8_t *data, size_t size, size_t maxSize,
174
0
                      unsigned int seed) {
175
0
  std::mt19937 rng(seed);
176
177
  // Find TLS records in the corpus.
178
0
  const auto records = ParseRecords(data, size);
179
0
  if (records.empty()) {
180
0
    return 0;
181
0
  }
182
183
  // Pick a record to truncate at random.
184
0
  std::uniform_int_distribution<size_t> dist(0, records.size() - 1);
185
0
  auto &rec = records.at(dist(rng));
186
187
  // Need a record with data.
188
0
  if (rec->size() <= 5 + EXTRA_HEADER_BYTES) {
189
0
    return 0;
190
0
  }
191
192
  // Truncate.
193
0
  std::uniform_int_distribution<size_t> dist2(5 + EXTRA_HEADER_BYTES,
194
0
                                              rec->size() - 1);
195
0
  size_t new_length = dist2(rng);
196
0
  rec->truncate(new_length);
197
198
  // Return the new final size.
199
0
  return size + new_length - rec->size();
200
0
}
201
202
// Mutator that splits a TLS record in two.
203
size_t FragmentRecord(uint8_t *data, size_t size, size_t maxSize,
204
0
                      unsigned int seed) {
205
0
  std::mt19937 rng(seed);
206
207
  // We can't deal with DTLS yet.
208
0
  if (EXTRA_HEADER_BYTES > 0) {
209
0
    return 0;
210
0
  }
211
212
0
  if (size + 5 > maxSize) {
213
0
    return 0;
214
0
  }
215
216
  // Find TLS records in the corpus.
217
0
  const auto records = ParseRecords(data, size);
218
0
  if (records.empty()) {
219
0
    return 0;
220
0
  }
221
222
  // Pick a record to fragment at random.
223
0
  std::uniform_int_distribution<size_t> rand_record(0, records.size() - 1);
224
0
  auto &rec = records.at(rand_record(rng));
225
0
  uint8_t *rdata = const_cast<uint8_t *>(rec->data());
226
0
  size_t length = rec->size();
227
0
  size_t content_length = length - 5;
228
229
0
  if (content_length < 2) {
230
0
    return 0;
231
0
  }
232
233
  // Assign a new length to the first fragment.
234
0
  std::uniform_int_distribution<size_t> rand_size(1, content_length - 1);
235
0
  size_t first_length = rand_size(rng);
236
0
  size_t second_length = content_length - first_length;
237
0
  rdata[3] = (first_length >> 8) & 0xff;
238
0
  rdata[4] = first_length & 0xff;
239
0
  uint8_t *second_record = rdata + 5 + first_length;
240
241
  // Make room for the header of the second record.
242
0
  memmove(second_record + 5, second_record,
243
0
          rec->remaining() + content_length - first_length);
244
245
  // Write second header.
246
0
  memcpy(second_record, rdata, 3);
247
0
  second_record[3] = (second_length >> 8) & 0xff;
248
0
  second_record[4] = second_length & 0xff;
249
250
0
  return size + 5;
251
0
}
252
253
// Cross-over function that merges and shuffles two transcripts.
254
size_t CrossOver(const uint8_t *data1, size_t size1, const uint8_t *data2,
255
                 size_t size2, uint8_t *out, size_t maxOutSize,
256
0
                 unsigned int seed) {
257
0
  std::mt19937 rng(seed);
258
259
  // Find TLS records in the corpus.
260
0
  auto records1 = ParseRecords(data1, size1);
261
0
  if (records1.empty()) {
262
0
    return 0;
263
0
  }
264
265
0
  {  // Merge the two vectors.
266
0
    auto records2 = ParseRecords(data2, size2);
267
0
    if (records2.empty()) {
268
0
      return 0;
269
0
    }
270
0
    std::move(records2.begin(), records2.end(), std::back_inserter(records1));
271
0
  }
272
273
  // Shuffle record order.
274
0
  std::shuffle(records1.begin(), records1.end(), rng);
275
276
0
  size_t total = 0;
277
0
  for (auto &rec : records1) {
278
0
    size_t length = rec->size();
279
0
    if (total + length > maxOutSize) {
280
0
      break;
281
0
    }
282
283
    // Write record to its new position.
284
0
    memcpy(out + total, rec->data(), length);
285
0
    total += length;
286
0
  }
287
288
0
  return total;
289
0
}
290
291
}  // namespace TlsMutators