/src/png_proto_fuzzer_example.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Example fuzzer for PNG using protos. |
2 | | #include <string> |
3 | | #include <sstream> |
4 | | #include <fstream> |
5 | | #include <zlib.h> // for crc32 |
6 | | |
7 | | #include "libprotobuf-mutator/src/libfuzzer/libfuzzer_macro.h" |
8 | | #include "png_fuzz_proto.pb.h" |
9 | | |
10 | 245k | static void WriteInt(std::stringstream &out, uint32_t x) { |
11 | 245k | x = __builtin_bswap32(x); |
12 | 245k | out.write((char *)&x, sizeof(x)); |
13 | 245k | } |
14 | | |
15 | 35.5k | static void WriteByte(std::stringstream &out, uint8_t x) { |
16 | 35.5k | out.write((char *)&x, sizeof(x)); |
17 | 35.5k | } |
18 | | |
19 | 18.2k | static std::string Compress(const std::string &s) { |
20 | 18.2k | std::string out(s.size() + 100, '\0'); |
21 | 18.2k | size_t out_len = out.size(); |
22 | 18.2k | compress((uint8_t *)&out[0], &out_len, (uint8_t *)s.data(), s.size()); |
23 | 18.2k | out.resize(out_len); |
24 | 18.2k | return out; |
25 | 18.2k | } |
26 | | |
27 | | // Chunk is written as: |
28 | | // * 4-byte length |
29 | | // * 4-byte type |
30 | | // * the data itself |
31 | | // * 4-byte crc (of type and data) |
32 | | static void WriteChunk(std::stringstream &out, const char *type, |
33 | 95.1k | const std::string &chunk, bool compress = false) { |
34 | 95.1k | std::string compressed; |
35 | 95.1k | const std::string *s = &chunk; |
36 | 95.1k | if (compress) { |
37 | 9.69k | compressed = Compress(chunk); |
38 | 9.69k | s = &compressed; |
39 | 9.69k | } |
40 | 95.1k | uint32_t len = s->size(); |
41 | 95.1k | uint32_t crc = crc32(crc32(0, (const unsigned char *)type, 4), |
42 | 95.1k | (const unsigned char *)s->data(), s->size()); |
43 | 95.1k | WriteInt(out, len); |
44 | 95.1k | out.write(type, 4); |
45 | 95.1k | out.write(s->data(), s->size()); |
46 | 95.1k | WriteInt(out, crc); |
47 | 95.1k | } |
48 | | |
49 | 18.3k | std::string ProtoToPng(const PngProto &png_proto) { |
50 | 18.3k | std::stringstream all; |
51 | 18.3k | const unsigned char header[] = {0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a}; |
52 | 18.3k | all.write((const char*)header, sizeof(header)); |
53 | 18.3k | std::stringstream ihdr_str; |
54 | 18.3k | auto &ihdr = png_proto.ihdr(); |
55 | | // Avoid large images. |
56 | | // They may have interesting bugs, but OOMs are going to kill fuzzing. |
57 | 18.3k | uint32_t w = std::min(ihdr.width(), 4096U); |
58 | 18.3k | uint32_t h = std::min(ihdr.height(), 4096U); |
59 | 18.3k | WriteInt(ihdr_str, w); |
60 | 18.3k | WriteInt(ihdr_str, h); |
61 | 18.3k | WriteInt(ihdr_str, ihdr.other1()); |
62 | 18.3k | WriteByte(ihdr_str, ihdr.other2()); |
63 | 18.3k | WriteChunk(all, "IHDR", ihdr_str.str()); |
64 | | |
65 | 79.5k | for (size_t i = 0, n = png_proto.chunks_size(); i < n; i++) { |
66 | 61.1k | auto &chunk = png_proto.chunks(i); |
67 | 61.1k | if (chunk.has_plte()) { |
68 | 4.99k | WriteChunk(all, "PLTE", chunk.plte().data()); |
69 | 56.1k | } else if (chunk.has_idat()) { |
70 | 9.69k | WriteChunk(all, "IDAT", chunk.idat().data(), true); |
71 | 46.4k | } else if (chunk.has_iccp()) { |
72 | 8.58k | std::stringstream iccp_str; |
73 | 8.58k | iccp_str << "xyz"; // don't fuzz iCCP name field. |
74 | 8.58k | WriteByte(iccp_str, 0); |
75 | 8.58k | WriteByte(iccp_str, 0); |
76 | 8.58k | auto compressed_data = Compress(chunk.iccp().data()); |
77 | 8.58k | iccp_str.write(compressed_data.data(), compressed_data.size()); |
78 | 8.58k | WriteChunk(all, "iCCP", iccp_str.str()); |
79 | 37.8k | } else if (chunk.has_other_chunk()) { |
80 | 35.7k | auto &other_chunk = chunk.other_chunk(); |
81 | 35.7k | char type[5] = {0}; |
82 | 35.7k | if (other_chunk.has_known_type()) { |
83 | 30.2k | static const char * known_chunks[] = { |
84 | 30.2k | "bKGD", "cHRM", "dSIG", "eXIf", "gAMA", "hIST", "iCCP", |
85 | 30.2k | "iTXt", "pHYs", "sBIT", "sPLT", "sRGB", "sTER", "tEXt", |
86 | 30.2k | "tIME", "tRNS", "zTXt", "sCAL", "pCAL", "oFFs", |
87 | 30.2k | }; |
88 | 30.2k | size_t known_chunks_size = |
89 | 30.2k | sizeof(known_chunks) / sizeof(known_chunks[0]); |
90 | 30.2k | size_t chunk_idx = other_chunk.known_type() % known_chunks_size; |
91 | 30.2k | memcpy(type, known_chunks[chunk_idx], 4); |
92 | 30.2k | } else if (other_chunk.has_unknown_type()) { |
93 | 4.85k | uint32_t unknown_type_int = other_chunk.unknown_type(); |
94 | 4.85k | memcpy(type, &unknown_type_int, 4); |
95 | 4.85k | } else { |
96 | 570 | continue; |
97 | 570 | } |
98 | 35.1k | type[4] = 0; |
99 | 35.1k | WriteChunk(all, type, other_chunk.data()); |
100 | 35.1k | } |
101 | 61.1k | } |
102 | 18.3k | WriteChunk(all, "IEND", ""); |
103 | | |
104 | 18.3k | std::string res = all.str(); |
105 | 18.3k | if (const char *dump_path = getenv("PROTO_FUZZER_DUMP_PATH")) { |
106 | | // With libFuzzer binary run this to generate a PNG file x.png: |
107 | | // PROTO_FUZZER_DUMP_PATH=x.png ./a.out proto-input |
108 | 0 | std::ofstream of(dump_path); |
109 | 0 | of.write(res.data(), res.size()); |
110 | 0 | } |
111 | 18.3k | return res; |
112 | 18.3k | } |
113 | | |
114 | | // The actual fuzz target that consumes the PNG data. |
115 | | extern "C" int FuzzPNG(const uint8_t* data, size_t size); |
116 | | |
117 | 18.3k | DEFINE_PROTO_FUZZER(const PngProto &png_proto) { |
118 | 18.3k | auto s = ProtoToPng(png_proto); |
119 | 18.3k | FuzzPNG((const uint8_t*)s.data(), s.size()); |
120 | 18.3k | } |