Coverage Report

Created: 2025-11-16 07:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libjxl/tools/jpegli_dec_fuzzer.cc
Line
Count
Source
1
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
2
//
3
// Use of this source code is governed by a BSD-style
4
// license that can be found in the LICENSE file.
5
6
#include <setjmp.h>
7
8
#include <algorithm>
9
#include <cstdint>
10
#include <cstdlib>
11
#include <cstring>
12
#include <hwy/targets.h>
13
#include <vector>
14
15
#include "lib/jpegli/common.h"
16
#include "lib/jpegli/decode.h"
17
#include "lib/jpegli/fuzztest.h"
18
#include "lib/jpegli/types.h"
19
20
namespace {
21
22
// Externally visible value to ensure pixels are used in the fuzzer.
23
int external_code = 0;
24
25
template <typename It>
26
2.67k
void Consume(const It& begin, const It& end) {
27
5.83G
  for (auto it = begin; it < end; ++it) {
28
5.83G
    if (*it == 0) {
29
914M
      external_code ^= ~0;
30
4.92G
    } else {
31
4.92G
      external_code ^= *it;
32
4.92G
    }
33
5.83G
  }
34
2.67k
}
35
36
// Options for the fuzzing
37
struct FuzzSpec {
38
  size_t chunk_size;
39
  JpegliDataType output_type;
40
  JpegliEndianness output_endianness;
41
  int crop_output;
42
};
43
44
constexpr uint8_t kFakeEoiMarker[2] = {0xff, 0xd9};
45
constexpr size_t kNumSourceBuffers = 4;
46
47
class SourceManager {
48
 public:
49
  SourceManager(const uint8_t* data, size_t len, size_t max_chunk_size)
50
4.69k
      : data_(data), len_(len), max_chunk_size_(max_chunk_size) {
51
4.69k
    pub_.skip_input_data = skip_input_data;
52
4.69k
    pub_.resync_to_restart = jpegli_resync_to_restart;
53
4.69k
    pub_.term_source = term_source;
54
4.69k
    pub_.init_source = init_source;
55
4.69k
    pub_.fill_input_buffer = fill_input_buffer;
56
4.69k
    if (max_chunk_size_ == 0) max_chunk_size_ = len;
57
4.69k
    buffers_.resize(kNumSourceBuffers, std::vector<uint8_t>(max_chunk_size_));
58
4.69k
    Reset();
59
4.69k
  }
60
61
4.69k
  void Reset() {
62
4.69k
    pub_.next_input_byte = nullptr;
63
4.69k
    pub_.bytes_in_buffer = 0;
64
4.69k
    pos_ = 0;
65
4.69k
    chunk_idx_ = 0;
66
4.69k
  }
67
68
 private:
69
  jpeg_source_mgr pub_;
70
  const uint8_t* data_;
71
  size_t len_;
72
  size_t chunk_idx_;
73
  size_t pos_;
74
  size_t max_chunk_size_;
75
  std::vector<std::vector<uint8_t>> buffers_;
76
77
4.69k
  static void init_source(j_decompress_ptr cinfo) {}
78
79
17.3M
  static boolean fill_input_buffer(j_decompress_ptr cinfo) {
80
17.3M
    auto* src = reinterpret_cast<SourceManager*>(cinfo->src);
81
17.3M
    if (src->pos_ < src->len_) {
82
932k
      size_t remaining = src->len_ - src->pos_;
83
932k
      size_t chunk_size = std::min(remaining, src->max_chunk_size_);
84
932k
      size_t next_idx = ++src->chunk_idx_ % kNumSourceBuffers;
85
      // Larger number of chunks causes fuzzer timuout.
86
932k
      if (src->chunk_idx_ >= (1u << 15)) {
87
17
        chunk_size = remaining;
88
17
        next_idx = src->buffers_.size();
89
17
        src->buffers_.emplace_back(chunk_size);
90
17
      }
91
932k
      uint8_t* next_buffer = src->buffers_[next_idx].data();
92
932k
      memcpy(next_buffer, src->data_ + src->pos_, chunk_size);
93
932k
      src->pub_.next_input_byte = next_buffer;
94
932k
      src->pub_.bytes_in_buffer = chunk_size;
95
16.4M
    } else {
96
16.4M
      src->pub_.next_input_byte = kFakeEoiMarker;
97
16.4M
      src->pub_.bytes_in_buffer = 2;
98
16.4M
      src->len_ += 2;
99
16.4M
    }
100
17.3M
    src->pos_ += src->pub_.bytes_in_buffer;
101
17.3M
    return TRUE;
102
17.3M
  }
103
104
  static void skip_input_data(j_decompress_ptr cinfo,
105
0
                              long num_bytes /* NOLINT */) {
106
0
    auto* src = reinterpret_cast<SourceManager*>(cinfo->src);
107
0
    if (num_bytes <= 0) {
108
0
      return;
109
0
    }
110
0
    if (src->pub_.bytes_in_buffer >= static_cast<size_t>(num_bytes)) {
111
0
      src->pub_.bytes_in_buffer -= num_bytes;
112
0
      src->pub_.next_input_byte += num_bytes;
113
0
    } else {
114
0
      src->pos_ += num_bytes - src->pub_.bytes_in_buffer;
115
0
      src->pub_.bytes_in_buffer = 0;
116
0
    }
117
0
  }
118
119
2.67k
  static void term_source(j_decompress_ptr cinfo) {}
120
};
121
122
bool DecodeJpeg(const uint8_t* data, size_t size, size_t max_pixels,
123
                const FuzzSpec& spec, std::vector<uint8_t>* pixels,
124
4.69k
                size_t* xsize, size_t* ysize) {
125
4.69k
  SourceManager src(data, size, spec.chunk_size);
126
4.69k
  jpeg_decompress_struct cinfo;
127
4.69k
  const auto try_catch_block = [&]() -> bool {
128
4.69k
    jpeg_error_mgr jerr;
129
4.69k
    jmp_buf env;
130
4.69k
    cinfo.err = jpegli_std_error(&jerr);
131
4.69k
    if (setjmp(env)) {
132
1.98k
      return false;
133
1.98k
    }
134
2.71k
    cinfo.client_data = reinterpret_cast<void*>(&env);
135
2.71k
    cinfo.err->error_exit = [](j_common_ptr cinfo) {
136
1.98k
      jmp_buf* env = reinterpret_cast<jmp_buf*>(cinfo->client_data);
137
1.98k
      jpegli_destroy(cinfo);
138
1.98k
      longjmp(*env, 1);
139
1.98k
    };
140
23.8k
    cinfo.err->emit_message = [](j_common_ptr cinfo, int msg_level) {};
141
2.71k
    jpegli_create_decompress(&cinfo);
142
2.71k
    cinfo.src = reinterpret_cast<jpeg_source_mgr*>(&src);
143
2.71k
    jpegli_read_header(&cinfo, TRUE);
144
2.71k
    *xsize = cinfo.image_width;
145
2.71k
    *ysize = cinfo.image_height;
146
2.71k
    size_t num_pixels = *xsize * *ysize;
147
2.71k
    if (num_pixels > max_pixels) return false;
148
2.67k
    jpegli_set_output_format(&cinfo, spec.output_type, spec.output_endianness);
149
2.67k
    jpegli_start_decompress(&cinfo);
150
2.67k
    if (spec.crop_output) {
151
1.02k
      JDIMENSION xoffset = cinfo.output_width / 3;
152
1.02k
      JDIMENSION xsize_cropped = cinfo.output_width / 3;
153
1.02k
      jpegli_crop_scanline(&cinfo, &xoffset, &xsize_cropped);
154
1.02k
    }
155
156
2.67k
    size_t bytes_per_sample = jpegli_bytes_per_sample(spec.output_type);
157
2.67k
    size_t stride =
158
2.67k
        bytes_per_sample * cinfo.output_components * cinfo.output_width;
159
2.67k
    size_t buffer_size = *ysize * stride;
160
2.67k
    pixels->resize(buffer_size);
161
19.5M
    for (size_t y = 0; y < *ysize; ++y) {
162
19.5M
      JSAMPROW rows[] = {pixels->data() + y * stride};
163
19.5M
      jpegli_read_scanlines(&cinfo, rows, 1);
164
19.5M
    }
165
2.67k
    Consume(pixels->cbegin(), pixels->cend());
166
2.67k
    jpegli_finish_decompress(&cinfo);
167
2.67k
    return true;
168
2.71k
  };
169
4.69k
  bool success = try_catch_block();
170
4.69k
  jpegli_destroy_decompress(&cinfo);
171
4.69k
  return success;
172
4.69k
}
173
174
4.69k
int DoTestOneInput(const uint8_t* data, size_t size) {
175
4.69k
  if (size < 4) return 0;
176
4.69k
  uint32_t flags = 0;
177
4.69k
  size_t used_flag_bits = 0;
178
4.69k
  memcpy(&flags, data + size - 4, 4);
179
4.69k
  size -= 4;
180
181
23.4k
  const auto getFlag = [&flags, &used_flag_bits](size_t max_value) {
182
23.4k
    size_t limit = 1;
183
75.1k
    while (limit <= max_value) {
184
51.6k
      limit <<= 1;
185
51.6k
      used_flag_bits++;
186
51.6k
      if (used_flag_bits > 32) abort();
187
51.6k
    }
188
23.4k
    uint32_t result = flags % limit;
189
23.4k
    flags /= limit;
190
23.4k
    return result % (max_value + 1);
191
23.4k
  };
192
193
4.69k
  FuzzSpec spec;
194
4.69k
  spec.output_type = static_cast<JpegliDataType>(getFlag(JPEGLI_TYPE_UINT16));
195
4.69k
  spec.output_endianness =
196
4.69k
      static_cast<JpegliEndianness>(getFlag(JPEGLI_BIG_ENDIAN));
197
4.69k
  uint32_t chunks = getFlag(15);
198
4.69k
  spec.chunk_size = chunks ? 1u << (chunks - 1) : 0;
199
4.69k
  spec.crop_output = getFlag(1);
200
201
4.69k
  std::vector<uint8_t> pixels;
202
4.69k
  size_t xsize;
203
4.69k
  size_t ysize;
204
4.69k
  size_t max_pixels = 1 << 21;
205
206
4.69k
  const auto targets = hwy::SupportedAndGeneratedTargets();
207
4.69k
  hwy::SetSupportedTargetsForTest(targets[getFlag(targets.size() - 1)]);
208
4.69k
  DecodeJpeg(data, size, max_pixels, spec, &pixels, &xsize, &ysize);
209
4.69k
  hwy::SetSupportedTargetsForTest(0);
210
211
4.69k
  return 0;
212
4.69k
}
213
214
}  // namespace
215
216
67.9k
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
217
67.9k
  return DoTestOneInput(data, size);
218
67.9k
}
219
220
0
void TestOneInput(const std::vector<uint8_t>& data) {
221
0
  DoTestOneInput(data.data(), data.size());
222
0
}
223
224
FUZZ_TEST(JpegliDecFuzzTest, TestOneInput);