This is an automated email from the ASF dual-hosted git repository.

leginee pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/openoffice.git


The following commit(s) were added to refs/heads/trunk by this push:
     new b6bfb4e830 vcl: check bounds in the font subsetter
b6bfb4e830 is described below

commit b6bfb4e8301b7208dc52a53fd4999d5cd5607589
Author: Peter Kovacs <[email protected]>
AuthorDate: Thu Sep 24 09:57:48 2026 +0200

    vcl: check bounds in the font subsetter
    
    Make the CFF charstring interpreter's stack and array checks run-time
    checks, and limit its output to the buffer it writes into.
    
    Check the TrueType parser's table, glyph and cmap subtable offsets and
    lengths against the tables they refer to.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 main/vcl/source/fontsubset/cff.cxx |  98 ++++++++------
 main/vcl/source/fontsubset/sft.cxx | 260 ++++++++++++++++++++++++++++++++++---
 2 files changed, 303 insertions(+), 55 deletions(-)

diff --git a/main/vcl/source/fontsubset/cff.cxx 
b/main/vcl/source/fontsubset/cff.cxx
index 2c33cf3e6a..240df48459 100644
--- a/main/vcl/source/fontsubset/cff.cxx
+++ b/main/vcl/source/fontsubset/cff.cxx
@@ -414,6 +414,8 @@ private:
        const U8* mpReadEnd;
 
        U8*             mpWritePtr;
+       // one past the last byte of the output buffer
+       const U8*       mpWriteEnd;
        bool    mbSawError;
        bool    mbNeedClose;
        bool    mbIgnoreHints;
@@ -470,10 +472,16 @@ private:
 public: // TODO: is public really needed?
        // accessing the value stack
        // TODO: add more checks
-       void    push( ValType nVal) { mnValStack[ mnStackIdx++] = nVal;}
+       void    push( ValType nVal) {
+               if( mnStackIdx >= NMAXSTACK) { mbSawError = true; return;}
+               mnValStack[ mnStackIdx++] = nVal;
+       }
        ValType popVal( void) { return ((mnStackIdx>0) ? mnValStack[ 
--mnStackIdx] : 0);}
        ValType peekVal( void) const { return ((mnStackIdx>0) ? mnValStack[ 
mnStackIdx-1] : 0);}
-       ValType getVal( int nIndex) const { return mnValStack[ nIndex];}
+       ValType getVal( int nIndex) const {
+               if( (nIndex < 0) || (nIndex >= mnStackIdx)) return 0;
+               return mnValStack[ nIndex];
+       }
        int             popInt( void);
        int             peekInt( void) const;
        int             getInt( int nIndex) const;
@@ -513,9 +521,15 @@ private:
 
 // --------------------------------------------------------------------
 
+// Abandon the charstring; convert2Type1Ops then emits a placeholder glyph.
+#define CFF_ABANDON_IF( bad ) \
+       if( bad ) { mbSawError = true; return; } else (void)0
+
 CffSubsetterContext::CffSubsetterContext( const U8* pBasePtr, int nBaseLen)
 :      mpBasePtr( pBasePtr)
 ,      mpBaseEnd( pBasePtr+nBaseLen)
+,      mpWritePtr( NULL)
+,      mpWriteEnd( NULL)
 ,      mnStackIdx(0)
 ,      mnHintSize(0)
 ,      mnHorzHintSize(0)
@@ -597,7 +611,7 @@ void CffSubsetterContext::addHints( bool bVerticalHints)
        if( mnStackIdx & 1) --mnStackIdx;//#######
        // TODO: if( !bSubr) assert( mnStackIdx >= 2);
 
-       assert( (mnHintSize + mnStackIdx) <= 2*NMAXHINTS);
+       CFF_ABANDON_IF( (mnHintSize + mnStackIdx) > NMAXHINTS);
 
 #ifdef IGNORE_HINTS
        mnHintSize += mnStackIdx;
