Microsoft Compiler mspdbcore.dll heap memory disclosure into output .pdb files, affects Microsoft Symbol Server CVE-2018-1037 Wikipedia describes the PDB file format as follows [1]: --- cut --- Program database (PDB) is a proprietary file format (developed by Microsoft) for storing debugging information about a program (or, commonly, program modules such as a DLL or EXE). PDB files commonly have a .pdb extension. A PDB file is typically created from source files during compilation. --- cut --- Internally, the Microsoft build environment (Visual Studio etc.) uses mspdbcore.dll to generate .pdb files corresponding to the output executables created during compilation. Parts of that library were made open-source by Microsoft in the microsoft-pdb GitHub project [2]. This makes it easier to read and understand portions of code relevant to the vulnerability discussed here. We have discovered that under certain circumstances, the library may disclose 3kB (0xC00 bytes) of uninitialized heap memory at offset 0x400 of the output PDB file, in one continuous chunk. We have reproduced the behavior with Visual Studio 2015 and version 14.0.24210.0 of the mspdbcore.dll file. The source file we're mostly interested in is msf.cpp [3]. It includes the declaration of a MSF_HB class, which is a container for the overall PDB. It stores the structures corresponding to the file header in the following union: --- cut --- 1015 union { 1016 MSF_HDR hdr; 1017 BIGMSF_HDR bighdr; 1018 }; --- cut --- Both MSF_HDR and BIGMSF_HDR structures contain a .cbPg field, which indicates the size of the smallest unit of data in the PDB file, a "page" (the header itself is also a page): --- cut --- 933 union MSF_HDR { // page 0 934 struct { ... 936 CB cbPg; // page size ... 942 }; 943 PG pg; 944 }; 945 946 union BIGMSF_HDR { // page 0 (and more if necessary) 947 struct { ... 949 CB cbPg; // page size ... 955 }; 956 PG pg; 957 }; --- cut --- Two most typical page sizes are 0x400 (1024) and 0x1000 (4096). When a PDB file is created for the first time, the MSF_HB::afterCreate method is called to initialize the internal structures. The prologue of the method body is as follows: --- cut --- 1662 BOOL MSF_HB::afterCreate(MSF_EC* pec, CB cbPage) { 1663 // init hdr; when creating a new MSF, always create the BigMsf variant. 1664 memset(&bighdr, 0, sizeof bighdr); 1665 memcpy(&bighdr.szMagic, szBigHdrMagic, sizeof szBigHdrMagic); 1666 bighdr.cbPg = cbPage; --- cut --- We can see that the "bighdr" structure (of size 0x1000) is zero'ed out at the very beginning. This means that regardless of whether the page size ends up being 0x400 or 0x1000 (as specified by the function caller), no uninitialized data prevails in the internal structures. In our test environment, cbPage is set to 0x1000 at that point of execution. However, when a PDB file already exists on disk and is only updated, the MSF_HB::afterOpen method is called instead. It starts with the following code: --- cut --- 1519 BOOL MSF_HB::afterOpen( MSF_EC* pec ) { 1520 // VSWhidbey:600553 1521 fBigMsf = true; // This is arbitrary, and will be overwritten in fValidHdr(). 1522 // We do this to avoid uninitialized reads of this variable in pnMac(). 1523 pnMac(1); // extantPn(pnHdr) must be TRUE for first readPn()! 1524 msfparms = rgmsfparms[0]; // need min page size set here for initial read. 1525 1526 if (!readPn(pnHdr, &hdr)) { 1527 if (pec) { 1528 *pec = MSF_EC_FILE_SYSTEM; 1529 } 1530 pIStream = NULL; 1531 return FALSE; 1532 } --- cut --- The rgmsfparms array is defined as follows: --- cut --- 150 const CB cbPgMax = 0x1000; 151 #ifdef SMALLPAGES 152 const CB cbPgMin = 0x200; 153 #else 154 const CB cbPgMin = 0x400; 155 #endif ... 196 const MSFParms rgmsfparms[] = { 197 #ifdef SMALLPAGES 198 MSF_PARMS(1024, 10, pnMaxMax, 8, 8), // gives 64meg 199 #else 200 MSF_PARMS(cbPgMin, 10, pnMaxMax, 8, 8), // gives 64meg (??) 201 #endif 202 MSF_PARMS(2048, 11, pnMaxMax, 4, 4), // gives 128meg 203 MSF_PARMS(cbPgMax, 12, 0x7fff, 2, 1) // gives 128meg 204 }; --- cut --- This means that MSF_HB::afterOpen only loads the minimum number of header bytes (0x400) into the "hdr" structure. If the actual page size of the file is 0x1000 (as it is in our case), then the trailing 0xC00 bytes remain uninitialized, and are written that way back to the PDB file on disk. Considering that the MSF_HB object is allocated from the process heap (the container process is mspdbsrv.exe) and not pre-initialized, the leaked bytes may contain various leftover chunks of data used by the process in the past. The disclosure can be easily confirmed by enabling the Page Heap mechanism in Application Verifier for the mspdbsrv.exe process and observing a repeated pattern of 3072 marker bytes at offset 1024 of the output file. A disclosure of this kind wouldn't be typically very severe considering that .pdb files aren't frequently exchanged over the Internet. However, it turns out that some PDBs generated by Microsoft for their Windows components, and shared with developers via the Microsoft Symbol Server or pre-built symbol packages, also contain uninitialized memory from internal Microsoft build servers. Based on some brief experimentation, we have found that around 0.5% official Microsoft PDBs for Windows 10 are affected by this problem. It's unclear what the total amount of leaked memory is, as we don't know how many unique symbol files are served by Microsoft. Symbols for systems prior to Windows 10 do not seem to be affected, which suggests that the vulnerability has been introduced in a recent version of the mspdbcore library. In order to estimate the scope of the disclosure, we have analyzed all 18 symbol packages available for various Windows 10 versions at the official Windows Symbol Packages website [4]. It is easy to determine if a particular .pdb file leaks uninitialized memory, by examining the .cbPg field at offset 0x20 of the file and checking if it is equal to 0x1000. Below is a summary of our findings: +------------------------------------------------------------+-------------+-----------------+------------+----------------------------+ | Symbol Package Set | Files total | Files with leak | Percentage | Amount of disclosed memory | +------------------------------------------------------------+-------------+-----------------+------------+----------------------------+ | Windows 10 a July 2015 | 30807 | 152 | 0.49% | 456 kB | | Windows 10 a November 2015 | 31712 | 152 | 0.48% | 456 kB | | Windows 10 a March 2016 | 16138 | 78 | 0.48% | 234 kB | | Windows 10 and Windows Server 2016 a August 2016 | 16238 | 76 | 0.47% | 228 kB | | Windows 10 a September 2016 | 16174 | 76 | 0.47% | 228 kB | | Windows 10 and Windows Server 2016 a April 2017 | 16755 | 76 | 0.45% | 228 kB | | Windows 10 and Windows Server, version 1709 a October 2017 | 17062 | 78 | 0.46% | 234 kB | | Total | 144886 | 688 | 0.47% | 2064 kB (2.02 MB) | +------------------------------------------------------------+-------------+-----------------+------------+----------------------------+ Across all Windows 10 symbols available from the website, there is a little over 2 MB of uninitialized memory disclosed due to the vulnerability. The leaks were found in the following 40 symbol files: 1 appxdeploymentclient.pdb 2 authbroker.pdb 3 biwinrt.pdb 4 combase.pdb 5 cryptowinrt.pdb 6 dllhst3g.pdb 7 mbaeapipublic.pdb 8 mbsmsapi.pdb 9 mbussdapi.pdb 10 msvideodsp.pdb 11 msxml6.pdb 12 nfccx.pdb 13 ole32.pdb 14 playtomanager.pdb 15 provcore.pdb 16 rtmediaframe.pdb 17 urlmon.pdb 18 uxtheme.pdb 19 vaultcli.pdb 20 webcamui.pdb 21 windows.applicationmodel.background.systemeventsbroker.pdb 22 windows.applicationmodel.background.timebroker.pdb 23 windows.applicationmodel.pdb 24 windows.devices.enumeration.pdb 25 windows.devices.portable.pdb 26 windows.devices.sensors.pdb 27 windows.globalization.fontgroups.pdb 28 windows.graphics.pdb 29 windows.media.streaming.pdb 30 windows.networking.backgroundtransfer.pdb 31 windows.networking.pdb 32 windows.storage.applicationdata.pdb 33 windows.storage.compression.pdb 34 windows.ui.input.inking.pdb 35 windows.ui.pdb 36 windows.ui.xaml.pdb 37 windows.web.pdb 38 wintypes.pdb 39 wpnapps.pdb 40 wwaapi.pdb In many cases, the uninitialized blobs consist of all zeros, or otherwise non-interesting binary data. However, some of them contain textual strings such as environment variables on Microsoft build servers, which in turn disclose local paths, SMB paths, domain names, command line flags and other information about the compilation environment. We have provided binary dumps of all uninitialized data we have extracted from the symbols to the vendor. This bug is subject to a 90 day disclosure deadline. After 90 days elapse or a patch has been made broadly available, the bug report will become visible to the public. References: [1] https://en.wikipedia.org/wiki/Program_database [2] https://github.com/Microsoft/microsoft-pdb [3] https://github.com/Microsoft/microsoft-pdb/blob/master/PDB/msf/msf.cpp [4] https://developer.microsoft.com/en-us/windows/hardware/download-symbols Found by: mjurczyk