Coverage Report

Created: 2025-06-12 06:14

/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
}