| 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, §ionHeader, 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 | } |