/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); |