FIX: CArchive May Corrupt Memory or Write Incorrect Data

Last reviewed: September 18, 1997
Article ID: Q133433
1.00 1.50 1.51 1.52 WINDOWS kbprg kbbuglist kbfixlist

The information in this article applies to:

  • The Microsoft Foundation Classes (MFC) included with: Microsoft Visual C++ for Windows, versions 1.0, 1.5, 1.51, 1.52

SYMPTOMS

The CArchive insertion operator (CArchive::operator<<) may, in rare circumstances, write incorrect data to its associated CFile or may overwrite other application memory. Likewise, the CArchive extraction operator (CArchive::operator>>) may incorrectly read data from its CFile object.

CAUSE

The CArchive class uses buffering for all of its operations. When extracting data from the archive, it maintains a pointer to the current location in the buffer. When the pointer goes past the end of the currently retrieved buffer, it retrieves another block of data from the associated CFile object. The extraction operators for CArchive do not properly check for the condition where the buffer pointer reaches the end of the block. In some cases, the pointer wraps to the beginning of its segment. Later calls to extract data from the CArchive retrieve the data from the wrong memory address.

The CArchive class uses a similar procedure when data is inserted into the CArchive. The "More Information" section of this article gives more details.

RESOLUTION

Below are three approaches to resolving this problem:

  • If you construct your own CArchive objects, you can allocate the buffer yourself and ensure that the error condition is never met. This can be accomplished by allocating a buffer eight bytes larger than actually needed as in this example:

          char *pBuf = new char[520];
          CArchive *pArcLoad = new CArchive(&file,CArchive::load,512,pBuf);
          // Use the archive in here
          delete pArcLoad;
          delete pBuf;
    

    NOTE: This code allocates a block of 520 bytes but only uses 512 of them in the CArchive. The reason for leaving a gap of eight bytes is because the default CArchive insertion or extraction operators take operands with a maximum size of eight bytes (double). By allocating a buffer eight bytes greater than necessary, you prevent m_lpBufCur from ever getting within eight bytes of 0xFFFF so it is never wrapped back to 0 on the addition check in the 'if' statement.

    -or-

  • The first technique could be used with GlobalAlloc. Because the offset of a pointer returned from GlobalAlloc is always zero, the boundary error won't occur unless you allocate a buffer of nearly 0xFFFF in size. Here's an example using GlobalAlloc:

          HGLOBAL hGlob = ::GlobalAlloc(GMEM_FIXED,512);
          LPVOID pBuf = ::GlobalLock(hGlob);
          CArchive *pArcLoad = new CArchive(&file,CArchive::load,512,pBuf);
          // Use the archive in here
          delete pArcLoad;
          ::GlobalUnlock(hGlob);
          ::GlobalFree(hGlob);
    

    NOTE: This piece of code and the previous one destroy the CArchive object before the memory buffer. If the buffer is destroyed before the CArchive object, earlier versions of Visual C++ may cause an assertion failure (which can be safely ignored). For more information about this problem, please see the following article in the Microsoft Knowledge Base:

          ARTICLE-ID: Q128113
    
          TITLE     : FIX: Assertion Failed Line 178 or Line 527 in ARCCORE.CPP
    
       -or-
    
    
  • CDocument, COleDocument, and CRecordset all use CArchive. You could create a CArchive-derived class to work around the problem, but you need to change all references to it in these classes to references to the fixed CArchive-derived class. If you do not want to do this, the simplest approach may be to re-build the MFC libraries with a fix to the CArchive class. The changes to the CArchive class would be in the 'if' statements for all of the CArchive::operator<< and CArchive::operator>> functions. These functions can all be found in AFX.INL (located in the directory <msvc>\MFC\INCLUDE). The "More Information" section of this article gives more detail as to why these specific changes would be needed. Following is an example of the change that would be necessary:

    Change this:

          _AFX_INLINE CArchive& CArchive::operator<<(WORD w)
          { if (m_lpBufCur + sizeof(WORD) > m_lpBufMax) Flush();
    
              *(WORD FAR*)m_lpBufCur = w; m_lpBufCur += sizeof(WORD);
              return *this; }
    
       To this:
    
          _AFX_INLINE CArchive& CArchive::operator<<(WORD w)
            { if (m_lpBufCur > m_lpBufMax - sizeof(WORD)) Flush();
                *(WORD FAR*)m_lpBufCur = w; m_lpBufCur += sizeof(WORD);
                return *this; }
    
       The only change is in the 'if' statement.
    
       For details on re-building the MFC libraries, please see Chapter 15 of
       the Class Library User's Guide and the README.TXT file located in the
       <msvc>\MFC\SRC directory.
    
       NOTE: If you rebuild the DLL version of the library (MFC250(D).DLL), you
       should also rename the DLL and the library to prevent conflicts with
       other versions.
    
    

