Rename AVIF_MAX_IMAGE_SIZE to AVIF_DEFAULT_MAX_IMAGE_SIZE, set defaults in avifDecoder, promote some indexing math vars to uint32_t, update some comments
diff --git a/apps/avifdec.c b/apps/avifdec.c index b4aafbd..497534a 100644 --- a/apps/avifdec.c +++ b/apps/avifdec.c
@@ -36,8 +36,8 @@ printf(" -u,--upsampling U : Chroma upsampling (for 420/422). automatic (default), fastest, best, nearest, or bilinear\n"); printf(" -i,--info : Decode all frames and display all image information instead of saving to disk\n"); printf(" --ignore-icc : If the input file contains an embedded ICC profile, ignore it (no-op if absent)\n"); - printf(" --size-limit C : Specifies the image size limit (in total pixels) that the AV1 codec should tolerate.\n"); - printf(" Default: %u, set to 0 to disable. Supported codecs: dav1d.\n", AVIF_MAX_IMAGE_SIZE); + printf(" --size-limit C : Specifies the image size limit (in total pixels) that should be tolerated.\n"); + printf(" Default: %u, set to 0 to disable.\n", AVIF_DEFAULT_MAX_IMAGE_SIZE); printf("\n"); avifPrintVersions(); } @@ -93,7 +93,7 @@ avifBool infoOnly = AVIF_FALSE; avifChromaUpsampling chromaUpsampling = AVIF_CHROMA_UPSAMPLING_AUTOMATIC; avifBool ignoreICC = AVIF_FALSE; - uint32_t imageSizeLimit = AVIF_MAX_IMAGE_SIZE; + uint32_t imageSizeLimit = AVIF_DEFAULT_MAX_IMAGE_SIZE; if (argc < 2) { syntax();
diff --git a/include/avif/avif.h b/include/avif/avif.h index e8e452a..9e01cdc 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h
@@ -76,9 +76,9 @@ #define AVIF_SPEED_SLOWEST 0 #define AVIF_SPEED_FASTEST 10 -// A maximum image size to avoid out-of-memory errors or integer overflow in +// A reasonable default for maximum image size to avoid out-of-memory errors or integer overflow in // (32-bit) int or unsigned int arithmetic operations. -#define AVIF_MAX_IMAGE_SIZE (16384 * 16384) +#define AVIF_DEFAULT_MAX_IMAGE_SIZE (16384 * 16384) enum avifPlanesFlags { @@ -144,7 +144,8 @@ AVIF_RESULT_IO_ERROR, AVIF_RESULT_WAITING_ON_IO, // similar to EAGAIN/EWOULDBLOCK, this means the avifIO doesn't have necessary data available yet AVIF_RESULT_INVALID_ARGUMENT, // an argument passed into this function is invalid - AVIF_RESULT_NOT_IMPLEMENTED // a requested code path is not (yet) implemented + AVIF_RESULT_NOT_IMPLEMENTED, // a requested code path is not (yet) implemented + AVIF_RESULT_IMAGE_TOO_LARGE // The image exceeds the configured imageSizeLimit } avifResult; AVIF_API const char * avifResultToString(avifResult result); @@ -705,9 +706,10 @@ avifBool ignoreExif; avifBool ignoreXMP; - // This represents the maximum size of a image (in pixel count) that the underlying AV1 decoder - // should attempt to decode. It defaults to AVIF_MAX_IMAGE_SIZE, and can be set to 0 to disable - // the limit. Currently supported codecs: dav1d. + // This represents the maximum size of a image (in pixel count) that libavif and the underlying + // AV1 decoder should attempt to decode. It defaults to AVIF_DEFAULT_MAX_IMAGE_SIZE, and can be + // set to 0 to disable the limit. + // Note: Only some underlying AV1 codecs support a configurable size limit (such as dav1d). uint32_t imageSizeLimit; // stats from the most recent read, possibly 0s if reading an image sequence
diff --git a/src/avif.c b/src/avif.c index 18e6734..085e526 100644 --- a/src/avif.c +++ b/src/avif.c
@@ -93,6 +93,7 @@ case AVIF_RESULT_WAITING_ON_IO: return "Waiting on IO"; case AVIF_RESULT_INVALID_ARGUMENT: return "Invalid argument"; case AVIF_RESULT_NOT_IMPLEMENTED: return "Not implemented"; + case AVIF_RESULT_IMAGE_TOO_LARGE: return "Image too large"; case AVIF_RESULT_UNKNOWN_ERROR: default: break;
diff --git a/src/read.c b/src/read.c index 44ee044..a826a80 100644 --- a/src/read.c +++ b/src/read.c
@@ -1182,14 +1182,14 @@ return AVIF_TRUE; } -static avifBool avifParseImageGridBox(avifImageGrid * grid, const uint8_t * raw, size_t rawLen) +static avifResult avifParseImageGridBox(avifImageGrid * grid, const uint8_t * raw, size_t rawLen, uint32_t imageSizeLimit) { BEGIN_STREAM(s, raw, rawLen); uint8_t version, flags; CHECK(avifROStreamRead(&s, &version, 1)); // unsigned int(8) version = 0; if (version != 0) { - return AVIF_FALSE; + return AVIF_RESULT_INVALID_IMAGE_GRID; } uint8_t rowsMinusOne, columnsMinusOne; CHECK(avifROStreamRead(&s, &flags, 1)); // unsigned int(8) flags; @@ -1208,15 +1208,18 @@ } else { if (fieldLength != 32) { // This should be impossible - return AVIF_FALSE; + return AVIF_RESULT_INVALID_IMAGE_GRID; } CHECK(avifROStreamReadU32(&s, &grid->outputWidth)); // unsigned int(FieldLength) output_width; CHECK(avifROStreamReadU32(&s, &grid->outputHeight)); // unsigned int(FieldLength) output_height; } - if ((grid->outputWidth == 0) || (grid->outputHeight == 0) || (grid->outputWidth > (AVIF_MAX_IMAGE_SIZE / grid->outputHeight))) { - return AVIF_FALSE; + if ((grid->outputWidth == 0) || (grid->outputHeight == 0) || (avifROStreamRemainingBytes(&s) != 0)) { + return AVIF_RESULT_INVALID_IMAGE_GRID; } - return avifROStreamRemainingBytes(&s) == 0; + if (imageSizeLimit && (grid->outputWidth > (imageSizeLimit / grid->outputHeight))) { + return AVIF_RESULT_IMAGE_TOO_LARGE; + } + return AVIF_RESULT_OK; } static avifBool avifParseImageSpatialExtentsProperty(avifProperty * prop, const uint8_t * raw, size_t rawLen) @@ -2233,6 +2236,7 @@ avifDecoder * decoder = (avifDecoder *)avifAlloc(sizeof(avifDecoder)); memset(decoder, 0, sizeof(avifDecoder)); decoder->maxThreads = 1; + decoder->imageSizeLimit = AVIF_DEFAULT_MAX_IMAGE_SIZE; return decoder; } @@ -2632,8 +2636,9 @@ if (readResult != AVIF_RESULT_OK) { return readResult; } - if (!avifParseImageGridBox(&data->colorGrid, readData.data, readData.size)) { - return AVIF_RESULT_INVALID_IMAGE_GRID; + avifResult parseResult = avifParseImageGridBox(&data->colorGrid, readData.data, readData.size, decoder->imageSizeLimit); + if (parseResult != AVIF_RESULT_OK) { + return parseResult; } } @@ -2671,8 +2676,10 @@ if (readResult != AVIF_RESULT_OK) { return readResult; } - if (!avifParseImageGridBox(&data->alphaGrid, readData.data, readData.size)) { - return AVIF_RESULT_INVALID_IMAGE_GRID; + avifResult parseResult = + avifParseImageGridBox(&data->alphaGrid, readData.data, readData.size, decoder->imageSizeLimit); + if (parseResult != AVIF_RESULT_OK) { + return parseResult; } } @@ -2747,6 +2754,12 @@ if (ispeProp) { decoder->image->width = ispeProp->u.ispe.width; decoder->image->height = ispeProp->u.ispe.height; + if (!decoder->image->width || !decoder->image->height) { + return AVIF_RESULT_BMFF_PARSE_FAILED; + } + if (decoder->imageSizeLimit && (decoder->image->width > (decoder->imageSizeLimit / decoder->image->height))) { + return AVIF_RESULT_IMAGE_TOO_LARGE; + } } else { decoder->image->width = 0; decoder->image->height = 0;
diff --git a/src/reformat.c b/src/reformat.c index be035c4..f1dfbdb 100644 --- a/src/reformat.c +++ b/src/reformat.c
@@ -180,7 +180,7 @@ uint32_t * yuvRowBytes = image->yuvRowBytes; for (uint32_t outerJ = 0; outerJ < image->height; outerJ += 2) { for (uint32_t outerI = 0; outerI < image->width; outerI += 2) { - int blockW = 2, blockH = 2; + uint32_t blockW = 2, blockH = 2; if ((outerI + 1) >= image->width) { blockW = 1; } @@ -189,10 +189,10 @@ } // Convert an entire 2x2 block to YUV, and populate any fully sampled channels as we go - for (int bJ = 0; bJ < blockH; ++bJ) { - for (int bI = 0; bI < blockW; ++bI) { - int i = outerI + bI; - int j = outerJ + bJ; + for (uint32_t bJ = 0; bJ < blockH; ++bJ) { + for (uint32_t bI = 0; bI < blockW; ++bI) { + uint32_t i = outerI + bI; + uint32_t j = outerJ + bJ; // Unpack RGB into normalized float if (state.rgbChannelBytes > 1) { @@ -297,8 +297,8 @@ float sumU = 0.0f; float sumV = 0.0f; - for (int bJ = 0; bJ < blockH; ++bJ) { - for (int bI = 0; bI < blockW; ++bI) { + for (uint32_t bJ = 0; bJ < blockH; ++bJ) { + for (uint32_t bI = 0; bI < blockW; ++bI) { sumU += yuvBlock[bI][bJ].u; sumV += yuvBlock[bI][bJ].v; } @@ -307,10 +307,10 @@ float avgU = sumU / totalSamples; float avgV = sumV / totalSamples; - const int chromaShiftX = 1; - const int chromaShiftY = 1; - int uvI = outerI >> chromaShiftX; - int uvJ = outerJ >> chromaShiftY; + const uint32_t chromaShiftX = 1; + const uint32_t chromaShiftY = 1; + uint32_t uvI = outerI >> chromaShiftX; + uint32_t uvJ = outerJ >> chromaShiftY; if (state.yuvChannelBytes > 1) { uint16_t * pU = (uint16_t *)&yuvPlanes[AVIF_CHAN_U][(uvI * 2) + (uvJ * yuvRowBytes[AVIF_CHAN_U])]; *pU = (uint16_t)avifReformatStateUVToUNorm(&state, avgU); @@ -323,10 +323,10 @@ } else if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV422) { // YUV422, average 2 samples (1x2), twice - for (int bJ = 0; bJ < blockH; ++bJ) { + for (uint32_t bJ = 0; bJ < blockH; ++bJ) { float sumU = 0.0f; float sumV = 0.0f; - for (int bI = 0; bI < blockW; ++bI) { + for (uint32_t bI = 0; bI < blockW; ++bI) { sumU += yuvBlock[bI][bJ].u; sumV += yuvBlock[bI][bJ].v; } @@ -334,9 +334,9 @@ float avgU = sumU / totalSamples; float avgV = sumV / totalSamples; - const int chromaShiftX = 1; - int uvI = outerI >> chromaShiftX; - int uvJ = outerJ + bJ; + const uint32_t chromaShiftX = 1; + uint32_t uvI = outerI >> chromaShiftX; + uint32_t uvJ = outerJ + bJ; if (state.yuvChannelBytes > 1) { uint16_t * pU = (uint16_t *)&yuvPlanes[AVIF_CHAN_U][(uvI * 2) + (uvJ * yuvRowBytes[AVIF_CHAN_U])]; *pU = (uint16_t)avifReformatStateUVToUNorm(&state, avgU); @@ -488,7 +488,7 @@ uint16_t unormU[2][2], unormV[2][2]; // How many bytes to add to a uint8_t pointer index to get to the adjacent (lesser) sample in a given direction - int uAdjCol, vAdjCol, uAdjRow, vAdjRow; + uint32_t uAdjCol, vAdjCol, uAdjRow, vAdjRow; if ((i == 0) || ((i == (image->width - 1)) && ((i % 2) != 0))) { uAdjCol = 0; vAdjCol = 0; @@ -538,8 +538,8 @@ unormV[1][1] = *((const uint16_t *)&vPlane[(uvJ * vRowBytes) + (uvI * yuvChannelBytes) + vAdjCol + vAdjRow]); // clamp incoming data to protect against bad LUT lookups - for (int bJ = 0; bJ < 2; ++bJ) { - for (int bI = 0; bI < 2; ++bI) { + for (uint32_t bJ = 0; bJ < 2; ++bJ) { + for (uint32_t bI = 0; bI < 2; ++bI) { unormU[bI][bJ] = AVIF_MIN(unormU[bI][bJ], yuvMaxChannel); unormV[bI][bJ] = AVIF_MIN(unormV[bI][bJ], yuvMaxChannel); }