Use structure aware fuzzing for pngs

Bug: 142252770
Bug: 169137236
Bug: 169139756
Test: this

Features built into the PNG format slow down fuzzing (e.g. CRC, encoded
lengths) [1]. Use structure aware fuzzing for PNG to do a better search
through the input space. Add png_mutator.h (unchanged from [2] under the
Apache 2.0 license). Split imagedecoder_fuzzer into two targets: one for
PNGs (since the mutator converts all inputs into PNG), and one for the
other formats.

Move fuzz_imagedecoder.cpp and the corpus/ into the new fuzz/ directory,
to keep fuzzing separate from the libjnigraphics library. Remove
png_test.png from the corpus; structure aware fuzzing will do a better
job of fuzzing PNGs, and the generic one should focus on other formats.

[1] https://github.com/google/fuzzing/blob/master/docs/structure-aware-fuzzing.md
[2] https://github.com/google/fuzzer-test-suite/blob/51356066dc70c43c9da0ad98e887684a0394860f/libpng-1.2.56/png_mutator.h#L1

Change-Id: I8aebb0d0abfa18793334f2d217b28a51d096123a
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 3751564..d464587 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -74,9 +74,9 @@
     unversioned_until: "current",
 }
 
-cc_fuzz {
-    name: "imagedecoder_fuzzer",
-    srcs: ["fuzz_imagedecoder.cpp"],
+cc_defaults {
+    name: "imagedecoder_fuzzer_defaults",
+    srcs: ["fuzz/fuzz_imagedecoder.cpp"],
     header_libs: ["jni_headers"],
     shared_libs: [
         "libbinder",
@@ -97,6 +97,22 @@
             "allocator_may_return_null = 1",
         ],
     },
-    corpus: ["corpus/*"],
     host_supported: true,
 }
