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);