main
cpp 393 lines 13.8 KB
Raw
1 // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 #include "precomp.h"
4
5
6 // structs
7 typedef struct _BURN_SECTION_HEADER
8 {
9 DWORD dwMagic;
10 DWORD dwVersion;
11
12 GUID guidBundleId;
13
14 DWORD dwStubSize;
15 DWORD dwOriginalChecksum;
16 DWORD dwOriginalSignatureOffset;
17 DWORD dwOriginalSignatureSize;
18
19 DWORD dwFormat;
20 DWORD cContainers;
21 DWORD rgcbContainers[1];
22 } BURN_SECTION_HEADER;
23
24 static HRESULT VerifySectionMatchesMemoryPEHeader(
25 __in REFGUID pSection
26 );
27
28
29 extern "C" HRESULT SectionInitialize(
30 __in BURN_SECTION* pSection,
31 __in HANDLE hEngineFile,
32 __in HANDLE hSourceEngineFile
33 )
34 {
35 HRESULT hr = S_OK;
36 DWORD cbRead = 0;
37 LARGE_INTEGER li = { };
38 LONGLONG llSize = 0;
39 IMAGE_DOS_HEADER dosHeader = { };
40 IMAGE_NT_HEADERS ntHeader = { };
41 DWORD dwChecksumOffset = 0;
42 DWORD dwCertificateTableOffset = 0;
43 DWORD dwSignatureOffset = 0;
44 DWORD cbSignature = 0;
45 IMAGE_SECTION_HEADER sectionHeader = { };
46 DWORD_PTR dwOriginalChecksumAndSignatureOffset = 0;
47 BURN_SECTION_HEADER* pBurnSectionHeader = NULL;
48 DWORD cMaxContainers = 0;
49
50 pSection->hEngineFile = hEngineFile;
51 ExitOnInvalidHandleWithLastError(pSection->hEngineFile, hr, "Failed to open handle to engine process path.");
52
53 pSection->hSourceEngineFile = INVALID_HANDLE_VALUE == hSourceEngineFile ? hEngineFile : hSourceEngineFile;
54
55 //
56 // First, make sure we have a valid DOS signature.
57 //
58 if (!::SetFilePointerEx(pSection->hEngineFile, li, NULL, FILE_BEGIN))
59 {
60 ExitWithLastError(hr, "Failed to seek to start of file.");
61 }
62
63 // read DOS header
64 if (!::ReadFile(pSection->hEngineFile, &dosHeader, sizeof(IMAGE_DOS_HEADER), &cbRead, NULL))
65 {
66 ExitWithLastError(hr, "Failed to read DOS header.");
67 }
68 else if (sizeof(IMAGE_DOS_HEADER) > cbRead || IMAGE_DOS_SIGNATURE != dosHeader.e_magic)
69 {
70 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
71 ExitOnRootFailure(hr, "Failed to find valid DOS image header in buffer.");
72 }
73
74 //
75 // Now, make sure we have a valid NT signature.
76 //
77
78 // seek to new header
79 li.QuadPart = dosHeader.e_lfanew;
80 if (!::SetFilePointerEx(pSection->hEngineFile, li, NULL, FILE_BEGIN))
81 {
82 ExitWithLastError(hr, "Failed to seek to NT header.");
83 }
84
85 // read NT header
86 if (!::ReadFile(pSection->hEngineFile, &ntHeader, sizeof(IMAGE_NT_HEADERS) - sizeof(IMAGE_OPTIONAL_HEADER), &cbRead, NULL))
87 {
88 ExitWithLastError(hr, "Failed to read NT header.");
89 }
90 else if ((sizeof(IMAGE_NT_HEADERS) - sizeof(IMAGE_OPTIONAL_HEADER)) > cbRead || IMAGE_NT_SIGNATURE != ntHeader.Signature)
91 {
92 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
93 ExitOnRootFailure(hr, "Failed to find valid NT image header in buffer.");
94 }
95
96 // Get the table offsets.
97 dwChecksumOffset = dosHeader.e_lfanew + sizeof(IMAGE_NT_HEADERS) - sizeof(IMAGE_OPTIONAL_HEADER) + (sizeof(DWORD) * 16);
98 dwCertificateTableOffset = dosHeader.e_lfanew + sizeof(IMAGE_NT_HEADERS) - (sizeof(IMAGE_DATA_DIRECTORY) * (IMAGE_NUMBEROF_DIRECTORY_ENTRIES - IMAGE_DIRECTORY_ENTRY_SECURITY));
99
100 // Seek into the certificate table to get the signature size.
101 li.QuadPart = dwCertificateTableOffset;
102 if (!::SetFilePointerEx(pSection->hEngineFile, li, NULL, FILE_BEGIN))
103 {
104 ExitWithLastError(hr, "Failed to seek to section info.");
105 }
106
107 if (!::ReadFile(pSection->hEngineFile, &dwSignatureOffset, sizeof(dwSignatureOffset), &cbRead, NULL))
108 {
109 ExitWithLastError(hr, "Failed to read signature offset.");
110 }
111
112 if (!::ReadFile(pSection->hEngineFile, &cbSignature, sizeof(cbSignature), &cbRead, NULL))
113 {
114 ExitWithLastError(hr, "Failed to read signature size.");
115 }
116
117 //
118 // Finally, get into the section table and look for the Burn section info.
119 //
120
121 // seek past optional headers
122 li.QuadPart = dosHeader.e_lfanew + sizeof(IMAGE_NT_HEADERS) - sizeof(IMAGE_OPTIONAL_HEADER) + ntHeader.FileHeader.SizeOfOptionalHeader;
123 if (!::SetFilePointerEx(pSection->hEngineFile, li, NULL, FILE_BEGIN))
124 {
125 ExitWithLastError(hr, "Failed to seek past optional headers.");
126 }
127
128 // read sections one by one until we find our section
129 for (DWORD i = 0; ; ++i)
130 {
131 // read section
132 if (!::ReadFile(pSection->hEngineFile, &sectionHeader, sizeof(IMAGE_SECTION_HEADER), &cbRead, NULL))
133 {
134 ExitWithLastError(hr, "Failed to read image section header, index: %u", i);
135 }
136 if (sizeof(IMAGE_SECTION_HEADER) > cbRead)
137 {
138 ExitWithRootFailure(hr, E_INVALIDDATA, "Failed to read complete image section header, index: %u", i);
139 }
140
141 // compare header name
142 C_ASSERT(sizeof(sectionHeader.Name) == sizeof(BURN_SECTION_NAME) - 1);
143 if (0 == memcmp(sectionHeader.Name, BURN_SECTION_NAME, sizeof(sectionHeader.Name)))
144 {
145 break;
146 }
147
148 // fail if we hit the end
149 if (i + 1 >= ntHeader.FileHeader.NumberOfSections)
150 {
151 ExitWithRootFailure(hr, E_INVALIDDATA, "Failed to find Burn section.");
152 }
153 }
154
155 //
156 // We've arrived at the section info.
157 //
158
159 // check size of section
160 if (sizeof(BURN_SECTION_HEADER) > sectionHeader.SizeOfRawData)
161 {
162 ExitWithRootFailure(hr, E_INVALIDDATA, "Failed to read section info, data too short: %u", sectionHeader.SizeOfRawData);
163 }
164
165 // allocate buffer for section info
166 pBurnSectionHeader = (BURN_SECTION_HEADER*)MemAlloc(sectionHeader.SizeOfRawData, TRUE);
167 ExitOnNull(pBurnSectionHeader, hr, E_OUTOFMEMORY, "Failed to allocate buffer for section info.");
168
169 // seek to section info
170 li.QuadPart = sectionHeader.PointerToRawData;
171 if (!::SetFilePointerEx(pSection->hEngineFile, li, NULL, FILE_BEGIN))
172 {
173 ExitWithLastError(hr, "Failed to seek to section info.");
174 }
175
176 // Note the location of original checksum and signature information in the burn section header.
177 dwOriginalChecksumAndSignatureOffset = sectionHeader.PointerToRawData + (reinterpret_cast<LPBYTE>(&pBurnSectionHeader->dwOriginalChecksum) - reinterpret_cast<LPBYTE>(pBurnSectionHeader));
178
179 // read section info
180 if (!::ReadFile(pSection->hEngineFile, pBurnSectionHeader, sectionHeader.SizeOfRawData, &cbRead, NULL))
181 {
182 ExitWithLastError(hr, "Failed to read section info.");
183 }
184 else if (sectionHeader.SizeOfRawData > cbRead)
185 {
186 ExitWithRootFailure(hr, E_INVALIDDATA, "Failed to read complete section info.");
187 }
188
189 // validate version of section info
190 if (BURN_SECTION_VERSION != pBurnSectionHeader->dwVersion)
191 {
192 ExitWithRootFailure(hr, E_INVALIDDATA, "Failed to read section info, unsupported version: %08x", pBurnSectionHeader->dwVersion);
193 }
194
195 cMaxContainers = (sectionHeader.SizeOfRawData - offsetof(BURN_SECTION_HEADER, rgcbContainers)) / sizeof(DWORD);
196 if (cMaxContainers < pBurnSectionHeader->cContainers)
197 {
198 ExitWithRootFailure(hr, E_INVALIDDATA, "Invalid section info, cContainers too large: %u", pBurnSectionHeader->cContainers);
199 }
200
201 hr = FileSizeByHandle(pSection->hSourceEngineFile, &llSize);
202 ExitOnFailure(hr, "Failed to get total size of bundle.");
203
204 pSection->cbStub = pBurnSectionHeader->dwStubSize;
205
206 // If there is an original signature use that to determine the engine size.
207 if (pBurnSectionHeader->dwOriginalSignatureOffset)
208 {
209 pSection->cbEngineSize = pBurnSectionHeader->dwOriginalSignatureOffset + pBurnSectionHeader->dwOriginalSignatureSize;
210 }
211 else if (dwSignatureOffset) // if there is a signature, use it.
212 {
213 pSection->cbEngineSize = dwSignatureOffset + cbSignature;
214 }
215 else // just use the stub and UX container as the size of the engine.
216 {
217 pSection->cbEngineSize = pSection->cbStub + pBurnSectionHeader->rgcbContainers[0];
218 }
219
220 pSection->qwBundleSize = static_cast<DWORD64>(llSize);
221
222 pSection->dwChecksumOffset = dwChecksumOffset;
223 pSection->dwCertificateTableOffset = dwCertificateTableOffset;
224 pSection->dwOriginalChecksumAndSignatureOffset = dwOriginalChecksumAndSignatureOffset;
225
226 pSection->dwOriginalChecksum = pBurnSectionHeader->dwOriginalChecksum;
227 pSection->dwOriginalSignatureOffset = pBurnSectionHeader->dwOriginalSignatureOffset;
228 pSection->dwOriginalSignatureSize = pBurnSectionHeader->dwOriginalSignatureSize;
229
230 pSection->dwFormat = pBurnSectionHeader->dwFormat;
231 pSection->cContainers = pBurnSectionHeader->cContainers;
232 pSection->rgcbContainers = (DWORD*)MemAlloc(sizeof(DWORD) * pSection->cContainers, TRUE);
233 ExitOnNull(pSection->rgcbContainers, hr, E_OUTOFMEMORY, "Failed to allocate memory for container sizes.");
234
235 memcpy(pSection->rgcbContainers, pBurnSectionHeader->rgcbContainers, sizeof(DWORD) * pSection->cContainers);
236
237 // TODO: verify more than just the GUID.
238 hr = VerifySectionMatchesMemoryPEHeader(pBurnSectionHeader->guidBundleId);
239 ExitOnRootFailure(hr, "PE Header from file didn't match PE Header in memory.");
240
241 LExit:
242 ReleaseMem(pBurnSectionHeader);
243
244 return hr;
245 }
246
247 extern "C" void SectionUninitialize(
248 __out BURN_SECTION* pSection
249 )
250 {
251 ReleaseMem(pSection->rgcbContainers);
252 memset(pSection, 0, sizeof(BURN_SECTION));
253 }
254
255 extern "C" HRESULT SectionGetAttachedContainerInfo(
256 __in BURN_SECTION* pSection,
257 __in DWORD iContainerIndex,
258 __in DWORD dwExpectedType,
259 __out DWORD64* pqwOffset,
260 __out DWORD64* pqwSize,
261 __out BOOL* pfPresent
262 )
263 {
264 HRESULT hr = S_OK;
265
266 // validate container info
267 if (iContainerIndex >= pSection->cContainers)
268 {
269 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
270 ExitOnRootFailure(hr, "Failed to find container info, too few elements: %u", pSection->cContainers);
271 }
272 else if (dwExpectedType != pSection->dwFormat)
273 {
274 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
275 ExitOnRootFailure(hr, "Unexpected container format.");
276 }
277
278 // If we are asking for the UX container, find it right after the stub.
279 if (0 == iContainerIndex)
280 {
281 *pqwOffset = pSection->cbStub;
282 }
283 else // attached containers start after the whole engine.
284 {
285 *pqwOffset = pSection->cbEngineSize;
286 for (DWORD i = 1; i < iContainerIndex; ++i)
287 {
288 *pqwOffset += pSection->rgcbContainers[i];
289 }
290 }
291
292 *pqwSize = pSection->rgcbContainers[iContainerIndex];
293 *pfPresent = (*pqwOffset + *pqwSize) <= pSection->qwBundleSize;
294
295 AssertSz(*pfPresent || pSection->qwBundleSize <= *pqwOffset, "An attached container should either be present or completely absent from the bundle. Found a case where the attached container is partially present which is wrong.");
296
297 LExit:
298 return hr;
299 }
300
301 HRESULT VerifySectionMatchesMemoryPEHeader(
302 __in REFGUID pBundleId
303 )
304 {
305 HRESULT hr = S_OK;
306 BYTE* pbPEHeader = NULL;
307 PIMAGE_DOS_HEADER pDosHeader = NULL;
308 PIMAGE_NT_HEADERS pNtHeader = NULL;
309 PIMAGE_SECTION_HEADER pSections = NULL;
310 PIMAGE_SECTION_HEADER pSectionHeader = NULL;
311 BURN_SECTION_HEADER* pBurnSectionHeader = NULL;
312
313 pbPEHeader = reinterpret_cast<BYTE*>(::GetModuleHandleW(NULL));
314 ExitOnNullWithLastError(pbPEHeader, hr, "Failed to get module handle to process.");
315
316 //
317 // First, make sure we have a valid DOS signature.
318 //
319
320 pDosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(pbPEHeader);
321 if (IMAGE_DOS_SIGNATURE != pDosHeader->e_magic)
322 {
323 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
324 ExitOnRootFailure(hr, "Failed to find valid DOS image header in buffer.");
325 }
326
327 //
328 // Now, make sure we have a valid NT signature.
329 //
330
331 pNtHeader = reinterpret_cast<PIMAGE_NT_HEADERS>(pbPEHeader + pDosHeader->e_lfanew);
332 if (IMAGE_NT_SIGNATURE != pNtHeader->Signature)
333 {
334 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
335 ExitOnRootFailure(hr, "Failed to find valid NT image header in buffer.");
336 }
337
338 //
339 // Finally, get into the section table and look for the Burn section info.
340 //
341
342 pSections = reinterpret_cast<PIMAGE_SECTION_HEADER>(pbPEHeader + pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS) - sizeof(IMAGE_OPTIONAL_HEADER) + pNtHeader->FileHeader.SizeOfOptionalHeader);
343
344 // Read sections one by one until we find our section.
345 for (DWORD i = 0; ; ++i)
346 {
347 pSectionHeader = pSections + i;
348
349 // Compare header name.
350 C_ASSERT(sizeof(pSectionHeader->Name) == sizeof(BURN_SECTION_NAME) - 1);
351 if (0 == memcmp(pSectionHeader->Name, BURN_SECTION_NAME, sizeof(pSectionHeader->Name)))
352 {
353 break;
354 }
355
356 // Fail if we hit the end.
357 if (i + 1 >= pNtHeader->FileHeader.NumberOfSections)
358 {
359 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
360 ExitOnRootFailure(hr, "Failed to find Burn section.");
361 }
362 }
363
364 //
365 // We've arrived at the section info.
366 //
367
368 // Check size of section.
369 if (sizeof(BURN_SECTION_HEADER) > pSectionHeader->SizeOfRawData)
370 {
371 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
372 ExitOnRootFailure(hr, "Failed to read section info, data too short: %u", pSectionHeader->SizeOfRawData);
373 }
374
375 // Get Burn section info.
376 pBurnSectionHeader = reinterpret_cast<BURN_SECTION_HEADER*>(pbPEHeader + pSectionHeader->VirtualAddress);
377
378 // Validate version of section info.
379 if (BURN_SECTION_VERSION != pBurnSectionHeader->dwVersion)
380 {
381 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
382 ExitOnRootFailure(hr, "Failed to read section info, unsupported version: %08x", pBurnSectionHeader->dwVersion);
383 }
384
385 if (!::IsEqualGUID(pBundleId, pBurnSectionHeader->guidBundleId))
386 {
387 hr = E_INVALIDDATA;
388 ExitOnRootFailure(hr, "Bundle guid didn't match the guid in the PE Header in memory.");
389 }
390
391 LExit:
392 return hr;
393 }