From 21c1c2ca1d07a92812990367e5606cf424426da0 Mon Sep 17 00:00:00 2001 From: Yannis Guyon Date: Tue, 16 Jan 2024 09:31:20 +0000 Subject: [PATCH] Add imageSizeLimit arg to avifutil avifReadImage() (#1941) Use AVIF_DEFAULT_IMAGE_SIZE_LIMIT in places where the image is encoded as AVIF afterwards anyway. Use 2^32-1 in other places for a reasonable threshold. Avoids out-of-memory issues in fuzztest targets. No entry in CHANGES.md as it should not impact the set of valid workflows in the public API or tools, besides the returned error code or message. BUG=oss-fuzz:65700 --- apps/avifenc.c | 3 +- apps/avifgainmaputil/convert_command.cc | 2 +- apps/avifgainmaputil/imageio.cc | 17 +++---- apps/shared/avifjpeg.c | 66 ++++++++++++++++++------- apps/shared/avifjpeg.h | 4 +- apps/shared/avifpng.c | 5 ++ apps/shared/avifpng.h | 1 + apps/shared/avifutil.c | 17 +++++-- apps/shared/avifutil.h | 2 + apps/shared/y4m.c | 10 +++- apps/shared/y4m.h | 6 ++- tests/gtest/are_images_equal.cc | 7 ++- tests/gtest/avif_fuzztest_read_image.cc | 5 +- tests/gtest/aviflosslesstest.cc | 2 +- tests/gtest/avifpng16bittest.cc | 2 +- tests/gtest/avifreadimagetest.cc | 64 ++++++++++++++++++------ tests/gtest/aviftest_helpers.cc | 2 +- tests/gtest/avify4mtest.cc | 10 ++-- 18 files changed, 164 insertions(+), 61 deletions(-) diff --git a/apps/avifenc.c b/apps/avifenc.c index cf31b4f6f6..980711d48c 100644 --- a/apps/avifenc.c +++ b/apps/avifenc.c @@ -582,7 +582,7 @@ static avifBool avifInputReadImage(avifInput * input, if (feof(stdin)) { return AVIF_FALSE; } - if (!y4mRead(NULL, dstImage, dstSourceTiming, &input->frameIter)) { + if (!y4mRead(NULL, AVIF_DEFAULT_IMAGE_SIZE_LIMIT, dstImage, dstSourceTiming, &input->frameIter)) { fprintf(stderr, "ERROR: Cannot read y4m through standard input"); return AVIF_FALSE; } @@ -611,6 +611,7 @@ static avifBool avifInputReadImage(avifInput * input, ignoreXMP, allowChangingCicp, ignoreGainMap, + AVIF_DEFAULT_IMAGE_SIZE_LIMIT, dstImage, dstDepth, dstSourceTiming, diff --git a/apps/avifgainmaputil/convert_command.cc b/apps/avifgainmaputil/convert_command.cc index 53c792a5bb..ecb617de04 100644 --- a/apps/avifgainmaputil/convert_command.cc +++ b/apps/avifgainmaputil/convert_command.cc @@ -45,7 +45,7 @@ avifResult ConvertCommand::Run() { /*ignoreExif=*/false, /*ignoreXMP=*/false, /*allowChangingCicp=*/true, - /*ignoreGainMap=*/false, image.get(), + /*ignoreGainMap=*/false, AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image.get(), /*outDepth=*/nullptr, /*sourceTiming=*/nullptr, /*frameIter=*/nullptr); diff --git a/apps/avifgainmaputil/imageio.cc b/apps/avifgainmaputil/imageio.cc index 001f0cb2b1..0a02712824 100644 --- a/apps/avifgainmaputil/imageio.cc +++ b/apps/avifgainmaputil/imageio.cc @@ -119,16 +119,13 @@ avifResult ReadImage(avifImage* image, const std::string& input_filename, } } } else { - const avifAppFileFormat file_format = - avifReadImage(input_filename.c_str(), requested_format, requested_depth, - AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC, ignore_profile, - /*ignoreExif=*/false, - /*ignoreXMP=*/false, - /*allowChangingCicp=*/true, - /*ignoreGainMap=*/true, image, - /*outDepth=*/nullptr, - /*sourceTiming=*/nullptr, - /*frameIter=*/nullptr); + const avifAppFileFormat file_format = avifReadImage( + input_filename.c_str(), requested_format, + static_cast(requested_depth), AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC, + ignore_profile, /*ignoreExif=*/false, /*ignoreXMP=*/false, + /*allowChangingCicp=*/true, /*ignoreGainMap=*/true, + AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image, /*outDepth=*/nullptr, + /*sourceTiming=*/nullptr, /*frameIter=*/nullptr); if (file_format == AVIF_APP_FILE_FORMAT_UNKNOWN) { std::cout << "Failed to decode image: " << input_filename; return AVIF_RESULT_INVALID_ARGUMENT; diff --git a/apps/shared/avifjpeg.c b/apps/shared/avifjpeg.c index 03713f5c03..62aed2462a 100644 --- a/apps/shared/avifjpeg.c +++ b/apps/shared/avifjpeg.c @@ -48,13 +48,16 @@ static void my_error_exit(j_common_ptr cinfo) // An internal function used by avifJPEGReadCopy(), this is the shared libjpeg decompression code // for all paths avifJPEGReadCopy() takes. -static avifBool avifJPEGCopyPixels(avifImage * avif, struct jpeg_decompress_struct * cinfo) +static avifBool avifJPEGCopyPixels(avifImage * avif, uint32_t imageSizeLimit, struct jpeg_decompress_struct * cinfo) { cinfo->raw_data_out = TRUE; jpeg_start_decompress(cinfo); avif->width = cinfo->image_width; avif->height = cinfo->image_height; + if ((uint32_t)avif->width > imageSizeLimit / (uint32_t)avif->height) { + return AVIF_FALSE; + } JSAMPIMAGE buffer = (*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_IMAGE, sizeof(JSAMPARRAY) * cinfo->num_components); @@ -129,7 +132,7 @@ static avifBool avifJPEGHasCompatibleMatrixCoefficients(avifMatrixCoefficients m // This attempts to copy the internal representation of the JPEG directly into avifImage without // YUV->RGB conversion. If it returns AVIF_FALSE, a typical RGB->YUV conversion is required. -static avifBool avifJPEGReadCopy(avifImage * avif, struct jpeg_decompress_struct * cinfo) +static avifBool avifJPEGReadCopy(avifImage * avif, uint32_t imageSizeLimit, struct jpeg_decompress_struct * cinfo) { if ((avif->depth != 8) || (avif->yuvRange != AVIF_RANGE_FULL)) { return AVIF_FALSE; @@ -160,7 +163,7 @@ static avifBool avifJPEGReadCopy(avifImage * avif, struct jpeg_decompress_struct } if (avif->yuvFormat == jpegFormat) { cinfo->out_color_space = JCS_YCbCr; - return avifJPEGCopyPixels(avif, cinfo); + return avifJPEGCopyPixels(avif, imageSizeLimit, cinfo); } } @@ -168,7 +171,7 @@ static avifBool avifJPEGReadCopy(avifImage * avif, struct jpeg_decompress_struct if ((avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) && (cinfo->comp_info[0].h_samp_factor == cinfo->max_h_samp_factor && cinfo->comp_info[0].v_samp_factor == cinfo->max_v_samp_factor)) { cinfo->out_color_space = JCS_YCbCr; - return avifJPEGCopyPixels(avif, cinfo); + return avifJPEGCopyPixels(avif, imageSizeLimit, cinfo); } } } else if (cinfo->jpeg_color_space == JCS_GRAYSCALE) { @@ -181,14 +184,14 @@ static avifBool avifJPEGReadCopy(avifImage * avif, struct jpeg_decompress_struct if ((avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) || (avif->yuvFormat == AVIF_PIXEL_FORMAT_NONE)) { avif->yuvFormat = AVIF_PIXEL_FORMAT_YUV400; cinfo->out_color_space = JCS_GRAYSCALE; - return avifJPEGCopyPixels(avif, cinfo); + return avifJPEGCopyPixels(avif, imageSizeLimit, cinfo); } // Grayscale->YUV: copy Y, fill UV with monochrome value. if ((avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) || (avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV422) || (avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV420)) { cinfo->out_color_space = JCS_GRAYSCALE; - if (!avifJPEGCopyPixels(avif, cinfo)) { + if (!avifJPEGCopyPixels(avif, imageSizeLimit, cinfo)) { return AVIF_FALSE; } @@ -205,7 +208,7 @@ static avifBool avifJPEGReadCopy(avifImage * avif, struct jpeg_decompress_struct ((avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) || (avif->yuvFormat == AVIF_PIXEL_FORMAT_NONE))) { avif->yuvFormat = AVIF_PIXEL_FORMAT_YUV444; cinfo->out_color_space = JCS_GRAYSCALE; - if (!avifJPEGCopyPixels(avif, cinfo)) { + if (!avifJPEGCopyPixels(avif, imageSizeLimit, cinfo)) { return AVIF_FALSE; } @@ -224,7 +227,7 @@ static avifBool avifJPEGReadCopy(avifImage * avif, struct jpeg_decompress_struct cinfo->comp_info[2].h_samp_factor == 1 && cinfo->comp_info[2].v_samp_factor == 1)) { avif->yuvFormat = AVIF_PIXEL_FORMAT_YUV444; cinfo->out_color_space = JCS_RGB; - return avifJPEGCopyPixels(avif, cinfo); + return avifJPEGCopyPixels(avif, imageSizeLimit, cinfo); } } @@ -339,7 +342,8 @@ static avifBool avifJPEGReadInternal(FILE * f, avifBool ignoreColorProfile, avifBool ignoreExif, avifBool ignoreXMP, - avifBool ignoreGainMap); + avifBool ignoreGainMap, + uint32_t imageSizeLimit); // Arbitrary max number of jpeg segments to parse before giving up. #define MAX_JPEG_SEGMENTS 100 @@ -669,7 +673,11 @@ avifBool avifJPEGParseGainMapXMP(const uint8_t * xmpData, size_t xmpSize, avifGa // See CIPA DC-007-Translation-2021 Multi-Picture Format at https://www.cipa.jp/e/std/std-sec.html // and https://helpx.adobe.com/camera-raw/using/gain-map.html in particular Figures 1 to 6. // Returns AVIF_FALSE if no gain map was found. -static avifBool avifJPEGExtractGainMapImageFromMpf(FILE * f, const avifROData * segmentData, avifImage * avif, avifChromaDownsampling chromaDownsampling) +static avifBool avifJPEGExtractGainMapImageFromMpf(FILE * f, + uint32_t imageSizeLimit, + const avifROData * segmentData, + avifImage * avif, + avifChromaDownsampling chromaDownsampling) { uint32_t offset = 0; @@ -770,7 +778,8 @@ static avifBool avifJPEGExtractGainMapImageFromMpf(FILE * f, const avifROData * /*ignoreColorProfile=*/AVIF_TRUE, /*ignoreExif=*/AVIF_TRUE, /*ignoreXMP=*/AVIF_FALSE, - /*ignoreGainMap=*/AVIF_TRUE)) { + /*ignoreGainMap=*/AVIF_TRUE, + imageSizeLimit)) { continue; } if (avifJPEGHasGainMapXMPNode(avif->xmp.data, avif->xmp.size)) { @@ -787,7 +796,11 @@ static avifBool avifJPEGExtractGainMapImageFromMpf(FILE * f, const avifROData * // See CIPA DC-007-Translation-2021 Multi-Picture Format at https://www.cipa.jp/e/std/std-sec.html // and https://helpx.adobe.com/camera-raw/using/gain-map.html // Returns AVIF_TRUE if a gain map was found. -static avifBool avifJPEGExtractGainMapImage(FILE * f, struct jpeg_decompress_struct * cinfo, avifGainMap * gainMap, avifChromaDownsampling chromaDownsampling) +static avifBool avifJPEGExtractGainMapImage(FILE * f, + uint32_t imageSizeLimit, + struct jpeg_decompress_struct * cinfo, + avifGainMap * gainMap, + avifChromaDownsampling chromaDownsampling) { const avifROData tagMpf = { (const uint8_t *)AVIF_JPEG_MPF_HEADER, AVIF_JPEG_MPF_HEADER_LENGTH }; for (jpeg_saved_marker_ptr marker = cinfo->marker_list; marker != NULL; marker = marker->next) { @@ -802,7 +815,7 @@ static avifBool avifJPEGExtractGainMapImage(FILE * f, struct jpeg_decompress_str assert(avifJPEGHasCompatibleMatrixCoefficients(image->matrixCoefficients)); const avifROData mpfData = { (const uint8_t *)marker->data + tagMpf.size, marker->data_length - tagMpf.size }; - if (!avifJPEGExtractGainMapImageFromMpf(f, &mpfData, image, chromaDownsampling)) { + if (!avifJPEGExtractGainMapImageFromMpf(f, imageSizeLimit, &mpfData, image, chromaDownsampling)) { fprintf(stderr, "Note: XMP metadata indicated the presence of a gain map, but it could not be found or decoded\n"); avifImageDestroy(image); return AVIF_FALSE; @@ -844,7 +857,8 @@ static avifBool avifJPEGReadInternal(FILE * f, avifBool ignoreColorProfile, avifBool ignoreExif, avifBool ignoreXMP, - avifBool ignoreGainMap) + avifBool ignoreGainMap, + uint32_t imageSizeLimit) { volatile avifBool ret = AVIF_FALSE; uint8_t * volatile iccData = NULL; @@ -900,7 +914,7 @@ static avifBool avifJPEGReadInternal(FILE * f, // JPEG doesn't have alpha. Prevent confusion. avif->alphaPremultiplied = AVIF_FALSE; - if (avifJPEGReadCopy(avif, &cinfo)) { + if (avifJPEGReadCopy(avif, imageSizeLimit, &cinfo)) { // JPEG pixels were successfully copied without conversion. Notify the enduser. assert(inputFilename); // JPEG read doesn't support stdin @@ -917,6 +931,10 @@ static avifBool avifJPEGReadInternal(FILE * f, avif->width = cinfo.output_width; avif->height = cinfo.output_height; + if ((uint32_t)avif->width > imageSizeLimit / (uint32_t)avif->height) { + fprintf(stderr, "Too big JPEG dimensions (%d x %d > %u px): %s\n", avif->width, avif->height, imageSizeLimit, inputFilename); + goto cleanup; + } #if defined(AVIF_ENABLE_EXPERIMENTAL_YCGCO_R) const avifBool useYCgCoR = (avif->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_YCGCO_RE || avif->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_YCGCO_RO); @@ -1141,7 +1159,7 @@ static avifBool avifJPEGReadInternal(FILE * f, goto cleanup; } // Ignore the return value: continue even if we fail to find/parse/decode the gain map. - if (avifJPEGExtractGainMapImage(f, &cinfo, gainMap, chromaDownsampling)) { + if (avifJPEGExtractGainMapImage(f, imageSizeLimit, &cinfo, gainMap, chromaDownsampling)) { // Since jpeg doesn't provide this metadata, assume the values are the same as the base image // with a PQ transfer curve. gainMap->altColorPrimaries = avif->colorPrimaries; @@ -1189,15 +1207,25 @@ avifBool avifJPEGRead(const char * inputFilename, avifBool ignoreColorProfile, avifBool ignoreExif, avifBool ignoreXMP, - avifBool ignoreGainMap) + avifBool ignoreGainMap, + uint32_t imageSizeLimit) { FILE * f = fopen(inputFilename, "rb"); if (!f) { fprintf(stderr, "Can't open JPEG file for read: %s\n", inputFilename); return AVIF_FALSE; } - const avifBool res = - avifJPEGReadInternal(f, inputFilename, avif, requestedFormat, requestedDepth, chromaDownsampling, ignoreColorProfile, ignoreExif, ignoreXMP, ignoreGainMap); + const avifBool res = avifJPEGReadInternal(f, + inputFilename, + avif, + requestedFormat, + requestedDepth, + chromaDownsampling, + ignoreColorProfile, + ignoreExif, + ignoreXMP, + ignoreGainMap, + imageSizeLimit); fclose(f); return res; } diff --git a/apps/shared/avifjpeg.h b/apps/shared/avifjpeg.h index 228bdee8f1..a4cbc0e586 100644 --- a/apps/shared/avifjpeg.h +++ b/apps/shared/avifjpeg.h @@ -11,6 +11,7 @@ extern "C" { #endif // Decodes the jpeg file at path 'inputFilename' into 'avif'. +// At most imageSizeLimit pixels will be read or an error returned. // 'ignoreGainMap' is only relevant for jpeg files that have a gain map // and only if AVIF_ENABLE_EXPERIMENTAL_JPEG_GAIN_MAP_CONVERSION is ON // (requires AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP and libxml2). Otherwise @@ -23,7 +24,8 @@ avifBool avifJPEGRead(const char * inputFilename, avifBool ignoreColorProfile, avifBool ignoreExif, avifBool ignoreXMP, - avifBool ignoreGainMap); + avifBool ignoreGainMap, + uint32_t imageSizeLimit); avifBool avifJPEGWrite(const char * outputFilename, const avifImage * avif, int jpegQuality, avifChromaUpsampling chromaUpsampling); #if defined(AVIF_ENABLE_EXPERIMENTAL_JPEG_GAIN_MAP_CONVERSION) diff --git a/apps/shared/avifpng.c b/apps/shared/avifpng.c index fe85104040..09a0f0a707 100644 --- a/apps/shared/avifpng.c +++ b/apps/shared/avifpng.c @@ -233,6 +233,7 @@ avifBool avifPNGRead(const char * inputFilename, avifBool ignoreExif, avifBool ignoreXMP, avifBool allowChangingCicp, + uint32_t imageSizeLimit, uint32_t * outPNGDepth) { volatile avifBool readResult = AVIF_FALSE; @@ -453,6 +454,10 @@ avifBool avifPNGRead(const char * inputFilename, fprintf(stderr, "png_get_channels() should return 3 or 4 but returns %d.\n", numChannels); goto cleanup; } + if ((uint32_t)avif->width > imageSizeLimit / (uint32_t)avif->height) { + fprintf(stderr, "Too big PNG dimensions (%d x %d > %u px): %s\n", avif->width, avif->height, imageSizeLimit, inputFilename); + goto cleanup; + } avifRGBImageSetDefaults(&rgb, avif); rgb.chromaDownsampling = chromaDownsampling; diff --git a/apps/shared/avifpng.h b/apps/shared/avifpng.h index 4f7e1c0598..16d5a2f5be 100644 --- a/apps/shared/avifpng.h +++ b/apps/shared/avifpng.h @@ -20,6 +20,7 @@ avifBool avifPNGRead(const char * inputFilename, avifBool ignoreExif, avifBool ignoreXMP, avifBool allowChangingCicp, + uint32_t imageSizeLimit, uint32_t * outPNGDepth); avifBool avifPNGWrite(const char * outputFilename, const avifImage * avif, diff --git a/apps/shared/avifutil.c b/apps/shared/avifutil.c index 743845bcdb..20f54ea47b 100644 --- a/apps/shared/avifutil.c +++ b/apps/shared/avifutil.c @@ -306,6 +306,7 @@ avifAppFileFormat avifReadImage(const char * filename, avifBool ignoreXMP, avifBool allowChangingCicp, avifBool ignoreGainMap, + uint32_t imageSizeLimit, avifImage * image, uint32_t * outDepth, avifAppSourceTiming * sourceTiming, @@ -313,21 +314,31 @@ avifAppFileFormat avifReadImage(const char * filename, { const avifAppFileFormat format = avifGuessFileFormat(filename); if (format == AVIF_APP_FILE_FORMAT_Y4M) { - if (!y4mRead(filename, image, sourceTiming, frameIter)) { + if (!y4mRead(filename, imageSizeLimit, image, sourceTiming, frameIter)) { return AVIF_APP_FILE_FORMAT_UNKNOWN; } if (outDepth) { *outDepth = image->depth; } } else if (format == AVIF_APP_FILE_FORMAT_JPEG) { - if (!avifJPEGRead(filename, image, requestedFormat, requestedDepth, chromaDownsampling, ignoreColorProfile, ignoreExif, ignoreXMP, ignoreGainMap)) { + if (!avifJPEGRead(filename, image, requestedFormat, requestedDepth, chromaDownsampling, ignoreColorProfile, ignoreExif, ignoreXMP, ignoreGainMap, imageSizeLimit)) { return AVIF_APP_FILE_FORMAT_UNKNOWN; } if (outDepth) { *outDepth = 8; } } else if (format == AVIF_APP_FILE_FORMAT_PNG) { - if (!avifPNGRead(filename, image, requestedFormat, requestedDepth, chromaDownsampling, ignoreColorProfile, ignoreExif, ignoreXMP, allowChangingCicp, outDepth)) { + if (!avifPNGRead(filename, + image, + requestedFormat, + requestedDepth, + chromaDownsampling, + ignoreColorProfile, + ignoreExif, + ignoreXMP, + allowChangingCicp, + imageSizeLimit, + outDepth)) { return AVIF_APP_FILE_FORMAT_UNKNOWN; } } else { diff --git a/apps/shared/avifutil.h b/apps/shared/avifutil.h index f3fac95135..55edd527f2 100644 --- a/apps/shared/avifutil.h +++ b/apps/shared/avifutil.h @@ -62,6 +62,7 @@ typedef struct avifAppSourceTiming struct y4mFrameIterator; // Reads an image from a file with the requested format and depth. +// At most imageSizeLimit pixels will be read or an error returned. // In case of a y4m file, sourceTiming and frameIter can be set. // Returns AVIF_APP_FILE_FORMAT_UNKNOWN in case of error. // 'ignoreGainMap' is only relevant for jpeg files that have a gain map @@ -77,6 +78,7 @@ avifAppFileFormat avifReadImage(const char * filename, avifBool ignoreXMP, avifBool allowChangingCicp, avifBool ignoreGainMap, + uint32_t imageSizeLimit, avifImage * image, uint32_t * outDepth, avifAppSourceTiming * sourceTiming, diff --git a/apps/shared/y4m.c b/apps/shared/y4m.c index 6b41f46e38..bd9c287d3b 100644 --- a/apps/shared/y4m.c +++ b/apps/shared/y4m.c @@ -262,7 +262,11 @@ static avifBool y4mClampSamples(avifImage * avif) goto cleanup; \ } while (0) -avifBool y4mRead(const char * inputFilename, avifImage * avif, avifAppSourceTiming * sourceTiming, struct y4mFrameIterator ** iter) +avifBool y4mRead(const char * inputFilename, + uint32_t imageSizeLimit, + avifImage * avif, + avifAppSourceTiming * sourceTiming, + struct y4mFrameIterator ** iter) { avifBool result = AVIF_FALSE; @@ -400,6 +404,10 @@ avifBool y4mRead(const char * inputFilename, avifImage * avif, avifAppSourceTimi fprintf(stderr, "Failed to parse y4m header (not enough information): %s\n", frame.displayFilename); goto cleanup; } + if ((uint32_t)frame.width > imageSizeLimit / (uint32_t)frame.height) { + fprintf(stderr, "Too big y4m dimensions (%d x %d > %u px): %s\n", frame.width, frame.height, imageSizeLimit, frame.displayFilename); + goto cleanup; + } if (sourceTiming) { *sourceTiming = frame.sourceTiming; diff --git a/apps/shared/y4m.h b/apps/shared/y4m.h index 3770769b02..a4d837a774 100644 --- a/apps/shared/y4m.h +++ b/apps/shared/y4m.h @@ -18,7 +18,11 @@ extern "C" { // The structure will always be freed upon failure or reaching EOF. struct y4mFrameIterator; -avifBool y4mRead(const char * inputFilename, avifImage * avif, avifAppSourceTiming * sourceTiming, struct y4mFrameIterator ** iter); +avifBool y4mRead(const char * inputFilename, + uint32_t imageSizeLimit, + avifImage * avif, + avifAppSourceTiming * sourceTiming, + struct y4mFrameIterator ** iter); avifBool y4mWrite(const char * outputFilename, const avifImage * avif); #ifdef __cplusplus diff --git a/tests/gtest/are_images_equal.cc b/tests/gtest/are_images_equal.cc index 71ffbd1589..80aaae6cb9 100644 --- a/tests/gtest/are_images_equal.cc +++ b/tests/gtest/are_images_equal.cc @@ -4,6 +4,7 @@ #include #include +#include #include #include "aviftest_helpers.h" @@ -36,8 +37,10 @@ int main(int argc, char** argv) { /*ignoreExif=*/AVIF_FALSE, /*ignoreXMP=*/AVIF_FALSE, /*allowChangingCicp=*/AVIF_TRUE, // TODO(maryla): also compare gain maps. - /*ignoreGainMap=*/AVIF_TRUE, decoded[i].get(), &depth[i], - nullptr, nullptr) == AVIF_APP_FILE_FORMAT_UNKNOWN) { + /*ignoreGainMap=*/AVIF_TRUE, + /*imageSizeLimit=*/std::numeric_limits::max(), + decoded[i].get(), &depth[i], nullptr, + nullptr) == AVIF_APP_FILE_FORMAT_UNKNOWN) { std::cerr << "Image " << argv[i + 1] << " cannot be read." << std::endl; return 2; } diff --git a/tests/gtest/avif_fuzztest_read_image.cc b/tests/gtest/avif_fuzztest_read_image.cc index 2e920db23f..557650b58d 100644 --- a/tests/gtest/avif_fuzztest_read_image.cc +++ b/tests/gtest/avif_fuzztest_read_image.cc @@ -60,10 +60,13 @@ void ReadImageFile(const std::string& arbitrary_bytes, ImagePtr avif_image(avifImageCreateEmpty()); avif_image->matrixCoefficients = matrix_coefficients; + // OSS-Fuzz limits the allocated memory to 2560 MB. Consider 16-bit samples. + constexpr uint32_t kImageSizeLimit = + 2560u * 1024 * 1024 / AVIF_MAX_AV1_LAYER_COUNT / sizeof(uint16_t); const avifAppFileFormat file_format = avifReadImage( file_path.c_str(), requested_format, requested_depth, chroma_downsampling, ignore_color_profile, ignore_exif, ignore_xmp, allow_changing_cicp, - ignore_gain_map, avif_image.get(), &out_depth, &timing, + ignore_gain_map, kImageSizeLimit, avif_image.get(), &out_depth, &timing, /*frameIter=*/nullptr); if (file_format != AVIF_APP_FILE_FORMAT_UNKNOWN) { diff --git a/tests/gtest/aviflosslesstest.cc b/tests/gtest/aviflosslesstest.cc index 85dcf567ca..51d1c8934b 100644 --- a/tests/gtest/aviflosslesstest.cc +++ b/tests/gtest/aviflosslesstest.cc @@ -38,7 +38,7 @@ TEST(BasicTest, EncodeDecodeMatrixCoefficients) { /*chromaDownsampling=*/AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC, /*ignoreColorProfile=*/false, /*ignoreExif=*/false, /*ignoreXMP=*/false, /*allowChangingCicp=*/true, - /*ignoreGainMap=*/true, image.get(), + /*ignoreGainMap=*/true, AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image.get(), /*outDepth=*/nullptr, /*sourceTiming=*/nullptr, /*frameIter=*/nullptr); #if defined(AVIF_ENABLE_EXPERIMENTAL_YCGCO_R) diff --git a/tests/gtest/avifpng16bittest.cc b/tests/gtest/avifpng16bittest.cc index 65cdd88297..f35aa46b7e 100644 --- a/tests/gtest/avifpng16bittest.cc +++ b/tests/gtest/avifpng16bittest.cc @@ -37,7 +37,7 @@ ImagePtr ReadImageLosslessBitDepth(const std::string& path, /*ignoreColorProfile=*/true, /*ignoreExif=*/true, /*ignoreXMP=*/true, /*allowChangingCicp=*/true, /*ignoreGainMap=*/true, - image.get(), &output_depth, + AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image.get(), &output_depth, /*sourceTiming=*/nullptr, /*frameIter=*/nullptr) == AVIF_APP_FILE_FORMAT_UNKNOWN) { return nullptr; diff --git a/tests/gtest/avifreadimagetest.cc b/tests/gtest/avifreadimagetest.cc index b72d42b88e..498906c5ab 100644 --- a/tests/gtest/avifreadimagetest.cc +++ b/tests/gtest/avifreadimagetest.cc @@ -1,11 +1,11 @@ // Copyright 2022 Google LLC // SPDX-License-Identifier: BSD-2-Clause -#include -#include +#include #include "avif/avif.h" #include "aviftest_helpers.h" +#include "avifutil.h" #include "gtest/gtest.h" #include "iccmaker.h" @@ -16,6 +16,7 @@ namespace { const char* data_path = nullptr; //------------------------------------------------------------------------------ +// Generic tests bool AreSamplesEqualForAllReadSettings(const char* file_name1, const char* file_name2) { @@ -56,6 +57,9 @@ TEST(PngTest, ReadAllSubsamplingsAndAllBitDepths) { "paris_icc_exif_xmp.png", "paris_icc_exif_xmp_at_end.png")); } +//------------------------------------------------------------------------------ +// PNG color metadata handling tests + // Verify we can read a PNG file with PNG_COLOR_TYPE_PALETTE and a tRNS chunk. TEST(PngTest, PaletteColorTypeWithTrnsChunk) { const ImagePtr image = testutil::ReadImage(data_path, "draw_points.png", @@ -95,7 +99,7 @@ constexpr size_t kGrayProfileSize = 275; // Verify we can read a color PNG file tagged as gamma 2.2 through gAMA chunk, // and set transfer characteristics correctly. TEST(PngTest, ColorGamma22) { - const auto image = testutil::ReadImage(data_path, "ffffcc-gamma2.2.png"); + const ImagePtr image = testutil::ReadImage(data_path, "ffffcc-gamma2.2.png"); ASSERT_NE(image, nullptr); // gamma 2.2 should match BT470M @@ -109,7 +113,7 @@ TEST(PngTest, ColorGamma22) { // Verify that color info does not get overwritten if allow_changing_cicp is // false. TEST(PngTest, ColorGamma22ForbitChangingCicp) { - const auto image = testutil::ReadImage( + const ImagePtr image = testutil::ReadImage( data_path, "ffffcc-gamma2.2.png", /*requested_format=*/AVIF_PIXEL_FORMAT_NONE, /*requested_depth=*/0, AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC, @@ -129,7 +133,7 @@ TEST(PngTest, ColorGamma22ForbitChangingCicp) { // Verify we can read a color PNG file tagged as gamma 1.6 through gAMA chunk, // and generate a color profile for it. TEST(PngTest, ColorGamma16) { - const auto image = testutil::ReadImage(data_path, "ffffcc-gamma1.6.png"); + const ImagePtr image = testutil::ReadImage(data_path, "ffffcc-gamma1.6.png"); ASSERT_NE(image, nullptr); // if ICC profile generated, CP and TC should be set to unspecified @@ -146,8 +150,8 @@ TEST(PngTest, ColorGamma16) { // Verify we can read a gray PNG file tagged as gamma 2.2 through gAMA chunk, // and set transfer characteristics correctly. TEST(PngTest, GrayGamma22) { - const auto image = testutil::ReadImage(data_path, "ffffff-gamma2.2.png", - AVIF_PIXEL_FORMAT_YUV400); + const ImagePtr image = testutil::ReadImage(data_path, "ffffff-gamma2.2.png", + AVIF_PIXEL_FORMAT_YUV400); ASSERT_NE(image, nullptr); // gamma 2.2 should match BT470M @@ -161,8 +165,8 @@ TEST(PngTest, GrayGamma22) { // Verify we can read a gray PNG file tagged as gamma 1.6 through gAMA chunk, // and generate a gray profile for it. TEST(PngTest, GrayGamma16) { - const auto image = testutil::ReadImage(data_path, "ffffff-gamma1.6.png", - AVIF_PIXEL_FORMAT_YUV400); + const ImagePtr image = testutil::ReadImage(data_path, "ffffff-gamma1.6.png", + AVIF_PIXEL_FORMAT_YUV400); ASSERT_NE(image, nullptr); // if ICC profile generated, CP and TC should be set to unspecified @@ -179,7 +183,7 @@ TEST(PngTest, GrayGamma16) { // Verify we can read a color PNG file tagged as sRGB through sRGB chunk, // and set color primaries and transfer characteristics correctly. TEST(PngTest, SRGBTagged) { - const auto image = testutil::ReadImage(data_path, "ffffcc-srgb.png"); + const ImagePtr image = testutil::ReadImage(data_path, "ffffcc-srgb.png"); ASSERT_NE(image, nullptr); // should set to BT709 primaries and SRGB transfer @@ -192,7 +196,7 @@ TEST(PngTest, SRGBTagged) { // Verify we are not generating profile if asked to ignore it. TEST(PngTest, IgnoreProfile) { - const auto image = testutil::ReadImage( + const ImagePtr image = testutil::ReadImage( data_path, "ffffcc-gamma1.6.png", AVIF_PIXEL_FORMAT_NONE, 0, AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC, true); ASSERT_NE(image, nullptr); @@ -209,7 +213,7 @@ TEST(PngTest, IgnoreProfile) { // and BT709 primaries through cHRM chunk, // and set color primaries and transfer characteristics correctly. TEST(PngTest, BT709Gamma22) { - const auto image = + const ImagePtr image = testutil::ReadImage(data_path, "ArcTriomphe-cHRM-orig.png"); ASSERT_NE(image, nullptr); @@ -228,7 +232,7 @@ TEST(PngTest, BT709Gamma22) { // and BT709 primaries with red and green swapped through cHRM chunk, // and generate a color profile for it. TEST(PngTest, BT709SwappedGamma22) { - const auto image = + const ImagePtr image = testutil::ReadImage(data_path, "ArcTriomphe-cHRM-red-green-swap.png"); ASSERT_NE(image, nullptr); @@ -243,10 +247,13 @@ TEST(PngTest, BT709SwappedGamma22) { // Generated profile is tested in test_cmd_icc_profile.sh } +//------------------------------------------------------------------------------ +// ICC metadata tests + constexpr size_t kChecksumOffset = 0x54; // Verify we wrote correct hash in generated ICC profile. -TEST(PngTest, GeneratedICCHash) { +TEST(ICCTest, GeneratedICCHash) { float primariesCoords[8]; avifColorPrimariesGetValues(AVIF_COLOR_PRIMARIES_BT709, primariesCoords); @@ -280,6 +287,35 @@ TEST(PngTest, GeneratedICCHash) { 0); } +//------------------------------------------------------------------------------ +// Memory management tests + +TEST(ImageSizeLimitTest, AllFormats) { + for (const uint32_t image_size_limit : + {1u, std::numeric_limits::max()}) { + for (const char* filename : + {"paris_exif_xmp_icc.jpg", "paris_icc_exif_xmp.png", + "cosmos1650_yuv444_10bpc_p3pq.y4m"}) { + const std::string filepath = std::string(data_path) + filename; + ImagePtr image(avifImageCreateEmpty()); + ASSERT_NE(image, nullptr); + + const avifAppFileFormat format = avifReadImage( + filepath.c_str(), AVIF_PIXEL_FORMAT_NONE, /*requestedDepth=*/0, + AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC, /*ignoreColorProfile=*/true, + /*ignoreExif=*/true, /*ignoreXMP=*/true, /*allowChangingCicp=*/true, + /*ignoreGainMap=*/true, image_size_limit, image.get(), + /*outDepth=*/nullptr, /*sourceTiming=*/nullptr, + /*frameIter=*/nullptr); + if (image_size_limit == 1) { + EXPECT_EQ(format, AVIF_APP_FILE_FORMAT_UNKNOWN); + } else { + EXPECT_NE(format, AVIF_APP_FILE_FORMAT_UNKNOWN); + } + } + } +} + //------------------------------------------------------------------------------ } // namespace diff --git a/tests/gtest/aviftest_helpers.cc b/tests/gtest/aviftest_helpers.cc index 3813103cfd..17dd72a4c1 100644 --- a/tests/gtest/aviftest_helpers.cc +++ b/tests/gtest/aviftest_helpers.cc @@ -467,7 +467,7 @@ ImagePtr ReadImage(const char* folder_path, const char* file_name, avifReadImage((std::string(folder_path) + file_name).c_str(), requested_format, requested_depth, chroma_downsampling, ignore_icc, ignore_exif, ignore_xmp, allow_changing_cicp, - ignore_gain_map, image.get(), + ignore_gain_map, AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image.get(), /*outDepth=*/nullptr, /*sourceTiming=*/nullptr, /*frameIter=*/nullptr) == AVIF_APP_FILE_FORMAT_UNKNOWN) { return nullptr; diff --git a/tests/gtest/avify4mtest.cc b/tests/gtest/avify4mtest.cc index ad89c19257..bb9ceec56d 100644 --- a/tests/gtest/avify4mtest.cc +++ b/tests/gtest/avify4mtest.cc @@ -50,8 +50,9 @@ TEST_P(Y4mTest, EncodeDecode) { ImagePtr decoded(avifImageCreateEmpty()); ASSERT_NE(decoded, nullptr); - ASSERT_TRUE(y4mRead(file_path.str().c_str(), decoded.get(), - /*sourceTiming=*/nullptr, /*iter=*/nullptr)); + ASSERT_TRUE(y4mRead(file_path.str().c_str(), AVIF_DEFAULT_IMAGE_SIZE_LIMIT, + decoded.get(), /*sourceTiming=*/nullptr, + /*iter=*/nullptr)); EXPECT_TRUE(testutil::AreImagesEqual(*image, *decoded)); } @@ -87,8 +88,9 @@ TEST_P(Y4mTest, OutOfRange) { // that tag along, it is ignored by the compression algorithm. ImagePtr decoded(avifImageCreateEmpty()); ASSERT_NE(decoded, nullptr); - ASSERT_TRUE(y4mRead(file_path.str().c_str(), decoded.get(), - /*sourceTiming=*/nullptr, /*iter=*/nullptr)); + ASSERT_TRUE(y4mRead(file_path.str().c_str(), AVIF_DEFAULT_IMAGE_SIZE_LIMIT, + decoded.get(), /*sourceTiming=*/nullptr, + /*iter=*/nullptr)); // Pass it through the libavif API to make sure reading a bad y4m does not // trigger undefined behavior.