locked
The bug in MFC tool tip control

    General discussion

  • Yesterday I encountered with the bug in MFC’s tool tip control implementation.

    The bug dozes in the following code:

    LRESULT CToolTipCtrl::OnAddTool(WPARAM wParam, LPARAM lParam)
    {
        TOOLINFO ti = *(LPTOOLINFO)lParam;     <----- HERE

        if ((ti.hinst == NULL) && (ti.lpszText != LPSTR_TEXTCALLBACK)
            && (ti.lpszText != NULL))
        {
            void* pv;
            if (!m_mapString.Lookup(ti.lpszText, pv))
                m_mapString.SetAt(ti.lpszText, NULL);
            // set lpszText to point to the permanent memory associated
            // with the CString
            VERIFY(m_mapString.LookupKey(ti.lpszText, ti.lpszText));
        }
        return DefWindowProc(TTM_ADDTOOL, wParam, (LPARAM)&ti);
    }

     

    If you look to definition of TOOLINFO structure closely, you’ll find that this structure has variable size which depends of the compilation macroses defined:

    typedef struct {
      UINT      cbSize;
      UINT      uFlags;
      HWND      hwnd;
      UINT_PTR  uId;
      RECT      rect;
      HINSTANCE hinst;
      LPTSTR    lpszText;
    #if (_WIN32_IE >= 0x0300)
      LPARAM    lParam;
    #endif
    #if (_WIN32_WINNT >= Ox0501)
      void      *lpReserved;
    #endif
    } TOOLINFO, *PTOOLINFO, *LPTOOLINFO;

     

    That is, a structure pointed to by lParam can have size from 40 to 48 bytes long depending of the compilation macroses defined and can’t be just copied to structure created inside the MFC module. Dealing with variable-size structures you can copy only a common part of a structure or analyze its version first (that is the cbSize field was made for!).

    We can easily figure out what TOOLINFO’s size expected by MFC:

    mfc71!CToolTipCtrl::OnAddTool:
    7c1a21e5 55               push    ebp
    7c1a21e6 8bec           mov     ebp,esp
    7c1a21e8 83ec30    sub     esp,30h

    7c1a21eb 53               push    ebx
    7c1a21ec 56               push    esi
    7c1a21ed 8b750c       mov     esi,dword ptr [ebp+0Ch]
    7c1a21f0 57               push    edi
    7c1a21f1 8bd9           mov     ebx,ecx
    7c1a21f3 6a0c         push    0Ch
    7c1a21f5 59             pop     ecx
    7c1a21f6 8d7dd0       lea     edi,[ebp-30h]

    7c1a21f9 f3a5         rep movs dword ptr es:[edi],dword ptr [esi]

     

    The size of TOOLINFO inside the MFC module is equal to 0xc*4 = 0x30 = 48 bytes. And the size of TOOLINFO structure inside our product’s module was equal to 44 bytes!

    Of course, in most cases nothing criminal will happen. CToolTipCtrl::OnAddTool just copies 4 extra bytes – it’s eventually not even use it. The only case when the problem may arise – if those 4 extra bytes don’t have read access. Though this situation is hardly possible if your TOOLINFO structure resides on the stack, what about heap memory? With probability far from zero your structure can occupy last free bytes in a memory page and next page can be not commited yet. This is exactly the case which I encountered with. As a result – access violation.

    Note that the problem can appear only if you allocate memory for TOOLINFO structure inside your own module, that is, use SendMessage approach .

    I found the bug in MFC 7.1 library, but it’s not fixed even in MFC 10.0.

     

    The ways to workaround

    There are several obvious ways to workaround the problem:

    1.    Do not use SendMessage approach to register a tool with a tool tip control. Use CToolTipCtrl::AddTool method instead.

     

    In case if you cannot avoid SendMessage approach (for static controls for example) you can do the following:

    2.    Compile your module which uses tool tip control with all macroses available (_WIN32_IE and _WIN32_WINNT).

    3.    Reserve extra bytes (4 or 8, depending of your options) which can be copied without impact.

     

    For example like this:

    // Extend TOOLINFO on 8 bytes
    struct TOOLINFO_EX : public TOOLINFO
    {
      DWORD m_dwReserve1;
      DWORD m_dwReserve2;
    };

    // Allocate memory from heap (remember, it’s just example ?)

    TOOLINFO* pti = new TOOLINFO_EX;

    // Initialization


    // Register a tool

    toolTipCtrl.SendMessage(TTM_ADDTOOL, 0, (LPARAM) pti);

    Friday, September 10, 2010 1:10 PM

All replies

  • Hello Andrey,

    Thanks for reporting this issue.  I had never heard of this problem previously.  I will investigate adding a fix for a future version of MFC.

    Of course I would recommend your workaround 2) above, but your other workarounds should work just as well.


    Pat Brenner, Visual C++ Libraries Development
    Tuesday, September 14, 2010 11:08 PM