/src/libjxl/lib/extras/dec/jpg.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 "lib/extras/dec/jpg.h" |
7 | | |
8 | | #include <cstdint> |
9 | | |
10 | | #include "lib/extras/dec/color_hints.h" |
11 | | #include "lib/extras/packed_image.h" |
12 | | #include "lib/extras/size_constraints.h" |
13 | | #include "lib/jxl/base/span.h" |
14 | | #include "lib/jxl/base/status.h" |
15 | | |
16 | | #if !JPEGXL_ENABLE_JPEG |
17 | | |
18 | | namespace jxl { |
19 | | namespace extras { |
20 | 0 | bool CanDecodeJPG() { return false; } |
21 | | Status DecodeImageJPG(const Span<const uint8_t> bytes, |
22 | | const ColorHints& color_hints, PackedPixelFile* ppf, |
23 | | const SizeConstraints* constraints, |
24 | 4 | const JPGDecompressParams* dparams) { |
25 | 4 | return false; |
26 | 4 | } |
27 | | } // namespace extras |
28 | | } // namespace jxl |
29 | | |
30 | | #else // JPEGXL_ENABLE_JPEG |
31 | | |
32 | | #include <jxl/codestream_header.h> |
33 | | #include <jxl/color_encoding.h> |
34 | | #include <jxl/types.h> |
35 | | |
36 | | #include <algorithm> |
37 | | #include <cstring> |
38 | | #include <memory> |
39 | | #include <numeric> |
40 | | #include <utility> |
41 | | #include <vector> |
42 | | |
43 | | #include "lib/jxl/base/compiler_specific.h" |
44 | | #include "lib/jxl/base/include_jpeglib.h" |
45 | | #include "lib/jxl/base/sanitizers.h" |
46 | | |
47 | | namespace jxl { |
48 | | namespace extras { |
49 | | |
50 | | namespace { |
51 | | |
52 | | constexpr unsigned char kICCSignature[12] = { |
53 | | 0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00}; |
54 | | constexpr int kICCMarker = JPEG_APP0 + 2; |
55 | | |
56 | | constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69, |
57 | | 0x66, 0x00, 0x00}; |
58 | | constexpr int kExifMarker = JPEG_APP0 + 1; |
59 | | |
60 | | inline bool IsJPG(const Span<const uint8_t> bytes) { |
61 | | if (bytes.size() < 2) return false; |
62 | | if (bytes[0] != 0xFF || bytes[1] != 0xD8) return false; |
63 | | return true; |
64 | | } |
65 | | |
66 | | bool MarkerIsICC(const jpeg_saved_marker_ptr marker) { |
67 | | return marker->marker == kICCMarker && |
68 | | marker->data_length >= sizeof kICCSignature + 2 && |
69 | | std::equal(std::begin(kICCSignature), std::end(kICCSignature), |
70 | | marker->data); |
71 | | } |
72 | | bool MarkerIsExif(const jpeg_saved_marker_ptr marker) { |
73 | | return marker->marker == kExifMarker && |
74 | | marker->data_length >= sizeof kExifSignature + 2 && |
75 | | std::equal(std::begin(kExifSignature), std::end(kExifSignature), |
76 | | marker->data); |
77 | | } |
78 | | |
79 | | Status ReadICCProfile(jpeg_decompress_struct* const cinfo, |
80 | | std::vector<uint8_t>* const icc) { |
81 | | constexpr size_t kICCSignatureSize = sizeof kICCSignature; |
82 | | // ICC signature + uint8_t index + uint8_t max_index. |
83 | | constexpr size_t kICCHeadSize = kICCSignatureSize + 2; |
84 | | // Markers are 1-indexed, and we keep them that way in this vector to get a |
85 | | // convenient 0 at the front for when we compute the offsets later. |
86 | | std::vector<size_t> marker_lengths; |
87 | | int num_markers = 0; |
88 | | int seen_markers_count = 0; |
89 | | bool has_num_markers = false; |
90 | | for (jpeg_saved_marker_ptr marker = cinfo->marker_list; marker != nullptr; |
91 | | marker = marker->next) { |
92 | | // marker is initialized by libjpeg, which we are not instrumenting with |
93 | | // msan. |
94 | | msan::UnpoisonMemory(marker, sizeof(*marker)); |
95 | | msan::UnpoisonMemory(marker->data, marker->data_length); |
96 | | if (!MarkerIsICC(marker)) continue; |
97 | | |
98 | | const int current_marker = marker->data[kICCSignatureSize]; |
99 | | if (current_marker == 0) { |
100 | | return JXL_FAILURE("inconsistent JPEG ICC marker numbering"); |
101 | | } |
102 | | const int current_num_markers = marker->data[kICCSignatureSize + 1]; |
103 | | if (current_marker > current_num_markers) { |
104 | | return JXL_FAILURE("inconsistent JPEG ICC marker numbering"); |
105 | | } |
106 | | if (has_num_markers) { |
107 | | if (current_num_markers != num_markers) { |
108 | | return JXL_FAILURE("inconsistent numbers of JPEG ICC markers"); |
109 | | } |
110 | | } else { |
111 | | num_markers = current_num_markers; |
112 | | has_num_markers = true; |
113 | | marker_lengths.resize(num_markers + 1); |
114 | | } |
115 | | |
116 | | size_t marker_length = marker->data_length - kICCHeadSize; |
117 | | |
118 | | if (marker_length == 0) { |
119 | | // NB: if we allow empty chunks, then the next check is incorrect. |
120 | | return JXL_FAILURE("Empty ICC chunk"); |
121 | | } |
122 | | |
123 | | if (marker_lengths[current_marker] != 0) { |
124 | | return JXL_FAILURE("duplicate JPEG ICC marker number"); |
125 | | } |
126 | | marker_lengths[current_marker] = marker_length; |
127 | | seen_markers_count++; |
128 | | } |
129 | | |
130 | | if (marker_lengths.empty()) { |
131 | | // Not an error. |
132 | | return false; |
133 | | } |
134 | | |
135 | | if (seen_markers_count != num_markers) { |
136 | | JXL_ENSURE(has_num_markers); |
137 | | return JXL_FAILURE("Incomplete set of ICC chunks"); |
138 | | } |
139 | | |
140 | | std::vector<size_t> offsets = std::move(marker_lengths); |
141 | | std::partial_sum(offsets.begin(), offsets.end(), offsets.begin()); |
142 | | icc->resize(offsets.back()); |
143 | | |
144 | | for (jpeg_saved_marker_ptr marker = cinfo->marker_list; marker != nullptr; |
145 | | marker = marker->next) { |
146 | | if (!MarkerIsICC(marker)) continue; |
147 | | const uint8_t* first = marker->data + kICCHeadSize; |
148 | | uint8_t current_marker = marker->data[kICCSignatureSize]; |
149 | | size_t offset = offsets[current_marker - 1]; |
150 | | size_t marker_length = offsets[current_marker] - offset; |
151 | | std::copy_n(first, marker_length, icc->data() + offset); |
152 | | } |
153 | | |
154 | | return true; |
155 | | } |
156 | | |
157 | | void ReadExif(jpeg_decompress_struct* const cinfo, |
158 | | std::vector<uint8_t>* const exif) { |
159 | | constexpr size_t kExifSignatureSize = sizeof kExifSignature; |
160 | | for (jpeg_saved_marker_ptr marker = cinfo->marker_list; marker != nullptr; |
161 | | marker = marker->next) { |
162 | | // marker is initialized by libjpeg, which we are not instrumenting with |
163 | | // msan. |
164 | | msan::UnpoisonMemory(marker, sizeof(*marker)); |
165 | | msan::UnpoisonMemory(marker->data, marker->data_length); |
166 | | if (!MarkerIsExif(marker)) continue; |
167 | | size_t marker_length = marker->data_length - kExifSignatureSize; |
168 | | exif->resize(marker_length); |
169 | | std::copy_n(marker->data + kExifSignatureSize, marker_length, exif->data()); |
170 | | return; |
171 | | } |
172 | | } |
173 | | |
174 | | void MyErrorExit(j_common_ptr cinfo) { |
175 | | jmp_buf* env = static_cast<jmp_buf*>(cinfo->client_data); |
176 | | (*cinfo->err->output_message)(cinfo); |
177 | | jpeg_destroy_decompress(reinterpret_cast<j_decompress_ptr>(cinfo)); |
178 | | longjmp(*env, 1); |
179 | | } |
180 | | |
181 | | void MyOutputMessage(j_common_ptr cinfo) { |
182 | | if (JXL_IS_DEBUG_BUILD) { |
183 | | char buf[JMSG_LENGTH_MAX + 1]; |
184 | | (*cinfo->err->format_message)(cinfo, buf); |
185 | | buf[JMSG_LENGTH_MAX] = 0; |
186 | | JXL_WARNING("%s", buf); |
187 | | } |
188 | | } |
189 | | |
190 | | Status UnmapColors(uint8_t* row, size_t xsize, int components, |
191 | | JSAMPARRAY colormap, size_t num_colors) { |
192 | | JXL_ENSURE(colormap != nullptr); |
193 | | std::vector<uint8_t> tmp(xsize * components); |
194 | | for (size_t x = 0; x < xsize; ++x) { |
195 | | JXL_ENSURE(row[x] < num_colors); |
196 | | for (int c = 0; c < components; ++c) { |
197 | | tmp[x * components + c] = colormap[c][row[x]]; |
198 | | } |
199 | | } |
200 | | memcpy(row, tmp.data(), tmp.size()); |
201 | | return true; |
202 | | } |
203 | | |
204 | | } // namespace |
205 | | |
206 | | bool CanDecodeJPG() { return true; } |
207 | | |
208 | | Status DecodeImageJPG(const Span<const uint8_t> bytes, |
209 | | const ColorHints& color_hints, PackedPixelFile* ppf, |
210 | | const SizeConstraints* constraints, |
211 | | const JPGDecompressParams* dparams) { |
212 | | // Don't do anything for non-JPEG files (no need to report an error) |
213 | | if (!IsJPG(bytes)) return false; |
214 | | |
215 | | // TODO(veluca): use JPEGData also for pixels? |
216 | | |
217 | | // We need to declare all the non-trivial destructor local variables before |
218 | | // the call to setjmp(). |
219 | | std::unique_ptr<JSAMPLE[]> row; |
220 | | |
221 | | const auto try_catch_block = [&]() -> bool { |
222 | | jpeg_decompress_struct cinfo = {}; |
223 | | // Setup error handling in jpeg library so we can deal with broken jpegs in |
224 | | // the fuzzer. |
225 | | jpeg_error_mgr jerr; |
226 | | jmp_buf env; |
227 | | cinfo.err = jpeg_std_error(&jerr); |
228 | | jerr.error_exit = &MyErrorExit; |
229 | | jerr.output_message = &MyOutputMessage; |
230 | | if (setjmp(env)) { |
231 | | return false; |
232 | | } |
233 | | cinfo.client_data = static_cast<void*>(&env); |
234 | | |
235 | | jpeg_create_decompress(&cinfo); |
236 | | jpeg_mem_src(&cinfo, reinterpret_cast<const unsigned char*>(bytes.data()), |
237 | | bytes.size()); |
238 | | jpeg_save_markers(&cinfo, kICCMarker, 0xFFFF); |
239 | | jpeg_save_markers(&cinfo, kExifMarker, 0xFFFF); |
240 | | const auto failure = [&cinfo](const char* str) -> Status { |
241 | | jpeg_abort_decompress(&cinfo); |
242 | | jpeg_destroy_decompress(&cinfo); |
243 | | return JXL_FAILURE("%s", str); |
244 | | }; |
245 | | int read_header_result = jpeg_read_header(&cinfo, TRUE); |
246 | | // TODO(eustas): what about JPEG_HEADER_TABLES_ONLY? |
247 | | if (read_header_result == JPEG_SUSPENDED) { |
248 | | return failure("truncated JPEG input"); |
249 | | } |
250 | | if (!VerifyDimensions(constraints, cinfo.image_width, cinfo.image_height)) { |
251 | | return failure("image too big"); |
252 | | } |
253 | | // Might cause CPU-zip bomb. |
254 | | if (cinfo.arith_code) { |
255 | | return failure("arithmetic code JPEGs are not supported"); |
256 | | } |
257 | | int nbcomp = cinfo.num_components; |
258 | | if (nbcomp != 1 && nbcomp != 3) { |
259 | | return failure("unsupported number of components in JPEG"); |
260 | | } |
261 | | if (ReadICCProfile(&cinfo, &ppf->icc)) { |
262 | | ppf->primary_color_representation = PackedPixelFile::kIccIsPrimary; |
263 | | } else { |
264 | | ppf->primary_color_representation = |
265 | | PackedPixelFile::kColorEncodingIsPrimary; |
266 | | ppf->icc.clear(); |
267 | | // Default to SRGB |
268 | | // Actually, (cinfo.output_components == nbcomp) will be checked after |
269 | | // `jpeg_start_decompress`. |
270 | | ppf->color_encoding.color_space = |
271 | | (nbcomp == 1) ? JXL_COLOR_SPACE_GRAY : JXL_COLOR_SPACE_RGB; |
272 | | ppf->color_encoding.white_point = JXL_WHITE_POINT_D65; |
273 | | ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB; |
274 | | ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB; |
275 | | ppf->color_encoding.rendering_intent = JXL_RENDERING_INTENT_PERCEPTUAL; |
276 | | } |
277 | | ReadExif(&cinfo, &ppf->metadata.exif); |
278 | | if (!ApplyColorHints(color_hints, /*color_already_set=*/true, |
279 | | /*is_gray=*/false, ppf)) { |
280 | | return failure("ApplyColorHints failed"); |
281 | | } |
282 | | |
283 | | ppf->info.xsize = cinfo.image_width; |
284 | | ppf->info.ysize = cinfo.image_height; |
285 | | // Original data is uint, so exponent_bits_per_sample = 0. |
286 | | ppf->info.bits_per_sample = BITS_IN_JSAMPLE; |
287 | | static_assert(BITS_IN_JSAMPLE == 8 || BITS_IN_JSAMPLE == 16, |
288 | | "Only 8/16 bit samples are supported"); |
289 | | ppf->info.exponent_bits_per_sample = 0; |
290 | | ppf->info.uses_original_profile = JXL_TRUE; |
291 | | |
292 | | // No alpha in JPG |
293 | | ppf->info.alpha_bits = 0; |
294 | | ppf->info.alpha_exponent_bits = 0; |
295 | | |
296 | | ppf->info.num_color_channels = nbcomp; |
297 | | ppf->info.orientation = JXL_ORIENT_IDENTITY; |
298 | | |
299 | | if (dparams && dparams->num_colors > 0) { |
300 | | cinfo.quantize_colors = TRUE; |
301 | | cinfo.desired_number_of_colors = dparams->num_colors; |
302 | | cinfo.two_pass_quantize = static_cast<boolean>(dparams->two_pass_quant); |
303 | | cinfo.dither_mode = static_cast<J_DITHER_MODE>(dparams->dither_mode); |
304 | | } |
305 | | |
306 | | jpeg_start_decompress(&cinfo); |
307 | | JXL_ENSURE(cinfo.out_color_components == nbcomp); |
308 | | JxlDataType data_type = |
309 | | ppf->info.bits_per_sample <= 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16; |
310 | | |
311 | | const JxlPixelFormat format{ |
312 | | /*num_channels=*/static_cast<uint32_t>(nbcomp), |
313 | | data_type, |
314 | | /*endianness=*/JXL_NATIVE_ENDIAN, |
315 | | /*align=*/0, |
316 | | }; |
317 | | ppf->frames.clear(); |
318 | | // Allocates the frame buffer. |
319 | | { |
320 | | JXL_ASSIGN_OR_RETURN( |
321 | | PackedFrame frame, |
322 | | PackedFrame::Create(cinfo.image_width, cinfo.image_height, format)); |
323 | | ppf->frames.emplace_back(std::move(frame)); |
324 | | } |
325 | | const auto& frame = ppf->frames.back(); |
326 | | JXL_ENSURE(sizeof(JSAMPLE) * cinfo.out_color_components * |
327 | | cinfo.image_width <= |
328 | | frame.color.stride); |
329 | | |
330 | | if (cinfo.quantize_colors) { |
331 | | JSAMPLE** colormap = cinfo.colormap; |
332 | | jxl::msan::UnpoisonMemory(reinterpret_cast<void*>(colormap), |
333 | | cinfo.out_color_components * sizeof(JSAMPLE*)); |
334 | | for (int c = 0; c < cinfo.out_color_components; ++c) { |
335 | | jxl::msan::UnpoisonMemory( |
336 | | reinterpret_cast<void*>(colormap[c]), |
337 | | cinfo.actual_number_of_colors * sizeof(JSAMPLE)); |
338 | | } |
339 | | } |
340 | | if (dparams && dparams->num_colors > 0) { |
341 | | JXL_ENSURE(cinfo.colormap != nullptr); |
342 | | } |
343 | | for (size_t y = 0; y < cinfo.image_height; ++y) { |
344 | | JSAMPROW rows[] = {reinterpret_cast<JSAMPLE*>( |
345 | | static_cast<uint8_t*>(frame.color.pixels()) + |
346 | | frame.color.stride * y)}; |
347 | | jpeg_read_scanlines(&cinfo, rows, 1); |
348 | | msan::UnpoisonMemory(rows[0], sizeof(JSAMPLE) * cinfo.output_components * |
349 | | cinfo.image_width); |
350 | | if (dparams && dparams->num_colors > 0) { |
351 | | JXL_RETURN_IF_ERROR( |
352 | | UnmapColors(rows[0], cinfo.output_width, cinfo.out_color_components, |
353 | | cinfo.colormap, cinfo.actual_number_of_colors)); |
354 | | } |
355 | | } |
356 | | |
357 | | jpeg_finish_decompress(&cinfo); |
358 | | jpeg_destroy_decompress(&cinfo); |
359 | | return true; |
360 | | }; |
361 | | |
362 | | return try_catch_block(); |
363 | | } |
364 | | |
365 | | } // namespace extras |
366 | | } // namespace jxl |
367 | | |
368 | | #endif // JPEGXL_ENABLE_JPEG |