From 5e116bb0dc7418084ec863b198392c43a8aeb44c Mon Sep 17 00:00:00 2001 From: Alexei Podtelezhnikov Date: Tue, 30 Jul 2024 21:51:51 -0400 Subject: [PATCH] [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. --- src/truetype/ttgxvar.c | 137 +++++++++++++++-------------------------- 1 file changed, 51 insertions(+), 86 deletions(-) diff --git a/src/truetype/ttgxvar.c b/src/truetype/ttgxvar.c index 80a9312d9..b4325b1f8 100644 --- a/src/truetype/ttgxvar.c +++ b/src/truetype/ttgxvar.c @@ -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 );