OSS-Fuzz integration
This commit integrates OSS-Fuzz targets directly into the libjpeg-turbo
source tree, thus obsoleting and improving code coverage relative to
Google's OSS-Fuzz target for libjpeg-turbo (previously available here:
https://github.com/google/oss-fuzz).
I hope to eventually create fuzz targets for the BMP, GIF, and PPM
readers as well, which would allow for fuzz-testing compression, but
since those readers all require an input file, it is unclear how to
build an efficient fuzzer around them. It doesn't make sense to
fuzz-test compression in isolation, because compression can't accept
arbitrary input data.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 962ecd7..f34d0e1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -192,6 +192,7 @@
boolean_number(WITH_SIMD)
option(WITH_TURBOJPEG "Include the TurboJPEG API library and associated test programs" TRUE)
boolean_number(WITH_TURBOJPEG)
+option(WITH_FUZZ "Build fuzz targets" FALSE)
macro(report_option var desc)
if(${var})
@@ -704,6 +705,10 @@
# TESTS
###############################################################################
+if(WITH_FUZZ)
+ add_subdirectory(fuzz)
+endif()
+
add_subdirectory(md5)
if(MSVC_IDE OR XCODE)
diff --git a/fuzz/CMakeLists.txt b/fuzz/CMakeLists.txt
new file mode 100644
index 0000000..6af371b
--- /dev/null
+++ b/fuzz/CMakeLists.txt
@@ -0,0 +1,36 @@
+if(NOT WITH_TURBOJPEG)
+ message(FATAL_ERROR "Fuzz targets require the TurboJPEG API library.")
+endif()
+
+set(FUZZ_BINDIR "" CACHE PATH
+ "Directory into which fuzz targets should be installed")
+if(NOT FUZZ_BINDIR)
+ message(FATAL_ERROR "FUZZ_BINDIR must be specified.")
+endif()
+message(STATUS "FUZZ_BINDIR = ${FUZZ_BINDIR}")
+
+set(FUZZ_LIBRARY "" CACHE STRING
+ "Path to fuzzer library or flags necessary to link with it")
+if(NOT FUZZ_LIBRARY)
+ message(FATAL_ERROR "FUZZ_LIBRARY must be specified.")
+endif()
+message(STATUS "FUZZ_LIBRARY = ${FUZZ_LIBRARY}")
+
+macro(add_fuzz_target target source_file)
+ add_executable(${target}_fuzzer ${source_file})
+ if(NOT ENABLE_SHARED)
+ target_link_libraries(${target}_fuzzer ${FUZZ_LIBRARY} turbojpeg-static)
+ else()
+ target_link_libraries(${target}_fuzzer ${FUZZ_LIBRARY} turbojpeg)
+ endif()
+ install(TARGETS ${target}_fuzzer RUNTIME DESTINATION ${FUZZ_BINDIR})
+endmacro()
+
+# NOTE: This target is named libjpeg_turbo_fuzzer instead of decompress_fuzzer
+# in order to preserve the corpora from Google's OSS-Fuzz target for
+# libjpeg-turbo, which this target replaces.
+add_fuzz_target(libjpeg_turbo decompress.c)
+
+add_fuzz_target(decompress_yuv decompress_yuv.c)
+
+add_fuzz_target(transform transform.c)
diff --git a/fuzz/build.sh b/fuzz/build.sh
new file mode 100644
index 0000000..cf8597b
--- /dev/null
+++ b/fuzz/build.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+set -u
+set -e
+
+cmake . -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_STATIC=1 -DENABLE_SHARED=0 \
+ -DCMAKE_C_FLAGS_RELWITHDEBINFO="-g -DNDEBUG" -DCMAKE_INSTALL_PREFIX=$WORK \
+ -DWITH_FUZZ=1 -DFUZZ_BINDIR=$OUT -DFUZZ_LIBRARY=$LIB_FUZZING_ENGINE
+make "-j$(nproc)" "--load-average=$(nproc)"
+make install
+
+cp $SRC/decompress_fuzzer_seed_corpus.zip $OUT/libjpeg_turbo_fuzzer_seed_corpus.zip
+cp $SRC/decompress_fuzzer_seed_corpus.zip $OUT/decompress_yuv_fuzzer_seed_corpus.zip
+cp $SRC/decompress_fuzzer_seed_corpus.zip $OUT/transform_fuzzer_seed_corpus.zip
diff --git a/fuzz/decompress.c b/fuzz/decompress.c
new file mode 100644
index 0000000..04cf56e
--- /dev/null
+++ b/fuzz/decompress.c
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C)2021 D. R. Commander. 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.
+ */
+
+#include <turbojpeg.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+
+#define NUMPF 4
+
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ tjhandle handle = NULL;
+ unsigned char *dstBuf = NULL;
+ int width, height, jpegSubsamp, jpegColorspace, pfi;
+ /* TJPF_RGB-TJPF_BGR share the same code paths, as do TJPF_RGBX-TJPF_XRGB and
+ 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 };
+
+ if ((handle = tjInitDecompress()) == NULL)
+ goto bailout;
+
+ /* We ignore the return value of tjDecompressHeader3(), because some JPEG
+ images may have unusual subsampling configurations that the TurboJPEG API
+ cannot identify but can still decompress. */
+ tjDecompressHeader3(handle, data, size, &width, &height, &jpegSubsamp,
+ &jpegColorspace);
+
+ /* Ignore 0-pixel images and images larger than 1 Megapixel, as Google's
+ OSS-Fuzz target for libjpeg-turbo did. Casting width to (uint64_t)
+ prevents integer overflow if width * height > INT_MAX. */
+ if (width < 1 || height < 1 || (uint64_t)width * height > 1048576)
+ goto bailout;
+
+ for (pfi = 0; pfi < NUMPF; pfi++) {
+ int pf = pixelFormats[pfi], flags = 0, i, sum = 0, w = width, h = height;
+
+ /* Test non-default decompression options on the first iteration. */
+ if (pf == TJPF_RGB)
+ flags = TJFLAG_BOTTOMUP | TJFLAG_FASTUPSAMPLE | TJFLAG_FASTDCT;
+ /* Test IDCT scaling on the second iteration. */
+ if (pf == TJPF_BGRX) {
+ w = (width + 1) / 2;
+ h = (height + 1) / 2;
+ }
+
+ if ((dstBuf = (unsigned char *)malloc(w * h * tjPixelSize[pf])) == NULL)
+ goto bailout;
+
+ tjDecompress2(handle, data, size, dstBuf, w, 0, h, pf, flags);
+
+ /* Touch all of the output pixels in order to catch uninitialized reads
+ when using MemorySanitizer. */
+ for (i = 0; i < w * h * tjPixelSize[pf]; i++)
+ sum += dstBuf[i];
+
+ free(dstBuf);
+ dstBuf = NULL;
+
+ /* Prevent the code above from being optimized out. This test should never
+ be true, but the compiler doesn't know that. */
+ if (sum > 255 * 1048576 * tjPixelSize[pf])
+ goto bailout;
+ }
+
+bailout:
+ if (dstBuf) free(dstBuf);
+ if (handle) tjDestroy(handle);
+ return 0;
+}
diff --git a/fuzz/decompress_yuv.c b/fuzz/decompress_yuv.c
new file mode 100644
index 0000000..bf5a09d
--- /dev/null
+++ b/fuzz/decompress_yuv.c
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C)2021 D. R. Commander. 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.
+ */
+
+#include <turbojpeg.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+
+#define NUMPF 3
+
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ tjhandle handle = NULL;
+ unsigned char *dstBuf = NULL, *yuvBuf = NULL;
+ int width, height, jpegSubsamp, jpegColorspace, pfi;
+ /* TJPF_RGB-TJPF_BGR share the same code paths, as do TJPF_RGBX-TJPF_XRGB and
+ 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 };
+
+ if ((handle = tjInitDecompress()) == NULL)
+ goto bailout;
+
+ if (tjDecompressHeader3(handle, data, size, &width, &height, &jpegSubsamp,
+ &jpegColorspace) < 0)
+ goto bailout;
+
+ /* Ignore 0-pixel images and images larger than 1 Megapixel. Casting width
+ to (uint64_t) prevents integer overflow if width * height > INT_MAX. */
+ if (width < 1 || height < 1 || (uint64_t)width * height > 1048576)
+ goto bailout;
+
+ for (pfi = 0; pfi < NUMPF; pfi++) {
+ int pf = pixelFormats[pfi], flags = 0, i, sum = 0, w = width, h = height;
+
+ /* Test non-default decompression options on the first iteration. */
+ if (pf == TJPF_RGB)
+ flags = TJFLAG_BOTTOMUP | TJFLAG_FASTUPSAMPLE | TJFLAG_FASTDCT;
+ /* Test IDCT scaling on the second iteration. */
+ if (pf == TJPF_XRGB) {
+ w = (width + 3) / 4;
+ h = (height + 3) / 4;
+ }
+
+ if ((dstBuf = (unsigned char *)malloc(w * h * tjPixelSize[pf])) == NULL)
+ goto bailout;
+ if ((yuvBuf =
+ (unsigned char *)malloc(tjBufSizeYUV2(w, 1, h, jpegSubsamp))) == NULL)
+ goto bailout;
+
+ tjDecompressToYUV2(handle, data, size, yuvBuf, w, 1, h, flags);
+ tjDecodeYUV(handle, yuvBuf, 1, jpegSubsamp, dstBuf, w, 0, h, pf, flags);
+
+ /* Touch all of the output pixels in order to catch uninitialized reads
+ when using MemorySanitizer. */
+ for (i = 0; i < w * h * tjPixelSize[pf]; i++)
+ sum += dstBuf[i];
+
+ free(dstBuf);
+ dstBuf = NULL;
+ free(yuvBuf);
+ yuvBuf = NULL;
+
+ /* Prevent the code above from being optimized out. This test should never
+ be true, but the compiler doesn't know that. */
+ if (sum > 255 * 1048576 * tjPixelSize[pf])
+ goto bailout;
+ }
+
+bailout:
+ if (dstBuf) free(dstBuf);
+ if (yuvBuf) free(yuvBuf);
+ if (handle) tjDestroy(handle);
+ return 0;
+}
diff --git a/fuzz/transform.c b/fuzz/transform.c
new file mode 100644
index 0000000..0c8bbc0
--- /dev/null
+++ b/fuzz/transform.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C)2021 D. R. Commander. 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.
+ */
+
+#include <turbojpeg.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+
+#define NUMXFORMS 3
+
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ tjhandle handle = NULL;
+ unsigned char *dstBufs[NUMXFORMS] = { NULL, NULL, NULL };
+ unsigned long dstSizes[NUMXFORMS] = { 0, 0, 0 }, maxBufSize;
+ int width, height, jpegSubsamp, jpegColorspace, i, t;
+ tjtransform transforms[NUMXFORMS];
+
+ if ((handle = tjInitTransform()) == NULL)
+ goto bailout;
+
+ /* We ignore the return value of tjDecompressHeader3(), because some JPEG
+ images may have unusual subsampling configurations that the TurboJPEG API
+ cannot identify but can still transform. */
+ tjDecompressHeader3(handle, data, size, &width, &height, &jpegSubsamp,
+ &jpegColorspace);
+
+ /* Ignore 0-pixel images and images larger than 1 Megapixel. Casting width
+ to (uint64_t) prevents integer overflow if width * height > INT_MAX. */
+ if (width < 1 || height < 1 || (uint64_t)width * height > 1048576)
+ goto bailout;
+
+ for (t = 0; t < NUMXFORMS; t++)
+ memset(&transforms[t], 0, sizeof(tjtransform));
+
+ transforms[0].op = TJXOP_NONE;
+ transforms[0].options = TJXOPT_PROGRESSIVE | TJXOPT_COPYNONE;
+
+ transforms[1].r.w = (width + 1) / 2;
+ transforms[1].r.h = (height + 1) / 2;
+ transforms[1].op = TJXOP_TRANSPOSE;
+ transforms[1].options = TJXOPT_GRAY | TJXOPT_CROP;
+
+ transforms[2].op = TJXOP_ROT90;
+ transforms[2].options = TJXOPT_TRIM;
+
+ tjTransform(handle, data, size, NUMXFORMS, dstBufs, dstSizes, transforms, 0);
+
+ maxBufSize = tjBufSize(width, height, TJSAMP_444);
+
+ /* Touch all of the output pixels in order to catch uninitialized reads
+ when using MemorySanitizer. */
+ for (t = 0; t < NUMXFORMS; t++) {
+ int sum = 0;
+
+ for (i = 0; i < dstSizes[t]; i++)
+ sum += dstBufs[t][i];
+
+ /* Prevent the code above from being optimized out. This test should never
+ be true, but the compiler doesn't know that. */
+ if (sum > 255 * maxBufSize)
+ goto bailout;
+
+ tjFree(dstBufs[t]);
+ dstBufs[t] = NULL;
+ }
+
+bailout:
+ for (t = 0; t < NUMXFORMS; t++) {
+ if (dstBufs[t]) tjFree(dstBufs[t]);
+ }
+ if (handle) tjDestroy(handle);
+ return 0;
+}