Skip to content

Harden the movie decoder against malformed ANIM data - #565

Merged
bibendovsky merged 2 commits into
bibendovsky:wipfrom
AdamCoulterOz:fix/movie-playback-hardening
Aug 4, 2026
Merged

Harden the movie decoder against malformed ANIM data#565
bibendovsky merged 2 commits into
bibendovsky:wipfrom
AdamCoulterOz:fix/movie-playback-hardening

Conversation

@AdamCoulterOz

Copy link
Copy Markdown

From a correctness review of wip: the movie reader trusted two values it read out of the file.

  • Validate the frame record size — an oversized one hands out a payload that runs past the end of the file, and a negative one rewinds the reader onto the very same frame, so the movie never ends
  • Keep an ANIM chunk inside the screen buffer — a chunk offset reaching past the last pixel wrote past the end of the buffer

🤖 Generated with Claude Code

@AdamCoulterOz
AdamCoulterOz force-pushed the fix/movie-playback-hardening branch from ec6a3ae to c611639 Compare August 4, 2026 03:40
AdamCoulterOz and others added 2 commits August 4, 2026 13:55
The record size of an ANIM frame is read straight from the asset and then
trusted twice over: AN_PAGE hands it to a sub-reader as the size of the
frame payload, and every handler advances the reader by it once the frame
is done.

Neither use was checked. A record size larger than the bytes left in the
file gives the sub-reader a length that reaches past the end of the buffer
the file was read into, and the chunk loop then walks - and blits - heap
memory beyond it. A negative one is worse: the reader skips backwards, so
the ten bytes of frame header are re-read on the next iteration and the
same frame plays forever with no way out, because neither the sound nor
the pause handler polls for input.

Check it once where it is read instead. The sound and pause handlers now
also require their two byte payload to fit inside the record rather than
merely inside the file, so a short record can no longer borrow the first
bytes of the following frame header.

All four shipped animations pass the new check unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An AN_PAGE chunk carries a sixteen bit offset and a sixteen bit length,
both taken from the asset as is. The only check on them bounds the source
read; nothing bounds the destination. VL_Plot does no clamping at all, so
a chunk ending past pixel 63999 writes attacker chosen bytes past the end
of both the UI buffer and the mask array - a chunk can reach pixel 131070,
more than twice the screen.

Reject such a chunk where the rest of the frame is validated. The blitter
stays a plain blitter, and all four shipped animations stay well inside
the screen, the furthest reaching pixel 52724.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@AdamCoulterOz
AdamCoulterOz force-pushed the fix/movie-playback-hardening branch from c611639 to 5aef667 Compare August 4, 2026 04:08
@bibendovsky bibendovsky added the bug label Aug 4, 2026
@bibendovsky bibendovsky added this to the v1.4.0 milestone Aug 4, 2026
@bibendovsky
bibendovsky merged commit b9a7ad3 into bibendovsky:wip Aug 4, 2026
6 checks passed
@bibendovsky

Copy link
Copy Markdown
Owner

Thank you!

@AdamCoulterOz
AdamCoulterOz deleted the fix/movie-playback-hardening branch August 4, 2026 10:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants