Yesterday we noticed a blog post and securityfocus article about a potential new vulnerability in Microsoft GDI+ when parsing a specially-crafted EMF file. You might have heard about it referred to as ‘GpFont.SetData()’. We wanted to address some speculation about this EMF parsing bug.

First, our initial investigation shows that it is not exploitable for code execution. We are still investigating all the potential ways to hit this code but in all the common cases so far, our /GS mitigation is an effective defense-in-depth measure. The EMF parsing bug ends up writing 0x0000, a single Unicode 0 character, over the lower two bytes of the /GS security cookie. Only those two bytes are overwritten and the application is terminated due to the /GS failure.

Here’s where the issue occurs:

0:000> k
ChildEBP RetAddr  
0007e37c 4ec9e783 gdiplus!GpFont::SetData+0x6a
0007e3a0 4ec9e70f gdiplus!MetafilePlayer::AddObject+0x8f
0007e3b4 4ec9d1b5 gdiplus!ObjectEPR::Play+0x1a
0007e3d0 4ec9ce34 gdiplus!GdipPlayMetafileRecordCallback+0x35
0007e3fc 4ec9cd4e gdiplus!MetafilePlayer::EnumerateEmfPlusRecords+0x66
0007e414 77f2072f gdiplus!EnumEmfWithDownLevel+0x52
0007e490 4ec9e625 gdi32!bInternalPlayEMF+0x707 
0007edd4 4eca0c90 gdiplus!MetafilePlayer::EnumerateEmfRecords+0xd7
0007ee70 4ec9e67f gdiplus!GpGraphics::EnumEmfPlusDual+0x27d
0007ef90 4ed03350 gdiplus!GpMetafile::EnumerateForPlayback+0x686
0007efc0 4ec9bb58 gdiplus!GpMetafile::Play+0x26

The gdiplus!GpFont::SetData code shows an obvious off-by-one bug.  This was subsequently fixed before Vista shipped.  Here’s the Windows XP code:

#define FamilyNameMax 32
...
WCHAR familyName[FamilyNameMax];
...
 length = fontData->Length; // this comes from the EMF file
...
if (length > FamilyNameMax)
{
 length = FamilyNameMax;
}
...
// read in the familyName/data
UnicodeStringCopyCount (familyName, (WCHAR *)dataBuffer, length);
familyName[length]=0;

Here’s what it looks like from the assembly:

gdiplus!GpFont::SetData:
4ecff9e9 8bff            mov     edi,edi
4ecff9eb 55              push    ebp
4ecff9ec 8bec            mov     ebp,esp
4ecff9ee 83ec44          sub     esp,44h
4ecff9f1 a10000dd4e      mov     eax,dword ptr [gdiplus!__security_cookie (4edd0000)] ds:0023:4edd0000=00006ea3 
4ecff9f6 53              push    ebx
4ecff9f7 8945fc          mov     dword ptr [ebp-4],eax // Security cookie saved in ebp-4
4ecff9fa 8b4508          mov     eax,dword ptr [ebp+8]

You can see from the top of the function that the security cookie is 00006ea3. Now let's look lower.

4ecffa29 8b4804          mov     ecx,dword ptr [eax+4]
4ecffa2c 894f10          mov     dword ptr [edi+10h],ecx
4ecffa2f 8b4808          mov     ecx,dword ptr [eax+8]
4ecffa32 894f18          mov     dword ptr [edi+18h],ecx
4ecffa35 8b480c          mov     ecx,dword ptr [eax+0Ch]
4ecffa38 894f14          mov     dword ptr [edi+14h],ecx
4ecffa3b 8b7014          mov     esi,dword ptr [eax+14h]
4ecffa3e 8d4c3618        lea     ecx,[esi+esi+18h]
4ecffa42 83c018          add     eax,18h
4ecffa45 394d0c          cmp     dword ptr [ebp+0Ch],ecx
4ecffa48 0f8283000000    jb      gdiplus!GpFont::SetData+0xe8 (4ecffad1)
4ecffa4e 83fe20          cmp     esi,20h
4ecffa51 7603            jbe     gdiplus!GpFont::SetData+0x6d (4ecffa56)
4ecffa53 6a20            push    20h
4ecffa55 5e              pop     esi
4ecffa56 56              push    esi
4ecffa57 50              push    eax
4ecffa58 8d45bc          lea     eax,[ebp-44h] //0x20 WCHAR buffer
4ecffa5b 50              push    eax
4ecffa5c e8d7750600      call    gdiplus!GpRuntime::UnicodeStringCopyCount (4ed67038)
4ecffa61 a15009dd4e      mov     eax,dword ptr [gdiplus!Globals::FontCollection (4edd0950)]
4ecffa66 66895c75bc      mov     word ptr [ebp+esi*2-44h],bx 
4ecffa6b 8b7008          mov     esi,dword ptr [eax+8]
4ecffa6e 395e08          cmp     dword ptr [esi+8],ebx

If we break in just before that string copy, we see the following:

eax=00913658 ebx=00000000 ecx=00000020 edx=05aa010c esi=00000020 edi=0091c798
eip=4ecffa66 esp=0007e32c ebp=0007e37c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
gdiplus!GpFont::SetData+0x7d:
4ecffa66 66895c75bc      mov     word ptr [ebp+esi*2-44h],bx ss:0023:0007e378=6ea3 

Remember from the assembly above that the security cookie was 00006ea3. Here’s how memory looks before the mov instruction:

0:000> dd ebp-0x10
0007e36c  44332211 001d0b0b 44332211 00006ea3
0007e37c  0007e3a0 4ec9e783 05aa00b4 00000024
0007e38c  0091e008 0091e008 00000030 0091e001
0007e39c  0091e008 0007e3b4 4ec9e70f 00000000
0007e3ac  05aa00b4 00000024 0007e3d0 4ec9d1b5
0007e3bc  0091e008 00004008 00000600 00000024
0007e3cc  05aa00a8 0007e3fc 4ec9ce34 00004008
0007e3dc  00000600 00000024 05aa00b4 0091e008

You can see the cookie on the stack protecting the return address. And then after the copy it is overwritten:

0:000> dd ebp-0x10
0007e36c  44332211 001d0b0b 44332211 00000000
0007e37c  0007e3a0 4ec9e783 05aa00b4 00000024
0007e38c  0091e008 0091e008 00000030 0091e001
0007e39c  0091e008 0007e3b4 4ec9e70f 00000000
0007e3ac  05aa00b4 00000024 0007e3d0 4ec9d1b5
0007e3bc  0091e008 00004008 00000600 00000024
0007e3cc  05aa00a8 0007e3fc 4ec9ce34 00004008
0007e3dc  00000600 00000024 05aa00b4 0091e008

As always, we encourage responsible disclosure of potential vulnerabilities. Best way to get ahold of us is secure@microsoft.com. Thanks.

- Jonathan Ness, MSRC Engineering

*Postings are provided "AS IS" with no warranties, and confers no rights.*