Skip to content

Commit ae187d1

Browse files
committed
Make image size limit configurable
Adapted from Joe Drago's pull request AOMediaCodec#527, with the limitation that decoder->imageSizeLimit must be less than or equal to the default value and must not be set to 0 (reserved for future use). This way we don't need to audit our code for integer overflows due to a large image width or height. Set decoder->imageSizeLimit to 11 * 1024 * 10 * 1024 in avif_decode_fuzzer.cc to keep its memory consumption under 2560 MB.
1 parent 5fb03ad commit ae187d1

File tree

6 files changed

+45
-26
lines changed

6 files changed

+45
-26
lines changed

include/avif/avif.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ typedef int avifBool;
6868

6969
#define AVIF_DIAGNOSTICS_ERROR_BUFFER_SIZE 256
7070

71+
// A reasonable default for maximum image size to avoid out-of-memory errors or integer overflow in
72+
// (32-bit) int or unsigned int arithmetic operations.
73+
#define AVIF_DEFAULT_MAX_IMAGE_SIZE (16384 * 16384)
74+
7175
// a 12 hour AVIF image sequence, running at 60 fps (a basic sanity check as this is quite ridiculous)
7276
#define AVIF_DEFAULT_IMAGE_COUNT_LIMIT (12 * 3600 * 60)
7377

@@ -810,6 +814,12 @@ typedef struct avifDecoder
810814
avifBool ignoreExif;
811815
avifBool ignoreXMP;
812816

817+
// This represents the maximum size of a image (in pixel count) that libavif and the underlying
818+
// AV1 decoder should attempt to decode. It defaults to AVIF_DEFAULT_MAX_IMAGE_SIZE, and can be
819+
// set to a smaller value. The value 0 is reserved.
820+
// Note: Only some underlying AV1 codecs support a configurable size limit (such as dav1d).
821+
uint32_t imageSizeLimit;
822+
813823
// This provides an upper bound on how many images the decoder is willing to attempt to decode,
814824
// to provide a bit of protection from malicious or malformed AVIFs citing millions upon
815825
// millions of frames, only to be invalid later. The default is AVIF_DEFAULT_IMAGE_COUNT_LIMIT

include/avif/internal.h

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ avifResult avifRGBImageUnpremultiplyAlphaLibYUV(avifRGBImage * rgb);
159159
// Scaling
160160

161161
// This scales the YUV/A planes in-place.
162-
avifBool avifImageScale(avifImage * image, uint32_t dstWidth, uint32_t dstHeight, avifDiagnostics * diag);
162+
avifBool avifImageScale(avifImage * image, uint32_t dstWidth, uint32_t dstHeight, uint32_t imageSizeLimit, avifDiagnostics * diag);
163163

164164
// ---------------------------------------------------------------------------
165165
// avifCodecDecodeInput
@@ -367,10 +367,6 @@ typedef struct avifSequenceHeader
367367
} avifSequenceHeader;
368368
avifBool avifSequenceHeaderParse(avifSequenceHeader * header, const avifROData * sample);
369369

370-
// A maximum image size to avoid out-of-memory errors or integer overflow in
371-
// (32-bit) int or unsigned int arithmetic operations.
372-
#define AVIF_MAX_IMAGE_SIZE (16384 * 16384)
373-
374370
#ifdef __cplusplus
375371
} // extern "C"
376372
#endif

