Commit 3e2f953a10cafa1b037d0172f81103c9f1a5f433

David Turner 2007-05-22T13:10:59

real fix for bug #19910. the .Z format is really badly designed :-(

diff --git a/ChangeLog b/ChangeLog
index 3f044a8..dd8c99e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,8 +1,8 @@
 2007-05-22  David Turner  <david@freetype.org>
 
-	* src/lzw/ftzopen.h, src/lzw/ftzopen.c: apply some "band-aid"
-	to avoid blowing up the heap in the case of malformed fonts.
-	related to bug #19910; *not* a real fix though...
+	* src/lzw/ftzopen.h, src/lzw/ftzopen.c: fix for bug #19910
+	(heap blowup with very large .Z font file). The .Z format is
+	*really* crappy :-(
 
 2007-05-20  Ismail Dönmez  <ismail@pardus.org.tr>
 
diff --git a/src/lzw/ftzopen.c b/src/lzw/ftzopen.c
index f486d1b..85e3223 100644
--- a/src/lzw/ftzopen.c
+++ b/src/lzw/ftzopen.c
@@ -23,59 +23,86 @@
 #include FT_INTERNAL_STREAM_H
 #include FT_INTERNAL_DEBUG_H
 
-  /* refill input buffer, return 0 on success, or -1 if eof */
   static int
   ft_lzwstate_refill( FT_LzwState  state )
   {
-    int  result = -1;
+    FT_ULong  count;
 
+    if (state->in_eof)
+      return -1;
 
-    if ( !state->in_eof )
-    {
-      FT_ULong  count = FT_Stream_TryRead( state->source,
-                                           state->in_buff,
-                                           sizeof ( state->in_buff ) );
+    count = FT_Stream_TryRead( state->source,
+                               state->buf_tab,
+                               state->num_bits );  /* WHY ?? */
 
-      state->in_cursor = state->in_buff;
-      state->in_limit  = state->in_buff + count;
-      state->in_eof    = FT_BOOL( count < sizeof ( state->in_buff ) );
+    state->buf_size   = (FT_UInt) count;
+    state->buf_total += count;
+    state->in_eof     = FT_BOOL( count < state->num_bits );
+    state->buf_offset = 0;
+    state->buf_size   = (state->buf_size << 3) - (state->num_bits-1);
 
-      if ( count > 0 )
-        result = 0;
-    }
-    return result;
+    if (count == 0)  /* end of file */
+      return -1;
+
+    return 0;
   }
 
 
-  /* return new code of 'num_bits', or -1 if eof */
   static FT_Int32
-  ft_lzwstate_get_code( FT_LzwState  state,
-                        FT_UInt      num_bits )
+  ft_lzwstate_get_code( FT_LzwState  state )
   {
-    FT_Int32   result   = -1;
-    FT_UInt32  pad      = state->pad;
-    FT_UInt    pad_bits = state->pad_bits;
+    FT_UInt   num_bits = state->num_bits;
+    FT_Int    offset   = state->buf_offset;
+    FT_Byte*  p;
+    FT_Int    result;
 
 
-    while ( num_bits > pad_bits )
+    if ( state->buf_clear                    ||
+         offset >= state->buf_size           ||
+         state->free_ent >= state->free_bits )
     {
-      if ( state->in_cursor >= state->in_limit &&
-           ft_lzwstate_refill( state ) < 0     )
-        goto Exit;
+      if ( state->free_ent >= state->free_bits )
+      {
+        state->num_bits = ++num_bits;
+        state->free_bits = state->num_bits < state->max_bits
+                        ? (FT_UInt)( ( 1UL << num_bits ) - 256 )
+                        : state->max_free + 1;
+      }
 
-      pad      |= (FT_UInt32)(*state->in_cursor++) << pad_bits;
-      pad_bits += 8;
+      if ( state->buf_clear )
+      {
+        state->num_bits  = num_bits = LZW_INIT_BITS;
+        state->free_bits = (FT_UInt)( ( 1UL << num_bits ) - 256 );
+        state->buf_clear = 0;
+      }
+
+      if ( ft_lzwstate_refill( state ) < 0 )
+        return -1;
+
+      offset = 0;
     }
 
-    result          = (FT_Int32)( pad & LZW_MASK( num_bits ) );
-    state->pad_bits = pad_bits - num_bits;
-    state->pad      = pad >> num_bits;
+    state->buf_offset = offset + num_bits;
+
+    p         = &state->buf_tab[offset >> 3];
+    offset   &= 7;
+    result    = *p++ >> offset;
+    offset    = 8-offset;
+    num_bits -= offset;
+    if (num_bits >= 8)
+    {
+      result   |= *p++ << offset;
+      offset   += 8;
+      num_bits -= 8;
+    }
+    if (num_bits > 0)
+      result |= (*p & LZW_MASK(num_bits)) << offset;
 
-  Exit:
     return result;
   }
 
 
+
   /* grow the character stack */
   static int
   ft_lzwstate_stack_grow( FT_LzwState  state )
@@ -87,11 +114,6 @@
       FT_UInt    old_size = state->stack_size;
       FT_UInt    new_size = old_size;
 
-      /* limit stack size to detect invalid files                           */
-      /* the magic number comes from the origin NetBSD zopen implementation */
-      if (state->stack_size >= 69001 )
-          return -1;
-
       new_size = new_size + ( new_size >> 1 ) + 4;
 
       if ( state->stack == state->stack_0 )
@@ -115,7 +137,6 @@
   {
     FT_UInt    old_size = state->prefix_size;
     FT_UInt    new_size = old_size;
-    FT_UInt    max_size = INT_MAX/(sizeof(FT_UShort)+sizeof(FT_Byte));
     FT_Memory  memory   = state->memory;
     FT_Error   error;
 
@@ -125,13 +146,6 @@
     else
       new_size += new_size >> 2;  /* don't grow too fast */
 
-    if (new_size < old_size || new_size > max_size)
-    {
-        new_size = max_size;
-        if (new_size == old_size)
-            return -1;
-    }
-
     /*
      *  Note that the `suffix' array is located in the same memory block
      *  pointed to by `prefix'.
@@ -159,12 +173,11 @@
   FT_LOCAL_DEF( void )
   ft_lzwstate_reset( FT_LzwState  state )
   {
-    state->in_cursor = state->in_buff;
-    state->in_limit  = state->in_buff;
     state->in_eof    = 0;
-    state->pad_bits  = 0;
-    state->pad       = 0;
-
+    state->buf_offset = 0;
+    state->buf_size   = 0;
+    state->buf_clear  = 0;
+    state->buf_total  = 0;
     state->stack_top = 0;
     state->num_bits  = LZW_INIT_BITS;
     state->phase     = FT_LZW_PHASE_START;
@@ -226,11 +239,9 @@
   {
     FT_ULong  result = 0;
 
-    FT_UInt  num_bits  = state->num_bits;
-    FT_UInt  free_ent  = state->free_ent;
-    FT_UInt  last_char = state->last_char;
-    FT_UInt  old_code  = state->old_code;
-    FT_UInt  in_code   = state->in_code;
+    FT_UInt  old_char = state->old_char;
+    FT_UInt  old_code = state->old_code;
+    FT_UInt  in_code  = state->in_code;
 
 
     if ( out_size == 0 )
@@ -256,27 +267,27 @@
         if ( state->max_bits > LZW_MAX_BITS )
           goto Eof;
 
-        num_bits = LZW_INIT_BITS;
-        free_ent = ( state->block_mode ? LZW_FIRST : LZW_CLEAR ) - 256;
+        state->num_bits = LZW_INIT_BITS;
+        state->free_ent = ( state->block_mode ? LZW_FIRST : LZW_CLEAR ) - 256;
         in_code  = 0;
 
-        state->free_bits = num_bits < state->max_bits
-                           ? (FT_UInt)( ( 1UL << num_bits ) - 256 )
+        state->free_bits = state->num_bits < state->max_bits
+                           ? (FT_UInt)( ( 1UL << state->num_bits ) - 256 )
                            : state->max_free + 1;
 
-        c = ft_lzwstate_get_code( state, num_bits );
+        c = ft_lzwstate_get_code( state );
         if ( c < 0 )
           goto Eof;
 
-        old_code = last_char = (FT_UInt)c;
+        old_code = old_char = (FT_UInt)c;
 
         if ( buffer )
-          buffer[result] = (FT_Byte)last_char;
-
-        state->phase = FT_LZW_PHASE_CODE;
+          buffer[result] = (FT_Byte)old_char;
 
         if ( ++result >= out_size )
           goto Exit;
+
+        state->phase = FT_LZW_PHASE_CODE;
       }
       /* fall-through */
 
@@ -287,7 +298,7 @@
 
 
       NextCode:
-        c = ft_lzwstate_get_code( state, num_bits );
+        c = ft_lzwstate_get_code( state );
         if ( c < 0 )
           goto Eof;
 
@@ -295,14 +306,9 @@
 
         if ( code == LZW_CLEAR && state->block_mode )
         {
-          free_ent = ( LZW_FIRST - 1 ) - 256; /* why not LZW_FIRST-256 ? */
-          num_bits = LZW_INIT_BITS;
-
-          state->free_bits = num_bits < state->max_bits
-                             ? (FT_UInt)( ( 1UL << num_bits ) - 256 )
-                             : state->max_free + 1;
-
-          c = ft_lzwstate_get_code( state, num_bits );
+          state->free_ent  = ( LZW_FIRST - 1 ) - 256; /* why not LZW_FIRST-256 ? */
+          state->buf_clear = 1;
+          c = ft_lzwstate_get_code( state );
           if ( c < 0 )
             goto Eof;
 
@@ -314,9 +320,9 @@
         if ( code >= 256U )
         {
           /* special case for KwKwKwK */
-          if ( code - 256U >= free_ent )
+          if ( code - 256U >= state->free_ent )
           {
-            FTLZW_STACK_PUSH( last_char );
+            FTLZW_STACK_PUSH( old_char );
             code = old_code;
           }
 
@@ -327,8 +333,8 @@
           }
         }
 
-        last_char = code;
-        FTLZW_STACK_PUSH( last_char );
+        old_char = code;
+        FTLZW_STACK_PUSH( old_char );
 
         state->phase = FT_LZW_PHASE_STACK;
       }
@@ -348,25 +354,18 @@
         }
 
         /* now create new entry */
