diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp index 3751564ca465b..d464587cb6c86 100644 --- a/native/graphics/jni/Android.bp +++ b/native/graphics/jni/Android.bp @@ -74,9 +74,9 @@ ndk_library { 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 @@ cc_fuzz { "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 523005168f726..0000000000000 Binary files a/native/graphics/jni/corpus/png_test.png and /dev/null 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 diff --git a/native/graphics/jni/fuzz_imagedecoder.cpp b/native/graphics/jni/fuzz/fuzz_imagedecoder.cpp similarity index 97% rename from native/graphics/jni/fuzz_imagedecoder.cpp rename to native/graphics/jni/fuzz/fuzz_imagedecoder.cpp index 015aca70e4e0b..838bf3f30a119 100644 --- a/native/graphics/jni/fuzz_imagedecoder.cpp +++ b/native/graphics/jni/fuzz/fuzz_imagedecoder.cpp @@ -22,6 +22,10 @@ #include #include +#ifdef PNG_MUTATOR_DEFINE_LIBFUZZER_CUSTOM_MUTATOR +#include +#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 0000000000000..f23c3436cd8b3 --- /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 +#include +#include +#include +#include +#include +#include +#include + +#include + +// 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; + + 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 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(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(Data1), Size1)); + std::stringstream in2( + std::string(reinterpret_cast(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