/***************************************************************************/ /* */ /* gxvjust.c */ /* */ /* TrueTypeGX/AAT just table validation (body). */ /* */ /* Copyright 2005-2015 by */ /* suzuki toshiya, Masatake YAMATO, Red Hat K.K., */ /* David Turner, Robert Wilhelm, and Werner Lemberg. */ /* */ /* This file is part of the FreeType project, and may only be used, */ /* modified, and distributed under the terms of the FreeType project */ /* license, LICENSE.TXT. By continuing to use, modify, or distribute */ /* this file you indicate that you have read the license and */ /* understand and accept it fully. */ /* */ /***************************************************************************/ /***************************************************************************/ /* */ /* gxvalid is derived from both gxlayout module and otvalid module. */ /* Development of gxlayout is supported by the Information-technology */ /* Promotion Agency(IPA), Japan. */ /* */ /***************************************************************************/ #include "gxvalid.h" #include "gxvcommn.h" #include FT_SFNT_NAMES_H /*************************************************************************/ /* */ /* The macro FT_COMPONENT is used in trace mode. It is an implicit */ /* parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log */ /* messages during execution. */ /* */ #undef FT_COMPONENT #define FT_COMPONENT trace_gxvjust /* * referred `just' table format specification: * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6just.html * last updated 2000. * ---------------------------------------------- * [JUST HEADER]: GXV_JUST_HEADER_SIZE * version (fixed: 32bit) = 0x00010000 * format (uint16: 16bit) = 0 is only defined (2000) * horizOffset (uint16: 16bit) * vertOffset (uint16: 16bit) * ---------------------------------------------- */ typedef struct GXV_just_DataRec_ { FT_UShort wdc_offset_max; FT_UShort wdc_offset_min; FT_UShort pc_offset_max; FT_UShort pc_offset_min; } GXV_just_DataRec, *GXV_just_Data; #define GXV_JUST_DATA( a ) GXV_TABLE_DATA( just, a ) /* GX just table does not define their subset of GID */ static void gxv_just_check_max_gid( FT_UShort gid, const FT_String* msg_tag, GXV_Validator gxvalid ) { if ( gid < gxvalid->face->num_glyphs ) return; GXV_TRACE(( "just table includes too large %s" " GID=%d > %d (in maxp)\n", msg_tag, gid, gxvalid->face->num_glyphs )); GXV_SET_ERR_IF_PARANOID( FT_INVALID_GLYPH_ID ); } static void gxv_just_wdp_entry_validate( FT_Bytes table, FT_Bytes limit, GXV_Validator gxvalid ) { FT_Bytes p = table; FT_ULong justClass; #ifdef GXV_LOAD_UNUSED_VARS FT_Fixed beforeGrowLimit; FT_Fixed beforeShrinkGrowLimit; FT_Fixed afterGrowLimit; FT_Fixed afterShrinkGrowLimit; FT_UShort growFlags; FT_UShort shrinkFlags; #endif GXV_LIMIT_CHECK( 4 + 4 + 4 + 4 + 4 + 2 + 2 ); justClass = FT_NEXT_ULONG( p ); #ifndef GXV_LOAD_UNUSED_VARS p += 4 + 4 + 4 + 4 + 2 + 2; #else beforeGrowLimit = FT_NEXT_ULONG( p ); beforeShrinkGrowLimit = FT_NEXT_ULONG( p ); afterGrowLimit = FT_NEXT_ULONG( p ); afterShrinkGrowLimit = FT_NEXT_ULONG( p ); growFlags = FT_NEXT_USHORT( p ); shrinkFlags = FT_NEXT_USHORT( p ); #endif /* According to Apple spec, only 7bits in justClass is used */ if ( ( justClass & 0xFFFFFF80UL ) != 0 ) { GXV_TRACE(( "just table includes non-zero value" " in unused justClass higher bits" " of WidthDeltaPair" )); GXV_SET_ERR_IF_PARANOID( FT_INVALID_DATA ); } gxvalid->subtable_length = (FT_ULong)( p - table ); } static void gxv_just_wdc_entry_validate( FT_Bytes table, FT_Bytes limit, GXV_Validator gxvalid ) { FT_Bytes p = table; FT_ULong count, i; GXV_LIMIT_CHECK( 4 ); count = FT_NEXT_ULONG( p ); for ( i = 0; i < count; i++ ) { GXV_TRACE(( "validating wdc pair %d/%d\n", i + 1, count )); gxv_just_wdp_entry_validate( p, limit, gxvalid ); p += gxvalid->subtable_length; } gxvalid->subtable_length = (FT_ULong)( p - table ); } static void gxv_just_widthDeltaClusters_validate( FT_Bytes table, FT_Bytes limit, GXV_Validator gxvalid ) { FT_Bytes p = table ; FT_Bytes wdc_end = table + GXV_JUST_DATA( wdc_offset_max ); FT_UInt i; GXV_NAME_ENTER( "just justDeltaClusters" ); if ( limit <= wdc_end ) FT_INVALID_OFFSET; for ( i = 0; p <= wdc_end; i++ ) { gxv_just_wdc_entry_validate( p, limit, gxvalid ); p += gxvalid->subtable_length; } gxvalid->subtable_length = (FT_ULong)( p - table ); GXV_EXIT; } static void gxv_just_actSubrecord_type0_validate( FT_Bytes table, FT_Bytes limit, GXV_Validator gxvalid ) { FT_Bytes p = table; FT_Fixed lowerLimit; FT_Fixed upperLimit; #ifdef GXV_LOAD_UNUSED_VARS FT_UShort order; #endif FT_UShort decomposedCount; FT_UInt i; GXV_LIMIT_CHECK( 4 + 4 + 2 + 2 ); lowerLimit = FT_NEXT_LONG( p ); upperLimit = FT_NEXT_LONG( p ); #ifdef GXV_LOAD_UNUSED_VARS order = FT_NEXT_USHORT( p ); #else p += 2; #endif decomposedCount = FT_NEXT_USHORT( p ); if ( lowerLimit >= upperLimit ) { GXV_TRACE(( "just table includes invalid range spec:" " lowerLimit(%d) > upperLimit(%d)\n" )); GXV_SET_ERR_IF_PARANOID( FT_INVALID_DATA ); } for ( i = 0; i < decomposedCount; i++ ) { FT_UShort glyphs; GXV_LIMIT_CHECK( 2 ); glyphs = FT_NEXT_USHORT( p ); gxv_just_check_max_gid( glyphs, "type0:glyphs", gxvalid ); } gxvalid->subtable_length = (FT_ULong)( p - table ); } static void gxv_just_actSubrecord_type1_validate( FT_Bytes table, FT_Bytes limit, GXV_Validator gxvalid ) { FT_Bytes p = table; FT_UShort addGlyph; GXV_LIMIT_CHECK( 2 ); addGlyph = FT_NEXT_USHORT( p ); gxv_just_check_max_gid( addGlyph, "type1:addGlyph", gxvalid ); gxvalid->subtable_length = (FT_ULong)( p - table ); } static void gxv_just_actSubrecord_type2_validate( FT_Bytes table, FT_Bytes limit, GXV_Validator gxvalid ) { FT_Bytes p = table; #ifdef GXV_LOAD_UNUSED_VARS FT_Fixed substThreshhold; /* Apple misspelled "Threshhold" */ #endif FT_UShort addGlyph; FT_UShort substGlyph; GXV_LIMIT_CHECK( 4 + 2 + 2 ); #ifdef GXV_LOAD_UNUSED_VARS substThreshhold = FT_NEXT_ULONG( p ); #else p += 4; #endif addGlyph = FT_NEXT_USHORT( p ); substGlyph = FT_NEXT_USHORT( p ); if ( addGlyph != 0xFFFF ) gxv_just_check_max_gid( addGlyph, "type2:addGlyph", gxvalid ); gxv_just_check_max_gid( substGlyph, "type2:substGlyph", gxvalid ); gxvalid->subtable_length = (FT_ULong)( p - table ); } static void gxv_just_actSubrecord_type4_validate( FT_Bytes table, FT_Bytes limit, GXV_Validator gxvalid ) { FT_Bytes p = table; FT_ULong variantsAxis; FT_Fixed minimumLimit; FT_Fixed noStretchValue; FT_Fixed maximumLimit; GXV_LIMIT_CHECK( 4 + 4 + 4 + 4 ); variantsAxis = FT_NEXT_ULONG( p ); minimumLimit = FT_NEXT_LONG( p ); noStretchValue = FT_NEXT_LONG( p ); maximumLimit = FT_NEXT_LONG( p ); gxvalid->subtable_length = (FT_ULong)( p - table ); if ( variantsAxis != 0x64756374L ) /* 'duct' */ GXV_TRACE(( "variantsAxis 0x%08x is non default value", variantsAxis )); if ( minimumLimit > noStretchValue ) GXV_TRACE(( "type4:minimumLimit 0x%08x > noStretchValue 0x%08x\n", minimumLimit, noStretchValue )); else if ( noStretchValue > maximumLimit ) GXV_TRACE(( "type4:noStretchValue 0x%08x > maximumLimit 0x%08x\n", noStretchValue, maximumLimit )); else if ( !IS_PARANOID_VALIDATION ) return; FT_INVALID_DATA; } static void gxv_just_actSubrecord_type5_validate( FT_Bytes table, FT_Bytes limit, GXV_Validator gxvalid ) { FT_Bytes p = table; FT_UShort flags; FT_UShort glyph; GXV_LIMIT_CHECK( 2 + 2 ); flags = FT_NEXT_USHORT( p ); glyph = FT_NEXT_USHORT( p ); if ( flags ) GXV_TRACE(( "type5: nonzero value 0x%04x in unused flags\n", flags )); gxv_just_check_max_gid( glyph, "type5:glyph", gxvalid ); gxvalid->subtable_length = (FT_ULong)( p - table ); } /* parse single actSubrecord */ static void gxv_just_actSubrecord_validate( FT_Bytes table, FT_Bytes limit, GXV_Validator gxvalid ) { FT_Bytes p = table; FT_UShort actionClass; FT_UShort actionType; FT_ULong actionLength; GXV_NAME_ENTER( "just actSubrecord" ); GXV_LIMIT_CHECK( 2 + 2 + 4 ); actionClass = FT_NEXT_USHORT( p ); actionType = FT_NEXT_USHORT( p ); actionLength = FT_NEXT_ULONG( p ); /* actionClass is related with justClass using 7bit only */ if ( ( actionClass & 0xFF80 ) != 0 ) GXV_SET_ERR_IF_PARANOID( FT_INVALID_DATA ); if ( actionType == 0 ) gxv_just_actSubrecord_type0_validate( p, limit, gxvalid ); else if ( actionType == 1 ) gxv_just_actSubrecord_type1_validate( p, limit, gxvalid ); else if ( actionType == 2 ) gxv_just_actSubrecord_type2_validate( p, limit, gxvalid ); else if ( actionType == 3 ) ; /* Stretch glyph action: no actionData */ else if ( actionType == 4 ) gxv_just_actSubrecord_type4_validate( p, limit, gxvalid ); else if ( actionType == 5 ) gxv_just_actSubrecord_type5_validate( p, limit, gxvalid ); else FT_INVALID_DATA; gxvalid->subtable_length = actionLength; GXV_EXIT; } static void gxv_just_pcActionRecord_validate( FT_Bytes table, FT_Bytes limit, GXV_Validator gxvalid ) { FT_Bytes p = table; FT_ULong actionCount; FT_ULong i; GXV_LIMIT_CHECK( 4 ); actionCount = FT_NEXT_ULONG( p ); GXV_TRACE(( "actionCount = %d\n", actionCount )); for ( i = 0; i < actionCount; i++ ) { gxv_just_actSubrecord_validate( p, limit, gxvalid ); p += gxvalid->subtable_length; } gxvalid->subtable_length = (FT_ULong)( p - table ); GXV_EXIT; } static void gxv_just_pcTable_LookupValue_entry_validate( FT_UShort glyph, GXV_LookupValueCPtr value_p, GXV_Validator gxvalid ) { FT_UNUSED( glyph ); if ( value_p->u > GXV_JUST_DATA( pc_offset_max ) ) GXV_JUST_DATA( pc_offset_max ) = value_p->u; if ( value_p->u < GXV_JUST_DATA( pc_offset_max ) ) GXV_JUST_DATA( pc_offset_min ) = value_p->u; } static void gxv_just_pcLookupTable_validate( FT_Bytes table, FT_Bytes limit, GXV_Validator gxvalid ) { FT_Bytes p = table; GXV_NAME_ENTER( "just pcLookupTable" ); GXV_JUST_DATA( pc_offset_max ) = 0x0000; GXV_JUST_DATA( pc_offset_min ) = 0xFFFFU; gxvalid->lookupval_sign = GXV_LOOKUPVALUE_UNSIGNED; gxvalid->lookupval_func = gxv_just_pcTable_LookupValue_entry_validate; gxv_LookupTable_validate( p, limit, gxvalid ); /* subtable_length is set by gxv_LookupTable_validate() */ GXV_EXIT; } static void gxv_just_postcompTable_validate( FT_Bytes table, FT_Bytes limit, GXV_Validator gxvalid ) { FT_Bytes p = table; GXV_NAME_ENTER( "just postcompTable" ); gxv_just_pcLookupTable_validate( p, limit, gxvalid ); p += gxvalid->subtable_length; gxv_just_pcActionRecord_validate( p, limit, gxvalid ); p += gxvalid->subtable_length; gxvalid->subtable_length = (FT_ULong)( p - table ); GXV_EXIT; } static void gxv_just_classTable_entry_validate( FT_Byte state, FT_UShort flags, GXV_StateTable_GlyphOffsetCPtr glyphOffset_p, FT_Bytes table, FT_Bytes limit, GXV_Validator gxvalid ) { #ifdef GXV_LOAD_UNUSED_VARS /* TODO: validate markClass & currentClass */ FT_UShort setMark; FT_UShort dontAdvance; FT_UShort markClass; FT_UShort currentClass; #endif FT_UNUSED( state ); FT_UNUSED( glyphOffset_p ); FT_UNUSED( table ); FT_UNUSED( limit ); FT_UNUSED( gxvalid ); #ifndef GXV_LOAD_UNUSED_VARS FT_UNUSED( flags ); #else setMark = (FT_UShort)( ( flags >> 15 ) & 1 ); dontAdvance = (FT_UShort)( ( flags >> 14 ) & 1 ); markClass = (FT_UShort)( ( flags >> 7 ) & 0x7F ); currentClass = (FT_UShort)( flags & 0x7F ); #endif } static void gxv_just_justClassTable_validate ( FT_Bytes table, FT_Bytes limit, GXV_Validator gxvalid ) { FT_Bytes p = table; FT_UShort length; FT_UShort coverage; FT_ULong subFeatureFlags; GXV_NAME_ENTER( "just justClassTable" ); GXV_LIMIT_CHECK( 2 + 2 + 4 ); length = FT_NEXT_USHORT( p ); coverage = FT_NEXT_USHORT( p ); subFeatureFlags = FT_NEXT_ULONG( p ); GXV_TRACE(( " justClassTable: coverage = 0x%04x (%s) ", coverage )); if ( ( coverage & 0x4000 ) == 0 ) GXV_TRACE(( "ascending\n" )); else GXV_TRACE(( "descending\n" )); if ( subFeatureFlags ) GXV_TRACE(( " justClassTable: nonzero value (0x%08x)" " in unused subFeatureFlags\n", subFeatureFlags )); gxvalid->statetable.optdata = NULL; gxvalid->statetable.optdata_load_func = NULL; gxvalid->statetable.subtable_setup_func = NULL; gxvalid->statetable.entry_glyphoffset_fmt = GXV_GLYPHOFFSET_NONE; gxvalid->statetable.entry_validate_func = gxv_just_classTable_entry_validate; gxv_StateTable_validate( p, table + length, gxvalid ); /* subtable_length is set by gxv_LookupTable_validate() */ GXV_EXIT; } static void gxv_just_wdcTable_LookupValue_validate( FT_UShort glyph, GXV_LookupValueCPtr value_p, GXV_Validator gxvalid ) { FT_UNUSED( glyph ); if ( value_p->u > GXV_JUST_DATA( wdc_offset_max ) ) GXV_JUST_DATA( wdc_offset_max ) = value_p->u; if ( value_p->u < GXV_JUST_DATA( wdc_offset_min ) ) GXV_JUST_DATA( wdc_offset_min ) = value_p->u; } static void gxv_just_justData_lookuptable_validate( FT_Bytes table, FT_Bytes limit, GXV_Validator gxvalid ) { FT_Bytes p = table; GXV_JUST_DATA( wdc_offset_max ) = 0x0000; GXV_JUST_DATA( wdc_offset_min ) = 0xFFFFU; gxvalid->lookupval_sign = GXV_LOOKUPVALUE_UNSIGNED; gxvalid->lookupval_func = gxv_just_wdcTable_LookupValue_validate; gxv_LookupTable_validate( p, limit, gxvalid ); /* subtable_length is set by gxv_LookupTable_validate() */ GXV_EXIT; } /* * gxv_just_justData_validate() parses and validates horizData, vertData. */ static void gxv_just_justData_validate( FT_Bytes table, FT_Bytes limit, GXV_Validator gxvalid ) { /* * following 3 offsets are measured from the start of `just' * (which table points to), not justData */ FT_UShort justClassTableOffset; FT_UShort wdcTableOffset; FT_UShort pcTableOffset; FT_Bytes p = table; GXV_ODTECT( 4, odtect ); GXV_NAME_ENTER( "just justData" ); GXV_ODTECT_INIT( odtect ); GXV_LIMIT_CHECK( 2 + 2 + 2 ); justClassTableOffset = FT_NEXT_USHORT( p ); wdcTableOffset = FT_NEXT_USHORT( p ); pcTableOffset = FT_NEXT_USHORT( p ); GXV_TRACE(( " (justClassTableOffset = 0x%04x)\n", justClassTableOffset )); GXV_TRACE(( " (wdcTableOffset = 0x%04x)\n", wdcTableOffset )); GXV_TRACE(( " (pcTableOffset = 0x%04x)\n", pcTableOffset )); gxv_just_justData_lookuptable_validate( p, limit, gxvalid ); gxv_odtect_add_range( p, gxvalid->subtable_length, "just_LookupTable", odtect ); if ( wdcTableOffset ) { gxv_just_widthDeltaClusters_validate( gxvalid->root->base + wdcTableOffset, limit, gxvalid ); gxv_odtect_add_range( gxvalid->root->base + wdcTableOffset, gxvalid->subtable_length, "just_wdcTable", odtect ); } if ( pcTableOffset ) { gxv_just_postcompTable_validate( gxvalid->root->base + pcTableOffset, limit, gxvalid ); gxv_odtect_add_range( gxvalid->root->base + pcTableOffset, gxvalid->subtable_length, "just_pcTable", odtect ); } if ( justClassTableOffset ) { gxv_just_justClassTable_validate( gxvalid->root->base + justClassTableOffset, limit, gxvalid ); gxv_odtect_add_range( gxvalid->root->base + justClassTableOffset, gxvalid->subtable_length, "just_justClassTable", odtect ); } gxv_odtect_validate( odtect, gxvalid ); GXV_EXIT; } FT_LOCAL_DEF( void ) gxv_just_validate( FT_Bytes table, FT_Face face, FT_Validator ftvalid ) { FT_Bytes p = table; FT_Bytes limit = 0; GXV_ValidatorRec gxvalidrec; GXV_Validator gxvalid = &gxvalidrec; GXV_just_DataRec justrec; GXV_just_Data just = &justrec; FT_ULong version; FT_UShort format; FT_UShort horizOffset; FT_UShort vertOffset; GXV_ODTECT( 3, odtect ); GXV_ODTECT_INIT( odtect ); gxvalid->root = ftvalid; gxvalid->table_data = just; gxvalid->face = face; FT_TRACE3(( "validating `just' table\n" )); GXV_INIT; limit = gxvalid->root->limit; GXV_LIMIT_CHECK( 4 + 2 + 2 + 2 ); version = FT_NEXT_ULONG( p ); format = FT_NEXT_USHORT( p ); horizOffset = FT_NEXT_USHORT( p ); vertOffset = FT_NEXT_USHORT( p ); gxv_odtect_add_range( table, (FT_ULong)( p - table ), "just header", odtect ); /* Version 1.0 (always:2000) */ GXV_TRACE(( " (version = 0x%08x)\n", version )); if ( version != 0x00010000UL ) FT_INVALID_FORMAT; /* format 0 (always:2000) */ GXV_TRACE(( " (format = 0x%04x)\n", format )); if ( format != 0x0000 ) FT_INVALID_FORMAT; GXV_TRACE(( " (horizOffset = %d)\n", horizOffset )); GXV_TRACE(( " (vertOffset = %d)\n", vertOffset )); /* validate justData */ if ( 0 < horizOffset ) { gxv_just_justData_validate( table + horizOffset, limit, gxvalid ); gxv_odtect_add_range( table + horizOffset, gxvalid->subtable_length, "horizJustData", odtect ); } if ( 0 < vertOffset ) { gxv_just_justData_validate( table + vertOffset, limit, gxvalid ); gxv_odtect_add_range( table + vertOffset, gxvalid->subtable_length, "vertJustData", odtect ); } gxv_odtect_validate( odtect, gxvalid ); FT_TRACE4(( "\n" )); } /* END */