+
+cc_fuzz {
+    name: "imagedecoder_fuzzer",
+    defaults: ["imagedecoder_fuzzer_defaults"],
+    corpus: ["fuzz/corpus/*"],
+}
+
+cc_fuzz {
+    name: "imagedecoder_png_fuzzer",
+    defaults: ["imagedecoder_fuzzer_defaults"],
+    shared_libs: [
+        "libz",
+    ],
+    cflags: [
+        "-DPNG_MUTATOR_DEFINE_LIBFUZZER_CUSTOM_MUTATOR",
+    ],
+}
diff --git a/native/graphics/jni/corpus/png_test.png b/native/graphics/jni/corpus/png_test.png
deleted file mode 100644
index 5230051..0000000
--- a/native/graphics/jni/corpus/png_test.png
+++ /dev/null
Binary files differ
diff --git a/native/graphics/jni/corpus/baseline_jpeg.jpg b/native/graphics/jni/fuzz/corpus/baseline_jpeg.jpg
similarity index 100%
rename from native/graphics/jni/corpus/baseline_jpeg.jpg
rename to native/graphics/jni/fuzz/corpus/baseline_jpeg.jpg
Binary files differ
diff --git a/native/graphics/jni/corpus/color_wheel.ico b/native/graphics/jni/fuzz/corpus/color_wheel.ico
similarity index 100%
rename from native/graphics/jni/corpus/color_wheel.ico
rename to native/graphics/jni/fuzz/corpus/color_wheel.ico
Binary files differ
diff --git a/native/graphics/jni/corpus/gif_test.gif b/native/graphics/jni/fuzz/corpus/gif_test.gif
similarity index 100%
rename from native/graphics/jni/corpus/gif_test.gif
rename to native/graphics/jni/fuzz/corpus/gif_test.gif
Binary files differ
diff --git a/native/graphics/jni/corpus/google_chrome.ico b/native/graphics/jni/fuzz/corpus/google_chrome.ico
similarity index 100%
rename from native/graphics/jni/corpus/google_chrome.ico
rename to native/graphics/jni/fuzz/corpus/google_chrome.ico
Binary files differ
diff --git a/native/graphics/jni/corpus/heifwriter_input.heic b/native/graphics/jni/fuzz/corpus/heifwriter_input.heic
similarity index 100%
rename from native/graphics/jni/corpus/heifwriter_input.heic
rename to native/graphics/jni/fuzz/corpus/heifwriter_input.heic
Binary files differ
diff --git a/native/graphics/jni/corpus/mandrill.wbmp b/native/graphics/jni/fuzz/corpus/mandrill.wbmp
similarity index 100%
rename from native/graphics/jni/corpus/mandrill.wbmp
rename to native/graphics/jni/fuzz/corpus/mandrill.wbmp
Binary files differ
diff --git a/native/graphics/jni/corpus/progressive_jpeg.jpg b/native/graphics/jni/fuzz/corpus/progressive_jpeg.jpg
similarity index 100%
rename from native/graphics/jni/corpus/progressive_jpeg.jpg
rename to native/graphics/jni/fuzz/corpus/progressive_jpeg.jpg
Binary files differ
diff --git a/native/graphics/jni/corpus/sample_1mp.dng b/native/graphics/jni/fuzz/corpus/sample_1mp.dng
similarity index 100%
rename from native/graphics/jni/corpus/sample_1mp.dng
rename to native/graphics/jni/fuzz/corpus/sample_1mp.dng
Binary files differ
diff --git a/native/graphics/jni/corpus/sample_arw.arw b/native/graphics/jni/fuzz/corpus/sample_arw.arw
similarity index 100%
rename from native/graphics/jni/corpus/sample_arw.arw
rename to native/graphics/jni/fuzz/corpus/sample_arw.arw
Binary files differ
diff --git a/native/graphics/jni/corpus/sample_cr2.cr2 b/native/graphics/jni/fuzz/corpus/sample_cr2.cr2
similarity index 100%
rename from native/graphics/jni/corpus/sample_cr2.cr2
rename to native/graphics/jni/fuzz/corpus/sample_cr2.cr2
Binary files differ
diff --git a/native/graphics/jni/corpus/sample_nef.nef b/native/graphics/jni/fuzz/corpus/sample_nef.nef
similarity index 100%
rename from native/graphics/jni/corpus/sample_nef.nef
rename to native/graphics/jni/fuzz/corpus/sample_nef.nef
Binary files differ
diff --git a/native/graphics/jni/corpus/sample_nrw.nrw b/native/graphics/jni/fuzz/corpus/sample_nrw.nrw
similarity index 100%
rename from native/graphics/jni/corpus/sample_nrw.nrw
rename to native/graphics/jni/fuzz/corpus/sample_nrw.nrw
Binary files differ
diff --git a/native/graphics/jni/corpus/sample_orf.orf b/native/graphics/jni/fuzz/corpus/sample_orf.orf
similarity index 100%
rename from native/graphics/jni/corpus/sample_orf.orf
rename to native/graphics/jni/fuzz/corpus/sample_orf.orf
Binary files differ
diff --git a/native/graphics/jni/corpus/sample_pef.pef b/native/graphics/jni/fuzz/corpus/sample_pef.pef
similarity index 100%
rename from native/graphics/jni/corpus/sample_pef.pef
rename to native/graphics/jni/fuzz/corpus/sample_pef.pef
Binary files differ
diff --git a/native/graphics/jni/corpus/sample_raf.raf b/native/graphics/jni/fuzz/corpus/sample_raf.raf
similarity index 100%
rename from native/graphics/jni/corpus/sample_raf.raf
rename to native/graphics/jni/fuzz/corpus/sample_raf.raf
Binary files differ
diff --git a/native/graphics/jni/corpus/sample_rw2.rw2 b/native/graphics/jni/fuzz/corpus/sample_rw2.rw2
similarity index 100%
rename from native/graphics/jni/corpus/sample_rw2.rw2
rename to native/graphics/jni/fuzz/corpus/sample_rw2.rw2
Binary files differ
diff --git a/native/graphics/jni/corpus/sample_srw.srw b/native/graphics/jni/fuzz/corpus/sample_srw.srw
similarity index 100%
rename from native/graphics/jni/corpus/sample_srw.srw
rename to native/graphics/jni/fuzz/corpus/sample_srw.srw
Binary files differ
diff --git a/native/graphics/jni/corpus/webp-color-profile-lossless.webp b/native/graphics/jni/fuzz/corpus/webp-color-profile-lossless.webp
similarity index 100%
rename from native/graphics/jni/corpus/webp-color-profile-lossless.webp
rename to native/graphics/jni/fuzz/corpus/webp-color-profile-lossless.webp
Binary files differ
diff --git a/native/graphics/jni/corpus/webp_animated.webp b/native/graphics/jni/fuzz/corpus/webp_animated.webp
similarity index 100%
rename from native/graphics/jni/corpus/webp_animated.webp
rename to native/graphics/jni/fuzz/corpus/webp_animated.webp
Binary files differ
diff --git a/native/graphics/jni/corpus/webp_test.webp b/native/graphics/jni/fuzz/corpus/webp_test.webp
similarity index 100%
rename from native/graphics/jni/corpus/webp_test.webp
rename to native/graphics/jni/fuzz/corpus/webp_test.webp
Binary files differ
diff --git a/native/graphics/jni/fuzz_imagedecoder.cpp b/native/graphics/jni/fuzz/fuzz_imagedecoder.cpp
similarity index 96%
rename from native/graphics/jni/fuzz_imagedecoder.cpp
rename to native/graphics/jni/fuzz/fuzz_imagedecoder.cpp
index 015aca7..838bf3f 100644
--- a/native/graphics/jni/fuzz_imagedecoder.cpp
+++ b/native/graphics/jni/fuzz/fuzz_imagedecoder.cpp
@@ -22,6 +22,10 @@
 #include <cstdlib>
 #include <memory>
 