@@ -620,8 +634,11 @@ void CffSubsetterContext::addHints( bool bVerticalHints)
 void CffSubsetterContext::getHintPair( int nIndex, ValType* pMin, ValType* 
pEnd) const
 {
        nIndex *= 2;
-       assert( nIndex < mnHintSize);
-       assert( nIndex >= 0);
+       // Reads a pair, so the second of the two has to be in the array too.
+       if( (nIndex < 0) || (nIndex + 1 >= mnHintSize)) {
+               *pMin = *pEnd = 0;
+               return;
+       }
        const ValType* pHint = &mnHintStack[ nIndex ];
        *pMin = pHint[0];
        *pEnd = pHint[1];
@@ -814,6 +831,8 @@ void CffSubsetterContext::read2push()
 
 void CffSubsetterContext::writeType1Val( ValType aVal)
 {
+       // The longest encoding below is five bytes.
+       if( mpWritePtr + 5 > mpWriteEnd) { mbSawError = true; return;}
        U8* pOut = mpWritePtr;
 
        int nInt = static_cast<int>(aVal);
@@ -859,6 +878,7 @@ inline void CffSubsetterContext::pop2write( void)
 
 inline void CffSubsetterContext::writeTypeOp( int nTypeOp)
 {
+       if( mpWritePtr + 1 > mpWriteEnd) { mbSawError = true; return;}
        *(mpWritePtr++) = static_cast<U8>(nTypeOp);
 }
 
@@ -866,6 +886,7 @@ inline void CffSubsetterContext::writeTypeOp( int nTypeOp)
 
 inline void CffSubsetterContext::writeTypeEsc( int nTypeEsc)
 {
+       if( mpWritePtr + 2 > mpWriteEnd) { mbSawError = true; return;}
        *(mpWritePtr++) = TYPE1OP::T1ESC;
        *(mpWritePtr++) = static_cast<U8>(nTypeEsc);
 }
@@ -1160,84 +1181,83 @@ void CffSubsetterContext::convertOneTypeEsc( void)
        // convert each T2op
        switch( nType2Esc) {
        case TYPE2OP::AND:
-               assert( mnStackIdx >= 2 );
+               CFF_ABANDON_IF( mnStackIdx < 2 );
                pTop[0] = static_cast<ValType>(static_cast<int>(pTop[0]) & 
static_cast<int>(pTop[-1]));
                --mnStackIdx;
                break;
        case TYPE2OP::OR:
-               assert( mnStackIdx >= 2 );
+               CFF_ABANDON_IF( mnStackIdx < 2 );
                pTop[0] = static_cast<ValType>(static_cast<int>(pTop[0]) | 
static_cast<int>(pTop[-1]));
                --mnStackIdx;
                break;
        case TYPE2OP::NOT:
-               assert( mnStackIdx >= 1 );
+               CFF_ABANDON_IF( mnStackIdx < 1 );
                pTop[0] = (pTop[0] == 0);
                break;
        case TYPE2OP::ABS:
-               assert( mnStackIdx >= 1 );
+               CFF_ABANDON_IF( mnStackIdx < 1 );
                if( pTop[0] >= 0)
                        break;
                // fall through
        case TYPE2OP::NEG:
-               assert( mnStackIdx >= 1 );
+               CFF_ABANDON_IF( mnStackIdx < 1 );
                pTop[0] = -pTop[0];
                break;
        case TYPE2OP::ADD:
-               assert( mnStackIdx >= 2 );
+               CFF_ABANDON_IF( mnStackIdx < 2 );
                pTop[0] += pTop[-1];
                --mnStackIdx;
                break;
        case TYPE2OP::SUB:
-               assert( mnStackIdx >= 2 );
+               CFF_ABANDON_IF( mnStackIdx < 2 );
                pTop[0] -= pTop[-1];
                --mnStackIdx;
                break;
        case TYPE2OP::MUL:
-               assert( mnStackIdx >= 2 );
+               CFF_ABANDON_IF( mnStackIdx < 2 );
                if( pTop[-1])
                        pTop[0] *= pTop[-1];
                --mnStackIdx;
                break;
        case TYPE2OP::DIV:
-               assert( mnStackIdx >= 2 );
+               CFF_ABANDON_IF( mnStackIdx < 2 );
                if( pTop[-1])
                        pTop[0] /= pTop[-1];
                --mnStackIdx;
                break;
        case TYPE2OP::EQ:
-               assert( mnStackIdx >= 2 );
+               CFF_ABANDON_IF( mnStackIdx < 2 );
                pTop[0] = (pTop[0] == pTop[-1]);
                --mnStackIdx;
                break;
        case TYPE2OP::DROP:
-               assert( mnStackIdx >= 1 );
+               CFF_ABANDON_IF( mnStackIdx < 1 );
                --mnStackIdx;
                break;
        case TYPE2OP::PUT: {
-               assert( mnStackIdx >= 2 );
+               CFF_ABANDON_IF( mnStackIdx < 2 );
                const int nIdx = static_cast<int>(pTop[0]);
-               assert( nIdx >= 0 );
-               assert( nIdx < NMAXTRANS );
+               CFF_ABANDON_IF( (nIdx < 0) || (nIdx >= NMAXTRANS) );
                mnTransVals[ nIdx] = pTop[-1];
                mnStackIdx -= 2;
                break;
                }
        case TYPE2OP::GET: {
-               assert( mnStackIdx >= 1 );
+               CFF_ABANDON_IF( mnStackIdx < 1 );
                const int nIdx = static_cast<int>(pTop[0]);
-               assert( nIdx >= 0 );
-               assert( nIdx < NMAXTRANS );
+               CFF_ABANDON_IF( (nIdx < 0) || (nIdx >= NMAXTRANS) );
                pTop[0] = mnTransVals[ nIdx ];
                break;
                }
        case TYPE2OP::IFELSE: {
-               assert( mnStackIdx >= 4 );
+               CFF_ABANDON_IF( mnStackIdx < 4 );
                if( pTop[-1] > pTop[0] )
                        pTop[-3] = pTop[-2];
                mnStackIdx -= 3;
                break;
                }
        case TYPE2OP::RANDOM:
+               CFF_ABANDON_IF( mnStackIdx >= NMAXSTACK );
                pTop[+1] = 1234; // TODO
                ++mnStackIdx;
                break;
@@ -1245,30 +1265,29 @@ void CffSubsetterContext::convertOneTypeEsc( void)
                // TODO: implement
                break;
        case TYPE2OP::DUP:
-               assert( mnStackIdx >= 1 );
+               CFF_ABANDON_IF( mnStackIdx < 1 );
+               CFF_ABANDON_IF( mnStackIdx >= NMAXSTACK );
                pTop[+1] = pTop[0];
                ++mnStackIdx;
                break;
        case TYPE2OP::EXCH: {
-               assert( mnStackIdx >= 2 );
+               CFF_ABANDON_IF( mnStackIdx < 2 );
                const ValType nVal = pTop[0];
                pTop[0] = pTop[-1];
                pTop[-1] = nVal;
                break;
                }
        case TYPE2OP::INDEX: {
-               assert( mnStackIdx >= 1 );
+               CFF_ABANDON_IF( mnStackIdx < 1 );
                const int nVal = static_cast<int>(pTop[0]);
-               assert( nVal >= 0 );
-               assert( nVal < mnStackIdx-1 );
+               CFF_ABANDON_IF( (nVal < 0) || (nVal >= mnStackIdx-1) );
                pTop[0] = pTop[-1-nVal];
                break;
                }
        case TYPE2OP::ROLL: {
-               assert( mnStackIdx >= 1 );
+               CFF_ABANDON_IF( mnStackIdx < 1 );
                const int nNum = static_cast<int>(pTop[0]);
-               assert( nNum >= 0);
-               assert( nNum < mnStackIdx-2 );
+               CFF_ABANDON_IF( (nNum < 0) || (nNum >= mnStackIdx-2) );
                (void)nNum; // TODO: implement
                const int nOfs = static_cast<int>(pTop[-1]);
                mnStackIdx -= 2;
@@ -1276,7 +1295,7 @@ void CffSubsetterContext::convertOneTypeEsc( void)
                break;
                }
        case TYPE2OP::HFLEX1: {
-                       assert( mnStackIdx == 9);
+                       CFF_ABANDON_IF( mnStackIdx != 9 );
 #if 0 // emulate hflex1 as straight line
                        const ValType* pX = &mnValStack[ mnStackIdx];
                        const ValType fDX = pX[-9] + pX[-7] + pX[-5] + pX[-4] + 
pX[-3] + pX[-1];
@@ -1291,7 +1310,7 @@ void CffSubsetterContext::convertOneTypeEsc( void)
                }
                break;
        case TYPE2OP::HFLEX: {
-                       assert( mnStackIdx == 7);
+                       CFF_ABANDON_IF( mnStackIdx != 7 );
                        ValType* pX = &mnValStack[ mnStackIdx];
 #if 0 // emulate hflex as straight line
                        const ValType fDX = pX[-7] + pX[-6] + pX[-4] + pX[-3] + 
pX[-2] + pX[-1];
@@ -1307,7 +1326,7 @@ void CffSubsetterContext::convertOneTypeEsc( void)
                }
                break;
        case TYPE2OP::FLEX: {
-                       assert( mnStackIdx == 13 );
+                       CFF_ABANDON_IF( mnStackIdx != 13 );
                        writeCurveTo( mnStackIdx, -13, -12, -11, -10, -9, -8 );
                        writeCurveTo( mnStackIdx,  -7,  -6,  -5,  -4, -3, -2 );
                        const ValType nFlexDepth =  mnValStack[ mnStackIdx-1 ];
@@ -1316,7 +1335,7 @@ void CffSubsetterContext::convertOneTypeEsc( void)
                }
                break;
        case TYPE2OP::FLEX1: {
-                       assert( mnStackIdx == 11 );
+                       CFF_ABANDON_IF( mnStackIdx != 11 );
                        // write the first part of the flex1-hinted curve
                        writeCurveTo( mnStackIdx, -11, -10, -9, -8, -7, -6 );
 
@@ -1338,7 +1357,7 @@ void CffSubsetterContext::convertOneTypeEsc( void)
                break;
        default:
                fprintf( stderr,"unhandled type2esc %d\n", nType2Esc);
-               assert( false);
+               mbSawError = true;
                break;
        }
 }
@@ -1358,7 +1377,7 @@ void CffSubsetterContext::callType2Subr( bool bGlobal, 
int nSubrNumber)
                seekIndexData( mpCffLocal->mnLocalSubrBase, nSubrNumber);
        }
 
-       while( mpReadPtr < mpReadEnd)
+       while( (mpReadPtr < mpReadEnd) && !mbSawError)
                convertOneTypeOp();
 
        mpReadPtr = pOldReadPtr;
@@ -1380,6 +1399,8 @@ int CffSubsetterContext::convert2Type1Ops( CffLocal* 
pCffLocal, const U8* const
        if( !pT1Ops)
                mpWritePtr = aType1Ops;
        *const_cast<U8**>(&pT1Ops) = mpWritePtr;
+       // Both callers pass a MAX_T1OPS_SIZE buffer, as does the fallback 
above.
+       mpWriteEnd = mpWritePtr + MAX_T1OPS_SIZE;
 #else
        assert( pT1Ops);
 #endif
@@ -1403,7 +1424,7 @@ mbNeedClose = false;
 mbIgnoreHints = false;
 mnHintSize=mnHorzHintSize=mnStackIdx=0; maCharWidth=-1;//#######
 mnCntrMask = 0;
-       while( mpReadPtr < mpReadEnd)
+       while( (mpReadPtr < mpReadEnd) && !mbSawError)
                convertOneTypeOp();
 //     if( bActivePath)
 //             writeTypeOp( TYPE1OP::CLOSEPATH);
@@ -1411,6 +1432,7 @@ mnCntrMask = 0;
 //             writeTypeOp( TYPE1OP::RETURN);
 if( mbSawError) {
        mpWritePtr = pT1Ops+4;
+       mbSawError = false;
        // create an "idiotproof" charstring
        writeType1Val( 0);
        writeType1Val( 800);
diff --git a/main/vcl/source/fontsubset/sft.cxx 
b/main/vcl/source/fontsubset/sft.cxx
index 4461186f0d..daee4582f4 100644
--- a/main/vcl/source/fontsubset/sft.cxx
+++ b/main/vcl/source/fontsubset/sft.cxx
@@ -254,6 +254,12 @@ _inline sal_uInt32 mkTag(sal_uInt8 a, sal_uInt8 b, 
sal_uInt8 c, sal_uInt8 d) {
     return (a << 24) | (b << 16) | (c << 8) | d;
 }
 
+/* Whether nBytes starting at nOffset lie inside a table of nSize bytes. */
+_inline bool fitsInTable(sal_uInt32 nOffset, sal_uInt32 nBytes, sal_uInt32 
nSize)
+{
+    return (nOffset <= nSize) && (nBytes <= nSize - nOffset);
+}
+
 /*- Data access macros for data stored in big-endian or little-endian format */
 _inline sal_Int16 GetInt16(const sal_uInt8 *ptr, size_t offset, int bigendian)
 {
@@ -507,7 +513,9 @@ static void GetMetrics(TrueTypeFont *ttf, sal_uInt32 
glyphID, TTGlyphMetrics *me
         metrics->lsb = GetInt16(table, 4 * glyphID + 2, 1);
     } else {
         metrics->aw  = GetUInt16(table, 4 * (ttf->numberOfHMetrics - 1), 1);
-        metrics->lsb = GetInt16(table + ttf->numberOfHMetrics * 4, (glyphID - 
ttf->numberOfHMetrics) * 2, 1);
+        const sal_uInt32 nLsbOff = ttf->numberOfHMetrics * 4 + (glyphID - 
ttf->numberOfHMetrics) * 2;
+        if (fitsInTable(nLsbOff, 2, getTableSize(ttf, O_hmtx)))
+            metrics->lsb = GetInt16(table, nLsbOff, 1);
     }
 
        table = getTable(ttf, O_vmtx);
@@ -519,7 +527,9 @@ static void GetMetrics(TrueTypeFont *ttf, sal_uInt32 
glyphID, TTGlyphMetrics *me
         metrics->tsb = GetInt16(table, 4 * glyphID + 2, 1);
     } else {
         metrics->ah  = GetUInt16(table, 4 * (ttf->numOfLongVerMetrics - 1), 1);
-        metrics->tsb = GetInt16(table + ttf->numOfLongVerMetrics * 4, (glyphID 
- ttf->numOfLongVerMetrics) * 2, 1);
+        const sal_uInt32 nTsbOff = ttf->numOfLongVerMetrics * 4 + (glyphID - 
ttf->numOfLongVerMetrics) * 2;
+        if (fitsInTable(nTsbOff, 2, getTableSize(ttf, O_vmtx)))
+            metrics->tsb = GetInt16(table, nTsbOff, 1);
     }
 }
 
@@ -539,10 +549,18 @@ static int GetSimpleTTOutline(TrueTypeFont *ttf, 
sal_uInt32 glyphID, ControlPoin
 
        if( glyphID >= ttf->nglyphs )                   /*- glyph is not 
present in the font */
                return 0;
+       /* goffsets were checked against glyf in doOpenTTFont() */
+       const sal_uInt32 nGlyphLen = ttf->goffsets[glyphID+1] - 
ttf->goffsets[glyphID];
        const sal_uInt8* ptr = table + ttf->goffsets[glyphID];
+       const sal_uInt8* const pEnd = ptr + nGlyphLen;
+       if( nGlyphLen < 10 )                                    /*- shorter 
than a glyph header */
+               return 0;
        const sal_Int16 numberOfContours = GetInt16(ptr, 0, 1);
        if( numberOfContours <= 0 )                             /*- glyph is 
not simple */
                return 0;
+       /* One end point per contour, then the two-byte instruction length. */
+       if( !fitsInTable(10, (sal_uInt32)numberOfContours * 2 + 2, nGlyphLen) )
+               return 0;
 
     if (metrics) {                                                    /*- 
GetCompoundTTOutline() calls this function with NULL metrics -*/
         metrics->xMin = GetInt16(ptr, 2, 1);
@@ -560,12 +578,24 @@ static int GetSimpleTTOutline(TrueTypeFont *ttf, 
sal_uInt32 glyphID, ControlPoin
 
        sal_uInt16 instLen = GetUInt16(ptr, 10 + numberOfContours*2, 1);
        const sal_uInt8* p = ptr + 10 + 2 * numberOfContours + 2 + instLen;
+       if( p > pEnd )                                  /*- the instructions 
leave the glyph */
+               return 0;
     ControlPoint* pa = (ControlPoint*)calloc(lastPoint+1, 
sizeof(ControlPoint));
+    if (!pa)
+        return 0;
 
     i = 0;
     while (i <= lastPoint) {
+        if (p >= pEnd) {
+            free(pa);
+            return 0;
+        }
         pa[i++].flags = (sal_uInt32) (flag = *p++);
         if (flag & 8) {                                     /*- repeat flag */
+            if (p >= pEnd) {
+                free(pa);
+                return 0;
+            }
             n = *p++;
             for (j=0; j<n; j++) {
                 if (i > lastPoint) {                        /*- if the font is 
really broken */
@@ -581,12 +611,20 @@ static int GetSimpleTTOutline(TrueTypeFont *ttf, 
sal_uInt32 glyphID, ControlPoin
     z = 0;
     for (i = 0; i <= lastPoint; i++) {
         if (pa[i].flags & 0x02) {
+            if (p >= pEnd) {
+                free(pa);
+                return 0;
+            }
             if (pa[i].flags & 0x10) {
                 z += (int) (*p++);
             } else {
                 z -= (int) (*p++);
             }
         } else if ( !(pa[i].flags & 0x10)) {
+            if (p + 2 > pEnd) {
+                free(pa);
+                return 0;
+            }
             z += GetInt16(p, 0, 1);
             p += 2;
         }
@@ -597,12 +635,20 @@ static int GetSimpleTTOutline(TrueTypeFont *ttf, 
sal_uInt32 glyphID, ControlPoin
     z = 0;
     for (i = 0; i <= lastPoint; i++) {
         if (pa[i].flags & 0x04) {
+            if (p >= pEnd) {
+                free(pa);
+                return 0;
+            }
             if (pa[i].flags & 0x20) {
                 z += *p++;
             } else {
                 z -= *p++;
             }
         } else if ( !(pa[i].flags & 0x20)) {
+            if (p + 2 > pEnd) {
+                free(pa);
+                return 0;
+            }
             z += GetInt16(p, 0, 1);
             p += 2;
         }
@@ -617,6 +663,9 @@ static int GetSimpleTTOutline(TrueTypeFont *ttf, sal_uInt32 
glyphID, ControlPoin
     return lastPoint + 1;
 }
 
+/* How deep a compound glyph may nest. */
+#define MAX_COMPOUND_DEPTH 16
+
 static int GetCompoundTTOutline(TrueTypeFont *ttf, sal_uInt32 glyphID, 
ControlPoint **pointArray, TTGlyphMetrics *metrics, std::vector< sal_uInt32 >& 
glyphlist) /*FOLD02*/
 {
     sal_uInt16 flags, index;
@@ -633,7 +682,11 @@ static int GetCompoundTTOutline(TrueTypeFont *ttf, 
sal_uInt32 glyphID, ControlPo
     if (glyphID >= ttf->nglyphs)                          /*- incorrect 
glyphID */
         return 0;
 
+    const sal_uInt32 nGlyphLen = ttf->goffsets[glyphID+1] - 
ttf->goffsets[glyphID];
     const sal_uInt8* ptr = table + ttf->goffsets[glyphID];
+    const sal_uInt8* const pEnd = ptr + nGlyphLen;
+    if (nGlyphLen < 10)                                   /*- shorter than a 
glyph header */
+        return 0;
     if ((numberOfContours = GetInt16(ptr, 0, 1)) != -1)   /*- glyph is not 
compound */
         return 0;
 
@@ -648,6 +701,8 @@ static int GetCompoundTTOutline(TrueTypeFont *ttf, 
sal_uInt32 glyphID, ControlPo
     ptr += 10;
 
     do {
+        if (ptr + 4 > pEnd)                               /*- no room for 
another component */
+            break;
         flags = GetUInt16(ptr, 0, 1);
         /* printf("flags: 0x%X\n", flags); */
         index = GetUInt16(ptr, 2, 1);
@@ -667,8 +722,13 @@ static int GetCompoundTTOutline(TrueTypeFont *ttf, 
sal_uInt32 glyphID, ControlPo
             fprintf(stderr,"]\n");
         /**/
 #endif
+            /* the component is already on the current path */
+            break;
         }
 
+        if( glyphlist.size() >= MAX_COMPOUND_DEPTH )
+            break;
+
         glyphlist.push_back( index );
 
 #ifdef DEBUG2
@@ -703,6 +763,9 @@ static int GetCompoundTTOutline(TrueTypeFont *ttf, 
sal_uInt32 glyphID, ControlPo
             if (metrics) GetMetrics(ttf, index, metrics);
         }
 
+        if (ptr + ((flags & ARG_1_AND_2_ARE_WORDS) ? 4 : 2) > pEnd)
+            break;
+
         if (flags & ARG_1_AND_2_ARE_WORDS) {
             e = GetInt16(ptr, 0, 1);
             f = GetInt16(ptr, 2, 1);
@@ -724,6 +787,16 @@ static int GetCompoundTTOutline(TrueTypeFont *ttf, 
sal_uInt32 glyphID, ControlPo
         a = d = 0x10000;
         b = c = 0;
 
+        /* The transform that may follow is two, four or eight bytes. */
+        {
+            int nXformLen = 0;
+            if (flags & WE_HAVE_A_SCALE) nXformLen = 2;
+            else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) nXformLen = 4;
+            else if (flags & WE_HAVE_A_TWO_BY_TWO) nXformLen = 8;
+            if (ptr + nXformLen > pEnd)
+                break;
+        }
+
         if (flags & WE_HAVE_A_SCALE) {
 #ifdef DEBUG2
             fprintf(stderr, "WE_HAVE_A_SCALE\n");
@@ -838,7 +911,7 @@ static int GetTTGlyphOutline(TrueTypeFont *ttf, sal_uInt32 
glyphID, ControlPoint
        const sal_uInt8* ptr = table + ttf->goffsets[glyphID];
        int length = ttf->goffsets[glyphID+1] - ttf->goffsets[glyphID];
 
-    if (length == 0) {                                      /*- empty glyphs 
still have hmtx and vmtx metrics values */
+    if (length < 2) {                                       /*- empty glyphs 
still have hmtx and vmtx metrics values */
         if (metrics) GetMetrics(ttf, glyphID, metrics);
         return 0;
     }
@@ -1390,6 +1463,82 @@ static sal_uInt32 getGlyph12(const sal_uInt8 *pCmap, 
sal_uInt32 cChar) {
 }
 
 
+/* Whether the arrays the format decoders above read lie inside cmap.
+ *
+ * nAvail is what is left of the cmap table from the start of this subtable.
+ */
+static bool cmapSubtableFits(const sal_uInt8* pSub, sal_uInt32 nAvail)
+{
+    if (nAvail < 4)
+        return false;
+
+    switch (GetUInt16(pSub, 0, 1)) {
+        case 0:
+            /* byte encoding: a 256 entry glyph array at offset 6 */
+            return nAvail >= 6 + 256;
+
+        case 2: {
+            /* high-byte mapping: 256 sub-header keys at offset 6, the
+             * sub-headers those keys address from offset 518, and for each
+             * sub-header a glyph array reached through its own idRangeOffset. 
*/
+            if (nAvail < 518)
+                return false;
+            for (sal_uInt32 i = 0; i < 256; ++i) {
+                const sal_uInt32 k = GetUInt16(pSub, 6 + 2 * i, 1) / 8;
+                const sal_uInt32 nHdr = 518 + 8 * k;
+                if (!fitsInTable(nHdr, 8, nAvail))
+                    return false;
+                const sal_uInt32 nCount = GetUInt16(pSub, nHdr + 2, 1);
+                const sal_uInt32 nRangeOff = GetUInt16(pSub, nHdr + 6, 1);
+                if (!nCount)
+                    continue;
+                /* the last entry this sub-header lets the decoder reach */
+                const sal_uInt32 nLast = nHdr + 6 + (nRangeOff / 2) * 2 + 
(nCount - 1) * 2;
+                if (!fitsInTable(nLast, 2, nAvail))
+                    return false;
+            }
+            return true;
+        }
+
+        case 4: {
+            /* segment mapping: end codes at 14, then start codes, deltas and
+             * range offsets, one array of segCount each. */
+            const sal_uInt32 nSegCount = GetUInt16(pSub, 6, 1) / 2;
+            if (!fitsInTable(16, 8 * nSegCount, nAvail))
+                return false;
+            for (sal_uInt32 i = 0; i < nSegCount; ++i) {
+                const sal_uInt32 nRangeOff = GetUInt16(pSub, 16 + 6 * 
nSegCount + 2 * i, 1);
+                if (!nRangeOff)
+                    continue;               /* answered from idDelta alone */
+                const sal_uInt32 nEnd = GetUInt16(pSub, 14 + 2 * i, 1);
+                const sal_uInt32 nStart = GetUInt16(pSub, 16 + 2 * nSegCount + 
2 * i, 1);
+                if (nEnd < nStart)
+                    continue;               /* no character falls in this 
segment */
+                /* the furthest the indirection reaches for this segment */
+                const sal_uInt32 nLast = 16 + 6 * nSegCount + 2 * i
+                                       + (nRangeOff / 2) * 2 + (nEnd - nStart) 
* 2;
+                if (!fitsInTable(nLast, 2, nAvail))
+                    return false;
+            }
+            return true;
+        }
+
+        case 6: {
+            /* trimmed table: entryCount glyphs at offset 10 */
+            if (nAvail < 10)
+                return false;
+            return fitsInTable(10, 2 * (sal_uInt32)GetUInt16(pSub, 8, 1), 
nAvail);
+        }
+
+        case 12:
+            /* segmented coverage: the subtable length has to lie inside cmap 
*/
+            return (nAvail >= 16) && (GetUInt32(pSub, 4, 1) <= nAvail);
+
+        default:
+            return false;
+    }
+}
+
 static void FindCmap(TrueTypeFont *ttf)
 {
        const sal_uInt8* table = getTable(ttf, O_cmap);
@@ -1410,15 +1559,15 @@ static void FindCmap(TrueTypeFont *ttf)
         sal_uInt16 pID, eID;
 
         /* sanity check, cmap entry must lie within table */
-        if( i*8+4 > table_size )
+        if( !fitsInTable(4 + i * 8, 8, table_size) )
             break;
 
         pID = GetUInt16(table, 4 + i * 8, 1);
         eID = GetUInt16(table, 6 + i * 8, 1);
         offset = GetUInt32(table, 8 + i * 8, 1);
 
-         /* sanity check, cmap must lie within file */
-        if( (table - ttf->ptr) + offset > (sal_uInt32)ttf->fsize )
+        /* sanity check, subtable format and length must lie within cmap */
+        if( !fitsInTable(offset, 4, table_size) )
             continue;
 
         /* Unicode tables in Apple fonts */
@@ -1470,6 +1619,12 @@ static void FindCmap(TrueTypeFont *ttf)
         ttf->cmap = 0;
     }
 
+    if (ttf->cmapType != CMAP_NOT_USABLE
+    &&  !cmapSubtableFits(ttf->cmap, table_size - (sal_uInt32)(ttf->cmap - 
table))) {
+        ttf->cmapType = CMAP_NOT_USABLE;
+        ttf->cmap = 0;
+    }
+
     if (ttf->cmapType != CMAP_NOT_USABLE) {
         switch (GetUInt16(ttf->cmap, 0, 1)) {
             case 0: ttf->mapper = getGlyph0; break;
@@ -1731,6 +1886,13 @@ static int doOpenTTFont( sal_uInt32 facenum, 
TrueTypeFont* t )
     sal_uInt32 tdoffset = 0;        /* offset to TableDirectory in a TTC file. 
For TTF files is 0 */
     int indexfmt, k;
 
+    /* The table directory header and the collection header are twelve bytes. 
*/
+    if (t->fsize < 12) {
+        CloseTTFont(t);
+        return SF_TTFORMAT;
+    }
+    const sal_uInt32 nFileSize = (sal_uInt32)t->fsize;
+
     sal_uInt32 version = GetInt32(t->ptr, 0, 1);
 
     if ((version == 0x00010000) || (version == T_true)) {
@@ -1746,6 +1908,10 @@ static int doOpenTTFont( sal_uInt32 facenum, 
TrueTypeFont* t )
             CloseTTFont(t);
             return SF_FONTNO;
         }
+        if ((sal_uInt64)12 + 4 * (sal_uInt64)facenum + 4 > 
(sal_uInt64)nFileSize) {
+            CloseTTFont(t);
+            return SF_TTFORMAT;
+        }
         tdoffset = GetUInt32(t->ptr, 12 + 4 * facenum, 1);
     } else {
         CloseTTFont(t);
@@ -1759,10 +1925,21 @@ static int doOpenTTFont( sal_uInt32 facenum, 
TrueTypeFont* t )
     /* magic number */
     t->tag = TTFontClassTag;
 
+    if (!fitsInTable(tdoffset, 12, nFileSize)) {
+        CloseTTFont(t);
+        return SF_TTFORMAT;
+    }
+
     t->ntables = GetUInt16(t->ptr + tdoffset, 4, 1);
     if( t->ntables >= 128 )
         return SF_TTFORMAT;
 
+    /* sixteen bytes per table directory entry */
+    if (!fitsInTable(tdoffset + 12, 16 * t->ntables, nFileSize)) {
+        CloseTTFont(t);
+        return SF_TTFORMAT;
+    }
+
     t->tables = (const sal_uInt8**)calloc(NUM_TAGS, sizeof(sal_uInt8*));
     assert(t->tables != 0);
     t->tlens = (sal_uInt32*)calloc(NUM_TAGS, sizeof(sal_uInt32));
@@ -1799,6 +1976,11 @@ static int doOpenTTFont( sal_uInt32 facenum, 
TrueTypeFont* t )
         if( nIndex >= 0 ) {
             sal_uInt32 nTableOffset = GetUInt32(t->ptr + tdoffset + 12, 16 * i 
+ 8, 1);
             length = GetUInt32(t->ptr + tdoffset + 12, 16 * i + 12, 1);
+            /* clamp the table to the file */
+            if( nTableOffset > nFileSize )
+                continue;
+            if( length > nFileSize - nTableOffset )
+                length = nFileSize - nTableOffset;
             t->tables[nIndex] = t->ptr + nTableOffset;
             t->tlens[nIndex] = length;
         }
@@ -1809,6 +1991,8 @@ static int doOpenTTFont( sal_uInt32 facenum, 
TrueTypeFont* t )
         sal_uInt8* pHead = (sal_uInt8*)t->tables[O_head];
         if( !pHead )
             return SF_TTFORMAT;
+        if( nFileSize < 54 )
+            return SF_TTFORMAT;
         /* limit Head candidate to TTC extract's limits */
         if( pHead > t->ptr + (t->fsize - 54) )
             pHead = t->ptr + (t->fsize - 54);
@@ -1864,7 +2048,12 @@ static int doOpenTTFont( sal_uInt32 facenum, 
TrueTypeFont* t )
      * maxp, head, name, cmap
      */
 
-    if( !(getTable(t, O_maxp) && getTable(t, O_head) && getTable(t, O_name) && 
getTable(t, O_cmap)) ) {
+    /* Long enough for the fields read below: numGlyphs at maxp 4, unitsPerEm
+     * at head 18, indexToLocFormat at head 50, the subtable count at cmap 2. 
*/
+    if( !(getTable(t, O_maxp) && getTableSize(t, O_maxp) >= 6 &&
+          getTable(t, O_head) && getTableSize(t, O_head) >= 52 &&
+          getTable(t, O_name) &&
+          getTable(t, O_cmap) && getTableSize(t, O_cmap) >= 4) ) {
         CloseTTFont(t);
         return SF_TTFORMAT;
     }
@@ -1883,6 +2072,11 @@ static int doOpenTTFont( sal_uInt32 facenum, 
TrueTypeFont* t )
 
        if( getTable(t, O_glyf) && getTable(t, O_loca) ) {  /* TTF or 
TTF-OpenType */
                k = (getTableSize(t, O_loca) / (indexfmt ? 4 : 2)) - 1;
+               /* loca has to hold at least two offsets */
+               if( k < 0 ) {
+                       CloseTTFont(t);
+                       return SF_TTFORMAT;
+               }
                if( k < (int)t->nglyphs )       /* Hack for broken Chinese 
fonts */
                        t->nglyphs = k;
 
@@ -1892,6 +2086,17 @@ static int doOpenTTFont( sal_uInt32 facenum, 
TrueTypeFont* t )
 
                for( i = 0; i <= (int)t->nglyphs; ++i )
                        t->goffsets[i] = indexfmt ? GetUInt32(table, i << 2, 1) 
: (sal_uInt32)GetUInt16(table, i << 1, 1) << 1;
+
+               /* Stop at the first offset outside glyf or below the one 
before it;
+                * the glyphs ahead of it stay usable. */
+               const sal_uInt32 nGlyfLen = getTableSize(t, O_glyf);
+               for( i = 0; i <= (int)t->nglyphs; ++i ) {
+                       if( t->goffsets[i] > nGlyfLen
+                       ||  (i > 0 && t->goffsets[i] < t->goffsets[i-1]) ) {
+                               t->nglyphs = (i > 0) ? (sal_uInt32)(i - 1) : 0;
+                               break;
+                       }
+               }
     } else if( getTable(t, O_CFF) ) {                  /* PS-OpenType */
         t->goffsets = (sal_uInt32 *) calloc(1+t->nglyphs, sizeof(sal_uInt32));
         /* TODO: implement to get subsetting */
@@ -1901,11 +2106,17 @@ static int doOpenTTFont( sal_uInt32 facenum, 
TrueTypeFont* t )
         return SF_TTFORMAT;
     }
 
+    /* numberOfHMetrics is at offset 34 of a 36-byte table, and hmtx has four
+     * bytes per long metric.  Same for the vertical pair. */
     table = getTable(t, O_hhea);
-    t->numberOfHMetrics = (table != 0) ? GetUInt16(table, 34, 1) : 0;
+    t->numberOfHMetrics = (table != 0 && getTableSize(t, O_hhea) >= 36) ? 
GetUInt16(table, 34, 1) : 0;
+    if( t->numberOfHMetrics > getTableSize(t, O_hmtx) / 4 )
+        t->numberOfHMetrics = getTableSize(t, O_hmtx) / 4;
 
     table = getTable(t, O_vhea);
-    t->numOfLongVerMetrics = (table != 0) ? GetUInt16(table, 34, 1) : 0;
+    t->numOfLongVerMetrics = (table != 0 && getTableSize(t, O_vhea) >= 36) ? 
GetUInt16(table, 34, 1) : 0;
+    if( t->numOfLongVerMetrics > getTableSize(t, O_vmtx) / 4 )
+        t->numOfLongVerMetrics = getTableSize(t, O_vmtx) / 4;
 
     GetNames(t);
     FindCmap(t);
@@ -2769,14 +2980,22 @@ GlyphData *GetTTRawGlyphData(TrueTypeFont *ttf, 
sal_uInt32 glyphID)
     sal_uInt32 length = getTableSize( ttf, O_glyf );
     if( length < ttf->goffsets[ glyphID+1 ] )
         return 0;
+    if( ttf->goffsets[ glyphID+1 ] < ttf->goffsets[ glyphID ] )
+        return 0;
 
     length = ttf->goffsets[glyphID+1] - ttf->goffsets[glyphID];
 
-    GlyphData* d = (GlyphData*)malloc(sizeof(GlyphData)); assert(d != 0);
+    GlyphData* d = (GlyphData*)malloc(sizeof(GlyphData));
+    if (d == 0)
+        return 0;
 
     if (length > 0) {
         const sal_uInt8* srcptr = glyf + ttf->goffsets[glyphID];
-        d->ptr = (sal_uInt8*)malloc((length + 1) & ~1); assert(d->ptr != 0);
+        d->ptr = (sal_uInt8*)malloc((length + 1) & ~1);
+        if (d->ptr == 0) {
+            free(d);
+            return 0;
+        }
         memcpy( d->ptr, srcptr, length );
         d->compflag = (GetInt16( srcptr, 0, 1 ) < 0);
     } else {
@@ -2804,12 +3023,19 @@ GlyphData *GetTTRawGlyphData(TrueTypeFont *ttf, 
sal_uInt32 glyphID)
     }
 
     /* get advance width and left sidebearing */
-    if (glyphID < ttf->numberOfHMetrics) {
-        d->aw = GetUInt16(hmtx, 4 * glyphID, 1);
-        d->lsb = GetInt16(hmtx, 4 * glyphID + 2, 1);
-    } else {
-        d->aw = GetUInt16(hmtx, 4 * (ttf->numberOfHMetrics - 1), 1);
-        d->lsb  = GetInt16(hmtx + ttf->numberOfHMetrics * 4, (glyphID - 
ttf->numberOfHMetrics) * 2, 1);
+    /* the same checks GetMetrics() makes */
+    d->aw = 0;
+    d->lsb = 0;
+    if (hmtx && ttf->numberOfHMetrics) {
+        if (glyphID < ttf->numberOfHMetrics) {
+            d->aw = GetUInt16(hmtx, 4 * glyphID, 1);
+            d->lsb = GetInt16(hmtx, 4 * glyphID + 2, 1);
+        } else {
+            const sal_uInt32 nLsbOff = ttf->numberOfHMetrics * 4 + (glyphID - 
ttf->numberOfHMetrics) * 2;
+            d->aw = GetUInt16(hmtx, 4 * (ttf->numberOfHMetrics - 1), 1);
+            if (fitsInTable(nLsbOff, 2, getTableSize(ttf, O_hmtx)))
+                d->lsb = GetInt16(hmtx, nLsbOff, 1);
+        }
     }
 
     return d;

Reply via email to