Commit 0d9459294238c4fa5a45ccf8077c8b258bd487ac

Werner Lemberg 2016-09-27T08:44:31

[truetype] Introduce dynamic limits for some bytecode opcodes. This speeds up FreeType's handling of malformed fonts. * src/truetype/ttinterp.c (TT_RunIns): Set up limits for the number of twilight points, the total number of negative jumps, and the total number of loops in LOOPCALL opcodes. The values are based on the number of points and entries in the CVT table. (Ins_JMPR): Test negative jump counter. (Ins_LOOPCALL): Test loopcall counter. * src/truetype/ttinterp.h (TT_ExecContext): Updated. * docs/CHANGES: Updated.

diff --git a/ChangeLog b/ChangeLog
index 04b2d54..a1d3e37 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,20 @@
+2016-09-27  Werner Lemberg  <wl@gnu.org>
+
+	[truetype] Introduce dynamic limits for some bytecode opcodes.
+
+	This speeds up FreeType's handling of malformed fonts.
+
+	* src/truetype/ttinterp.c (TT_RunIns): Set up limits for the number
+	of twilight points, the total number of negative jumps, and the
+	total number of loops in LOOPCALL opcodes.  The values are based on
+	the number of points and entries in the CVT table.
+	(Ins_JMPR): Test negative jump counter.
+	(Ins_LOOPCALL): Test loopcall counter.
+
+	* src/truetype/ttinterp.h (TT_ExecContext): Updated.
+
+	* docs/CHANGES: Updated.
+
 2016-09-25  Werner Lemberg  <wl@gnu.org>
 
 	[truetype] Sanitize only last entry of `loca' table.
diff --git a/docs/CHANGES b/docs/CHANGES
index 49590f8..307d83c 100644
--- a/docs/CHANGES
+++ b/docs/CHANGES
@@ -1,4 +1,31 @@
 
+CHANGES BETWEEN 2.7 and 2.7.1
+
+  I. IMPORTANT CHANGES
+
+
+  II. IMPORTANT BUG FIXES
+
+
+  III. MISCELLANEOUS
+
+    - Some limits for TrueType  bytecode execution have been tightened
+      to  speed   up  FreeType's  handling  of   malformed  fonts,  in
+      particular to quickly abort endless loops.
+
+      - The  number of  twilight points  can no  longer be  set to  an
+        arbitrarily large value.
+
+      - The total number of jump  opcode instructions (like JMPR) with
+        negative arguments  is dynamically restricted; the  same holds
+        for the total number of iterations in LOOPCALL opcodes.
+
+      The dynamic limits are based on  the number of points in a glyph
+      and the number of CVT entries.  Please report if you encounter a
+      font where the selected values are not adequate.
+
+======================================================================
+
 CHANGES BETWEEN 2.6.5 and 2.7
 
   I. IMPORTANT CHANGES
diff --git a/src/truetype/ttinterp.c b/src/truetype/ttinterp.c
index 2d15ea7..a5244eb 100644
--- a/src/truetype/ttinterp.c
+++ b/src/truetype/ttinterp.c
@@ -3388,13 +3388,27 @@
             FT_Long*        args )
   {
     if ( args[0] == 0 && exc->args == 0 )
+    {
       exc->error = FT_THROW( Bad_Argument );
+      return;
+    }
+
     exc->IP += args[0];
     if ( exc->IP < 0                                             ||
          ( exc->callTop > 0                                    &&
            exc->IP > exc->callStack[exc->callTop - 1].Def->end ) )
+    {
       exc->error = FT_THROW( Bad_Argument );
+      return;
+    }
+
     exc->step_ins = FALSE;
+
+    if ( args[0] < 0 )
+    {
+      if ( ++exc->neg_jump_counter > exc->neg_jump_counter_max )
+        exc->error = FT_THROW( Execution_Too_Long );
+    }
   }
 
 
@@ -3949,6 +3963,10 @@
       Ins_Goto_CodeRange( exc, def->range, def->start );
 
       exc->step_ins = FALSE;
+
+      exc->loopcall_counter += args[0];
+      if ( exc->loopcall_counter > exc->loopcall_counter_max )
+        exc->error = FT_THROW( Execution_Too_Long );
     }
 
     return;
@@ -7533,6 +7551,7 @@
   TT_RunIns( TT_ExecContext  exc )
   {
     FT_Long    ins_counter = 0;  /* executed instructions counter */
+    FT_Long    num_twilight_points;
     FT_UShort  i;
 
 #ifdef TT_SUPPORT_SUBPIXEL_HINTING_INFINALITY
@@ -7568,6 +7587,41 @@
     exc->iupy_called = FALSE;
 #endif
 
+    /* We restrict the number of twilight points to a reasonable,     */
+    /* heuristic value to avoid slow execution of malformed bytecode. */
+    num_twilight_points = FT_MAX( 30,
+                                  2 * ( exc->pts.n_points + exc->cvtSize ) );
+    if ( exc->twilight.n_points > num_twilight_points )
+    {
+      FT_TRACE5(( "TT_RunIns: Resetting number of twilight points\n"
+                  "           from %d to the more reasonable value %d\n",
+                  exc->twilight.n_points,
+                  num_twilight_points ));
+      exc->twilight.n_points = num_twilight_points;
+    }
+
+    /* Set up loop detectors.  We restrict the number of LOOPCALL loops  */
+    /* and the number of JMPR, JROT, and JROF calls with a negative      */
+    /* argument to values that depend on the size of the CVT table and   */
+    /* the number of points in the current glyph (if applicable).        */
+    /*                                                                   */
+    /* The idea is that in real-world bytecode you either iterate over   */
+    /* all CVT entries, or over all points (or contours) of a glyph, and */
+    /* such iterations don't happen very often.                          */
+    exc->loopcall_counter = 0;
+    exc->neg_jump_counter = 0;
+
+    /* The maximum values are heuristic. */
+    exc->loopcall_counter_max = FT_MAX( 100,
+                                        10 * ( exc->pts.n_points +
+                                               exc->cvtSize ) );
+    FT_TRACE5(( "TT_RunIns: Limiting total number of loops in LOOPCALL"
+                " to %d\n", exc->loopcall_counter_max ));
+
+    exc->neg_jump_counter_max = exc->loopcall_counter_max;
+    FT_TRACE5(( "TT_RunIns: Limiting total number of backward jumps"
+                " to %d\n", exc->neg_jump_counter_max ));
+
     /* set PPEM and CVT functions */
     exc->tt_metrics.ratio = 0;
     if ( exc->metrics.x_ppem != exc->metrics.y_ppem )
diff --git a/src/truetype/ttinterp.h b/src/truetype/ttinterp.h
index 53f0944..be87e08 100644
--- a/src/truetype/ttinterp.h
+++ b/src/truetype/ttinterp.h
@@ -408,6 +408,14 @@ FT_BEGIN_HEADER
 
 #endif /* TT_SUPPORT_SUBPIXEL_HINTING_INFINALITY */
 
+    /* We maintain two counters (in addition to the instruction counter) */
+    /* that act as loop detectors for LOOPCALL and jump opcodes with     */
+    /* negative arguments.                                               */
+    FT_Long            loopcall_counter;
+    FT_Long            loopcall_counter_max;
+    FT_Long            neg_jump_counter;
+    FT_Long            neg_jump_counter_max;
+
   } TT_ExecContextRec;