@joebigelow / wix-1 / commits / 124fef39

Add ability to skip a local path candidate if it failed verification.

Fixes 6818

Sean Hall committed Aug 3, 2022 at 14:55 UTC 124fef398a26bc8e139e889a2345602d2478590c
14 files changed +444 -30
src/burn/engine/apply.cpp
+120 -17
@@ -38,7 +38,7 @@ typedef struct _BURN_CACHE_CONTEXT
38 LPWSTR* rgSearchPaths;
39 DWORD cSearchPaths;
40 DWORD cSearchPathsMax;
41 - LPWSTR sczLastUsedFolderCandidate;
41 + LPWSTR sczLocalAcquisitionSourcePath;
42 } BURN_CACHE_CONTEXT;
43
44 typedef struct _BURN_CACHE_PROGRESS_CONTEXT
@@ -92,6 +92,16 @@ static HRESULT ApplyCachePackage(
92 __in BURN_CACHE_CONTEXT* pContext,
93 __in BURN_PACKAGE* pPackage
94 );
95 +static void FinalizeContainerAcquisition(
96 + __in BURN_CACHE_CONTEXT* pContext,
97 + __in BURN_CONTAINER* pContainer,
98 + __in BOOL fSuccess
99 + );
100 +static void FinalizePayloadAcquisition(
101 + __in BURN_CACHE_CONTEXT* pContext,
102 + __in BURN_PAYLOAD* pPayload,
103 + __in BOOL fSuccess
104 + );
105 static HRESULT ApplyExtractContainer(
106 __in BURN_CACHE_CONTEXT* pContext,
107 __in BURN_CONTAINER* pContainer
@@ -667,7 +677,7 @@ LExit:
677 ReleaseNullStr(cacheContext.rgSearchPaths[i]);
678 }
679 ReleaseMem(cacheContext.rgSearchPaths);
670 - ReleaseStr(cacheContext.sczLastUsedFolderCandidate);
680 + ReleaseStr(cacheContext.sczLocalAcquisitionSourcePath);
681
682 UserExperienceOnCacheComplete(pUX, hr);
683 return hr;
@@ -1038,6 +1048,46 @@ LExit:
1048 return hr;
1049 }
1050
1051 +static void FinalizeContainerAcquisition(
1052 + __in BURN_CACHE_CONTEXT* pContext,
1053 + __in BURN_CONTAINER* pContainer,
1054 + __in BOOL fSuccess
1055 + )
1056 +{
1057 + ReleaseNullStr(pContainer->sczFailedLocalAcquisitionPath);
1058 +
1059 + if (fSuccess)
1060 + {
1061 + ReleaseNullStr(pContext->sczLocalAcquisitionSourcePath);
1062 + pContainer->fFailedVerificationFromAcquisition = FALSE;
1063 + }
1064 + else if (pContext->sczLocalAcquisitionSourcePath)
1065 + {
1066 + pContainer->sczFailedLocalAcquisitionPath = pContext->sczLocalAcquisitionSourcePath;
1067 + pContext->sczLocalAcquisitionSourcePath = NULL;
1068 + }
1069 +}
1070 +
1071 +static void FinalizePayloadAcquisition(
1072 + __in BURN_CACHE_CONTEXT* pContext,
1073 + __in BURN_PAYLOAD* pPayload,
1074 + __in BOOL fSuccess
1075 + )
1076 +{
1077 + ReleaseNullStr(pPayload->sczFailedLocalAcquisitionPath);
1078 +
1079 + if (fSuccess)
1080 + {
1081 + ReleaseNullStr(pContext->sczLocalAcquisitionSourcePath);
1082 + pPayload->fFailedVerificationFromAcquisition = FALSE;
1083 + }
1084 + else if (pContext->sczLocalAcquisitionSourcePath)
1085 + {
1086 + pPayload->sczFailedLocalAcquisitionPath = pContext->sczLocalAcquisitionSourcePath;
1087 + pContext->sczLocalAcquisitionSourcePath = NULL;
1088 + }
1089 +}
1090 +
1091 static HRESULT ApplyExtractContainer(
1092 __in BURN_CACHE_CONTEXT* pContext,
1093 __in BURN_CONTAINER* pContainer
@@ -1066,10 +1116,11 @@ static HRESULT ApplyExtractContainer(
1116 hr = ExtractContainer(pContext, pContainer);
1117 LogExitOnFailure(hr, MSG_FAILED_EXTRACT_CONTAINER, "Failed to extract payloads from container: %ls to working path: %ls", pContainer->sczId, pContainer->sczUnverifiedPath);
1118
1069 - if (pContext->sczLastUsedFolderCandidate)
1119 + if (pContext->sczLocalAcquisitionSourcePath)
1120 {
1121 // We successfully copied from a source location, set that as the last used source.
1072 - CacheSetLastUsedSource(pContext->pVariables, pContext->sczLastUsedFolderCandidate, pContainer->sczFilePath);
1122 + CacheSetLastUsedSource(pContext->pVariables, pContext->sczLocalAcquisitionSourcePath, pContainer->sczFilePath);
1123 + ReleaseNullStr(pContext->sczLocalAcquisitionSourcePath);
1124 }
1125
1126 if (pContainer->qwExtractSizeTotal < pContainer->qwCommittedExtractProgress)
@@ -1086,7 +1137,7 @@ static HRESULT ApplyExtractContainer(
1137 pContainer->qwCommittedExtractProgress = pContainer->qwExtractSizeTotal;
1138
1139 LExit:
1089 - ReleaseNullStr(pContext->sczLastUsedFolderCandidate);
1140 + FinalizeContainerAcquisition(pContext, pContainer, SUCCEEDED(hr));
1141
1142 return hr;
1143 }
@@ -1157,13 +1208,13 @@ static HRESULT ApplyLayoutContainer(
1208 ++cTryAgainAttempts;
1209 pContext->qwSuccessfulCacheProgress -= pContainer->qwCommittedCacheProgress;
1210 pContainer->qwCommittedCacheProgress = 0;
1160 - ReleaseNullStr(pContext->sczLastUsedFolderCandidate);
1211 + FinalizeContainerAcquisition(pContext, pContainer, FALSE);
1212 LogErrorId(hr, MSG_CACHE_RETRYING_CONTAINER, pContainer->sczId, NULL, NULL);
1213 }
1214 }
1215
1216 LExit:
1166 - ReleaseNullStr(pContext->sczLastUsedFolderCandidate);
1217 + FinalizeContainerAcquisition(pContext, pContainer, SUCCEEDED(hr));
1218
1219 return hr;
1220 }
@@ -1230,13 +1281,13 @@ static HRESULT ApplyProcessPayload(
1281 ++cTryAgainAttempts;
1282 pContext->qwSuccessfulCacheProgress -= pPayloadGroupItem->qwCommittedCacheProgress;
1283 pPayloadGroupItem->qwCommittedCacheProgress = 0;
1233 - ReleaseNullStr(pContext->sczLastUsedFolderCandidate);
1284 + FinalizePayloadAcquisition(pContext, pPayload, FALSE);
1285 LogErrorId(hr, MSG_CACHE_RETRYING_PAYLOAD, pPayload->sczKey, NULL, NULL);
1286 }
1287 }
1288
1289 LExit:
1239 - ReleaseNullStr(pContext->sczLastUsedFolderCandidate);
1290 + FinalizePayloadAcquisition(pContext, pPayload, SUCCEEDED(hr));
1291
1292 return hr;
1293 }
@@ -1570,6 +1621,7 @@ static HRESULT AcquireContainerOrPayload(
1621 BOOL fPreferExtract = FALSE;
1622 DWORD64 qwFileSize = 0;
1623 BOOL fMinimumFileSize = FALSE;
1624 + BOOL fEqual = FALSE;
1625
1626 if (pContainer)
1627 {
@@ -1615,21 +1667,44 @@ static HRESULT AcquireContainerOrPayload(
1667 // When a payload comes from a container, the container has the highest chance of being correct.
1668 // But we want to avoid extracting the container multiple times.
1669 // So only consider the destination path, which means the container was already extracted.
1618 - if (IsValidLocalFile(pContext->rgSearchPaths[dwDestinationSearchPath], qwFileSize, fMinimumFileSize))
1670 + if (!pPayload->fFailedVerificationFromAcquisition && IsValidLocalFile(pContext->rgSearchPaths[dwDestinationSearchPath], qwFileSize, fMinimumFileSize))
1671 {
1672 fFoundLocal = TRUE;
1673 dwChosenSearchPath = dwDestinationSearchPath;
1674 }
1623 - else // don't prefer the container if extracting it already failed.
1675 + else // don't prefer the container if it was already extracted.
1676 {
1625 - fPreferExtract = SUCCEEDED(pPayload->pContainer->hrExtract);
1677 + fPreferExtract = !pPayload->pContainer->fExtracted;
1678 }
1679 }
1680
1681 if (!fFoundLocal)
1682 {
1683 + BOOL fFailedVerificationFromAcquisition = pContainer ? pContainer->fFailedVerificationFromAcquisition : pPayload->fFailedVerificationFromAcquisition;
1684 + LPCWSTR wzFailedLocalAcquisitionPath = pContainer ? pContainer->sczFailedLocalAcquisitionPath : pPayload->sczFailedLocalAcquisitionPath;
1685 +
1686 for (DWORD i = 0; i < pContext->cSearchPaths; ++i)
1687 {
1688 + // If the file failed verification from acquisition then certain paths should not be considered.
1689 + if (fFailedVerificationFromAcquisition)
1690 + {
1691 + if (i == dwDestinationSearchPath)
1692 + {
1693 + continue;
1694 + }
1695 +
1696 + if (wzFailedLocalAcquisitionPath)
1697 + {
1698 + hr = PathCompareCanonicalized(pContext->rgSearchPaths[i], wzFailedLocalAcquisitionPath, &fEqual);
1699 + ExitOnFailure(hr, "Failed to compare '%ls' to '%ls'.", pContext->rgSearchPaths[i], wzFailedLocalAcquisitionPath);
1700 +
1701 + if (fEqual)
1702 + {
1703 + continue;
1704 + }
1705 + }
1706 + }
1707 +
1708 // If the file exists locally with the correct size, choose it.
1709 if (IsValidLocalFile(pContext->rgSearchPaths[i], qwFileSize, fMinimumFileSize))
1710 {
@@ -1703,7 +1778,7 @@ static HRESULT AcquireContainerOrPayload(
1778 ExitOnFailure(hr, "Failed to copy payload: %ls", wzPayloadId);
1779
1780 // Store the source path so it can be used as the LastUsedFolder if it passes verification.
1706 - pContext->sczLastUsedFolderCandidate = pContext->rgSearchPaths[dwChosenSearchPath];
1781 + pContext->sczLocalAcquisitionSourcePath = pContext->rgSearchPaths[dwChosenSearchPath];
1782 pContext->rgSearchPaths[dwChosenSearchPath] = NULL;
1783 }
1784
@@ -1731,12 +1806,14 @@ static HRESULT AcquireContainerOrPayload(
1806 LExit:
1807 if (BOOTSTRAPPER_CACHE_OPERATION_EXTRACT == cacheOperation)
1808 {
1734 - if (FAILED(hr) && SUCCEEDED(pPayload->pContainer->hrExtract) &&
1809 + // If this was the first extraction attempt and it failed
1810 + // but there was a different method of acquisition available then recommend retrying.
1811 + if (FAILED(hr) && !pPayload->pContainer->fExtracted &&
1812 (fFoundLocal || pPayload->downloadSource.sczUrl && *pPayload->downloadSource.sczUrl))
1813 {
1814 *pfRetry = TRUE;
1815 }
1739 - pPayload->pContainer->hrExtract = hr;
1816 + pPayload->pContainer->fExtracted = TRUE;
1817 }
1818 UserExperienceOnCacheAcquireComplete(pContext->pUX, wzPackageOrContainerId, wzPayloadId, hr, pfRetry);
1819
@@ -2103,6 +2180,31 @@ static HRESULT CALLBACK CacheMessageHandler(
2180 hr = UserExperienceOnCacheContainerOrPayloadVerifyComplete(pProgress->pCacheContext->pUX, wzPackageOrContainerId, wzPayloadId, hr);
2181 break;
2182 }
2183 + case BURN_CACHE_MESSAGE_FAILURE:
2184 + switch (pMessage->failure.cacheStep)
2185 + {
2186 + case BURN_CACHE_STEP_HASH:
2187 + if (pProgress->pContainer)
2188 + {
2189 + LogStringLine(REPORT_DEBUG, "Verification failed on container: %ls", pProgress->pContainer->sczId);
2190 + pProgress->pContainer->fFailedVerificationFromAcquisition = TRUE;
2191 + }
2192 + else if (pProgress->pPayloadGroupItem)
2193 + {
2194 + LogStringLine(REPORT_DEBUG, "Verification failed on payload group item: %ls", pProgress->pPayloadGroupItem->pPayload->sczKey);
2195 + pProgress->pPayloadGroupItem->pPayload->fFailedVerificationFromAcquisition = TRUE;
2196 + }
2197 + else if (pProgress->pPayload)
2198 + {
2199 + LogStringLine(REPORT_DEBUG, "Verification failed on payload: %ls", pProgress->pPayload->sczKey);
2200 + pProgress->pPayload->fFailedVerificationFromAcquisition = TRUE;
2201 + }
2202 + else
2203 + {
2204 + LogStringLine(REPORT_DEBUG, "Verification failed on unknown item");
2205 + }
2206 + break;
2207 + }
2208 }
2209
2210 return hr;
@@ -2159,10 +2261,11 @@ static HRESULT CompleteCacheProgress(
2261 pContext->pPayloadGroupItem->qwCommittedCacheProgress += qwFileSize;
2262 }
2263
2162 - if (BURN_CACHE_PROGRESS_TYPE_FINALIZE == pContext->type && pContext->pCacheContext->sczLastUsedFolderCandidate)
2264 + if (BURN_CACHE_PROGRESS_TYPE_FINALIZE == pContext->type && pContext->pCacheContext->sczLocalAcquisitionSourcePath)
2265 {
2266 // We successfully copied from a source location, set that as the last used source.
2165 - CacheSetLastUsedSource(pContext->pCacheContext->pVariables, pContext->pCacheContext->sczLastUsedFolderCandidate, pContext->pContainer ? pContext->pContainer->sczFilePath : pContext->pPayloadGroupItem->pPayload->sczFilePath);
2267 + CacheSetLastUsedSource(pContext->pCacheContext->pVariables, pContext->pCacheContext->sczLocalAcquisitionSourcePath, pContext->pContainer ? pContext->pContainer->sczFilePath : pContext->pPayloadGroupItem->pPayload->sczFilePath);
2268 + ReleaseNullStr(pContext->pCacheContext->sczLocalAcquisitionSourcePath);
2269 }
2270 }
2271 else if (PROGRESS_CANCEL == dwResult)
src/burn/engine/cache.cpp
+63 -7
@@ -12,7 +12,11 @@ static const DWORD FILE_OPERATION_RETRY_WAIT = 2000;
12 static HRESULT CacheVerifyPayloadSignature(
13 __in BURN_PAYLOAD* pPayload,
14 __in_z LPCWSTR wzUnverifiedPayloadPath,
15 - __in HANDLE hFile
15 + __in HANDLE hFile,
16 + __in BURN_CACHE_STEP cacheStep,
17 + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler,
18 + __in LPPROGRESS_ROUTINE pfnProgress,
19 + __in LPVOID pContext
20 );
21 static HRESULT CalculatePotentialBaseWorkingFolders(
22 __in BURN_CACHE* pCache,
@@ -159,7 +163,11 @@ static HRESULT SendCacheCompleteMessage(
163 __in LPVOID pContext,
164 __in HRESULT hrStatus
165 );
162 -
166 +static HRESULT SendCacheFailureMessage(
167 + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler,
168 + __in LPVOID pContext,
169 + __in BURN_CACHE_STEP cacheStep
170 + );
171
172 extern "C" HRESULT CacheInitialize(
173 __in BURN_CACHE* pCache,
@@ -1254,11 +1262,16 @@ LExit:
1262 static HRESULT CacheVerifyPayloadSignature(
1263 __in BURN_PAYLOAD* pPayload,
1264 __in_z LPCWSTR wzUnverifiedPayloadPath,
1257 - __in HANDLE hFile
1265 + __in HANDLE hFile,
1266 + __in BURN_CACHE_STEP cacheStep,
1267 + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler,
1268 + __in LPPROGRESS_ROUTINE /*pfnProgress*/,
1269 + __in LPVOID pContext
1270 )
1271 {
1272 HRESULT hr = S_OK;
1273 LONG er = ERROR_SUCCESS;
1274 + BOOL fFailedVerification = FALSE;
1275
1276 GUID guidAuthenticode = WINTRUST_ACTION_GENERIC_VERIFY_V2;
1277 WINTRUST_FILE_INFO wfi = { };
@@ -1266,6 +1279,11 @@ static HRESULT CacheVerifyPayloadSignature(
1279 CRYPT_PROVIDER_DATA* pProviderData = NULL;
1280 CRYPT_PROVIDER_SGNR* pSigner = NULL;
1281
1282 + hr = SendCacheBeginMessage(pfnCacheMessageHandler, pContext, cacheStep);
1283 + ExitOnFailure(hr, "Aborted cache verify payload signature begin.");
1284 +
1285 + fFailedVerification = TRUE;
1286 +
1287 // Verify the payload assuming online.
1288 wfi.cbStruct = sizeof(wfi);
1289 wfi.pcwszFilePath = wzUnverifiedPayloadPath;
@@ -1297,7 +1315,19 @@ static HRESULT CacheVerifyPayloadSignature(
1315 hr = VerifyPayloadAgainstCertChain(pPayload, pSigner->pChainContext);
1316 ExitOnFailure(hr, "Failed to verify expected payload against actual certificate chain.");
1317
1318 + fFailedVerification = FALSE;
1319 +
1320 + hr = SendCacheSuccessMessage(pfnCacheMessageHandler, pContext, pPayload->qwFileSize);
1321 +
1322 LExit:
1323 + if (fFailedVerification)
1324 + {
1325 + // Make sure the BA process marks this payload as having failed verification.
1326 + SendCacheFailureMessage(pfnCacheMessageHandler, pContext, cacheStep);
1327 + }
1328 +
1329 + SendCacheCompleteMessage(pfnCacheMessageHandler, pContext, hr);
1330 +
1331 return hr;
1332 }
1333
@@ -1744,7 +1774,7 @@ static HRESULT VerifyThenTransferPayload(
1774 switch (pPayload->verification)
1775 {
1776 case BURN_PAYLOAD_VERIFICATION_AUTHENTICODE:
1747 - hr = CacheVerifyPayloadSignature(pPayload, wzUnverifiedPayloadPath, hFile);
1777 + hr = CacheVerifyPayloadSignature(pPayload, wzUnverifiedPayloadPath, hFile, BURN_CACHE_STEP_HASH, pfnCacheMessageHandler, pfnProgress, pContext);
1778 ExitOnFailure(hr, "Failed to verify payload signature: %ls", wzCachedPath);
1779 break;
1780 case BURN_PAYLOAD_VERIFICATION_HASH:
@@ -1890,7 +1920,7 @@ static HRESULT VerifyFileAgainstPayload(
1920 switch (pPayload->verification)
1921 {
1922 case BURN_PAYLOAD_VERIFICATION_AUTHENTICODE:
1893 - hr = CacheVerifyPayloadSignature(pPayload, wzVerifyPath, hFile);
1923 + hr = CacheVerifyPayloadSignature(pPayload, wzVerifyPath, hFile, cacheStep, pfnCacheMessageHandler, pfnProgress, pContext);
1924 ExitOnFailure(hr, "Failed to verify signature of payload: %ls", pPayload->sczKey);
1925 break;
1926 case BURN_PAYLOAD_VERIFICATION_HASH:
@@ -2285,17 +2315,18 @@ static HRESULT VerifyHash(
2315 __in LPVOID pContext
2316 )
2317 {
2288 - UNREFERENCED_PARAMETER(wzUnverifiedPayloadPath);
2289 -
2318 HRESULT hr = S_OK;
2319 BYTE rgbActualHash[SHA512_HASH_LEN] = { };
2320 DWORD64 qwHashedBytes = 0;
2321 LPWSTR pszExpected = NULL;
2322 LPWSTR pszActual = NULL;
2323 + BOOL fFailedVerification = FALSE;
2324
2325 hr = SendCacheBeginMessage(pfnCacheMessageHandler, pContext, cacheStep);
2326 ExitOnFailure(hr, "Aborted cache verify hash begin.");
2327
2328 + fFailedVerification = TRUE;
2329 +
2330 if (fVerifyFileSize)
2331 {
2332 hr = VerifyFileSize(hFile, qwFileSize, wzUnverifiedPayloadPath);
@@ -2323,9 +2354,17 @@ static HRESULT VerifyHash(
2354 }
2355 }
2356
2357 + fFailedVerification = FALSE;
2358 +
2359 hr = SendCacheSuccessMessage(pfnCacheMessageHandler, pContext, qwFileSize);
2360
2361 LExit:
2362 + if (fFailedVerification)
2363 + {
2364 + // Make sure the BA process marks this container or payload as having failed verification.
2365 + SendCacheFailureMessage(pfnCacheMessageHandler, pContext, cacheStep);
2366 + }
2367 +
2368 SendCacheCompleteMessage(pfnCacheMessageHandler, pContext, hr);
2369
2370 ReleaseStr(pszActual);
@@ -2448,3 +2487,20 @@ static HRESULT SendCacheCompleteMessage(
2487
2488 return hr;
2489 }
2490 +
2491 +static HRESULT SendCacheFailureMessage(
2492 + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler,
2493 + __in LPVOID pContext,
2494 + __in BURN_CACHE_STEP cacheStep
2495 + )
2496 +{
2497 + HRESULT hr = S_OK;
2498 + BURN_CACHE_MESSAGE message = { };
2499 +
2500 + message.type = BURN_CACHE_MESSAGE_FAILURE;
2501 + message.failure.cacheStep = cacheStep;
2502 +
2503 + hr = pfnCacheMessageHandler(&message, pContext);
2504 +
2505 + return hr;
2506 +}
src/burn/engine/cache.h
+5
@@ -13,6 +13,7 @@ enum BURN_CACHE_MESSAGE_TYPE
13 BURN_CACHE_MESSAGE_BEGIN,
14 BURN_CACHE_MESSAGE_SUCCESS,
15 BURN_CACHE_MESSAGE_COMPLETE,
16 + BURN_CACHE_MESSAGE_FAILURE,
17 };
18
19 enum BURN_CACHE_STEP
@@ -68,6 +69,10 @@ typedef struct _BURN_CACHE_MESSAGE
69 {
70 HRESULT hrStatus;
71 } complete;
72 + struct
73 + {
74 + BURN_CACHE_STEP cacheStep;
75 + } failure;
76 };
77 } BURN_CACHE_MESSAGE;
78
src/burn/engine/container.cpp
+1
@@ -173,6 +173,7 @@ extern "C" void ContainersUninitialize(
173 ReleaseStr(pContainer->downloadSource.sczUser);
174 ReleaseStr(pContainer->downloadSource.sczPassword);
175 ReleaseStr(pContainer->sczUnverifiedPath);
176 + ReleaseStr(pContainer->sczFailedLocalAcquisitionPath);
177 ReleaseDict(pContainer->sdhPayloads);
178 }
179 MemFree(pContainers->rgContainers);
src/burn/engine/container.h
+3 -1
@@ -88,7 +88,9 @@ typedef struct _BURN_CONTAINER
88 DWORD64 qwExtractSizeTotal;
89 DWORD64 qwCommittedCacheProgress;
90 DWORD64 qwCommittedExtractProgress;
91 - HRESULT hrExtract;
91 + BOOL fExtracted;
92 + BOOL fFailedVerificationFromAcquisition;
93 + LPWSTR sczFailedLocalAcquisitionPath;
94 } BURN_CONTAINER;
95
96 typedef struct _BURN_CONTAINERS
src/burn/engine/elevation.cpp
+20 -3
@@ -42,6 +42,7 @@ typedef enum _BURN_ELEVATION_MESSAGE_TYPE
42 BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_BEGIN,
43 BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_COMPLETE,
44 BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_SUCCESS,
45 + BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_FAILURE,
46 BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROGRESS,
47 BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROCESS_CANCEL,
48 BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROCESS_STARTED,
@@ -1792,11 +1793,19 @@ static HRESULT ProcessBurnCacheMessages(
1793 case BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_SUCCESS:
1794 // read message parameters
1795 hr = BuffReadNumber64((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &message.success.qwFileSize);
1795 - ExitOnFailure(hr, "Failed to read begin cache step.");
1796 + ExitOnFailure(hr, "Failed to read success file size.");
1797
1798 message.type = BURN_CACHE_MESSAGE_SUCCESS;
1799 break;
1800
1801 + case BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_FAILURE:
1802 + // read message parameters
1803 + hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, reinterpret_cast<DWORD*>(&message.failure.cacheStep));
1804 + ExitOnFailure(hr, "Failed to read failure cache step.");
1805 +
1806 + message.type = BURN_CACHE_MESSAGE_FAILURE;
1807 + break;
1808 +
1809 case BURN_ELEVATION_MESSAGE_TYPE_PROGRESS_ROUTINE:
1810 fProgressRoutine = TRUE;
1811 break;
@@ -3511,7 +3520,7 @@ static HRESULT CALLBACK BurnCacheMessageHandler(
3520 case BURN_CACHE_MESSAGE_BEGIN:
3521 // serialize message data
3522 hr = BuffWriteNumber(&pbData, &cbData, pMessage->begin.cacheStep);
3514 - ExitOnFailure(hr, "Failed to write progress percentage to message buffer.");
3523 + ExitOnFailure(hr, "Failed to write cache step to message buffer.");
3524
3525 dwMessage = BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_BEGIN;
3526 break;
@@ -3526,10 +3535,18 @@ static HRESULT CALLBACK BurnCacheMessageHandler(
3535
3536 case BURN_CACHE_MESSAGE_SUCCESS:
3537 hr = BuffWriteNumber64(&pbData, &cbData, pMessage->success.qwFileSize);
3529 - ExitOnFailure(hr, "Failed to count of files in use to message buffer.");
3538 + ExitOnFailure(hr, "Failed to write file size to message buffer.");
3539
3540 dwMessage = BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_SUCCESS;
3541 break;
3542 +
3543 + case BURN_CACHE_MESSAGE_FAILURE:
3544 + // serialize message data
3545 + hr = BuffWriteNumber(&pbData, &cbData, pMessage->failure.cacheStep);
3546 + ExitOnFailure(hr, "Failed to write cache step to message buffer.");
3547 +
3548 + dwMessage = BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_FAILURE;
3549 + break;
3550 }
3551
3552 // send message
src/burn/engine/payload.cpp
+1
@@ -240,6 +240,7 @@ extern "C" void PayloadUninitialize(
240 ReleaseMem(pPayload->pbCertificateRootPublicKeyIdentifier);
241 ReleaseStr(pPayload->sczSourcePath);
242 ReleaseStr(pPayload->sczLocalFilePath);
243 + ReleaseStr(pPayload->sczFailedLocalAcquisitionPath);
244 ReleaseStr(pPayload->downloadSource.sczUrl);
245 ReleaseStr(pPayload->downloadSource.sczUser);
246 ReleaseStr(pPayload->downloadSource.sczPassword);
src/burn/engine/payload.h
+3
@@ -60,6 +60,9 @@ typedef struct _BURN_PAYLOAD
60
61 LPWSTR sczUnverifiedPath;
62 DWORD cRemainingInstances;
63 +
64 + BOOL fFailedVerificationFromAcquisition;
65 + LPWSTR sczFailedLocalAcquisitionPath;
66 } BURN_PAYLOAD;
67
68 typedef struct _BURN_PAYLOADS
src/burn/engine/plan.cpp
+5 -1
@@ -2105,7 +2105,9 @@ static void ResetPlannedContainerState(
2105 pContainer->qwExtractSizeTotal = 0;
2106 pContainer->qwCommittedCacheProgress = 0;
2107 pContainer->qwCommittedExtractProgress = 0;
2108 - pContainer->hrExtract = S_OK;
2108 + pContainer->fExtracted = FALSE;
2109 + pContainer->fFailedVerificationFromAcquisition = FALSE;
2110 + ReleaseNullStr(pContainer->sczFailedLocalAcquisitionPath);
2111 }
2112
2113 static void ResetPlannedPayloadsState(
@@ -2118,7 +2120,9 @@ static void ResetPlannedPayloadsState(
2120
2121 pPayload->cRemainingInstances = 0;
2122 pPayload->state = BURN_PAYLOAD_STATE_NONE;
2123 + pPayload->fFailedVerificationFromAcquisition = FALSE;
2124 ReleaseNullStr(pPayload->sczLocalFilePath);
2125 + ReleaseNullStr(pPayload->sczFailedLocalAcquisitionPath);
2126 }
2127 }
2128
src/test/burn/TestBA/TestBA.cs
+51
@@ -155,6 +155,9 @@ namespace WixToolset.Test.BA
155 this.quitAfterDetect = false;
156 }
157
158 + this.ImportContainerSources();
159 + this.ImportPayloadSources();
160 +
161 this.wait.WaitOne();
162
163 if (this.action == LaunchAction.Help)
@@ -657,6 +660,54 @@ namespace WixToolset.Test.BA
660 this.Engine.Log(LogLevel.Standard, String.Concat("TESTBA", relation, ": ", message));
661 }
662
663 + private void ImportContainerSources()
664 + {
665 + string testName = this.Engine.GetVariableString("TestGroupName");
666 + using (RegistryKey testKey = Registry.LocalMachine.OpenSubKey(String.Format(@"Software\WiX\Tests\TestBAControl\{0}\container", testName)))
667 + {
668 + if (testKey == null)
669 + {
670 + return;
671 + }
672 +
673 + foreach (var containerId in testKey.GetSubKeyNames())
674 + {
675 + using (RegistryKey subkey = testKey.OpenSubKey(containerId))
676 + {
677 + string initialSource = subkey == null ? null : subkey.GetValue("InitialLocalSource") as string;
678 + if (initialSource != null)
679 + {
680 + this.Engine.SetLocalSource(containerId, null, initialSource);
681 + }
682 + }
683 + }
684 + }
685 + }
686 +
687 + private void ImportPayloadSources()
688 + {
689 + string testName = this.Engine.GetVariableString("TestGroupName");
690 + using (RegistryKey testKey = Registry.LocalMachine.OpenSubKey(String.Format(@"Software\WiX\Tests\TestBAControl\{0}\payload", testName)))
691 + {
692 + if (testKey == null)
693 + {
694 + return;
695 + }
696 +
697 + foreach (var payloadId in testKey.GetSubKeyNames())
698 + {
699 + using (RegistryKey subkey = testKey.OpenSubKey(payloadId))
700 + {
701 + string initialSource = subkey == null ? null : subkey.GetValue("InitialLocalSource") as string;
702 + if (initialSource != null)
703 + {
704 + this.Engine.SetLocalSource(null, payloadId, initialSource);
705 + }
706 + }
707 + }
708 + }
709 + }
710 +
711 private List<string> ReadVerifyArguments()
712 {
713 string testName = this.Engine.GetVariableString("TestGroupName");
src/test/burn/TestData/LayoutTests/BundleB/BundleB.wixproj new
+17
@@ -0,0 +1,17 @@
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 +<Project Sdk="WixToolset.Sdk">
3 + <PropertyGroup>
4 + <BA>hyperlinkLicense</BA>
5 + <OutputType>Bundle</OutputType>
6 + <UpgradeCode>{CBF22B7D-C6C0-408A-9F29-81FE8610559C}</UpgradeCode>
7 + </PropertyGroup>
8 + <ItemGroup>
9 + <Compile Include="..\..\Templates\Bundle.wxs" Link="Bundle.wxs" />
10 + </ItemGroup>
11 + <ItemGroup>
12 + <ProjectReference Include="..\PackageA\PackageA.wixproj" />
13 + </ItemGroup>
14 + <ItemGroup>
15 + <PackageReference Include="WixToolset.Bal.wixext" />
16 + </ItemGroup>
17 +</Project>
\ No newline at end of file
src/test/burn/TestData/LayoutTests/BundleB/BundleB.wxs new
+10
@@ -0,0 +1,10 @@
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 +
4 +<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
5 + <Fragment>
6 + <PackageGroup Id="BundlePackages">
7 + <MsiPackage Id="PackageA" SourceFile="$(var.PackageA.TargetPath)" Compressed="no" DownloadUrl="$(var.WebServerBaseUrl)BundleB/{2}" />
8 + </PackageGroup>
9 + </Fragment>
10 +</Wix>
src/test/burn/WixToolsetTest.BurnE2E/LayoutTests.cs
+93
@@ -65,5 +65,98 @@ namespace WixToolsetTest.BurnE2E
65 Assert.True(File.Exists(Path.Combine(layoutDirectory, "packages.cab")));
66 Assert.True(File.Exists(Path.Combine(layoutDirectory, "BundleA.wxs")));
67 }
68 +
69 + [RuntimeFact]
70 + public void CanSkipOverCorruptLocalFileForDownloadableFile()
71 + {
72 + var bundleB = this.CreateBundleInstaller("BundleB");
73 + var webServer = this.CreateWebServer();
74 +
75 + webServer.AddFiles(new Dictionary<string, string>
76 + {
77 + { "/BundleB/PackageA.msi", Path.Combine(this.TestContext.TestDataFolder, "PackageA.msi") },
78 + });
79 + webServer.Start();
80 +
81 + using var dfs = new DisposableFileSystem();
82 + var baseDirectory = dfs.GetFolder(true);
83 + var sourceDirectory = Path.Combine(baseDirectory, "source");
84 + Directory.CreateDirectory(sourceDirectory);
85 + var layoutDirectory = Path.Combine(baseDirectory, "layout");
86 + Directory.CreateDirectory(layoutDirectory);
87 +
88 + // Manually copy bundle to empty directory and then run from there so it can't find the uncorrupted file.
89 + var bundleBFileInfo = new FileInfo(bundleB.Bundle);
90 + var bundleBCopiedPath = Path.Combine(sourceDirectory, bundleBFileInfo.Name);
91 + bundleBFileInfo.CopyTo(bundleBCopiedPath);
92 +
93 + // Copy a corrupted version of PackageA.msi next to the bundle.
94 + var packageAFileInfo = new FileInfo(Path.Combine(bundleBFileInfo.DirectoryName, "PackageA.msi"));
95 + var packageACorruptedFileInfo = new FileInfo(Path.Combine(sourceDirectory, packageAFileInfo.Name));
96 + packageAFileInfo.CopyTo(packageACorruptedFileInfo.FullName);
97 + SubtlyCorruptFile(packageACorruptedFileInfo);
98 +
99 + var layoutLogPath = bundleB.Layout(bundleBCopiedPath, layoutDirectory);
100 + bundleB.VerifyUnregisteredAndRemovedFromPackageCache();
101 +
102 + Assert.True(File.Exists(Path.Combine(layoutDirectory, bundleBFileInfo.Name)));
103 + Assert.True(File.Exists(Path.Combine(layoutDirectory, packageAFileInfo.Name)));
104 + Assert.True(LogVerifier.MessageInLogFile(layoutLogPath, "Verification failed on payload group item: PackageA"));
105 + }
106 +
107 + [RuntimeFact]
108 + public void CanSkipOverCorruptLocalFileForOtherLocalFile()
109 + {
110 + var bundleA = this.CreateBundleInstaller("BundleA");
111 + var testBAController = this.CreateTestBAController();
112 +
113 + using var dfs = new DisposableFileSystem();
114 + var baseDirectory = dfs.GetFolder(true);
115 + var sourceDirectory = Path.Combine(baseDirectory, "source");
116 + Directory.CreateDirectory(sourceDirectory);
117 + var layoutDirectory = Path.Combine(baseDirectory, "layout");
118 + Directory.CreateDirectory(layoutDirectory);
119 +
120 + // Copy a corrupted version of packages.cab and BundleA.wxs.
121 + var bundleAFileInfo = new FileInfo(bundleA.Bundle);
122 + var packagesCabFileInfo = new FileInfo(Path.Combine(bundleAFileInfo.DirectoryName, "packages.cab"));
123 + var packagesCabCorruptedFileInfo = new FileInfo(Path.Combine(sourceDirectory, packagesCabFileInfo.Name));
124 + packagesCabFileInfo.CopyTo(packagesCabCorruptedFileInfo.FullName);
125 + SubtlyCorruptFile(packagesCabCorruptedFileInfo);
126 +
127 + var layoutOnlyPayloadFileInfo = new FileInfo(Path.Combine(bundleAFileInfo.DirectoryName, "BundleA.wxs"));
128 + var layoutOnlyPayloadCorruptedFileInfo = new FileInfo(Path.Combine(sourceDirectory, layoutOnlyPayloadFileInfo.Name));
129 + layoutOnlyPayloadFileInfo.CopyTo(layoutOnlyPayloadCorruptedFileInfo.FullName);
130 + SubtlyCorruptFile(layoutOnlyPayloadCorruptedFileInfo);
131 +
132 + // Set the source to absolute path so the engine tries the corrupted files first.
133 + testBAController.SetContainerInitialLocalSource("PackagesContainer", packagesCabCorruptedFileInfo.FullName);
134 + testBAController.SetPayloadInitialLocalSource("LayoutOnlyPayload", layoutOnlyPayloadCorruptedFileInfo.FullName);
135 +
136 + var layoutLogPath = bundleA.Layout(layoutDirectory);
137 + bundleA.VerifyUnregisteredAndRemovedFromPackageCache();
138 +
139 + Assert.True(File.Exists(Path.Combine(layoutDirectory, bundleAFileInfo.Name)));
140 + Assert.True(File.Exists(Path.Combine(layoutDirectory, packagesCabFileInfo.Name)));
141 + Assert.True(File.Exists(Path.Combine(layoutDirectory, "BundleA.wxs")));
142 + Assert.True(LogVerifier.MessageInLogFile(layoutLogPath, "Verification failed on container: PackagesContainer"));
143 + Assert.True(LogVerifier.MessageInLogFile(layoutLogPath, "Verification failed on payload group item: LayoutOnlyPayload"));
144 + }
145 +
146 + private static void SubtlyCorruptFile(FileInfo fileInfo)
147 + {
148 + using (var v = fileInfo.Open(FileMode.Open, FileAccess.ReadWrite))
149 + {
150 + // Change one byte of information in the middle of the file to corrupt it.
151 + // Hopefully this ensures that these tests will continue to work even if optimizations are added later,
152 + // such as checking the file header, product code, or product version during acquisition.
153 + var bytePosition = v.Length / 2;
154 + v.Position = bytePosition;
155 + var byteValue = v.ReadByte();
156 + byteValue = byteValue == 0 ? 1 : 0;
157 + v.Position = bytePosition;
158 + v.WriteByte((byte)byteValue);
159 + }
160 + }
161 }
162 }
src/test/burn/WixToolsetTest.BurnE2E/Utilities/TestBAController.cs
+52 -1
@@ -182,6 +182,26 @@ namespace WixToolsetTest.BurnE2E
182 this.SetPackageState(packageId, "ProcessCancelAction", action.ToString());
183 }
184
185 + /// <summary>
186 + /// At startup, set the payload's source path to the given path.
187 + /// </summary>
188 + /// <param name="payloadId"></param>
189 + /// <param name="sourcePath"></param>
190 + public void SetPayloadInitialLocalSource(string payloadId, string sourcePath)
191 + {
192 + this.SetPayloadState(payloadId, "InitialLocalSource", sourcePath);
193 + }
194 +
195 + /// <summary>
196 + /// At startup, set the container's source path to the given path.
197 + /// </summary>
198 + /// <param name="containerId"></param>
199 + /// <param name="sourcePath"></param>
200 + public void SetContainerInitialLocalSource(string containerId, string sourcePath)
201 + {
202 + this.SetContainerState(containerId, "InitialLocalSource", sourcePath);
203 + }
204 +
205 /// <summary>
206 /// Sets the number of times to re-run the Detect phase.
207 /// </summary>
@@ -204,7 +224,6 @@ namespace WixToolsetTest.BurnE2E
224 public void SetVerifyArguments(string verifyArguments)
225 {
226 this.SetBurnTestValue("VerifyArguments", verifyArguments);
207 -
227 }
228
229 private void SetPackageState(string packageId, string name, string value)
@@ -223,6 +242,38 @@ namespace WixToolsetTest.BurnE2E
242 }
243 }
244
245 + private void SetContainerState(string containerId, string name, string value)
246 + {
247 + var key = String.Format(@"{0}\container\{1}", this.TestBaseRegKeyPath, containerId);
248 + using (var containerKey = Registry.LocalMachine.CreateSubKey(key))
249 + {
250 + if (String.IsNullOrEmpty(value))
251 + {
252 + containerKey.DeleteValue(name, false);
253 + }
254 + else
255 + {
256 + containerKey.SetValue(name, value);
257 + }
258 + }
259 + }
260 +
261 + private void SetPayloadState(string payloadId, string name, string value)
262 + {
263 + var key = String.Format(@"{0}\payload\{1}", this.TestBaseRegKeyPath, payloadId ?? String.Empty);
264 + using (var payloadKey = Registry.LocalMachine.CreateSubKey(key))
265 + {
266 + if (String.IsNullOrEmpty(value))
267 + {
268 + payloadKey.DeleteValue(name, false);
269 + }
270 + else
271 + {
272 + payloadKey.SetValue(name, value);
273 + }
274 + }
275 + }
276 +
277 public void Dispose()
278 {
279 Registry.LocalMachine.DeleteSubKeyTree($@"{this.BaseRegKeyPath}\{this.TestGroupName}", false);