Skip to content

Reject fdAT chunks with data_length < 4 in APNG parser to prevent heap overflow#184301

Closed
mohammadmseet-hue wants to merge 4 commits into
flutter:masterfrom
mohammadmseet-hue:fix/apng-fdat-underflow
Closed

Reject fdAT chunks with data_length < 4 in APNG parser to prevent heap overflow#184301
mohammadmseet-hue wants to merge 4 commits into
flutter:masterfrom
mohammadmseet-hue:fix/apng-fdat-underflow

Conversation

@mohammadmseet-hue

Copy link
Copy Markdown
Contributor

Summary

The APNG parser in image_generator_apng.cc has an integer underflow in DemuxNextImage() that leads to a heap buffer overflow when processing crafted APNG images with malformed fdAT chunks.

The Bug

When converting fdAT chunks to IDAT chunks, the code subtracts 4 from data_length to remove the 4-byte sequence number (line 519):

write_header->set_data_length(c->get_data_length() - 4);

If an fdAT chunk has data_length < 4, this uint32_t subtraction underflows:

set_data_length(0 - 4) → 0xFFFFFFFC (4,294,967,292)

The underflowed value is then passed to memcpy (lines 525-527):

memcpy(write_cursor,
       reinterpret_cast<const uint8_t*>(c) + sizeof(ChunkHeader) + 4,
       write_header->get_data_length());  // copies ~4GB → heap buffer overflow

The same issue exists in the chunk_space calculation (line 477), where chunk_space -= 4 can produce an undersized allocation.

Attack Vector

Any Flutter application that loads images from external sources is affected:

Image.network('https://attacker.com/exploit.apng')

The APNG parser is registered as a default image decoder in ImageGeneratorRegistry and processes all image data with a valid PNG signature + acTL chunk. The trigger flow:

  1. Attacker serves crafted APNG with an fdAT chunk whose data_length is 0
  2. Frame 0 decodes normally (uses IDAT, no fdAT conversion)
  3. Frame 1 triggers DemuxNextImage() on the malicious fdAT chunk
  4. Integer underflow → memcpy with ~4GB length → heap buffer overflow

No user interaction is required beyond the application loading the image.

The Fix

This commit adds data_length < 4 checks in both locations:

  1. chunk_space calculation (line 476): Reject malformed fdAT before computing buffer size
  2. fdAT-to-IDAT conversion (line 515): Reject malformed fdAT before the subtraction

Both checks return std::make_pair(std::nullopt, nullptr) to gracefully fail the decode.

PoC

A valid APNG with this structure triggers the vulnerability:

PNG signature (8 bytes)
IHDR: 1×1 RGBA
acTL: num_frames=2, num_plays=0
fcTL: frame 0 control (1×1, offset 0,0)
IDAT: valid compressed frame 0 data
fcTL: frame 1 control (1×1, offset 0,0)
fdAT: data_length=0, sequence_number only (NO compressed data)
IEND

When loaded by a Flutter app, frame 1 decode triggers the heap overflow.

Related Prior Fixes

Impact

  • Heap buffer overflow — writes up to ~4GB past the allocated buffer
  • Remote trigger — attacker serves malicious APNG from any web server
  • No user interaction beyond the app loading the image
  • Same vulnerability class as CVE-2023-4863 (libwebp) and CVE-2023-41064 (ImageIO)

When converting fdAT chunks to IDAT chunks during APNG demuxing,
DemuxNextImage() subtracts 4 from data_length to remove the fdAT
sequence number. If an fdAT chunk has data_length < 4, this uint32_t
subtraction underflows to ~4GB (0xFFFFFFFC for data_length=0). The
underflowed value is then used as the length argument to memcpy,
causing a heap buffer overflow.

A crafted APNG image served from any HTTP server can trigger this
when loaded by a Flutter application via Image.network() or similar
APIs. The APNG parser is registered as a default image decoder and
runs on all image data with a valid PNG signature + acTL chunk.

This commit adds bounds checks in both the chunk_space calculation
loop and the fdAT-to-IDAT conversion loop to reject fdAT chunks
with insufficient data before the subtraction occurs.

Related prior fixes in this file:
- PR flutter#56928: frame offset bounds check
- PR flutter#57025: integer wrapping fix for the bounds check
Neither addressed the fdAT data_length underflow.
@flutter-dashboard