STATUS

Microsoft has confirmed this to be a bug in the Microsoft products listed at the beginning of this article. This problem has been corrected in the 16- bit component that comes with Visual C++ version 2.2.

MORE INFORMATION

The problem with the CArchive insertion and extraction operators is in the way they check the pointer to the current location in the buffer (m_lpBufCur). For example, CArchive::operator<<(WORD w) is defined as:

   _AFX_INLINE CArchive& CArchive::operator<<(WORD w)
     { if (m_lpBufCur + sizeof(WORD) > m_lpBufMax) Flush();
         *(WORD FAR*)m_lpBufCur = w; m_lpBufCur += sizeof(WORD);
         return *this; }

It is possible that an allocated buffer could end up with a memory block ending address close to <s>:0xFFFF. As call insert data into your buffer consecutively, m_lpBufCur approaches the end of the address block that might be somewhere near <s>:0xFFFF.

For example, consider this scenario:

   m_lpBufCur==<s>:0xFFFE

In this scenario, the 'if' statement would evaluate as:

   if (<s>:0xFFFE + 2 > <s>:0xFFFE) Flush();

Because both pointers are far pointers this is equivalent to:

   if ( <s>:0x0000 > <s>:0xFFFE ) Flush();

This evaluation is not desirable. It appears that there is enough room left in the buffer to put the WORD into it and then m_lpBufCur is set to <s>:0x0000. Because the beginning of the buffer may not have been at the beginning of the segment, subsequent operations will begin to write at whatever data happens to be located at <s>:0x0000 on up to the beginning of the buffer.

The same boundary checks exist for the extraction operators. When m_lpBufCur is set back to 0x0000, all further extraction operators get data from the wrong location.

IMPORTANT NOTE: Because the problem occurs only when a particular memory block is allocated at a very specific address, the odds of encountering this problem are very low. There are cases where the layout of your application's heap and the thread of execution of the code causes this to occur every time you run your application, but if you are experiencing memory corruption, it is unlikely that this is the cause, so you shouldn't necessarily expect these workarounds to take care of the problem.


Additional reference words: 1.00 1.50 1.51 1.52 2.00 2.50 2.51 2.52
StoreFields LoadFields SaveToStorage LoadFromStorage
KBCategory: kbprg kbbuglist kbfixlist
KBSubcategory: MfcMisc
Keywords : MfcMisc kbbuglist kbfixlist kbprg
Technology : kbMfc
Version : 1.00 1.50 1.51 1.52
Platform : WINDOWS
Solution Type : kbfix


THE INFORMATION PROVIDED IN THE MICROSOFT KNOWLEDGE BASE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. MICROSOFT DISCLAIMS ALL WARRANTIES, EITHER EXPRESS OR IMPLIED, INCLUDING THE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL MICROSOFT CORPORATION OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER INCLUDING DIRECT, INDIRECT, INCIDENTAL, CONSEQUENTIAL, LOSS OF BUSINESS PROFITS OR SPECIAL DAMAGES, EVEN IF MICROSOFT CORPORATION OR ITS SUPPLIERS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES SO THE FOREGOING LIMITATION MAY NOT APPLY.

Last reviewed: September 18, 1997
© 1998 Microsoft Corporation. All rights reserved. Terms of Use.