Plan 9: Emoji

 

Noto Emoji Font

Note: Noto Color emoji needs Color Bitmap Data (CBDT) support.

 

Four independent bugs, spanning truetypefs.c, hint.c, and head.c:

1. assert(gs[i] != nil) crash in compilesub (truetypefs.c)
ttffindchar returning glyph 0 for a rune inside a declared cmap segment (a genuine gap, not just the NUL special-case) skipped the retry-via-notdef path entirely. If the font's own .notdef then also failed to rasterize, the assert took the whole fileserver down. Fixed by replacing the assert with a synthesized blank placeholder as the last resort in the fallback chain.

2. No bounds checking on glyph geometry before blit() (truetypefs.c)
blit() has zero validation of its own; compilesub sized the destination strip from the font's declared ascentpx + descentpx but never checked that an individual glyph's actual rendered width/height/vertical position fit inside that buffer. A decorative font (architectsdaughter) reporting ink extents past its declared metrics caused blit to write past the buffer, corrupting the heap. Fixed with badglyph(), gating every glyph — primary TTF, bitmap fallback, and notdef — before it's ever blitted.

3. Unbounded native recursion in the hinting interpreter (hint.c)
h_call/h_loopcall implement the TrueType CALL/LOOPCALL instructions via genuine recursive C calls to run(), with an h->level counter that was tracked but never actually checked against any bound. A font with a deeply-nested fpgm/prep program (Noto Sans; architectsdaughter's trivial/absent hinting never touched this) blew the real process stack — this is what produced the sys: trap/suicide crashes and, separately, the reported "stack overflow" text. Fixed with a MAXLEVEL cap (64) in both instructions, failing cleanly via the existing herror()/longjmp path instead of faulting.

4. assert() on the interpreter's own operand-stack bounds in push()/pop() (hint.c)
Same failure class as #1, different subsystem: a bytecode-driven condition (NPUSHB/NPUSHW pushing more values than the stack's declared capacity) wired to abort() instead of the graceful herror() path already used everywhere else in the file. Fixed by converting both to bounds checks that call herror().

5. maxp.maxStackElements trusted at face value (head.c)
Once #4 stopped crashing, it surfaced a real data problem underneath: Noto Sans's own declared stack-size field is smaller than what its hinting program actually needs — a well-documented unreliability in real-world fonts' maxp tables, not a parsing bug (the field order was verified correct against the TrueType spec). Fixed by flooring u->maxStackElements to 512 right after parsing, so both the stack allocation and the runtime bound check in runpg — which both read that same field — get the padding for free from one edit.

Net effect: architectsdaughter was never actually broken by any of this — it just never had a hinting program complex enough to reach bugs #3–5, and never had a glyph malformed enough to reach #2. Noto Sans routed through all four in sequence, one crash at a time, until it renders cleanly with #5.

 

Comments

Popular posts from this blog

Plan 9 : The Infinity Notebook

Plan 9: Quick Boot with UEFI

Comics Builder in GNU Emacs