The Microsoft compiler mspdbcore.dll suffers from a heap memory disclosure into output .pdb files. This affects Microsoft Symbol Server.
005199a3345b019bfc7e71e839648cb178fbbab4dd5219b2b6b06bea5751b3dc
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] <a href="https://en.wikipedia.org/wiki/Program_database" title="" class="" rel="nofollow">https://en.wikipedia.org/wiki/Program_database</a>
[2] <a href="https://github.com/Microsoft/microsoft-pdb" title="" class="" rel="nofollow">https://github.com/Microsoft/microsoft-pdb</a>
[3] <a href="https://github.com/Microsoft/microsoft-pdb/blob/master/PDB/msf/msf.cpp" title="" class="" rel="nofollow">https://github.com/Microsoft/microsoft-pdb/blob/master/PDB/msf/msf.cpp</a>
[4] <a href="https://developer.microsoft.com/en-us/windows/hardware/download-symbols" title="" class="" rel="nofollow">https://developer.microsoft.com/en-us/windows/hardware/download-symbols</a>
Found by: mjurczyk