• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
PngMutator(std::istream & in)33   PngMutator(std::istream &in) {
34     ihdr_.resize(13);
35     Read4(in);
36     Read4(in); // Skip the 8-byte magic value.
37     // read IHDR.
38     if (ReadInteger(in) != 13) return;
39     if (Read4(in) != Type("IHDR")) return;
40     // Read 13 values.
41     in.read((char*)ihdr_.data(), ihdr_.size());
42     Read4(in);  // ignore CRC
43     ssize_t idat_idx = -1;
44 
45     while (in) {
46       uint32_t len = ReadInteger(in);
47       uint32_t type = Read4(in);
48       if (type == Type("IEND")) break;  // do nothing
49       char chunk_name[5];
50       memcpy(chunk_name, &type, 4);
51       chunk_name[4] = 0;
52       if (len > (1 << 20)) return;
53       V v(len);
54       in.read((char *)v.data(), len);
55       Read4(in);  // ignore CRC
56 
57       if (type == Type("IDAT")) {
58         if (idat_idx != -1)
59           Append(&chunks_[idat_idx].v, v);
60         else {
61           idat_idx = chunks_.size();
62           chunks_.push_back({type, v});
63         }
64       } else if (type == Type("iCCP")) {
65         auto it = v.begin();
66         while (it < v.end() && isprint(*it)) it++;
67         if (it < v.end() && !*it) it++;
68         if (it < v.end() && !*it) it++;
69         v = V(it, v.end());
70         auto uncompressed = Uncompress(v);
71         chunks_.push_back({type, uncompressed});
72         auto compressed = Compress(uncompressed);
73       } else {
74         chunks_.push_back({type, v});
75       }
76       //  std::cerr << "CHUNK: " << chunk_name << std::endl;
77     }
78     if (idat_idx != -1)
79       chunks_[idat_idx].v = Uncompress(chunks_[idat_idx].v);
80   }
81 
82   // Write back the PNG file.
Serialize(std::ostream & out)83   void Serialize(std::ostream &out) {
84     const unsigned char header[] = {0x89, 0x50, 0x4e, 0x47,
85                                     0x0d, 0x0a, 0x1a, 0x0a};
86     out.write((const char*)header, sizeof(header));
87     WriteChunk(out, "IHDR", ihdr_);
88     for (auto &ch : chunks_) {
89       if (ch.type == Type("iCCP")) {
90         V v;
91         v.push_back('x');  // assuming the iCCP name doesn't matter.
92         v.push_back(0);
93         v.push_back(0);
94         auto compressed = Compress(ch.v);
95         Append(&v, compressed);
96         WriteChunk(out, ch.type, v);
97       } else {
98         WriteChunk(out, ch.type, ch.v);
99       }
100     }
101 
102     WriteChunk(out, "IEND", {});
103   }
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.
Mutate(Mutator m,unsigned int Seed)110   void Mutate(Mutator m, unsigned int Seed) {
111     std::minstd_rand rnd(Seed);
112     auto M = [&](V *v) {
113       if (v->empty())
114         v->resize(v->size() + 1 + rnd() % 256);
115       v->resize(m(v->data(), v->size(), v->size()));
116     };
117     switch (rnd() % 6) {
118       // Mutate IHDR.
119       case 0:
120         m(ihdr_.data(), ihdr_.size(), ihdr_.size());
121         break;
122       // Mutate some other chunk.
123       case 1:
124         if (!chunks_.empty()) M(&chunks_[rnd() % chunks_.size()].v);
125         break;
126       // Shuffle the chunks.
127       case 2:
128         std::shuffle(chunks_.begin(), chunks_.end(), rnd);
129         break;
130       // Delete a random chunk.
131       case 3:
132         if (!chunks_.empty())
133          chunks_.erase(chunks_.begin() + rnd() % chunks_.size());
134         break;
135       // Insert a random chunk with one of the known types, or a random type.
136       case 4: {
137         static const char *types[] = {
138             "IATx", "sTER", "hIST", "sPLT", "mkBF", "mkBS", "mkTS", "prVW",
139             "oFFs", "iDOT", "zTXt", "mkBT", "acTL", "iTXt", "sBIT", "tIME",
140             "iCCP", "vpAg", "tRNS", "cHRM", "PLTE", "bKGD", "gAMA", "sRGB",
141             "pHYs", "fdAT", "fcTL", "tEXt", "IDAT",
142             "pCAL", "sCAL", "eXIf",
143             "fUZz", // special chunk for extra fuzzing hints.
144         };
145         static const size_t n_types = sizeof(types) / sizeof(types[0]);
146         uint32_t type =
147             (rnd() % 10 <= 8) ? Type(types[rnd() % n_types]) : (uint32_t)rnd();
148         size_t len = rnd() % 256;
149         if (type == Type("fUZz"))
150           len = 16;
151         V v(len);
152         for (auto &b : v) b = rnd();
153         size_t pos = rnd() % (chunks_.size() + 1);
154         chunks_.insert(chunks_.begin() + pos, {type, v});
155       } break;
156       // Any more interesting mutations with a PNG file?
157       case 5: {
158         auto it = std::find_if(
159             chunks_.begin(), chunks_.end(),
160             [](const Chunk &ch) { return ch.type == Type("fUZz"); });
161         if (it != chunks_.end())
162           m(it->v.data(), it->v.size(), it->v.size());
163       }
164 
165     }
166   }
167 
168   // Takes a random chunk from p and inserts into *this.
CrossOver(const PngMutator & p,unsigned int Seed)169   void CrossOver(const PngMutator &p, unsigned int Seed) {
170     if (p.chunks_.empty()) return;
171     std::minstd_rand rnd(Seed);
172     size_t idx = rnd() % p.chunks_.size();
173     auto &ch = p.chunks_[idx];
174     size_t pos = rnd() % (chunks_.size() + 1);
175     chunks_.insert(chunks_.begin() + pos, ch);
176   }
177 
178  private:
Append(V * to,const V & from)179   void Append(V *to, const V &from) {
180     to->insert(to->end(), from.begin(), from.end());
181   }
182 
Read4(std::istream & in)183   uint32_t Read4(std::istream &in) {
184     uint32_t res = 0;
185     in.read((char *)&res, sizeof(res));
186     return res;
187   }
ReadInteger(std::istream & in)188   uint32_t ReadInteger(std::istream &in) {
189     return __builtin_bswap32(Read4(in));
190   }
Type(const char * tagname)191   static uint32_t Type(const char *tagname) {
192     uint32_t res;
193     assert(strlen(tagname) == 4);
194     memcpy(&res, tagname, 4);
195     return res;
196   }
197 
WriteInt(std::ostream & out,uint32_t x)198   void WriteInt(std::ostream &out, uint32_t x) {
199     x = __builtin_bswap32(x);
200     out.write((char *)&x, sizeof(x));
201   }
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                   bool compress = false) {
210     V compressed;
211     const V *v = &chunk;
212     if (compress) {
213       compressed = Compress(chunk);
214       v = &compressed;
215     }
216     uint32_t len = v->size();
217     uint32_t crc = crc32(0, (const unsigned char *)type, 4);
218     if (v->size())
219       crc = crc32(crc, (const unsigned char *)v->data(), v->size());
220     WriteInt(out, len);
221     out.write(type, 4);
222     out.write((const char*)v->data(), v->size());
223     WriteInt(out, crc);
224   }
225 
WriteChunk(std::ostream & out,uint32_t type,const V & chunk)226   void WriteChunk(std::ostream &out, uint32_t type, const V &chunk) {
227     char type_s[5];
228     memcpy(type_s, &type, 4);
229     type_s[4] = 0;
230     WriteChunk(out, type_s, chunk);
231   }
232 
Uncompress(const V & compressed)233   V Uncompress(const V &compressed) {
234     V v;
235     static const size_t kMaxBuffer = 1 << 28;
236     for (size_t sz = compressed.size() * 4; sz < kMaxBuffer; sz *= 2) {
237       v.resize(sz);
238       unsigned long len = sz;
239       auto res =
240           uncompress(v.data(), &len, compressed.data(), compressed.size());
241       if (res == Z_BUF_ERROR) continue;
242       if (res != Z_OK) return {};
243       v.resize(len);
244       break;
245     }
246     return v;
247   }
248 
Compress(const V & uncompressed)249   V Compress(const V &uncompressed) {
250     V v;
251     static const size_t kMaxBuffer = 1 << 28;
252     for (size_t sz = uncompressed.size(); sz < kMaxBuffer; sz *= 2) {
253       v.resize(sz);
254       unsigned long len = sz;
255       auto res =
256           compress(v.data(), &len, uncompressed.data(), uncompressed.size());
257       if (res == Z_BUF_ERROR) continue;
258       if (res != Z_OK) return {};
259       v.resize(len);
260       break;
261     }
262     return v;
263   }
264 
PrintHex(const V & v,size_t max_n)265   void PrintHex(const V &v, size_t max_n) {
266     for (size_t i = 0; i < max_n && i < v.size(); i++) {
267       std::cerr << "0x" << std::hex << (unsigned)v[i] << " " << std::dec;
268     }
269     std::cerr << std::endl;
270   }
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
LLVMFuzzerMutate(uint8_t * Data,size_t Size,size_t MaxSize)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 
LLVMFuzzerCustomMutator(uint8_t * Data,size_t Size,size_t MaxSize,unsigned int Seed)293 extern "C" size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size,
294                                           size_t MaxSize, unsigned int Seed) {
295   std::string s(reinterpret_cast<const char*>(Data), Size);
296   std::stringstream in(s);
297   std::stringstream out;
298   PngMutator p(in);
299   p.Mutate(LLVMFuzzerMutate, Seed);
300   p.Serialize(out);
301   const auto &str = out.str();
302   if (str.size() > MaxSize) return Size;
303   memcpy(Data, str.data(), str.size());
304   return str.size();
305 }
306 
LLVMFuzzerCustomCrossOver(const uint8_t * Data1,size_t Size1,const uint8_t * Data2,size_t Size2,uint8_t * Out,size_t MaxOutSize,unsigned int Seed)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                                             unsigned int Seed) {
311   std::stringstream in1(
312       std::string(reinterpret_cast<const char *>(Data1), Size1));
313   std::stringstream in2(
314       std::string(reinterpret_cast<const char *>(Data2), Size2));
315   PngMutator p1(in1);
316   PngMutator p2(in2);
317   p1.CrossOver(p2, Seed);
318   std::stringstream out;
319   p1.Serialize(out);
320   const auto &str = out.str();
321   if (str.size() > MaxOutSize) return 0;
322   memcpy(Out, str.data(), str.size());
323   return str.size();
324 }
325 
326 #endif  // PNG_MUTATOR_DEFINE_LIBFUZZER_CUSTOM_MUTATOR
327