/src/libpng_transforms_fuzzer.cc
Line | Count | Source (jump to first uncovered line) |
1 | | #include <algorithm> |
2 | | #include <cassert> |
3 | | #include <cstring> |
4 | | #include <string> |
5 | | #include <vector> |
6 | | |
7 | | #include "png.h" |
8 | | |
9 | | namespace { |
10 | | |
11 | | struct PngReader { |
12 | | png_structp png_ptr = nullptr; |
13 | | png_infop info_ptr = nullptr; |
14 | | png_infop end_info = nullptr; |
15 | | }; |
16 | | |
17 | | struct PngArrayStream { |
18 | | const uint8_t *data; |
19 | | size_t size; |
20 | | size_t pos; |
21 | | }; |
22 | | |
23 | | void PngArrayStreamCallback(png_structp png_ptr, png_bytep data, |
24 | 145M | png_size_t size) { |
25 | 145M | PngArrayStream *stream = |
26 | 145M | static_cast<PngArrayStream *>(png_get_io_ptr(png_ptr)); |
27 | 145M | if (stream->pos + size > stream->size) { |
28 | 145M | memset(data, 0, size); |
29 | 145M | stream->pos = size; |
30 | 145M | } else { |
31 | 112k | memcpy(data, &stream->data[stream->pos], size); |
32 | 112k | stream->pos += size; |
33 | 112k | } |
34 | 145M | } |
35 | | |
36 | | static bool PngVerboseWarnings = getenv("PNG_VERBOSE_WARNINGS") != nullptr; |
37 | | |
38 | 4.56k | void PngErrorHandler(png_structp png_ptr, png_const_charp error_message) { |
39 | 4.56k | if (PngVerboseWarnings) fprintf(stderr, "%s\n", error_message); |
40 | 4.56k | longjmp(png_jmpbuf(png_ptr), 1); |
41 | 4.56k | } |
42 | | |
43 | 5.40k | void PngWarningHandler(png_structp png_ptr, png_const_charp warning_message) { |
44 | 5.40k | if (PngVerboseWarnings) fprintf(stderr, "%s\n", warning_message); |
45 | 5.40k | longjmp(png_jmpbuf(png_ptr), 1); |
46 | 5.40k | } |
47 | | |
48 | | } // namespace |
49 | | |
50 | 10.1k | extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { |
51 | 10.1k | const size_t kPngSignatureSize = 8; |
52 | 10.1k | const size_t kIHDRSize = 4 + 4 + 13 + 4; |
53 | 10.1k | const size_t kMaxImageSize = 1 << 20; |
54 | 10.1k | const size_t kMaxHeight = 1 << 10; |
55 | | |
56 | 25.9k | auto Read32 = [&](const uint8_t *p) { |
57 | 25.9k | uint32_t res; |
58 | 25.9k | assert(p >= data); |
59 | 25.9k | assert(p + sizeof(res) < data + size); |
60 | 25.9k | memcpy(&res, p, sizeof(res)); |
61 | 25.9k | return res; |
62 | 25.9k | }; |
63 | | |
64 | 10.1k | if (size < kPngSignatureSize + kIHDRSize) return 0; |
65 | 10.1k | if (png_sig_cmp(data, 0, kPngSignatureSize)) return 0; |
66 | 10.0k | uint32_t width = __builtin_bswap32(Read32(data + kPngSignatureSize + 8)); |
67 | 10.0k | uint32_t height = __builtin_bswap32(Read32(data + kPngSignatureSize + 12)); |
68 | | // Reject too large images because they will OOM. |
69 | | // Also reject images with a too large height, because large height |
70 | | // will cause too many mallocs. |
71 | | // These two heuristics are far from optimal and may cause us to lose some |
72 | | // coverage. But w/o them fuzzing is way too slow. |
73 | 10.0k | if ((uint64_t)width * height > kMaxImageSize) return 0; |
74 | 10.0k | if (height > kMaxHeight) return 0; |
75 | | |
76 | | // Find the fUZz chunk and it's contents. |
77 | 9.97k | const size_t fUZz_chunk_size = 16; |
78 | 9.97k | const uint8_t fUZz_signature[8] = {0, 0, 0, fUZz_chunk_size, |
79 | 9.97k | 'f', 'U', 'Z', 'z'}; |
80 | 9.97k | const uint8_t *fUZz_beg = |
81 | 9.97k | std::search(data, data + size, fUZz_signature, |
82 | 9.97k | fUZz_signature + sizeof(fUZz_signature)); |
83 | 9.97k | if (fUZz_beg + sizeof(fUZz_signature) + fUZz_chunk_size < data + size) |
84 | 2.92k | fUZz_beg += sizeof(fUZz_signature); |
85 | 7.04k | else |
86 | 7.04k | fUZz_beg = nullptr; |
87 | | |
88 | 9.97k | PngReader reader; |
89 | 9.97k | reader.png_ptr = |
90 | 9.97k | png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); |
91 | 9.97k | assert(reader.png_ptr); |
92 | 9.97k | reader.info_ptr = png_create_info_struct(reader.png_ptr); |
93 | 9.97k | assert(reader.info_ptr); |
94 | 9.97k | reader.end_info = png_create_info_struct(reader.png_ptr); |
95 | 9.97k | assert(reader.end_info); |
96 | | |
97 | 9.97k | png_set_error_fn(reader.png_ptr, png_get_error_ptr(reader.png_ptr), |
98 | 9.97k | PngErrorHandler, PngWarningHandler); |
99 | | |
100 | 9.97k | PngArrayStream stream{data, size, 0}; |
101 | | |
102 | 9.97k | if (setjmp(png_jmpbuf(reader.png_ptr)) == 0) { |
103 | 9.97k | png_set_read_fn(reader.png_ptr, &stream, PngArrayStreamCallback); |
104 | | |
105 | | // Take transforms from the fUZz chunk. By default, enable all. |
106 | 9.97k | int transforms = fUZz_beg ? Read32(fUZz_beg) : ~0; |
107 | 9.97k | png_read_png(reader.png_ptr, reader.info_ptr, transforms, nullptr); |
108 | 9.97k | } |
109 | 9.97k | png_destroy_read_struct(&reader.png_ptr, &reader.info_ptr, &reader.end_info); |
110 | | |
111 | | // Run the same image through another libpng API. |
112 | | // There is probably some redundancy here (I don't know what I am doing!) |
113 | 9.97k | png_image image; |
114 | 9.97k | memset(&image, 0, sizeof(image)); |
115 | 9.97k | image.version = PNG_IMAGE_VERSION; |
116 | 9.97k | if (png_image_begin_read_from_memory(&image, data, size)) { |
117 | 4.62k | const size_t kMaxBufferSize = 64 << 20; |
118 | 4.62k | image.format = fUZz_beg ? Read32(fUZz_beg + 4) : PNG_FORMAT_RGBA; |
119 | 4.62k | size_t image_size = PNG_IMAGE_SIZE(image); |
120 | 4.62k | if (image_size <= kMaxBufferSize) { |
121 | 4.62k | png_bytep buffer = new png_byte[image_size]; |
122 | 4.62k | const size_t kColorMapSize = 256 * 4; |
123 | | // Do we need to take color & colormap from the fuzzed input? |
124 | 4.62k | png_color color = {1, 2, 3}; |
125 | 4.62k | png_uint_16 colormap[256*4] = {0}; |
126 | 4.74M | for (size_t i = 0; i < kColorMapSize; i++) |
127 | 4.74M | colormap[i] = i; |
128 | 4.62k | png_image_finish_read(&image, &color, buffer, 0, colormap); |
129 | 4.62k | delete[] buffer; |
130 | 4.62k | } |
131 | 4.62k | } |
132 | 9.97k | png_image_free(&image); |
133 | 9.97k | return 0; |
134 | 9.97k | } |
135 | | |
136 | 0 | extern "C" const char *__asan_default_options() { |
137 | | // TODO: remove this once |
138 | | // https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=12716 |
139 | | // is fixed. |
140 | 0 | return "detect_leaks=0"; |
141 | 0 | } |