1

I'm working with an in-house MFC application which attempts to programmatically change the font size of a dialog by finding it in a DLGTEMPLATEEX structure obtained using the Win32 LoadResource function and writing directly to the font size member of that structure. When running under the Debugger in VS 2019, this triggers a memory write access violation.

BOOL CMk20Dlg::Create(UINT nIDTemplate, CWnd* pParentWnd)
{
    LPCTSTR lpszTemplateName = MAKEINTRESOURCE(nIDTemplate);

    ASSERT(IS_INTRESOURCE(lpszTemplateName) || AfxIsValidString(lpszTemplateName));

    m_lpszTemplateName = lpszTemplateName;
    if (IS_INTRESOURCE(m_lpszTemplateName) && m_nIDHelp == 0)
        m_nIDHelp = LOWORD(reinterpret_cast<DWORD_PTR>(m_lpszTemplateName));

    HINSTANCE hInst = AfxFindResourceHandle(lpszTemplateName, RT_DIALOG);
    HRSRC hResource = ::FindResource(hInst, lpszTemplateName, RT_DIALOG);
    if (hResource == NULL)
        TRACE(_T("FindResource failed with error %d\n"), ::GetLastError());

    HGLOBAL hTemplate = ::LoadResource(hInst, hResource);
    DLGTEMPLATE* lpTemplate = static_cast<DLGTEMPLATE*>(::LockResource(hTemplate));

    WORD* pwFontSize = GetResFontSizeOffset(lpTemplate);
    *pwFontSize = static_cast<WORD>((8.0 * theApp.GetFontScaleFactor()) + 0.5);
    // ^-- crash on write to *pwFontSize

I believe the code is finding the correct target memory location in the returned structure, because the structure has the correct signature at the expected offset (0xFFFF identifying it as a DLGTEMPLATEEX not just a DLGTEMPLATE -- although the code doesn't have any error checking for if it were not), and I single-stepped the code which computes the pointer to the font size member. It points to a DWORD of value "8" which seems reasonable for the system font size.

WORD* CMk20Dlg::GetResFontSizeOffset(DLGTEMPLATE* lpTemplate)
{
   WORD* pwPtr = reinterpret_cast<WORD*>(lpTemplate);

    // lpTemplate is in the format of a DLGTEMPLATEEX
    // Refer Win32 SDK documentation of "struct" DLGTEMPLATEEX
    ++pwPtr;        // dlgVer
    ++pwPtr;        // signature
    /* ^-- At this point *pwPtr has value 0xFFFF in the debugger, indicating DLGTEMPLATEEX */

     /* The ++'ing of pwPtr continues through the rest of the struct members,
        then it skips over the menu array, window class array, title, etc.  Finally: */

    // We are now pointing at the font size word
    return pwPtr;
}

The return value points at a DWORD "8" when the code tries to actually change that "8" to another number by writing through the pointer, it crashes in the Visual Studio 2019 debugger with a memory "write access violation".

I have many concerns about what I'm seeing here.

First of all, does clobbering this memory location like this even accomplish anything? How does the OS see that the value changed? I would expect the correct way to do it to be some Win32 function call like "SetDlgFontSize" or something, not spelunking around in data structures.

Second, how did this code ever work at all? Is it something that worked on older versions of Windows, but not on Windows 10? Is it failing now because I upgraded the project to VS 2019 (previously it was up to 2012). Or is the VS 2019 Debugger (or Debug build) pre-emptively setting a write-protect bit on memory pages with operating system structures I shouldn't be writing?

Third, I need to decide whether to fix this by commenting out the crashing code or setting the font size "properly." That depends on whether the offending code actually ever had an effect. If it didn't I can comment it out, but if I need to replicate the effect correctly the only MFC code I've found so far for doing this sets the font size and font family together. So then I'd need to also add code to find out the correct font...

Dennis
  • 2,607
  • 3
  • 21
  • 28
  • 3
    I expect that the value returned from `LoadResource`/`LockResource` is essentially a pointer to the resource data contained in the executable/DLL, and that it would not be writable (but I haven't found any documentation to that effect with a quick search). – 1201ProgramAlarm Oct 29 '19 at 17:52
  • 1
    I've modified data in the pointer returned and not had any bad effects, but I've always used LockResource() after calling LoadResource(). You could look at the implementation of CDialogTemplate in MFC. It's declared in . Implementation is in the file dlgtempl.cpp in the MFC source. – Joseph Willcoxson Oct 29 '19 at 19:42
  • see this [link](https://stackoverflow.com/a/58347597/4603670) if you just want to change the dialog font – Barmak Shemirani Oct 30 '19 at 11:27

1 Answers1

3

As long as the dialog in question is created after the clobber it makes sense that doing this would change the template that is used to create the dialog (so long as the write is allowed by memory permissions), however I would expect that resource memory is meant to be read-only and so I am surprised that this ever worked without an access violation.

It work(ed) because the dialog template is read each time the dialog is created. If the dialog is already created at the time this sequence is undertaken then it should have no effect.

It would make much more sense to load the resource (via FindResource + LoadResource + LockResource) copying the entire dialog template to an explicitly allocated buffer, make any needed changes then use DialogBoxIndirect or DialogBoxIndirectParam to instantiate the dialog from the in-memory template (rather than from a resource ide identifier). Making a copy like that would side-step the possibility of memory permission issues.

I suppose you could also use VirtualProtect and make the page(s) in question writable but that strikes me as a nasty way to solve this kind of problem.

SoronelHaetir
  • 14,104
  • 1
  • 12
  • 23
  • 2
    A blog post by Raymond Chen, not about this but about Property Sheets, includes the statement, "When the property sheet manager writes to the dialog template to fix the style, a first-chance exception is raised because resources start out as read-only pages." https://devblogs.microsoft.com/oldnewthing/?p=28493 – Dennis Oct 30 '19 at 15:20