Commit 0d1c306e51aeda3e51bc54fcaa1e41f34c387a4b

Werner Lemberg 2021-05-25T11:27:56

[psaux] Guard and trace AFM kern data allocation. Reported as https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=31543 * include/freetype/internal/fttrace.h: Add 'afmparse' trace component. * src/psaux/afmparse.c (FT_COMPONENT): Define. (afm_parse_track_kern, afm_parse_kern_pairs): Protect against allocations bombs. Add tracing. (afm_parse_kern_data): Don't allow multiple kern data sections.

diff --git a/ChangeLog b/ChangeLog
index d88c2f2..01c4d70 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,20 @@
+2021-05-25  Werner Lemberg  <wl@gnu.org>
+
+	[psaux] Guard and trace AFM kern data allocation.
+
+	Reported as
+
+	  https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=31543
+
+	* include/freetype/internal/fttrace.h: Add 'afmparse' trace
+	component.
+
+	* src/psaux/afmparse.c (FT_COMPONENT): Define.
+	(afm_parse_track_kern, afm_parse_kern_pairs): Protect against
+	allocations bombs.
+	Add tracing.
+	(afm_parse_kern_data): Don't allow multiple kern data sections.
+
 2021-05-23  Alexei Podtelezhnikov  <apodtele@gmail.com>
 
 	* meson.build (ft2_public_headers): Add missing `ftcid.h'.
diff --git a/include/freetype/internal/fttrace.h b/include/freetype/internal/fttrace.h
index b846d57..6b1019e 100644
--- a/include/freetype/internal/fttrace.h
+++ b/include/freetype/internal/fttrace.h
@@ -83,6 +83,7 @@ FT_TRACE_DEF( t1objs )
 FT_TRACE_DEF( t1parse )
 
   /* PostScript helper module `psaux' */
+FT_TRACE_DEF( afmparse )
 FT_TRACE_DEF( cffdecode )
 FT_TRACE_DEF( psconv )
 FT_TRACE_DEF( psobjs )
