Coverage Report

Created: 2025-06-13 07:37

/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