diff --git a/tests/fuzzers/build_google_oss_fuzzers.sh b/tests/fuzzers/build_google_oss_fuzzers.sh index d99f639fa..499e99eb5 100755 --- a/tests/fuzzers/build_google_oss_fuzzers.sh +++ b/tests/fuzzers/build_google_oss_fuzzers.sh @@ -18,6 +18,7 @@ if [ "$CXX" == "" ]; then fi SRC_DIR=$(dirname $0)/../.. +FUZZER_DIR=$(dirname $0) build_fuzzer() { @@ -31,9 +32,56 @@ build_fuzzer() $LIB_FUZZING_ENGINE $SRC_DIR/build/bin/libopenjp2.a -lm -lpthread } -fuzzerFiles=$(dirname $0)/*.cpp +# Build all fuzzers +echo "Building fuzzers..." +fuzzerFiles=$FUZZER_DIR/*.cpp for F in $fuzzerFiles; do fuzzerName=$(basename $F .cpp) build_fuzzer $fuzzerName $F done +echo "Copying dictionaries..." + +# Copy JPEG 2000 dictionary for all fuzzers +if [ -f "$FUZZER_DIR/opj_decompress_fuzzer.dict" ]; then + cp "$FUZZER_DIR/opj_decompress_fuzzer.dict" "$OUT/" + + # Decoder fuzzers use the dictionary + for fuzzer in opj_decompress_fuzzer_J2K opj_decompress_fuzzer_JP2 \ + opj_tile_fuzzer opj_decode_area_fuzzer opj_components_fuzzer \ + opj_dump_info_fuzzer; do + cp "$OUT/opj_decompress_fuzzer.dict" "$OUT/${fuzzer}.dict" 2>/dev/null || true + done + + # Round-trip and encoder fuzzers can also benefit from the dictionary + for fuzzer in opj_roundtrip_fuzzer opj_compress_fuzzer opj_tiled_encoder_fuzzer \ + opj_mct_fuzzer opj_encoder_options_fuzzer opj_subsampled_fuzzer; do + cp "$OUT/opj_decompress_fuzzer.dict" "$OUT/${fuzzer}.dict" 2>/dev/null || true + done +fi + +echo "Copying options files..." + +# Copy options files +for optFile in $FUZZER_DIR/*.options; do + if [ -f "$optFile" ]; then + cp "$optFile" "$OUT/" + fi +done + +echo "Building seed corpora..." + +# Build encoder seed corpus if it exists +if [ -d "$FUZZER_DIR/corpus/opj_compress_fuzzer" ]; then + cd "$FUZZER_DIR/corpus/opj_compress_fuzzer" + zip -j "$OUT/opj_compress_fuzzer_seed_corpus.zip" * 2>/dev/null || true + cd - > /dev/null + + # All encoder fuzzers can use the same seed corpus + for fuzzer in opj_roundtrip_fuzzer opj_tiled_encoder_fuzzer opj_mct_fuzzer \ + opj_encoder_options_fuzzer opj_subsampled_fuzzer; do + cp "$OUT/opj_compress_fuzzer_seed_corpus.zip" "$OUT/${fuzzer}_seed_corpus.zip" 2>/dev/null || true + done +fi + +echo "Fuzzer build complete! Built $(ls -1 $OUT/*_fuzzer 2>/dev/null | wc -l) fuzzers." diff --git a/tests/fuzzers/build_seed_corpus.sh b/tests/fuzzers/build_seed_corpus.sh index 5836c2244..5cf6f6537 100755 --- a/tests/fuzzers/build_seed_corpus.sh +++ b/tests/fuzzers/build_seed_corpus.sh @@ -8,12 +8,49 @@ if [ "$OUT" == "" ]; then fi SRC_DIR=$(dirname $0)/../.. +FUZZER_DIR=$(dirname $0) +echo "Building decoder seed corpus..." + +# Build main decoder corpus from test data cd $SRC_DIR/data/input/conformance rm -f $OUT/opj_decompress_fuzzer_seed_corpus.zip -zip $OUT/opj_decompress_fuzzer_seed_corpus.zip *.jp2 *.j2k +zip $OUT/opj_decompress_fuzzer_seed_corpus.zip *.jp2 *.j2k 2>/dev/null || true cd $OLDPWD -cd $SRC_DIR/data/input/nonregression/htj2k -zip $OUT/opj_decompress_fuzzer_seed_corpus.zip *.j2k *.jhc *.jph -cd $OLDPWD +# Add HTJ2K test files +if [ -d "$SRC_DIR/data/input/nonregression/htj2k" ]; then + cd $SRC_DIR/data/input/nonregression/htj2k + zip $OUT/opj_decompress_fuzzer_seed_corpus.zip *.j2k *.jhc *.jph 2>/dev/null || true + cd $OLDPWD +fi + +# Create corpus copies for format-specific fuzzers +echo "Creating corpus for format-specific fuzzers..." +cp $OUT/opj_decompress_fuzzer_seed_corpus.zip $OUT/opj_decompress_fuzzer_J2K_seed_corpus.zip 2>/dev/null || true +cp $OUT/opj_decompress_fuzzer_seed_corpus.zip $OUT/opj_decompress_fuzzer_JP2_seed_corpus.zip 2>/dev/null || true + +# Create corpus for tile fuzzer (uses same test files) +cp $OUT/opj_decompress_fuzzer_seed_corpus.zip $OUT/opj_tile_fuzzer_seed_corpus.zip 2>/dev/null || true + +# Create corpus for decode area fuzzer +cp $OUT/opj_decompress_fuzzer_seed_corpus.zip $OUT/opj_decode_area_fuzzer_seed_corpus.zip 2>/dev/null || true + +# Create corpus for components fuzzer +cp $OUT/opj_decompress_fuzzer_seed_corpus.zip $OUT/opj_components_fuzzer_seed_corpus.zip 2>/dev/null || true + +echo "Building encoder seed corpus..." + +# Build encoder seed corpus from synthetic test inputs +if [ -d "$FUZZER_DIR/corpus/opj_compress_fuzzer" ]; then + cd "$FUZZER_DIR/corpus/opj_compress_fuzzer" + zip -j "$OUT/opj_compress_fuzzer_seed_corpus.zip" * 2>/dev/null || true + cd - > /dev/null +fi + +# Build roundtrip seed corpus (same as encoder) +if [ -f "$OUT/opj_compress_fuzzer_seed_corpus.zip" ]; then + cp "$OUT/opj_compress_fuzzer_seed_corpus.zip" "$OUT/opj_roundtrip_fuzzer_seed_corpus.zip" +fi + +echo "Seed corpus build complete!" diff --git a/tests/fuzzers/corpus/opj_compress_fuzzer/seed_gray_8x8.bin b/tests/fuzzers/corpus/opj_compress_fuzzer/seed_gray_8x8.bin new file mode 100644 index 000000000..ba7087ea5 Binary files /dev/null and b/tests/fuzzers/corpus/opj_compress_fuzzer/seed_gray_8x8.bin differ diff --git a/tests/fuzzers/corpus/opj_compress_fuzzer/seed_jp2_8x8.bin b/tests/fuzzers/corpus/opj_compress_fuzzer/seed_jp2_8x8.bin new file mode 100644 index 000000000..53b247ffb Binary files /dev/null and b/tests/fuzzers/corpus/opj_compress_fuzzer/seed_jp2_8x8.bin differ diff --git a/tests/fuzzers/corpus/opj_compress_fuzzer/seed_lossy_16x16.bin b/tests/fuzzers/corpus/opj_compress_fuzzer/seed_lossy_16x16.bin new file mode 100644 index 000000000..73a19a782 Binary files /dev/null and b/tests/fuzzers/corpus/opj_compress_fuzzer/seed_lossy_16x16.bin differ diff --git a/tests/fuzzers/corpus/opj_compress_fuzzer/seed_rgb_16x16.bin b/tests/fuzzers/corpus/opj_compress_fuzzer/seed_rgb_16x16.bin new file mode 100644 index 000000000..5b1bf7445 Binary files /dev/null and b/tests/fuzzers/corpus/opj_compress_fuzzer/seed_rgb_16x16.bin differ diff --git a/tests/fuzzers/corpus/opj_compress_fuzzer/seed_rgba_32x32_tiles.bin b/tests/fuzzers/corpus/opj_compress_fuzzer/seed_rgba_32x32_tiles.bin new file mode 100644 index 000000000..28e7813af Binary files /dev/null and b/tests/fuzzers/corpus/opj_compress_fuzzer/seed_rgba_32x32_tiles.bin differ diff --git a/tests/fuzzers/opj_components_fuzzer.cpp b/tests/fuzzers/opj_components_fuzzer.cpp new file mode 100644 index 000000000..9e5869f66 --- /dev/null +++ b/tests/fuzzers/opj_components_fuzzer.cpp @@ -0,0 +1,238 @@ +/* + * The copyright in this software is being made available under the 2-clauses + * BSD License, included below. This software may be subject to other third + * party and contributor rights, including patent rights, and no such rights + * are granted under this license. + * + * Copyright (c) 2024, OpenJPEG contributors + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * 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 OWNER 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 exercises component selection decoding: + * - opj_set_decoded_components() to decode only specific components + * - Various combinations of component indices + */ + +#include +#include +#include +#include + +#include "openjpeg.h" + +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv); +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len); + +typedef struct { + const uint8_t* pabyData; + size_t nCurPos; + size_t nLength; +} MemFile; + +static void ErrorCallback(const char* msg, void* client_data) +{ + (void)msg; + (void)client_data; +} + +static void WarningCallback(const char* msg, void* client_data) +{ + (void)msg; + (void)client_data; +} + +static void InfoCallback(const char* msg, void* client_data) +{ + (void)msg; + (void)client_data; +} + +static OPJ_SIZE_T ReadCallback(void* pBuffer, OPJ_SIZE_T nBytes, void* pUserData) +{ + MemFile* memFile = (MemFile*)pUserData; + if (memFile->nCurPos >= memFile->nLength) { + return (OPJ_SIZE_T)-1; + } + if (memFile->nCurPos + nBytes >= memFile->nLength) { + size_t nToRead = memFile->nLength - memFile->nCurPos; + memcpy(pBuffer, memFile->pabyData + memFile->nCurPos, nToRead); + memFile->nCurPos = memFile->nLength; + return nToRead; + } + if (nBytes == 0) { + return (OPJ_SIZE_T)-1; + } + memcpy(pBuffer, memFile->pabyData + memFile->nCurPos, nBytes); + memFile->nCurPos += nBytes; + return nBytes; +} + +static OPJ_BOOL SeekCallback(OPJ_OFF_T nBytes, void* pUserData) +{ + MemFile* memFile = (MemFile*)pUserData; + memFile->nCurPos = (size_t)nBytes; + return OPJ_TRUE; +} + +static OPJ_OFF_T SkipCallback(OPJ_OFF_T nBytes, void* pUserData) +{ + MemFile* memFile = (MemFile*)pUserData; + memFile->nCurPos += (size_t)nBytes; + return nBytes; +} + +static const unsigned char jpc_header[] = {0xff, 0x4f}; +static const unsigned char jp2_box_jp[] = {0x6a, 0x50, 0x20, 0x20}; + +int LLVMFuzzerInitialize(int* argc, char*** argv) +{ + (void)argc; + (void)argv; + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) +{ + if (len < 10) { + return 0; + } + + // Use first bytes for component selection control + uint8_t ctrl_num_comps = buf[0]; + uint8_t ctrl_comp0 = buf[1]; + uint8_t ctrl_comp1 = buf[2]; + uint8_t ctrl_comp2 = buf[3]; + uint8_t ctrl_comp3 = buf[4]; + + const uint8_t* data = buf + 5; + size_t data_len = len - 5; + + // Determine codec format + OPJ_CODEC_FORMAT eCodecFormat; + if (data_len >= sizeof(jpc_header) && + memcmp(data, jpc_header, sizeof(jpc_header)) == 0) { + eCodecFormat = OPJ_CODEC_J2K; + } else if (data_len >= 12 && + memcmp(data + 4, jp2_box_jp, sizeof(jp2_box_jp)) == 0) { + eCodecFormat = OPJ_CODEC_JP2; + } else { + return 0; + } + + opj_codec_t* pCodec = opj_create_decompress(eCodecFormat); + if (!pCodec) { + return 0; + } + + opj_set_info_handler(pCodec, InfoCallback, NULL); + opj_set_warning_handler(pCodec, WarningCallback, NULL); + opj_set_error_handler(pCodec, ErrorCallback, NULL); + + opj_dparameters_t parameters; + opj_set_default_decoder_parameters(¶meters); + + if (!opj_setup_decoder(pCodec, ¶meters)) { + opj_destroy_codec(pCodec); + return 0; + } + + opj_stream_t* pStream = opj_stream_create(1024, OPJ_TRUE); + if (!pStream) { + opj_destroy_codec(pCodec); + return 0; + } + + MemFile memFile; + memFile.pabyData = data; + memFile.nLength = data_len; + memFile.nCurPos = 0; + + opj_stream_set_user_data_length(pStream, data_len); + opj_stream_set_read_function(pStream, ReadCallback); + opj_stream_set_seek_function(pStream, SeekCallback); + opj_stream_set_skip_function(pStream, SkipCallback); + opj_stream_set_user_data(pStream, &memFile, NULL); + + opj_image_t* psImage = NULL; + if (!opj_read_header(pStream, pCodec, &psImage)) { + opj_destroy_codec(pCodec); + opj_stream_destroy(pStream); + opj_image_destroy(psImage); + return 0; + } + + // Build component indices array based on fuzz input + OPJ_UINT32 imageNumComps = psImage->numcomps; + if (imageNumComps == 0) { + opj_end_decompress(pCodec, pStream); + opj_stream_destroy(pStream); + opj_destroy_codec(pCodec); + opj_image_destroy(psImage); + return 0; + } + + // Determine how many components to select (1-4) + OPJ_UINT32 numCompsToSelect = (ctrl_num_comps % 4) + 1; + if (numCompsToSelect > imageNumComps) { + numCompsToSelect = imageNumComps; + } + + OPJ_UINT32 compIndices[4]; + compIndices[0] = ctrl_comp0 % imageNumComps; + compIndices[1] = ctrl_comp1 % imageNumComps; + compIndices[2] = ctrl_comp2 % imageNumComps; + compIndices[3] = ctrl_comp3 % imageNumComps; + + // Try to set decoded components (may fail, which is OK) + // Note: apply_color_transforms must be OPJ_FALSE per API docs + opj_set_decoded_components(pCodec, numCompsToSelect, compIndices, OPJ_FALSE); + + // Limit decode area to prevent OOM + OPJ_UINT32 width = psImage->x1 - psImage->x0; + OPJ_UINT32 height = psImage->y1 - psImage->y0; + + OPJ_UINT32 width_to_read = width; + if (width_to_read > 1024) { + width_to_read = 1024; + } + OPJ_UINT32 height_to_read = height; + if (height_to_read > 1024) { + height_to_read = 1024; + } + + if (opj_set_decode_area(pCodec, psImage, + psImage->x0, psImage->y0, + psImage->x0 + width_to_read, + psImage->y0 + height_to_read)) { + opj_decode(pCodec, pStream, psImage); + } + + opj_end_decompress(pCodec, pStream); + opj_stream_destroy(pStream); + opj_destroy_codec(pCodec); + opj_image_destroy(psImage); + + return 0; +} diff --git a/tests/fuzzers/opj_compress_fuzzer.cpp b/tests/fuzzers/opj_compress_fuzzer.cpp new file mode 100644 index 000000000..417107340 --- /dev/null +++ b/tests/fuzzers/opj_compress_fuzzer.cpp @@ -0,0 +1,267 @@ +/* + * The copyright in this software is being made available under the 2-clauses + * BSD License, included below. This software may be subject to other third + * party and contributor rights, including patent rights, and no such rights + * are granted under this license. + * + * Copyright (c) 2024, OpenJPEG contributors + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * 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 OWNER 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 +#include +#include +#include + +#include "openjpeg.h" + +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv); +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len); + +// Maximum input size to prevent OOM +static const size_t kMaxInputSize = 64 * 1024; + +// Maximum image dimensions to prevent excessive memory usage +static const OPJ_UINT32 kMaxWidth = 256; +static const OPJ_UINT32 kMaxHeight = 256; +static const OPJ_UINT32 kMaxComponents = 4; + +typedef struct { + const uint8_t* data; + size_t size; + size_t offset; +} MemWriteStream; + +static void ErrorCallback(const char* msg, void* client_data) +{ + (void)msg; + (void)client_data; +} + +static void WarningCallback(const char* msg, void* client_data) +{ + (void)msg; + (void)client_data; +} + +static void InfoCallback(const char* msg, void* client_data) +{ + (void)msg; + (void)client_data; +} + +static OPJ_SIZE_T WriteCallback(void* pBuffer, OPJ_SIZE_T nBytes, void* pUserData) +{ + MemWriteStream* stream = (MemWriteStream*)pUserData; + // Discard output - we're only testing the encoder, not the output + (void)pBuffer; + stream->offset += nBytes; + return nBytes; +} + +static OPJ_BOOL SeekCallback(OPJ_OFF_T nBytes, void* pUserData) +{ + MemWriteStream* stream = (MemWriteStream*)pUserData; + stream->offset = (size_t)nBytes; + return OPJ_TRUE; +} + +static OPJ_OFF_T SkipCallback(OPJ_OFF_T nBytes, void* pUserData) +{ + MemWriteStream* stream = (MemWriteStream*)pUserData; + stream->offset += (size_t)nBytes; + return nBytes; +} + +// Helper to read a value from fuzz input +static uint8_t ReadByte(const uint8_t* buf, size_t len, size_t* pos) +{ + if (*pos >= len) { + return 0; + } + return buf[(*pos)++]; +} + +static uint16_t ReadUInt16(const uint8_t* buf, size_t len, size_t* pos) +{ + uint16_t val = ReadByte(buf, len, pos); + val |= (uint16_t)ReadByte(buf, len, pos) << 8; + return val; +} + +int LLVMFuzzerInitialize(int* argc, char*** argv) +{ + (void)argc; + (void)argv; + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) +{ + if (len < 16 || len > kMaxInputSize) { + return 0; + } + + size_t pos = 0; + + // Read image parameters from fuzz input + OPJ_UINT32 width = (ReadByte(buf, len, &pos) % kMaxWidth) + 1; + OPJ_UINT32 height = (ReadByte(buf, len, &pos) % kMaxHeight) + 1; + OPJ_UINT32 numcomps = (ReadByte(buf, len, &pos) % kMaxComponents) + 1; + OPJ_UINT32 prec = (ReadByte(buf, len, &pos) % 16) + 1; // 1-16 bits + OPJ_BOOL sgnd = ReadByte(buf, len, &pos) & 1; + + // Read encoding parameters + uint8_t codec_type = ReadByte(buf, len, &pos) & 1; // 0=J2K, 1=JP2 + uint8_t prog_order = ReadByte(buf, len, &pos) % 5; + uint8_t num_resolutions = (ReadByte(buf, len, &pos) % 6) + 1; + uint8_t irreversible = ReadByte(buf, len, &pos) & 1; + uint8_t tcp_numlayers = (ReadByte(buf, len, &pos) % 10) + 1; + uint8_t cblockw_exp = (ReadByte(buf, len, &pos) % 4) + 2; // 4-64 + uint8_t cblockh_exp = (ReadByte(buf, len, &pos) % 4) + 2; // 4-64 + uint8_t use_tiles = ReadByte(buf, len, &pos) & 1; + uint8_t tile_size = ReadByte(buf, len, &pos); + + // Create image + opj_image_cmptparm_t* cmptparms = (opj_image_cmptparm_t*)calloc(numcomps, sizeof(opj_image_cmptparm_t)); + if (!cmptparms) { + return 0; + } + + for (OPJ_UINT32 i = 0; i < numcomps; i++) { + cmptparms[i].dx = 1; + cmptparms[i].dy = 1; + cmptparms[i].w = width; + cmptparms[i].h = height; + cmptparms[i].x0 = 0; + cmptparms[i].y0 = 0; + cmptparms[i].prec = prec; + cmptparms[i].sgnd = sgnd; + } + + OPJ_COLOR_SPACE color_space = (numcomps >= 3) ? OPJ_CLRSPC_SRGB : OPJ_CLRSPC_GRAY; + opj_image_t* image = opj_image_create(numcomps, cmptparms, color_space); + free(cmptparms); + + if (!image) { + return 0; + } + + image->x0 = 0; + image->y0 = 0; + image->x1 = width; + image->y1 = height; + + // Fill image data from remaining fuzz input + size_t pixels = (size_t)width * height; + for (OPJ_UINT32 c = 0; c < numcomps; c++) { + if (!image->comps[c].data) { + opj_image_destroy(image); + return 0; + } + for (size_t p = 0; p < pixels; p++) { + if (pos < len) { + image->comps[c].data[p] = (OPJ_INT32)buf[pos++]; + } else { + image->comps[c].data[p] = 0; + } + } + } + + // Setup encoder + OPJ_CODEC_FORMAT format = codec_type ? OPJ_CODEC_JP2 : OPJ_CODEC_J2K; + opj_codec_t* codec = opj_create_compress(format); + if (!codec) { + opj_image_destroy(image); + return 0; + } + + opj_set_info_handler(codec, InfoCallback, NULL); + opj_set_warning_handler(codec, WarningCallback, NULL); + opj_set_error_handler(codec, ErrorCallback, NULL); + + opj_cparameters_t parameters; + opj_set_default_encoder_parameters(¶meters); + + parameters.prog_order = (OPJ_PROG_ORDER)prog_order; + parameters.numresolution = num_resolutions; + parameters.irreversible = irreversible; + parameters.tcp_numlayers = tcp_numlayers; + parameters.cblockw_init = 1 << cblockw_exp; + parameters.cblockh_init = 1 << cblockh_exp; + parameters.cp_disto_alloc = 1; + + // Set layer rates + for (int i = 0; i < tcp_numlayers; i++) { + parameters.tcp_rates[i] = (float)(tcp_numlayers - i); + } + parameters.tcp_rates[tcp_numlayers - 1] = 1.0f; // Last layer lossless + + // Optionally use tiles + if (use_tiles && tile_size > 0) { + OPJ_UINT32 ts = ((tile_size % 64) + 16); + parameters.tile_size_on = OPJ_TRUE; + parameters.cp_tx0 = 0; + parameters.cp_ty0 = 0; + parameters.cp_tdx = (int)ts; + parameters.cp_tdy = (int)ts; + } + + if (!opj_setup_encoder(codec, ¶meters, image)) { + opj_destroy_codec(codec); + opj_image_destroy(image); + return 0; + } + + // Create output stream (discard output) + opj_stream_t* stream = opj_stream_create(1024, OPJ_FALSE); + if (!stream) { + opj_destroy_codec(codec); + opj_image_destroy(image); + return 0; + } + + MemWriteStream memStream; + memStream.data = NULL; + memStream.size = 0; + memStream.offset = 0; + + opj_stream_set_write_function(stream, WriteCallback); + opj_stream_set_seek_function(stream, SeekCallback); + opj_stream_set_skip_function(stream, SkipCallback); + opj_stream_set_user_data(stream, &memStream, NULL); + + // Encode + if (opj_start_compress(codec, image, stream)) { + opj_encode(codec, stream); + opj_end_compress(codec, stream); + } + + // Cleanup + opj_stream_destroy(stream); + opj_destroy_codec(codec); + opj_image_destroy(image); + + return 0; +} diff --git a/tests/fuzzers/opj_compress_fuzzer.options b/tests/fuzzers/opj_compress_fuzzer.options new file mode 100644 index 000000000..c63c1cd5f --- /dev/null +++ b/tests/fuzzers/opj_compress_fuzzer.options @@ -0,0 +1,9 @@ +[libfuzzer] +max_len = 65536 +timeout = 30 + +[honggfuzz] +timeout = 30 + +[afl] +timeout = 30 diff --git a/tests/fuzzers/opj_decode_area_fuzzer.cpp b/tests/fuzzers/opj_decode_area_fuzzer.cpp new file mode 100644 index 000000000..1bf859fb0 --- /dev/null +++ b/tests/fuzzers/opj_decode_area_fuzzer.cpp @@ -0,0 +1,244 @@ +/* + * The copyright in this software is being made available under the 2-clauses + * BSD License, included below. This software may be subject to other third + * party and contributor rights, including patent rights, and no such rights + * are granted under this license. + * + * Copyright (c) 2024, OpenJPEG contributors + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * 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 OWNER 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 exercises partial/area decoding with various options: + * - opj_set_decode_area() with fuzz-controlled regions + * - opj_set_decoded_resolution_factor() for multi-resolution decoding + * - opj_decoder_set_strict_mode() for strict vs lenient parsing + * - opj_codec_set_threads() for multi-threaded decoding + */ + +#include +#include +#include +#include + +#include "openjpeg.h" + +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv); +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len); + +typedef struct { + const uint8_t* pabyData; + size_t nCurPos; + size_t nLength; +} MemFile; + +static void ErrorCallback(const char* msg, void* client_data) +{ + (void)msg; + (void)client_data; +} + +static void WarningCallback(const char* msg, void* client_data) +{ + (void)msg; + (void)client_data; +} + +static void InfoCallback(const char* msg, void* client_data) +{ + (void)msg; + (void)client_data; +} + +static OPJ_SIZE_T ReadCallback(void* pBuffer, OPJ_SIZE_T nBytes, void* pUserData) +{ + MemFile* memFile = (MemFile*)pUserData; + if (memFile->nCurPos >= memFile->nLength) { + return (OPJ_SIZE_T)-1; + } + if (memFile->nCurPos + nBytes >= memFile->nLength) { + size_t nToRead = memFile->nLength - memFile->nCurPos; + memcpy(pBuffer, memFile->pabyData + memFile->nCurPos, nToRead); + memFile->nCurPos = memFile->nLength; + return nToRead; + } + if (nBytes == 0) { + return (OPJ_SIZE_T)-1; + } + memcpy(pBuffer, memFile->pabyData + memFile->nCurPos, nBytes); + memFile->nCurPos += nBytes; + return nBytes; +} + +static OPJ_BOOL SeekCallback(OPJ_OFF_T nBytes, void* pUserData) +{ + MemFile* memFile = (MemFile*)pUserData; + memFile->nCurPos = (size_t)nBytes; + return OPJ_TRUE; +} + +static OPJ_OFF_T SkipCallback(OPJ_OFF_T nBytes, void* pUserData) +{ + MemFile* memFile = (MemFile*)pUserData; + memFile->nCurPos += (size_t)nBytes; + return nBytes; +} + +static const unsigned char jpc_header[] = {0xff, 0x4f}; +static const unsigned char jp2_box_jp[] = {0x6a, 0x50, 0x20, 0x20}; + +int LLVMFuzzerInitialize(int* argc, char*** argv) +{ + (void)argc; + (void)argv; + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) +{ + if (len < 16) { + return 0; + } + + // Use first bytes for fuzzer control parameters + uint8_t ctrl_strict = buf[0] & 0x01; + uint8_t ctrl_threads = (buf[0] >> 1) & 0x03; // 0-3 threads + uint8_t ctrl_reduce = (buf[0] >> 3) & 0x07; // 0-7 resolution reduction + uint8_t ctrl_x0_pct = buf[1]; + uint8_t ctrl_y0_pct = buf[2]; + uint8_t ctrl_x1_pct = buf[3]; + uint8_t ctrl_y1_pct = buf[4]; + + // Skip control bytes for codec detection + const uint8_t* data = buf + 5; + size_t data_len = len - 5; + + // Determine codec format + OPJ_CODEC_FORMAT eCodecFormat; + if (data_len >= sizeof(jpc_header) && + memcmp(data, jpc_header, sizeof(jpc_header)) == 0) { + eCodecFormat = OPJ_CODEC_J2K; + } else if (data_len >= 12 && + memcmp(data + 4, jp2_box_jp, sizeof(jp2_box_jp)) == 0) { + eCodecFormat = OPJ_CODEC_JP2; + } else { + return 0; + } + + opj_codec_t* pCodec = opj_create_decompress(eCodecFormat); + if (!pCodec) { + return 0; + } + + opj_set_info_handler(pCodec, InfoCallback, NULL); + opj_set_warning_handler(pCodec, WarningCallback, NULL); + opj_set_error_handler(pCodec, ErrorCallback, NULL); + + opj_dparameters_t parameters; + opj_set_default_decoder_parameters(¶meters); + parameters.cp_reduce = ctrl_reduce; + + if (!opj_setup_decoder(pCodec, ¶meters)) { + opj_destroy_codec(pCodec); + return 0; + } + + // Set strict mode based on fuzz input + opj_decoder_set_strict_mode(pCodec, ctrl_strict ? OPJ_TRUE : OPJ_FALSE); + + // Set thread count based on fuzz input + if (ctrl_threads > 0) { + opj_codec_set_threads(pCodec, ctrl_threads); + } + + opj_stream_t* pStream = opj_stream_create(1024, OPJ_TRUE); + if (!pStream) { + opj_destroy_codec(pCodec); + return 0; + } + + MemFile memFile; + memFile.pabyData = data; + memFile.nLength = data_len; + memFile.nCurPos = 0; + + opj_stream_set_user_data_length(pStream, data_len); + opj_stream_set_read_function(pStream, ReadCallback); + opj_stream_set_seek_function(pStream, SeekCallback); + opj_stream_set_skip_function(pStream, SkipCallback); + opj_stream_set_user_data(pStream, &memFile, NULL); + + opj_image_t* psImage = NULL; + if (!opj_read_header(pStream, pCodec, &psImage)) { + opj_destroy_codec(pCodec); + opj_stream_destroy(pStream); + opj_image_destroy(psImage); + return 0; + } + + // Set resolution factor (different from cp_reduce in parameters) + opj_set_decoded_resolution_factor(pCodec, ctrl_reduce % 4); + + // Calculate decode area based on fuzz-controlled percentages + OPJ_INT32 img_width = (OPJ_INT32)(psImage->x1 - psImage->x0); + OPJ_INT32 img_height = (OPJ_INT32)(psImage->y1 - psImage->y0); + + if (img_width <= 0 || img_height <= 0) { + opj_end_decompress(pCodec, pStream); + opj_stream_destroy(pStream); + opj_destroy_codec(pCodec); + opj_image_destroy(psImage); + return 0; + } + + // Limit dimensions to prevent OOM + if (img_width > 4096) img_width = 4096; + if (img_height > 4096) img_height = 4096; + + OPJ_INT32 x0 = psImage->x0 + (img_width * ctrl_x0_pct) / 256; + OPJ_INT32 y0 = psImage->y0 + (img_height * ctrl_y0_pct) / 256; + OPJ_INT32 x1 = psImage->x0 + (img_width * ctrl_x1_pct) / 256; + OPJ_INT32 y1 = psImage->y0 + (img_height * ctrl_y1_pct) / 256; + + // Ensure valid area (swap if needed, ensure non-zero size) + if (x0 > x1) { OPJ_INT32 tmp = x0; x0 = x1; x1 = tmp; } + if (y0 > y1) { OPJ_INT32 tmp = y0; y0 = y1; y1 = tmp; } + if (x1 <= x0) x1 = x0 + 1; + if (y1 <= y0) y1 = y0 + 1; + + // Limit area size + if (x1 - x0 > 1024) x1 = x0 + 1024; + if (y1 - y0 > 1024) y1 = y0 + 1024; + + if (opj_set_decode_area(pCodec, psImage, x0, y0, x1, y1)) { + opj_decode(pCodec, pStream, psImage); + } + + opj_end_decompress(pCodec, pStream); + opj_stream_destroy(pStream); + opj_destroy_codec(pCodec); + opj_image_destroy(psImage); + + return 0; +} diff --git a/tests/fuzzers/opj_decompress_fuzzer.dict b/tests/fuzzers/opj_decompress_fuzzer.dict new file mode 100644 index 000000000..55b121517 --- /dev/null +++ b/tests/fuzzers/opj_decompress_fuzzer.dict @@ -0,0 +1,79 @@ +# JPEG 2000 dictionary for fuzzing +# Magic bytes and markers + +# J2K codestream markers +"\xff\x4f" # SOC - Start of codestream +"\xff\x51" # SIZ - Image and tile size +"\xff\x52" # COD - Coding style default +"\xff\x53" # COC - Coding style component +"\xff\x55" # TLM - Tile-part lengths +"\xff\x57" # PLM - Packet length, main header +"\xff\x58" # PLT - Packet length, tile-part header +"\xff\x5c" # QCD - Quantization default +"\xff\x5d" # QCC - Quantization component +"\xff\x5e" # RGN - Region of interest +"\xff\x5f" # POC - Progression order change +"\xff\x60" # PPM - Packed packet headers, main header +"\xff\x61" # PPT - Packed packet headers, tile-part header +"\xff\x63" # CRG - Component registration +"\xff\x64" # COM - Comment +"\xff\x90" # SOT - Start of tile-part +"\xff\x91" # SOP - Start of packet +"\xff\x92" # EPH - End of packet header +"\xff\x93" # SOD - Start of data +"\xff\xd9" # EOC - End of codestream + +# JP2 file format boxes +"\x6a\x50\x20\x20" # JP2 signature 'jP ' +"\x6a\x50\x32\x20" # JP2 signature alt +"\x66\x74\x79\x70" # ftyp - File type box +"\x6a\x70\x32\x68" # jp2h - JP2 header box +"\x69\x68\x64\x72" # ihdr - Image header box +"\x63\x6f\x6c\x72" # colr - Color specification box +"\x6a\x70\x32\x63" # jp2c - Contiguous codestream box +"\x75\x75\x69\x64" # uuid - UUID box +"\x72\x65\x73\x20" # res - Resolution box +"\x72\x65\x73\x63" # resc - Capture resolution box +"\x72\x65\x73\x64" # resd - Default display resolution box +"\x62\x70\x63\x63" # bpcc - Bits per component box +"\x70\x63\x6c\x72" # pclr - Palette box +"\x63\x6d\x61\x70" # cmap - Component mapping box +"\x63\x64\x65\x66" # cdef - Channel definition box +"\x78\x6d\x6c\x20" # xml - XML box +"\x6a\x70\x32\x69" # jp2i - Intellectual property box + +# JPH/HTJ2K markers +"\xff\x59" # CAP - Extended capabilities + +# Common integer values +"\x00\x00\x00\x00" +"\x00\x00\x00\x01" +"\x00\x00\x00\x02" +"\x00\x00\x00\x04" +"\x00\x00\x00\x08" +"\x00\x00\x00\x10" +"\x00\x00\x00\x20" +"\x00\x00\x00\x40" +"\x00\x00\x00\x80" +"\xff\xff\xff\xff" + +# Size values (16-bit) +"\x00\x01" +"\x00\x02" +"\x00\x04" +"\x00\x08" +"\x00\x10" +"\x00\x20" +"\x00\x40" +"\x00\x80" +"\x01\x00" +"\x02\x00" +"\x04\x00" +"\x08\x00" + +# Progression orders +"\x00" # LRCP +"\x01" # RLCP +"\x02" # RPCL +"\x03" # PCRL +"\x04" # CPRL diff --git a/tests/fuzzers/opj_dump_info_fuzzer.cpp b/tests/fuzzers/opj_dump_info_fuzzer.cpp new file mode 100644 index 000000000..090dde15b --- /dev/null +++ b/tests/fuzzers/opj_dump_info_fuzzer.cpp @@ -0,0 +1,160 @@ +/* + * Codec dump/info fuzzer - exercises metadata and index APIs + * Covers: opj_get_cstr_info, opj_get_cstr_index, opj_get_jp2_metadata, + * opj_get_jp2_index, opj_dump_codec + */ + +#include +#include +#include +#include +#include + +#include "openjpeg.h" + +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv); +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len); + +typedef struct { + const uint8_t* data; + size_t size; + size_t pos; +} MemFile; + +static void DummyCallback(const char*, void*) {} + +static OPJ_SIZE_T ReadCallback(void* pBuffer, OPJ_SIZE_T nBytes, void* pUserData) +{ + MemFile* f = (MemFile*)pUserData; + if (f->pos >= f->size) return (OPJ_SIZE_T)-1; + size_t toRead = nBytes; + if (f->pos + toRead > f->size) toRead = f->size - f->pos; + if (toRead == 0) return (OPJ_SIZE_T)-1; + memcpy(pBuffer, f->data + f->pos, toRead); + f->pos += toRead; + return toRead; +} + +static OPJ_BOOL SeekCallback(OPJ_OFF_T nBytes, void* pUserData) +{ + MemFile* f = (MemFile*)pUserData; + f->pos = (size_t)nBytes; + return OPJ_TRUE; +} + +static OPJ_OFF_T SkipCallback(OPJ_OFF_T nBytes, void* pUserData) +{ + MemFile* f = (MemFile*)pUserData; + f->pos += (size_t)nBytes; + return nBytes; +} + +static const unsigned char jpc_header[] = {0xff, 0x4f}; +static const unsigned char jp2_box_jp[] = {0x6a, 0x50, 0x20, 0x20}; + +int LLVMFuzzerInitialize(int*, char***) { return 0; } + +int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) +{ + if (len < 8) return 0; + + // Determine codec format + OPJ_CODEC_FORMAT format; + if (len >= 2 && memcmp(buf, jpc_header, 2) == 0) { + format = OPJ_CODEC_J2K; + } else if (len >= 12 && memcmp(buf + 4, jp2_box_jp, 4) == 0) { + format = OPJ_CODEC_JP2; + } else { + return 0; + } + + opj_codec_t* codec = opj_create_decompress(format); + if (!codec) return 0; + + opj_set_info_handler(codec, DummyCallback, NULL); + opj_set_warning_handler(codec, DummyCallback, NULL); + opj_set_error_handler(codec, DummyCallback, NULL); + + opj_dparameters_t params; + opj_set_default_decoder_parameters(¶ms); + + if (!opj_setup_decoder(codec, ¶ms)) { + opj_destroy_codec(codec); + return 0; + } + + opj_stream_t* stream = opj_stream_create(1024, OPJ_TRUE); + if (!stream) { + opj_destroy_codec(codec); + return 0; + } + + MemFile memFile = {buf, len, 0}; + opj_stream_set_user_data_length(stream, len); + opj_stream_set_read_function(stream, ReadCallback); + opj_stream_set_seek_function(stream, SeekCallback); + opj_stream_set_skip_function(stream, SkipCallback); + opj_stream_set_user_data(stream, &memFile, NULL); + + opj_image_t* image = NULL; + if (!opj_read_header(stream, codec, &image)) { + opj_stream_destroy(stream); + opj_destroy_codec(codec); + return 0; + } + + // Exercise all info/dump APIs + + // 1. Get codestream info + opj_codestream_info_v2_t* cstrInfo = opj_get_cstr_info(codec); + if (cstrInfo) { + // Access fields to ensure they're valid + (void)cstrInfo->tx0; + (void)cstrInfo->ty0; + (void)cstrInfo->tdx; + (void)cstrInfo->tdy; + (void)cstrInfo->tw; + (void)cstrInfo->th; + (void)cstrInfo->nbcomps; + opj_destroy_cstr_info(&cstrInfo); + } + + // 2. Get codestream index + opj_codestream_index_t* cstrIndex = opj_get_cstr_index(codec); + if (cstrIndex) { + // Access fields + (void)cstrIndex->main_head_start; + (void)cstrIndex->main_head_end; + (void)cstrIndex->codestream_size; + (void)cstrIndex->nb_of_tiles; + opj_destroy_cstr_index(&cstrIndex); + } + + // Note: opj_get_jp2_metadata and opj_get_jp2_index are declared + // but not implemented in the library + + // 4. Dump codec info to /dev/null + FILE* nullFile = fopen("/dev/null", "w"); + if (nullFile) { + opj_dump_codec(codec, OPJ_IMG_INFO | OPJ_J2K_MH_INFO | OPJ_J2K_TH_INFO, nullFile); + fclose(nullFile); + } + + // Try to decode a small area to exercise more code paths + OPJ_UINT32 width = image->x1 - image->x0; + OPJ_UINT32 height = image->y1 - image->y0; + if (width > 256) width = 256; + if (height > 256) height = 256; + + if (opj_set_decode_area(codec, image, image->x0, image->y0, + image->x0 + width, image->y0 + height)) { + opj_decode(codec, stream, image); + } + + opj_end_decompress(codec, stream); + opj_stream_destroy(stream); + opj_destroy_codec(codec); + opj_image_destroy(image); + + return 0; +} diff --git a/tests/fuzzers/opj_encoder_options_fuzzer.cpp b/tests/fuzzers/opj_encoder_options_fuzzer.cpp new file mode 100644 index 000000000..e08c6c579 --- /dev/null +++ b/tests/fuzzers/opj_encoder_options_fuzzer.cpp @@ -0,0 +1,187 @@ +/* + * Extended encoder options fuzzer + * Covers: opj_encoder_set_extra_options with PLT, TLM, GUARD_BITS + * Also tests various codec modes, code block sizes, and precinct sizes + */ + +#include +#include +#include +#include +#include + +#include "openjpeg.h" + +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv); +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len); + +static const size_t kMaxInputSize = 32 * 1024; + +static void DummyCallback(const char*, void*) {} + +static OPJ_SIZE_T WriteCallback(void*, OPJ_SIZE_T nBytes, void*) +{ + return nBytes; +} + +static OPJ_BOOL SeekCallback(OPJ_OFF_T, void*) { return OPJ_TRUE; } +static OPJ_OFF_T SkipCallback(OPJ_OFF_T nBytes, void*) { return nBytes; } + +static uint8_t ReadByte(const uint8_t* buf, size_t len, size_t* pos) +{ + if (*pos >= len) return 0; + return buf[(*pos)++]; +} + +int LLVMFuzzerInitialize(int*, char***) { return 0; } + +int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) +{ + if (len < 30 || len > kMaxInputSize) return 0; + + size_t pos = 0; + + // Image parameters + OPJ_UINT32 width = (ReadByte(buf, len, &pos) % 48) + 16; + OPJ_UINT32 height = (ReadByte(buf, len, &pos) % 48) + 16; + OPJ_UINT32 numComps = (ReadByte(buf, len, &pos) % 4) + 1; + OPJ_UINT32 prec = (ReadByte(buf, len, &pos) % 12) + 1; + OPJ_BOOL sgnd = ReadByte(buf, len, &pos) & 1; + + // Extended options + uint8_t use_plt = ReadByte(buf, len, &pos) & 1; + uint8_t use_tlm = ReadByte(buf, len, &pos) & 1; + uint8_t guard_bits = ReadByte(buf, len, &pos) % 8; // 0-7 + uint8_t codec_type = ReadByte(buf, len, &pos) & 1; + uint8_t irreversible = ReadByte(buf, len, &pos) & 1; + uint8_t prog_order = ReadByte(buf, len, &pos) % 5; + uint8_t num_resolutions = (ReadByte(buf, len, &pos) % 5) + 1; + + // Code block size (power of 2: 4, 8, 16, 32, 64) + uint8_t cblk_exp_w = (ReadByte(buf, len, &pos) % 5) + 2; + uint8_t cblk_exp_h = (ReadByte(buf, len, &pos) % 5) + 2; + + // Precinct sizes + uint8_t use_precincts = ReadByte(buf, len, &pos) & 1; + uint8_t prec_exp = (ReadByte(buf, len, &pos) % 4) + 6; // 64-512 + + // SOP/EPH markers + uint8_t use_sop = ReadByte(buf, len, &pos) & 1; + uint8_t use_eph = ReadByte(buf, len, &pos) & 1; + + // Create image + opj_image_cmptparm_t* cmptparms = (opj_image_cmptparm_t*)calloc(numComps, sizeof(opj_image_cmptparm_t)); + if (!cmptparms) return 0; + + for (OPJ_UINT32 i = 0; i < numComps; i++) { + cmptparms[i].dx = 1; + cmptparms[i].dy = 1; + cmptparms[i].w = width; + cmptparms[i].h = height; + cmptparms[i].prec = prec; + cmptparms[i].sgnd = sgnd; + } + + OPJ_COLOR_SPACE colorSpace = (numComps >= 3) ? OPJ_CLRSPC_SRGB : OPJ_CLRSPC_GRAY; + opj_image_t* image = opj_image_create(numComps, cmptparms, colorSpace); + free(cmptparms); + if (!image) return 0; + + image->x0 = 0; + image->y0 = 0; + image->x1 = width; + image->y1 = height; + + // Fill image data + size_t pixels = width * height; + for (OPJ_UINT32 c = 0; c < numComps; c++) { + if (!image->comps[c].data) { + opj_image_destroy(image); + return 0; + } + for (size_t p = 0; p < pixels; p++) { + image->comps[c].data[p] = (pos < len) ? buf[pos++] : 0; + } + } + + // Create encoder + OPJ_CODEC_FORMAT format = codec_type ? OPJ_CODEC_JP2 : OPJ_CODEC_J2K; + opj_codec_t* codec = opj_create_compress(format); + if (!codec) { + opj_image_destroy(image); + return 0; + } + + opj_set_info_handler(codec, DummyCallback, NULL); + opj_set_warning_handler(codec, DummyCallback, NULL); + opj_set_error_handler(codec, DummyCallback, NULL); + + // Setup encoding parameters + opj_cparameters_t params; + opj_set_default_encoder_parameters(¶ms); + + params.prog_order = (OPJ_PROG_ORDER)prog_order; + params.numresolution = num_resolutions; + params.irreversible = irreversible; + params.tcp_numlayers = 1; + params.tcp_rates[0] = 0; + params.cp_disto_alloc = 1; + + // Code block size + params.cblockw_init = 1 << cblk_exp_w; + params.cblockh_init = 1 << cblk_exp_h; + + // Precinct sizes + if (use_precincts) { + params.csty |= 0x01; // Use precincts + params.res_spec = num_resolutions; + for (int i = 0; i < num_resolutions; i++) { + params.prcw_init[i] = 1 << prec_exp; + params.prch_init[i] = 1 << prec_exp; + } + } + + // SOP/EPH markers + if (use_sop) params.csty |= 0x02; + if (use_eph) params.csty |= 0x04; + + if (!opj_setup_encoder(codec, ¶ms, image)) { + opj_destroy_codec(codec); + opj_image_destroy(image); + return 0; + } + + // Set extended options using opj_encoder_set_extra_options + char pltOpt[16], tlmOpt[16], guardOpt[16]; + snprintf(pltOpt, sizeof(pltOpt), "PLT=%s", use_plt ? "YES" : "NO"); + snprintf(tlmOpt, sizeof(tlmOpt), "TLM=%s", use_tlm ? "YES" : "NO"); + snprintf(guardOpt, sizeof(guardOpt), "GUARD_BITS=%d", guard_bits); + + const char* options[] = {pltOpt, tlmOpt, guardOpt, NULL}; + opj_encoder_set_extra_options(codec, options); + + // Create output stream + opj_stream_t* stream = opj_stream_create(1024, OPJ_FALSE); + if (!stream) { + opj_destroy_codec(codec); + opj_image_destroy(image); + return 0; + } + + opj_stream_set_write_function(stream, WriteCallback); + opj_stream_set_seek_function(stream, SeekCallback); + opj_stream_set_skip_function(stream, SkipCallback); + opj_stream_set_user_data(stream, NULL, NULL); + + // Encode + if (opj_start_compress(codec, image, stream)) { + opj_encode(codec, stream); + opj_end_compress(codec, stream); + } + + opj_stream_destroy(stream); + opj_destroy_codec(codec); + opj_image_destroy(image); + + return 0; +} diff --git a/tests/fuzzers/opj_mct_fuzzer.cpp b/tests/fuzzers/opj_mct_fuzzer.cpp new file mode 100644 index 000000000..e4d54338e --- /dev/null +++ b/tests/fuzzers/opj_mct_fuzzer.cpp @@ -0,0 +1,194 @@ +/* + * MCT (Multi-Component Transform) encoder fuzzer + * Covers: opj_set_MCT, custom MCT matrices, Part-2 extensions + * Also tests various encoder profiles and progression orders + */ + +#include +#include +#include +#include +#include + +#include "openjpeg.h" + +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv); +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len); + +static const size_t kMaxInputSize = 32 * 1024; + +static void DummyCallback(const char*, void*) {} + +static OPJ_SIZE_T WriteCallback(void*, OPJ_SIZE_T nBytes, void*) +{ + return nBytes; // Discard output +} + +static OPJ_BOOL SeekCallback(OPJ_OFF_T, void*) { return OPJ_TRUE; } +static OPJ_OFF_T SkipCallback(OPJ_OFF_T nBytes, void*) { return nBytes; } + +static uint8_t ReadByte(const uint8_t* buf, size_t len, size_t* pos) +{ + if (*pos >= len) return 0; + return buf[(*pos)++]; +} + +static float ReadFloat(const uint8_t* buf, size_t len, size_t* pos) +{ + uint8_t b0 = ReadByte(buf, len, pos); + uint8_t b1 = ReadByte(buf, len, pos); + // Generate a float between -2.0 and 2.0 + int16_t val = (int16_t)((b1 << 8) | b0); + return (float)val / 16384.0f; +} + +int LLVMFuzzerInitialize(int*, char***) { return 0; } + +int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) +{ + if (len < 50 || len > kMaxInputSize) return 0; + + size_t pos = 0; + + // Image parameters - always 3 components for MCT + OPJ_UINT32 width = (ReadByte(buf, len, &pos) % 32) + 8; // 8-39 + OPJ_UINT32 height = (ReadByte(buf, len, &pos) % 32) + 8; // 8-39 + OPJ_UINT32 numComps = 3; // MCT requires at least 3 components + OPJ_UINT32 prec = (ReadByte(buf, len, &pos) % 8) + 1; // 1-8 bits + OPJ_BOOL sgnd = ReadByte(buf, len, &pos) & 1; + + // Encoder parameters + uint8_t codec_type = ReadByte(buf, len, &pos) & 1; + uint8_t use_mct = ReadByte(buf, len, &pos); + uint8_t prog_order = ReadByte(buf, len, &pos) % 5; + uint8_t num_resolutions = (ReadByte(buf, len, &pos) % 4) + 1; + uint8_t irreversible = ReadByte(buf, len, &pos) & 1; + uint8_t num_layers = (ReadByte(buf, len, &pos) % 5) + 1; + uint8_t cblk_style = ReadByte(buf, len, &pos); + uint8_t profile = ReadByte(buf, len, &pos); + + // Create image + opj_image_cmptparm_t cmptparms[3]; + memset(cmptparms, 0, sizeof(cmptparms)); + for (OPJ_UINT32 i = 0; i < numComps; i++) { + cmptparms[i].dx = 1; + cmptparms[i].dy = 1; + cmptparms[i].w = width; + cmptparms[i].h = height; + cmptparms[i].prec = prec; + cmptparms[i].sgnd = sgnd; + } + + opj_image_t* image = opj_image_create(numComps, cmptparms, OPJ_CLRSPC_SRGB); + if (!image) return 0; + + image->x0 = 0; + image->y0 = 0; + image->x1 = width; + image->y1 = height; + + // Fill image data + size_t pixels = width * height; + for (OPJ_UINT32 c = 0; c < numComps; c++) { + if (!image->comps[c].data) { + opj_image_destroy(image); + return 0; + } + for (size_t p = 0; p < pixels; p++) { + image->comps[c].data[p] = (pos < len) ? buf[pos++] : 0; + } + } + + // Create encoder + OPJ_CODEC_FORMAT format = codec_type ? OPJ_CODEC_JP2 : OPJ_CODEC_J2K; + opj_codec_t* codec = opj_create_compress(format); + if (!codec) { + opj_image_destroy(image); + return 0; + } + + opj_set_info_handler(codec, DummyCallback, NULL); + opj_set_warning_handler(codec, DummyCallback, NULL); + opj_set_error_handler(codec, DummyCallback, NULL); + + // Setup encoding parameters + opj_cparameters_t params; + opj_set_default_encoder_parameters(¶ms); + + params.prog_order = (OPJ_PROG_ORDER)prog_order; + params.numresolution = num_resolutions; + params.irreversible = irreversible; + params.tcp_numlayers = num_layers; + params.cp_disto_alloc = 1; + params.mode = cblk_style & 0x3F; // Valid cblk_style bits + + // Set layer rates + for (int i = 0; i < num_layers; i++) { + params.tcp_rates[i] = (float)(num_layers - i + 1); + } + params.tcp_rates[num_layers - 1] = 0; // Last layer lossless + + // Set profile based on fuzz input + switch (profile % 6) { + case 0: params.rsiz = OPJ_PROFILE_NONE; break; + case 1: params.rsiz = OPJ_PROFILE_0; break; + case 2: params.rsiz = OPJ_PROFILE_1; break; + case 3: params.rsiz = OPJ_PROFILE_CINEMA_2K; break; + case 4: params.rsiz = OPJ_PROFILE_CINEMA_4K; break; + case 5: params.rsiz = OPJ_PROFILE_PART2 | OPJ_EXTENSION_MCT; break; + } + + // Setup MCT if requested + if (use_mct & 0x01) { + params.tcp_mct = 1; // Enable standard MCT + } + + // Try custom MCT if profile supports Part-2 + if ((use_mct & 0x02) && (params.rsiz & OPJ_PROFILE_PART2)) { + // Read custom MCT matrix (3x3 for 3 components) + OPJ_FLOAT32 mctMatrix[9]; + for (int i = 0; i < 9; i++) { + mctMatrix[i] = ReadFloat(buf, len, &pos); + } + + // Read DC shift values + OPJ_INT32 dcShift[3]; + for (int i = 0; i < 3; i++) { + dcShift[i] = (OPJ_INT32)(ReadByte(buf, len, &pos) - 128); + } + + // Set custom MCT + opj_set_MCT(¶ms, mctMatrix, dcShift, numComps); + } + + if (!opj_setup_encoder(codec, ¶ms, image)) { + opj_destroy_codec(codec); + opj_image_destroy(image); + return 0; + } + + // Create output stream + opj_stream_t* stream = opj_stream_create(1024, OPJ_FALSE); + if (!stream) { + opj_destroy_codec(codec); + opj_image_destroy(image); + return 0; + } + + opj_stream_set_write_function(stream, WriteCallback); + opj_stream_set_seek_function(stream, SeekCallback); + opj_stream_set_skip_function(stream, SkipCallback); + opj_stream_set_user_data(stream, NULL, NULL); + + // Encode + if (opj_start_compress(codec, image, stream)) { + opj_encode(codec, stream); + opj_end_compress(codec, stream); + } + + opj_stream_destroy(stream); + opj_destroy_codec(codec); + opj_image_destroy(image); + + return 0; +} diff --git a/tests/fuzzers/opj_roundtrip_fuzzer.cpp b/tests/fuzzers/opj_roundtrip_fuzzer.cpp new file mode 100644 index 000000000..cc6464e3f --- /dev/null +++ b/tests/fuzzers/opj_roundtrip_fuzzer.cpp @@ -0,0 +1,342 @@ +/* + * The copyright in this software is being made available under the 2-clauses + * BSD License, included below. This software may be subject to other third + * party and contributor rights, including patent rights, and no such rights + * are granted under this license. + * + * Copyright (c) 2024, OpenJPEG contributors + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * 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 OWNER 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. + */ + +/* + * Round-trip fuzzer: Encode image data, then decode the result. + * This catches encoder bugs that produce invalid output, and exercises + * both encode and decode paths together. + */ + +#include +#include +#include +#include +#include + +#include "openjpeg.h" + +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv); +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len); + +static const size_t kMaxInputSize = 32 * 1024; +static const OPJ_UINT32 kMaxWidth = 128; +static const OPJ_UINT32 kMaxHeight = 128; +static const OPJ_UINT32 kMaxComponents = 4; + +// Memory buffer for encoding output +static std::vector g_encodedData; +static size_t g_encodedSize; +static size_t g_encodedPos; + +static void ErrorCallback(const char* msg, void* client_data) +{ + (void)msg; + (void)client_data; +} + +static void WarningCallback(const char* msg, void* client_data) +{ + (void)msg; + (void)client_data; +} + +static void InfoCallback(const char* msg, void* client_data) +{ + (void)msg; + (void)client_data; +} + +// Write callback for encoder +static OPJ_SIZE_T WriteCallback(void* pBuffer, OPJ_SIZE_T nBytes, void* pUserData) +{ + (void)pUserData; + size_t newSize = g_encodedPos + nBytes; + if (newSize > g_encodedData.size()) { + // Limit maximum encoded size + if (newSize > 16 * 1024 * 1024) { + return (OPJ_SIZE_T)-1; + } + g_encodedData.resize(newSize); + } + memcpy(g_encodedData.data() + g_encodedPos, pBuffer, nBytes); + g_encodedPos += nBytes; + if (g_encodedPos > g_encodedSize) { + g_encodedSize = g_encodedPos; + } + return nBytes; +} + +static OPJ_BOOL WriteSeekCallback(OPJ_OFF_T nBytes, void* pUserData) +{ + (void)pUserData; + if (nBytes < 0) { + return OPJ_FALSE; + } + g_encodedPos = (size_t)nBytes; + return OPJ_TRUE; +} + +static OPJ_OFF_T WriteSkipCallback(OPJ_OFF_T nBytes, void* pUserData) +{ + (void)pUserData; + g_encodedPos += (size_t)nBytes; + return nBytes; +} + +// Read callback for decoder +typedef struct { + const uint8_t* data; + size_t size; + size_t pos; +} ReadState; + +static OPJ_SIZE_T ReadCallback(void* pBuffer, OPJ_SIZE_T nBytes, void* pUserData) +{ + ReadState* state = (ReadState*)pUserData; + if (state->pos >= state->size) { + return (OPJ_SIZE_T)-1; + } + size_t toRead = nBytes; + if (state->pos + toRead > state->size) { + toRead = state->size - state->pos; + } + if (toRead == 0) { + return (OPJ_SIZE_T)-1; + } + memcpy(pBuffer, state->data + state->pos, toRead); + state->pos += toRead; + return toRead; +} + +static OPJ_BOOL ReadSeekCallback(OPJ_OFF_T nBytes, void* pUserData) +{ + ReadState* state = (ReadState*)pUserData; + state->pos = (size_t)nBytes; + return OPJ_TRUE; +} + +static OPJ_OFF_T ReadSkipCallback(OPJ_OFF_T nBytes, void* pUserData) +{ + ReadState* state = (ReadState*)pUserData; + state->pos += (size_t)nBytes; + return nBytes; +} + +static uint8_t ReadByte(const uint8_t* buf, size_t len, size_t* pos) +{ + if (*pos >= len) return 0; + return buf[(*pos)++]; +} + +int LLVMFuzzerInitialize(int* argc, char*** argv) +{ + (void)argc; + (void)argv; + g_encodedData.reserve(1024 * 1024); // Pre-allocate 1MB + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) +{ + if (len < 16 || len > kMaxInputSize) { + return 0; + } + + size_t pos = 0; + + // Read image parameters + OPJ_UINT32 width = (ReadByte(buf, len, &pos) % kMaxWidth) + 1; + OPJ_UINT32 height = (ReadByte(buf, len, &pos) % kMaxHeight) + 1; + OPJ_UINT32 numcomps = (ReadByte(buf, len, &pos) % kMaxComponents) + 1; + OPJ_UINT32 prec = (ReadByte(buf, len, &pos) % 12) + 1; // 1-12 bits + OPJ_BOOL sgnd = ReadByte(buf, len, &pos) & 1; + + // Read encoding parameters + uint8_t codec_type = ReadByte(buf, len, &pos) & 1; + uint8_t irreversible = ReadByte(buf, len, &pos) & 1; + uint8_t num_resolutions = (ReadByte(buf, len, &pos) % 5) + 1; + uint8_t prog_order = ReadByte(buf, len, &pos) % 5; + + // Create image + opj_image_cmptparm_t* cmptparms = (opj_image_cmptparm_t*)calloc(numcomps, sizeof(opj_image_cmptparm_t)); + if (!cmptparms) { + return 0; + } + + for (OPJ_UINT32 i = 0; i < numcomps; i++) { + cmptparms[i].dx = 1; + cmptparms[i].dy = 1; + cmptparms[i].w = width; + cmptparms[i].h = height; + cmptparms[i].x0 = 0; + cmptparms[i].y0 = 0; + cmptparms[i].prec = prec; + cmptparms[i].sgnd = sgnd; + } + + OPJ_COLOR_SPACE color_space = (numcomps >= 3) ? OPJ_CLRSPC_SRGB : OPJ_CLRSPC_GRAY; + opj_image_t* image = opj_image_create(numcomps, cmptparms, color_space); + free(cmptparms); + + if (!image) { + return 0; + } + + image->x0 = 0; + image->y0 = 0; + image->x1 = width; + image->y1 = height; + + // Fill image data + size_t pixels = (size_t)width * height; + for (OPJ_UINT32 c = 0; c < numcomps; c++) { + if (!image->comps[c].data) { + opj_image_destroy(image); + return 0; + } + for (size_t p = 0; p < pixels; p++) { + if (pos < len) { + image->comps[c].data[p] = (OPJ_INT32)buf[pos++]; + } else { + image->comps[c].data[p] = 0; + } + } + } + + // Setup encoder + OPJ_CODEC_FORMAT format = codec_type ? OPJ_CODEC_JP2 : OPJ_CODEC_J2K; + opj_codec_t* encoder = opj_create_compress(format); + if (!encoder) { + opj_image_destroy(image); + return 0; + } + + opj_set_info_handler(encoder, InfoCallback, NULL); + opj_set_warning_handler(encoder, WarningCallback, NULL); + opj_set_error_handler(encoder, ErrorCallback, NULL); + + opj_cparameters_t cparams; + opj_set_default_encoder_parameters(&cparams); + cparams.prog_order = (OPJ_PROG_ORDER)prog_order; + cparams.numresolution = num_resolutions; + cparams.irreversible = irreversible; + cparams.tcp_numlayers = 1; + cparams.tcp_rates[0] = 0; // Lossless + cparams.cp_disto_alloc = 1; + + if (!opj_setup_encoder(encoder, &cparams, image)) { + opj_destroy_codec(encoder); + opj_image_destroy(image); + return 0; + } + + // Create output stream + opj_stream_t* outStream = opj_stream_create(1024, OPJ_FALSE); + if (!outStream) { + opj_destroy_codec(encoder); + opj_image_destroy(image); + return 0; + } + + g_encodedData.clear(); + g_encodedSize = 0; + g_encodedPos = 0; + + opj_stream_set_write_function(outStream, WriteCallback); + opj_stream_set_seek_function(outStream, WriteSeekCallback); + opj_stream_set_skip_function(outStream, WriteSkipCallback); + opj_stream_set_user_data(outStream, NULL, NULL); + + // Encode + bool encodeSuccess = false; + if (opj_start_compress(encoder, image, outStream)) { + if (opj_encode(encoder, outStream)) { + if (opj_end_compress(encoder, outStream)) { + encodeSuccess = true; + } + } + } + + opj_stream_destroy(outStream); + opj_destroy_codec(encoder); + opj_image_destroy(image); + + if (!encodeSuccess || g_encodedSize == 0) { + return 0; + } + + // Now decode the encoded data + opj_codec_t* decoder = opj_create_decompress(format); + if (!decoder) { + return 0; + } + + opj_set_info_handler(decoder, InfoCallback, NULL); + opj_set_warning_handler(decoder, WarningCallback, NULL); + opj_set_error_handler(decoder, ErrorCallback, NULL); + + opj_dparameters_t dparams; + opj_set_default_decoder_parameters(&dparams); + + if (!opj_setup_decoder(decoder, &dparams)) { + opj_destroy_codec(decoder); + return 0; + } + + opj_stream_t* inStream = opj_stream_create(1024, OPJ_TRUE); + if (!inStream) { + opj_destroy_codec(decoder); + return 0; + } + + ReadState readState; + readState.data = g_encodedData.data(); + readState.size = g_encodedSize; + readState.pos = 0; + + opj_stream_set_user_data_length(inStream, g_encodedSize); + opj_stream_set_read_function(inStream, ReadCallback); + opj_stream_set_seek_function(inStream, ReadSeekCallback); + opj_stream_set_skip_function(inStream, ReadSkipCallback); + opj_stream_set_user_data(inStream, &readState, NULL); + + opj_image_t* decodedImage = NULL; + if (opj_read_header(inStream, decoder, &decodedImage)) { + opj_decode(decoder, inStream, decodedImage); + opj_end_decompress(decoder, inStream); + } + + opj_stream_destroy(inStream); + opj_destroy_codec(decoder); + opj_image_destroy(decodedImage); + + return 0; +} diff --git a/tests/fuzzers/opj_roundtrip_fuzzer.options b/tests/fuzzers/opj_roundtrip_fuzzer.options new file mode 100644 index 000000000..e96aa6e0f --- /dev/null +++ b/tests/fuzzers/opj_roundtrip_fuzzer.options @@ -0,0 +1,10 @@ +[libfuzzer] +max_len = 32768 +timeout = 60 +rss_limit_mb = 2048 + +[honggfuzz] +timeout = 60 + +[afl] +timeout = 60 diff --git a/tests/fuzzers/opj_subsampled_fuzzer.cpp b/tests/fuzzers/opj_subsampled_fuzzer.cpp new file mode 100644 index 000000000..10b236f3e --- /dev/null +++ b/tests/fuzzers/opj_subsampled_fuzzer.cpp @@ -0,0 +1,187 @@ +/* + * Subsampled image and ROI encoder fuzzer + * Covers: subsampling (dx/dy != 1), ROI encoding, different color spaces + */ + +#include +#include +#include +#include + +#include "openjpeg.h" + +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv); +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len); + +static const size_t kMaxInputSize = 32 * 1024; + +static void DummyCallback(const char*, void*) {} + +static OPJ_SIZE_T WriteCallback(void*, OPJ_SIZE_T nBytes, void*) +{ + return nBytes; +} + +static OPJ_BOOL SeekCallback(OPJ_OFF_T, void*) { return OPJ_TRUE; } +static OPJ_OFF_T SkipCallback(OPJ_OFF_T nBytes, void*) { return nBytes; } + +static uint8_t ReadByte(const uint8_t* buf, size_t len, size_t* pos) +{ + if (*pos >= len) return 0; + return buf[(*pos)++]; +} + +int LLVMFuzzerInitialize(int*, char***) { return 0; } + +int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) +{ + if (len < 30 || len > kMaxInputSize) return 0; + + size_t pos = 0; + + // Image parameters + OPJ_UINT32 width = (ReadByte(buf, len, &pos) % 48) + 16; + OPJ_UINT32 height = (ReadByte(buf, len, &pos) % 48) + 16; + OPJ_UINT32 numComps = (ReadByte(buf, len, &pos) % 4) + 1; + OPJ_UINT32 prec = (ReadByte(buf, len, &pos) % 12) + 1; + OPJ_BOOL sgnd = ReadByte(buf, len, &pos) & 1; + + // Subsampling factors + uint8_t subsamp_type = ReadByte(buf, len, &pos) % 4; + + // ROI parameters + uint8_t use_roi = ReadByte(buf, len, &pos) & 1; + uint8_t roi_comp = ReadByte(buf, len, &pos); + uint8_t roi_shift = ReadByte(buf, len, &pos); + + // Color space + uint8_t color_space_idx = ReadByte(buf, len, &pos) % 5; + + // Other encoder parameters + uint8_t codec_type = ReadByte(buf, len, &pos) & 1; + uint8_t irreversible = ReadByte(buf, len, &pos) & 1; + uint8_t num_resolutions = (ReadByte(buf, len, &pos) % 4) + 1; + + // Create image with subsampling + opj_image_cmptparm_t* cmptparms = (opj_image_cmptparm_t*)calloc(numComps, sizeof(opj_image_cmptparm_t)); + if (!cmptparms) return 0; + + for (OPJ_UINT32 i = 0; i < numComps; i++) { + // Apply different subsampling patterns + switch (subsamp_type) { + case 0: // 4:4:4 (no subsampling) + cmptparms[i].dx = 1; + cmptparms[i].dy = 1; + break; + case 1: // 4:2:2 (horizontal subsampling for chroma) + cmptparms[i].dx = (i > 0) ? 2 : 1; + cmptparms[i].dy = 1; + break; + case 2: // 4:2:0 (both horizontal and vertical for chroma) + cmptparms[i].dx = (i > 0) ? 2 : 1; + cmptparms[i].dy = (i > 0) ? 2 : 1; + break; + case 3: // 4:1:1 + cmptparms[i].dx = (i > 0) ? 4 : 1; + cmptparms[i].dy = 1; + break; + } + + // Calculate actual component dimensions + cmptparms[i].w = (width + cmptparms[i].dx - 1) / cmptparms[i].dx; + cmptparms[i].h = (height + cmptparms[i].dy - 1) / cmptparms[i].dy; + cmptparms[i].prec = prec; + cmptparms[i].sgnd = sgnd; + } + + // Select color space + OPJ_COLOR_SPACE colorSpace; + switch (color_space_idx) { + case 0: colorSpace = OPJ_CLRSPC_UNKNOWN; break; + case 1: colorSpace = OPJ_CLRSPC_SRGB; break; + case 2: colorSpace = OPJ_CLRSPC_GRAY; break; + case 3: colorSpace = OPJ_CLRSPC_SYCC; break; + case 4: colorSpace = OPJ_CLRSPC_EYCC; break; + default: colorSpace = OPJ_CLRSPC_UNSPECIFIED; break; + } + + opj_image_t* image = opj_image_create(numComps, cmptparms, colorSpace); + free(cmptparms); + if (!image) return 0; + + image->x0 = 0; + image->y0 = 0; + image->x1 = width; + image->y1 = height; + + // Fill image data + for (OPJ_UINT32 c = 0; c < numComps; c++) { + if (!image->comps[c].data) { + opj_image_destroy(image); + return 0; + } + size_t compPixels = (size_t)image->comps[c].w * image->comps[c].h; + for (size_t p = 0; p < compPixels; p++) { + image->comps[c].data[p] = (pos < len) ? buf[pos++] : 0; + } + } + + // Create encoder + OPJ_CODEC_FORMAT format = codec_type ? OPJ_CODEC_JP2 : OPJ_CODEC_J2K; + opj_codec_t* codec = opj_create_compress(format); + if (!codec) { + opj_image_destroy(image); + return 0; + } + + opj_set_info_handler(codec, DummyCallback, NULL); + opj_set_warning_handler(codec, DummyCallback, NULL); + opj_set_error_handler(codec, DummyCallback, NULL); + + // Setup encoding parameters + opj_cparameters_t params; + opj_set_default_encoder_parameters(¶ms); + + params.numresolution = num_resolutions; + params.irreversible = irreversible; + params.tcp_numlayers = 1; + params.tcp_rates[0] = 0; + params.cp_disto_alloc = 1; + + // Setup ROI if requested + if (use_roi && numComps > 0) { + params.roi_compno = roi_comp % numComps; + params.roi_shift = roi_shift % 38; // Valid range 0-37 + } + + if (!opj_setup_encoder(codec, ¶ms, image)) { + opj_destroy_codec(codec); + opj_image_destroy(image); + return 0; + } + + // Create output stream + opj_stream_t* stream = opj_stream_create(1024, OPJ_FALSE); + if (!stream) { + opj_destroy_codec(codec); + opj_image_destroy(image); + return 0; + } + + opj_stream_set_write_function(stream, WriteCallback); + opj_stream_set_seek_function(stream, SeekCallback); + opj_stream_set_skip_function(stream, SkipCallback); + opj_stream_set_user_data(stream, NULL, NULL); + + // Encode + if (opj_start_compress(codec, image, stream)) { + opj_encode(codec, stream); + opj_end_compress(codec, stream); + } + + opj_stream_destroy(stream); + opj_destroy_codec(codec); + opj_image_destroy(image); + + return 0; +} diff --git a/tests/fuzzers/opj_tile_fuzzer.cpp b/tests/fuzzers/opj_tile_fuzzer.cpp new file mode 100644 index 000000000..80467722f --- /dev/null +++ b/tests/fuzzers/opj_tile_fuzzer.cpp @@ -0,0 +1,265 @@ +/* + * The copyright in this software is being made available under the 2-clauses + * BSD License, included below. This software may be subject to other third + * party and contributor rights, including patent rights, and no such rights + * are granted under this license. + * + * Copyright (c) 2024, OpenJPEG contributors + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * 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 OWNER 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 exercises the tile-based decoding API: + * - opj_get_decoded_tile() + * - opj_read_tile_header() + * - opj_decode_tile_data() + * + * These functions have 0% coverage in the existing fuzzers. + */ + +#include +#include +#include +#include + +#include "openjpeg.h" + +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv); +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len); + +typedef struct { + const uint8_t* pabyData; + size_t nCurPos; + size_t nLength; +} MemFile; + +static void ErrorCallback(const char* msg, void* client_data) +{ + (void)msg; + (void)client_data; +} + +static void WarningCallback(const char* msg, void* client_data) +{ + (void)msg; + (void)client_data; +} + +static void InfoCallback(const char* msg, void* client_data) +{ + (void)msg; + (void)client_data; +} + +static OPJ_SIZE_T ReadCallback(void* pBuffer, OPJ_SIZE_T nBytes, void* pUserData) +{ + MemFile* memFile = (MemFile*)pUserData; + if (memFile->nCurPos >= memFile->nLength) { + return (OPJ_SIZE_T)-1; + } + if (memFile->nCurPos + nBytes >= memFile->nLength) { + size_t nToRead = memFile->nLength - memFile->nCurPos; + memcpy(pBuffer, memFile->pabyData + memFile->nCurPos, nToRead); + memFile->nCurPos = memFile->nLength; + return nToRead; + } + if (nBytes == 0) { + return (OPJ_SIZE_T)-1; + } + memcpy(pBuffer, memFile->pabyData + memFile->nCurPos, nBytes); + memFile->nCurPos += nBytes; + return nBytes; +} + +static OPJ_BOOL SeekCallback(OPJ_OFF_T nBytes, void* pUserData) +{ + MemFile* memFile = (MemFile*)pUserData; + memFile->nCurPos = (size_t)nBytes; + return OPJ_TRUE; +} + +static OPJ_OFF_T SkipCallback(OPJ_OFF_T nBytes, void* pUserData) +{ + MemFile* memFile = (MemFile*)pUserData; + memFile->nCurPos += (size_t)nBytes; + return nBytes; +} + +static const unsigned char jpc_header[] = {0xff, 0x4f}; +static const unsigned char jp2_box_jp[] = {0x6a, 0x50, 0x20, 0x20}; + +int LLVMFuzzerInitialize(int* argc, char*** argv) +{ + (void)argc; + (void)argv; + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) +{ + if (len < 8) { + return 0; + } + + // Determine codec format + OPJ_CODEC_FORMAT eCodecFormat; + if (len >= sizeof(jpc_header) && + memcmp(buf, jpc_header, sizeof(jpc_header)) == 0) { + eCodecFormat = OPJ_CODEC_J2K; + } else if (len >= 12 && + memcmp(buf + 4, jp2_box_jp, sizeof(jp2_box_jp)) == 0) { + eCodecFormat = OPJ_CODEC_JP2; + } else { + return 0; + } + + opj_codec_t* pCodec = opj_create_decompress(eCodecFormat); + if (!pCodec) { + return 0; + } + + opj_set_info_handler(pCodec, InfoCallback, NULL); + opj_set_warning_handler(pCodec, WarningCallback, NULL); + opj_set_error_handler(pCodec, ErrorCallback, NULL); + + opj_dparameters_t parameters; + opj_set_default_decoder_parameters(¶meters); + + if (!opj_setup_decoder(pCodec, ¶meters)) { + opj_destroy_codec(pCodec); + return 0; + } + + opj_stream_t* pStream = opj_stream_create(1024, OPJ_TRUE); + if (!pStream) { + opj_destroy_codec(pCodec); + return 0; + } + + MemFile memFile; + memFile.pabyData = buf; + memFile.nLength = len; + memFile.nCurPos = 0; + + opj_stream_set_user_data_length(pStream, len); + opj_stream_set_read_function(pStream, ReadCallback); + opj_stream_set_seek_function(pStream, SeekCallback); + opj_stream_set_skip_function(pStream, SkipCallback); + opj_stream_set_user_data(pStream, &memFile, NULL); + + opj_image_t* psImage = NULL; + if (!opj_read_header(pStream, pCodec, &psImage)) { + opj_destroy_codec(pCodec); + opj_stream_destroy(pStream); + opj_image_destroy(psImage); + return 0; + } + + // Get codestream info to determine number of tiles + opj_codestream_info_v2_t* pCodeStreamInfo = opj_get_cstr_info(pCodec); + if (!pCodeStreamInfo) { + opj_end_decompress(pCodec, pStream); + opj_stream_destroy(pStream); + opj_destroy_codec(pCodec); + opj_image_destroy(psImage); + return 0; + } + + OPJ_UINT32 numTilesX = pCodeStreamInfo->tw; + OPJ_UINT32 numTilesY = pCodeStreamInfo->th; + OPJ_UINT32 numTiles = numTilesX * numTilesY; + + opj_destroy_cstr_info(&pCodeStreamInfo); + + // Limit number of tiles to process + if (numTiles > 16) { + numTiles = 16; + } + + // Use last byte of input to select decoding method + uint8_t decodeMethod = (len > 0) ? buf[len - 1] : 0; + + if (decodeMethod & 0x01) { + // Method 1: Use opj_get_decoded_tile() to decode individual tiles + for (OPJ_UINT32 tileIndex = 0; tileIndex < numTiles; tileIndex++) { + // Reset stream position for each tile + memFile.nCurPos = 0; + + if (!opj_get_decoded_tile(pCodec, pStream, psImage, tileIndex)) { + // Tile decode failed, which is OK for malformed input + break; + } + } + } else { + // Method 2: Use opj_read_tile_header() + opj_decode_tile_data() + OPJ_UINT32 tileIndex = 0; + OPJ_UINT32 dataSize = 0; + OPJ_INT32 tileX0, tileY0, tileX1, tileY1; + OPJ_UINT32 nbComps; + OPJ_BOOL shouldGoOn = OPJ_TRUE; + + while (shouldGoOn) { + if (!opj_read_tile_header(pCodec, pStream, &tileIndex, &dataSize, + &tileX0, &tileY0, &tileX1, &tileY1, + &nbComps, &shouldGoOn)) { + break; + } + + if (!shouldGoOn) { + break; + } + + // Limit data size to prevent OOM + if (dataSize > 16 * 1024 * 1024) { + break; + } + + if (dataSize > 0) { + OPJ_BYTE* tileData = (OPJ_BYTE*)malloc(dataSize); + if (!tileData) { + break; + } + + if (!opj_decode_tile_data(pCodec, tileIndex, tileData, dataSize, pStream)) { + free(tileData); + break; + } + + free(tileData); + } + + // Limit iterations + if (tileIndex >= numTiles) { + break; + } + } + } + + opj_end_decompress(pCodec, pStream); + opj_stream_destroy(pStream); + opj_destroy_codec(pCodec); + opj_image_destroy(psImage); + + return 0; +} diff --git a/tests/fuzzers/opj_tile_fuzzer.options b/tests/fuzzers/opj_tile_fuzzer.options new file mode 100644 index 000000000..5a0374b31 --- /dev/null +++ b/tests/fuzzers/opj_tile_fuzzer.options @@ -0,0 +1,10 @@ +[libfuzzer] +max_len = 262144 +timeout = 30 +rss_limit_mb = 2048 + +[honggfuzz] +timeout = 30 + +[afl] +timeout = 30 diff --git a/tests/fuzzers/opj_tiled_encoder_fuzzer.cpp b/tests/fuzzers/opj_tiled_encoder_fuzzer.cpp new file mode 100644 index 000000000..14ce9155a --- /dev/null +++ b/tests/fuzzers/opj_tiled_encoder_fuzzer.cpp @@ -0,0 +1,178 @@ +/* + * Tile-based encoder fuzzer - uses opj_write_tile() API + * This covers: opj_j2k_write_tile, opj_jp2_write_tile, opj_j2k_post_write_tile + */ + +#include +#include +#include +#include +#include + +#include "openjpeg.h" + +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv); +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len); + +static const size_t kMaxInputSize = 32 * 1024; + +static void DummyCallback(const char*, void*) {} + +static OPJ_SIZE_T WriteCallback(void*, OPJ_SIZE_T nBytes, void* pUserData) +{ + size_t* written = (size_t*)pUserData; + *written += nBytes; + return nBytes; +} + +static OPJ_BOOL SeekCallback(OPJ_OFF_T, void*) { return OPJ_TRUE; } +static OPJ_OFF_T SkipCallback(OPJ_OFF_T nBytes, void*) { return nBytes; } + +static uint8_t ReadByte(const uint8_t* buf, size_t len, size_t* pos) +{ + if (*pos >= len) return 0; + return buf[(*pos)++]; +} + +int LLVMFuzzerInitialize(int*, char***) { return 0; } + +int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) +{ + if (len < 20 || len > kMaxInputSize) return 0; + + size_t pos = 0; + + // Image parameters + OPJ_UINT32 imgWidth = (ReadByte(buf, len, &pos) % 64) + 16; // 16-79 + OPJ_UINT32 imgHeight = (ReadByte(buf, len, &pos) % 64) + 16; // 16-79 + OPJ_UINT32 numComps = (ReadByte(buf, len, &pos) % 3) + 1; // 1-3 + OPJ_UINT32 prec = (ReadByte(buf, len, &pos) % 8) + 1; // 1-8 bits + OPJ_BOOL sgnd = ReadByte(buf, len, &pos) & 1; + + // Tile parameters + OPJ_UINT32 tileWidth = (ReadByte(buf, len, &pos) % 32) + 8; // 8-39 + OPJ_UINT32 tileHeight = (ReadByte(buf, len, &pos) % 32) + 8; // 8-39 + uint8_t codec_type = ReadByte(buf, len, &pos) & 1; + uint8_t irreversible = ReadByte(buf, len, &pos) & 1; + uint8_t num_resolutions = (ReadByte(buf, len, &pos) % 4) + 1; + + // Create tile-based image using opj_image_tile_create + opj_image_cmptparm_t* cmptparms = (opj_image_cmptparm_t*)calloc(numComps, sizeof(opj_image_cmptparm_t)); + if (!cmptparms) return 0; + + for (OPJ_UINT32 i = 0; i < numComps; i++) { + cmptparms[i].dx = 1; + cmptparms[i].dy = 1; + cmptparms[i].w = imgWidth; + cmptparms[i].h = imgHeight; + cmptparms[i].prec = prec; + cmptparms[i].sgnd = sgnd; + } + + OPJ_COLOR_SPACE colorSpace = (numComps >= 3) ? OPJ_CLRSPC_SRGB : OPJ_CLRSPC_GRAY; + opj_image_t* image = opj_image_tile_create(numComps, cmptparms, colorSpace); + free(cmptparms); + if (!image) return 0; + + image->x0 = 0; + image->y0 = 0; + image->x1 = imgWidth; + image->y1 = imgHeight; + + // Create encoder + OPJ_CODEC_FORMAT format = codec_type ? OPJ_CODEC_JP2 : OPJ_CODEC_J2K; + opj_codec_t* codec = opj_create_compress(format); + if (!codec) { + opj_image_destroy(image); + return 0; + } + + opj_set_info_handler(codec, DummyCallback, NULL); + opj_set_warning_handler(codec, DummyCallback, NULL); + opj_set_error_handler(codec, DummyCallback, NULL); + + // Setup encoding parameters with tiles + opj_cparameters_t params; + opj_set_default_encoder_parameters(¶ms); + params.tile_size_on = OPJ_TRUE; + params.cp_tx0 = 0; + params.cp_ty0 = 0; + params.cp_tdx = tileWidth; + params.cp_tdy = tileHeight; + params.numresolution = num_resolutions; + params.irreversible = irreversible; + params.tcp_numlayers = 1; + params.tcp_rates[0] = 0; + params.cp_disto_alloc = 1; + + if (!opj_setup_encoder(codec, ¶ms, image)) { + opj_destroy_codec(codec); + opj_image_destroy(image); + return 0; + } + + // Create output stream + opj_stream_t* stream = opj_stream_create(1024, OPJ_FALSE); + if (!stream) { + opj_destroy_codec(codec); + opj_image_destroy(image); + return 0; + } + + size_t bytesWritten = 0; + opj_stream_set_write_function(stream, WriteCallback); + opj_stream_set_seek_function(stream, SeekCallback); + opj_stream_set_skip_function(stream, SkipCallback); + opj_stream_set_user_data(stream, &bytesWritten, NULL); + + if (!opj_start_compress(codec, image, stream)) { + opj_stream_destroy(stream); + opj_destroy_codec(codec); + opj_image_destroy(image); + return 0; + } + + // Calculate number of tiles + OPJ_UINT32 numTilesX = (imgWidth + tileWidth - 1) / tileWidth; + OPJ_UINT32 numTilesY = (imgHeight + tileHeight - 1) / tileHeight; + OPJ_UINT32 numTiles = numTilesX * numTilesY; + + // Limit tiles to prevent excessive processing + if (numTiles > 16) numTiles = 16; + + // Write each tile using opj_write_tile + for (OPJ_UINT32 tileIdx = 0; tileIdx < numTiles; tileIdx++) { + // Calculate tile dimensions + OPJ_UINT32 tileX = tileIdx % numTilesX; + OPJ_UINT32 tileY = tileIdx / numTilesX; + OPJ_UINT32 tileX0 = tileX * tileWidth; + OPJ_UINT32 tileY0 = tileY * tileHeight; + OPJ_UINT32 tileX1 = (tileX0 + tileWidth > imgWidth) ? imgWidth : tileX0 + tileWidth; + OPJ_UINT32 tileY1 = (tileY0 + tileHeight > imgHeight) ? imgHeight : tileY0 + tileHeight; + OPJ_UINT32 tileSizeX = tileX1 - tileX0; + OPJ_UINT32 tileSizeY = tileY1 - tileY0; + + // Calculate data size for this tile + size_t bytesPerSample = (prec <= 8) ? 1 : ((prec <= 16) ? 2 : 4); + size_t tileDataSize = tileSizeX * tileSizeY * numComps * bytesPerSample; + + // Allocate and fill tile data from fuzz input + OPJ_BYTE* tileData = (OPJ_BYTE*)malloc(tileDataSize); + if (!tileData) break; + + for (size_t i = 0; i < tileDataSize; i++) { + tileData[i] = (pos < len) ? buf[pos++] : 0; + } + + // Write tile + opj_write_tile(codec, tileIdx, tileData, (OPJ_UINT32)tileDataSize, stream); + free(tileData); + } + + opj_end_compress(codec, stream); + opj_stream_destroy(stream); + opj_destroy_codec(codec); + opj_image_destroy(image); + + return 0; +} diff --git a/tests/fuzzers/opj_tiled_encoder_fuzzer.options b/tests/fuzzers/opj_tiled_encoder_fuzzer.options new file mode 100644 index 000000000..e58cb9eb2 --- /dev/null +++ b/tests/fuzzers/opj_tiled_encoder_fuzzer.options @@ -0,0 +1,9 @@ +[libfuzzer] +max_len = 32768 +timeout = 60 + +[honggfuzz] +timeout = 60 + +[afl] +timeout = 60