You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
121 lines
4.0 KiB
121 lines
4.0 KiB
// Example fuzzer for PNG using protos.
|
|
#include <string>
|
|
#include <sstream>
|
|
#include <fstream>
|
|
#include <zlib.h> // for crc32
|
|
|
|
#include "libprotobuf-mutator/src/libfuzzer/libfuzzer_macro.h"
|
|
#include "png_fuzz_proto.pb.h"
|
|
|
|
static void WriteInt(std::stringstream &out, uint32_t x) {
|
|
x = __builtin_bswap32(x);
|
|
out.write((char *)&x, sizeof(x));
|
|
}
|
|
|
|
static void WriteByte(std::stringstream &out, uint8_t x) {
|
|
out.write((char *)&x, sizeof(x));
|
|
}
|
|
|
|
static std::string Compress(const std::string &s) {
|
|
std::string out(s.size() + 100, '\0');
|
|
size_t out_len = out.size();
|
|
compress((uint8_t *)&out[0], &out_len, (uint8_t *)s.data(), s.size());
|
|
out.resize(out_len);
|
|
return out;
|
|
}
|
|
|
|
// Chunk is written as:
|
|
// * 4-byte length
|
|
// * 4-byte type
|
|
// * the data itself
|
|
// * 4-byte crc (of type and data)
|
|
static void WriteChunk(std::stringstream &out, const char *type,
|
|
const std::string &chunk, bool compress = false) {
|
|
std::string compressed;
|
|
const std::string *s = &chunk;
|
|
if (compress) {
|
|
compressed = Compress(chunk);
|
|
s = &compressed;
|
|
}
|
|
uint32_t len = s->size();
|
|
uint32_t crc = crc32(crc32(0, (const unsigned char *)type, 4),
|
|
(const unsigned char *)s->data(), s->size());
|
|
WriteInt(out, len);
|
|
out.write(type, 4);
|
|
out.write(s->data(), s->size());
|
|
WriteInt(out, crc);
|
|
}
|
|
|
|
std::string ProtoToPng(const PngProto &png_proto) {
|
|
std::stringstream all;
|
|
const unsigned char header[] = {0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a};
|
|
all.write((const char*)header, sizeof(header));
|
|
std::stringstream ihdr_str;
|
|
auto &ihdr = png_proto.ihdr();
|
|
// Avoid large images.
|
|
// They may have interesting bugs, but OOMs are going to kill fuzzing.
|
|
uint32_t w = std::min(ihdr.width(), 4096U);
|
|
uint32_t h = std::min(ihdr.height(), 4096U);
|
|
WriteInt(ihdr_str, w);
|
|
WriteInt(ihdr_str, h);
|
|
WriteInt(ihdr_str, ihdr.other1());
|
|
WriteByte(ihdr_str, ihdr.other2());
|
|
WriteChunk(all, "IHDR", ihdr_str.str());
|
|
|
|
for (size_t i = 0, n = png_proto.chunks_size(); i < n; i++) {
|
|
auto &chunk = png_proto.chunks(i);
|
|
if (chunk.has_plte()) {
|
|
WriteChunk(all, "PLTE", chunk.plte().data());
|
|
} else if (chunk.has_idat()) {
|
|
WriteChunk(all, "IDAT", chunk.idat().data(), true);
|
|
} else if (chunk.has_iccp()) {
|
|
std::stringstream iccp_str;
|
|
iccp_str << "xyz"; // don't fuzz iCCP name field.
|
|
WriteByte(iccp_str, 0);
|
|
WriteByte(iccp_str, 0);
|
|
auto compressed_data = Compress(chunk.iccp().data());
|
|
iccp_str.write(compressed_data.data(), compressed_data.size());
|
|
WriteChunk(all, "iCCP", iccp_str.str());
|
|
} else if (chunk.has_other_chunk()) {
|
|
auto &other_chunk = chunk.other_chunk();
|
|
char type[5] = {0};
|
|
if (other_chunk.has_known_type()) {
|
|
static const char * known_chunks[] = {
|
|
"bKGD", "cHRM", "dSIG", "eXIf", "gAMA", "hIST", "iCCP",
|
|
"iTXt", "pHYs", "sBIT", "sPLT", "sRGB", "sTER", "tEXt",
|
|
"tIME", "tRNS", "zTXt", "sCAL", "pCAL", "oFFs",
|
|
};
|
|
size_t known_chunks_size =
|
|
sizeof(known_chunks) / sizeof(known_chunks[0]);
|
|
size_t chunk_idx = other_chunk.known_type() % known_chunks_size;
|
|
memcpy(type, known_chunks[chunk_idx], 4);
|
|
} else if (other_chunk.has_unknown_type()) {
|
|
uint32_t unknown_type_int = other_chunk.unknown_type();
|
|
memcpy(type, &unknown_type_int, 4);
|
|
} else {
|
|
continue;
|
|
}
|
|
type[4] = 0;
|
|
WriteChunk(all, type, other_chunk.data());
|
|
}
|
|
}
|
|
WriteChunk(all, "IEND", "");
|
|
|
|
std::string res = all.str();
|
|
if (const char *dump_path = getenv("PROTO_FUZZER_DUMP_PATH")) {
|
|
// With libFuzzer binary run this to generate a PNG file x.png:
|
|
// PROTO_FUZZER_DUMP_PATH=x.png ./a.out proto-input
|
|
std::ofstream of(dump_path);
|
|
of.write(res.data(), res.size());
|
|
}
|
|
return res;
|
|
}
|
|
|
|
// The actual fuzz target that consumes the PNG data.
|
|
extern "C" int FuzzPNG(const uint8_t* data, size_t size);
|
|
|
|
DEFINE_PROTO_FUZZER(const PngProto &png_proto) {
|
|
auto s = ProtoToPng(png_proto);
|
|
FuzzPNG((const uint8_t*)s.data(), s.size());
|
|
}
|