Commit 3eccc3a3f849f44e50a14c7562e4b9aea1740925

Werner Lemberg 2015-10-20T22:31:57

[cid] Add a bunch of safety checks. * src/cid/cidload.c (parse_fd_array): Check `num_dicts' against stream size. (cid_read_subrs): Check largest offset against stream size. (cid_parse_dict): Move safety check to ... (cid_face_open): ... this function. Also test length of binary data and values of `SDBytes', `SubrMapOffset', `SubrCount', `CIDMapOffset', and `CIDCount'.

diff --git a/ChangeLog b/ChangeLog
index 85dcfb0..b95f65e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,17 @@
 2015-10-20  Werner Lemberg  <wl@gnu.org>
 
+	[cid] Add a bunch of safety checks.
+
+	* src/cid/cidload.c (parse_fd_array): Check `num_dicts' against
+	stream size.
+	(cid_read_subrs): Check largest offset against stream size.
+	(cid_parse_dict): Move safety check to ...
+	(cid_face_open): ... this function.
+	Also test length of binary data and values of `SDBytes',
+	`SubrMapOffset', `SubrCount', `CIDMapOffset', and `CIDCount'.
+
+2015-10-20  Werner Lemberg  <wl@gnu.org>
+
 	[cid] Avoid segfault with malformed input (#46250).
 
 	* src/cid/cidload.c (cid_read_subrs): Return a proper error code for
diff --git a/src/cid/cidload.c b/src/cid/cidload.c
index 0bf73d4..aa125a4 100644
--- a/src/cid/cidload.c
+++ b/src/cid/cidload.c
@@ -215,6 +215,7 @@
   {
     CID_FaceInfo  cid    = &face->cid;
     FT_Memory     memory = face->root.memory;
+    FT_Stream     stream = parser->stream;
     FT_Error      error  = FT_Err_Ok;
     FT_Long       num_dicts;
 
@@ -227,6 +228,31 @@
       goto Exit;
     }
 
+    /*
+     * A single entry in the FDArray must (at least) contain the following
+     * structure elements.
+     *
+     *   %ADOBeginFontDict              18
+     *   X dict begin                   13
+     *     /FontMatrix [X X X X]        22
+     *     /Private X dict begin        22
+     *     end                           4
+     *   end                             4
+     *   %ADOEndFontDict                16
+     *
+     * This needs 18+13+22+22+4+4+16=99 bytes or more.  Normally, you also
+     * need a `dup X' at the very beginning and a `put' at the end, so a
+     * rough guess using 100 bytes as the minimum is justified.
+     */
+    if ( (FT_ULong)num_dicts > stream->size / 100 )
+    {
+      FT_TRACE0(( "parse_fd_array: adjusting FDArray size"
+                  " (from %d to %d)\n",
+                  num_dicts,
+                  stream->size / 100 ));
+      num_dicts = (FT_Long)( stream->size / 100 );
+    }
+
     if ( !cid->font_dicts )
     {
       FT_Int  n;
@@ -401,16 +427,6 @@
         FT_ERROR(( "cid_parse_dict: No font dictionary found\n" ));
         return FT_THROW( Invalid_File_Format );
       }
-
-      /* allow at most 32bit offsets */
-      if ( face->cid.fd_bytes > 4 || face->cid.gd_bytes > 4 )
-      {
-        FT_ERROR(( "cid_parse_dict:"
-                   " Values of `FDBytes' or `GDBytes' larger than 4\n"
-                   "               "
-                   " are not supported\n" ));
-        return FT_THROW( Invalid_File_Format );
-      }
     }
 
     return parser->root.error;
@@ -445,13 +461,6 @@
       FT_Byte*      p;
 
 
