Skip to content

Commit

Permalink
Add imageSizeLimit arg to avifutil avifReadImage() (AOMediaCodec#1941)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
y-guyon authored Jan 16, 2024
1 parent 5740873 commit 21c1c2c
Show file tree
Hide file tree
Showing 18 changed files with 164 additions and 61 deletions.
3 changes: 2 additions & 1 deletion apps/avifenc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -611,6 +611,7 @@ static avifBool avifInputReadImage(avifInput * input,
ignoreXMP,
allowChangingCicp,
ignoreGainMap,
AVIF_DEFAULT_IMAGE_SIZE_LIMIT,
dstImage,
dstDepth,
dstSourceTiming,
Expand Down
2 changes: 1 addition & 1 deletion apps/avifgainmaputil/convert_command.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
17 changes: 7 additions & 10 deletions apps/avifgainmaputil/imageio.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>(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;
Expand Down
66 changes: 47 additions & 19 deletions apps/shared/avifjpeg.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -160,15 +163,15 @@ 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);
}
}

// YUV->Grayscale: subsample Y plane not allowed.
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) {
Expand All @@ -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;
}

Expand All @@ -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;
}

Expand All @@ -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);
}
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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)) {
Expand All @@ -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) {
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
4 changes: 3 additions & 1 deletion apps/shared/avifjpeg.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions apps/shared/avifpng.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions apps/shared/avifpng.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
17 changes: 14 additions & 3 deletions apps/shared/avifutil.c
Original file line number Diff line number Diff line change
Expand Up @@ -306,28 +306,39 @@ avifAppFileFormat avifReadImage(const char * filename,
avifBool ignoreXMP,
avifBool allowChangingCicp,
avifBool ignoreGainMap,
uint32_t imageSizeLimit,
avifImage * image,
uint32_t * outDepth,
avifAppSourceTiming * sourceTiming,
struct y4mFrameIterator ** frameIter)
{
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 {
Expand Down
2 changes: 2 additions & 0 deletions apps/shared/avifutil.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down
Loading

0 comments on commit 21c1c2c

Please sign in to comment.