/src/libjxl/tools/djxl_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 <jxl/codestream_header.h> |
7 | | #include <jxl/decode.h> |
8 | | #include <jxl/decode_cxx.h> |
9 | | #include <jxl/memory_manager.h> |
10 | | #include <jxl/thread_parallel_runner.h> |
11 | | #include <jxl/thread_parallel_runner_cxx.h> |
12 | | #include <jxl/types.h> |
13 | | |
14 | | #include <algorithm> |
15 | | #include <cstdint> |
16 | | #include <cstdio> |
17 | | #include <cstdlib> |
18 | | #include <cstring> |
19 | | #include <hwy/targets.h> |
20 | | #include <memory> |
21 | | #include <mutex> |
22 | | #include <random> |
23 | | #include <vector> |
24 | | |
25 | | #include "lib/jxl/fuzztest.h" |
26 | | #include "tools/tracking_memory_manager.h" |
27 | | |
28 | | namespace { |
29 | | |
30 | | // Externally visible value to ensure pixels are used in the fuzzer. |
31 | | int external_code = 0; |
32 | | |
33 | | constexpr const size_t kStreamingTargetNumberOfChunks = 128; |
34 | | |
35 | | using ::jpegxl::tools::kGiB; |
36 | | using ::jpegxl::tools::TrackingMemoryManager; |
37 | | |
38 | 20.4k | void CheckImpl(bool ok, const char* condition, const char* file, int line) { |
39 | 20.4k | if (!ok) { |
40 | 0 | fprintf(stderr, "Check(%s) failed at %s:%d\n", condition, file, line); |
41 | 0 | __builtin_trap(); |
42 | 0 | } |
43 | 20.4k | } |
44 | 20.4k | #define Check(OK) CheckImpl((OK), #OK, __FILE__, __LINE__) |
45 | | |
46 | | // Options for the fuzzing |
47 | | struct FuzzSpec { |
48 | | JxlDataType output_type; |
49 | | JxlEndianness output_endianness; |
50 | | size_t output_align; |
51 | | bool get_alpha; |
52 | | bool get_grayscale; |
53 | | bool use_streaming; |
54 | | bool jpeg_to_pixels; // decode to pixels even if it is JPEG-reconstructible |
55 | | // Whether to use the callback mechanism for the output image or not. |
56 | | bool use_callback; |
57 | | bool keep_orientation; |
58 | | bool decode_boxes; |
59 | | bool coalescing; |
60 | | // Used for random variation of chunk sizes, extra channels, ... to get |
61 | | uint32_t random_seed; |
62 | | }; |
63 | | |
64 | | template <typename It> |
65 | 212k | void Consume(const It& begin, const It& end) { |
66 | 3.58G | for (auto it = begin; it < end; ++it) { |
67 | 3.58G | if (*it == 0) { |
68 | 1.45G | external_code ^= ~0; |
69 | 2.13G | } else { |
70 | 2.13G | external_code ^= *it; |
71 | 2.13G | } |
72 | 3.58G | } |
73 | 212k | } djxl_fuzzer.cc:void (anonymous namespace)::Consume<unsigned char const*>(unsigned char const* const&, unsigned char const* const&) Line | Count | Source | 65 | 86.4k | void Consume(const It& begin, const It& end) { | 66 | 6.84M | for (auto it = begin; it < end; ++it) { | 67 | 6.75M | if (*it == 0) { | 68 | 6.38M | external_code ^= ~0; | 69 | 6.38M | } else { | 70 | 371k | external_code ^= *it; | 71 | 371k | } | 72 | 6.75M | } | 73 | 86.4k | } |
djxl_fuzzer.cc:void (anonymous namespace)::Consume<std::__1::__wrap_iter<char const*> >(std::__1::__wrap_iter<char const*> const&, std::__1::__wrap_iter<char const*> const&) Line | Count | Source | 65 | 70.1k | void Consume(const It& begin, const It& end) { | 66 | 269k | for (auto it = begin; it < end; ++it) { | 67 | 198k | if (*it == 0) { | 68 | 86.8k | external_code ^= ~0; | 69 | 112k | } else { | 70 | 112k | external_code ^= *it; | 71 | 112k | } | 72 | 198k | } | 73 | 70.1k | } |
djxl_fuzzer.cc:void (anonymous namespace)::Consume<std::__1::__wrap_iter<unsigned char const*> >(std::__1::__wrap_iter<unsigned char const*> const&, std::__1::__wrap_iter<unsigned char const*> const&) Line | Count | Source | 65 | 51.1k | void Consume(const It& begin, const It& end) { | 66 | 3.57G | for (auto it = begin; it < end; ++it) { | 67 | 3.57G | if (*it == 0) { | 68 | 1.44G | external_code ^= ~0; | 69 | 2.12G | } else { | 70 | 2.12G | external_code ^= *it; | 71 | 2.12G | } | 72 | 3.57G | } | 73 | 51.1k | } |
djxl_fuzzer.cc:void (anonymous namespace)::Consume<std::__1::__wrap_iter<unsigned char*> >(std::__1::__wrap_iter<unsigned char*> const&, std::__1::__wrap_iter<unsigned char*> const&) Line | Count | Source | 65 | 4.63k | void Consume(const It& begin, const It& end) { | 66 | 92.2k | for (auto it = begin; it < end; ++it) { | 67 | 87.6k | if (*it == 0) { | 68 | 25.7k | external_code ^= ~0; | 69 | 61.9k | } else { | 70 | 61.9k | external_code ^= *it; | 71 | 61.9k | } | 72 | 87.6k | } | 73 | 4.63k | } |
|
74 | | |
75 | | template <typename T> |
76 | 86.4k | void Consume(const T& entry) { |
77 | 86.4k | const uint8_t* begin = reinterpret_cast<const uint8_t*>(&entry); |
78 | 86.4k | Consume(begin, begin + sizeof(T)); |
79 | 86.4k | } djxl_fuzzer.cc:void (anonymous namespace)::Consume<JxlBasicInfo>(JxlBasicInfo const&) Line | Count | Source | 76 | 16.3k | void Consume(const T& entry) { | 77 | 16.3k | const uint8_t* begin = reinterpret_cast<const uint8_t*>(&entry); | 78 | 16.3k | Consume(begin, begin + sizeof(T)); | 79 | 16.3k | } |
djxl_fuzzer.cc:void (anonymous namespace)::Consume<JxlExtraChannelInfo>(JxlExtraChannelInfo const&) Line | Count | Source | 76 | 41.7k | void Consume(const T& entry) { | 77 | 41.7k | const uint8_t* begin = reinterpret_cast<const uint8_t*>(&entry); | 78 | 41.7k | Consume(begin, begin + sizeof(T)); | 79 | 41.7k | } |
djxl_fuzzer.cc:void (anonymous namespace)::Consume<JxlFrameHeader>(JxlFrameHeader const&) Line | Count | Source | 76 | 28.3k | void Consume(const T& entry) { | 77 | 28.3k | const uint8_t* begin = reinterpret_cast<const uint8_t*>(&entry); | 78 | 28.3k | Consume(begin, begin + sizeof(T)); | 79 | 28.3k | } |
|
80 | | |
81 | | // use_streaming: if true, decodes the data in small chunks, if false, decodes |
82 | | // it in one shot. |
83 | | bool DecodeJpegXl(const uint8_t* jxl, size_t size, |
84 | | JxlMemoryManager* memory_manager, size_t max_pixels, |
85 | | size_t max_total_pixels, const FuzzSpec& spec, |
86 | | std::vector<uint8_t>* pixels, std::vector<uint8_t>* jpeg, |
87 | | size_t* xsize, size_t* ysize, |
88 | 20.4k | std::vector<uint8_t>* icc_profile) { |
89 | 20.4k | size_t total_pixels = 0; |
90 | 20.4k | size_t retry_pixel_tax = 0; |
91 | | // Multi-threaded parallel runner. Limit to max 2 threads since the fuzzer |
92 | | // itself is already multithreaded. |
93 | 20.4k | size_t num_threads = |
94 | 20.4k | std::min<size_t>(2, JxlThreadParallelRunnerDefaultNumWorkerThreads()); |
95 | 20.4k | auto runner = JxlThreadParallelRunnerMake(memory_manager, num_threads); |
96 | | |
97 | 20.4k | auto mt = std::make_unique<std::mt19937>(spec.random_seed); |
98 | 20.4k | std::exponential_distribution<> dis_streaming(kStreamingTargetNumberOfChunks); |
99 | | |
100 | 20.4k | auto dec = JxlDecoderMake(memory_manager); |
101 | 20.4k | if (JXL_DEC_SUCCESS != |
102 | 20.4k | JxlDecoderSubscribeEvents( |
103 | 20.4k | dec.get(), JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | |
104 | 20.4k | JXL_DEC_PREVIEW_IMAGE | JXL_DEC_FRAME | |
105 | 20.4k | JXL_DEC_FULL_IMAGE | JXL_DEC_JPEG_RECONSTRUCTION | |
106 | 20.4k | JXL_DEC_BOX)) { |
107 | 0 | return false; |
108 | 0 | } |
109 | 20.4k | if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(), |
110 | 20.4k | JxlThreadParallelRunner, |
111 | 20.4k | runner.get())) { |
112 | 0 | return false; |
113 | 0 | } |
114 | 20.4k | if (JXL_DEC_SUCCESS != JxlDecoderSetKeepOrientation( |
115 | 20.4k | dec.get(), TO_JXL_BOOL(spec.keep_orientation))) { |
116 | 0 | abort(); |
117 | 0 | } |
118 | 20.4k | if (JXL_DEC_SUCCESS != |
119 | 20.4k | JxlDecoderSetCoalescing(dec.get(), TO_JXL_BOOL(spec.coalescing))) { |
120 | 0 | abort(); |
121 | 0 | } |
122 | 20.4k | JxlBasicInfo info; |
123 | 20.4k | uint32_t channels = (spec.get_grayscale ? 1 : 3) + (spec.get_alpha ? 1 : 0); |
124 | 20.4k | JxlPixelFormat format = {channels, spec.output_type, spec.output_endianness, |
125 | 20.4k | spec.output_align}; |
126 | | |
127 | 20.4k | if (!spec.use_streaming) { |
128 | | // Set all input at once |
129 | 10.1k | JxlDecoderSetInput(dec.get(), jxl, size); |
130 | 10.1k | JxlDecoderCloseInput(dec.get()); |
131 | 10.1k | } |
132 | | |
133 | 20.4k | bool seen_basic_info = false; |
134 | 20.4k | bool seen_color_encoding = false; |
135 | 20.4k | bool seen_preview = false; |
136 | 20.4k | bool seen_need_image_out = false; |
137 | 20.4k | bool seen_full_image = false; |
138 | 20.4k | bool seen_frame = false; |
139 | 20.4k | uint32_t num_frames = 0; |
140 | 20.4k | bool seen_jpeg_reconstruction = false; |
141 | 20.4k | bool seen_jpeg_need_more_output = false; |
142 | | // If streaming and seen around half the input, test flushing |
143 | 20.4k | bool tested_flush = false; |
144 | | |
145 | | // Size made available for the streaming input, emulating a subset of the |
146 | | // full input size. |
147 | 20.4k | size_t streaming_size = 0; |
148 | 20.4k | size_t leftover = size; |
149 | 20.4k | size_t preview_xsize = 0; |
150 | 20.4k | size_t preview_ysize = 0; |
151 | 20.4k | bool want_preview = false; |
152 | 20.4k | std::vector<uint8_t> preview_pixels; |
153 | | |
154 | 20.4k | std::vector<uint8_t> extra_channel_pixels; |
155 | | |
156 | 20.4k | struct CalledRow { |
157 | 20.4k | std::vector<int> delta; |
158 | | // Use the pixel values. |
159 | 20.4k | uint32_t value = 0; |
160 | 20.4k | }; |
161 | | |
162 | | // Callback function used when decoding with use_callback. |
163 | 20.4k | struct DecodeCallbackData { |
164 | 20.4k | JxlBasicInfo info; |
165 | 20.4k | size_t xsize = 0; |
166 | 20.4k | size_t ysize = 0; |
167 | 20.4k | std::unique_ptr<std::mutex[]> called_rows_mutex; |
168 | | // For each row stores the segments of the row being called. For each row |
169 | | // the sum of all the int values in the map up to [i] (inclusive) tell how |
170 | | // many times a callback included the pixel i of that row. |
171 | 20.4k | std::vector<CalledRow> called_rows; |
172 | 20.4k | }; |
173 | 20.4k | DecodeCallbackData decode_callback_data; |
174 | 20.4k | auto decode_callback = +[](void* opaque, size_t x, size_t y, |
175 | 47.0M | size_t num_pixels, const void* pixels) { |
176 | 47.0M | DecodeCallbackData* data = static_cast<DecodeCallbackData*>(opaque); |
177 | 47.0M | if (num_pixels > data->xsize) abort(); |
178 | 47.0M | if (x + num_pixels > data->xsize) abort(); |
179 | 47.0M | if (y >= data->ysize) abort(); |
180 | 47.0M | if (num_pixels && !pixels) abort(); |
181 | | // Keep track of the segments being called by the callback. |
182 | 47.0M | { |
183 | 47.0M | const std::lock_guard<std::mutex> lock(data->called_rows_mutex.get()[y]); |
184 | 47.0M | auto& called_row = data->called_rows[y]; |
185 | 47.0M | called_row.delta[x]++; |
186 | 47.0M | called_row.delta[x + num_pixels]--; |
187 | 47.0M | called_row.value += *static_cast<const uint8_t*>(pixels); |
188 | 47.0M | } |
189 | 47.0M | }; |
190 | | |
191 | 20.4k | JxlExtraChannelInfo extra_channel_info; |
192 | | |
193 | 20.4k | std::vector<uint8_t> box_buffer; |
194 | | |
195 | 20.4k | if (spec.decode_boxes && |
196 | 9.62k | JXL_DEC_SUCCESS != JxlDecoderSetDecompressBoxes(dec.get(), JXL_TRUE)) { |
197 | | // error ignored, can still fuzz if it doesn't brotli-decompress brob boxes. |
198 | 0 | } |
199 | | |
200 | 690k | for (;;) { |
201 | 690k | JxlDecoderStatus status = JxlDecoderProcessInput(dec.get()); |
202 | 690k | if (status == JXL_DEC_ERROR) { |
203 | 19.2k | return false; |
204 | 670k | } else if (status == JXL_DEC_NEED_MORE_INPUT) { |
205 | 550k | if (spec.use_streaming) { |
206 | 550k | if (total_pixels > max_total_pixels) return false; |
207 | 550k | total_pixels += retry_pixel_tax; |
208 | 550k | size_t remaining = JxlDecoderReleaseInput(dec.get()); |
209 | | // move any remaining bytes to the front if necessary |
210 | 550k | size_t used = streaming_size - remaining; |
211 | 550k | jxl += used; |
212 | 550k | leftover -= used; |
213 | 550k | streaming_size -= used; |
214 | 550k | size_t chunk_size = std::max<size_t>( |
215 | 550k | 1, size * std::min<double>(1.0, dis_streaming(*mt))); |
216 | 550k | size_t add_size = |
217 | 550k | std::min<size_t>(chunk_size, leftover - streaming_size); |
218 | 550k | if (add_size == 0) { |
219 | | // End of the streaming data reached |
220 | 13 | return false; |
221 | 13 | } |
222 | 550k | streaming_size += add_size; |
223 | 550k | if (JXL_DEC_SUCCESS != |
224 | 550k | JxlDecoderSetInput(dec.get(), jxl, streaming_size)) { |
225 | 0 | return false; |
226 | 0 | } |
227 | 550k | if (leftover == streaming_size) { |
228 | | // All possible input bytes given |
229 | 7.88k | JxlDecoderCloseInput(dec.get()); |
230 | 7.88k | } |
231 | | |
232 | 550k | if (!tested_flush && seen_frame) { |
233 | | // Test flush max once to avoid too slow fuzzer run |
234 | 1.88k | tested_flush = true; |
235 | 1.88k | JxlDecoderFlushImage(dec.get()); |
236 | 1.88k | } |
237 | 550k | } else { |
238 | 0 | return false; |
239 | 0 | } |
240 | 550k | } else if (status == JXL_DEC_JPEG_NEED_MORE_OUTPUT) { |
241 | 0 | if (want_preview) abort(); // expected preview before frame |
242 | 0 | if (spec.jpeg_to_pixels) abort(); |
243 | 0 | if (!seen_jpeg_reconstruction) abort(); |
244 | 0 | seen_jpeg_need_more_output = true; |
245 | 0 | size_t used_jpeg_output = |
246 | 0 | jpeg->size() - JxlDecoderReleaseJPEGBuffer(dec.get()); |
247 | 0 | jpeg->resize(std::max<size_t>(4096, jpeg->size() * 2)); |
248 | 0 | uint8_t* jpeg_buffer = jpeg->data() + used_jpeg_output; |
249 | 0 | size_t jpeg_buffer_size = jpeg->size() - used_jpeg_output; |
250 | |
|
251 | 0 | if (JXL_DEC_SUCCESS != |
252 | 0 | JxlDecoderSetJPEGBuffer(dec.get(), jpeg_buffer, jpeg_buffer_size)) { |
253 | 0 | return false; |
254 | 0 | } |
255 | 120k | } else if (status == JXL_DEC_BASIC_INFO) { |
256 | 16.3k | if (seen_basic_info) abort(); // already seen basic info |
257 | 16.3k | seen_basic_info = true; |
258 | | |
259 | 16.3k | memset(&info, 0, sizeof(info)); |
260 | 16.3k | if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &info)) { |
261 | 0 | return false; |
262 | 0 | } |
263 | 16.3k | Consume(info); |
264 | | |
265 | 16.3k | *xsize = info.xsize; |
266 | 16.3k | *ysize = info.ysize; |
267 | 16.3k | decode_callback_data.info = info; |
268 | 16.3k | size_t num_pixels = *xsize * *ysize; |
269 | | // num_pixels overflow |
270 | 16.3k | if (*xsize != 0 && num_pixels / *xsize != *ysize) return false; |
271 | | // limit max memory of this fuzzer test |
272 | 16.3k | if (num_pixels > max_pixels) return false; |
273 | 16.2k | retry_pixel_tax = num_pixels; |
274 | | |
275 | 16.2k | if (info.have_preview) { |
276 | 1.14k | want_preview = true; |
277 | 1.14k | preview_xsize = info.preview.xsize; |
278 | 1.14k | preview_ysize = info.preview.ysize; |
279 | 1.14k | size_t preview_num_pixels = preview_xsize * preview_ysize; |
280 | | // num_pixels overflow |
281 | 1.14k | if (preview_xsize != 0 && |
282 | 1.14k | preview_num_pixels / preview_xsize != preview_ysize) { |
283 | 0 | return false; |
284 | 0 | } |
285 | | // limit max memory of this fuzzer test |
286 | 1.14k | if (preview_num_pixels > max_pixels) return false; |
287 | 1.14k | } |
288 | | |
289 | 58.0k | for (size_t ec = 0; ec < info.num_extra_channels; ++ec) { |
290 | 41.7k | memset(&extra_channel_info, 0, sizeof(extra_channel_info)); |
291 | 41.7k | if (JXL_DEC_SUCCESS != |
292 | 41.7k | JxlDecoderGetExtraChannelInfo(dec.get(), ec, &extra_channel_info)) { |
293 | 0 | abort(); |
294 | 0 | } |
295 | 41.7k | Consume(extra_channel_info); |
296 | 41.7k | std::vector<char> ec_name(extra_channel_info.name_length + 1); |
297 | 41.7k | if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelName(dec.get(), ec, |
298 | 41.7k | ec_name.data(), |
299 | 41.7k | ec_name.size())) { |
300 | 0 | abort(); |
301 | 0 | } |
302 | 41.7k | Consume(ec_name.cbegin(), ec_name.cend()); |
303 | 41.7k | } |
304 | 104k | } else if (status == JXL_DEC_COLOR_ENCODING) { |
305 | 14.8k | if (!seen_basic_info) abort(); // expected basic info first |
306 | 14.8k | if (seen_color_encoding) abort(); // already seen color encoding |
307 | 14.8k | seen_color_encoding = true; |
308 | | |
309 | | // Get the ICC color profile of the pixel data |
310 | 14.8k | size_t icc_size; |
311 | 14.8k | if (JXL_DEC_SUCCESS != |
312 | 14.8k | JxlDecoderGetICCProfileSize(dec.get(), JXL_COLOR_PROFILE_TARGET_DATA, |
313 | 14.8k | &icc_size)) { |
314 | 2 | return false; |
315 | 2 | } |
316 | 14.8k | icc_profile->resize(icc_size); |
317 | 14.8k | if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsICCProfile( |
318 | 14.8k | dec.get(), JXL_COLOR_PROFILE_TARGET_DATA, |
319 | 14.8k | icc_profile->data(), icc_profile->size())) { |
320 | 0 | return false; |
321 | 0 | } |
322 | 89.3k | } else if (status == JXL_DEC_NEED_PREVIEW_OUT_BUFFER) { |
323 | 1.05k | if (seen_preview) abort(); |
324 | 1.05k | if (!want_preview) abort(); |
325 | 1.05k | if (!seen_color_encoding) abort(); |
326 | 1.05k | size_t preview_size; |
327 | 1.05k | if (JXL_DEC_SUCCESS != |
328 | 1.05k | JxlDecoderPreviewOutBufferSize(dec.get(), &format, &preview_size)) { |
329 | 4 | return false; |
330 | 4 | } |
331 | 1.04k | preview_pixels.resize(preview_size); |
332 | 1.04k | if (JXL_DEC_SUCCESS != JxlDecoderSetPreviewOutBuffer( |
333 | 1.04k | dec.get(), &format, preview_pixels.data(), |
334 | 1.04k | preview_pixels.size())) { |
335 | 0 | abort(); |
336 | 0 | } |
337 | 88.2k | } else if (status == JXL_DEC_PREVIEW_IMAGE) { |
338 | 708 | if (seen_preview) abort(); |
339 | 708 | if (!want_preview) abort(); |
340 | 708 | if (!seen_color_encoding) abort(); |
341 | 708 | want_preview = false; |
342 | 708 | seen_preview = true; |
343 | 708 | Consume(preview_pixels.cbegin(), preview_pixels.cend()); |
344 | 87.5k | } else if (status == JXL_DEC_FRAME || |
345 | 59.2k | status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) { |
346 | 46.6k | if (want_preview) abort(); // expected preview before frame |
347 | 46.6k | if (!seen_color_encoding) abort(); // expected color encoding first |
348 | 46.6k | if (status == JXL_DEC_FRAME) { |
349 | 28.3k | if (seen_frame) abort(); // already seen JXL_DEC_FRAME |
350 | 28.3k | seen_frame = true; |
351 | 28.3k | JxlFrameHeader frame_header; |
352 | 28.3k | memset(&frame_header, 0, sizeof(frame_header)); |
353 | 28.3k | if (JXL_DEC_SUCCESS != |
354 | 28.3k | JxlDecoderGetFrameHeader(dec.get(), &frame_header)) { |
355 | 0 | abort(); |
356 | 0 | } |
357 | 28.3k | decode_callback_data.xsize = frame_header.layer_info.xsize; |
358 | 28.3k | decode_callback_data.ysize = frame_header.layer_info.ysize; |
359 | 28.3k | size_t num_pixels = static_cast<size_t>(frame_header.layer_info.xsize) * |
360 | 28.3k | frame_header.layer_info.ysize; |
361 | 28.3k | if (num_pixels > max_pixels) return false; |
362 | 28.3k | if (total_pixels > max_total_pixels) return false; |
363 | 28.3k | total_pixels += num_pixels; |
364 | 28.3k | if (!spec.coalescing) { |
365 | 26.4k | decode_callback_data.called_rows.clear(); |
366 | 26.4k | } |
367 | 28.3k | decode_callback_data.called_rows_mutex = |
368 | 28.3k | std::make_unique<std::mutex[]>(decode_callback_data.ysize); |
369 | 28.3k | decode_callback_data.called_rows.resize(decode_callback_data.ysize); |
370 | 15.1M | for (size_t y = 0; y < decode_callback_data.ysize; ++y) { |
371 | 15.1M | decode_callback_data.called_rows[y].delta.clear(); |
372 | 15.1M | decode_callback_data.called_rows[y].delta.resize( |
373 | 15.1M | decode_callback_data.xsize + 1); |
374 | 15.1M | } |
375 | 28.3k | Consume(frame_header); |
376 | 28.3k | std::vector<char> frame_name(frame_header.name_length + 1); |
377 | 28.3k | if (JXL_DEC_SUCCESS != JxlDecoderGetFrameName(dec.get(), |
378 | 28.3k | frame_name.data(), |
379 | 28.3k | frame_name.size())) { |
380 | 0 | abort(); |
381 | 0 | } |
382 | 28.3k | Consume(frame_name.cbegin(), frame_name.cend()); |
383 | | // When not testing streaming, test that JXL_DEC_NEED_IMAGE_OUT_BUFFER |
384 | | // occurs instead, so do not set buffer now. |
385 | 28.3k | if (!spec.use_streaming) continue; |
386 | 28.3k | } |
387 | 28.3k | if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) { |
388 | | // expected JXL_DEC_FRAME instead |
389 | 18.3k | if (!seen_frame) abort(); |
390 | | // already should have set buffer if streaming |
391 | 18.3k | if (spec.use_streaming) abort(); |
392 | | // already seen need image out |
393 | 18.3k | if (seen_need_image_out) abort(); |
394 | 18.3k | seen_need_image_out = true; |
395 | 18.3k | } |
396 | | |
397 | 28.3k | if (info.num_extra_channels > 0) { |
398 | 8.31k | std::uniform_int_distribution<> dis(0, info.num_extra_channels); |
399 | 8.31k | size_t ec_index = dis(*mt); |
400 | | // There is also a probability no extra channel is chosen |
401 | 8.31k | if (ec_index < info.num_extra_channels) { |
402 | | // TODO(eustas): that looks suspicious to me |
403 | 4.87k | size_t last_ec_index = info.num_extra_channels - 1; |
404 | 4.87k | size_t ec_size; |
405 | 4.87k | if (JXL_DEC_SUCCESS != |
406 | 4.87k | JxlDecoderExtraChannelBufferSize(dec.get(), &format, &ec_size, |
407 | 4.87k | last_ec_index)) { |
408 | 12 | return false; |
409 | 12 | } |
410 | 4.86k | extra_channel_pixels.resize(ec_size); |
411 | 4.86k | if (JXL_DEC_SUCCESS != |
412 | 4.86k | JxlDecoderSetExtraChannelBuffer(dec.get(), &format, |
413 | 4.86k | extra_channel_pixels.data(), |
414 | 4.86k | ec_size, last_ec_index)) { |
415 | 0 | return false; |
416 | 0 | } |
417 | 4.86k | } |
418 | 8.31k | } |
419 | | |
420 | 28.3k | if (spec.use_callback) { |
421 | 9.17k | if (JXL_DEC_SUCCESS != |
422 | 9.17k | JxlDecoderSetImageOutCallback(dec.get(), &format, decode_callback, |
423 | 9.17k | &decode_callback_data)) { |
424 | 20 | return false; |
425 | 20 | } |
426 | 19.1k | } else { |
427 | | // Use the pixels output buffer. |
428 | 19.1k | size_t buffer_size; |
429 | 19.1k | if (JXL_DEC_SUCCESS != |
430 | 19.1k | JxlDecoderImageOutBufferSize(dec.get(), &format, &buffer_size)) { |
431 | 46 | return false; |
432 | 46 | } |
433 | 19.1k | pixels->resize(buffer_size); |
434 | 19.1k | void* pixels_buffer = static_cast<void*>(pixels->data()); |
435 | 19.1k | size_t pixels_buffer_size = pixels->size(); |
436 | 19.1k | if (JXL_DEC_SUCCESS != |
437 | 19.1k | JxlDecoderSetImageOutBuffer(dec.get(), &format, pixels_buffer, |
438 | 19.1k | pixels_buffer_size)) { |
439 | 0 | return false; |
440 | 0 | } |
441 | 19.1k | } |
442 | 40.8k | } else if (status == JXL_DEC_JPEG_RECONSTRUCTION) { |
443 | | // Do not check preview precedence here, since this event only declares |
444 | | // that JPEG is going to be decoded; though, when first byte of JPEG |
445 | | // arrives (JXL_DEC_JPEG_NEED_MORE_OUTPUT) it is certain that preview |
446 | | // should have been produced already. |
447 | 31 | if (seen_jpeg_reconstruction) abort(); |
448 | 31 | seen_jpeg_reconstruction = true; |
449 | 31 | if (!spec.jpeg_to_pixels) { |
450 | | // Make sure buffer is allocated, but current size is too small to |
451 | | // contain valid JPEG. |
452 | 27 | jpeg->resize(1); |
453 | 27 | uint8_t* jpeg_buffer = jpeg->data(); |
454 | 27 | size_t jpeg_buffer_size = jpeg->size(); |
455 | 27 | if (JXL_DEC_SUCCESS != |
456 | 27 | JxlDecoderSetJPEGBuffer(dec.get(), jpeg_buffer, jpeg_buffer_size)) { |
457 | 0 | return false; |
458 | 0 | } |
459 | 27 | } |
460 | 40.8k | } else if (status == JXL_DEC_FULL_IMAGE) { |
461 | 25.1k | if (want_preview) abort(); // expected preview before frame |
462 | 25.1k | if (!spec.jpeg_to_pixels && seen_jpeg_reconstruction) { |
463 | 0 | if (!seen_jpeg_need_more_output) abort(); |
464 | 0 | jpeg->resize(jpeg->size() - JxlDecoderReleaseJPEGBuffer(dec.get())); |
465 | 25.1k | } else { |
466 | | // expected need image out or frame first |
467 | 25.1k | if (!seen_need_image_out && !seen_frame) abort(); |
468 | 25.1k | } |
469 | | |
470 | 25.1k | seen_full_image = true; // there may be multiple if animated |
471 | | |
472 | | // There may be a next animation frame so expect those again: |
473 | 25.1k | seen_need_image_out = false; |
474 | 25.1k | seen_frame = false; |
475 | 25.1k | num_frames++; |
476 | | |
477 | | // "Use" all the pixels; MSAN needs a conditional to count as usage. |
478 | 25.1k | Consume(pixels->cbegin(), pixels->cend()); |
479 | 25.1k | Consume(jpeg->cbegin(), jpeg->cend()); |
480 | | |
481 | | // When not coalescing, check that the whole (possibly cropped) frame was |
482 | | // sent |
483 | 25.1k | if (seen_need_image_out && spec.use_callback && spec.coalescing) { |
484 | | // Check that the callback sent all the pixels |
485 | 0 | for (uint32_t y = 0; y < decode_callback_data.ysize; y++) { |
486 | 0 | const auto& deltas = decode_callback_data.called_rows[y].delta; |
487 | 0 | if (deltas[0] != 1) abort(); |
488 | 0 | if (deltas[decode_callback_data.xsize] != -1) abort(); |
489 | 0 | for (size_t i = 1; i < decode_callback_data.xsize; ++i) { |
490 | 0 | if (deltas[i] != 0) abort(); |
491 | 0 | } |
492 | 0 | } |
493 | 0 | } |
494 | | // Nothing to do. Do not yet return. If the image is an animation, more |
495 | | // full frames may be decoded. This example only keeps the last one. |
496 | 25.1k | } else if (status == JXL_DEC_SUCCESS) { |
497 | 904 | if (!seen_full_image) abort(); // expected full image before finishing |
498 | | |
499 | | // When decoding we may not get seen_need_image_out unless we were |
500 | | // decoding the image to pixels. |
501 | 904 | if (seen_need_image_out && spec.use_callback && spec.coalescing) { |
502 | | // Check that the callback sent all the pixels |
503 | 0 | for (uint32_t y = 0; y < decode_callback_data.ysize; y++) { |
504 | 0 | const auto& deltas = decode_callback_data.called_rows[y].delta; |
505 | 0 | if (deltas[0] != static_cast<int>(num_frames)) abort(); |
506 | 0 | if (deltas[decode_callback_data.xsize] != -1) abort(); |
507 | 0 | for (size_t i = 1; i < decode_callback_data.xsize; ++i) { |
508 | 0 | if (deltas[i] != 0) abort(); |
509 | 0 | } |
510 | 0 | } |
511 | 0 | } |
512 | | |
513 | | // All decoding successfully finished. |
514 | | // It's not required to call JxlDecoderReleaseInput(dec.get()) here since |
515 | | // the decoder will be destroyed. |
516 | 904 | return true; |
517 | 14.7k | } else if (status == JXL_DEC_BOX) { |
518 | 10.5k | if (spec.decode_boxes) { |
519 | 6.68k | if (!box_buffer.empty()) { |
520 | 4.64k | size_t remaining = JxlDecoderReleaseBoxBuffer(dec.get()); |
521 | 4.64k | size_t box_size = box_buffer.size() - remaining; |
522 | 4.64k | if (box_size != 0) { |
523 | 4.63k | Consume(box_buffer.begin(), box_buffer.begin() + box_size); |
524 | 4.63k | box_buffer.clear(); |
525 | 4.63k | } |
526 | 4.64k | } |
527 | 6.68k | box_buffer.resize(64); |
528 | 6.68k | JxlDecoderSetBoxBuffer(dec.get(), box_buffer.data(), box_buffer.size()); |
529 | 6.68k | } |
530 | 10.5k | } else if (status == JXL_DEC_BOX_NEED_MORE_OUTPUT) { |
531 | 4.19k | if (!spec.decode_boxes) { |
532 | 0 | abort(); // Not expected when not setting output buffer |
533 | 0 | } |
534 | 4.19k | size_t remaining = JxlDecoderReleaseBoxBuffer(dec.get()); |
535 | 4.19k | size_t box_size = box_buffer.size() - remaining; |
536 | 4.19k | box_buffer.resize(box_buffer.size() * 2); |
537 | 4.19k | JxlDecoderSetBoxBuffer(dec.get(), box_buffer.data() + box_size, |
538 | 4.19k | box_buffer.size() - box_size); |
539 | 4.19k | } else { |
540 | 0 | return false; |
541 | 0 | } |
542 | 690k | } |
543 | 20.4k | } |
544 | | |
545 | 20.4k | int DoTestOneInput(const uint8_t* data, size_t size) { |
546 | 20.4k | if (size < 4) return 0; |
547 | 20.4k | uint32_t flags = 0; |
548 | 20.4k | size_t used_flag_bits = 0; |
549 | 20.4k | memcpy(&flags, data + size - 4, 4); |
550 | 20.4k | size -= 4; |
551 | | |
552 | 245k | const auto getFlag = [&flags, &used_flag_bits](size_t max_value) { |
553 | 245k | size_t limit = 1; |
554 | 653k | while (limit <= max_value) { |
555 | 408k | limit <<= 1; |
556 | 408k | used_flag_bits++; |
557 | 408k | if (used_flag_bits > 32) abort(); |
558 | 408k | } |
559 | 245k | uint32_t result = flags % limit; |
560 | 245k | flags /= limit; |
561 | 245k | return result % (max_value + 1); |
562 | 245k | }; |
563 | 163k | const auto getBoolFlag = [&getFlag]() -> bool { |
564 | 163k | return static_cast<bool>(getFlag(1)); |
565 | 163k | }; |
566 | | |
567 | 20.4k | FuzzSpec spec; |
568 | | // Allows some different possible variations in the chunk sizes of the |
569 | | // streaming case |
570 | 20.4k | spec.random_seed = flags ^ size; |
571 | 20.4k | spec.get_alpha = getBoolFlag(); |
572 | 20.4k | spec.get_grayscale = getBoolFlag(); |
573 | 20.4k | spec.use_streaming = getBoolFlag(); |
574 | 20.4k | spec.jpeg_to_pixels = getBoolFlag(); |
575 | 20.4k | spec.use_callback = getBoolFlag(); |
576 | 20.4k | spec.keep_orientation = getBoolFlag(); |
577 | 20.4k | spec.coalescing = getBoolFlag(); |
578 | 20.4k | spec.output_type = static_cast<JxlDataType>(getFlag(JXL_TYPE_FLOAT16)); |
579 | 20.4k | spec.output_endianness = static_cast<JxlEndianness>(getFlag(JXL_BIG_ENDIAN)); |
580 | 20.4k | spec.output_align = getFlag(16); |
581 | 20.4k | spec.decode_boxes = getBoolFlag(); |
582 | | |
583 | 20.4k | std::vector<uint8_t> pixels; |
584 | 20.4k | std::vector<uint8_t> jpeg; |
585 | 20.4k | std::vector<uint8_t> icc; |
586 | 20.4k | size_t xsize; |
587 | 20.4k | size_t ysize; |
588 | 20.4k | size_t max_pixels = 1 << 21; |
589 | 20.4k | size_t max_total_pixels = 5 * max_pixels; |
590 | | |
591 | 20.4k | TrackingMemoryManager memory_manager{/* cap */ 1 * kGiB, |
592 | 20.4k | /* total_cap */ 5 * kGiB}; |
593 | 20.4k | const auto targets = hwy::SupportedAndGeneratedTargets(); |
594 | 20.4k | hwy::SetSupportedTargetsForTest(targets[getFlag(targets.size() - 1)]); |
595 | 20.4k | DecodeJpegXl(data, size, memory_manager.get(), max_pixels, max_total_pixels, |
596 | 20.4k | spec, &pixels, &jpeg, &xsize, &ysize, &icc); |
597 | 20.4k | hwy::SetSupportedTargetsForTest(0); |
598 | 20.4k | Check(memory_manager.Reset()); |
599 | | |
600 | 20.4k | return 0; |
601 | 20.4k | } |
602 | | |
603 | | } // namespace |
604 | | |
605 | 32.8k | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { |
606 | 32.8k | return DoTestOneInput(data, size); |
607 | 32.8k | } |
608 | | |
609 | 0 | void TestOneInput(const std::vector<uint8_t>& data) { |
610 | 0 | DoTestOneInput(data.data(), data.size()); |
611 | 0 | } |
612 | | |
613 | | FUZZ_TEST(DjxlFuzzTest, TestOneInput); |