The sending path was careful — read the bounds first, then decode sampled —
but the two places that draw an image RECEIVED from a peer called
`decodeByteArray` with no options at all, decoding at full size. Inbound
attachments are capped at 12 MiB, and 12 MiB of a suitably chosen JPEG is a few
hundred megabytes of ARGB_8888 once expanded: OutOfMemoryError.
The effect is not one crash. The decode happens every time the thread or the
story is opened, so a single message wedges that conversation permanently from
the outside, and the victim cannot get past it to delete anything.
Both render paths now go through `ImagePrep.decodeBounded`, which reads the
bounds before allocating — the same thing the send path has always done. The
story composer's own draft goes through it too: a 50MP photo is just as much
bitmap whoever picked it.
⚠️ A byte-count cap does not bound this. The 12 MiB limit was already in place
and is exactly what made the attack convenient — the constraint that matters is
the decoded dimensions, not the compressed size.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>