@@ -27,7 +27,9 @@ use crate::unicode::VariationSelector;
2727use crate :: { tag, GlyphId } ;
2828
2929const SUBST_RECURSION_LIMIT : usize = 2 ;
30- const MAX_GLYPHS : usize = 10_000 ;
30+ // Matches Harfbuzz:
31+ // https://github.com/harfbuzz/harfbuzz/blob/8062c372590980d36d5b4cc720d33dca2662c56e/src/hb-limits.hh#L32
32+ pub ( crate ) const MAX_GLYPHS_FACTOR : usize = 256 ;
3133
3234#[ derive( Copy , Clone , Eq , PartialEq , Debug ) ]
3335pub struct FeatureInfo {
@@ -294,6 +296,7 @@ pub fn gsub_apply_lookup<T: GlyphData>(
294296 feature_tag : u32 ,
295297 opt_alternate : Option < usize > ,
296298 glyphs : & mut Vec < RawGlyph < T > > ,
299+ max_glyphs : usize ,
297300 start : usize ,
298301 mut length : usize ,
299302 pred : impl Fn ( & RawGlyph < T > ) -> bool ,
@@ -313,7 +316,7 @@ pub fn gsub_apply_lookup<T: GlyphData>(
313316 let mut i = start;
314317 while i < start + length {
315318 if match_type. match_glyph ( opt_gdef_table, & glyphs[ i] ) && pred ( & glyphs[ i] ) {
316- match multiplesubst ( subtables, i, glyphs) ? {
319+ match multiplesubst ( subtables, i, glyphs, max_glyphs ) ? {
317320 Some ( replace_count) => {
318321 i += replace_count;
319322 length += replace_count;
@@ -364,6 +367,7 @@ pub fn gsub_apply_lookup<T: GlyphData>(
364367 match_type,
365368 i,
366369 glyphs,
370+ max_glyphs,
367371 ) ? {
368372 Some ( ( input_length, changes) ) => {
369373 i += input_length;
@@ -390,6 +394,7 @@ pub fn gsub_apply_lookup<T: GlyphData>(
390394 match_type,
391395 i,
392396 glyphs,
397+ max_glyphs,
393398 ) ? {
394399 Some ( ( input_length, changes) ) => {
395400 i += input_length;
@@ -460,11 +465,18 @@ fn multiplesubst<T: GlyphData>(
460465 subtables : & [ MultipleSubst ] ,
461466 i : usize ,
462467 glyphs : & mut Vec < RawGlyph < T > > ,
468+ max_glyphs : usize ,
463469) -> Result < Option < usize > , ParseError > {
464470 match multiplesubst_would_apply ( subtables, i, glyphs) ? {
465471 Some ( sequence_table) => {
466- if sequence_table. substitute_glyphs . len ( ) + glyphs. len ( ) >= MAX_GLYPHS {
467- return Err ( ParseError :: LimitExceeded ) ;
472+ if sequence_table. substitute_glyphs . len ( ) + glyphs. len ( ) >= max_glyphs {
473+ // The Unicode text rendering tests say that shaping should stop when the limit is
474+ // reached, but not fail:
475+ //
476+ // "If your implementation is immune to this attack, it should neither crash nor
477+ // hang when rendering lol with this font. Instead, your implementation should stop
478+ // executing once its internal buffer has reached a size limit."
479+ return Ok ( Some ( 0 ) ) ;
468480 }
469481
470482 if !sequence_table. substitute_glyphs . is_empty ( ) {
@@ -590,6 +602,7 @@ fn contextsubst<T: GlyphData>(
590602 match_type : MatchType ,
591603 i : usize ,
592604 glyphs : & mut Vec < RawGlyph < T > > ,
605+ max_glyphs : usize ,
593606) -> Result < Option < ( usize , isize ) > , ParseError > {
594607 match contextsubst_would_apply ( opt_gdef_table, subtables, match_type, i, glyphs) ? {
595608 Some ( subst) => apply_subst_context (
@@ -602,6 +615,7 @@ fn contextsubst<T: GlyphData>(
602615 & subst,
603616 i,
604617 glyphs,
618+ max_glyphs,
605619 ) ,
606620 None => Ok ( None ) ,
607621 }
@@ -637,6 +651,7 @@ fn chaincontextsubst<T: GlyphData>(
637651 match_type : MatchType ,
638652 i : usize ,
639653 glyphs : & mut Vec < RawGlyph < T > > ,
654+ max_glyphs : usize ,
640655) -> Result < Option < ( usize , isize ) > , ParseError > {
641656 match chaincontextsubst_would_apply ( opt_gdef_table, subtables, match_type, i, glyphs) ? {
642657 Some ( subst) => apply_subst_context (
@@ -649,6 +664,7 @@ fn chaincontextsubst<T: GlyphData>(
649664 & subst,
650665 i,
651666 glyphs,
667+ max_glyphs,
652668 ) ,
653669 None => Ok ( None ) ,
654670 }
@@ -702,6 +718,7 @@ fn apply_subst_context<T: GlyphData>(
702718 subst : & SubstContext < ' _ > ,
703719 i : usize ,
704720 glyphs : & mut Vec < RawGlyph < T > > ,
721+ max_glyphs : usize ,
705722) -> Result < Option < ( usize , isize ) > , ParseError > {
706723 let mut changes = 0 ;
707724 let len = match match_type. find_nth (
@@ -724,6 +741,7 @@ fn apply_subst_context<T: GlyphData>(
724741 usize:: from ( * subst_lookup_index) ,
725742 feature_tag,
726743 glyphs,
744+ max_glyphs,
727745 i,
728746 ) ? {
729747 Some ( changes0) => changes += changes0,
@@ -754,6 +772,7 @@ fn apply_subst<T: GlyphData>(
754772 lookup_index : usize ,
755773 feature_tag : u32 ,
756774 glyphs : & mut Vec < RawGlyph < T > > ,
775+ max_glyphs : usize ,
757776 index : usize ,
758777) -> Result < Option < isize > , ParseError > {
759778 let lookup = lookup_list. lookup_cache_gsub ( gsub_cache, lookup_index) ?;
@@ -767,10 +786,12 @@ fn apply_subst<T: GlyphData>(
767786 singlesubst ( subtables, feature_tag, & mut glyphs[ i] ) ?;
768787 Ok ( Some ( 0 ) )
769788 }
770- SubstLookup :: MultipleSubst ( ref subtables) => match multiplesubst ( subtables, i, glyphs) ? {
771- Some ( replace_count) => Ok ( Some ( ( replace_count as isize ) - 1 ) ) ,
772- None => Ok ( None ) ,
773- } ,
789+ SubstLookup :: MultipleSubst ( ref subtables) => {
790+ match multiplesubst ( subtables, i, glyphs, max_glyphs) ? {
791+ Some ( replace_count) => Ok ( Some ( ( replace_count as isize ) - 1 ) ) ,
792+ None => Ok ( None ) ,
793+ }
794+ }
774795 SubstLookup :: AlternateSubst ( ref subtables) => {
775796 alternatesubst ( subtables, 0 , & mut glyphs[ i] ) ?;
776797 Ok ( Some ( 0 ) )
@@ -793,6 +814,7 @@ fn apply_subst<T: GlyphData>(
793814 match_type,
794815 i,
795816 glyphs,
817+ max_glyphs,
796818 ) ? {
797819 Some ( ( _length, change) ) => Ok ( Some ( change) ) ,
798820 None => Ok ( None ) ,
@@ -813,6 +835,7 @@ fn apply_subst<T: GlyphData>(
813835 match_type,
814836 i,
815837 glyphs,
838+ max_glyphs,
816839 ) ? {
817840 Some ( ( _length, change) ) => Ok ( Some ( change) ) ,
818841 None => Ok ( None ) ,
@@ -1077,6 +1100,7 @@ pub fn apply(
10771100 num_glyphs : u16 ,
10781101 glyphs : & mut Vec < RawGlyph < ( ) > > ,
10791102) -> Result < ( ) , ShapingError > {
1103+ let max_glyphs = glyphs. len ( ) . saturating_mul ( MAX_GLYPHS_FACTOR ) ;
10801104 match features {
10811105 Features :: Custom ( features_list) => gsub_apply_custom (
10821106 gsub_cache,
@@ -1087,6 +1111,7 @@ pub fn apply(
10871111 tuple,
10881112 num_glyphs,
10891113 glyphs,
1114+ max_glyphs,
10901115 ) ,
10911116 Features :: Mask ( feature_mask) => gsub_apply_default (
10921117 dotted_circle_index,
@@ -1098,6 +1123,7 @@ pub fn apply(
10981123 tuple,
10991124 num_glyphs,
11001125 glyphs,
1126+ max_glyphs,
11011127 ) ,
11021128 }
11031129}
@@ -1111,6 +1137,7 @@ fn gsub_apply_custom(
11111137 tuple : Option < Tuple < ' _ > > ,
11121138 num_glyphs : u16 ,
11131139 glyphs : & mut Vec < RawGlyph < ( ) > > ,
1140+ max_glyphs : usize ,
11141141) -> Result < ( ) , ShapingError > {
11151142 let gsub_table = & gsub_cache. layout_table ;
11161143 if let Some ( script) = gsub_table. find_script_or_default ( script_tag) ? {
@@ -1137,6 +1164,7 @@ fn gsub_apply_custom(
11371164 tag:: RVRN ,
11381165 None ,
11391166 glyphs,
1167+ max_glyphs,
11401168 0 ,
11411169 glyphs. len ( ) ,
11421170 |_| true ,
@@ -1156,6 +1184,7 @@ fn gsub_apply_custom(
11561184 feature_tag,
11571185 alternate,
11581186 glyphs,
1187+ max_glyphs,
11591188 glyphs. len ( ) - 1 ,
11601189 1 ,
11611190 |_| true ,
@@ -1169,6 +1198,7 @@ fn gsub_apply_custom(
11691198 feature_tag,
11701199 alternate,
11711200 glyphs,
1201+ max_glyphs,
11721202 0 ,
11731203 glyphs. len ( ) ,
11741204 |_| true ,
@@ -1457,6 +1487,7 @@ fn gsub_apply_default(
14571487 tuple : Option < Tuple < ' _ > > ,
14581488 num_glyphs : u16 ,
14591489 glyphs : & mut Vec < RawGlyph < ( ) > > ,
1490+ max_glyphs : usize ,
14601491) -> Result < ( ) , ShapingError > {
14611492 let gsub_table = & gsub_cache. layout_table ;
14621493 let feature_variations = gsub_table. feature_variations ( tuple) ?;
@@ -1480,6 +1511,7 @@ fn gsub_apply_default(
14801511 opt_lang_tag,
14811512 feature_variations,
14821513 glyphs,
1514+ max_glyphs,
14831515 ) ?;
14841516 }
14851517 feature_mask. remove ( FeatureMask :: RVRN ) ;
@@ -1493,6 +1525,7 @@ fn gsub_apply_default(
14931525 opt_lang_tag,
14941526 feature_variations,
14951527 glyphs,
1528+ max_glyphs,
14961529 ) ?,
14971530 ScriptType :: Indic => scripts:: indic:: gsub_apply_indic (
14981531 dotted_circle_index,
@@ -1531,6 +1564,7 @@ fn gsub_apply_default(
15311564 opt_lang_tag,
15321565 feature_variations,
15331566 glyphs,
1567+ max_glyphs,
15341568 ) ?,
15351569 ScriptType :: ThaiLao => scripts:: thai_lao:: gsub_apply_thai_lao (
15361570 gsub_cache,
@@ -1540,6 +1574,7 @@ fn gsub_apply_default(
15401574 opt_lang_tag,
15411575 feature_variations,
15421576 glyphs,
1577+ max_glyphs,
15431578 ) ?,
15441579 ScriptType :: Default => {
15451580 feature_mask &= get_supported_features ( gsub_cache, script_tag, opt_lang_tag) ?;
@@ -1569,6 +1604,7 @@ fn gsub_apply_default(
15691604 lookups,
15701605 lookups_frac,
15711606 glyphs,
1607+ max_glyphs,
15721608 ) ?;
15731609 } else {
15741610 let index = get_lookups_cache_index (
@@ -1579,7 +1615,14 @@ fn gsub_apply_default(
15791615 feature_mask,
15801616 ) ?;
15811617 let lookups = & gsub_cache. cached_lookups . lock ( ) . unwrap ( ) [ index] ;
1582- gsub_apply_lookups ( gsub_cache, gsub_table, opt_gdef_table, lookups, glyphs) ?;
1618+ gsub_apply_lookups (
1619+ gsub_cache,
1620+ gsub_table,
1621+ opt_gdef_table,
1622+ lookups,
1623+ glyphs,
1624+ max_glyphs,
1625+ ) ?;
15831626 }
15841627 }
15851628 }
@@ -1595,13 +1638,15 @@ fn gsub_apply_lookups(
15951638 opt_gdef_table : Option < & GDEFTable > ,
15961639 lookups : & [ ( usize , u32 ) ] ,
15971640 glyphs : & mut Vec < RawGlyph < ( ) > > ,
1641+ max_glyphs : usize ,
15981642) -> Result < ( ) , ShapingError > {
15991643 gsub_apply_lookups_impl (
16001644 gsub_cache,
16011645 gsub_table,
16021646 opt_gdef_table,
16031647 lookups,
16041648 glyphs,
1649+ max_glyphs,
16051650 0 ,
16061651 glyphs. len ( ) ,
16071652 ) ?;
@@ -1614,6 +1659,7 @@ fn gsub_apply_lookups_impl(
16141659 opt_gdef_table : Option < & GDEFTable > ,
16151660 lookups : & [ ( usize , u32 ) ] ,
16161661 glyphs : & mut Vec < RawGlyph < ( ) > > ,
1662+ max_glyphs : usize ,
16171663 start : usize ,
16181664 mut length : usize ,
16191665) -> Result < usize , ShapingError > {
@@ -1626,6 +1672,7 @@ fn gsub_apply_lookups_impl(
16261672 * feature_tag,
16271673 None ,
16281674 glyphs,
1675+ max_glyphs,
16291676 start,
16301677 length,
16311678 |_| true ,
@@ -1641,6 +1688,7 @@ fn gsub_apply_lookups_frac(
16411688 lookups : & [ ( usize , u32 ) ] ,
16421689 lookups_frac : & [ ( usize , u32 ) ] ,
16431690 glyphs : & mut Vec < RawGlyph < ( ) > > ,
1691+ max_glyphs : usize ,
16441692) -> Result < ( ) , ShapingError > {
16451693 let mut i = 0 ;
16461694 while i < glyphs. len ( ) {
@@ -1652,6 +1700,7 @@ fn gsub_apply_lookups_frac(
16521700 opt_gdef_table,
16531701 lookups,
16541702 glyphs,
1703+ max_glyphs,
16551704 i,
16561705 start_pos,
16571706 ) ?;
@@ -1662,6 +1711,7 @@ fn gsub_apply_lookups_frac(
16621711 opt_gdef_table,
16631712 lookups_frac,
16641713 glyphs,
1714+ max_glyphs,
16651715 i,
16661716 end_pos - start_pos + 1 ,
16671717 ) ?;
@@ -1672,6 +1722,7 @@ fn gsub_apply_lookups_frac(
16721722 opt_gdef_table,
16731723 lookups,
16741724 glyphs,
1725+ max_glyphs,
16751726 i,
16761727 glyphs. len ( ) - i,
16771728 ) ?;
@@ -1717,6 +1768,7 @@ fn apply_rvrn(
17171768 opt_lang_tag : Option < u32 > ,
17181769 feature_variations : Option < & FeatureTableSubstitution < ' _ > > ,
17191770 glyphs : & mut Vec < RawGlyph < ( ) > > ,
1771+ max_glyphs : usize ,
17201772) -> Result < ( ) , ShapingError > {
17211773 let gsub_table = & gsub_cache. layout_table ;
17221774 let index = get_lookups_cache_index (
@@ -1727,13 +1779,24 @@ fn apply_rvrn(
17271779 FeatureMask :: RVRN ,
17281780 ) ?;
17291781 let lookups = & gsub_cache. cached_lookups . lock ( ) . unwrap ( ) [ index] ;
1730- gsub_apply_lookups ( gsub_cache, gsub_table, opt_gdef_table, lookups, glyphs) ?;
1782+ gsub_apply_lookups (
1783+ gsub_cache,
1784+ gsub_table,
1785+ opt_gdef_table,
1786+ lookups,
1787+ glyphs,
1788+ max_glyphs,
1789+ ) ?;
17311790 Ok ( ( ) )
17321791}
17331792
17341793#[ cfg( test) ]
17351794mod tests {
17361795 use super :: * ;
1796+ use crate :: {
1797+ binary:: read:: ReadScope , font:: MatchingPresentation , font_data:: FontData ,
1798+ tests:: read_fixture, Font ,
1799+ } ;
17371800
17381801 #[ test]
17391802 fn feature_mask_iter ( ) {
@@ -1769,4 +1832,26 @@ mod tests {
17691832 ] ;
17701833 assert_eq ! ( & mask. features( ) . collect:: <Vec <_>>( ) , expected) ;
17711834 }
1835+
1836+ #[ test]
1837+ fn billion_laughs ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
1838+ let data = read_fixture ( "tests/fonts/opentype/TestGSUBThree.ttf" ) ;
1839+ let scope = ReadScope :: new ( & data) ;
1840+ let font_file = scope. read :: < FontData < ' _ > > ( ) ?;
1841+ let provider = font_file. table_provider ( 0 ) ?;
1842+ let mut font = Font :: new ( provider) ?;
1843+
1844+ // Map text to glyphs and then apply font shaping
1845+ let script = tag:: LATN ;
1846+ let lang = tag ! ( b"ENG " ) ;
1847+ let features = Features :: default ( ) ;
1848+ let glyphs = font. map_glyphs ( "lol" , script, MatchingPresentation :: NotRequired ) ;
1849+ let infos = font
1850+ . shape ( glyphs, script, Some ( lang) , & features, None , true )
1851+ . map_err ( |( err, _info) | err) ?;
1852+
1853+ assert_eq ! ( infos. len( ) , 759 ) ;
1854+
1855+ Ok ( ( ) )
1856+ }
17721857}
0 commit comments