diff --git a/ChangeLog b/ChangeLog index 04b2d54ed..a1d3e37f5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,20 @@ +2016-09-27 Werner Lemberg + + [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 [truetype] Sanitize only last entry of `loca' table. diff --git a/docs/CHANGES b/docs/CHANGES index 49590f866..307d83c17 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 2d15ea712..a5244ebb5 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 53f09446b..be87e0837 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;