src/codec_dav1d.c

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ static avifBool dav1dCodecGetNextImage(struct avifCodec * codec,
5959
// Give all available threads to decode a single frame as fast as possible
6060
codec->internal->dav1dSettings.n_frame_threads = 1;
6161
codec->internal->dav1dSettings.n_tile_threads = AVIF_CLAMP(decoder->maxThreads, 1, DAV1D_MAX_TILE_THREADS);
62+
// Set a maximum frame size limit to avoid OOM'ing fuzzers. In 32-bit builds, if
63+
// frame_size_limit > 8192 * 8192, dav1d reduces frame_size_limit to 8192 * 8192 and logs
64+
// a message, so we set frame_size_limit to at most 8192 * 8192 to avoid the dav1d_log
65+
// message.
66+
codec->internal->dav1dSettings.frame_size_limit = (sizeof(size_t) < 8) ? AVIF_MIN(decoder->imageSizeLimit, 8192 * 8192)
67+
: decoder->imageSizeLimit;
6268
codec->internal->dav1dSettings.operating_point = codec->operatingPoint;
6369
codec->internal->dav1dSettings.all_layers = codec->allLayers;
6470

@@ -215,11 +221,6 @@ avifCodec * avifCodecCreateDav1d(void)
215221
memset(codec->internal, 0, sizeof(struct avifCodecInternal));
216222
dav1d_default_settings(&codec->internal->dav1dSettings);
217223

218-
// Set a maximum frame size limit to avoid OOM'ing fuzzers. In 32-bit builds, if
219-
// frame_size_limit > 8192 * 8192, dav1d reduces frame_size_limit to 8192 * 8192 and logs a
220-
// message, so we set frame_size_limit to 8192 * 8192 to avoid the dav1d_log message.
221-
codec->internal->dav1dSettings.frame_size_limit = (sizeof(size_t) < 8) ? (8192 * 8192) : AVIF_MAX_IMAGE_SIZE;
222-
223224
// Ensure that we only get the "highest spatial layer" as a single frame
224225
// for each input sample, instead of getting each spatial layer as its own
225226
// frame one at a time ("all layers").

src/read.c

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1598,7 +1598,7 @@ static avifBool avifParseItemLocationBox(avifMeta * meta, const uint8_t * raw, s
15981598
return AVIF_TRUE;
15991599
}
16001600

1601-
static avifBool avifParseImageGridBox(avifImageGrid * grid, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
1601+
static avifBool avifParseImageGridBox(avifImageGrid * grid, const uint8_t * raw, size_t rawLen, uint32_t imageSizeLimit, avifDiagnostics * diag)
16021602
{
16031603
BEGIN_STREAM(s, raw, rawLen, diag, "Box[grid]");
16041604

@@ -1631,7 +1631,7 @@ static avifBool avifParseImageGridBox(avifImageGrid * grid, const uint8_t * raw,
16311631
CHECK(avifROStreamReadU32(&s, &grid->outputWidth)); // unsigned int(FieldLength) output_width;
16321632
CHECK(avifROStreamReadU32(&s, &grid->outputHeight)); // unsigned int(FieldLength) output_height;
16331633
}
1634-
if ((grid->outputWidth == 0) || (grid->outputHeight == 0) || (grid->outputWidth > (AVIF_MAX_IMAGE_SIZE / grid->outputHeight))) {
1634+
if ((grid->outputWidth == 0) || (grid->outputHeight == 0) || (grid->outputWidth > (imageSizeLimit / grid->outputHeight))) {
16351635
avifDiagnosticsPrintf(diag, "Grid box contains illegal dimensions: [%u x %u]", grid->outputWidth, grid->outputHeight);
16361636
return AVIF_FALSE;
16371637
}
@@ -2322,7 +2322,7 @@ static avifBool avifParseMetaBox(avifMeta * meta, const uint8_t * raw, size_t ra
23222322
return AVIF_TRUE;
23232323
}
23242324

2325-
static avifBool avifParseTrackHeaderBox(avifTrack * track, const uint8_t * raw, size_t rawLen, avifDiagnostics * diag)
2325+
static avifBool avifParseTrackHeaderBox(avifTrack * track, const uint8_t * raw, size_t rawLen, uint32_t imageSizeLimit, avifDiagnostics * diag)
23262326
{
23272327
BEGIN_STREAM(s, raw, rawLen, diag, "Box[tkhd]");
23282328

@@ -2365,7 +2365,7 @@ static avifBool avifParseTrackHeaderBox(avifTrack * track, const uint8_t * raw,
23652365
track->width = width >> 16;
23662366
track->height = height >> 16;
23672367

2368-
if ((track->width == 0) || (track->height == 0) || (track->width > (AVIF_MAX_IMAGE_SIZE / track->height))) {
2368+
if ((track->width == 0) || (track->height == 0) || (track->width > (imageSizeLimit / track->height))) {
23692369
avifDiagnosticsPrintf(diag, "Track ID [%u] has an invalid size [%ux%u]", track->id, track->width, track->height);
23702370
return AVIF_FALSE;
23712371
}
@@ -2644,7 +2644,7 @@ static avifBool avifTrackReferenceBox(avifTrack * track, const uint8_t * raw, si
26442644
return AVIF_TRUE;
26452645
}
26462646

2647-
static avifBool avifParseTrackBox(avifDecoderData * data, const uint8_t * raw, size_t rawLen)
2647+
static avifBool avifParseTrackBox(avifDecoderData * data, const uint8_t * raw, size_t rawLen, uint32_t imageSizeLimit)
26482648
{
26492649
BEGIN_STREAM(s, raw, rawLen, data->diag, "Box[trak]");
26502650

@@ -2655,7 +2655,7 @@ static avifBool avifParseTrackBox(avifDecoderData * data, const uint8_t * raw, s
26552655
CHECK(avifROStreamReadBoxHeader(&s, &header));
26562656

26572657
if (!memcmp(header.type, "tkhd", 4)) {
2658-
CHECK(avifParseTrackHeaderBox(track, avifROStreamCurrent(&s), header.size, data->diag));
2658+
CHECK(avifParseTrackHeaderBox(track, avifROStreamCurrent(&s), header.size, imageSizeLimit, data->diag));
26592659
} else if (!memcmp(header.type, "meta", 4)) {
26602660
CHECK(avifParseMetaBox(track->meta, avifROStreamCurrent(&s), header.size, data->diag));
26612661
} else if (!memcmp(header.type, "mdia", 4)) {
@@ -2669,7 +2669,7 @@ static avifBool avifParseTrackBox(avifDecoderData * data, const uint8_t * raw, s
26692669
return AVIF_TRUE;
26702670
}
26712671

2672-
static avifBool avifParseMoovBox(avifDecoderData * data, const uint8_t * raw, size_t rawLen)
2672+
static avifBool avifParseMoovBox(avifDecoderData * data, const uint8_t * raw, size_t rawLen, uint32_t imageSizeLimit)
26732673
{
26742674
BEGIN_STREAM(s, raw, rawLen, data->diag, "Box[moov]");
26752675

@@ -2678,7 +2678,7 @@ static avifBool avifParseMoovBox(avifDecoderData * data, const uint8_t * raw, si
26782678
CHECK(avifROStreamReadBoxHeader(&s, &header));
26792679

26802680
if (!memcmp(header.type, "trak", 4)) {
2681-
CHECK(avifParseTrackBox(data, avifROStreamCurrent(&s), header.size));
2681+
CHECK(avifParseTrackBox(data, avifROStreamCurrent(&s), header.size, imageSizeLimit));
26822682
}
26832683

26842684
CHECK(avifROStreamSkip(&s, header.size));
@@ -2779,7 +2779,7 @@ static avifResult avifParse(avifDecoder * decoder)
27792779
metaSeen = AVIF_TRUE;
27802780
} else if (!memcmp(header.type, "moov", 4)) {
27812781
CHECKERR(!moovSeen, AVIF_RESULT_BMFF_PARSE_FAILED);
2782-
CHECKERR(avifParseMoovBox(data, boxContents.data, boxContents.size), AVIF_RESULT_BMFF_PARSE_FAILED);
2782+
CHECKERR(avifParseMoovBox(data, boxContents.data, boxContents.size, decoder->imageSizeLimit), AVIF_RESULT_BMFF_PARSE_FAILED);
27832783
moovSeen = AVIF_TRUE;
27842784
}
27852785

@@ -2847,6 +2847,7 @@ avifDecoder * avifDecoderCreate(void)
28472847
avifDecoder * decoder = (avifDecoder *)avifAlloc(sizeof(avifDecoder));
28482848
memset(decoder, 0, sizeof(avifDecoder));
28492849
decoder->maxThreads = 1;
2850+
decoder->imageSizeLimit = AVIF_DEFAULT_MAX_IMAGE_SIZE;
28502851
decoder->imageCountLimit = AVIF_DEFAULT_IMAGE_COUNT_LIMIT;
28512852
decoder->strictFlags = AVIF_STRICT_ENABLED;
28522853
return decoder;
@@ -3033,6 +3034,11 @@ avifResult avifDecoderParse(avifDecoder * decoder)
30333034
{
30343035
avifDiagnosticsClearError(&decoder->diag);
30353036

3037+
// An imageSizeLimit greater than AVIF_DEFAULT_MAX_IMAGE_SIZE and the special value of 0 to
3038+
// disable the limit are not yet implemented.
3039+
if ((decoder->imageSizeLimit > AVIF_DEFAULT_MAX_IMAGE_SIZE) || (decoder->imageSizeLimit == 0)) {
3040+
return AVIF_RESULT_NOT_IMPLEMENTED;
3041+
}
30363042
if (!decoder->io || !decoder->io->read) {
30373043
return AVIF_RESULT_IO_NOT_SET;
30383044
}
@@ -3073,7 +3079,7 @@ avifResult avifDecoderParse(avifDecoder * decoder)
30733079
item->width = ispeProp->u.ispe.width;
30743080
item->height = ispeProp->u.ispe.height;
30753081

3076-
if ((item->width == 0) || (item->height == 0) || (item->width > (AVIF_MAX_IMAGE_SIZE / item->height))) {
3082+
if ((item->width == 0) || (item->height == 0) || (item->width > (decoder->imageSizeLimit / item->height))) {
30773083
avifDiagnosticsPrintf(data->diag, "Item ID [%u] has an invalid size [%ux%u]", item->id, item->width, item->height);
30783084
return AVIF_RESULT_BMFF_PARSE_FAILED;
30793085
}
@@ -3302,7 +3308,7 @@ avifResult avifDecoderReset(avifDecoder * decoder)
33023308
if (readResult != AVIF_RESULT_OK) {
33033309
return readResult;
33043310
}
3305-
if (!avifParseImageGridBox(&data->colorGrid, readData.data, readData.size, data->diag)) {
3311+
if (!avifParseImageGridBox(&data->colorGrid, readData.data, readData.size, decoder->imageSizeLimit, data->diag)) {
33063312
return AVIF_RESULT_INVALID_IMAGE_GRID;
33073313
}
33083314
}
@@ -3342,7 +3348,7 @@ avifResult avifDecoderReset(avifDecoder * decoder)
33423348
if (readResult != AVIF_RESULT_OK) {
33433349
return readResult;
33443350
}
3345-
if (!avifParseImageGridBox(&data->alphaGrid, readData.data, readData.size, data->diag)) {
3351+
if (!avifParseImageGridBox(&data->alphaGrid, readData.data, readData.size, decoder->imageSizeLimit, data->diag)) {
33463352
return AVIF_RESULT_INVALID_IMAGE_GRID;
33473353
}
33483354
}
@@ -3611,7 +3617,7 @@ avifResult avifDecoderNextImage(avifDecoder * decoder)
36113617

36123618
// Scale the decoded image so that it corresponds to this tile's output dimensions
36133619
if ((tile->width != tile->image->width) || (tile->height != tile->image->height)) {
3614-
if (!avifImageScale(tile->image, tile->width, tile->height, &decoder->diag)) {
3620+
if (!avifImageScale(tile->image, tile->width, tile->height, decoder->imageSizeLimit, &decoder->diag)) {
36153621
avifDiagnosticsPrintf(&decoder->diag, "avifImageScale() failed");
36163622
return tile->input->alpha ? AVIF_RESULT_DECODE_ALPHA_FAILED : AVIF_RESULT_DECODE_COLOR_FAILED;
36173623
}

src/scale.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55

66
#if !defined(AVIF_LIBYUV_ENABLED)
77

8-
avifBool avifImageScale(avifImage * image, uint32_t dstWidth, uint32_t dstHeight, avifDiagnostics * diag)
8+
avifBool avifImageScale(avifImage * image, uint32_t dstWidth, uint32_t dstHeight, uint32_t imageSizeLimit, avifDiagnostics * diag)
99
{
1010
(void)image;
1111
(void)dstWidth;
1212
(void)dstHeight;
13+
(void)imageSizeLimit;
1314
avifDiagnosticsPrintf(diag, "avifImageScale() called, but is unimplemented without libyuv!");
1415
return AVIF_FALSE;
1516
}
@@ -28,14 +29,14 @@ avifBool avifImageScale(avifImage * image, uint32_t dstWidth, uint32_t dstHeight
2829
// This should be configurable and/or smarter. kFilterBox has the highest quality but is the slowest.
2930
#define AVIF_LIBYUV_FILTER_MODE kFilterBox
3031

31-
avifBool avifImageScale(avifImage * image, uint32_t dstWidth, uint32_t dstHeight, avifDiagnostics * diag)
32+
avifBool avifImageScale(avifImage * image, uint32_t dstWidth, uint32_t dstHeight, uint32_t imageSizeLimit, avifDiagnostics * diag)
3233
{
3334
if ((image->width == dstWidth) && (image->height == dstHeight)) {
3435
// Nothing to do
3536
return AVIF_TRUE;
3637
}
3738

38-
if ((dstWidth == 0) || (dstHeight == 0) || (dstWidth > (AVIF_MAX_IMAGE_SIZE / dstHeight))) {
39+
if ((dstWidth == 0) || (dstHeight == 0) || (dstWidth > (imageSizeLimit / dstHeight))) {
3940
avifDiagnosticsPrintf(diag, "avifImageScale requested invalid dst dimensions [%ux%u]", dstWidth, dstHeight);
4041
return AVIF_FALSE;
4142
}

tests/oss-fuzz/avif_decode_fuzzer.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t * Data, size_t Size)
1818
static size_t yuvDepthsCount = sizeof(yuvDepths) / sizeof(yuvDepths[0]);
1919

2020
avifDecoder * decoder = avifDecoderCreate();
21+
// ClusterFuzz passes -rss_limit_mb=2560 to avif_decode_fuzzer. Empirically setting
22+
// decoder->imageSizeLimit to this value allows avif_decode_fuzzer to consume no more than
23+
// 2560 MB of memory.
24+
static_assert(11 * 1024 * 10 * 1024 <= AVIF_DEFAULT_MAX_IMAGE_SIZE, "");
25+
decoder->imageSizeLimit = 11 * 1024 * 10 * 1024;
2126
avifResult result = avifDecoderSetIOMemory(decoder, Data, Size);
2227
if (result == AVIF_RESULT_OK) {
2328
result = avifDecoderParse(decoder);

0 commit comments

Comments
 (0)