Coverage Report

Created: 2026-02-26 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libjpeg-turbo.main/fuzz/decompress_libjpeg.cc
Line
Count
Source
1
/*
2
 * Copyright (C) 2021-2024, 2026 D. R. Commander.  All Rights Reserved.
3
 * Copyright (C) 2025 Leslie P. Polzer.  All Rights Reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 * - Redistributions of source code must retain the above copyright notice,
9
 *   this list of conditions and the following disclaimer.
10
 * - Redistributions in binary form must reproduce the above copyright notice,
11
 *   this list of conditions and the following disclaimer in the documentation
12
 *   and/or other materials provided with the distribution.
13
 * - Neither the name of the libjpeg-turbo Project nor the names of its
14
 *   contributors may be used to endorse or promote products derived from this
15
 *   software without specific prior written permission.
16
 *
17
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS",
18
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
21
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
 * POSSIBILITY OF SUCH DAMAGE.
28
 */
29
30
/* This fuzzer uses the libjpeg API to exercise code paths that are not covered
31
 * by the other fuzzers (or by the TurboJPEG API in general):
32
 *
33
 * - JCS_UNKNOWN (NULL color conversion with a component count other than 3 or
34
 *   4)
35
 * - Floating point IDCT
36
 * - Buffered-image mode
37
 * - Interstitial line skipping
38
 * - jpeg_save_markers() with a length limit
39
 * - Custom marker processor
40
 * - JCS_RGB565
41
 * - Color quantization
42
 */
