Coverage Report

Created: 2023-06-07 06:03

/src/fuzzer-test-suite/libpng-1.2.56/png_mutator.h
Line
Count
Source (jump to first uncovered line)
1
// Copyright 2019 Google Inc. All Rights Reserved.
2
// Licensed under the Apache License, Version 2.0 (the "License");
3
#include <algorithm>
4
#include <iostream>
5
#include <sstream>
6
#include <string>
7
#include <vector>
8
#include <cstring>
9
#include <cassert>
10
#include <random>
11
12
#include <zlib.h>
13
14
// A simple class for parsing, serializing, and mutating an PNG file.
15
// https://en.wikipedia.org/wiki/Portable_Network_Graphics
16
// It is an example of a custom mutator for libFuzzer
17
// (https://llvm.org/docs/LibFuzzer.html) used for
18
// "structure-aware coverage-guided fuzzing".
19
//
20
// If you have a non structure-aware fuzz target for any API that handles
21
// PNG inputs, you can turn that fuzz target into a structure-aware one
22
// by defining PNG_MUTATOR_DEFINE_LIBFUZZER_CUSTOM_MUTATOR and then
23
// including this file.
24
class PngMutator {
25
  using V = std::vector<uint8_t>;
26
27
 public:
28
29
  // Parse the input stream as a PNG file,
30
  // put every chunk into its own vector,
31
  // uncompress chunk data when needed,
32
  // merge the IDAT chunks into one vector.
33
0
  PngMutator(std::istream &in) {
34
0
    ihdr_.resize(13);
35
0
    Read4(in);
36
0
    Read4(in); // Skip the 8-byte magic value.
37
    // read IHDR.
38
0
    if (ReadInteger(in) != 13) return;
39
0
    if (Read4(in) != Type("IHDR")) return;
40
    // Read 13 values.
41
0
    in.read((char*)ihdr_.data(), ihdr_.size());
42
0
    Read4(in);  // ignore CRC
43
0
    ssize_t idat_idx = -1;
44
45
0
    while (in) {
46
0
      uint32_t len = ReadInteger(in);
47
0
      uint32_t type = Read4(in);
48
0
      if (type == Type("IEND")) break;  // do nothing
49
0
      char chunk_name[5];
50
0
      memcpy(chunk_name, &type, 4);
51
0
      chunk_name[4] = 0;
52
0
      if (len > (1 << 20)) return;
53
0
      V v(len);
54
0
      in.read((char *)v.data(), len);
55
0
      Read4(in);  // ignore CRC
56
57
0
      if (type == Type("IDAT")) {
58
0
        if (idat_idx != -1)
59
0
          Append(&chunks_[idat_idx].v, v);
60
0
        else {
61
0
          idat_idx = chunks_.size();
62
0
          chunks_.push_back({type, v});
63
0
        }
64
0
      } else if (type == Type("iCCP")) {
65
0
        auto it = v.begin();
66
0
        while (it < v.end() && isprint(*it)) it++;
67
0
        if (it < v.end() && !*it) it++;
68
0
        if (it < v.end() && !*it) it++;
69
0
        v = V(it, v.end());
70
0
        auto uncompressed = Uncompress(v);
71
0
        chunks_.push_back({type, uncompressed});
72
0
        auto compressed = Compress(uncompressed);
73
0
      } else {
74
0
        chunks_.push_back({type, v});
75
0
      }
76
      //  std::cerr << "CHUNK: " << chunk_name << std::endl;
77
0
    }
78
0
    if (idat_idx != -1)
79
0
      chunks_[idat_idx].v = Uncompress(chunks_[idat_idx].v);
80
0
  }
81
82
  // Write back the PNG file.
83
0
  void Serialize(std::ostream &out) {
84
0
    const unsigned char header[] = {0x89, 0x50, 0x4e, 0x47,
85
0
                                    0x0d, 0x0a, 0x1a, 0x0a};
86
0
    out.write((const char*)header, sizeof(header));
87
0
    WriteChunk(out, "IHDR", ihdr_);
88
0
    for (auto &ch : chunks_) {
89
0
      if (ch.type == Type("iCCP")) {
90
0
        V v;
91
0
        v.push_back('x');  // assuming the iCCP name doesn't matter.
92
0
        v.push_back(0);
93
0
        v.push_back(0);
94
0
        auto compressed = Compress(ch.v);
95
0
        Append(&v, compressed);
96
0
        WriteChunk(out, ch.type, v);
97
0
      } else {
98
0
        WriteChunk(out, ch.type, ch.v);
99
0
      }
100
0
    }
101
102
0
    WriteChunk(out, "IEND", {});
103
0
  }
104
105
  // Raw byte array mutator, like that provided by libFuzzer.
106
  using Mutator = size_t (*)(uint8_t *Data, size_t Size, size_t MaxSize);
107
108
  // Mutate the in-memory representation of a PNG file.
109
  // Given the same Seed, the same mutation is performed.
110
0
  void Mutate(Mutator m, unsigned int Seed) {
111
0
    std::minstd_rand rnd(Seed);
112
0
    auto M = [&](V *v) {
113
0
      if (v->empty())
114
0
        v->resize(v->size() + 1 + rnd() % 256);
115
0
      v->resize(m(v->data(), v->size(), v->size()));
116
0
    };
117
0
    switch (rnd() % 6) {
118
      // Mutate IHDR.
119
0
      case 0:
120
0
        m(ihdr_.data(), ihdr_.size(), ihdr_.size());
121
0
        break;
122
      // Mutate some other chunk.
123
0
      case 1:
124
0
        if (!chunks_.empty()) M(&chunks_[rnd() % chunks_.size()].v);
125
0
        break;
126
      // Shuffle the chunks.
127
0
      case 2:
128
0
        std::shuffle(chunks_.begin(), chunks_.end(), rnd);
129
0
        break;
130
      // Delete a random chunk.
131
0
      case 3:
132
0
        if (!chunks_.empty())
133
0
         chunks_.erase(chunks_.begin() + rnd() % chunks_.size());
134
0
        break;
135
      // Insert a random chunk with one of the known types, or a random type.
136
0
      case 4: {
137
0
        static const char *types[] = {
138
0
            "IATx", "sTER", "hIST", "sPLT", "mkBF", "mkBS", "mkTS", "prVW",
139
0
            "oFFs", "iDOT", "zTXt", "mkBT", "acTL", "iTXt", "sBIT", "tIME",
140
0
            "iCCP", "vpAg", "tRNS", "cHRM", "PLTE", "bKGD", "gAMA", "sRGB",
141
0
            "pHYs", "fdAT", "fcTL", "tEXt", "IDAT",
142
0
            "pCAL", "sCAL", "eXIf",
143
0
            "fUZz", // special chunk for extra fuzzing hints.
144
0
        };
145
0
        static const size_t n_types = sizeof(types) / sizeof(types[0]);
146
0
        uint32_t type =
147
0
            (rnd() % 10 <= 8) ? Type(types[rnd() % n_types]) : (uint32_t)rnd();
148
0
        size_t len = rnd() % 256;
149
0
        if (type == Type("fUZz"))
150
0
          len = 16;
151
0
        V v(len);
152
0
        for (auto &b : v) b = rnd();
153
0
        size_t pos = rnd() % (chunks_.size() + 1);
154
0
        chunks_.insert(chunks_.begin() + pos, {type, v});
155
0
      } break;
156
      // Any more interesting mutations with a PNG file?
157
0
      case 5: {
158
0
        auto it = std::find_if(
159
0
            chunks_.begin(), chunks_.end(),
160
0
            [](const Chunk &ch) { return ch.type == Type("fUZz"); });
161
0
        if (it != chunks_.end())
162
0
          m(it->v.data(), it->v.size(), it->v.size());
163
0
      }
164
165
0
    }
166
0
  }
167
168
  // Takes a random chunk from p and inserts into *this.
169
0
  void CrossOver(const PngMutator &p, unsigned int Seed) {
170
0
    if (p.chunks_.empty()) return;
171
0
    std::minstd_rand rnd(Seed);
172
0
    size_t idx = rnd() % p.chunks_.size();
173
0
    auto &ch = p.chunks_[idx];
174
0
    size_t pos = rnd() % (chunks_.size() + 1);
175
0
    chunks_.insert(chunks_.begin() + pos, ch);
176
0
  }
177
178
 private:
179
0
  void Append(V *to, const V &from) {
180
0
    to->insert(to->end(), from.begin(), from.end());
181
0
  }
182
183
0
  uint32_t Read4(std::istream &in) {
184
0
    uint32_t res = 0;
185
0
    in.read((char *)&res, sizeof(res));
186
0
    return res;
187
0
  }
188
0
  uint32_t ReadInteger(std::istream &in) {
189
0
    return __builtin_bswap32(Read4(in));
190
0
  }
191
0
  static uint32_t Type(const char *tagname) {
192
0
    uint32_t res;
193
0
    assert(strlen(tagname) == 4);
194
0
    memcpy(&res, tagname, 4);
195
0
    return res;
196
0
  }
197
198
0
  void WriteInt(std::ostream &out, uint32_t x) {
199
0
    x = __builtin_bswap32(x);
200
0
    out.write((char *)&x, sizeof(x));
201
0
  }
202
203
  // Chunk is written as:
204
  //  * 4-byte length
205
  //  * 4-byte type
206
  //  * the data itself
207
  //  * 4-byte crc (of type and data)
208
  void WriteChunk(std::ostream &out, const char *type, const V &chunk,
209
0
                  bool compress = false) {
210
0
    V compressed;
211
0
    const V *v = &chunk;
212
0
    if (compress) {
213
0
      compressed = Compress(chunk);
214
0
      v = &compressed;
215
0
    }
216
0
    uint32_t len = v->size();
217
0
    uint32_t crc = crc32(0, (const unsigned char *)type, 4);
218
0
    if (v->size())
219
0
      crc = crc32(crc, (const unsigned char *)v->data(), v->size());
220
0
    WriteInt(out, len);
221
0
    out.write(type, 4);
222
0
    out.write((const char*)v->data(), v->size());
223
0
    WriteInt(out, crc);
224
0
  }
225
226
0
  void WriteChunk(std::ostream &out, uint32_t type, const V &chunk) {
227
0
    char type_s[5];
228
0
    memcpy(type_s, &type, 4);
229
0
    type_s[4] = 0;
230
0
    WriteChunk(out, type_s, chunk);
231
0
  }
232
233
0
  V Uncompress(const V &compressed) {
234
0
    V v;
235
0
    static const size_t kMaxBuffer = 1 << 28;
236
0
    for (size_t sz = compressed.size() * 4; sz < kMaxBuffer; sz *= 2) {
237
0
      v.resize(sz);
238
0
      unsigned long len = sz;
239
0
      auto res =
240
0
          uncompress(v.data(), &len, compressed.data(), compressed.size());
241
0
      if (res == Z_BUF_ERROR) continue;
242
0
      if (res != Z_OK) return {};
243
0
      v.resize(len);
244
0
      break;
245
0
    }
246
0
    return v;
247
0
  }
248
249
0
  V Compress(const V &uncompressed) {
250
0
    V v;
251
0
    static const size_t kMaxBuffer = 1 << 28;
252
0
    for (size_t sz = uncompressed.size(); sz < kMaxBuffer; sz *= 2) {
253
0
      v.resize(sz);
254
0
      unsigned long len = sz;
255
0
      auto res =
256
0
          compress(v.data(), &len, uncompressed.data(), uncompressed.size());
257
0
      if (res == Z_BUF_ERROR) continue;
258
0
      if (res != Z_OK) return {};
259
0
      v.resize(len);
260
0
      break;
261
0
    }
262
0
    return v;
263
0
  }
264
265
0
  void PrintHex(const V &v, size_t max_n) {
266
0
    for (size_t i = 0; i < max_n && i < v.size(); i++) {
267
0
      std::cerr << "0x" << std::hex << (unsigned)v[i] << " " << std::dec;
268
0
    }
269
0
    std::cerr << std::endl;
270
0
  }
271
272
  V ihdr_;
273
274
  struct Chunk {
275
    uint32_t type;
276
    V v;
277
  };
278
  std::vector<Chunk> chunks_;
279
};
280
281
282
#ifdef PNG_MUTATOR_DEFINE_LIBFUZZER_CUSTOM_MUTATOR
283
284
extern "C" size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize);
285
286
#if STANDALONE_TARGET
287
size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize) {
288
  assert(false && "LLVMFuzzerMutate should not be called from StandaloneFuzzTargetMain");
289
  return 0;
290
}
291
#endif
292
293
extern "C" size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size,
294
0
                                          size_t MaxSize, unsigned int Seed) {
295
0
  std::string s(reinterpret_cast<const char*>(Data), Size);
296
0
  std::stringstream in(s);
297
0
  std::stringstream out;
298
0
  PngMutator p(in);
299
0
  p.Mutate(LLVMFuzzerMutate, Seed);
300
0
  p.Serialize(out);
301
0
  const auto &str = out.str();
302
0
  if (str.size() > MaxSize) return Size;
303
0
  memcpy(Data, str.data(), str.size());
304
0
  return str.size();
305
0
}
306
307
extern "C" size_t LLVMFuzzerCustomCrossOver(const uint8_t *Data1, size_t Size1,
308
                                            const uint8_t *Data2, size_t Size2,
309
                                            uint8_t *Out, size_t MaxOutSize,
310
0
                                            unsigned int Seed) {
311
0
  std::stringstream in1(
312
0
      std::string(reinterpret_cast<const char *>(Data1), Size1));
313
0
  std::stringstream in2(
314
0
      std::string(reinterpret_cast<const char *>(Data2), Size2));
315
0
  PngMutator p1(in1);
316
0
  PngMutator p2(in2);
317
0
  p1.CrossOver(p2, Seed);
318
0
  std::stringstream out;
319
0
  p1.Serialize(out);
320
0
  const auto &str = out.str();
321
0
  if (str.size() > MaxOutSize) return 0;
322
0
  memcpy(Out, str.data(), str.size());
323
0
  return str.size();
324
0
}
325
326
#endif  // PNG_MUTATOR_DEFINE_LIBFUZZER_CUSTOM_MUTATOR