Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added lib/ui/fixtures/out_of_bounds.apng
Binary file not shown.
Binary file added lib/ui/fixtures/out_of_bounds_wrapping.apng
Binary file not shown.
2 changes: 1 addition & 1 deletion lib/ui/painting/image_decoder_no_gl_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,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
Expand Down
36 changes: 35 additions & 1 deletion lib/ui/painting/image_generator_apng.cc
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,28 @@ bool APNGImageGenerator::GetPixels(const SkImageInfo& info,
<< ") of APNG due to the frame missing data (frame_info).";
return false;
}
if (
// Check for unsigned integer wrapping for
// frame.{x|y}_offset + frame_info.{width|height}().
frame.x_offset >
std::numeric_limits<uint32_t>::max() - frame_info.width() ||
frame.y_offset >
std::numeric_limits<uint32_t>::max() - frame_info.height() ||

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.
Expand Down Expand Up @@ -630,7 +652,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: "
Expand Down
40 changes: 40 additions & 0 deletions testing/dart/codec_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,46 @@ 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'));
}
}
});

test(
'Animated apng frame decode does not crash with invalid destination region and bounds wrapping',
() async {
final Uint8List data = File(
path.join('flutter', 'lib', 'ui', 'fixtures', 'out_of_bounds_wrapping.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.
Expand Down
Loading