Animated image frame-count onbegrensd (dimensie-cap vangt één frame, niet de animatie) #1354

Closed
opened 2026-08-07 22:17:42 +00:00 by brenno · 0 comments
Owner

Bevinding

CappedImage (lib/utils/image_limits.dart) capt de decode-dimensies per
frame naar kMaxImageDecodeDimension = 4096. Dat dicht de klassieke
decode-bom: een 30000×30000 PNG van een paar KB decodeert niet meer tot
gigabytes.

Maar er is geen frame-count-limiet. CappedImage gebruikt
MultiFrameImageStreamCompleter, die alle frames van een animated GIF/WebP/
APNG decodeert. Een GIF met 10.000 frames à 4096×4096×4 bytes = ~64 MiB per
frame = honderden GiB totaal. De dimensie-cap vangt één frame, niet de
animatie als geheel.

Dit is hetzelfde klasse probleem als de decode-bom die
kMaxImageDecodeDimension al oplost — maar dan voor de tijdsas in plaats van
de ruimteas.

Locatie

  • lib/utils/image_limits.dart: CappedImage._decode gebruikt
    getTargetSize voor dimensie-capping, maar geen frame-limiet.
  • MultiFrameImageStreamCompleter (Flutter engine) decodeert alle frames.

Oplossingsrichting

Een maxFrames-limiet op CappedImage, of een pre-check die het frame-count
uit de codec haalt vóór volledige decode. Flutter's ui.Codec heeft een
numFrames-property na instantiateImageCodec, dus een optie is:

  1. Eerst de codec instantiëren (goedkoop — leest alleen de header).
  2. numFrames checken tegen een limiet (bv. 256 — ruim voor elke echte
    animatie, begrenst een bom).
  3. Bij over-cap: alleen het eerste frame tonen (zelfde trade-off als de
    dimensie-cap al maakt voor over-cap afbeeldingen: "shows its first frame,
    a rare, safe trade-off" per het bestaande commentaar).

Inschatting: ~20-30 regels, plus een test met een synthetic high-frame-count
GIF.

Herkomst

Gevonden tijdens security research naar defense-in-depth voor OciDeck. De
bestaande decode-bom-verdediging (kMaxImageDecodeDimension) is volwassen voor
stilstaande afbeeldingen, maar de animatie-as staat open.

## Bevinding `CappedImage` (`lib/utils/image_limits.dart`) capt de decode-dimensies per frame naar `kMaxImageDecodeDimension = 4096`. Dat dicht de klassieke decode-bom: een 30000×30000 PNG van een paar KB decodeert niet meer tot gigabytes. Maar er is **geen frame-count-limiet**. `CappedImage` gebruikt `MultiFrameImageStreamCompleter`, die alle frames van een animated GIF/WebP/ APNG decodeert. Een GIF met 10.000 frames à 4096×4096×4 bytes = ~64 MiB per frame = honderden GiB totaal. De dimensie-cap vangt één frame, niet de animatie als geheel. Dit is hetzelfde klasse probleem als de decode-bom die `kMaxImageDecodeDimension` al oplost — maar dan voor de tijdsas in plaats van de ruimteas. ## Locatie - `lib/utils/image_limits.dart`: `CappedImage._decode` gebruikt `getTargetSize` voor dimensie-capping, maar geen frame-limiet. - `MultiFrameImageStreamCompleter` (Flutter engine) decodeert alle frames. ## Oplossingsrichting Een `maxFrames`-limiet op `CappedImage`, of een pre-check die het frame-count uit de codec haalt vóór volledige decode. Flutter's `ui.Codec` heeft een `numFrames`-property na `instantiateImageCodec`, dus een optie is: 1. Eerst de codec instantiëren (goedkoop — leest alleen de header). 2. `numFrames` checken tegen een limiet (bv. 256 — ruim voor elke echte animatie, begrenst een bom). 3. Bij over-cap: alleen het eerste frame tonen (zelfde trade-off als de dimensie-cap al maakt voor over-cap afbeeldingen: "shows its first frame, a rare, safe trade-off" per het bestaande commentaar). Inschatting: ~20-30 regels, plus een test met een synthetic high-frame-count GIF. ## Herkomst Gevonden tijdens security research naar defense-in-depth voor OciDeck. De bestaande decode-bom-verdediging (`kMaxImageDecodeDimension`) is volwassen voor stilstaande afbeeldingen, maar de animatie-as staat open.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
LibreKAT/Ocideck#1354
No description provided.