tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

jpg.cc (12294B)


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