Skip to content

fix: harden latent libeot-parity gaps (v1.5.1) - #6

Merged
ChristopherVR merged 2 commits into
mainfrom
fix/latent-hardening
Jul 17, 2026
Merged

fix: harden latent libeot-parity gaps (v1.5.1)#6
ChristopherVR merged 2 commits into
mainfrom
fix/latent-hardening

Conversation

@ChristopherVR

Copy link
Copy Markdown
Owner

Follow-up to the #5 review — closes the low-severity items that were latent or dead-code but still worth fixing for parity with libeot.

Fixes

  • decompressMtx aliased the caller's buffer. The uncompressed passthrough returned the caller's own array while a comment claimed it copied. libeot (writeFontFile.c) always copies — now so do we, so mutating the result can't write back into the caller's input.
  • LZCOMP decode-overrun guard. A final copy item can drive pos past outLen; libeot raises ERR_LZCOMP_Decode_bounds here rather than returning the trailing garbage the overshoot produced. Now throws EotError(MtxError).
  • Stream.checksumU32 bounded on size, not endPos. An unaligned span folded in bytes past endPos, yielding a different checksum than libeot. Now bounds strictly on endPos and validates the span. (Dead code today — inlined elsewhere — but exported and unit-tested.)
  • Stream.readNBits / writeU24 range checks. readNBits now rejects widths > 32 and returns unsigned (a 32-bit read with the top bit set was surfacing as negative); writeU24 rejects out-of-range values instead of silently truncating.
  • BitIO.readValue returns unsigned. Same signed-shift issue for a full 32-bit read.
  • ahuff.bitsUsed(0) returns 1 to match libeot's BitsUsed (defensive; never reached in practice — range is always 8 or ≥267).

None of these changed a valid-input decode path; they harden corrupt-input handling and exported-utility correctness.

Deferred (tracked separately)

The genuinely cosmetic / by-design remainder is captured in an issue rather than changed here: mid-byte boundary enforcement, the malformed-input bbox int16 wrap, grow-vs-error on *ThroughReserve, and the silent hdmx/VDMX skip.

Verification

  • bun run typecheck, bun run build — pass
  • bun run test214 passing (up from 209; +5 regression tests)

Patch release 1.5.1.

🤖 Generated with Claude Code

ChristopherVR and others added 2 commits July 18, 2026 06:58
Follow-up to the v1.5.0 review — the low-severity items that were latent or
dead-code but worth closing:

- mtx-decompress: the uncompressed passthrough returned the caller's own
  buffer while a comment claimed it copied. libeot (writeFontFile.c) always
  copies; return an owned copy so mutating the result never writes back into
  the caller's array.
- lzcomp: add the ERR_LZCOMP_Decode_bounds equivalent — a final copy item can
  drive `pos` past `outLen`, and libeot errors here rather than returning the
  trailing garbage. Throws EotError(MtxError) on overrun.
- stream: checksumU32 now bounds on endPos (not size), so an unaligned range
  no longer folds in bytes past endPos, and it validates the span; readNBits
  rejects widths > 32 and returns unsigned; writeU24 rejects out-of-range
  values instead of silently truncating.
- bitio: readValue returns unsigned (a 32-bit read with the top bit set was
  surfacing as negative).
- ahuff: bitsUsed(0) returns 1 to match libeot's BitsUsed (defensive; never
  reached in practice).

All exercised by new unit tests; 214 passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ChristopherVR
ChristopherVR merged commit 001c1a0 into main Jul 17, 2026
1 check passed
@ChristopherVR
ChristopherVR deleted the fix/latent-hardening branch July 17, 2026 21:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant