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