-      /* Check for possible overflow. */
-      if ( num_subrs == FT_UINT_MAX )
-      {
-        error = FT_THROW( Syntax_Error );
-        goto Fail;
-      }
-
       /* reallocate offsets array if needed */
       if ( num_subrs + 1 > max_offsets )
       {
@@ -485,17 +494,24 @@
       for ( count = 1; count <= num_subrs; count++ )
         if ( offsets[count - 1] > offsets[count] )
         {
-          FT_TRACE1(( "cid_read_subrs: offsets are not ordered\n" ));
-          error = FT_THROW( Syntax_Error );
+          FT_ERROR(( "cid_read_subrs: offsets are not ordered\n" ));
+          error = FT_THROW( Invalid_File_Format );
           goto Fail;
         }
 
+      if ( offsets[num_subrs] > stream->size - cid->data_offset )
+      {
+        FT_ERROR(( "cid_read_subrs: too large `subrs' offsets\n" ));
+        error = FT_THROW( Invalid_File_Format );
+        goto Fail;
+      }
+
       /* now, compute the size of subrs charstrings, */
       /* allocate, and read them                     */
       data_len = offsets[num_subrs] - offsets[0];
 
       if ( FT_NEW_ARRAY( subr->code, num_subrs + 1 ) ||
-               FT_ALLOC( subr->code[0], data_len )   )
+           FT_ALLOC( subr->code[0], data_len )       )
         goto Fail;
 
       if ( FT_STREAM_SEEK( cid->data_offset + offsets[0] ) ||
@@ -675,6 +691,12 @@
     CID_Parser*  parser;
     FT_Memory    memory = face->root.memory;
     FT_Error     error;
+    FT_Int       n;
+
+    CID_FaceInfo  cid = &face->cid;
+
+    FT_ULong  binary_length;
+    FT_ULong  entry_len;
 
 
     cid_init_loader( &loader, face );
@@ -699,6 +721,17 @@
 
     if ( parser->binary_length )
     {
+      if ( parser->binary_length >
+             face->root.stream->size - parser->data_offset )
+      {
+        FT_TRACE0(( "cid_face_open: adjusting length of binary data\n"
+                    "               (from %d to %d bytes)\n",
+                    parser->binary_length,
+                    face->root.stream->size - parser->data_offset ));
+        parser->binary_length = face->root.stream->size -
+                                parser->data_offset;
+      }
+
       /* we must convert the data section from hexadecimal to binary */
       if ( FT_ALLOC( face->binary_data, parser->binary_length )         ||
            cid_hex_to_binary( face->binary_data, parser->binary_length,
@@ -707,14 +740,78 @@
 
       FT_Stream_OpenMemory( face->cid_stream,
                             face->binary_data, parser->binary_length );
-      face->cid.data_offset = 0;
+      cid->data_offset = 0;
     }
     else
     {
-      *face->cid_stream     = *face->root.stream;
-      face->cid.data_offset = loader.parser.data_offset;
+      *face->cid_stream = *face->root.stream;
+      cid->data_offset  = loader.parser.data_offset;
+    }
+
+    /* sanity tests */
+
+    /* allow at most 32bit offsets */
+    if ( cid->fd_bytes > 4 || cid->gd_bytes > 4 )
+    {
+      FT_ERROR(( "cid_parse_dict:"
+                 " Values of `FDBytes' or `GDBytes' larger than 4\n"
+                 "               "
+                 " are not supported\n" ));
+      error = FT_THROW( Invalid_File_Format );
+      goto Exit;
+    }
+
+    binary_length = face->cid_stream->size - cid->data_offset;
+    entry_len     = (FT_ULong)( cid->fd_bytes + cid->gd_bytes );
+
+    for ( n = 0; n < cid->num_dicts; n++ )
+    {
+      CID_FaceDict  dict = cid->font_dicts + n;
+
+
+      if ( dict->sd_bytes > 4 )
+      {
+        FT_ERROR(( "cid_parse_dict:"
+                   " Values of `SDBytes' larger than 4"
+                   " are not supported\n" ));
+        error = FT_THROW( Invalid_File_Format );
+        goto Exit;
+      }
+
+      if ( dict->subrmap_offset > binary_length )
+      {
+        FT_ERROR(( "cid_parse_dict: Invalid `SubrMapOffset' value\n" ));
+        error = FT_THROW( Invalid_File_Format );
+        goto Exit;
+      }
+
+      if ( dict->sd_bytes                                              &&
+           dict->num_subrs >
+             ( binary_length - dict->subrmap_offset ) / dict->sd_bytes )
+      {
+        FT_ERROR(( "cid_parse_dict: Invalid `SubrCount' value\n" ));
+        error = FT_THROW( Invalid_File_Format );
+        goto Exit;
+      }
+    }
+
+    if ( cid->cidmap_offset > binary_length )
+    {
+      FT_ERROR(( "cid_parse_dict: Invalid `CIDMapOffset' value\n" ));
+      error = FT_THROW( Invalid_File_Format );
+      goto Exit;
+    }
+
+    if ( entry_len                                            &&
+         cid->cid_count >
+           ( binary_length - cid->cidmap_offset ) / entry_len )
+    {
+      FT_ERROR(( "cid_parse_dict: Invalid `CIDCount' value\n" ));
+      error = FT_THROW( Invalid_File_Format );
+      goto Exit;
     }
 
+    /* we can now safely proceed */
     error = cid_read_subrs( face );
 
   Exit: