[pcf] Revise driver.

This commit improves tracing and handling of malformed fonts.  In
particular, the changes to `pcf_get_properties' fix

  https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=379

* src/pcf/pcfread.c (tableNames): Use long names for better
readability.
(pcf_read_TOC): Allow at most 9 tables.
(pcf_get_properties): Allow at most 256 properties.
Limit strings array length to 256 * (65536 + 1) bytes.
Better tracing.
(pcf_get_metric): Trace metric data.
(pcf_get_metrics): Allow at most 65536 metrics.
Fix comparison of `metrics->ascent' and `metrics->descent' to avoid
potential overflow.
Better tracing.
(pcf_get_bitmaps): Allow at most 65536 bitmaps.
Better tracing.
(pcf_get_encodings, pcf_get_accel): Better tracing.

* src/pcf/pcfdrivr.c (PCF_Glyph_Load): Don't trace `format' details.
These are now shown by `pcf_get_bitmaps'.
This commit is contained in:
Werner Lemberg 2017-01-06 11:47:55 +01:00
parent 348d39c68a
commit 8b755445bb
3 changed files with 236 additions and 88 deletions

View File

@ -1,3 +1,30 @@
2017-01-06 Werner Lemberg <wl@gnu.org>
[pcf] Revise driver.
This commit improves tracing and handling of malformed fonts. In
particular, the changes to `pcf_get_properties' fix
https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=379
* src/pcf/pcfread.c (tableNames): Use long names for better
readability.
(pcf_read_TOC): Allow at most 9 tables.
(pcf_get_properties): Allow at most 256 properties.
Limit strings array length to 256 * (65536 + 1) bytes.
Better tracing.
(pcf_get_metric): Trace metric data.
(pcf_get_metrics): Allow at most 65536 metrics.
Fix comparison of `metrics->ascent' and `metrics->descent' to avoid
potential overflow.
Better tracing.
(pcf_get_bitmaps): Allow at most 65536 bitmaps.
Better tracing.
(pcf_get_encodings, pcf_get_accel): Better tracing.
* src/pcf/pcfdrivr.c (PCF_Glyph_Load): Don't trace `format' details.
These are now shown by `pcf_get_bitmaps'.
2017-01-04 Werner Lemberg <wl@gnu.org>
* src/pcf/pcfdrivr.c (PCF_Face_Init): Trace compression format.

View File

@ -524,11 +524,6 @@ THE SOFTWARE.
bitmap->num_grays = 1;
bitmap->pixel_mode = FT_PIXEL_MODE_MONO;
FT_TRACE6(( "BIT_ORDER %d ; BYTE_ORDER %d ; GLYPH_PAD %d\n",
PCF_BIT_ORDER( face->bitmapsFormat ),
PCF_BYTE_ORDER( face->bitmapsFormat ),
PCF_GLYPH_PAD( face->bitmapsFormat ) ));
switch ( PCF_GLYPH_PAD( face->bitmapsFormat ) )
{
case 1:
@ -630,19 +625,23 @@ THE SOFTWARE.
}
else
{
if ( prop->value.l > 0x7FFFFFFFL || prop->value.l < ( -1 - 0x7FFFFFFFL ) )
if ( prop->value.l > 0x7FFFFFFFL ||
prop->value.l < ( -1 - 0x7FFFFFFFL ) )
{
FT_TRACE1(( "pcf_get_bdf_property:" ));
FT_TRACE1(( " too large integer 0x%x is truncated\n" ));
}
/* Apparently, the PCF driver loads all properties as signed integers!
/*
* The PCF driver loads all properties as signed integers.
* This really doesn't seem to be a problem, because this is
* sufficient for any meaningful values.
*/
aproperty->type = BDF_PROPERTY_TYPE_INTEGER;
aproperty->u.integer = (FT_Int32)prop->value.l;
}
return 0;
return FT_Err_Ok;
}
return FT_THROW( Invalid_Argument );
@ -657,7 +656,7 @@ THE SOFTWARE.
*acharset_encoding = face->charset_encoding;
*acharset_registry = face->charset_registry;
return 0;
return FT_Err_Ok;
}

View File

@ -50,8 +50,15 @@ THE SOFTWARE.
#ifdef FT_DEBUG_LEVEL_TRACE
static const char* const tableNames[] =
{
"prop", "accl", "mtrcs", "bmps", "imtrcs",
"enc", "swidth", "names", "accel"
"properties",
"accelerators",
"metrics",
"bitmaps",
"ink metrics",
"encodings",
"swidths",
"glyph names",
"BDF accelerators"
};
#endif
@ -109,22 +116,20 @@ THE SOFTWARE.
if ( stream->size < 16 )
return FT_THROW( Invalid_File_Format );
/* We need 16 bytes per TOC entry. Additionally, as a */
/* heuristic protection against gzip bombs (i.e., very */
/* small input files that expand to insanely large */
/* files), we limit the number of TOC entries to 1024. */
if ( toc->count > stream->size >> 4 ||
toc->count > 1024 )
/* we need 16 bytes per TOC entry, */
/* and there can be most 9 tables */
if ( toc->count > ( stream->size >> 4 ) ||
toc->count > 9 )
{
FT_TRACE0(( "pcf_read_TOC: adjusting number of tables"
" (from %d to %d)\n",
toc->count,
FT_MIN( stream->size >> 4, 1024 ) ));
toc->count = FT_MIN( stream->size >> 4, 1024 );
FT_MIN( stream->size >> 4, 9 ) ));
toc->count = FT_MIN( stream->size >> 4, 9 );
}
if ( FT_NEW_ARRAY( face->toc.tables, toc->count ) )
return FT_THROW( Out_Of_Memory );
return error;
tables = face->toc.tables;
for ( n = 0; n < toc->count; n++ )
@ -348,6 +353,17 @@ THE SOFTWARE.
metric->attributes = 0;
}
FT_TRACE5(( " width=%d,"
" lsb=%d, rsb=%d,"
" ascent=%d, descent=%d,"
" attributes=%d\n",
metric->characterWidth,
metric->leftSideBearing,
metric->rightSideBearing,
metric->ascent,
metric->descent,
metric->attributes ));
Exit:
return error;
}
@ -466,7 +482,7 @@ THE SOFTWARE.
{
PCF_ParseProperty props = NULL;
PCF_Property properties = NULL;
FT_ULong nprops, i;
FT_ULong nprops, orig_nprops, i;
FT_ULong format, size;
FT_Error error;
FT_Memory memory = FT_FACE( face )->memory;
@ -486,32 +502,43 @@ THE SOFTWARE.
if ( FT_READ_ULONG_LE( format ) )
goto Bail;
FT_TRACE4(( "pcf_get_properties:\n" ));
FT_TRACE4(( " format = %ld\n", format ));
FT_TRACE4(( "pcf_get_properties:\n"
" format: 0x%lX (%s)\n",
format,
PCF_BYTE_ORDER( format ) == MSBFirst ? "MSB" : "LSB" ));
if ( !PCF_FORMAT_MATCH( format, PCF_DEFAULT_FORMAT ) )
goto Bail;
if ( PCF_BYTE_ORDER( format ) == MSBFirst )
(void)FT_READ_ULONG( nprops );
(void)FT_READ_ULONG( orig_nprops );
else
(void)FT_READ_ULONG_LE( nprops );
(void)FT_READ_ULONG_LE( orig_nprops );
if ( error )
goto Bail;
FT_TRACE4(( " nprop = %d (truncate %d props)\n",
(int)nprops, nprops - (FT_ULong)(int)nprops ));
nprops = (FT_ULong)(int)nprops;
FT_TRACE4(( " number of properties: %ld\n", orig_nprops ));
/* rough estimate */
if ( nprops > size / PCF_PROPERTY_SIZE )
if ( orig_nprops > size / PCF_PROPERTY_SIZE )
{
error = FT_THROW( Invalid_Table );
goto Bail;
}
/* as a heuristic limit to avoid excessive allocation in */
/* gzip bombs (i.e., very small, invalid input data that */
/* pretends to expand to an insanely large file) we only */
/* load the first 256 properties */
if ( orig_nprops > 256 )
{
FT_TRACE0(( "pcf_get_properties:"
" only loading first 256 properties\n" ));
nprops = 256;
}
else
nprops = orig_nprops;
face->nprops = (int)nprops;
if ( FT_NEW_ARRAY( props, nprops ) )
@ -531,14 +558,23 @@ THE SOFTWARE.
}
}
/* this skip will only work if we really have an extremely large */
/* number of properties; it will fail for fake data, avoiding an */
/* unnecessarily large allocation later on */
if ( FT_STREAM_SKIP( ( orig_nprops - nprops ) * PCF_PROPERTY_SIZE ) )
{
error = FT_THROW( Invalid_Stream_Skip );
goto Bail;
}
/* pad the property array */
/* */
/* clever here - nprops is the same as the number of odd-units read, */
/* as only isStringProp are odd length (Keith Packard) */
/* */
if ( nprops & 3 )
if ( orig_nprops & 3 )
{
i = 4 - ( nprops & 3 );
i = 4 - ( orig_nprops & 3 );
if ( FT_STREAM_SKIP( i ) )
{
error = FT_THROW( Invalid_Stream_Skip );
@ -553,15 +589,24 @@ THE SOFTWARE.
if ( error )
goto Bail;
FT_TRACE4(( " string_size = %ld\n", string_size ));
FT_TRACE4(( " string size: %ld\n", string_size ));
/* rough estimate */
if ( string_size > size - nprops * PCF_PROPERTY_SIZE )
if ( string_size > size - orig_nprops * PCF_PROPERTY_SIZE )
{
error = FT_THROW( Invalid_Table );
goto Bail;
}
/* the strings in the `strings' array are PostScript strings, */
/* which can have a maximum length of 65536 characters each */
if ( string_size > 16777472 ) /* 256 * (65536 + 1) */
{
FT_TRACE0(( "pcf_get_properties:"
" loading only 16777472 bytes of strings array\n" ));
string_size = 16777472;
}
/* allocate one more byte so that we have a final null byte */
if ( FT_NEW_ARRAY( strings, string_size + 1 ) )
goto Bail;
@ -575,6 +620,7 @@ THE SOFTWARE.
face->properties = properties;
FT_TRACE4(( "\n" ));
for ( i = 0; i < nprops; i++ )
{
FT_Long name_offset = props[i].name;
@ -637,7 +683,7 @@ THE SOFTWARE.
FT_Memory memory = FT_FACE( face )->memory;
FT_ULong format, size;
PCF_Metric metrics = NULL;
FT_ULong nmetrics, i;
FT_ULong nmetrics, orig_nmetrics, i;
error = pcf_seek_to_table_type( stream,
@ -652,6 +698,13 @@ THE SOFTWARE.
if ( FT_READ_ULONG_LE( format ) )
goto Bail;
FT_TRACE4(( "pcf_get_metrics:\n"
" format: 0x%lX (%s, %s)\n",
format,
PCF_BYTE_ORDER( format ) == MSBFirst ? "MSB" : "LSB",
PCF_FORMAT_MATCH( format, PCF_COMPRESSED_METRICS ) ?
"compressed" : "uncompressed" ));
if ( !PCF_FORMAT_MATCH( format, PCF_DEFAULT_FORMAT ) &&
!PCF_FORMAT_MATCH( format, PCF_COMPRESSED_METRICS ) )
return FT_THROW( Invalid_File_Format );
@ -659,61 +712,70 @@ THE SOFTWARE.
if ( PCF_FORMAT_MATCH( format, PCF_DEFAULT_FORMAT ) )
{
if ( PCF_BYTE_ORDER( format ) == MSBFirst )
(void)FT_READ_ULONG( nmetrics );
(void)FT_READ_ULONG( orig_nmetrics );
else
(void)FT_READ_ULONG_LE( nmetrics );
(void)FT_READ_ULONG_LE( orig_nmetrics );
}
else
{
if ( PCF_BYTE_ORDER( format ) == MSBFirst )
(void)FT_READ_USHORT( nmetrics );
(void)FT_READ_USHORT( orig_nmetrics );
else
(void)FT_READ_USHORT_LE( nmetrics );
(void)FT_READ_USHORT_LE( orig_nmetrics );
}
if ( error )
return FT_THROW( Invalid_File_Format );
face->nmetrics = nmetrics;
if ( !nmetrics )
return FT_THROW( Invalid_Table );
FT_TRACE4(( "pcf_get_metrics:\n" ));
FT_TRACE4(( " number of metrics: %d\n", nmetrics ));
FT_TRACE4(( " number of metrics: %ld\n", orig_nmetrics ));
/* rough estimate */
if ( PCF_FORMAT_MATCH( format, PCF_DEFAULT_FORMAT ) )
{
if ( nmetrics > size / PCF_METRIC_SIZE )
if ( orig_nmetrics > size / PCF_METRIC_SIZE )
return FT_THROW( Invalid_Table );
}
else
{
if ( nmetrics > size / PCF_COMPRESSED_METRIC_SIZE )
if ( orig_nmetrics > size / PCF_COMPRESSED_METRIC_SIZE )
return FT_THROW( Invalid_Table );
}
if ( !orig_nmetrics )
return FT_THROW( Invalid_Table );
/* PCF is a format from ancient times; Unicode was in its */
/* infancy, and widely used two-byte character sets for CJK */
/* scripts (Big 5, GB 2312, JIS X 0208, etc.) did have at most */
/* 15000 characters. Even the more exotic CNS 11643 and CCCII */
/* standards, which were essentially three-byte character sets, */
/* provided less then 65536 assigned characters. */
/* */
/* While technically possible to have a larger number of glyphs */
/* in PCF files, we thus limit the number to 65536. */
if ( orig_nmetrics > 65536 )
{
FT_TRACE0(( "pcf_get_metrics:"
" only loading first 65536 metrics\n" ));
nmetrics = 65536;
}
else
nmetrics = orig_nmetrics;
face->nmetrics = nmetrics;
if ( FT_NEW_ARRAY( face->metrics, nmetrics ) )
return FT_THROW( Out_Of_Memory );
return error;
metrics = face->metrics;
FT_TRACE4(( "\n" ));
for ( i = 0; i < nmetrics; i++, metrics++ )
{
FT_TRACE5(( " idx %ld:", i ));
error = pcf_get_metric( stream, format, metrics );
metrics->bits = 0;
FT_TRACE5(( " idx %d: width=%d, "
"lsb=%d, rsb=%d, ascent=%d, descent=%d, swidth=%d\n",
i,
metrics->characterWidth,
metrics->leftSideBearing,
metrics->rightSideBearing,
metrics->ascent,
metrics->descent,
metrics->attributes ));
if ( error )
break;
@ -721,7 +783,7 @@ THE SOFTWARE.
/* compute a glyph's bitmap dimensions, thus setting them to zero in */
/* case of an error disables this particular glyph only */
if ( metrics->rightSideBearing < metrics->leftSideBearing ||
metrics->ascent + metrics->descent < 0 )
metrics->ascent < -metrics->descent )
{
metrics->characterWidth = 0;
metrics->leftSideBearing = 0;
@ -751,7 +813,7 @@ THE SOFTWARE.
FT_Long* offsets = NULL;
FT_Long bitmapSizes[GLYPHPADOPTIONS];
FT_ULong format, size;
FT_ULong nbitmaps, i, sizebitmaps = 0;
FT_ULong nbitmaps, orig_nbitmaps, i, sizebitmaps = 0;
error = pcf_seek_to_table_type( stream,
@ -769,18 +831,40 @@ THE SOFTWARE.
format = FT_GET_ULONG_LE();
if ( PCF_BYTE_ORDER( format ) == MSBFirst )
nbitmaps = FT_GET_ULONG();
orig_nbitmaps = FT_GET_ULONG();
else
nbitmaps = FT_GET_ULONG_LE();
orig_nbitmaps = FT_GET_ULONG_LE();
FT_Stream_ExitFrame( stream );
FT_TRACE4(( "pcf_get_bitmaps:\n"
" format: 0x%lX\n"
" (%s, %s,\n"
" padding=%d bits, scanning=%d bits)\n",
format,
PCF_BYTE_ORDER( format ) == MSBFirst
? "most significant byte first"
: "least significant byte first",
PCF_BIT_ORDER( format ) == MSBFirst
? "most significant bit first"
: "least significant bit first",
8 << PCF_GLYPH_PAD_INDEX( format ),
8 << PCF_SCAN_UNIT_INDEX( format ) ));
if ( !PCF_FORMAT_MATCH( format, PCF_DEFAULT_FORMAT ) )
return FT_THROW( Invalid_File_Format );
FT_TRACE4(( "pcf_get_bitmaps:\n" ));
FT_TRACE4(( " number of bitmaps: %ld\n", orig_nbitmaps ));
FT_TRACE4(( " number of bitmaps: %d\n", nbitmaps ));
/* see comment in `pcf_get_metrics' */
if ( orig_nbitmaps > 65536 )
{
FT_TRACE0(( "pcf_get_bitmaps:"
" only loading first 65536 bitmaps\n" ));
nbitmaps = 65536;
}
else
nbitmaps = orig_nbitmaps;
if ( nbitmaps != face->nmetrics )
return FT_THROW( Invalid_File_Format );
@ -788,6 +872,7 @@ THE SOFTWARE.
if ( FT_NEW_ARRAY( offsets, nbitmaps ) )
return error;
FT_TRACE5(( "\n" ));
for ( i = 0; i < nbitmaps; i++ )
{
if ( PCF_BYTE_ORDER( format ) == MSBFirst )
@ -795,7 +880,7 @@ THE SOFTWARE.
else
(void)FT_READ_LONG_LE( offsets[i] );
FT_TRACE5(( " bitmap %d: offset %ld (0x%lX)\n",
FT_TRACE5(( " bitmap %ld: offset %ld (0x%lX)\n",
i, offsets[i], offsets[i] ));
}
if ( error )
@ -812,17 +897,19 @@ THE SOFTWARE.
sizebitmaps = (FT_ULong)bitmapSizes[PCF_GLYPH_PAD_INDEX( format )];
FT_TRACE4(( " padding %d implies a size of %ld\n",
i, bitmapSizes[i] ));
FT_TRACE4(( " %ld-bit padding implies a size of %ld\n",
8 << i, bitmapSizes[i] ));
}
FT_TRACE4(( " %d bitmaps, padding index %ld\n",
FT_TRACE4(( " %ld bitmaps, using %ld-bit padding\n",
nbitmaps,
PCF_GLYPH_PAD_INDEX( format ) ));
FT_TRACE4(( " bitmap size = %d\n", sizebitmaps ));
8 << PCF_GLYPH_PAD_INDEX( format ) ));
FT_TRACE4(( " bitmap size: %ld\n", sizebitmaps ));
FT_UNUSED( sizebitmaps ); /* only used for debugging */
/* right now, we only check the bitmap offsets; */
/* actual bitmaps are only loaded on demand */
for ( i = 0; i < nbitmaps; i++ )
{
/* rough estimate */
@ -830,7 +917,7 @@ THE SOFTWARE.
( (FT_ULong)offsets[i] > size ) )
{
FT_TRACE0(( "pcf_get_bitmaps:"
" invalid offset to bitmap data of glyph %d\n", i ));
" invalid offset to bitmap data of glyph %ld\n", i ));
}
else
face->metrics[i].bits = stream->pos + (FT_ULong)offsets[i];
@ -894,10 +981,20 @@ THE SOFTWARE.
FT_Stream_ExitFrame( stream );
FT_TRACE4(( "pcf_get_encodings:\n"
" format: 0x%lX (%s)\n",
format,
PCF_BYTE_ORDER( format ) == MSBFirst ? "MSB" : "LSB" ));
if ( !PCF_FORMAT_MATCH( format, PCF_DEFAULT_FORMAT ) )
return FT_THROW( Invalid_File_Format );
/* sanity checks */
FT_TRACE4(( " firstCol 0x%X, lastCol 0x%X\n"
" firstRow 0x%X, lastRow 0x%X\n",
firstCol, lastCol,
firstRow, lastRow ));
/* sanity checks; we limit numbers of rows and columns to 256 */
if ( firstCol < 0 ||
firstCol > lastCol ||
lastCol > 0xFF ||
@ -906,21 +1003,18 @@ THE SOFTWARE.
lastRow > 0xFF )
return FT_THROW( Invalid_Table );
FT_TRACE4(( "pdf_get_encodings:\n" ));
FT_TRACE4(( " firstCol %d, lastCol %d, firstRow %d, lastRow %d\n",
firstCol, lastCol, firstRow, lastRow ));
nencoding = (FT_ULong)( lastCol - firstCol + 1 ) *
(FT_ULong)( lastRow - firstRow + 1 );
if ( FT_NEW_ARRAY( encoding, nencoding ) )
return FT_THROW( Out_Of_Memory );
return error;
error = FT_Stream_EnterFrame( stream, 2 * nencoding );
if ( error )
goto Bail;
FT_TRACE5(( "\n" ));
k = 0;
for ( i = firstRow; i <= lastRow; i++ )
{
@ -1029,6 +1123,15 @@ THE SOFTWARE.
if ( FT_READ_ULONG_LE( format ) )
goto Bail;
FT_TRACE4(( "pcf_get_accel%s:\n"
" format: 0x%lX (%s, %s)\n",
type == PCF_BDF_ACCELERATORS ? " (getting BDF accelerators)"
: "",
format,
PCF_BYTE_ORDER( format ) == MSBFirst ? "MSB" : "LSB",
PCF_FORMAT_MATCH( format, PCF_ACCEL_W_INKBOUNDS ) ?
"accelerated" : "not accelerated" ));
if ( !PCF_FORMAT_MATCH( format, PCF_DEFAULT_FORMAT ) &&
!PCF_FORMAT_MATCH( format, PCF_ACCEL_W_INKBOUNDS ) )
goto Bail;
@ -1044,12 +1147,29 @@ THE SOFTWARE.
goto Bail;
}
FT_TRACE5(( " noOverlap=%s, constantMetrics=%s,"
" terminalFont=%s, constantWidth=%s\n"
" inkInside=%s, inkMetrics=%s, drawDirection=%s\n"
" fontAscent=%ld, fontDescent=%ld, maxOverlap=%ld\n",
accel->noOverlap ? "yes" : "no",
accel->constantMetrics ? "yes" : "no",
accel->terminalFont ? "yes" : "no",
accel->constantWidth ? "yes" : "no",
accel->inkInside ? "yes" : "no",
accel->inkMetrics ? "yes" : "no",
accel->drawDirection ? "RTL" : "LTR",
accel->fontAscent,
accel->fontDescent,
accel->maxOverlap ));
FT_TRACE5(( " minbounds:" ));
error = pcf_get_metric( stream,
format & ( ~PCF_FORMAT_MASK ),
&(accel->minbounds) );
if ( error )
goto Bail;
FT_TRACE5(( " maxbounds:" ));
error = pcf_get_metric( stream,
format & ( ~PCF_FORMAT_MASK ),
&(accel->maxbounds) );
@ -1058,12 +1178,14 @@ THE SOFTWARE.
if ( PCF_FORMAT_MATCH( format, PCF_ACCEL_W_INKBOUNDS ) )
{
FT_TRACE5(( " ink minbounds:" ));
error = pcf_get_metric( stream,
format & ( ~PCF_FORMAT_MASK ),
&(accel->ink_minbounds) );
if ( error )
goto Bail;
FT_TRACE5(( " ink maxbounds:" ));
error = pcf_get_metric( stream,
format & ( ~PCF_FORMAT_MASK ),
&(accel->ink_maxbounds) );
@ -1072,7 +1194,7 @@ THE SOFTWARE.
}
else
{
accel->ink_minbounds = accel->minbounds; /* I'm not sure about this */
accel->ink_minbounds = accel->minbounds;
accel->ink_maxbounds = accel->maxbounds;
}