Copy link
Copy Markdown

It looks like this pull request may not have tests. Please make sure to add tests or get an explicit test exemption before merging.

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. If you believe this PR qualifies for a test exemption, contact "@test-exemption-reviewer" in the #hackers channel in Discord (don't just cc them here, they won't see it!). The test exemption team is a small volunteer group, so all reviewers should feel empowered to ask for tests, without delegating that responsibility entirely to the test exemption group.

@github-actions github-actions Bot added the engine flutter/engine related. See also e: labels. label Mar 29, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces bounds checking for APNG fdAT chunks to prevent potential heap buffer overflows caused by uint32_t underflow. The reviewer pointed out that the validation check is redundantly implemented in two separate loops within the same function and suggested consolidating the logic by removing the unreachable second check.

Comment on lines +516 to +521
// Reject fdAT chunks with insufficient data to contain the
// 4-byte sequence number. Without this check, the subtraction
// underflows uint32_t and causes a heap buffer overflow.
if (c->get_data_length() < 4) {
return std::make_pair(std::nullopt, nullptr);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This entire block (comment and check) is redundant. An identical check is performed for all fdAT chunks within the same image_chunks collection earlier in this function at lines 477-479. Since that first check will cause an early return for any invalid chunk, this second check is unreachable for invalid data. To improve clarity and avoid redundancy, I suggest removing this block. You might consider moving this detailed comment to the first check at line 477 to retain the important context about the vulnerability.

@mohammadmseet-hue

This comment was marked as outdated.

Tests that malformed APNG images with fdAT chunks whose data_length
is less than the required 4 bytes (for the sequence number) do not
cause crashes. Without the bounds check, the uint32_t subtraction
in DemuxNextImage() underflows and causes a heap buffer overflow.

Three test cases:
- fdAT with data_length=0 (underflows to 0xFFFFFFFC)
- fdAT with data_length=2 (underflows to 0xFFFFFFFE)
- fdAT with data_length=8 (valid, should be accepted)
Extend the data_length validation to cover two additional chunk
types that use CastChunkData<T>() without size checks:

- acTL (line 272): reject if data_length < sizeof(AnimationControlChunkData)
  Prevents heap OOB read of num_frames and num_plays fields.

- fcTL (line 419): reject if data_length < sizeof(FrameControlChunkData)
  Prevents heap OOB read of width, height, offsets, delay, and
  blend/dispose operation fields.

Same vulnerability class as the fdAT fix in the previous commit.
@gaaclarke gaaclarke requested a review from jtmcdole March 30, 2026 17:48
Add image_generator_apng_unittests.cc to ui_unittests sources so the
fdAT/fcTL/acTL data_length validation tests are compiled and run.
@mohammadmseet-hue

Copy link
Copy Markdown
Contributor Author

@bdero @jason-simmons Could you review this when you get a chance?

This PR fixes 3 heap buffer overflow vulnerabilities in the APNG parser (image_generator_apng.cc) — all caused by missing data_length validation before accessing chunk data:

  1. fdAT (line 519): data_length < 4 → uint32_t underflow → ~4GB memcpy (WRITE overflow, SIGSEGV confirmed)
  2. fcTL (line 419): data_length < sizeof(FrameControlChunkData) → heap OOB read of width/height/offsets
  3. acTL (line 272): data_length < sizeof(AnimationControlChunkData) → heap OOB read of num_frames/num_plays

Latest commit wires the unit tests into BUILD.gn (was missing before). All 3 tests + a positive validation test included.

@jason-simmons

Copy link
Copy Markdown
Member

Thank you for your contribution! Sorry for the delay in reviewing.

I've created a new PR containing your branch plus some additional changes: #186700

gaaclarke pushed a commit to gaaclarke/flutter that referenced this pull request May 21, 2026
…ing malformed fdAT chunks (flutter#186700)

An fdAT (frame data) chunk in an APNG should contain a sequence number
followed by image data.  The APNG decoder needs to reject invalid fdAT
chunks that do not have the expected contents.

Based on flutter#184301 and
flutter#183180

Fixes flutter#183179

---------

Co-authored-by: 1seal <[email protected]>
Co-authored-by: mohammadmseet-hue <[email protected]>
@jason-simmons

Copy link
Copy Markdown
Member

The #186700 version of this fix has been merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

engine flutter/engine related. See also e: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants