diff --git a/apps/avifdec.c b/apps/avifdec.c index 2e1d7ee0f5..497534a93f 100644 --- a/apps/avifdec.c +++ b/apps/avifdec.c @@ -36,6 +36,8 @@ static void syntax(void) 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 should be tolerated.\n"); + printf(" Default: %u, set to 0 to disable.\n", AVIF_DEFAULT_MAX_IMAGE_SIZE); printf("\n"); avifPrintVersions(); } @@ -91,6 +93,7 @@ int main(int argc, char * argv[]) avifBool infoOnly = AVIF_FALSE; avifChromaUpsampling chromaUpsampling = AVIF_CHROMA_UPSAMPLING_AUTOMATIC; avifBool ignoreICC = AVIF_FALSE; + uint32_t imageSizeLimit = AVIF_DEFAULT_MAX_IMAGE_SIZE; if (argc < 2) { syntax(); @@ -161,6 +164,9 @@ int main(int argc, char * argv[]) infoOnly = AVIF_TRUE; } else if (!strcmp(arg, "--ignore-icc")) { ignoreICC = AVIF_TRUE; + } else if (!strcmp(arg, "--size-limit")) { + NEXTARG(); + imageSizeLimit = strtoul(arg, NULL, 10); } else { // Positional argument if (!inputFilename) { @@ -205,6 +211,7 @@ int main(int argc, char * argv[]) avifDecoder * decoder = avifDecoderCreate(); decoder->maxThreads = jobs; decoder->codecChoice = codecChoice; + decoder->imageSizeLimit = imageSizeLimit; avifResult decodeResult = avifDecoderReadFile(decoder, avif, inputFilename); if (decodeResult == AVIF_RESULT_OK) { printf("Image decoded: %s\n", inputFilename); diff --git a/include/avif/avif.h b/include/avif/avif.h index bb5a59824a..9e01cdc4a1 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -76,6 +76,10 @@ typedef int avifBool; #define AVIF_SPEED_SLOWEST 0 #define AVIF_SPEED_FASTEST 10 +// 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_DEFAULT_MAX_IMAGE_SIZE (16384 * 16384) + enum avifPlanesFlags { AVIF_PLANES_YUV = (1 << 0), @@ -140,7 +144,8 @@ typedef enum avifResult 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); @@ -701,6 +706,12 @@ typedef struct avifDecoder avifBool ignoreExif; avifBool ignoreXMP; + // 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 avifIOStats ioStats; diff --git a/include/avif/internal.h b/include/avif/internal.h index 380081a701..5ad5e99289 100644 --- a/include/avif/internal.h +++ b/include/avif/internal.h @@ -330,10 +330,6 @@ typedef struct avifSequenceHeader } avifSequenceHeader; avifBool avifSequenceHeaderParse(avifSequenceHeader * header, const avifROData * sample); -// A 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) - #ifdef __cplusplus } // extern "C" #endif diff --git a/src/avif.c b/src/avif.c index 18e6734e1f..085e526f2d 100644 --- a/src/avif.c +++ b/src/avif.c @@ -93,6 +93,7 @@ const char * avifResultToString(avifResult result) 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/codec_dav1d.c b/src/codec_dav1d.c index 054f846bdd..65a78be972 100644 --- a/src/codec_dav1d.c +++ b/src/codec_dav1d.c @@ -55,6 +55,7 @@ static avifBool dav1dCodecOpen(avifCodec * codec, avifDecoder * decoder) // Give all available threads to decode a single frame as fast as possible codec->internal->dav1dSettings.n_frame_threads = 1; codec->internal->dav1dSettings.n_tile_threads = AVIF_CLAMP(decoder->maxThreads, 1, DAV1D_MAX_TILE_THREADS); + codec->internal->dav1dSettings.frame_size_limit = decoder->imageSizeLimit; if (dav1d_open(&codec->internal->dav1dContext, &codec->internal->dav1dSettings) != 0) { return AVIF_FALSE; @@ -209,9 +210,6 @@ avifCodec * avifCodecCreateDav1d(void) memset(codec->internal, 0, sizeof(struct avifCodecInternal)); dav1d_default_settings(&codec->internal->dav1dSettings); - // Set a maximum frame size limit to avoid OOM'ing fuzzers. - codec->internal->dav1dSettings.frame_size_limit = AVIF_MAX_IMAGE_SIZE; - // Ensure that we only get the "highest spatial layer" as a single frame // for each input sample, instead of getting each spatial layer as its own // frame one at a time ("all layers"). diff --git a/src/read.c b/src/read.c index 44ee044669..a826a804a8 100644 --- a/src/read.c +++ b/src/read.c @@ -1182,14 +1182,14 @@ static avifBool avifParseItemLocationBox(avifMeta * meta, const uint8_t * raw, s 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 @@ static avifBool avifParseImageGridBox(avifImageGrid * grid, const uint8_t * raw, } 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; + } + if (imageSizeLimit && (grid->outputWidth > (imageSizeLimit / grid->outputHeight))) { + return AVIF_RESULT_IMAGE_TOO_LARGE; } - return avifROStreamRemainingBytes(&s) == 0; + return AVIF_RESULT_OK; } static avifBool avifParseImageSpatialExtentsProperty(avifProperty * prop, const uint8_t * raw, size_t rawLen) @@ -2233,6 +2236,7 @@ avifDecoder * avifDecoderCreate(void) 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 @@ avifResult avifDecoderReset(avifDecoder * decoder) 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 @@ avifResult avifDecoderReset(avifDecoder * decoder) 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 @@ avifResult avifDecoderReset(avifDecoder * decoder) 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 be035c4a73..f1dfbdbb56 100644 --- a/src/reformat.c +++ b/src/reformat.c @@ -180,7 +180,7 @@ avifResult avifImageRGBToYUV(avifImage * image, const avifRGBImage * rgb) 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 @@ avifResult avifImageRGBToYUV(avifImage * image, const avifRGBImage * rgb) } // 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 @@ avifResult avifImageRGBToYUV(avifImage * image, const avifRGBImage * rgb) 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 @@ avifResult avifImageRGBToYUV(avifImage * image, const avifRGBImage * rgb) 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 @@ avifResult avifImageRGBToYUV(avifImage * image, const avifRGBImage * rgb) } 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 @@ avifResult avifImageRGBToYUV(avifImage * image, const avifRGBImage * rgb) 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 @@ static avifResult avifImageYUVAnyToRGBAnySlow(const avifImage * image, 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 @@ static avifResult avifImageYUVAnyToRGBAnySlow(const avifImage * image, 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); }