2727*/
2828import glyf from './tables/glyf.mjs' ;
2929
30+ // Safety limits to prevent denial-of-service from crafted fonts.
31+ const MAX_INSTRUCTIONS = 1000000 ; // max instructions per hinting session
32+ const MAX_CALL_DEPTH = 64 ; // max nested CALL/LOOPCALL depth
33+ const MAX_LOOP_COUNT = 10000 ; // max LOOPCALL iterations & SLOOP value
34+
3035/*
3136* turn on for intensive debugging.
3237*/
@@ -593,6 +598,8 @@ Hinting.prototype.exec = function(glyph, ppem) {
593598
594599 fpgmState . funcs = [ ] ;
595600 fpgmState . font = font ;
601+ fpgmState . instructionCount = 0 ;
602+ fpgmState . callDepth = 0 ;
596603
597604 if ( DEBUG ) {
598605 console . log ( '---EXEC FPGM---' ) ;
@@ -618,6 +625,8 @@ Hinting.prototype.exec = function(glyph, ppem) {
618625 new State ( 'prep' , font . tables . prep ) ;
619626
620627 prepState . ppem = ppem ;
628+ prepState . instructionCount = 0 ;
629+ prepState . callDepth = 0 ;
621630
622631 // Creates a copy of the cvt table
623632 // and scales it to the current ppem setting.
@@ -676,6 +685,8 @@ execGlyph = function(glyph, prepState) {
676685 State . prototype = prepState ;
677686 if ( ! components ) {
678687 state = new State ( 'glyf' , glyph . instructions ) ;
688+ state . instructionCount = 0 ;
689+ state . callDepth = 0 ;
679690 if ( DEBUG ) {
680691 console . log ( '---EXEC GLYPH---' ) ;
681692 state . step = - 1 ;
@@ -691,6 +702,8 @@ execGlyph = function(glyph, prepState) {
691702 const cg = font . glyphs . get ( c . glyphIndex ) ;
692703
693704 state = new State ( 'glyf' , cg . instructions ) ;
705+ state . instructionCount = 0 ;
706+ state . callDepth = 0 ;
694707
695708 if ( DEBUG ) {
696709 console . log ( '---EXEC COMP ' + i + '---' ) ;
@@ -834,6 +847,11 @@ exec = function(state) {
834847 let ins ;
835848
836849 for ( state . ip = 0 ; state . ip < pLen ; state . ip ++ ) {
850+ if ( ++ state . instructionCount > MAX_INSTRUCTIONS ) {
851+ throw new Error (
852+ 'Hinting instructions exceeded maximum of ' + MAX_INSTRUCTIONS
853+ ) ;
854+ }
837855 if ( DEBUG ) state . step ++ ;
838856 ins = instructionTable [ prog [ state . ip ] ] ;
839857
@@ -1230,6 +1248,10 @@ function SZPS(state) {
12301248function SLOOP ( state ) {
12311249 state . loop = state . stack . pop ( ) ;
12321250
1251+ if ( state . loop > MAX_LOOP_COUNT ) {
1252+ state . loop = MAX_LOOP_COUNT ;
1253+ }
1254+
12331255 if ( DEBUG ) console . log ( state . step , 'SLOOP[]' , state . loop ) ;
12341256}
12351257
@@ -1349,10 +1371,16 @@ function DEPTH(state) {
13491371function LOOPCALL ( state ) {
13501372 const stack = state . stack ;
13511373 const fn = stack . pop ( ) ;
1352- const c = stack . pop ( ) ;
1374+ let c = stack . pop ( ) ;
1375+
1376+ if ( c > MAX_LOOP_COUNT ) c = MAX_LOOP_COUNT ;
13531377
13541378 if ( DEBUG ) console . log ( state . step , 'LOOPCALL[]' , fn , c ) ;
13551379
1380+ if ( ++ state . callDepth > MAX_CALL_DEPTH ) {
1381+ throw new Error ( 'Hinting call depth exceeded maximum of ' + MAX_CALL_DEPTH ) ;
1382+ }
1383+
13561384 // saves callers program
13571385 const cip = state . ip ;
13581386 const cprog = state . prog ;
@@ -1373,6 +1401,7 @@ function LOOPCALL(state) {
13731401 // restores the callers program
13741402 state . ip = cip ;
13751403 state . prog = cprog ;
1404+ state . callDepth -- ;
13761405}
13771406
13781407// CALL[] CALL function
@@ -1382,6 +1411,10 @@ function CALL(state) {
13821411
13831412 if ( DEBUG ) console . log ( state . step , 'CALL[]' , fn ) ;
13841413
1414+ if ( ++ state . callDepth > MAX_CALL_DEPTH ) {
1415+ throw new Error ( 'Hinting call depth exceeded maximum of ' + MAX_CALL_DEPTH ) ;
1416+ }
1417+
13851418 // saves callers program
13861419 const cip = state . ip ;
13871420 const cprog = state . prog ;
@@ -1394,6 +1427,7 @@ function CALL(state) {
13941427 // restores the callers program
13951428 state . ip = cip ;
13961429 state . prog = cprog ;
1430+ state . callDepth -- ;
13971431
13981432 if ( DEBUG ) console . log ( ++ state . step , 'returning from' , fn ) ;
13991433}
0 commit comments