+#ifdef PNG_MUTATOR_DEFINE_LIBFUZZER_CUSTOM_MUTATOR
+#include <fuzz/png_mutator.h>
+#endif
+
 struct DecoderDeleter {
     void operator()(AImageDecoder* decoder) const { AImageDecoder_delete(decoder); }
 };
diff --git a/native/graphics/jni/fuzz/png_mutator.h b/native/graphics/jni/fuzz/png_mutator.h
new file mode 100644
index 0000000..f23c343
--- /dev/null
+++ b/native/graphics/jni/fuzz/png_mutator.h
@@ -0,0 +1,326 @@
+// Copyright 2019 Google Inc. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0 (the "License");
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+#include <cstring>
+#include <cassert>
+#include <random>
+
+#include <zlib.h>
+
+// A simple class for parsing, serializing, and mutating an PNG file.
+// https://en.wikipedia.org/wiki/Portable_Network_Graphics
+// It is an example of a custom mutator for libFuzzer
+// (https://llvm.org/docs/LibFuzzer.html) used for
+// "structure-aware coverage-guided fuzzing".
+//
+// If you have a non structure-aware fuzz target for any API that handles
+// PNG inputs, you can turn that fuzz target into a structure-aware one
+// by defining PNG_MUTATOR_DEFINE_LIBFUZZER_CUSTOM_MUTATOR and then
+// including this file.
+class PngMutator {
+  using V = std::vector<uint8_t>;
+
+ public:
+
+  // Parse the input stream as a PNG file,
+  // put every chunk into its own vector,
+  // uncompress chunk data when needed,
+  // merge the IDAT chunks into one vector.
+  PngMutator(std::istream &in) {
+    ihdr_.resize(13);
+    Read4(in);
+    Read4(in); // Skip the 8-byte magic value.
+    // read IHDR.
+    if (ReadInteger(in) != 13) return;
+    if (Read4(in) != Type("IHDR")) return;
+    // Read 13 values.
+    in.read((char*)ihdr_.data(), ihdr_.size());
+    Read4(in);  // ignore CRC
+    ssize_t idat_idx = -1;
+
+    while (in) {
+      uint32_t len = ReadInteger(in);
+      uint32_t type = Read4(in);
+      if (type == Type("IEND")) break;  // do nothing
+      char chunk_name[5];
+      memcpy(chunk_name, &type, 4);
+      chunk_name[4] = 0;
+      if (len > (1 << 20)) return;
+      V v(len);
+      in.read((char *)v.data(), len);
+      Read4(in);  // ignore CRC
+
+      if (type == Type("IDAT")) {
+        if (idat_idx != -1)
+          Append(&chunks_[idat_idx].v, v);
+        else {
+          idat_idx = chunks_.size();
+          chunks_.push_back({type, v});
+        }
+      } else if (type == Type("iCCP")) {
+        auto it = v.begin();
+        while (it < v.end() && isprint(*it)) it++;
+        if (it < v.end() && !*it) it++;
+        if (it < v.end() && !*it) it++;
+        v = V(it, v.end());
+        auto uncompressed = Uncompress(v);
+        chunks_.push_back({type, uncompressed});
+        auto compressed = Compress(uncompressed);
+      } else {
+        chunks_.push_back({type, v});
+      }
+      //  std::cerr << "CHUNK: " << chunk_name << std::endl;
+    }
+    if (idat_idx != -1)
+      chunks_[idat_idx].v = Uncompress(chunks_[idat_idx].v);
+  }
+
+  // Write back the PNG file.
+  void Serialize(std::ostream &out) {
+    const unsigned char header[] = {0x89, 0x50, 0x4e, 0x47,
+                                    0x0d, 0x0a, 0x1a, 0x0a};
+    out.write((const char*)header, sizeof(header));
+    WriteChunk(out, "IHDR", ihdr_);
+    for (auto &ch : chunks_) {
+      if (ch.type == Type("iCCP")) {
+        V v;
+        v.push_back('x');  // assuming the iCCP name doesn't matter.
+        v.push_back(0);
+        v.push_back(0);
+        auto compressed = Compress(ch.v);
+        Append(&v, compressed);
+        WriteChunk(out, ch.type, v);
+      } else {
+        WriteChunk(out, ch.type, ch.v);
+      }
+    }
+
+    WriteChunk(out, "IEND", {});
+  }
+
+  // Raw byte array mutator, like that provided by libFuzzer.
+  using Mutator = size_t (*)(uint8_t *Data, size_t Size, size_t MaxSize);
+
+  // Mutate the in-memory representation of a PNG file.
+  // Given the same Seed, the same mutation is performed.
+  void Mutate(Mutator m, unsigned int Seed) {
+    std::minstd_rand rnd(Seed);
+    auto M = [&](V *v) {
+      if (v->empty())
+        v->resize(v->size() + 1 + rnd() % 256);
+      v->resize(m(v->data(), v->size(), v->size()));
+    };
+    switch (rnd() % 6) {
+      // Mutate IHDR.
+      case 0:
+        m(ihdr_.data(), ihdr_.size(), ihdr_.size());
+        break;
+      // Mutate some other chunk.
+      case 1:
+        if (!chunks_.empty()) M(&chunks_[rnd() % chunks_.size()].v);
+        break;
+      // Shuffle the chunks.
+      case 2:
+        std::shuffle(chunks_.begin(), chunks_.end(), rnd);
+        break;
+      // Delete a random chunk.
+      case 3:
+        if (!chunks_.empty())
+         chunks_.erase(chunks_.begin() + rnd() % chunks_.size());
+        break;
+      // Insert a random chunk with one of the known types, or a random type.
+      case 4: {
+        static const char *types[] = {
+            "IATx", "sTER", "hIST", "sPLT", "mkBF", "mkBS", "mkTS", "prVW",
+            "oFFs", "iDOT", "zTXt", "mkBT", "acTL", "iTXt", "sBIT", "tIME",
+            "iCCP", "vpAg", "tRNS", "cHRM", "PLTE", "bKGD", "gAMA", "sRGB",
+            "pHYs", "fdAT", "fcTL", "tEXt", "IDAT",
+            "pCAL", "sCAL", "eXIf",
+            "fUZz", // special chunk for extra fuzzing hints.
+        };
+        static const size_t n_types = sizeof(types) / sizeof(types[0]);
+        uint32_t type =
+            (rnd() % 10 <= 8) ? Type(types[rnd() % n_types]) : (uint32_t)rnd();
+        size_t len = rnd() % 256;
+        if (type == Type("fUZz"))
+          len = 16;
+        V v(len);
+        for (auto &b : v) b = rnd();
+        size_t pos = rnd() % (chunks_.size() + 1);
+        chunks_.insert(chunks_.begin() + pos, {type, v});
+      } break;
+      // Any more interesting mutations with a PNG file?
+      case 5: {
+        auto it = std::find_if(
+            chunks_.begin(), chunks_.end(),
+            [](const Chunk &ch) { return ch.type == Type("fUZz"); });
+        if (it != chunks_.end())
+          m(it->v.data(), it->v.size(), it->v.size());
+      }
+
+    }
+  }
+
+  // Takes a random chunk from p and inserts into *this.
+  void CrossOver(const PngMutator &p, unsigned int Seed) {
+    if (p.chunks_.empty()) return;
+    std::minstd_rand rnd(Seed);
+    size_t idx = rnd() % p.chunks_.size();
+    auto &ch = p.chunks_[idx];
+    size_t pos = rnd() % (chunks_.size() + 1);
+    chunks_.insert(chunks_.begin() + pos, ch);
+  }
+
+ private:
+  void Append(V *to, const V &from) {
+    to->insert(to->end(), from.begin(), from.end());
+  }
+
+  uint32_t Read4(std::istream &in) {
+    uint32_t res = 0;
+    in.read((char *)&res, sizeof(res));
+    return res;
+  }
+  uint32_t ReadInteger(std::istream &in) {
+    return __builtin_bswap32(Read4(in));
+  }
+  static uint32_t Type(const char *tagname) {
+    uint32_t res;
+    assert(strlen(tagname) == 4);
+    memcpy(&res, tagname, 4);
+    return res;
+  }
+
+  void WriteInt(std::ostream &out, uint32_t x) {
+    x = __builtin_bswap32(x);
+    out.write((char *)&x, sizeof(x));
+  }
+
+  // Chunk is written as:
+  //  * 4-byte length
+  //  * 4-byte type
+  //  * the data itself
+  //  * 4-byte crc (of type and data)
+  void WriteChunk(std::ostream &out, const char *type, const V &chunk,
+                  bool compress = false) {
+    V compressed;
+    const V *v = &chunk;
+    if (compress) {
+      compressed = Compress(chunk);
+      v = &compressed;
+    }
+    uint32_t len = v->size();
+    uint32_t crc = crc32(0, (const unsigned char *)type, 4);
+    if (v->size())
+      crc = crc32(crc, (const unsigned char *)v->data(), v->size());
+    WriteInt(out, len);
+    out.write(type, 4);
+    out.write((const char*)v->data(), v->size());
+    WriteInt(out, crc);
+  }
+
+  void WriteChunk(std::ostream &out, uint32_t type, const V &chunk) {
+    char type_s[5];
+    memcpy(type_s, &type, 4);
+    type_s[4] = 0;
+    WriteChunk(out, type_s, chunk);
+  }
+
+  V Uncompress(const V &compressed) {
+    V v;
+    static const size_t kMaxBuffer = 1 << 28;
+    for (size_t sz = compressed.size() * 4; sz < kMaxBuffer; sz *= 2) {
+      v.resize(sz);
+      unsigned long len = sz;
+      auto res =
+          uncompress(v.data(), &len, compressed.data(), compressed.size());
+      if (res == Z_BUF_ERROR) continue;
+      if (res != Z_OK) return {};
+      v.resize(len);
+      break;
+    }
+    return v;
+  }
+
+  V Compress(const V &uncompressed) {
+    V v;
+    static const size_t kMaxBuffer = 1 << 28;
+    for (size_t sz = uncompressed.size(); sz < kMaxBuffer; sz *= 2) {
+      v.resize(sz);
+      unsigned long len = sz;
+      auto res =
+          compress(v.data(), &len, uncompressed.data(), uncompressed.size());
+      if (res == Z_BUF_ERROR) continue;
+      if (res != Z_OK) return {};
+      v.resize(len);
+      break;
+    }
+    return v;
+  }
+
+  void PrintHex(const V &v, size_t max_n) {
+    for (size_t i = 0; i < max_n && i < v.size(); i++) {
+      std::cerr << "0x" << std::hex << (unsigned)v[i] << " " << std::dec;
+    }
+    std::cerr << std::endl;
+  }
+
+  V ihdr_;
+
+  struct Chunk {
+    uint32_t type;
+    V v;
+  };
+  std::vector<Chunk> chunks_;
+};
+
+
+#ifdef PNG_MUTATOR_DEFINE_LIBFUZZER_CUSTOM_MUTATOR
+
+extern "C" size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize);
+
+#if STANDALONE_TARGET
+size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize) {
+  assert(false && "LLVMFuzzerMutate should not be called from StandaloneFuzzTargetMain");
+  return 0;
+}
+#endif
+
+extern "C" size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size,
+                                          size_t MaxSize, unsigned int Seed) {
+  std::string s(reinterpret_cast<const char*>(Data), Size);
+  std::stringstream in(s);
+  std::stringstream out;
+  PngMutator p(in);
+  p.Mutate(LLVMFuzzerMutate, Seed);
+  p.Serialize(out);
+  const auto &str = out.str();
+  if (str.size() > MaxSize) return Size;
+  memcpy(Data, str.data(), str.size());
+  return str.size();
+}
+
+extern "C" size_t LLVMFuzzerCustomCrossOver(const uint8_t *Data1, size_t Size1,
+                                            const uint8_t *Data2, size_t Size2,
+                                            uint8_t *Out, size_t MaxOutSize,
+                                            unsigned int Seed) {
+  std::stringstream in1(
+      std::string(reinterpret_cast<const char *>(Data1), Size1));
+  std::stringstream in2(
+      std::string(reinterpret_cast<const char *>(Data2), Size2));
+  PngMutator p1(in1);
+  PngMutator p2(in2);
+  p1.CrossOver(p2, Seed);
+  std::stringstream out;
+  p1.Serialize(out);
+  const auto &str = out.str();
+  if (str.size() > MaxOutSize) return 0;
+  memcpy(Out, str.data(), str.size());
+  return str.size();
+}
+
+#endif  // PNG_MUTATOR_DEFINE_LIBFUZZER_CUSTOM_MUTATOR