Coverage Report

Created: 2026-10-17 07:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libwebp/imageio/jpegdec.c
Line
Count
Source
1
// Copyright 2012 Google Inc. All Rights Reserved.
2
//
3
// Use of this source code is governed by a BSD-style license
4
// that can be found in the COPYING file in the root of the source
5
// tree. An additional intellectual property rights grant can be found
6
// in the file PATENTS. All contributing project authors may
7
// be found in the AUTHORS file in the root of the source tree.
8
// -----------------------------------------------------------------------------
9
//
10
// JPEG decode.
11
12
#include "./jpegdec.h"
13
14
#ifdef HAVE_CONFIG_H
15
#include "webp/config.h"
16
#endif
17
18
#include <stdio.h>
19
20
#ifdef WEBP_HAVE_JPEG
21
#include <jerror.h>
22
#include <jpeglib.h>
23
#include <setjmp.h>
24
#include <stdlib.h>
25
#include <string.h>
26
27
#include "./imageio_util.h"
28
#include "./metadata.h"
29
#include "webp/encode.h"
30
#include "webp/types.h"
31
32
// -----------------------------------------------------------------------------
33
// Metadata processing
34
35
#ifndef JPEG_APP1
36
33
#define JPEG_APP1 (JPEG_APP0 + 1)
37
#endif
38
#ifndef JPEG_APP2
39
33
#define JPEG_APP2 (JPEG_APP0 + 2)
40
#endif
41
42
typedef struct {
43
  const uint8_t* data;
44
  size_t data_length;
45
  int seq;  // this segment's sequence number [1, 255] for use in reassembly.
46
} ICCPSegment;
47
48
33
static void SaveMetadataMarkers(j_decompress_ptr dinfo) {
49
33
  const unsigned int max_marker_length = 0xffff;
50
33
  jpeg_save_markers(dinfo, JPEG_APP1, max_marker_length);  // Exif/XMP
51
33
  jpeg_save_markers(dinfo, JPEG_APP2, max_marker_length);  // ICC profile
52
33
}
53
54
0
static int CompareICCPSegments(const void* a, const void* b) {
55
0
  const ICCPSegment* s1 = (const ICCPSegment*)a;
56
0
  const ICCPSegment* s2 = (const ICCPSegment*)b;
57
0
  return s1->seq - s2->seq;
58
0
}
59
60
// Extract ICC profile segments from the marker list in 'dinfo', reassembling
61
// and storing them in 'iccp'.
62
// Returns true on success and false for memory errors and corrupt profiles.
63
0
static int StoreICCP(j_decompress_ptr dinfo, MetadataPayload* const iccp) {
64
  // ICC.1:2010-12 (4.3.0.0) Annex B.4 Embedding ICC Profiles in JPEG files
65
0
  static const char kICCPSignature[] = "ICC_PROFILE";
66
0
  static const size_t kICCPSignatureLength = 12;  // signature includes '\0'
67
0
  static const size_t kICCPSkipLength = 14;       // signature + seq & count
68
0
  int expected_count = 0;
69
0
  int actual_count = 0;
70
0
  int seq_max = 0;
71
0
  size_t total_size = 0;
72
0
  ICCPSegment iccp_segments[255];
73
0
  jpeg_saved_marker_ptr marker;
74
75
0
  memset(iccp_segments, 0, sizeof(iccp_segments));
76
0
  for (marker = dinfo->marker_list; marker != NULL; marker = marker->next) {
77
0
    if (marker->marker == JPEG_APP2 && marker->data_length > kICCPSkipLength &&
78
0
        !memcmp(marker->data, kICCPSignature, kICCPSignatureLength)) {
79
      // ICC_PROFILE\0<seq><count>; 'seq' starts at 1.
80
0
      const int seq = marker->data[kICCPSignatureLength];
81
0
      const int count = marker->data[kICCPSignatureLength + 1];
82
0
      const size_t segment_size = marker->data_length - kICCPSkipLength;
83
0
      ICCPSegment* segment;
84
85
0
      if (segment_size == 0 || count == 0 || seq == 0) {
86
0
        fprintf(stderr,
87
0
                "[ICCP] size (%d) / count (%d) / sequence number (%d)"
88
0
                " cannot be 0!\n",
89
0
                (int)segment_size, seq, count);
90
0
        return 0;
91
0
      }
92
93
0
      if (expected_count == 0) {
94
0
        expected_count = count;
95
0
      } else if (expected_count != count) {
96
0
        fprintf(stderr, "[ICCP] Inconsistent segment count (%d / %d)!\n",
97
0
                expected_count, count);
98
0
        return 0;
99
0
      }
100
101
0
      segment = iccp_segments + seq - 1;
102
0
      if (segment->data_length != 0) {
103
0
        fprintf(stderr, "[ICCP] Duplicate segment number (%d)!\n", seq);
104
0
        return 0;
105
0
      }
106
107
0
      segment->data = marker->data + kICCPSkipLength;
108
0
      segment->data_length = segment_size;
109
0
      segment->seq = seq;
110
0
      total_size += segment_size;
111
0
      if (seq > seq_max) seq_max = seq;
112
0
      ++actual_count;
113
0
    }
114
0
  }
115
116
0
  if (actual_count == 0) return 1;
117
0
  if (seq_max != actual_count) {
118
0
    fprintf(stderr, "[ICCP] Discontinuous segments, expected: %d actual: %d!\n",
119
0
            actual_count, seq_max);
120
0
    return 0;
121
0
  }
122
0
  if (expected_count != actual_count) {
123
0
    fprintf(stderr, "[ICCP] Segment count: %d does not match expected: %d!\n",
124
0
            actual_count, expected_count);
125
0
    return 0;
126
0
  }
127
128
  // The segments may appear out of order in the file, sort them based on
129
  // sequence number before assembling the payload.
130
0
  qsort(iccp_segments, actual_count, sizeof(*iccp_segments),
131
0
        CompareICCPSegments);
132
133
0
  iccp->bytes = (uint8_t*)malloc(total_size);
134
0
  if (iccp->bytes == NULL) return 0;
135
0
  iccp->size = total_size;
136
137
0
  {
138
0
    int i;
139
0
    size_t offset = 0;
140
0
    for (i = 0; i < seq_max; ++i) {
141
0
      memcpy(iccp->bytes + offset, iccp_segments[i].data,
142
0
             iccp_segments[i].data_length);
143
0
      offset += iccp_segments[i].data_length;
144
0
    }
145
0
  }
146
0
  return 1;
147
0
}
148
149
// Returns true on success and false for memory errors and corrupt profiles.
150
// The caller must use MetadataFree() on 'metadata' in all cases.
151
static int ExtractMetadataFromJPEG(j_decompress_ptr dinfo,
152
0
                                   Metadata* const metadata) {
153
0
  static const struct {
154
0
    int marker;
155
0
    const char* signature;
156
0
    size_t signature_length;
157
0
    size_t storage_offset;
158
0
  } kJPEGMetadataMap[] = {
159
      // Exif 2.2 Section 4.7.2 Interoperability Structure of APP1 ...
160
0
      {JPEG_APP1, "Exif\0", 6, METADATA_OFFSET(exif)},
161
      // XMP Specification Part 3 Section 3 Embedding XMP Metadata ... #JPEG
162
      // TODO(jzern) Add support for 'ExtendedXMP'
163
0
      {JPEG_APP1, "http://ns.adobe.com/xap/1.0/", 29, METADATA_OFFSET(xmp)},
164
0
      {0, NULL, 0, 0},
165
0
  };
166
0
  jpeg_saved_marker_ptr marker;
167
  // Treat ICC profiles separately as they may be segmented and out of order.
168
0
  if (!StoreICCP(dinfo, &metadata->iccp)) return 0;
169
170
0
  for (marker = dinfo->marker_list; marker != NULL; marker = marker->next) {
171
0
    int i;
172
0
    for (i = 0; kJPEGMetadataMap[i].marker != 0; ++i) {
173
0
      if (marker->marker == kJPEGMetadataMap[i].marker &&
174
0
          marker->data_length > kJPEGMetadataMap[i].signature_length &&
175
0
          !memcmp(marker->data, kJPEGMetadataMap[i].signature,
176
0
                  kJPEGMetadataMap[i].signature_length)) {
177
0
        MetadataPayload* const payload =
178
0
            (MetadataPayload*)((uint8_t*)metadata +
179
0
                               kJPEGMetadataMap[i].storage_offset);
180
181
0
        if (payload->bytes == NULL) {
182
0
          const char* marker_data =
183
0
              (const char*)marker->data + kJPEGMetadataMap[i].signature_length;
184
0
          const size_t marker_data_length =
185
0
              marker->data_length - kJPEGMetadataMap[i].signature_length;
186
0
          if (!MetadataCopy(marker_data, marker_data_length, payload)) return 0;
187
0
        } else {
188
0
          fprintf(stderr, "Ignoring additional '%s' marker\n",
189
0
                  kJPEGMetadataMap[i].signature);
190
0
        }
191
0
      }
192
0
    }
193
0
  }
194
0
  return 1;
195
0
}
196
197
#undef JPEG_APP1
198
#undef JPEG_APP2
199
200
// -----------------------------------------------------------------------------
201
// JPEG decoding
202
203
struct my_error_mgr {
204
  struct jpeg_error_mgr pub;
205
  jmp_buf setjmp_buffer;
206
};
207
208
220
static void my_error_exit(j_common_ptr dinfo) {
209
220
  struct my_error_mgr* myerr = (struct my_error_mgr*)dinfo->err;
210
  // The following code is disabled in fuzzing mode because:
211
  // - the logs can be flooded due to invalid JPEG files
212
  // - msg_code is wrongfully seen as uninitialized by msan when the libjpeg
213
  //   dependency is not built with sanitizers enabled
214
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
215
  const int msg_code = myerr->pub.msg_code;
216
  fprintf(stderr, "libjpeg error: ");
217
  dinfo->err->output_message(dinfo);
218
  if (msg_code == JERR_INPUT_EOF || msg_code == JERR_FILE_READ) {
219
    fprintf(stderr, "`jpegtran -copy all` MAY be able to process this file.\n");
220
  }
221
#endif
222
220
  longjmp(myerr->setjmp_buffer, 1);
223
220
}
224
225
typedef struct {
226
  struct jpeg_source_mgr pub;
227
  const uint8_t* data;
228
  size_t data_size;
229
} JPEGReadContext;
230
231
215
static void ContextInit(j_decompress_ptr cinfo) {
232
215
  JPEGReadContext* const ctx = (JPEGReadContext*)cinfo->src;
233
215
  ctx->pub.next_input_byte = ctx->data;
234
215
  ctx->pub.bytes_in_buffer = ctx->data_size;
235
215
}
236
237
206
static boolean ContextFill(j_decompress_ptr cinfo) {
238
  // we shouldn't get here.
239
206
  ERREXIT(cinfo, JERR_FILE_READ);
240
206
  return FALSE;
241
206
}
242
243
167
static void ContextSkip(j_decompress_ptr cinfo, long jump_size) {
244
167
  JPEGReadContext* const ctx = (JPEGReadContext*)cinfo->src;
245
167
  size_t jump;
246
167
  if (jump_size <= 0) return;
247
167
  jump = (size_t)jump_size;
248
167
  if (jump > ctx->pub.bytes_in_buffer) {  // Don't overflow the buffer.
249
128
    jump = ctx->pub.bytes_in_buffer;
250
128
  }
251
167
  ctx->pub.bytes_in_buffer -= jump;
252
167
  ctx->pub.next_input_byte += jump;
253
167
}
254
255
0
static void ContextTerm(j_decompress_ptr cinfo) { (void)cinfo; }
256
257
static void ContextSetup(volatile struct jpeg_decompress_struct* const cinfo,
258
215
                         JPEGReadContext* const ctx) {
259
215
  cinfo->src = (struct jpeg_source_mgr*)ctx;
260
215
  ctx->pub.init_source = ContextInit;
261
215
  ctx->pub.fill_input_buffer = ContextFill;
262
215
  ctx->pub.skip_input_data = ContextSkip;
263
215
  ctx->pub.resync_to_restart = jpeg_resync_to_restart;
264
215
  ctx->pub.term_source = ContextTerm;
265
215
  ctx->pub.bytes_in_buffer = 0;
266
215
  ctx->pub.next_input_byte = NULL;
267
215
}
268
269
14
static void my_output_message_silent(j_common_ptr dinfo) { (void)dinfo; }
270
271
static int ReadJPEGInternal(const uint8_t* const data, size_t data_size,
272
                            WebPPicture* const pic, int keep_alpha,
273
                            Metadata* const metadata, int* const width_out,
274
22.7k
                            int* const height_out) {
275
22.7k
  volatile int ok = 0;
276
22.7k
  int width, height;
277
22.7k
  int64_t stride;
278
22.7k
  volatile struct jpeg_decompress_struct dinfo;
279
22.7k
  struct my_error_mgr jerr;
280
22.7k
  uint8_t* volatile rgb = NULL;
281
22.7k
  JSAMPROW buffer[1];
282
22.7k
  JPEGReadContext ctx;
283
284
22.7k
  if (data == NULL || data_size < 2 || data[0] != 0xFF || data[1] != 0xD8) {
285
22.4k
    return 0;
286
22.4k
  }
287
220
  if (pic == NULL && (width_out == NULL || height_out == NULL)) return 0;
288
289
220
  (void)keep_alpha;
290
220
  memset(&ctx, 0, sizeof(ctx));
291
220
  ctx.data = data;
292
220
  ctx.data_size = data_size;
293
294
220
  memset((j_decompress_ptr)&dinfo, 0, sizeof(dinfo));  // for setjmp safety
295
220
  dinfo.err = jpeg_std_error(&jerr.pub);
296
220
  jerr.pub.error_exit = my_error_exit;
297
220
  if (pic == NULL) {
298
182
    jerr.pub.output_message = my_output_message_silent;
299
182
  }
300
301
220
  if (setjmp(jerr.setjmp_buffer)) {
302
220
  Error:
303
220
    ok = 0;
304
220
    MetadataFree(metadata);
305
220
    jpeg_destroy_decompress((j_decompress_ptr)&dinfo);
306
220
    goto End;
307
220
  }
308
309
220
  jpeg_create_decompress((j_decompress_ptr)&dinfo);
310
0
  ContextSetup(&dinfo, &ctx);
311
33
  if (metadata != NULL) SaveMetadataMarkers((j_decompress_ptr)&dinfo);
312
0
  jpeg_read_header((j_decompress_ptr)&dinfo, TRUE);
313
314
0
  if (width_out != NULL) *width_out = dinfo.image_width;
315
0
  if (height_out != NULL) *height_out = dinfo.image_height;
316
0
  if (pic == NULL) {
317
0
    jpeg_destroy_decompress((j_decompress_ptr)&dinfo);
318
0
    return 1;
319
0
  }
320
321
0
  dinfo.out_color_space = JCS_RGB;
322
0
  dinfo.do_fancy_upsampling = TRUE;
323
324
0
  jpeg_start_decompress((j_decompress_ptr)&dinfo);
325
326
0
  if (dinfo.output_components != 3) {
327
0
    goto Error;
328
0
  }
329
330
0
  width = dinfo.output_width;
331
0
  height = dinfo.output_height;
332
0
  stride = (int64_t)dinfo.output_width * dinfo.output_components * sizeof(*rgb);
333
334
0
  if (stride != (int)stride ||
335
0
      !ImgIoUtilCheckSizeArgumentsOverflow(stride, height)) {
336
0
    goto Error;
337
0
  }
338
339
0
  rgb = (uint8_t*)malloc((size_t)stride * height);
340
0
  if (rgb == NULL) {
341
0
    goto Error;
342
0
  }
343
0
  buffer[0] = (JSAMPLE*)rgb;
344
345
0
  while (dinfo.output_scanline < dinfo.output_height) {
346
0
    if (jpeg_read_scanlines((j_decompress_ptr)&dinfo, buffer, 1) != 1) {
347
0
      goto Error;
348
0
    }
349
0
    buffer[0] += stride;
350
0
  }
351
352
0
  if (metadata != NULL) {
353
0
    ok = ExtractMetadataFromJPEG((j_decompress_ptr)&dinfo, metadata);
354
0
    if (!ok) {
355
0
      fprintf(stderr, "Error extracting JPEG metadata!\n");
356
0
      goto Error;
357
0
    }
358
0
  }
359
360
0
  jpeg_finish_decompress((j_decompress_ptr)&dinfo);
361
0
  jpeg_destroy_decompress((j_decompress_ptr)&dinfo);
362
363
  // WebP conversion.
364
0
  pic->width = width;
365
0
  pic->height = height;
366
0
  ok = WebPPictureImportRGB(pic, rgb, (int)stride);
367
0
  if (!ok) {
368
0
    pic->width = 0;   // WebPPictureImportRGB() barely touches 'pic' on failure.
369
0
    pic->height = 0;  // Just reset dimensions but keep any 'custom_ptr' etc.
370
0
    MetadataFree(metadata);  // In case the caller forgets to free it on error.
371
0
  }
372
373
220
End:
374
220
  free(rgb);
375
220
  return ok;
376
0
}
377
378
int ReadJPEG(const uint8_t* const data, size_t data_size,
379
346
             WebPPicture* const pic, int keep_alpha, Metadata* const metadata) {
380
346
  if (pic == NULL) return 0;
381
346
  return ReadJPEGInternal(data, data_size, pic, keep_alpha, metadata, NULL,
382
346
                          NULL);
383
346
}
384
385
int ReadJpegDimensions(const uint8_t* const data, size_t data_size,
386
22.3k
                       int* const width, int* const height) {
387
22.3k
  return ReadJPEGInternal(data, data_size, NULL, 0, NULL, width, height);
388
22.3k
}
389
#else   // !WEBP_HAVE_JPEG
390
int ReadJPEG(const uint8_t* const data, size_t data_size,
391
             struct WebPPicture* const pic, int keep_alpha,
392
             struct Metadata* const metadata) {
393
  (void)data;
394
  (void)data_size;
395
  (void)pic;
396
  (void)keep_alpha;
397
  (void)metadata;
398
  fprintf(stderr,
399
          "JPEG support not compiled. Please install the libjpeg "
400
          "development package before building.\n");
401
  return 0;
402
}
403
404
int ReadJpegDimensions(const uint8_t* const data, size_t data_size,
405
                       int* const width, int* const height) {
406
  (void)data;
407
  (void)data_size;
408
  (void)width;
409
  (void)height;
410
  return 0;
411
}
412
#endif  // WEBP_HAVE_JPEG
413
414
// -----------------------------------------------------------------------------