diff --git a/src/psaux/afmparse.c b/src/psaux/afmparse.c
index a778cde..61f581f 100644
--- a/src/psaux/afmparse.c
+++ b/src/psaux/afmparse.c
@@ -29,6 +29,16 @@
 
   /**************************************************************************
    *
+   * The macro FT_COMPONENT is used in trace mode.  It is an implicit
+   * parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log
+   * messages during execution.
+   */
+#undef  FT_COMPONENT
+#define FT_COMPONENT  afmparse
+
+
+  /**************************************************************************
+   *
    * AFM_Stream
    *
    * The use of AFM_Stream is largely inspired by parseAFM.[ch] from t1lib.
@@ -586,21 +596,38 @@
   static FT_Error
   afm_parse_track_kern( AFM_Parser  parser )
   {
-    AFM_FontInfo   fi = parser->FontInfo;
+    AFM_FontInfo   fi     = parser->FontInfo;
+    AFM_Stream     stream = parser->stream;
     AFM_TrackKern  tk;
-    char*          key;
-    FT_Offset      len;
-    int            n = -1;
-    FT_Int         tmp;
+
+    char*      key;
+    FT_Offset  len;
+    int        n = -1;
+    FT_Int     tmp;
 
 
     if ( afm_parser_read_int( parser, &tmp ) )
         goto Fail;
 
     if ( tmp < 0 )
+    {
+      FT_ERROR(( "afm_parse_track_kern: invalid number of track kerns\n" ));
       goto Fail;
+    }
 
     fi->NumTrackKern = (FT_UInt)tmp;
+    FT_TRACE3(( "afm_parse_track_kern: %u track kern%s expected\n",
+                fi->NumTrackKern,
+                fi->NumTrackKern == 1 ? "" : "s" ));
+
+    /* Rough sanity check: The minimum line length of the `TrackKern` */
+    /* command is 20 characters (including the EOL character).        */
+    if ( ( stream->limit - stream->cursor ) / 20 < fi->NumTrackKern )
+    {
+      FT_ERROR(( "afm_parse_track_kern:"
+                 " number of track kern entries exceeds stream size\n" ));
+      goto Fail;
+    }
 
     if ( fi->NumTrackKern )
     {
@@ -623,7 +650,10 @@
         n++;
 
         if ( n >= (int)fi->NumTrackKern )
-          goto Fail;
+          {
+            FT_ERROR(( "afm_parse_track_kern: too many track kern data\n" ));
+            goto Fail;
+          }
 
         tk = fi->TrackKerns + n;
 
@@ -633,7 +663,12 @@
         shared_vals[3].type = AFM_VALUE_TYPE_FIXED;
         shared_vals[4].type = AFM_VALUE_TYPE_FIXED;
         if ( afm_parser_read_vals( parser, shared_vals, 5 ) != 5 )
+        {
+          FT_ERROR(( "afm_parse_track_kern:"
+                     " insufficient number of parameters for entry %d\n",
+                     n ));
           goto Fail;
+        }
 
         tk->degree     = shared_vals[0].u.i;
         tk->min_ptsize = shared_vals[1].u.f;
@@ -646,7 +681,19 @@
       case AFM_TOKEN_ENDTRACKKERN:
       case AFM_TOKEN_ENDKERNDATA:
       case AFM_TOKEN_ENDFONTMETRICS:
-        fi->NumTrackKern = (FT_UInt)( n + 1 );
+        tmp = n + 1;
+        if ( (FT_UInt)tmp != fi->NumTrackKern )
+        {
+          FT_TRACE1(( "afm_parse_track_kern: %s%d track kern entr%s seen\n",
+                      tmp == 0 ? "" : "only ",
+                      tmp,
+                      tmp == 1 ? "y" : "ies" ));
+          fi->NumTrackKern = (FT_UInt)tmp;
+        }
+        else
+          FT_TRACE3(( "afm_parse_track_kern: %d track kern entr%s seen\n",
+                      tmp,
+                      tmp == 1 ? "y" : "ies" ));
         return FT_Err_Ok;
 
       case AFM_TOKEN_UNKNOWN:
@@ -690,7 +737,8 @@
   static FT_Error
   afm_parse_kern_pairs( AFM_Parser  parser )
   {
-    AFM_FontInfo  fi = parser->FontInfo;
+    AFM_FontInfo  fi     = parser->FontInfo;
+    AFM_Stream    stream = parser->stream;
     AFM_KernPair  kp;
     char*         key;
     FT_Offset     len;
@@ -702,9 +750,25 @@
       goto Fail;
 
     if ( tmp < 0 )
+    {
+      FT_ERROR(( "afm_parse_kern_pairs: invalid number of kern pairs\n" ));
       goto Fail;
+    }
 
     fi->NumKernPair = (FT_UInt)tmp;
+    FT_TRACE3(( "afm_parse_kern_pairs: %u kern pair%s expected\n",
+                fi->NumKernPair,
+                fi->NumKernPair == 1 ? "" : "s" ));
+
+    /* Rough sanity check: The minimum line length of the `KP`,    */
+    /* `KPH`,`KPX`, and `KPY` commands is 10 characters (including */
+    /* the EOL character).                                         */
+    if ( ( stream->limit - stream->cursor ) / 10 < fi->NumKernPair )
+    {
+      FT_ERROR(( "afm_parse_kern_pairs:"
+                 " number of kern pairs exceeds stream size\n" ));
+      goto Fail;
+    }
 
     if ( fi->NumKernPair )
     {
@@ -734,7 +798,10 @@
           n++;
 
           if ( n >= (int)fi->NumKernPair )
+          {
+            FT_ERROR(( "afm_parse_kern_pairs: too many kern pairs\n" ));
             goto Fail;
+          }
 
           kp = fi->KernPairs + n;
 
@@ -744,7 +811,12 @@
           shared_vals[3].type = AFM_VALUE_TYPE_INTEGER;
           r = afm_parser_read_vals( parser, shared_vals, 4 );
           if ( r < 3 )
+          {
+            FT_ERROR(( "afm_parse_kern_pairs:"
+                       " insufficient number of parameters for entry %d\n",
+                       n ));
             goto Fail;
+          }
 
           /* index values can't be negative */
           kp->index1 = shared_vals[0].u.u;
@@ -766,7 +838,20 @@
       case AFM_TOKEN_ENDKERNPAIRS:
       case AFM_TOKEN_ENDKERNDATA:
       case AFM_TOKEN_ENDFONTMETRICS:
-        fi->NumKernPair = (FT_UInt)( n + 1 );
+        tmp = n + 1;
+        if ( (FT_UInt)tmp != fi->NumKernPair )
+        {
+          FT_TRACE1(( "afm_parse_kern_pairs: %s%d kern pair%s seen\n",
+                      tmp == 0 ? "" : "only ",
+                      tmp,
+                      tmp == 1 ? "" : "s" ));
+          fi->NumKernPair = (FT_UInt)tmp;
+        }
+        else
+          FT_TRACE3(( "afm_parse_kern_pairs: %d kern pair%s seen\n",
+                      tmp,
+                      tmp == 1 ? "" : "s" ));
+
         ft_qsort( fi->KernPairs, fi->NumKernPair,
                   sizeof ( AFM_KernPairRec ),
                   afm_compare_kern_pairs );
@@ -792,22 +877,43 @@
     char*      key;
     FT_Offset  len;
 
+    int  have_trackkern = 0;
+    int  have_kernpairs = 0;
+
 
     while ( ( key = afm_parser_next_key( parser, 1, &len ) ) != 0 )
     {
       switch ( afm_tokenize( key, len ) )
       {
       case AFM_TOKEN_STARTTRACKKERN:
+        if ( have_trackkern )
+        {
+          FT_ERROR(( "afm_parse_kern_data:"
+                     " invalid second horizontal track kern section\n" ));
+          goto Fail;
+        }
+
         error = afm_parse_track_kern( parser );
         if ( error )
           return error;
+
+        have_trackkern = 1;
         break;
 
       case AFM_TOKEN_STARTKERNPAIRS:
       case AFM_TOKEN_STARTKERNPAIRS0:
+        if ( have_kernpairs )
+        {
+          FT_ERROR(( "afm_parse_kern_data:"
+                     " invalid second horizontal kern pair section\n" ));
+          goto Fail;
+        }
+
         error = afm_parse_kern_pairs( parser );
         if ( error )
           return error;
+
+        have_kernpairs = 1;
         break;
 
       case AFM_TOKEN_ENDKERNDATA: