Drop APNG frames that don't fit entirely within the destination surface. (flutter/engine#56928)

As per the [spec](https://www.w3.org/TR/png/#fcTL-chunk):

> The frame must be rendered within the region defined by x_offset, y_offset, width, and height. This region may not fall outside of the default image; thus x_offset plus width must not be greater than the [IHDR](https://www.w3.org/TR/png/#11IHDR) width; similarly y_offset plus height must not be greater than the [IHDR](https://www.w3.org/TR/png/#11IHDR) height.
This commit is contained in:
Brandon DeRosier 2024-12-05 01:34:32 -08:00 committed by GitHub
parent 7b1b6d13f2
commit ba21393f49
4 changed files with 48 additions and 2 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

View File

@ -197,7 +197,7 @@ TEST(ImageDecoderNoGLTest, ImpellerWideGamutIndexedPng) {
#endif // IMPELLER_SUPPORTS_RENDERING
}
TEST(ImageDecoderNoGLTest, ImepllerUnmultipliedAlphaPng) {
TEST(ImageDecoderNoGLTest, ImpellerUnmultipliedAlphaPng) {
#if defined(OS_FUCHSIA)
GTEST_SKIP() << "Fuchsia can't load the test fixtures.";
#endif

View File

@ -110,6 +110,20 @@ bool APNGImageGenerator::GetPixels(const SkImageInfo& info,
<< ") of APNG due to the frame missing data (frame_info).";
return false;
}
if (frame.x_offset + frame_info.width() >
static_cast<unsigned int>(info.width()) ||
frame.y_offset + frame_info.height() >
static_cast<unsigned int>(info.height())) {
FML_DLOG(ERROR)
<< "Decoded image at index " << image_index
<< " (frame index: " << frame_index
<< ") rejected because the destination region (x: " << frame.x_offset
<< ", y: " << frame.y_offset << ", width: " << frame_info.width()
<< ", height: " << frame_info.height()
<< ") is not entirely within the destination surface (width: "
<< info.width() << ", height: " << info.height() << ").";
return false;
}
//----------------------------------------------------------------------------
/// 3. Composite the frame onto the canvas.
@ -630,7 +644,19 @@ uint32_t APNGImageGenerator::ChunkHeader::ComputeChunkCrc32() {
bool APNGImageGenerator::RenderDefaultImage(const SkImageInfo& info,
void* pixels,
size_t row_bytes) {
SkCodec::Result result = images_[0].codec->getPixels(info, pixels, row_bytes);
APNGImage& frame = images_[0];
SkImageInfo frame_info = frame.codec->getInfo();
if (frame_info.width() > info.width() ||
frame_info.height() > info.height()) {
FML_DLOG(ERROR)
<< "Default image rejected because the destination region (width: "
<< frame_info.width() << ", height: " << frame_info.height()
<< ") is not entirely within the destination surface (width: "
<< info.width() << ", height: " << info.height() << ").";
return false;
}
SkCodec::Result result = frame.codec->getPixels(info, pixels, row_bytes);
if (result != SkCodec::kSuccess) {
FML_DLOG(ERROR) << "Failed to decode the APNG's default/fallback image. "
"SkCodec::Result: "

View File

@ -252,6 +252,26 @@ void main() {
imageData = (await image.toByteData())!;
expect(imageData.getUint32(imageData.lengthInBytes - 4), 0x00000000);
});
test(
'Animated apng frame decode does not crash with invalid destination region',
() async {
final Uint8List data = File(
path.join('flutter', 'lib', 'ui', 'fixtures', 'out_of_bounds.apng'),
).readAsBytesSync();
final ui.Codec codec = await ui.instantiateImageCodec(data);
try {
await codec.getNextFrame();
fail('exception not thrown');
} on Exception catch (e) {
if (impellerEnabled) {
expect(e.toString(), contains('Could not decompress image.'));
} else {
expect(e.toString(), contains('Codec failed'));
}
}
});
}
/// Returns a File handle to a file in the skia/resources directory.