tOS PNG Decoder — Heap Out-of-Bounds Read via Integer Overflow
In png_decode of png.c, the row stride is computed as width multiplied by channel count using 32-bit arithmetic before being validated against the maximum allowed image size. An attacker-controlled width value in the IHDR chunk can cause this multiplication to overflow, wrapping the computed stride to a small value that passes the size check, while the pixel buffer is subsequently allocated using the wrapped stride. A later conversion loop still iterates using the original, un-wrapped width when indexing into that undersized buffer, resulting in an out-of-bounds heap read. This is reachable by decoding any attacker-supplied PNG file (e.g. via an image viewer, wallpaper loader, or icon loader).
Having seen the same 'blindly trust a value from a file/packet' pattern repeat across ASI-2026-0001, 0002, and 0003, we decided to look for it in image parsing as well. While auditing kernel/display, we found that the stride calculation in png_decode had no protection at all against integer overflow.
The stride value was computed as width * channels using 32-bit arithmetic and checked against the maximum allowed image size before the pixel buffer was allocated. Because the width value from the IHDR chunk is attacker-controlled, this multiplication could overflow, wrapping the computed stride to a value small enough to pass the size check. The pixel buffer was then allocated using this wrapped, undersized stride. However, a later conversion loop still used the original, un-wrapped width as its iteration bound when indexing into that undersized buffer.
An attacker crafts a PNG file with a width value in the IHDR chunk specifically chosen to overflow the 32-bit stride calculation. When the victim opens this file through any path that calls png_decode — an image viewer, paint app, wallpaper loader, or icon loader — the parser reads past the bounds of the undersized allocated buffer, indexing using the original, un-overflowed width. This results in an out-of-bounds heap read and potential disclosure of adjacent heap memory.
The stride/raw-capacity calculation was performed and bounds-checked in 32-bit before allocation, allowing the multiplication to overflow and silently pass the MAX_RAW_SIZE check with an undersized stride, while a later loop still used the real, un-wrapped width as its iteration bound.
The stride/raw-capacity calculation was moved to 64-bit arithmetic and validated before any narrowing back to 32-bit, so an overflowing width can no longer produce a mismatched stride.
CWE-190, CWE-125
Artfical DT Developer Talha Berk Arslan