-        if ( free_ent < state->max_free )
+        if ( state->free_ent < state->max_free )
         {
-          if ( free_ent >= state->prefix_size       &&
-               ft_lzwstate_prefix_grow( state ) < 0 )
+          if ( state->free_ent >= state->prefix_size &&
+               ft_lzwstate_prefix_grow( state ) < 0  )
             goto Eof;
 
-          FT_ASSERT( free_ent < state->prefix_size );
-
-          state->prefix[free_ent] = (FT_UShort)old_code;
-          state->suffix[free_ent] = (FT_Byte)  last_char;
+          FT_ASSERT( state->free_ent < state->prefix_size );
 
-          if ( ++free_ent == state->free_bits )
-          {
-            num_bits++;
+          state->prefix[state->free_ent] = (FT_UShort)old_code;
+          state->suffix[state->free_ent] = (FT_Byte)  old_char;
 
-            state->free_bits = num_bits < state->max_bits
-                               ? (FT_UInt)( ( 1UL << num_bits ) - 256 )
-                               : state->max_free + 1;
-          }
+          state->free_ent += 1;
         }
 
         old_code = in_code;
@@ -380,11 +379,9 @@
     }
 
   Exit:
-    state->num_bits  = num_bits;
-    state->free_ent  = free_ent;
-    state->old_code  = old_code;
-    state->last_char = last_char;
-    state->in_code   = in_code;
+    state->old_code = old_code;
+    state->old_char = old_char;
+    state->in_code  = in_code;
 
     return result;
 
