@@ -150,16 +150,83 @@ impl FromData for LookupFlags {
150150
151151pub ( crate ) fn parse_extension_lookup < ' a , T : ' a > (
152152 data : & ' a [ u8 ] ,
153+ ext_kind : u16 ,
153154 parse : impl FnOnce ( & ' a [ u8 ] , u16 ) -> Option < T > ,
154155) -> Option < T > {
155156 let mut s = Stream :: new ( data) ;
156157 let format = s. read :: < u16 > ( ) ?;
157158 match format {
158159 1 => {
159160 let kind = s. read :: < u16 > ( ) ?;
161+ // An Extension subtable must reference a lookup type other than the
162+ // extension type itself. The OpenType spec requires extensionLookupType
163+ // to "be set to any lookup type other than the extension lookup type".
164+ // Without this check, a self-referential extension (e.g. kind == ext_kind
165+ // with extensionOffset == 0) makes `parse` re-enter this function forever
166+ // and overflow the stack on malformed fonts.
167+ if kind == ext_kind {
168+ return None ;
169+ }
160170 let offset = s. read :: < Offset32 > ( ) ?. to_usize ( ) ;
161171 parse ( data. get ( offset..) ?, kind)
162172 }
163173 _ => None ,
164174 }
165175}
176+
177+ #[ cfg( test) ]
178+ mod tests {
179+ use super :: LookupSubtable ;
180+ use crate :: gpos:: PositioningSubtable ;
181+ use crate :: gsub:: SubstitutionSubtable ;
182+
183+ // A GSUB Extension subtable (type 7) whose inner extensionLookupType is
184+ // itself 7 and whose extensionOffset is 0 references its own bytes. Before
185+ // the recursion guard this made the parser re-enter itself forever and
186+ // overflow the stack. It must now be rejected without recursing.
187+ #[ test]
188+ fn gsub_self_referential_extension_is_rejected ( ) {
189+ let data: & [ u8 ] = & [
190+ 0x00 , 0x01 , // format = 1
191+ 0x00 , 0x07 , // extensionLookupType = 7 (nested extension, invalid)
192+ 0x00 , 0x00 , 0x00 , 0x00 , // extensionOffset = 0 (points at self)
193+ ] ;
194+ assert ! ( SubstitutionSubtable :: parse( data, 7 ) . is_none( ) ) ;
195+ }
196+
197+ // Same self-reference for a GPOS Extension subtable (type 9).
198+ #[ test]
199+ fn gpos_self_referential_extension_is_rejected ( ) {
200+ let data: & [ u8 ] = & [
201+ 0x00 , 0x01 , // format = 1
202+ 0x00 , 0x09 , // extensionLookupType = 9 (nested extension, invalid)
203+ 0x00 , 0x00 , 0x00 , 0x00 , // extensionOffset = 0 (points at self)
204+ ] ;
205+ assert ! ( PositioningSubtable :: parse( data, 9 ) . is_none( ) ) ;
206+ }
207+
208+ // A well-formed GSUB Extension subtable wrapping a real Single Substitution
209+ // must still resolve to its inner subtable: the guard rejects only the
210+ // extension-of-extension case, not legitimate extensions.
211+ #[ test]
212+ fn gsub_valid_extension_still_resolves ( ) {
213+ let data: & [ u8 ] = & [
214+ // Extension subtable
215+ 0x00 , 0x01 , // format = 1
216+ 0x00 , 0x01 , // extensionLookupType = 1 (SingleSubst)
217+ 0x00 , 0x00 , 0x00 , 0x08 , // extensionOffset = 8
218+ // Inner SingleSubstitution, format 1 (at offset 8)
219+ 0x00 , 0x01 , // substFormat = 1
220+ 0x00 , 0x06 , // coverageOffset = 6 (relative to inner)
221+ 0x00 , 0x00 , // deltaGlyphID = 0
222+ // Coverage table, format 1 (at inner offset 6)
223+ 0x00 , 0x01 , // coverageFormat = 1
224+ 0x00 , 0x01 , // glyphCount = 1
225+ 0x00 , 0x41 , // glyphArray[0] = 65
226+ ] ;
227+ assert ! ( matches!(
228+ SubstitutionSubtable :: parse( data, 7 ) ,
229+ Some ( SubstitutionSubtable :: Single ( _) )
230+ ) ) ;
231+ }
232+ }
0 commit comments