font: avoid panic on malformed composite glyphs - #269
Conversation
|
@benoitkugler Is it better to guard the consumers, or to alter |
|
@ChrisJr404 Thank you for the bug report !
I indeed think it would be a bit cleaner to actually enforce the phantom points creation. It will only penalize cases which happen barely in practice, and keep the code simpler. |
getPointsForGlyph is assumed to always return at least the phantom points, so glyphDataFromGlyf and getGlyfPoints slice them off with points[:len(points)-phantomCount]. That assumption breaks for a composite glyph whose component cannot be resolved (an out-of-range glyph index, or nesting beyond maxCompositeNesting): the recursion returned before appending the phantom points, so the glyph yielded fewer than phantomCount points and the slice bound went negative, panicking with 'slice bounds out of range [:-4]'. Enforce the invariant in the producer instead of the callers: the early-return path in getPointsForGlyphRec now contributes the phantom points too, so a missing or too-deeply-nested glyph yields an empty-but-valid outline. With the guarantee upheld, the redundant length checks at the call sites and the internal component check are removed. Well-formed fonts are unaffected.
4ff0642 to
7233e78
Compare
|
Thanks, that makes sense to me too. I've reworked it so the guarantee lives in |
benoitkugler
left a comment
There was a problem hiding this comment.
This looks great to me, thank you for this nice fix !
Problem
Face.GlyphData/Face.GlyphDataOutlinepanic withslice bounds out of range [:-4]on certain malformed TrueType fonts.getPointsForGlyphis documented to always return the four phantom points, soglyphDataFromGlyfslices them off unconditionally:That invariant does not actually hold for composite glyphs. In
getPointsForGlyphRec, when a component resolves to fewer thanphantomCountpoints (e.g. it references an out-of-range glyph index, or nesting exceedsmaxCompositeNesting), the function returns early before appending the phantom points:So a composite glyph whose (first) component is unresolvable makes
getPointsForGlyphreturn 0–3 points, andpoints[:len(points)-phantomCount]slices with a negative bound and panics. Any caller passing untrusted font data tofont.ParseTTFand then reading glyph outlines can be crashed by a crafted font.getGlyfPoints(reached fromGlyphExtentson variable fonts) has the same unchecked assumption atallPoints[len(allPoints)-phantomCount:].Fix
Guard both consumers: if
getPointsForGlyphreturns fewer thanphantomCountpoints, treat the glyph as empty instead of slicing out of range. This matches the existing behaviour for glyphs with no outline (a blank glyph), and leaves well-formed fonts completely unaffected.Test
Added
TestGlyphDataMalformedComposite, which builds a composite glyph referencing an out-of-range component and checks that the glyph-data accessors return an empty outline rather than panicking. It panics without the fix and passes with it. Found by fuzzingParseTTF+ glyph-outline extraction; the original crashing font and a hand-built minimal reproducer both go through the same code path.