diff --git a/src/lzw/ftzopen.h b/src/lzw/ftzopen.h
index 220c1cc..16a53ee 100644
--- a/src/lzw/ftzopen.h
+++ b/src/lzw/ftzopen.h
@@ -112,13 +112,13 @@
   typedef struct  _FT_LzwStateRec
   {
     FT_LzwPhase  phase;
-
     FT_Int       in_eof;
-    FT_Byte*     in_cursor;   /* current buffer pos   */
-    FT_Byte*     in_limit;    /* current buffer limit */
 
-    FT_UInt32    pad;         /* a pad value where incoming bits were read */
-    FT_Int       pad_bits;    /* number of meaningful bits in pad value    */
+    FT_Byte      buf_tab[16];
+    FT_Int       buf_offset;
+    FT_Int       buf_size;
+    FT_Bool      buf_clear;
+    FT_Int       buf_total;
 
     FT_UInt      max_bits;    /* max code bits, from file header   */
     FT_Int       block_mode;  /* block mode flag, from file header */
@@ -128,7 +128,7 @@
     FT_UInt      free_ent;    /* index of next free entry */
     FT_UInt      free_bits;   /* if reached by free_ent, increment num_bits */
     FT_UInt      old_code;
-    FT_UInt      last_char;
+    FT_UInt      old_char;
     FT_UInt      in_code;
 
     FT_UShort*   prefix;      /* always dynamically allocated / reallocated */
@@ -138,8 +138,6 @@
     FT_Byte*     stack;       /* character stack */
     FT_UInt      stack_top;
     FT_UInt      stack_size;
-
-    FT_Byte      in_buff[FT_LZW_IN_BUFF_SIZE];       /* small read-buffer   */
     FT_Byte      stack_0[FT_LZW_DEFAULT_STACK_SIZE]; /* minimize heap alloc */
 
     FT_Stream    source;      /* source stream */