/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 | } |