Skip to content

Malformed JPEG and TGA headers escape as IndexOutOfRangeException instead of InvalidImageDataException #390

Description

@matt-edmondson

What happens

ImageDecoder's documented contract (ImGui.App/Images/ImageDecoder.cs:22-23, restated in CLAUDE.md) is that "anything unrecognised or malformed raises InvalidImageDataException naming what was found". JpegDecoder.Decode and TgaDecoder.Decode each declare only that exception.

Two header fields are used as array indices without validation.

JPEG progressive spectral selectors. ImGui.App/Images/JpegDecoder.cs:542-543 reads them straight out of the SOS header as raw bytes (0–255):

int spectralStart = header[1 + (scanComponentCount * 2)];
int spectralEnd = header[2 + (scanComponentCount * 2)];

and clamps them only for baseline (if (!progressive) at :549). ZigZag (:44) has 64 entries, and the progressive paths index it unbounded — :781 block[ZigZag[k]], :794 RefineCoefficient(ref bits, block, ZigZag[band], bit), :834 int index = ZigZag[k].

TGA colour-map entry size. ImGui.App/Images/TgaDecoder.cs:62 reads colorMapEntryBits and never validates it — note that pixelBits immediately below is validated at :80. Line 89 computes entryBytes = (colorMapEntryBits + 7) / 8 and line 99 hands a span of that length to ReadPixel, whose default: branch (:245-250) reads source[2] and source[3].

Failure scenario

Built ImGui.App and called the public ImageDecoder.Decode:

control Ss=1 Se=5 Ah=0 Al=0 : decoded 8x8
Ss=64 Se=64 Ah=1 Al=0       : System.IndexOutOfRangeException
Ss=64 Se=64 Ah=0 Al=0       : System.IndexOutOfRangeException
TGA control entrySize=24    : decoded 2x2
TGA entrySize=0             : System.IndexOutOfRangeException
TGA entrySize=12            : System.IndexOutOfRangeException

Inputs were minimal hand-built files: SOI/DQT/SOF2/DHT/SOS with Ss=Se=64; and an 18-byte TGA header with colorMapType=1, colorMapLength=2, colorMapEntryBits=0.

Why it matters

InvalidImageDataException is the exception type consumers are told to catch around texture loading. A truncated download or a corrupt icon takes down the caller instead of being reported as a bad image.

This matters more than usual here because the decoders are in-house specifically so the package carries no imaging dependency — the contract is the whole interface to them.

Missing coverage

tests/ImGui.App.Tests/Images/JpegDecoderTests.cs has two malformed-input tests (:107, :118) and neither reaches a scan header.

Suggested fix

  • In ReadScan, reject spectralStart > 63 || spectralEnd > 63 || spectralEnd < spectralStart — and, per the spec, spectralStart == 0 requiring spectralEnd == 0 — with a named InvalidImageDataException.
  • In TgaDecoder.Decode, validate colorMapEntryBits is 8 or 15 or 16 or 24 or 32 alongside the existing pixelBits check at :80.

Activity

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

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions