[truetype/GX] Read points and deltas more carefully.

Hopefully fixes newly introduced buffer overflows:
  https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=70807
  https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=70809

* src/truetype/ttgxvar.c (ft_var_readpacked{points,deltas}): Explicitly
check stream frame limits and modify run counting.
This commit is contained in:
Alexei Podtelezhnikov 2024-07-30 21:51:51 -04:00
parent 104f85448d
commit 5e116bb0dc
1 changed files with 51 additions and 86 deletions

View File

@ -129,9 +129,6 @@
* stream :: * stream ::
* The data stream. * The data stream.
* *
* size ::
* The size of the table holding the data.
*
* @Output: * @Output:
* point_cnt :: * point_cnt ::
* The number of points read. A zero value means that * The number of points read. A zero value means that
@ -144,12 +141,11 @@
*/ */
static FT_UShort* static FT_UShort*
ft_var_readpackedpoints( FT_Stream stream, ft_var_readpackedpoints( FT_Stream stream,
FT_ULong size,
FT_UInt *point_cnt ) FT_UInt *point_cnt )
{ {
FT_UShort *points = NULL; FT_UShort *points = NULL;
FT_UInt n; FT_UInt n;
FT_UInt runcnt; FT_UInt runcnt, cnt;
FT_UInt i, j; FT_UInt i, j;
FT_UShort first; FT_UShort first;
FT_Byte* p; FT_Byte* p;
@ -170,59 +166,60 @@
n |= FT_GET_BYTE(); n |= FT_GET_BYTE();
} }
if ( n > size ) if ( FT_QNEW_ARRAY( points, n ) )
{
FT_TRACE1(( "ft_var_readpackedpoints: number of points too large\n" ));
return NULL; return NULL;
}
/* in the nested loops below we increase `i' twice; */
/* it is faster to simply allocate one more slot */
/* than to add another test within the loop */
if ( FT_QNEW_ARRAY( points, n + 1 ) )
return NULL;
*point_cnt = n;
p = stream->cursor; p = stream->cursor;
first = 0; first = 0;
i = 0; i = 0;
while ( i < n ) while ( i < n )
{ {
if ( p >= stream->limit )
goto Fail;
runcnt = FT_NEXT_BYTE( p ); runcnt = FT_NEXT_BYTE( p );
if ( runcnt & GX_PT_POINTS_ARE_WORDS ) cnt = runcnt & GX_PT_POINT_RUN_COUNT_MASK;
{
runcnt &= GX_PT_POINT_RUN_COUNT_MASK;
first += FT_NEXT_USHORT( p );
points[i++] = first;
/* first point not included in run count */ /* first point not included in run count */
for ( j = 0; j < runcnt; j++ ) cnt++;
if ( i + cnt > n )
cnt = n - i;
if ( runcnt & GX_PT_POINTS_ARE_WORDS )
{
if ( p + 2 * cnt > stream->limit )
goto Fail;
for ( j = 0; j < cnt; j++ )
{ {
first += FT_NEXT_USHORT( p ); first += FT_NEXT_USHORT( p );
points[i++] = first; points[i++] = first;
if ( i >= n )
break;
} }
} }
else else
{ {
first += FT_NEXT_BYTE( p ); if ( p + cnt > stream->limit )
points[i++] = first; goto Fail;
for ( j = 0; j < runcnt; j++ ) for ( j = 0; j < cnt; j++ )
{ {
first += FT_NEXT_BYTE( p ); first += FT_NEXT_BYTE( p );
points[i++] = first; points[i++] = first;
if ( i >= n )
break;
} }
} }
} }
stream->cursor = p; stream->cursor = p;
*point_cnt = n;
return points; return points;
Fail:
FT_TRACE1(( "ft_var_readpackedpoints: invalid table\n" ));
FT_FREE( points );
return NULL;
} }
@ -244,9 +241,6 @@
* stream :: * stream ::
* The data stream. * The data stream.
* *
* size ::
* The size of the table holding the data.
*
* delta_cnt :: * delta_cnt ::
* The number of deltas to be read. * The number of deltas to be read.
* *
@ -262,13 +256,11 @@
*/ */
static FT_Fixed* static FT_Fixed*
ft_var_readpackeddeltas( FT_Stream stream, ft_var_readpackeddeltas( FT_Stream stream,
FT_ULong size,
FT_UInt delta_cnt ) FT_UInt delta_cnt )
{ {
FT_Fixed *deltas = NULL; FT_Fixed *deltas = NULL;
FT_UInt runcnt, cnt; FT_UInt runcnt, cnt;
FT_UInt i, j; FT_UInt i, j;
FT_UInt bytes_used;
FT_Byte* p; FT_Byte* p;
FT_Memory memory = stream->memory; FT_Memory memory = stream->memory;
FT_Error error; FT_Error error;
@ -279,62 +271,42 @@
p = stream->cursor; p = stream->cursor;
i = 0; i = 0;
bytes_used = 0; while ( i < delta_cnt )
while ( i < delta_cnt && bytes_used < size )
{ {
if ( p >= stream->limit )
{
goto Fail;
}
runcnt = FT_NEXT_BYTE( p ); runcnt = FT_NEXT_BYTE( p );
cnt = runcnt & GX_DT_DELTA_RUN_COUNT_MASK; cnt = runcnt & GX_DT_DELTA_RUN_COUNT_MASK;
bytes_used++; /* first point not included in run count */
cnt++;
if ( i + cnt > delta_cnt )
cnt = delta_cnt - i;
if ( runcnt & GX_DT_DELTAS_ARE_ZERO ) if ( runcnt & GX_DT_DELTAS_ARE_ZERO )
{ {
/* `cnt` + 1 zeroes get added */ for ( j = 0; j < cnt; j++ )
for ( j = 0; j <= cnt && i < delta_cnt; j++ )
deltas[i++] = 0; deltas[i++] = 0;
} }
else if ( runcnt & GX_DT_DELTAS_ARE_WORDS ) else if ( runcnt & GX_DT_DELTAS_ARE_WORDS )
{ {
/* `cnt` + 1 shorts from the stack */ if ( p + 2 * cnt > stream->limit )
bytes_used += 2 * ( cnt + 1 );
if ( bytes_used > size )
{
FT_TRACE1(( "ft_var_readpackeddeltas:"
" number of short deltas too large\n" ));
goto Fail; goto Fail;
}
for ( j = 0; j <= cnt && i < delta_cnt; j++ ) for ( j = 0; j < cnt; j++ )
deltas[i++] = FT_intToFixed( FT_NEXT_SHORT( p ) ); deltas[i++] = FT_intToFixed( FT_NEXT_SHORT( p ) );
} }
else else
{ {
/* `cnt` + 1 signed bytes from the stack */ if ( p + cnt > stream->limit )
bytes_used += cnt + 1;
if ( bytes_used > size )
{
FT_TRACE1(( "ft_var_readpackeddeltas:"
" number of byte deltas too large\n" ));
goto Fail; goto Fail;
}
for ( j = 0; j <= cnt && i < delta_cnt; j++ ) for ( j = 0; j < cnt; j++ )
deltas[i++] = FT_intToFixed( FT_NEXT_CHAR( p ) ); deltas[i++] = FT_intToFixed( FT_NEXT_CHAR( p ) );
} }
if ( j <= cnt )
{
FT_TRACE1(( "ft_var_readpackeddeltas:"
" number of deltas too large\n" ));
goto Fail;
}
}
if ( i < delta_cnt )
{
FT_TRACE1(( "ft_var_readpackeddeltas: not enough deltas\n" ));
goto Fail;
} }
stream->cursor = p; stream->cursor = p;
@ -342,6 +314,8 @@
return deltas; return deltas;
Fail: Fail:
FT_TRACE1(( "ft_var_readpackeddeltas: invalid table\n" ));
FT_FREE( deltas ); FT_FREE( deltas );
return NULL; return NULL;
} }
@ -3612,9 +3586,8 @@
FT_Stream_SeekSet( stream, offsetToData ); FT_Stream_SeekSet( stream, offsetToData );
sharedpoints = ft_var_readpackedpoints( stream, sharedpoints = ft_var_readpackedpoints( stream, &spoint_count );
table_len,
&spoint_count );
offsetToData = FT_Stream_FTell( stream ); offsetToData = FT_Stream_FTell( stream );
FT_Stream_SeekSet( stream, here ); FT_Stream_SeekSet( stream, here );
@ -3688,9 +3661,7 @@
if ( tupleIndex & GX_TI_PRIVATE_POINT_NUMBERS ) if ( tupleIndex & GX_TI_PRIVATE_POINT_NUMBERS )
{ {
localpoints = ft_var_readpackedpoints( stream, localpoints = ft_var_readpackedpoints( stream, &point_count );
table_len,
&point_count );
points = localpoints; points = localpoints;
} }
else else
@ -3701,7 +3672,6 @@
} }
deltas = ft_var_readpackeddeltas( stream, deltas = ft_var_readpackeddeltas( stream,
table_len,
point_count == 0 ? face->cvt_size point_count == 0 ? face->cvt_size
: point_count ); : point_count );
@ -4150,9 +4120,8 @@
FT_Stream_SeekSet( stream, offsetToData ); FT_Stream_SeekSet( stream, offsetToData );
sharedpoints = ft_var_readpackedpoints( stream, sharedpoints = ft_var_readpackedpoints( stream, &spoint_count );
blend->gvar_size,
&spoint_count );
offsetToData = FT_Stream_FTell( stream ); offsetToData = FT_Stream_FTell( stream );
FT_Stream_SeekSet( stream, here ); FT_Stream_SeekSet( stream, here );
@ -4236,9 +4205,7 @@
if ( tupleIndex & GX_TI_PRIVATE_POINT_NUMBERS ) if ( tupleIndex & GX_TI_PRIVATE_POINT_NUMBERS )
{ {
localpoints = ft_var_readpackedpoints( stream, localpoints = ft_var_readpackedpoints( stream, &point_count );
blend->gvar_size,
&point_count );
points = localpoints; points = localpoints;
} }
else else
@ -4248,11 +4215,9 @@
} }
deltas_x = ft_var_readpackeddeltas( stream, deltas_x = ft_var_readpackeddeltas( stream,
blend->gvar_size,
point_count == 0 ? n_points point_count == 0 ? n_points
: point_count ); : point_count );
deltas_y = ft_var_readpackeddeltas( stream, deltas_y = ft_var_readpackeddeltas( stream,
blend->gvar_size,
point_count == 0 ? n_points point_count == 0 ? n_points
: point_count ); : point_count );