43
44
#include <stdint.h>
45
#include <stdio.h>
46
#include <stdlib.h>
47
#include <string.h>
48
#include <setjmp.h>
49
50
extern "C" {
51
#include "../src/jpeglib.h"
52
#include "../src/jerror.h"
53
}
54
55
56
struct fuzzer_error_mgr {
57
  struct jpeg_error_mgr pub;
58
  jmp_buf setjmp_buffer;
59
};
60
61
62
static void fuzzer_error_exit(j_common_ptr cinfo)
63
3.57k
{
64
3.57k
  struct fuzzer_error_mgr *fuzz_err = (struct fuzzer_error_mgr *)cinfo->err;
65
66
3.57k
  longjmp(fuzz_err->setjmp_buffer, 1);
67
3.57k
}
68
69
70
static void fuzzer_emit_message(j_common_ptr cinfo, int msg_level)
71
36.1M
{
72
36.1M
}
73
74
75
static int64_t marker_sum = 0;
76
77
static boolean custom_marker_processor(j_decompress_ptr cinfo)
78
15.5k
{
79
15.5k
  struct jpeg_source_mgr *src = cinfo->src;
80
15.5k
  INT32 length;
81
82
  /* Read and consume the 2-byte length field. */
83
15.5k
  if (src->bytes_in_buffer < 2)
84
44
    return FALSE;
85
86
15.5k
  length = ((INT32)src->next_input_byte[0] << 8) +
87
15.5k
            (INT32)src->next_input_byte[1];
88
15.5k
  src->next_input_byte += 2;
89
15.5k
  src->bytes_in_buffer -= 2;
90
15.5k
  length -= 2;
91
92
15.5k
  if (length < 0)
93
12.8k
    return FALSE;
94
95
  /* Consume and touch all marker data in order to catch uninitialized reads
96
     when using MemorySanitizer. */
97
3.55M
  while (length > 0) {
98
3.55M
    if (src->bytes_in_buffer == 0) {
99
3.55M
      if (!(*src->fill_input_buffer) (cinfo))
100
0
        return FALSE;
101
3.55M
    }
102
103
3.55M
    size_t available = (size_t)length < src->bytes_in_buffer ?
104
3.55M
                       (size_t)length : src->bytes_in_buffer;
105
106
12.6M
    for (size_t i = 0; i < available; i++)
107
9.06M
      marker_sum += src->next_input_byte[i];
108
109
3.55M
    src->next_input_byte += available;
110
3.55M
    src->bytes_in_buffer -= available;
111
3.55M
    length -= (INT32)available;
112
3.55M
  }
113
114
2.61k
  return TRUE;
115
2.61k
}
116
117
118
#define NUMTESTS  7
119
120
121
struct test {
122
  J_COLOR_SPACE out_color_space;
123
  boolean quantize_colors;
124
  boolean two_pass_quantize;
125
  J_DITHER_MODE dither_mode;
126
};
127
128
129
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
130
6.09k
{
131
6.09k
  struct jpeg_decompress_struct cinfo;
132
6.09k
  struct fuzzer_error_mgr jerr;
133
6.09k
  JSAMPARRAY buffer = NULL;
134
6.09k
  int row_stride;
135
6.09k
  int numTests = 1;
136
6.09k
  struct test tests[NUMTESTS] = {
137
    /*
138
      Output       Quantize 2-Pass Dither
139
      Colorspace   Colors   Quant  Mode
140
    */
141
6.09k
    { JCS_RGB565,  FALSE,   FALSE, JDITHER_NONE    },
142
6.09k
    { JCS_RGB565,  FALSE,   FALSE, JDITHER_ORDERED },
143
6.09k
    { JCS_UNKNOWN, TRUE,    FALSE, JDITHER_NONE    },
144
6.09k
    { JCS_UNKNOWN, TRUE,    FALSE, JDITHER_ORDERED },
145
6.09k
    { JCS_UNKNOWN, TRUE,    FALSE, JDITHER_FS      },
146
6.09k
    { JCS_UNKNOWN, TRUE,    TRUE,  JDITHER_NONE    },
147
6.09k
    { JCS_UNKNOWN, TRUE,    TRUE,  JDITHER_FS      }
148
6.09k
  };
149
150
  /* Reject too-small input. */
151
6.09k
  if (size < 2)
152
1
    return 0;
153
154
6.09k
  cinfo.err = jpeg_std_error(&jerr.pub);
155
6.09k
  jerr.pub.error_exit = fuzzer_error_exit;
156
6.09k
  jerr.pub.emit_message = fuzzer_emit_message;
157
158
6.09k
  jpeg_create_decompress(&cinfo);
159
160
24.4k
  for (int ti = 0; ti < numTests; ti++) {
161
18.4k
    int64_t sum = 0;
162
163
18.4k
    marker_sum = 0;
164
165
18.4k
    if (setjmp(jerr.setjmp_buffer)) {
166
3.57k
      jpeg_abort_decompress(&cinfo);
167
3.57k
      continue;
168
3.57k
    }
169
170
14.8k
    jpeg_mem_src(&cinfo, data, (unsigned long)size);
171
172
309k
    for (int m = JPEG_APP0; m <= JPEG_APP0 + 15; m++) {
173
295k
      if (m != JPEG_APP0 + 3)
174
276k
        jpeg_save_markers(&cinfo, m, 256);
175
295k
    }
176
14.8k
    jpeg_set_marker_processor(&cinfo, JPEG_APP0 + 3, custom_marker_processor);
177
178
14.8k
    jpeg_read_header(&cinfo, TRUE);
179
180
    /* Sanity check dimensions to avoid memory exhaustion.  Casting width to
181
       (uint64_t) prevents integer overflow if width * height > INT_MAX. */
182
16.9k
    if (cinfo.image_width < 1 || cinfo.image_height < 1 ||
183
16.9k
        (uint64_t)cinfo.image_width * cinfo.image_height > 1048576)
184
43
      goto bailout;
185
186
14.8k
    cinfo.dct_method = JDCT_FLOAT;
187
14.8k
    cinfo.buffered_image = jpeg_has_multiple_scans(&cinfo);
188
14.8k
    if (((cinfo.jpeg_color_space == JCS_YCbCr ||
189
10.5k
          cinfo.jpeg_color_space == JCS_RGB) && cinfo.num_components == 3) ||
190
6.35k
        (cinfo.jpeg_color_space == JCS_GRAYSCALE &&
191
15.8k
         cinfo.num_components == 1)) {
192
15.8k
      cinfo.out_color_space = tests[ti].out_color_space;
193
15.8k
      if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
194
5.32k
        numTests = 5;
195
5.32k
        if (cinfo.out_color_space == JCS_UNKNOWN)
196
2.77k
          cinfo.out_color_space = JCS_GRAYSCALE;
197
10.5k
      } else {
198
10.5k
        numTests = 7;
199
10.5k
        if (cinfo.out_color_space == JCS_UNKNOWN)
200
6.99k
          cinfo.out_color_space = ti % 2 ? JCS_RGB : JCS_EXT_BGR;
201
10.5k
      }
202
15.8k
      cinfo.quantize_colors = tests[ti].quantize_colors;
203
15.8k
      cinfo.two_pass_quantize = tests[ti].two_pass_quantize;
204
15.8k
      cinfo.dither_mode = tests[ti].dither_mode;
205
15.8k
    }
206
207
14.8k
    if (!jpeg_start_decompress(&cinfo)) {
208
0
      jpeg_abort_decompress(&cinfo);
209
0
      continue;
210
0
    }
211
212
14.8k
    row_stride = cinfo.output_width * cinfo.output_components;
213
14.8k
    buffer = (*cinfo.mem->alloc_sarray)
214
14.8k
      ((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1);
215
216
14.8k
    if (cinfo.buffered_image) {
217
      /* Process all scans. */
218
41.3k
      while (!jpeg_input_complete(&cinfo) &&
219
34.9k
             cinfo.input_scan_number != cinfo.output_scan_number) {
220
34.9k
        int retval;
221
222
34.9k
        if (cinfo.input_scan_number > 100) {
223
26
          jpeg_abort_decompress(&cinfo);
224
26
          goto bailout;
225
26
        }
226
227
        /* Consume input data until we have a complete scan or reach the end
228
           of input. */
229
10.8M
        do {
230
10.8M
          retval = jpeg_consume_input(&cinfo);
231
10.8M
        } while (retval != JPEG_SUSPENDED && retval != JPEG_REACHED_SOS &&
232
10.8M
                 retval != JPEG_REACHED_EOI);
233
234
34.8k
        if (retval == JPEG_REACHED_EOI)
235
5.57k
          break;
236
237
        /* Start outputting the current scan. */
238
29.3k
        if (!jpeg_start_output(&cinfo, cinfo.input_scan_number))
239
18
          break;
240
241
81.3M
        while (cinfo.output_scanline < cinfo.output_height) {
242
81.3M
          if (!cinfo.two_pass_quantize &&
243
61.6M
              (cinfo.output_scanline == 0 || cinfo.output_scanline == 16)) {
244
40.2k
            JDIMENSION output_scanline = cinfo.output_scanline;
245
246
40.2k
            jpeg_skip_scanlines(&cinfo, 8);
247
40.2k
            if (cinfo.output_scanline == output_scanline)
248
1.14k
              break;
249
81.3M
          } else {
250
81.3M
            if (jpeg_read_scanlines(&cinfo, buffer, 1) != 1)
251
467
              break;
252
            /* Touch all of the output pixels in order to catch uninitialized
253
               reads when using MemorySanitizer. */
254
5.50G
            for (int i = 0; i < row_stride; i++)
255
5.41G
              sum += buffer[0][i];
256
81.3M
          }
257
81.3M
        }
258
259
        /* Finish this output pass. */
260
29.2k
        if (!jpeg_finish_output(&cinfo))
261
31
          break;
262
29.2k
      }
263
264
12.1k
    } else {
265
266
86.5M
      while (cinfo.output_scanline < cinfo.output_height) {
267
86.5M
        if (!cinfo.two_pass_quantize &&
268
63.6M
            (cinfo.output_scanline == 0 || cinfo.output_scanline == 16))
269
6.65k
          jpeg_skip_scanlines(&cinfo, 8);
270
86.4M
        else {
271
86.4M
          jpeg_read_scanlines(&cinfo, buffer, 1);
272
1.53G
          for (int i = 0; i < row_stride; i++)
273
1.44G
            sum += buffer[0][i];
274
86.4M
        }
275
86.5M
      }
276
277
2.70k
    }
278
279
14.7k
    jpeg_finish_decompress(&cinfo);
280
281
    /* Prevent the sums above from being optimized out.  This test should never
282
       be true, but the compiler doesn't know that. */
283
14.7k
    if (sum > (int64_t)255 * 1048576 * 4 ||
284
14.7k
        marker_sum > (int64_t)255 * 1048576)
285
6
      goto bailout;
286
14.7k
  }
287
288
6.09k
bailout:
289
6.09k
  jpeg_destroy_decompress(&cinfo);
290
6.09k
  return 0;
291
6.09k
}