[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

View File

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