initial commit
This commit is contained in:
Executable
+291
@@ -0,0 +1,291 @@
|
||||
/*
|
||||
* Copyright (C) 2021-2024, 2026 D. R. Commander. All Rights Reserved.
|
||||
* Copyright (C) 2025 Leslie P. Polzer. All Rights Reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* - Neither the name of the libjpeg-turbo Project nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS",
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/* This fuzzer uses the libjpeg API to exercise code paths that are not covered
|
||||
* by the other fuzzers (or by the TurboJPEG API in general):
|
||||
*
|
||||
* - JCS_UNKNOWN (NULL color conversion with a component count other than 3 or
|
||||
* 4)
|
||||
* - Floating point IDCT
|
||||
* - Buffered-image mode
|
||||
* - Interstitial line skipping
|
||||
* - jpeg_save_markers() with a length limit
|
||||
* - Custom marker processor
|
||||
* - JCS_RGB565
|
||||
* - Color quantization
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
extern "C" {
|
||||
#include "../src/jpeglib.h"
|
||||
#include "../src/jerror.h"
|
||||
}
|
||||
|
||||
|
||||
struct fuzzer_error_mgr {
|
||||
struct jpeg_error_mgr pub;
|
||||
jmp_buf setjmp_buffer;
|
||||
};
|
||||
|
||||
|
||||
static void fuzzer_error_exit(j_common_ptr cinfo)
|
||||
{
|
||||
struct fuzzer_error_mgr *fuzz_err = (struct fuzzer_error_mgr *)cinfo->err;
|
||||
|
||||
longjmp(fuzz_err->setjmp_buffer, 1);
|
||||
}
|
||||
|
||||
|
||||
static void fuzzer_emit_message(j_common_ptr cinfo, int msg_level)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
static int64_t marker_sum = 0;
|
||||
|
||||
static boolean custom_marker_processor(j_decompress_ptr cinfo)
|
||||
{
|
||||
struct jpeg_source_mgr *src = cinfo->src;
|
||||
INT32 length;
|
||||
|
||||
/* Read and consume the 2-byte length field. */
|
||||
if (src->bytes_in_buffer < 2)
|
||||
return FALSE;
|
||||
|
||||
length = ((INT32)src->next_input_byte[0] << 8) +
|
||||
(INT32)src->next_input_byte[1];
|
||||
src->next_input_byte += 2;
|
||||
src->bytes_in_buffer -= 2;
|
||||
length -= 2;
|
||||
|
||||
if (length < 0)
|
||||
return FALSE;
|
||||
|
||||
/* Consume and touch all marker data in order to catch uninitialized reads
|
||||
when using MemorySanitizer. */
|
||||
while (length > 0) {
|
||||
if (src->bytes_in_buffer == 0) {
|
||||
if (!(*src->fill_input_buffer) (cinfo))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
size_t available = (size_t)length < src->bytes_in_buffer ?
|
||||
(size_t)length : src->bytes_in_buffer;
|
||||
|
||||
for (size_t i = 0; i < available; i++)
|
||||
marker_sum += src->next_input_byte[i];
|
||||
|
||||
src->next_input_byte += available;
|
||||
src->bytes_in_buffer -= available;
|
||||
length -= (INT32)available;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
#define NUMTESTS 7
|
||||
|
||||
|
||||
struct test {
|
||||
J_COLOR_SPACE out_color_space;
|
||||
boolean quantize_colors;
|
||||
boolean two_pass_quantize;
|
||||
J_DITHER_MODE dither_mode;
|
||||
};
|
||||
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||
{
|
||||
struct jpeg_decompress_struct cinfo;
|
||||
struct fuzzer_error_mgr jerr;
|
||||
JSAMPARRAY buffer = NULL;
|
||||
int row_stride;
|
||||
int numTests = 1;
|
||||
struct test tests[NUMTESTS] = {
|
||||
/*
|
||||
Output Quantize 2-Pass Dither
|
||||
Colorspace Colors Quant Mode
|
||||
*/
|
||||
{ JCS_RGB565, FALSE, FALSE, JDITHER_NONE },
|
||||
{ JCS_RGB565, FALSE, FALSE, JDITHER_ORDERED },
|
||||
{ JCS_UNKNOWN, TRUE, FALSE, JDITHER_NONE },
|
||||
{ JCS_UNKNOWN, TRUE, FALSE, JDITHER_ORDERED },
|
||||
{ JCS_UNKNOWN, TRUE, FALSE, JDITHER_FS },
|
||||
{ JCS_UNKNOWN, TRUE, TRUE, JDITHER_NONE },
|
||||
{ JCS_UNKNOWN, TRUE, TRUE, JDITHER_FS }
|
||||
};
|
||||
|
||||
/* Reject too-small input. */
|
||||
if (size < 2)
|
||||
return 0;
|
||||
|
||||
cinfo.err = jpeg_std_error(&jerr.pub);
|
||||
jerr.pub.error_exit = fuzzer_error_exit;
|
||||
jerr.pub.emit_message = fuzzer_emit_message;
|
||||
|
||||
jpeg_create_decompress(&cinfo);
|
||||
|
||||
for (int ti = 0; ti < numTests; ti++) {
|
||||
int64_t sum = 0;
|
||||
|
||||
marker_sum = 0;
|
||||
|
||||
if (setjmp(jerr.setjmp_buffer)) {
|
||||
jpeg_abort_decompress(&cinfo);
|
||||
continue;
|
||||
}
|
||||
|
||||
jpeg_mem_src(&cinfo, data, (unsigned long)size);
|
||||
|
||||
for (int m = JPEG_APP0; m <= JPEG_APP0 + 15; m++) {
|
||||
if (m != JPEG_APP0 + 3)
|
||||
jpeg_save_markers(&cinfo, m, 256);
|
||||
}
|
||||
jpeg_set_marker_processor(&cinfo, JPEG_APP0 + 3, custom_marker_processor);
|
||||
|
||||
jpeg_read_header(&cinfo, TRUE);
|
||||
|
||||
/* Sanity check dimensions to avoid memory exhaustion. Casting width to
|
||||
(uint64_t) prevents integer overflow if width * height > INT_MAX. */
|
||||
if (cinfo.image_width < 1 || cinfo.image_height < 1 ||
|
||||
(uint64_t)cinfo.image_width * cinfo.image_height > 1048576)
|
||||
goto bailout;
|
||||
|
||||
cinfo.dct_method = JDCT_FLOAT;
|
||||
cinfo.buffered_image = jpeg_has_multiple_scans(&cinfo);
|
||||
if (((cinfo.jpeg_color_space == JCS_YCbCr ||
|
||||
cinfo.jpeg_color_space == JCS_RGB) && cinfo.num_components == 3) ||
|
||||
(cinfo.jpeg_color_space == JCS_GRAYSCALE &&
|
||||
cinfo.num_components == 1)) {
|
||||
cinfo.out_color_space = tests[ti].out_color_space;
|
||||
if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
|
||||
numTests = 5;
|
||||
if (cinfo.out_color_space == JCS_UNKNOWN)
|
||||
cinfo.out_color_space = JCS_GRAYSCALE;
|
||||
} else {
|
||||
numTests = 7;
|
||||
if (cinfo.out_color_space == JCS_UNKNOWN)
|
||||
cinfo.out_color_space = ti % 2 ? JCS_RGB : JCS_EXT_BGR;
|
||||
}
|
||||
cinfo.quantize_colors = tests[ti].quantize_colors;
|
||||
cinfo.two_pass_quantize = tests[ti].two_pass_quantize;
|
||||
cinfo.dither_mode = tests[ti].dither_mode;
|
||||
}
|
||||
|
||||
if (!jpeg_start_decompress(&cinfo)) {
|
||||
jpeg_abort_decompress(&cinfo);
|
||||
continue;
|
||||
}
|
||||
|
||||
row_stride = cinfo.output_width * cinfo.output_components;
|
||||
buffer = (*cinfo.mem->alloc_sarray)
|
||||
((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1);
|
||||
|
||||
if (cinfo.buffered_image) {
|
||||
/* Process all scans. */
|
||||
while (!jpeg_input_complete(&cinfo) &&
|
||||
cinfo.input_scan_number != cinfo.output_scan_number) {
|
||||
int retval;
|
||||
|
||||
if (cinfo.input_scan_number > 100) {
|
||||
jpeg_abort_decompress(&cinfo);
|
||||
goto bailout;
|
||||
}
|
||||
|
||||
/* Consume input data until we have a complete scan or reach the end
|
||||
of input. */
|
||||
do {
|
||||
retval = jpeg_consume_input(&cinfo);
|
||||
} while (retval != JPEG_SUSPENDED && retval != JPEG_REACHED_SOS &&
|
||||
retval != JPEG_REACHED_EOI);
|
||||
|
||||
if (retval == JPEG_REACHED_EOI)
|
||||
break;
|
||||
|
||||
/* Start outputting the current scan. */
|
||||
if (!jpeg_start_output(&cinfo, cinfo.input_scan_number))
|
||||
break;
|
||||
|
||||
while (cinfo.output_scanline < cinfo.output_height) {
|
||||
if (!cinfo.two_pass_quantize &&
|
||||
(cinfo.output_scanline == 0 || cinfo.output_scanline == 16)) {
|
||||
JDIMENSION output_scanline = cinfo.output_scanline;
|
||||
|
||||
jpeg_skip_scanlines(&cinfo, 8);
|
||||
if (cinfo.output_scanline == output_scanline)
|
||||
break;
|
||||
} else {
|
||||
if (jpeg_read_scanlines(&cinfo, buffer, 1) != 1)
|
||||
break;
|
||||
/* Touch all of the output pixels in order to catch uninitialized
|
||||
reads when using MemorySanitizer. */
|
||||
for (int i = 0; i < row_stride; i++)
|
||||
sum += buffer[0][i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Finish this output pass. */
|
||||
if (!jpeg_finish_output(&cinfo))
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
while (cinfo.output_scanline < cinfo.output_height) {
|
||||
if (!cinfo.two_pass_quantize &&
|
||||
(cinfo.output_scanline == 0 || cinfo.output_scanline == 16))
|
||||
jpeg_skip_scanlines(&cinfo, 8);
|
||||
else {
|
||||
jpeg_read_scanlines(&cinfo, buffer, 1);
|
||||
for (int i = 0; i < row_stride; i++)
|
||||
sum += buffer[0][i];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
jpeg_finish_decompress(&cinfo);
|
||||
|
||||
/* Prevent the sums above from being optimized out. This test should never
|
||||
be true, but the compiler doesn't know that. */
|
||||
if (sum > (int64_t)255 * 1048576 * 4 ||
|
||||
marker_sum > (int64_t)255 * 1048576)
|
||||
goto bailout;
|
||||
}
|
||||
|
||||
bailout:
|
||||
jpeg_destroy_decompress(&cinfo);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user