Coverage Report

Created: 2026-01-25 06:04

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
2.97k
{
64
2.97k
  struct fuzzer_error_mgr *fuzz_err = (struct fuzzer_error_mgr *)cinfo->err;
65
66
2.97k
  longjmp(fuzz_err->setjmp_buffer, 1);
67
2.97k
}
68
69
70
static void fuzzer_emit_message(j_common_ptr cinfo, int msg_level)
71
41.9M
{
72
41.9M
}
73
74
75
static int64_t marker_sum = 0;
76
77
static boolean custom_marker_processor(j_decompress_ptr cinfo)
78
2.77k
{
79
2.77k
  struct jpeg_source_mgr *src = cinfo->src;
80
2.77k
  INT32 length;
81
82
  /* Read and consume the 2-byte length field. */
83
2.77k
  if (src->bytes_in_buffer < 2)
84
31
    return FALSE;
85
86
2.74k
  length = ((INT32)src->next_input_byte[0] << 8) +
87
2.74k
            (INT32)src->next_input_byte[1];
88
2.74k
  src->next_input_byte += 2;
89
2.74k
  src->bytes_in_buffer -= 2;
90
2.74k
  length -= 2;
91
92
2.74k
  if (length < 0)
93
681
    return FALSE;
94
95
  /* Consume and touch all marker data in order to catch uninitialized reads
96
     when using MemorySanitizer. */
97
3.43M
  while (length > 0) {
98
3.43M
    if (src->bytes_in_buffer == 0) {
99
3.43M
      if (!(*src->fill_input_buffer) (cinfo))
100
0
        return FALSE;
101
3.43M
    }
102
103
3.43M
    size_t available = (size_t)length < src->bytes_in_buffer ?
104
3.43M
                       (size_t)length : src->bytes_in_buffer;
105
106
11.0M
    for (size_t i = 0; i < available; i++)
107
7.60M
      marker_sum += src->next_input_byte[i];
108
109
3.43M
    src->next_input_byte += available;
110
3.43M
    src->bytes_in_buffer -= available;
111
3.43M
    length -= (INT32)available;
112
3.43M
  }
113
114
2.06k
  return TRUE;
115
2.06k
}
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
4.67k
{
131
4.67k
  struct jpeg_decompress_struct cinfo;
132
4.67k
  struct fuzzer_error_mgr jerr;
133
4.67k
  JSAMPARRAY buffer = NULL;
134
4.67k
  int row_stride;
135
4.67k
  int numTests = 1;
136
4.67k
  struct test tests[NUMTESTS] = {
137
    /*
138
      Output       Quantize 2-Pass Dither
139
      Colorspace   Colors   Quant  Mode
140
    */
141
4.67k
    { JCS_RGB565,  FALSE,   FALSE, JDITHER_NONE    },
142
4.67k
    { JCS_RGB565,  FALSE,   FALSE, JDITHER_ORDERED },
143
4.67k
    { JCS_UNKNOWN, TRUE,    FALSE, JDITHER_NONE    },
144
4.67k
    { JCS_UNKNOWN, TRUE,    FALSE, JDITHER_ORDERED },
145
4.67k
    { JCS_UNKNOWN, TRUE,    FALSE, JDITHER_FS      },
146
4.67k
    { JCS_UNKNOWN, TRUE,    TRUE,  JDITHER_NONE    },
147
4.67k
    { JCS_UNKNOWN, TRUE,    TRUE,  JDITHER_FS      }
148
4.67k
  };
149
150
  /* Reject too-small input. */
151
4.67k
  if (size < 2)
152
1
    return 0;
153
154
4.67k
  cinfo.err = jpeg_std_error(&jerr.pub);
155
4.67k
  jerr.pub.error_exit = fuzzer_error_exit;
156
4.67k
  jerr.pub.emit_message = fuzzer_emit_message;
157
158
4.67k
  jpeg_create_decompress(&cinfo);
159
160
18.4k
  for (int ti = 0; ti < numTests; ti++) {
161
13.7k
    int64_t sum = 0;
162
163
13.7k
    marker_sum = 0;
164
165
13.7k
    if (setjmp(jerr.setjmp_buffer)) {
166
2.97k
      jpeg_abort_decompress(&cinfo);
167
2.97k
      continue;
168
2.97k
    }
169
170
10.8k
    jpeg_mem_src(&cinfo, data, (unsigned long)size);
171
172
231k
    for (int m = JPEG_APP0; m <= JPEG_APP0 + 15; m++) {
173
220k
      if (m != JPEG_APP0 + 3)
174
206k
        jpeg_save_markers(&cinfo, m, 256);
175
220k
    }
176
10.8k
    jpeg_set_marker_processor(&cinfo, JPEG_APP0 + 3, custom_marker_processor);
177
178
10.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
12.8k
    if (cinfo.image_width < 1 || cinfo.image_height < 1 ||
183
12.8k
        (uint64_t)cinfo.image_width * cinfo.image_height > 1048576)
184
43
      goto bailout;
185
186
10.7k
    cinfo.dct_method = JDCT_FLOAT;
187
10.7k
    cinfo.buffered_image = jpeg_has_multiple_scans(&cinfo);
188
10.7k
    if (((cinfo.jpeg_color_space == JCS_YCbCr ||
189
9.27k
          cinfo.jpeg_color_space == JCS_RGB) && cinfo.num_components == 3) ||
190
3.52k
        (cinfo.jpeg_color_space == JCS_GRAYSCALE &&
191
11.9k
         cinfo.num_components == 1)) {
192
11.9k
      cinfo.out_color_space = tests[ti].out_color_space;
193
11.9k
      if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
194
2.69k
        numTests = 5;
195
2.69k
        if (cinfo.out_color_space == JCS_UNKNOWN)
196
1.40k
          cinfo.out_color_space = JCS_GRAYSCALE;
197
9.27k
      } else {
198
9.27k
        numTests = 7;
199
9.27k
        if (cinfo.out_color_space == JCS_UNKNOWN)
200
5.96k
          cinfo.out_color_space = ti % 2 ? JCS_RGB : JCS_EXT_BGR;
201
9.27k
      }
202
11.9k
      cinfo.quantize_colors = tests[ti].quantize_colors;
203
11.9k
      cinfo.two_pass_quantize = tests[ti].two_pass_quantize;
204
11.9k
      cinfo.dither_mode = tests[ti].dither_mode;
205
11.9k
    }
206
207
10.7k
    if (!jpeg_start_decompress(&cinfo)) {
208
0
      jpeg_abort_decompress(&cinfo);
209
0
      continue;
210
0
    }
211
212
10.7k
    row_stride = cinfo.output_width * cinfo.output_components;
213
10.7k
    buffer = (*cinfo.mem->alloc_sarray)
214
10.7k
      ((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1);
215
216
10.7k
    if (cinfo.buffered_image) {
217
      /* Process all scans. */
218
55.5k
      while (!jpeg_input_complete(&cinfo) &&
219
49.6k
             cinfo.input_scan_number != cinfo.output_scan_number) {
220
49.6k
        int retval;
221
222
49.6k
        if (cinfo.input_scan_number > 500) {
223
2
          jpeg_abort_decompress(&cinfo);
224
2
          goto bailout;
225
2
        }
226
227
        /* Consume input data until we have a complete scan or reach the end
228
           of input. */
229
6.71M
        do {
230
6.71M
          retval = jpeg_consume_input(&cinfo);
231
6.71M
        } while (retval != JPEG_SUSPENDED && retval != JPEG_REACHED_SOS &&
232
6.67M
                 retval != JPEG_REACHED_EOI);
233
234
49.6k
        if (retval == JPEG_REACHED_EOI || retval == JPEG_SUSPENDED)
235
4.17k
          break;
236
237
        /* Start outputting the current scan. */
238
45.4k
        if (!jpeg_start_output(&cinfo, cinfo.input_scan_number))
239
14
          break;
240
241
59.3M
        while (cinfo.output_scanline < cinfo.output_height) {
242
59.2M
          if (!cinfo.two_pass_quantize &&
243
43.5M
              (cinfo.output_scanline == 0 || cinfo.output_scanline == 16))
244
61.5k
            jpeg_skip_scanlines(&cinfo, 8);
245
59.2M
          else {
246
59.2M
            jpeg_read_scanlines(&cinfo, buffer, 1);
247
            /* Touch all of the output pixels in order to catch uninitialized
248
               reads when using MemorySanitizer. */
249
4.70G
            for (int i = 0; i < row_stride; i++)
250
4.64G
              sum += buffer[0][i];
251
59.2M
          }
252
59.2M
        }
253
254
        /* Finish this output pass. */
255
45.4k
        if (!jpeg_finish_output(&cinfo))
256
4
          break;
257
45.4k
      }
258
259
10.1k
    } else {
260
261
8.34M
      while (cinfo.output_scanline < cinfo.output_height) {
262
8.34M
        if (!cinfo.two_pass_quantize &&
263
7.20M
            (cinfo.output_scanline == 0 || cinfo.output_scanline == 16))
264
3.52k
          jpeg_skip_scanlines(&cinfo, 8);
265
8.33M
        else {
266
8.33M
          jpeg_read_scanlines(&cinfo, buffer, 1);
267
360M
          for (int i = 0; i < row_stride; i++)
268
352M
            sum += buffer[0][i];
269
8.33M
        }
270
8.34M
      }
271
272
650
    }
273
274
10.7k
    jpeg_finish_decompress(&cinfo);
275
276
    /* Prevent the sums above from being optimized out.  This test should never
277
       be true, but the compiler doesn't know that. */
278
10.7k
    if (sum > (int64_t)255 * 1048576 * 4 ||
279
10.7k
        marker_sum > (int64_t)255 * 1048576)
280
2
      goto bailout;
281
10.7k
  }
282
283
4.67k
bailout:
284
4.67k
  jpeg_destroy_decompress(&cinfo);
285
4.67k
  return 0;
286
4.67k
}