aboutsummaryrefslogtreecommitdiff
path: root/src/engine/cache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine/cache.cpp')
-rw-r--r--src/engine/cache.cpp2026
1 files changed, 2026 insertions, 0 deletions
diff --git a/src/engine/cache.cpp b/src/engine/cache.cpp
new file mode 100644
index 00000000..9338426d
--- /dev/null
+++ b/src/engine/cache.cpp
@@ -0,0 +1,2026 @@
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
5static const LPCWSTR BUNDLE_CLEAN_ROOM_WORKING_FOLDER_NAME = L".cr";
6static const LPCWSTR BUNDLE_WORKING_FOLDER_NAME = L".be";
7static const LPCWSTR UNVERIFIED_CACHE_FOLDER_NAME = L".unverified";
8static const LPCWSTR PACKAGE_CACHE_FOLDER_NAME = L"Package Cache";
9static const DWORD FILE_OPERATION_RETRY_COUNT = 3;
10static const DWORD FILE_OPERATION_RETRY_WAIT = 2000;
11
12static BOOL vfInitializedCache = FALSE;
13static BOOL vfRunningFromCache = FALSE;
14static LPWSTR vsczSourceProcessPath = NULL;
15static LPWSTR vsczWorkingFolder = NULL;
16static LPWSTR vsczDefaultUserPackageCache = NULL;
17static LPWSTR vsczDefaultMachinePackageCache = NULL;
18static LPWSTR vsczCurrentMachinePackageCache = NULL;
19
20static HRESULT CalculateWorkingFolder(
21 __in_z LPCWSTR wzBundleId,
22 __deref_out_z LPWSTR* psczWorkingFolder
23 );
24static HRESULT GetLastUsedSourceFolder(
25 __in BURN_VARIABLES* pVariables,
26 __out_z LPWSTR* psczLastSource
27 );
28static HRESULT CreateCompletedPath(
29 __in BOOL fPerMachine,
30 __in LPCWSTR wzCacheId,
31 __out LPWSTR* psczCacheDirectory
32 );
33static HRESULT CreateUnverifiedPath(
34 __in BOOL fPerMachine,
35 __in_z LPCWSTR wzPayloadId,
36 __out_z LPWSTR* psczUnverifiedPayloadPath
37 );
38static HRESULT GetRootPath(
39 __in BOOL fPerMachine,
40 __in BOOL fAllowRedirect,
41 __deref_out_z LPWSTR* psczRootPath
42 );
43static HRESULT VerifyThenTransferContainer(
44 __in BURN_CONTAINER* pContainer,
45 __in_z LPCWSTR wzCachedPath,
46 __in_z LPCWSTR wzUnverifiedContainerPath,
47 __in BOOL fMove
48 );
49static HRESULT VerifyThenTransferPayload(
50 __in BURN_PAYLOAD* pPayload,
51 __in_z LPCWSTR wzCachedPath,
52 __in_z LPCWSTR wzUnverifiedPayloadPath,
53 __in BOOL fMove
54 );
55static HRESULT TransferWorkingPathToUnverifiedPath(
56 __in_z LPCWSTR wzWorkingPath,
57 __in_z LPCWSTR wzUnverifiedPayloadPath,
58 __in BOOL fMove
59 );
60static HRESULT VerifyFileAgainstPayload(
61 __in BURN_PAYLOAD* pPayload,
62 __in_z LPCWSTR wzVerifyPath
63 );
64static HRESULT ResetPathPermissions(
65 __in BOOL fPerMachine,
66 __in_z LPCWSTR wzPath
67 );
68static HRESULT SecurePath(
69 __in LPCWSTR wzPath
70 );
71static HRESULT CopyEngineToWorkingFolder(
72 __in_z LPCWSTR wzSourcePath,
73 __in_z LPCWSTR wzWorkingFolderName,
74 __in_z LPCWSTR wzExecutableName,
75 __in BURN_PAYLOADS* pUxPayloads,
76 __in BURN_SECTION* pSection,
77 __deref_out_z_opt LPWSTR* psczEngineWorkingPath
78 );
79static HRESULT CopyEngineWithSignatureFixup(
80 __in HANDLE hEngineFile,
81 __in_z LPCWSTR wzEnginePath,
82 __in_z LPCWSTR wzTargetPath,
83 __in BURN_SECTION* pSection
84 );
85static HRESULT RemoveBundleOrPackage(
86 __in BOOL fBundle,
87 __in BOOL fPerMachine,
88 __in_z LPCWSTR wzBundleOrPackageId,
89 __in_z LPCWSTR wzCacheId
90 );
91static HRESULT VerifyHash(
92 __in BYTE* pbHash,
93 __in DWORD cbHash,
94 __in_z LPCWSTR wzUnverifiedPayloadPath,
95 __in HANDLE hFile
96 );
97static HRESULT VerifyPayloadWithCatalog(
98 __in BURN_PAYLOAD* pPayload,
99 __in_z LPCWSTR wzUnverifiedPayloadPath,
100 __in HANDLE hFile
101 );
102static HRESULT VerifyPayloadAgainstChain(
103 __in BURN_PAYLOAD* pPayload,
104 __in PCCERT_CHAIN_CONTEXT pChainContext
105 );
106
107
108extern "C" HRESULT CacheInitialize(
109 __in BURN_REGISTRATION* pRegistration,
110 __in BURN_VARIABLES* pVariables,
111 __in_z_opt LPCWSTR wzSourceProcessPath
112 )
113{
114 HRESULT hr = S_OK;
115 LPWSTR sczCurrentPath = NULL;
116 LPWSTR sczCompletedFolder = NULL;
117 LPWSTR sczCompletedPath = NULL;
118 LPWSTR sczOriginalSource = NULL;
119 LPWSTR sczOriginalSourceFolder = NULL;
120 int nCompare = 0;
121
122 if (!vfInitializedCache)
123 {
124 hr = PathForCurrentProcess(&sczCurrentPath, NULL);
125 ExitOnFailure(hr, "Failed to get current process path.");
126
127 // Determine if we are running from the package cache or not.
128 hr = CacheGetCompletedPath(pRegistration->fPerMachine, pRegistration->sczId, &sczCompletedFolder);
129 ExitOnFailure(hr, "Failed to get completed path for bundle.");
130
131 hr = PathConcat(sczCompletedFolder, pRegistration->sczExecutableName, &sczCompletedPath);
132 ExitOnFailure(hr, "Failed to combine working path with engine file name.");
133
134 hr = PathCompare(sczCurrentPath, sczCompletedPath, &nCompare);
135 ExitOnFailure(hr, "Failed to compare current path for bundle: %ls", sczCurrentPath);
136
137 vfRunningFromCache = (CSTR_EQUAL == nCompare);
138
139 // If a source process path was not provided (e.g. we are not being
140 // run in a clean room) then use the current process path as the
141 // source process path.
142 if (!wzSourceProcessPath)
143 {
144 wzSourceProcessPath = sczCurrentPath;
145 }
146
147 hr = StrAllocString(&vsczSourceProcessPath, wzSourceProcessPath, 0);
148 ExitOnFailure(hr, "Failed to initialize cache source path.");
149
150 // If we're not running from the cache, ensure the original source is set.
151 if (!vfRunningFromCache)
152 {
153 // If the original source has not been set already then set it where the bundle is
154 // running from right now. This value will be persisted and we'll use it when launched
155 // from the clean room or package cache since none of our packages will be relative to
156 // those locations.
157 hr = VariableGetString(pVariables, BURN_BUNDLE_ORIGINAL_SOURCE, &sczOriginalSource);
158 if (E_NOTFOUND == hr)
159 {
160 hr = VariableSetLiteralString(pVariables, BURN_BUNDLE_ORIGINAL_SOURCE, wzSourceProcessPath, FALSE);
161 ExitOnFailure(hr, "Failed to set original source variable.");
162
163 hr = StrAllocString(&sczOriginalSource, wzSourceProcessPath, 0);
164 ExitOnFailure(hr, "Failed to copy current path to original source.");
165 }
166
167 hr = VariableGetString(pVariables, BURN_BUNDLE_ORIGINAL_SOURCE_FOLDER, &sczOriginalSourceFolder);
168 if (E_NOTFOUND == hr)
169 {
170 hr = PathGetDirectory(sczOriginalSource, &sczOriginalSourceFolder);
171 ExitOnFailure(hr, "Failed to get directory from original source path.");
172
173 hr = VariableSetLiteralString(pVariables, BURN_BUNDLE_ORIGINAL_SOURCE_FOLDER, sczOriginalSourceFolder, FALSE);
174 ExitOnFailure(hr, "Failed to set original source directory variable.");
175 }
176 }
177
178 vfInitializedCache = TRUE;
179 }
180
181LExit:
182 ReleaseStr(sczCurrentPath);
183 ReleaseStr(sczCompletedFolder);
184 ReleaseStr(sczCompletedPath);
185 ReleaseStr(sczOriginalSource);
186 ReleaseStr(sczOriginalSourceFolder);
187
188 return hr;
189}
190
191extern "C" HRESULT CacheEnsureWorkingFolder(
192 __in_z LPCWSTR wzBundleId,
193 __deref_out_z_opt LPWSTR* psczWorkingFolder
194 )
195{
196 HRESULT hr = S_OK;
197 LPWSTR sczWorkingFolder = NULL;
198
199 hr = CalculateWorkingFolder(wzBundleId, &sczWorkingFolder);
200 ExitOnFailure(hr, "Failed to calculate working folder to ensure it exists.");
201
202 hr = DirEnsureExists(sczWorkingFolder, NULL);
203 ExitOnFailure(hr, "Failed create working folder.");
204
205 // Best effort to ensure our working folder is not encrypted.
206 ::DecryptFileW(sczWorkingFolder, 0);
207
208 if (psczWorkingFolder)
209 {
210 hr = StrAllocString(psczWorkingFolder, sczWorkingFolder, 0);
211 ExitOnFailure(hr, "Failed to copy working folder.");
212 }
213
214LExit:
215 ReleaseStr(sczWorkingFolder);
216
217 return hr;
218}
219
220extern "C" HRESULT CacheCalculateBundleWorkingPath(
221 __in_z LPCWSTR wzBundleId,
222 __in LPCWSTR wzExecutableName,
223 __deref_out_z LPWSTR* psczWorkingPath
224 )
225{
226 Assert(vfInitializedCache);
227
228 HRESULT hr = S_OK;
229 LPWSTR sczWorkingFolder = NULL;
230
231 // If the bundle is running out of the package cache then we use that as the
232 // working folder since we feel safe in the package cache.
233 if (vfRunningFromCache)
234 {
235 hr = PathForCurrentProcess(psczWorkingPath, NULL);
236 ExitOnFailure(hr, "Failed to get current process path.");
237 }
238 else // Otherwise, use the real working folder.
239 {
240 hr = CalculateWorkingFolder(wzBundleId, &sczWorkingFolder);
241 ExitOnFailure(hr, "Failed to get working folder for bundle.");
242
243 hr = StrAllocFormatted(psczWorkingPath, L"%ls%ls\\%ls", sczWorkingFolder, BUNDLE_WORKING_FOLDER_NAME, wzExecutableName);
244 ExitOnFailure(hr, "Failed to calculate the bundle working path.");
245 }
246
247LExit:
248 ReleaseStr(sczWorkingFolder);
249
250 return hr;
251}
252
253extern "C" HRESULT CacheCalculateBundleLayoutWorkingPath(
254 __in_z LPCWSTR wzBundleId,
255 __deref_out_z LPWSTR* psczWorkingPath
256 )
257{
258 HRESULT hr = S_OK;
259 LPWSTR sczWorkingFolder = NULL;
260
261 hr = CalculateWorkingFolder(wzBundleId, psczWorkingPath);
262 ExitOnFailure(hr, "Failed to get working folder for bundle layout.");
263
264 hr = StrAllocConcat(psczWorkingPath, wzBundleId, 0);
265 ExitOnFailure(hr, "Failed to append bundle id for bundle layout working path.");
266
267LExit:
268 ReleaseStr(sczWorkingFolder);
269
270 return hr;
271}
272
273extern "C" HRESULT CacheCalculatePayloadWorkingPath(
274 __in_z LPCWSTR wzBundleId,
275 __in BURN_PAYLOAD* pPayload,
276 __deref_out_z LPWSTR* psczWorkingPath
277 )
278{
279 HRESULT hr = S_OK;
280
281 hr = CalculateWorkingFolder(wzBundleId, psczWorkingPath);
282 ExitOnFailure(hr, "Failed to get working folder for payload.");
283
284 hr = StrAllocConcat(psczWorkingPath, pPayload->sczKey, 0);
285 ExitOnFailure(hr, "Failed to append SHA1 hash as payload unverified path.");
286
287LExit:
288 return hr;
289}
290
291extern "C" HRESULT CacheCalculateContainerWorkingPath(
292 __in_z LPCWSTR wzBundleId,
293 __in BURN_CONTAINER* pContainer,
294 __deref_out_z LPWSTR* psczWorkingPath
295 )
296{
297 HRESULT hr = S_OK;
298
299 hr = CalculateWorkingFolder(wzBundleId, psczWorkingPath);
300 ExitOnFailure(hr, "Failed to get working folder for container.");
301
302 hr = StrAllocConcat(psczWorkingPath, pContainer->sczHash, 0);
303 ExitOnFailure(hr, "Failed to append SHA1 hash as container unverified path.");
304
305LExit:
306 return hr;
307}
308
309extern "C" HRESULT CacheGetRootCompletedPath(
310 __in BOOL fPerMachine,
311 __in BOOL fForceInitialize,
312 __deref_out_z LPWSTR* psczRootCompletedPath
313 )
314{
315 HRESULT hr = S_OK;
316
317 if (fForceInitialize)
318 {
319 hr = CreateCompletedPath(fPerMachine, L"", psczRootCompletedPath);
320 }
321 else
322 {
323 hr = GetRootPath(fPerMachine, TRUE, psczRootCompletedPath);
324 }
325
326 return hr;
327}
328
329extern "C" HRESULT CacheGetCompletedPath(
330 __in BOOL fPerMachine,
331 __in_z LPCWSTR wzCacheId,
332 __deref_out_z LPWSTR* psczCompletedPath
333 )
334{
335 HRESULT hr = S_OK;
336 BOOL fRedirected = FALSE;
337 LPWSTR sczRootPath = NULL;
338 LPWSTR sczCurrentCompletedPath = NULL;
339 LPWSTR sczDefaultCompletedPath = NULL;
340
341 hr = GetRootPath(fPerMachine, TRUE, &sczRootPath);
342 ExitOnFailure(hr, "Failed to get %hs package cache root directory.", fPerMachine ? "per-machine" : "per-user");
343
344 // GetRootPath returns S_FALSE if the package cache is redirected elsewhere.
345 fRedirected = S_FALSE == hr;
346
347 hr = PathConcat(sczRootPath, wzCacheId, &sczCurrentCompletedPath);
348 ExitOnFailure(hr, "Failed to construct cache path.");
349
350 hr = PathBackslashTerminate(&sczCurrentCompletedPath);
351 ExitOnFailure(hr, "Failed to ensure cache path was backslash terminated.");
352
353 // Return the old package cache directory if the new directory does not exist but the old directory does.
354 // If neither package cache directory exists return the (possibly) redirected package cache directory.
355 if (fRedirected && !DirExists(sczCurrentCompletedPath, NULL))
356 {
357 hr = GetRootPath(fPerMachine, FALSE, &sczRootPath);
358 ExitOnFailure(hr, "Failed to get old %hs package cache root directory.", fPerMachine ? "per-machine" : "per-user");
359
360 hr = PathConcat(sczRootPath, wzCacheId, &sczDefaultCompletedPath);
361 ExitOnFailure(hr, "Failed to construct cache path.");
362
363 hr = PathBackslashTerminate(&sczDefaultCompletedPath);
364 ExitOnFailure(hr, "Failed to ensure cache path was backslash terminated.");
365
366 if (DirExists(sczDefaultCompletedPath, NULL))
367 {
368 *psczCompletedPath = sczDefaultCompletedPath;
369 sczDefaultCompletedPath = NULL;
370
371 ExitFunction();
372 }
373 }
374
375 *psczCompletedPath = sczCurrentCompletedPath;
376 sczCurrentCompletedPath = NULL;
377
378LExit:
379 ReleaseNullStr(sczDefaultCompletedPath);
380 ReleaseNullStr(sczCurrentCompletedPath);
381 ReleaseNullStr(sczRootPath);
382
383 return hr;
384}
385
386extern "C" HRESULT CacheGetResumePath(
387 __in_z LPCWSTR wzPayloadWorkingPath,
388 __deref_out_z LPWSTR* psczResumePath
389 )
390{
391 HRESULT hr = S_OK;
392
393 hr = StrAllocFormatted(psczResumePath, L"%ls.R", wzPayloadWorkingPath);
394 ExitOnFailure(hr, "Failed to create resume path.");
395
396LExit:
397 return hr;
398}
399
400extern "C" HRESULT CacheFindLocalSource(
401 __in_z LPCWSTR wzSourcePath,
402 __in BURN_VARIABLES* pVariables,
403 __out BOOL* pfFound,
404 __out_z LPWSTR* psczSourceFullPath
405 )
406{
407 HRESULT hr = S_OK;
408 LPWSTR sczSourceProcessFolder = NULL;
409 LPWSTR sczCurrentPath = NULL;
410 LPWSTR sczLastSourcePath = NULL;
411 LPWSTR sczLastSourceFolder = NULL;
412 LPWSTR sczLayoutPath = NULL;
413 LPWSTR sczLayoutFolder = NULL;
414 LPCWSTR rgwzSearchPaths[3] = { };
415 DWORD cSearchPaths = 0;
416
417 // If the source path provided is a full path, obviously that is where we should be looking.
418 if (PathIsAbsolute(wzSourcePath))
419 {
420 rgwzSearchPaths[0] = wzSourcePath;
421 cSearchPaths = 1;
422 }
423 else
424 {
425 // If we're not running from cache or we couldn't get the last source, use
426 // the source path location first. In the case where we are in the bundle's
427 // package cache and couldn't find a last used source we unfortunately will
428 // be picking the package cache path which isn't likely to have what we are
429 // looking for.
430 hr = GetLastUsedSourceFolder(pVariables, &sczLastSourceFolder);
431 if (!vfRunningFromCache || FAILED(hr))
432 {
433 hr = PathGetDirectory(vsczSourceProcessPath, &sczSourceProcessFolder);
434 ExitOnFailure(hr, "Failed to get current process directory.");
435
436 hr = PathConcat(sczSourceProcessFolder, wzSourcePath, &sczCurrentPath);
437 ExitOnFailure(hr, "Failed to combine last source with source.");
438
439 rgwzSearchPaths[0] = sczCurrentPath;
440 cSearchPaths = 1;
441 }
442
443 // If we have a last used source and it does not duplicate the existing search path,
444 // add the last used source to the search path second.
445 if (sczLastSourceFolder && *sczLastSourceFolder)
446 {
447 hr = PathConcat(sczLastSourceFolder, wzSourcePath, &sczLastSourcePath);
448 ExitOnFailure(hr, "Failed to combine last source with source.");
449
450 if (0 == cSearchPaths || CSTR_EQUAL != ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, rgwzSearchPaths[0], -1, sczLastSourcePath, -1))
451 {
452 rgwzSearchPaths[cSearchPaths] = sczLastSourcePath;
453 ++cSearchPaths;
454 }
455 }
456
457 // Also consider the layout directory if set on the command line or by the BA.
458 hr = VariableGetString(pVariables, BURN_BUNDLE_LAYOUT_DIRECTORY, &sczLayoutFolder);
459 if (E_NOTFOUND != hr)
460 {
461 ExitOnFailure(hr, "Failed to get bundle layout directory property.");
462
463 hr = PathConcat(sczLayoutFolder, wzSourcePath, &sczLayoutPath);
464 ExitOnFailure(hr, "Failed to combine layout source with source.");
465
466 rgwzSearchPaths[cSearchPaths] = sczLayoutPath;
467 ++cSearchPaths;
468 }
469 }
470
471 *pfFound = FALSE; // assume we won't find the file locally.
472
473 for (DWORD i = 0; i < cSearchPaths; ++i)
474 {
475 // If the file exists locally, copy its path.
476 if (FileExistsEx(rgwzSearchPaths[i], NULL))
477 {
478 hr = StrAllocString(psczSourceFullPath, rgwzSearchPaths[i], 0);
479 ExitOnFailure(hr, "Failed to copy source path.");
480
481 *pfFound = TRUE;
482 break;
483 }
484 }
485
486 // If nothing was found, return the first thing in our search path as the
487 // best path where we thought we should have found the file.
488 if (!*pfFound)
489 {
490 hr = StrAllocString(psczSourceFullPath, rgwzSearchPaths[0], 0);
491 ExitOnFailure(hr, "Failed to copy source path.");
492 }
493
494LExit:
495 ReleaseStr(sczCurrentPath);
496 ReleaseStr(sczSourceProcessFolder);
497 ReleaseStr(sczLastSourceFolder);
498 ReleaseStr(sczLastSourcePath);
499 ReleaseStr(sczLayoutFolder);
500 ReleaseStr(sczLayoutPath);
501
502 return hr;
503}
504
505extern "C" HRESULT CacheSetLastUsedSource(
506 __in BURN_VARIABLES* pVariables,
507 __in_z LPCWSTR wzSourcePath,
508 __in_z LPCWSTR wzRelativePath
509 )
510{
511 HRESULT hr = S_OK;
512 size_t cchSourcePath = 0;
513 size_t cchRelativePath = 0;
514 size_t iSourceRelativePath = 0;
515 LPWSTR sczSourceFolder = NULL;
516 LPWSTR sczLastSourceFolder = NULL;
517 int nCompare = 0;
518
519 hr = ::StringCchLengthW(wzSourcePath, STRSAFE_MAX_CCH, &cchSourcePath);
520 ExitOnFailure(hr, "Failed to determine length of source path.");
521
522 hr = ::StringCchLengthW(wzRelativePath, STRSAFE_MAX_CCH, &cchRelativePath);
523 ExitOnFailure(hr, "Failed to determine length of relative path.");
524
525 // If the source path is smaller than the relative path (plus space for "X:\") then we know they
526 // are not relative to each other.
527 if (cchSourcePath < cchRelativePath + 3)
528 {
529 ExitFunction();
530 }
531
532 // If the source path ends with the relative path then this source could be a new path.
533 iSourceRelativePath = cchSourcePath - cchRelativePath;
534 if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, wzSourcePath + iSourceRelativePath, -1, wzRelativePath, -1))
535 {
536 hr = StrAllocString(&sczSourceFolder, wzSourcePath, iSourceRelativePath);
537 ExitOnFailure(hr, "Failed to trim source folder.");
538
539 hr = VariableGetString(pVariables, BURN_BUNDLE_LAST_USED_SOURCE, &sczLastSourceFolder);
540 if (SUCCEEDED(hr))
541 {
542 nCompare = ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, sczSourceFolder, -1, sczLastSourceFolder, -1);
543 }
544 else if (E_NOTFOUND == hr)
545 {
546 nCompare = CSTR_GREATER_THAN;
547 hr = S_OK;
548 }
549
550 if (CSTR_EQUAL != nCompare)
551 {
552 hr = VariableSetLiteralString(pVariables, BURN_BUNDLE_LAST_USED_SOURCE, sczSourceFolder, FALSE);
553 ExitOnFailure(hr, "Failed to set last source.");
554 }
555 }
556
557LExit:
558 ReleaseStr(sczLastSourceFolder);
559 ReleaseStr(sczSourceFolder);
560
561 return hr;
562}
563
564extern "C" HRESULT CacheSendProgressCallback(
565 __in DOWNLOAD_CACHE_CALLBACK* pCallback,
566 __in DWORD64 dw64Progress,
567 __in DWORD64 dw64Total,
568 __in HANDLE hDestinationFile
569 )
570{
571 static LARGE_INTEGER LARGE_INTEGER_ZERO = { };
572
573 HRESULT hr = S_OK;
574 DWORD dwResult = PROGRESS_CONTINUE;
575 LARGE_INTEGER liTotalSize = { };
576 LARGE_INTEGER liTotalTransferred = { };
577
578 if (pCallback->pfnProgress)
579 {
580 liTotalSize.QuadPart = dw64Total;
581 liTotalTransferred.QuadPart = dw64Progress;
582
583 dwResult = (*pCallback->pfnProgress)(liTotalSize, liTotalTransferred, LARGE_INTEGER_ZERO, LARGE_INTEGER_ZERO, 1, CALLBACK_CHUNK_FINISHED, INVALID_HANDLE_VALUE, hDestinationFile, pCallback->pv);
584 switch (dwResult)
585 {
586 case PROGRESS_CONTINUE:
587 hr = S_OK;
588 break;
589
590 case PROGRESS_CANCEL: __fallthrough; // TODO: should cancel and stop be treated differently?
591 case PROGRESS_STOP:
592 hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT);
593 ExitOnRootFailure(hr, "UX aborted on download progress.");
594
595 case PROGRESS_QUIET: // Not actually an error, just an indication to the caller to stop requesting progress.
596 pCallback->pfnProgress = NULL;
597 hr = S_OK;
598 break;
599
600 default:
601 hr = E_UNEXPECTED;
602 ExitOnRootFailure(hr, "Invalid return code from progress routine.");
603 }
604 }
605
606LExit:
607 return hr;
608}
609
610extern "C" void CacheSendErrorCallback(
611 __in DOWNLOAD_CACHE_CALLBACK* pCallback,
612 __in HRESULT hrError,
613 __in_z_opt LPCWSTR wzError,
614 __out_opt BOOL* pfRetry
615 )
616{
617 if (pfRetry)
618 {
619 *pfRetry = FALSE;
620 }
621
622 if (pCallback->pfnCancel)
623 {
624 int nResult = (*pCallback->pfnCancel)(hrError, wzError, pfRetry != NULL, pCallback->pv);
625 if (pfRetry && IDRETRY == nResult)
626 {
627 *pfRetry = TRUE;
628 }
629 }
630}
631
632extern "C" BOOL CacheBundleRunningFromCache()
633{
634 return vfRunningFromCache;
635}
636
637extern "C" HRESULT CacheBundleToCleanRoom(
638 __in BURN_PAYLOADS* pUxPayloads,
639 __in BURN_SECTION* pSection,
640 __deref_out_z_opt LPWSTR* psczCleanRoomBundlePath
641 )
642{
643 HRESULT hr = S_OK;
644 LPWSTR sczSourcePath = NULL;
645 LPWSTR wzExecutableName = NULL;
646
647 hr = PathForCurrentProcess(&sczSourcePath, NULL);
648 ExitOnFailure(hr, "Failed to get current path for process to cache to clean room.");
649
650 wzExecutableName = PathFile(sczSourcePath);
651
652 hr = CopyEngineToWorkingFolder(sczSourcePath, BUNDLE_CLEAN_ROOM_WORKING_FOLDER_NAME, wzExecutableName, pUxPayloads, pSection, psczCleanRoomBundlePath);
653 ExitOnFailure(hr, "Failed to cache bundle to clean room.");
654
655LExit:
656 ReleaseStr(sczSourcePath);
657
658 return hr;
659}
660
661extern "C" HRESULT CacheBundleToWorkingDirectory(
662 __in_z LPCWSTR /*wzBundleId*/,
663 __in_z LPCWSTR wzExecutableName,
664 __in BURN_PAYLOADS* pUxPayloads,
665 __in BURN_SECTION* pSection,
666 __deref_out_z_opt LPWSTR* psczEngineWorkingPath
667 )
668{
669 Assert(vfInitializedCache);
670
671 HRESULT hr = S_OK;
672 LPWSTR sczSourcePath = NULL;
673
674 // Initialize the source.
675 hr = PathForCurrentProcess(&sczSourcePath, NULL);
676 ExitOnFailure(hr, "Failed to get current process path.");
677
678 // If the bundle is running out of the package cache then we don't need to copy it to
679 // the working folder since we feel safe in the package cache and will run from there.
680 if (vfRunningFromCache)
681 {
682 hr = StrAllocString(psczEngineWorkingPath, sczSourcePath, 0);
683 ExitOnFailure(hr, "Failed to use current process path as target path.");
684 }
685 else // otherwise, carry on putting the bundle in the working folder.
686 {
687 hr = CopyEngineToWorkingFolder(sczSourcePath, BUNDLE_WORKING_FOLDER_NAME, wzExecutableName, pUxPayloads, pSection, psczEngineWorkingPath);
688 ExitOnFailure(hr, "Failed to copy engine to working folder.");
689 }
690
691LExit:
692 ReleaseStr(sczSourcePath);
693
694 return hr;
695}
696
697extern "C" HRESULT CacheLayoutBundle(
698 __in_z LPCWSTR wzExecutableName,
699 __in_z LPCWSTR wzLayoutDirectory,
700 __in_z LPCWSTR wzSourceBundlePath
701 )
702{
703 HRESULT hr = S_OK;
704 LPWSTR sczTargetPath = NULL;
705
706 hr = PathConcat(wzLayoutDirectory, wzExecutableName, &sczTargetPath);
707 ExitOnFailure(hr, "Failed to combine completed path with engine file name for layout.");
708
709 LogStringLine(REPORT_STANDARD, "Layout bundle from: '%ls' to: '%ls'", wzSourceBundlePath, sczTargetPath);
710
711 hr = FileEnsureMoveWithRetry(wzSourceBundlePath, sczTargetPath, TRUE, TRUE, FILE_OPERATION_RETRY_COUNT, FILE_OPERATION_RETRY_WAIT);
712 ExitOnFailure(hr, "Failed to layout bundle from: '%ls' to '%ls'", wzSourceBundlePath, sczTargetPath);
713
714LExit:
715 ReleaseStr(sczTargetPath);
716
717 return hr;
718}
719
720extern "C" HRESULT CacheCompleteBundle(
721 __in BOOL fPerMachine,
722 __in_z LPCWSTR wzExecutableName,
723 __in_z LPCWSTR wzBundleId,
724 __in BURN_PAYLOADS* pUxPayloads,
725 __in_z LPCWSTR wzSourceBundlePath
726#ifdef DEBUG
727 , __in_z LPCWSTR wzExecutablePath
728#endif
729 )
730{
731 HRESULT hr = S_OK;
732 int nCompare = 0;
733 LPWSTR sczTargetDirectory = NULL;
734 LPWSTR sczTargetPath = NULL;
735 LPWSTR sczSourceDirectory = NULL;
736 LPWSTR sczPayloadSourcePath = NULL;
737
738 hr = CreateCompletedPath(fPerMachine, wzBundleId, &sczTargetDirectory);
739 ExitOnFailure(hr, "Failed to create completed cache path for bundle.");
740
741 hr = PathConcat(sczTargetDirectory, wzExecutableName, &sczTargetPath);
742 ExitOnFailure(hr, "Failed to combine completed path with engine file name.");
743
744 Assert(CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, wzExecutablePath, -1, sczTargetPath, -1));
745
746 // If the bundle is running out of the package cache then we don't need to copy it there
747 // (and don't want to since it'll be in use) so bail.
748 hr = PathCompare(wzSourceBundlePath, sczTargetPath, &nCompare);
749 ExitOnFailure(hr, "Failed to compare completed cache path for bundle: %ls", wzSourceBundlePath);
750
751 if (CSTR_EQUAL == nCompare)
752 {
753 ExitFunction();
754 }
755
756 // Otherwise, carry on putting the bundle in the cache.
757 LogStringLine(REPORT_STANDARD, "Caching bundle from: '%ls' to: '%ls'", wzSourceBundlePath, sczTargetPath);
758
759 FileRemoveFromPendingRename(sczTargetPath); // best effort to ensure bundle is not deleted from cache post restart.
760
761 hr = FileEnsureCopyWithRetry(wzSourceBundlePath, sczTargetPath, TRUE, FILE_OPERATION_RETRY_COUNT, FILE_OPERATION_RETRY_WAIT);
762 ExitOnFailure(hr, "Failed to cache bundle from: '%ls' to '%ls'", wzSourceBundlePath, sczTargetPath);
763
764 // Reset the path permissions in the cache.
765 hr = ResetPathPermissions(fPerMachine, sczTargetPath);
766 ExitOnFailure(hr, "Failed to reset permissions on cached bundle: '%ls'", sczTargetPath);
767
768 hr = PathGetDirectory(wzSourceBundlePath, &sczSourceDirectory);
769 ExitOnFailure(hr, "Failed to get directory from engine working path: %ls", wzSourceBundlePath);
770
771 // Cache external UX payloads to completed path.
772 for (DWORD i = 0; i < pUxPayloads->cPayloads; ++i)
773 {
774 BURN_PAYLOAD* pPayload = &pUxPayloads->rgPayloads[i];
775
776 if (BURN_PAYLOAD_PACKAGING_EXTERNAL == pPayload->packaging)
777 {
778 hr = PathConcat(sczSourceDirectory, pPayload->sczSourcePath, &sczPayloadSourcePath);
779 ExitOnFailure(hr, "Failed to build payload source path.");
780
781 hr = CacheCompletePayload(fPerMachine, pPayload, wzBundleId, sczPayloadSourcePath, FALSE);
782 ExitOnFailure(hr, "Failed to complete the cache of payload: %ls", pPayload->sczKey);
783 }
784 }
785
786LExit:
787 ReleaseStr(sczPayloadSourcePath);
788 ReleaseStr(sczSourceDirectory);
789 ReleaseStr(sczTargetPath);
790 ReleaseStr(sczTargetDirectory);
791
792 return hr;
793}
794
795extern "C" HRESULT CacheLayoutContainer(
796 __in BURN_CONTAINER* pContainer,
797 __in_z_opt LPCWSTR wzLayoutDirectory,
798 __in_z LPCWSTR wzUnverifiedContainerPath,
799 __in BOOL fMove
800 )
801{
802 HRESULT hr = S_OK;
803 LPWSTR sczCachedPath = NULL;
804
805 hr = PathConcat(wzLayoutDirectory, pContainer->sczFilePath, &sczCachedPath);
806 ExitOnFailure(hr, "Failed to concat complete cached path.");
807
808 hr = VerifyThenTransferContainer(pContainer, sczCachedPath, wzUnverifiedContainerPath, fMove);
809 ExitOnFailure(hr, "Failed to layout container from cached path: %ls", sczCachedPath);
810
811LExit:
812 ReleaseStr(sczCachedPath);
813
814 return hr;
815}
816
817extern "C" HRESULT CacheLayoutPayload(
818 __in BURN_PAYLOAD* pPayload,
819 __in_z_opt LPCWSTR wzLayoutDirectory,
820 __in_z LPCWSTR wzUnverifiedPayloadPath,
821 __in BOOL fMove
822 )
823{
824 HRESULT hr = S_OK;
825 LPWSTR sczCachedPath = NULL;
826
827 hr = PathConcat(wzLayoutDirectory, pPayload->sczFilePath, &sczCachedPath);
828 ExitOnFailure(hr, "Failed to concat complete cached path.");
829
830 hr = VerifyThenTransferPayload(pPayload, sczCachedPath, wzUnverifiedPayloadPath, fMove);
831 ExitOnFailure(hr, "Failed to layout payload from cached payload: %ls", sczCachedPath);
832
833LExit:
834 ReleaseStr(sczCachedPath);
835
836 return hr;
837}
838
839extern "C" HRESULT CacheCompletePayload(
840 __in BOOL fPerMachine,
841 __in BURN_PAYLOAD* pPayload,
842 __in_z_opt LPCWSTR wzCacheId,
843 __in_z LPCWSTR wzWorkingPayloadPath,
844 __in BOOL fMove
845 )
846{
847 HRESULT hr = S_OK;
848 LPWSTR sczCachedDirectory = NULL;
849 LPWSTR sczCachedPath = NULL;
850 LPWSTR sczUnverifiedPayloadPath = NULL;
851
852 hr = CreateCompletedPath(fPerMachine, wzCacheId, &sczCachedDirectory);
853 ExitOnFailure(hr, "Failed to get cached path for package with cache id: %ls", wzCacheId);
854
855 hr = PathConcat(sczCachedDirectory, pPayload->sczFilePath, &sczCachedPath);
856 ExitOnFailure(hr, "Failed to concat complete cached path.");
857
858 // If the cached file matches what we expected, we're good.
859 hr = VerifyFileAgainstPayload(pPayload, sczCachedPath);
860 if (SUCCEEDED(hr))
861 {
862 ::DecryptFileW(sczCachedPath, 0); // Let's try to make sure it's not encrypted.
863 LogId(REPORT_STANDARD, MSG_VERIFIED_EXISTING_PAYLOAD, pPayload->sczKey, sczCachedPath);
864 ExitFunction();
865 }
866 else if (E_PATHNOTFOUND != hr && E_FILENOTFOUND != hr)
867 {
868 LogErrorId(hr, MSG_FAILED_VERIFY_PAYLOAD, pPayload->sczKey, sczCachedPath, NULL);
869
870 FileEnsureDelete(sczCachedPath); // if the file existed but did not verify correctly, make it go away.
871 }
872
873 hr = CreateUnverifiedPath(fPerMachine, pPayload->sczKey, &sczUnverifiedPayloadPath);
874 ExitOnFailure(hr, "Failed to create unverified path.");
875
876 // If the working path exists, let's get it into the unverified path so we can reset the ACLs and verify the file.
877 if (FileExistsEx(wzWorkingPayloadPath, NULL))
878 {
879 hr = TransferWorkingPathToUnverifiedPath(wzWorkingPayloadPath, sczUnverifiedPayloadPath, fMove);
880 ExitOnFailure(hr, "Failed to transfer working path to unverified path for payload: %ls.", pPayload->sczKey);
881 }
882 else if (!FileExistsEx(sczUnverifiedPayloadPath, NULL)) // if the working path and unverified path do not exist, nothing we can do.
883 {
884 hr = E_FILENOTFOUND;
885 ExitOnFailure(hr, "Failed to find payload: %ls in working path: %ls and unverified path: %ls", pPayload->sczKey, wzWorkingPayloadPath, sczUnverifiedPayloadPath);
886 }
887
888 hr = ResetPathPermissions(fPerMachine, sczUnverifiedPayloadPath);
889 ExitOnFailure(hr, "Failed to reset permissions on unverified cached payload: %ls", pPayload->sczKey);
890
891 hr = VerifyFileAgainstPayload(pPayload, sczUnverifiedPayloadPath);
892 if (FAILED(hr))
893 {
894 LogErrorId(hr, MSG_FAILED_VERIFY_PAYLOAD, pPayload->sczKey, sczUnverifiedPayloadPath, NULL);
895
896 FileEnsureDelete(sczUnverifiedPayloadPath); // if the file did not verify correctly, make it go away.
897 ExitFunction();
898 }
899
900 LogId(REPORT_STANDARD, MSG_VERIFIED_ACQUIRED_PAYLOAD, pPayload->sczKey, sczUnverifiedPayloadPath, fMove ? "moving" : "copying", sczCachedPath);
901
902 hr = FileEnsureMoveWithRetry(sczUnverifiedPayloadPath, sczCachedPath, TRUE, TRUE, FILE_OPERATION_RETRY_COUNT, FILE_OPERATION_RETRY_WAIT);
903 ExitOnFailure(hr, "Failed to move verified file to complete payload path: %ls", sczCachedPath);
904
905 ::DecryptFileW(sczCachedPath, 0); // Let's try to make sure it's not encrypted.
906
907LExit:
908 ReleaseStr(sczUnverifiedPayloadPath);
909 ReleaseStr(sczCachedPath);
910 ReleaseStr(sczCachedDirectory);
911
912 return hr;
913}
914
915extern "C" HRESULT CacheRemoveWorkingFolder(
916 __in_z_opt LPCWSTR wzBundleId
917 )
918{
919 HRESULT hr = S_OK;
920 LPWSTR sczWorkingFolder = NULL;
921
922 if (vfInitializedCache)
923 {
924 hr = CalculateWorkingFolder(wzBundleId, &sczWorkingFolder);
925 ExitOnFailure(hr, "Failed to calculate the working folder to remove it.");
926
927 // Try to clean out everything in the working folder.
928 hr = DirEnsureDeleteEx(sczWorkingFolder, DIR_DELETE_FILES | DIR_DELETE_RECURSE | DIR_DELETE_SCHEDULE);
929 TraceError(hr, "Could not delete bundle engine working folder.");
930 }
931
932LExit:
933 ReleaseStr(sczWorkingFolder);
934
935 return hr;
936}
937
938extern "C" HRESULT CacheRemoveBundle(
939 __in BOOL fPerMachine,
940 __in_z LPCWSTR wzBundleId
941 )
942{
943 HRESULT hr = S_OK;
944
945 hr = RemoveBundleOrPackage(TRUE, fPerMachine, wzBundleId, wzBundleId);
946 ExitOnFailure(hr, "Failed to remove bundle id: %ls.", wzBundleId);
947
948LExit:
949 return hr;
950}
951
952extern "C" HRESULT CacheRemovePackage(
953 __in BOOL fPerMachine,
954 __in_z LPCWSTR wzPackageId,
955 __in_z LPCWSTR wzCacheId
956 )
957{
958 HRESULT hr = S_OK;
959
960 hr = RemoveBundleOrPackage(FALSE, fPerMachine, wzPackageId, wzCacheId);
961 ExitOnFailure(hr, "Failed to remove package id: %ls.", wzPackageId);
962
963LExit:
964 return hr;
965}
966
967extern "C" HRESULT CacheVerifyPayloadSignature(
968 __in BURN_PAYLOAD* pPayload,
969 __in_z LPCWSTR wzUnverifiedPayloadPath,
970 __in HANDLE hFile
971 )
972{
973 HRESULT hr = S_OK;
974 LONG er = ERROR_SUCCESS;
975
976 GUID guidAuthenticode = WINTRUST_ACTION_GENERIC_VERIFY_V2;
977 WINTRUST_FILE_INFO wfi = { };
978 WINTRUST_DATA wtd = { };
979 CRYPT_PROVIDER_DATA* pProviderData = NULL;
980 CRYPT_PROVIDER_SGNR* pSigner = NULL;
981
982 // Verify the payload assuming online.
983 wfi.cbStruct = sizeof(wfi);
984 wfi.pcwszFilePath = wzUnverifiedPayloadPath;
985 wfi.hFile = hFile;
986
987 wtd.cbStruct = sizeof(wtd);
988 wtd.dwUnionChoice = WTD_CHOICE_FILE;
989 wtd.pFile = &wfi;
990 wtd.dwStateAction = WTD_STATEACTION_VERIFY;
991 wtd.dwProvFlags = WTD_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT;
992 wtd.dwUIChoice = WTD_UI_NONE;
993
994 er = ::WinVerifyTrust(static_cast<HWND>(INVALID_HANDLE_VALUE), &guidAuthenticode, &wtd);
995 if (er)
996 {
997 // Verify the payload assuming offline.
998 wtd.dwProvFlags |= WTD_CACHE_ONLY_URL_RETRIEVAL;
999
1000 er = ::WinVerifyTrust(static_cast<HWND>(INVALID_HANDLE_VALUE), &guidAuthenticode, &wtd);
1001 ExitOnWin32Error(er, hr, "Failed authenticode verification of payload: %ls", wzUnverifiedPayloadPath);
1002 }
1003
1004 pProviderData = WTHelperProvDataFromStateData(wtd.hWVTStateData);
1005 ExitOnNullWithLastError(pProviderData, hr, "Failed to get provider state from authenticode certificate.");
1006
1007 pSigner = WTHelperGetProvSignerFromChain(pProviderData, 0, FALSE, 0);
1008 ExitOnNullWithLastError(pSigner, hr, "Failed to get signer chain from authenticode certificate.");
1009
1010 hr = VerifyPayloadAgainstChain(pPayload, pSigner->pChainContext);
1011 ExitOnFailure(hr, "Failed to verify expected payload against actual certificate chain.");
1012
1013LExit:
1014 return hr;
1015}
1016
1017extern "C" void CacheCleanup(
1018 __in BOOL fPerMachine,
1019 __in_z LPCWSTR wzBundleId
1020 )
1021{
1022 HRESULT hr = S_OK;
1023 LPWSTR sczFolder = NULL;
1024 LPWSTR sczFiles = NULL;
1025 LPWSTR sczDelete = NULL;
1026 HANDLE hFind = INVALID_HANDLE_VALUE;
1027 WIN32_FIND_DATAW wfd = { };
1028 DWORD cFileName = 0;
1029
1030 hr = CacheGetCompletedPath(fPerMachine, UNVERIFIED_CACHE_FOLDER_NAME, &sczFolder);
1031 if (SUCCEEDED(hr))
1032 {
1033 hr = DirEnsureDeleteEx(sczFolder, DIR_DELETE_FILES | DIR_DELETE_RECURSE | DIR_DELETE_SCHEDULE);
1034 }
1035
1036 if (!fPerMachine)
1037 {
1038 hr = CalculateWorkingFolder(wzBundleId, &sczFolder);
1039 if (SUCCEEDED(hr))
1040 {
1041 hr = PathConcat(sczFolder, L"*.*", &sczFiles);
1042 if (SUCCEEDED(hr))
1043 {
1044 hFind = ::FindFirstFileW(sczFiles, &wfd);
1045 if (INVALID_HANDLE_VALUE != hFind)
1046 {
1047 do
1048 {
1049 // Skip directories.
1050 if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1051 {
1052 continue;
1053 }
1054
1055 // For extra safety and to silence OACR.
1056 wfd.cFileName[MAX_PATH - 1] = L'\0';
1057
1058 // Skip resume files (they end with ".R").
1059 cFileName = lstrlenW(wfd.cFileName);
1060 if (2 < cFileName && L'.' == wfd.cFileName[cFileName - 2] && (L'R' == wfd.cFileName[cFileName - 1] || L'r' == wfd.cFileName[cFileName - 1]))
1061 {
1062 continue;
1063 }
1064
1065 hr = PathConcat(sczFolder, wfd.cFileName, &sczDelete);
1066 if (SUCCEEDED(hr))
1067 {
1068 hr = FileEnsureDelete(sczDelete);
1069 }
1070 } while (::FindNextFileW(hFind, &wfd));
1071 }
1072 }
1073 }
1074 }
1075
1076 if (INVALID_HANDLE_VALUE != hFind)
1077 {
1078 ::FindClose(hFind);
1079 }
1080
1081 ReleaseStr(sczDelete);
1082 ReleaseStr(sczFiles);
1083 ReleaseStr(sczFolder);
1084}
1085
1086extern "C" void CacheUninitialize()
1087{
1088 ReleaseNullStr(vsczCurrentMachinePackageCache);
1089 ReleaseNullStr(vsczDefaultMachinePackageCache);
1090 ReleaseNullStr(vsczDefaultUserPackageCache);
1091 ReleaseNullStr(vsczWorkingFolder);
1092 ReleaseNullStr(vsczSourceProcessPath);
1093
1094 vfRunningFromCache = FALSE;
1095 vfInitializedCache = FALSE;
1096}
1097
1098// Internal functions.
1099
1100static HRESULT CalculateWorkingFolder(
1101 __in_z LPCWSTR /*wzBundleId*/,
1102 __deref_out_z LPWSTR* psczWorkingFolder
1103 )
1104{
1105 HRESULT hr = S_OK;
1106 RPC_STATUS rs = RPC_S_OK;
1107 BOOL fElevated = FALSE;
1108 WCHAR wzTempPath[MAX_PATH] = { };
1109 UUID guid = {};
1110 WCHAR wzGuid[39];
1111
1112 if (!vsczWorkingFolder)
1113 {
1114 ProcElevated(::GetCurrentProcess(), &fElevated);
1115
1116 if (fElevated)
1117 {
1118 if (!::GetWindowsDirectoryW(wzTempPath, countof(wzTempPath)))
1119 {
1120 ExitWithLastError(hr, "Failed to get windows path for working folder.");
1121 }
1122
1123 hr = PathFixedBackslashTerminate(wzTempPath, countof(wzTempPath));
1124 ExitOnFailure(hr, "Failed to ensure windows path for working folder ended in backslash.");
1125
1126 hr = ::StringCchCatW(wzTempPath, countof(wzTempPath), L"Temp\\");
1127 ExitOnFailure(hr, "Failed to concat Temp directory on windows path for working folder.");
1128 }
1129 else if (0 == ::GetTempPathW(countof(wzTempPath), wzTempPath))
1130 {
1131 ExitWithLastError(hr, "Failed to get temp path for working folder.");
1132 }
1133
1134 rs = ::UuidCreate(&guid);
1135 hr = HRESULT_FROM_RPC(rs);
1136 ExitOnFailure(hr, "Failed to create working folder guid.");
1137
1138 if (!::StringFromGUID2(guid, wzGuid, countof(wzGuid)))
1139 {
1140 hr = E_OUTOFMEMORY;
1141 ExitOnRootFailure(hr, "Failed to convert working folder guid into string.");
1142 }
1143
1144 hr = StrAllocFormatted(&vsczWorkingFolder, L"%ls%ls\\", wzTempPath, wzGuid);
1145 ExitOnFailure(hr, "Failed to append bundle id on to temp path for working folder.");
1146 }
1147
1148 hr = StrAllocString(psczWorkingFolder, vsczWorkingFolder, 0);
1149 ExitOnFailure(hr, "Failed to copy working folder path.");
1150
1151LExit:
1152 return hr;
1153}
1154
1155static HRESULT GetRootPath(
1156 __in BOOL fPerMachine,
1157 __in BOOL fAllowRedirect,
1158 __deref_out_z LPWSTR* psczRootPath
1159 )
1160{
1161 HRESULT hr = S_OK;
1162 LPWSTR sczAppData = NULL;
1163 int nCompare = 0;
1164
1165 // Cache paths are initialized once so they cannot be changed while the engine is caching payloads.
1166 if (fPerMachine)
1167 {
1168 // Always construct the default machine package cache path so we can determine if we're redirected.
1169 if (!vsczDefaultMachinePackageCache)
1170 {
1171 hr = PathGetKnownFolder(CSIDL_COMMON_APPDATA, &sczAppData);
1172 ExitOnFailure(hr, "Failed to find local %hs appdata directory.", "per-machine");
1173
1174 hr = PathConcat(sczAppData, PACKAGE_CACHE_FOLDER_NAME, &vsczDefaultMachinePackageCache);
1175 ExitOnFailure(hr, "Failed to construct %hs package cache directory name.", "per-machine");
1176
1177 hr = PathBackslashTerminate(&vsczDefaultMachinePackageCache);
1178 ExitOnFailure(hr, "Failed to backslash terminate default %hs package cache directory name.", "per-machine");
1179 }
1180
1181 if (!vsczCurrentMachinePackageCache)
1182 {
1183 hr = PolcReadString(POLICY_BURN_REGISTRY_PATH, L"PackageCache", NULL, &vsczCurrentMachinePackageCache);
1184 ExitOnFailure(hr, "Failed to read PackageCache policy directory.");
1185
1186 if (vsczCurrentMachinePackageCache)
1187 {
1188 hr = PathBackslashTerminate(&vsczCurrentMachinePackageCache);
1189 ExitOnFailure(hr, "Failed to backslash terminate redirected per-machine package cache directory name.");
1190 }
1191 else
1192 {
1193 hr = StrAllocString(&vsczCurrentMachinePackageCache, vsczDefaultMachinePackageCache, 0);
1194 ExitOnFailure(hr, "Failed to copy default package cache directory to current package cache directory.");
1195 }
1196 }
1197
1198 hr = StrAllocString(psczRootPath, fAllowRedirect ? vsczCurrentMachinePackageCache : vsczDefaultMachinePackageCache, 0);
1199 ExitOnFailure(hr, "Failed to copy %hs package cache root directory.", "per-machine");
1200
1201 hr = PathCompare(vsczDefaultMachinePackageCache, *psczRootPath, &nCompare);
1202 ExitOnFailure(hr, "Failed to compare default and current package cache directories.");
1203
1204 // Return S_FALSE if the current location is not the default location (redirected).
1205 hr = CSTR_EQUAL == nCompare ? S_OK : S_FALSE;
1206 }
1207 else
1208 {
1209 if (!vsczDefaultUserPackageCache)
1210 {
1211 hr = PathGetKnownFolder(CSIDL_LOCAL_APPDATA, &sczAppData);
1212 ExitOnFailure(hr, "Failed to find local %hs appdata directory.", "per-user");
1213
1214 hr = PathConcat(sczAppData, PACKAGE_CACHE_FOLDER_NAME, &vsczDefaultUserPackageCache);
1215 ExitOnFailure(hr, "Failed to construct %hs package cache directory name.", "per-user");
1216
1217 hr = PathBackslashTerminate(&vsczDefaultUserPackageCache);
1218 ExitOnFailure(hr, "Failed to backslash terminate default %hs package cache directory name.", "per-user");
1219 }
1220
1221 hr = StrAllocString(psczRootPath, vsczDefaultUserPackageCache, 0);
1222 ExitOnFailure(hr, "Failed to copy %hs package cache root directory.", "per-user");
1223 }
1224
1225LExit:
1226 ReleaseStr(sczAppData);
1227
1228 return hr;
1229}
1230
1231static HRESULT GetLastUsedSourceFolder(
1232 __in BURN_VARIABLES* pVariables,
1233 __out_z LPWSTR* psczLastSource
1234 )
1235{
1236 HRESULT hr = S_OK;
1237 LPWSTR sczOriginalSource = NULL;
1238
1239 hr = VariableGetString(pVariables, BURN_BUNDLE_LAST_USED_SOURCE, psczLastSource);
1240 if (E_NOTFOUND == hr)
1241 {
1242 // Try the original source folder.
1243 hr = VariableGetString(pVariables, BURN_BUNDLE_ORIGINAL_SOURCE, &sczOriginalSource);
1244 if (SUCCEEDED(hr))
1245 {
1246 hr = PathGetDirectory(sczOriginalSource, psczLastSource);
1247 }
1248 }
1249
1250 return hr;
1251}
1252
1253static HRESULT CreateCompletedPath(
1254 __in BOOL fPerMachine,
1255 __in LPCWSTR wzId,
1256 __out LPWSTR* psczCacheDirectory
1257 )
1258{
1259 static BOOL fPerMachineCacheRootVerified = FALSE;
1260
1261 HRESULT hr = S_OK;
1262 LPWSTR sczCacheDirectory = NULL;
1263
1264 // If we are doing a permachine install but have not yet verified that the root cache folder
1265 // was created with the correct ACLs yet, do that now.
1266 if (fPerMachine && !fPerMachineCacheRootVerified)
1267 {
1268 hr = GetRootPath(fPerMachine, TRUE, &sczCacheDirectory);
1269 ExitOnFailure(hr, "Failed to get cache directory.");
1270
1271 hr = DirEnsureExists(sczCacheDirectory, NULL);
1272 ExitOnFailure(hr, "Failed to create cache directory: %ls", sczCacheDirectory);
1273
1274 hr = SecurePath(sczCacheDirectory);
1275 ExitOnFailure(hr, "Failed to secure cache directory: %ls", sczCacheDirectory);
1276
1277 fPerMachineCacheRootVerified = TRUE;
1278 }
1279
1280 // Get the cache completed path, ensure it exists, and reset any permissions people
1281 // might have tried to set on the directory so we inherit the (correct!) security
1282 // permissions from the parent directory.
1283 hr = CacheGetCompletedPath(fPerMachine, wzId, &sczCacheDirectory);
1284 ExitOnFailure(hr, "Failed to get cache directory.");
1285
1286 hr = DirEnsureExists(sczCacheDirectory, NULL);
1287 ExitOnFailure(hr, "Failed to create cache directory: %ls", sczCacheDirectory);
1288
1289 ResetPathPermissions(fPerMachine, sczCacheDirectory);
1290
1291 *psczCacheDirectory = sczCacheDirectory;
1292 sczCacheDirectory = NULL;
1293
1294LExit:
1295 ReleaseStr(sczCacheDirectory);
1296 return hr;
1297}
1298
1299static HRESULT CreateUnverifiedPath(
1300 __in BOOL fPerMachine,
1301 __in_z LPCWSTR wzPayloadId,
1302 __out_z LPWSTR* psczUnverifiedPayloadPath
1303 )
1304{
1305 static BOOL fUnverifiedCacheFolderCreated = FALSE;
1306
1307 HRESULT hr = S_OK;
1308 LPWSTR sczUnverifiedCacheFolder = NULL;
1309
1310 hr = CacheGetCompletedPath(fPerMachine, UNVERIFIED_CACHE_FOLDER_NAME, &sczUnverifiedCacheFolder);
1311 ExitOnFailure(hr, "Failed to get cache directory.");
1312
1313 if (!fUnverifiedCacheFolderCreated)
1314 {
1315 hr = DirEnsureExists(sczUnverifiedCacheFolder, NULL);
1316 ExitOnFailure(hr, "Failed to create unverified cache directory: %ls", sczUnverifiedCacheFolder);
1317
1318 ResetPathPermissions(fPerMachine, sczUnverifiedCacheFolder);
1319 }
1320
1321 hr = PathConcat(sczUnverifiedCacheFolder, wzPayloadId, psczUnverifiedPayloadPath);
1322 ExitOnFailure(hr, "Failed to concat payload id to unverified folder path.");
1323
1324LExit:
1325 ReleaseStr(sczUnverifiedCacheFolder);
1326
1327 return hr;
1328}
1329
1330static HRESULT VerifyThenTransferContainer(
1331 __in BURN_CONTAINER* pContainer,
1332 __in_z LPCWSTR wzCachedPath,
1333 __in_z LPCWSTR wzUnverifiedContainerPath,
1334 __in BOOL fMove
1335 )
1336{
1337 HRESULT hr = S_OK;
1338 HANDLE hFile = INVALID_HANDLE_VALUE;
1339
1340 // Get the container on disk actual hash.
1341 hFile = ::CreateFileW(wzUnverifiedContainerPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
1342 if (INVALID_HANDLE_VALUE == hFile)
1343 {
1344 ExitWithLastError(hr, "Failed to open container in working path: %ls", wzUnverifiedContainerPath);
1345 }
1346
1347 // Container should have a hash we can use to verify with.
1348 if (pContainer->pbHash)
1349 {
1350 hr = VerifyHash(pContainer->pbHash, pContainer->cbHash, wzUnverifiedContainerPath, hFile);
1351 ExitOnFailure(hr, "Failed to verify container hash: %ls", wzCachedPath);
1352 }
1353
1354 LogStringLine(REPORT_STANDARD, "%ls container from working path '%ls' to path '%ls'", fMove ? L"Moving" : L"Copying", wzUnverifiedContainerPath, wzCachedPath);
1355
1356 if (fMove)
1357 {
1358 hr = FileEnsureMoveWithRetry(wzUnverifiedContainerPath, wzCachedPath, TRUE, TRUE, FILE_OPERATION_RETRY_COUNT, FILE_OPERATION_RETRY_WAIT);
1359 ExitOnFailure(hr, "Failed to move %ls to %ls", wzUnverifiedContainerPath, wzCachedPath);
1360 }
1361 else
1362 {
1363 hr = FileEnsureCopyWithRetry(wzUnverifiedContainerPath, wzCachedPath, TRUE, FILE_OPERATION_RETRY_COUNT, FILE_OPERATION_RETRY_WAIT);
1364 ExitOnFailure(hr, "Failed to copy %ls to %ls", wzUnverifiedContainerPath, wzCachedPath);
1365 }
1366
1367LExit:
1368 ReleaseFileHandle(hFile);
1369
1370 return hr;
1371}
1372
1373static HRESULT VerifyThenTransferPayload(
1374 __in BURN_PAYLOAD* pPayload,
1375 __in_z LPCWSTR wzCachedPath,
1376 __in_z LPCWSTR wzUnverifiedPayloadPath,
1377 __in BOOL fMove
1378 )
1379{
1380 HRESULT hr = S_OK;
1381 HANDLE hFile = INVALID_HANDLE_VALUE;
1382
1383 // Get the payload on disk actual hash.
1384 hFile = ::CreateFileW(wzUnverifiedPayloadPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
1385 if (INVALID_HANDLE_VALUE == hFile)
1386 {
1387 ExitWithLastError(hr, "Failed to open payload in working path: %ls", wzUnverifiedPayloadPath);
1388 }
1389
1390 // If the payload has a certificate root public key identifier provided, verify the certificate.
1391 if (pPayload->pbCertificateRootPublicKeyIdentifier)
1392 {
1393 hr = CacheVerifyPayloadSignature(pPayload, wzUnverifiedPayloadPath, hFile);
1394 ExitOnFailure(hr, "Failed to verify payload signature: %ls", wzCachedPath);
1395 }
1396 else if (pPayload->pCatalog) // If catalog files are specified, attempt to verify the file with a catalog file
1397 {
1398 hr = VerifyPayloadWithCatalog(pPayload, wzUnverifiedPayloadPath, hFile);
1399 ExitOnFailure(hr, "Failed to verify payload signature: %ls", wzCachedPath);
1400 }
1401 else if (pPayload->pbHash) // the payload should have a hash we can use to verify it.
1402 {
1403 hr = VerifyHash(pPayload->pbHash, pPayload->cbHash, wzUnverifiedPayloadPath, hFile);
1404 ExitOnFailure(hr, "Failed to verify payload hash: %ls", wzCachedPath);
1405 }
1406
1407 LogStringLine(REPORT_STANDARD, "%ls payload from working path '%ls' to path '%ls'", fMove ? L"Moving" : L"Copying", wzUnverifiedPayloadPath, wzCachedPath);
1408
1409 if (fMove)
1410 {
1411 hr = FileEnsureMoveWithRetry(wzUnverifiedPayloadPath, wzCachedPath, TRUE, TRUE, FILE_OPERATION_RETRY_COUNT, FILE_OPERATION_RETRY_WAIT);
1412 ExitOnFailure(hr, "Failed to move %ls to %ls", wzUnverifiedPayloadPath, wzCachedPath);
1413 }
1414 else
1415 {
1416 hr = FileEnsureCopyWithRetry(wzUnverifiedPayloadPath, wzCachedPath, TRUE, FILE_OPERATION_RETRY_COUNT, FILE_OPERATION_RETRY_WAIT);
1417 ExitOnFailure(hr, "Failed to copy %ls to %ls", wzUnverifiedPayloadPath, wzCachedPath);
1418 }
1419
1420LExit:
1421 ReleaseFileHandle(hFile);
1422
1423 return hr;
1424}
1425
1426static HRESULT TransferWorkingPathToUnverifiedPath(
1427 __in_z LPCWSTR wzWorkingPath,
1428 __in_z LPCWSTR wzUnverifiedPayloadPath,
1429 __in BOOL fMove
1430 )
1431{
1432 HRESULT hr = S_OK;
1433
1434 if (fMove)
1435 {
1436 hr = FileEnsureMoveWithRetry(wzWorkingPath, wzUnverifiedPayloadPath, TRUE, TRUE, FILE_OPERATION_RETRY_COUNT, FILE_OPERATION_RETRY_WAIT);
1437 ExitOnFailure(hr, "Failed to move %ls to %ls", wzWorkingPath, wzUnverifiedPayloadPath);
1438 }
1439 else
1440 {
1441 hr = FileEnsureCopyWithRetry(wzWorkingPath, wzUnverifiedPayloadPath, TRUE, FILE_OPERATION_RETRY_COUNT, FILE_OPERATION_RETRY_WAIT);
1442 ExitOnFailure(hr, "Failed to copy %ls to %ls", wzWorkingPath, wzUnverifiedPayloadPath);
1443 }
1444
1445LExit:
1446 return hr;
1447}
1448
1449static HRESULT VerifyFileAgainstPayload(
1450 __in BURN_PAYLOAD* pPayload,
1451 __in_z LPCWSTR wzVerifyPath
1452 )
1453{
1454 HRESULT hr = S_OK;
1455 HANDLE hFile = INVALID_HANDLE_VALUE;
1456
1457 // Get the payload on disk actual hash.
1458 hFile = ::CreateFileW(wzVerifyPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
1459 if (INVALID_HANDLE_VALUE == hFile)
1460 {
1461 hr = HRESULT_FROM_WIN32(::GetLastError());
1462 if (E_PATHNOTFOUND == hr || E_FILENOTFOUND == hr)
1463 {
1464 ExitFunction(); // do not log error when the file was not found.
1465 }
1466 ExitOnRootFailure(hr, "Failed to open payload at path: %ls", wzVerifyPath);
1467 }
1468
1469 // If the payload has a certificate root public key identifier provided, verify the certificate.
1470 if (pPayload->pbCertificateRootPublicKeyIdentifier)
1471 {
1472 hr = CacheVerifyPayloadSignature(pPayload, wzVerifyPath, hFile);
1473 ExitOnFailure(hr, "Failed to verify signature of payload: %ls", pPayload->sczKey);
1474 }
1475 else if (pPayload->pCatalog) // If catalog files are specified, attempt to verify the file with a catalog file
1476 {
1477 hr = VerifyPayloadWithCatalog(pPayload, wzVerifyPath, hFile);
1478 ExitOnFailure(hr, "Failed to verify catalog signature of payload: %ls", pPayload->sczKey);
1479 }
1480 else if (pPayload->pbHash) // the payload should have a hash we can use to verify it.
1481 {
1482 hr = VerifyHash(pPayload->pbHash, pPayload->cbHash, wzVerifyPath, hFile);
1483 ExitOnFailure(hr, "Failed to verify hash of payload: %ls", pPayload->sczKey);
1484 }
1485
1486LExit:
1487 ReleaseFileHandle(hFile);
1488
1489 return hr;
1490}
1491
1492static HRESULT AllocateSid(
1493 __in WELL_KNOWN_SID_TYPE type,
1494 __out PSID* ppSid
1495 )
1496{
1497 HRESULT hr = S_OK;
1498 PSID pAllocSid = NULL;
1499 DWORD cbSid = SECURITY_MAX_SID_SIZE;
1500
1501 pAllocSid = static_cast<PSID>(MemAlloc(cbSid, TRUE));
1502 ExitOnNull(pAllocSid, hr, E_OUTOFMEMORY, "Failed to allocate memory for well known SID.");
1503
1504 if (!::CreateWellKnownSid(type, NULL, pAllocSid, &cbSid))
1505 {
1506 ExitWithLastError(hr, "Failed to create well known SID.");
1507 }
1508
1509 *ppSid = pAllocSid;
1510 pAllocSid = NULL;
1511
1512LExit:
1513 ReleaseMem(pAllocSid);
1514 return hr;
1515}
1516
1517
1518static HRESULT ResetPathPermissions(
1519 __in BOOL fPerMachine,
1520 __in LPCWSTR wzPath
1521 )
1522{
1523 HRESULT hr = S_OK;
1524 DWORD er = ERROR_SUCCESS;
1525 DWORD dwSetSecurity = DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION;
1526 ACL acl = { };
1527 PSID pSid = NULL;
1528
1529 if (fPerMachine)
1530 {
1531 hr = AllocateSid(WinBuiltinAdministratorsSid, &pSid);
1532 ExitOnFailure(hr, "Failed to allocate administrator SID.");
1533
1534 // Create an empty (not NULL!) ACL to reset the permissions on the file to purely inherit from parent.
1535 if (!::InitializeAcl(&acl, sizeof(acl), ACL_REVISION))
1536 {
1537 ExitWithLastError(hr, "Failed to initialize ACL.");
1538 }
1539
1540 dwSetSecurity |= OWNER_SECURITY_INFORMATION;
1541 }
1542
1543 hr = AclSetSecurityWithRetry(wzPath, SE_FILE_OBJECT, dwSetSecurity, pSid, NULL, &acl, NULL, FILE_OPERATION_RETRY_COUNT, FILE_OPERATION_RETRY_WAIT);
1544 ExitOnWin32Error(er, hr, "Failed to reset the ACL on cached file: %ls", wzPath);
1545
1546 ::SetFileAttributesW(wzPath, FILE_ATTRIBUTE_NORMAL); // Let's try to reset any possible read-only/system bits.
1547
1548LExit:
1549 ReleaseMem(pSid);
1550 return hr;
1551}
1552
1553
1554static HRESULT GrantAccessAndAllocateSid(
1555 __in WELL_KNOWN_SID_TYPE type,
1556 __in DWORD dwGrantAccess,
1557 __in EXPLICIT_ACCESS* pAccess
1558 )
1559{
1560 HRESULT hr = S_OK;
1561
1562 hr = AllocateSid(type, reinterpret_cast<PSID*>(&pAccess->Trustee.ptstrName));
1563 ExitOnFailure(hr, "Failed to allocate SID to grate access.");
1564
1565 pAccess->grfAccessMode = GRANT_ACCESS;
1566 pAccess->grfAccessPermissions = dwGrantAccess;
1567 pAccess->grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
1568 pAccess->Trustee.TrusteeForm = TRUSTEE_IS_SID;
1569 pAccess->Trustee.TrusteeType = TRUSTEE_IS_GROUP;
1570
1571LExit:
1572 return hr;
1573}
1574
1575
1576static HRESULT SecurePath(
1577 __in LPCWSTR wzPath
1578 )
1579{
1580 HRESULT hr = S_OK;
1581 DWORD er = ERROR_SUCCESS;
1582 EXPLICIT_ACCESSW access[4] = { };
1583 PACL pAcl = NULL;
1584
1585 // Administrators must be the first one in the array so we can reuse the allocated SID below.
1586 hr = GrantAccessAndAllocateSid(WinBuiltinAdministratorsSid, FILE_ALL_ACCESS, &access[0]);
1587 ExitOnFailure(hr, "Failed to allocate access for Administrators group to path: %ls", wzPath);
1588
1589 hr = GrantAccessAndAllocateSid(WinLocalSystemSid, FILE_ALL_ACCESS, &access[1]);
1590 ExitOnFailure(hr, "Failed to allocate access for SYSTEM group to path: %ls", wzPath);
1591
1592 hr = GrantAccessAndAllocateSid(WinWorldSid, GENERIC_READ | GENERIC_EXECUTE, &access[2]);
1593 ExitOnFailure(hr, "Failed to allocate access for Everyone group to path: %ls", wzPath);
1594
1595 hr = GrantAccessAndAllocateSid(WinBuiltinUsersSid, GENERIC_READ | GENERIC_EXECUTE, &access[3]);
1596 ExitOnFailure(hr, "Failed to allocate access for Users group to path: %ls", wzPath);
1597
1598 er = ::SetEntriesInAclW(countof(access), access, NULL, &pAcl);
1599 ExitOnWin32Error(er, hr, "Failed to create ACL to secure cache path: %ls", wzPath);
1600
1601 // Set the ACL and ensure the Administrators group ends up the owner
1602 hr = AclSetSecurityWithRetry(wzPath, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
1603 reinterpret_cast<PSID>(access[0].Trustee.ptstrName), NULL, pAcl, NULL, FILE_OPERATION_RETRY_COUNT, FILE_OPERATION_RETRY_WAIT);
1604 ExitOnFailure(hr, "Failed to secure cache path: %ls", wzPath);
1605
1606LExit:
1607 if (pAcl)
1608 {
1609 ::LocalFree(pAcl);
1610 }
1611
1612 for (DWORD i = 0; i < countof(access); ++i)
1613 {
1614 ReleaseMem(access[i].Trustee.ptstrName);
1615 }
1616
1617 return hr;
1618}
1619
1620
1621static HRESULT CopyEngineToWorkingFolder(
1622 __in_z LPCWSTR wzSourcePath,
1623 __in_z LPCWSTR wzWorkingFolderName,
1624 __in_z LPCWSTR wzExecutableName,
1625 __in BURN_PAYLOADS* pUxPayloads,
1626 __in BURN_SECTION* pSection,
1627 __deref_out_z_opt LPWSTR* psczEngineWorkingPath
1628 )
1629{
1630 HRESULT hr = S_OK;
1631 LPWSTR sczWorkingFolder = NULL;
1632 LPWSTR sczTargetDirectory = NULL;
1633 LPWSTR sczTargetPath = NULL;
1634 LPWSTR sczSourceDirectory = NULL;
1635 LPWSTR sczPayloadSourcePath = NULL;
1636 LPWSTR sczPayloadTargetPath = NULL;
1637
1638 hr = CacheEnsureWorkingFolder(NULL, &sczWorkingFolder);
1639 ExitOnFailure(hr, "Failed to create working path to copy engine.");
1640
1641 hr = PathConcat(sczWorkingFolder, wzWorkingFolderName, &sczTargetDirectory);
1642 ExitOnFailure(hr, "Failed to calculate the bundle working folder target name.");
1643
1644 hr = DirEnsureExists(sczTargetDirectory, NULL);
1645 ExitOnFailure(hr, "Failed create bundle working folder.");
1646
1647 hr = PathConcat(sczTargetDirectory, wzExecutableName, &sczTargetPath);
1648 ExitOnFailure(hr, "Failed to combine working path with engine file name.");
1649
1650 // Copy the engine without any attached containers to the working path.
1651 hr = CopyEngineWithSignatureFixup(pSection->hEngineFile, wzSourcePath, sczTargetPath, pSection);
1652 ExitOnFailure(hr, "Failed to copy engine: '%ls' to working path: %ls", wzSourcePath, sczTargetPath);
1653
1654 // Copy external UX payloads to working path.
1655 for (DWORD i = 0; i < pUxPayloads->cPayloads; ++i)
1656 {
1657 BURN_PAYLOAD* pPayload = &pUxPayloads->rgPayloads[i];
1658
1659 if (BURN_PAYLOAD_PACKAGING_EXTERNAL == pPayload->packaging)
1660 {
1661 if (!sczSourceDirectory)
1662 {
1663 hr = PathGetDirectory(wzSourcePath, &sczSourceDirectory);
1664 ExitOnFailure(hr, "Failed to get directory from engine path: %ls", wzSourcePath);
1665 }
1666
1667 hr = PathConcat(sczSourceDirectory, pPayload->sczSourcePath, &sczPayloadSourcePath);
1668 ExitOnFailure(hr, "Failed to build payload source path for working copy.");
1669
1670 hr = PathConcat(sczTargetDirectory, pPayload->sczFilePath, &sczPayloadTargetPath);
1671 ExitOnFailure(hr, "Failed to build payload target path for working copy.");
1672
1673 hr = FileEnsureCopyWithRetry(sczPayloadSourcePath, sczPayloadTargetPath, TRUE, FILE_OPERATION_RETRY_COUNT, FILE_OPERATION_RETRY_WAIT);
1674 ExitOnFailure(hr, "Failed to copy UX payload from: '%ls' to: '%ls'", sczPayloadSourcePath, sczPayloadTargetPath);
1675 }
1676 }
1677
1678 if (psczEngineWorkingPath)
1679 {
1680 hr = StrAllocString(psczEngineWorkingPath, sczTargetPath, 0);
1681 ExitOnFailure(hr, "Failed to copy target path for engine working path.");
1682 }
1683
1684LExit:
1685 ReleaseStr(sczPayloadTargetPath);
1686 ReleaseStr(sczPayloadSourcePath);
1687 ReleaseStr(sczSourceDirectory);
1688 ReleaseStr(sczTargetPath);
1689 ReleaseStr(sczTargetDirectory);
1690 ReleaseStr(sczWorkingFolder);
1691
1692 return hr;
1693}
1694
1695
1696static HRESULT CopyEngineWithSignatureFixup(
1697 __in HANDLE hEngineFile,
1698 __in_z LPCWSTR wzEnginePath,
1699 __in_z LPCWSTR wzTargetPath,
1700 __in BURN_SECTION* pSection
1701 )
1702{
1703 HRESULT hr = S_OK;
1704 HANDLE hTarget = INVALID_HANDLE_VALUE;
1705 LARGE_INTEGER li = { };
1706 DWORD dwZeroOriginals[3] = { };
1707
1708 hTarget = ::CreateFileW(wzTargetPath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
1709 if (INVALID_HANDLE_VALUE == hTarget)
1710 {
1711 ExitWithLastError(hr, "Failed to create engine file at path: %ls", wzTargetPath);
1712 }
1713
1714 hr = FileSetPointer(hEngineFile, 0, NULL, FILE_BEGIN);
1715 ExitOnFailure(hr, "Failed to seek to beginning of engine file: %ls", wzEnginePath);
1716
1717 hr = FileCopyUsingHandles(hEngineFile, hTarget, pSection->cbEngineSize, NULL);
1718 ExitOnFailure(hr, "Failed to copy engine from: %ls to: %ls", wzEnginePath, wzTargetPath);
1719
1720 // If the original executable was signed, let's put back the checksum and signature.
1721 if (pSection->dwOriginalSignatureOffset)
1722 {
1723 // Fix up the checksum.
1724 li.QuadPart = pSection->dwChecksumOffset;
1725 if (!::SetFilePointerEx(hTarget, li, NULL, FILE_BEGIN))
1726 {
1727 ExitWithLastError(hr, "Failed to seek to checksum in exe header.");
1728 }
1729
1730 hr = FileWriteHandle(hTarget, reinterpret_cast<LPBYTE>(&pSection->dwOriginalChecksum), sizeof(pSection->dwOriginalChecksum));
1731 ExitOnFailure(hr, "Failed to update signature offset.");
1732
1733 // Fix up the signature information.
1734 li.QuadPart = pSection->dwCertificateTableOffset;
1735 if (!::SetFilePointerEx(hTarget, li, NULL, FILE_BEGIN))
1736 {
1737 ExitWithLastError(hr, "Failed to seek to signature table in exe header.");
1738 }
1739
1740 hr = FileWriteHandle(hTarget, reinterpret_cast<LPBYTE>(&pSection->dwOriginalSignatureOffset), sizeof(pSection->dwOriginalSignatureOffset));
1741 ExitOnFailure(hr, "Failed to update signature offset.");
1742
1743 hr = FileWriteHandle(hTarget, reinterpret_cast<LPBYTE>(&pSection->dwOriginalSignatureSize), sizeof(pSection->dwOriginalSignatureSize));
1744 ExitOnFailure(hr, "Failed to update signature offset.");
1745
1746 // Zero out the original information since that is how it was when the file was originally signed.
1747 li.QuadPart = pSection->dwOriginalChecksumAndSignatureOffset;
1748 if (!::SetFilePointerEx(hTarget, li, NULL, FILE_BEGIN))
1749 {
1750 ExitWithLastError(hr, "Failed to seek to original data in exe burn section header.");
1751 }
1752
1753 hr = FileWriteHandle(hTarget, reinterpret_cast<LPBYTE>(&dwZeroOriginals), sizeof(dwZeroOriginals));
1754 ExitOnFailure(hr, "Failed to zero out original data offset.");
1755 }
1756
1757LExit:
1758 ReleaseFileHandle(hTarget);
1759
1760 return hr;
1761}
1762
1763
1764static HRESULT RemoveBundleOrPackage(
1765 __in BOOL fBundle,
1766 __in BOOL fPerMachine,
1767 __in_z LPCWSTR wzBundleOrPackageId,
1768 __in_z LPCWSTR wzCacheId
1769 )
1770{
1771 HRESULT hr = S_OK;
1772 LPWSTR sczRootCacheDirectory = NULL;
1773 LPWSTR sczDirectory = NULL;
1774
1775 hr = CacheGetCompletedPath(fPerMachine, wzCacheId, &sczDirectory);
1776 ExitOnFailure(hr, "Failed to calculate cache path.");
1777
1778 LogId(REPORT_STANDARD, fBundle ? MSG_UNCACHE_BUNDLE : MSG_UNCACHE_PACKAGE, wzBundleOrPackageId, sczDirectory);
1779
1780 // Try really hard to remove the cache directory.
1781 hr = E_FAIL;
1782 for (DWORD iRetry = 0; FAILED(hr) && iRetry < FILE_OPERATION_RETRY_COUNT; ++iRetry)
1783 {
1784 if (0 < iRetry)
1785 {
1786 ::Sleep(FILE_OPERATION_RETRY_WAIT);
1787 }
1788
1789 hr = DirEnsureDeleteEx(sczDirectory, DIR_DELETE_FILES | DIR_DELETE_RECURSE | DIR_DELETE_SCHEDULE);
1790 if (E_PATHNOTFOUND == hr)
1791 {
1792 break;
1793 }
1794 }
1795
1796 if (FAILED(hr))
1797 {
1798 LogId(REPORT_STANDARD, fBundle ? MSG_UNABLE_UNCACHE_BUNDLE : MSG_UNABLE_UNCACHE_PACKAGE, wzBundleOrPackageId, sczDirectory, hr);
1799 hr = S_OK;
1800 }
1801 else
1802 {
1803 // Try to remove root package cache in the off chance it is now empty.
1804 hr = GetRootPath(fPerMachine, TRUE, &sczRootCacheDirectory);
1805 ExitOnFailure(hr, "Failed to get %hs package cache root directory.", fPerMachine ? "per-machine" : "per-user");
1806 DirEnsureDeleteEx(sczRootCacheDirectory, DIR_DELETE_SCHEDULE);
1807
1808 // GetRootPath returns S_FALSE if the package cache is redirected elsewhere.
1809 if (S_FALSE == hr)
1810 {
1811 hr = GetRootPath(fPerMachine, FALSE, &sczRootCacheDirectory);
1812 ExitOnFailure(hr, "Failed to get old %hs package cache root directory.", fPerMachine ? "per-machine" : "per-user");
1813 DirEnsureDeleteEx(sczRootCacheDirectory, DIR_DELETE_SCHEDULE);
1814 }
1815 }
1816
1817LExit:
1818 ReleaseStr(sczDirectory);
1819 ReleaseStr(sczRootCacheDirectory);
1820
1821 return hr;
1822}
1823
1824static HRESULT VerifyHash(
1825 __in BYTE* pbHash,
1826 __in DWORD cbHash,
1827 __in_z LPCWSTR wzUnverifiedPayloadPath,
1828 __in HANDLE hFile
1829 )
1830{
1831 UNREFERENCED_PARAMETER(wzUnverifiedPayloadPath);
1832
1833 HRESULT hr = S_OK;
1834 BYTE rgbActualHash[SHA1_HASH_LEN] = { };
1835 DWORD64 qwHashedBytes;
1836 LPWSTR pszExpected = NULL;
1837 LPWSTR pszActual = NULL;
1838
1839 // TODO: create a cryp hash file that sends progress.
1840 hr = CrypHashFileHandle(hFile, PROV_RSA_FULL, CALG_SHA1, rgbActualHash, sizeof(rgbActualHash), &qwHashedBytes);
1841 ExitOnFailure(hr, "Failed to calculate hash for path: %ls", wzUnverifiedPayloadPath);
1842
1843 // Compare hashes.
1844 if (cbHash != sizeof(rgbActualHash) || 0 != memcmp(pbHash, rgbActualHash, SHA1_HASH_LEN))
1845 {
1846 hr = CRYPT_E_HASH_VALUE;
1847
1848 // Best effort to log the expected and actual hash value strings.
1849 if (SUCCEEDED(StrAllocHexEncode(pbHash, cbHash, &pszExpected)) &&
1850 SUCCEEDED(StrAllocHexEncode(rgbActualHash, SHA1_HASH_LEN, &pszActual)))
1851 {
1852 ExitOnFailure(hr, "Hash mismatch for path: %ls, expected: %ls, actual: %ls", wzUnverifiedPayloadPath, pszExpected, pszActual);
1853 }
1854 else
1855 {
1856 ExitOnFailure(hr, "Hash mismatch for path: %ls", wzUnverifiedPayloadPath);
1857 }
1858 }
1859
1860LExit:
1861 ReleaseStr(pszActual);
1862 ReleaseStr(pszExpected);
1863
1864 return hr;
1865}
1866
1867static HRESULT VerifyPayloadWithCatalog(
1868 __in BURN_PAYLOAD* pPayload,
1869 __in_z LPCWSTR wzUnverifiedPayloadPath,
1870 __in HANDLE hFile
1871 )
1872{
1873 HRESULT hr = S_FALSE;
1874 DWORD er = ERROR_SUCCESS;
1875 WINTRUST_DATA WinTrustData = { };
1876 WINTRUST_CATALOG_INFO WinTrustCatalogInfo = { };
1877 GUID gSubSystemDriver = WINTRUST_ACTION_GENERIC_VERIFY_V2;
1878 LPWSTR sczLowerCaseFile = NULL;
1879 LPWSTR pCurrent = NULL;
1880 LPWSTR sczName = NULL;
1881 DWORD dwHashSize = 0;
1882 DWORD dwTagSize;
1883 LPBYTE pbHash = NULL;
1884
1885 // Get lower case file name. Older operating systems need a lower case file
1886 // to match in the catalog
1887 hr = StrAllocString(&sczLowerCaseFile, wzUnverifiedPayloadPath, 0);
1888 ExitOnFailure(hr, "Failed to allocate memory");
1889
1890 // Go through each character doing the lower case of each letter
1891 pCurrent = sczLowerCaseFile;
1892 while ('\0' != *pCurrent)
1893 {
1894 *pCurrent = (WCHAR)_tolower(*pCurrent);
1895 pCurrent++;
1896 }
1897
1898 // Get file hash
1899 CryptCATAdminCalcHashFromFileHandle(hFile, &dwHashSize, pbHash, 0);
1900 er = ::GetLastError();
1901 if (ERROR_INSUFFICIENT_BUFFER == er)
1902 {
1903 pbHash = (LPBYTE)MemAlloc(dwHashSize, TRUE);
1904 if (!CryptCATAdminCalcHashFromFileHandle(hFile, &dwHashSize, pbHash, 0))
1905 {
1906 ExitWithLastError(hr, "Failed to get file hash.");
1907 }
1908 }
1909 else
1910 {
1911 ExitOnWin32Error(er, hr, "Failed to get file hash.");
1912 }
1913
1914 // Make the hash into a string. This is the member tag for the catalog
1915 dwTagSize = (dwHashSize * 2) + 1;
1916 hr = StrAlloc(&sczName, dwTagSize);
1917 ExitOnFailure(hr, "Failed to allocate string.");
1918 hr = StrHexEncode(pbHash, dwHashSize, sczName, dwTagSize);
1919 ExitOnFailure(hr, "Failed to encode file hash.");
1920
1921 // Set up the WinVerifyTrust structures assuming online.
1922 WinTrustData.cbStruct = sizeof(WINTRUST_DATA);
1923 WinTrustData.dwUIChoice = WTD_UI_NONE;
1924 WinTrustData.dwUnionChoice = WTD_CHOICE_CATALOG;
1925 WinTrustData.dwStateAction = WTD_STATEACTION_VERIFY;
1926 WinTrustData.dwProvFlags = WTD_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT;
1927 WinTrustData.pCatalog = &WinTrustCatalogInfo;
1928
1929 WinTrustCatalogInfo.cbStruct = sizeof(WINTRUST_CATALOG_INFO);
1930 WinTrustCatalogInfo.pbCalculatedFileHash = pbHash;
1931 WinTrustCatalogInfo.cbCalculatedFileHash = dwHashSize;
1932 WinTrustCatalogInfo.hMemberFile = hFile;
1933 WinTrustCatalogInfo.pcwszMemberTag = sczName;
1934 WinTrustCatalogInfo.pcwszMemberFilePath = sczLowerCaseFile;
1935 WinTrustCatalogInfo.pcwszCatalogFilePath = pPayload->pCatalog->sczLocalFilePath;
1936
1937 hr = ::WinVerifyTrust(static_cast<HWND>(INVALID_HANDLE_VALUE), &gSubSystemDriver, &WinTrustData);
1938 if (hr)
1939 {
1940 // Set up the WinVerifyTrust structures assuming online.
1941 WinTrustData.dwProvFlags |= WTD_CACHE_ONLY_URL_RETRIEVAL;
1942
1943 er = ::WinVerifyTrust(static_cast<HWND>(INVALID_HANDLE_VALUE), &gSubSystemDriver, &WinTrustData);
1944
1945 // WinVerifyTrust returns 0 for success, a few different Win32 error codes if it can't
1946 // find the provider, and any other error code is provider specific, so may not
1947 // be an actual Win32 error code
1948 ExitOnWin32Error(er, hr, "Could not verify file %ls.", wzUnverifiedPayloadPath);
1949 }
1950
1951 // Need to close the WinVerifyTrust action
1952 WinTrustData.dwStateAction = WTD_STATEACTION_CLOSE;
1953 er = ::WinVerifyTrust(static_cast<HWND>(INVALID_HANDLE_VALUE), &gSubSystemDriver, &WinTrustData);
1954 ExitOnWin32Error(er, hr, "Could not close verify handle.");
1955
1956LExit:
1957 ReleaseStr(sczLowerCaseFile);
1958 ReleaseStr(sczName);
1959 ReleaseMem(pbHash);
1960
1961 return hr;
1962}
1963
1964static HRESULT VerifyPayloadAgainstChain(
1965 __in BURN_PAYLOAD* pPayload,
1966 __in PCCERT_CHAIN_CONTEXT pChainContext
1967 )
1968{
1969 HRESULT hr = S_OK;
1970 PCCERT_CONTEXT pChainElementCertContext = NULL;
1971
1972 BYTE rgbPublicKeyIdentifier[SHA1_HASH_LEN] = { };
1973 DWORD cbPublicKeyIdentifier = sizeof(rgbPublicKeyIdentifier);
1974 BYTE* pbThumbprint = NULL;
1975 DWORD cbThumbprint = 0;
1976
1977 // Walk up the chain looking for a certificate in the chain that matches our expected public key identifier
1978 // and thumbprint (if a thumbprint was provided).
1979 HRESULT hrChainVerification = E_NOTFOUND; // assume we won't find a match.
1980 for (DWORD i = 0; i < pChainContext->rgpChain[0]->cElement; ++i)
1981 {
1982 pChainElementCertContext = pChainContext->rgpChain[0]->rgpElement[i]->pCertContext;
1983
1984 // Get the certificate's public key identifier.
1985 if (!::CryptHashPublicKeyInfo(NULL, CALG_SHA1, 0, X509_ASN_ENCODING, &pChainElementCertContext->pCertInfo->SubjectPublicKeyInfo, rgbPublicKeyIdentifier, &cbPublicKeyIdentifier))
1986 {
1987 ExitWithLastError(hr, "Failed to get certificate public key identifier.");
1988 }
1989
1990 // Compare the certificate's public key identifier with the payload's public key identifier. If they
1991 // match, we're one step closer to the a positive result.
1992 if (pPayload->cbCertificateRootPublicKeyIdentifier == cbPublicKeyIdentifier &&
1993 0 == memcmp(pPayload->pbCertificateRootPublicKeyIdentifier, rgbPublicKeyIdentifier, cbPublicKeyIdentifier))
1994 {
1995 // If the payload specified a thumbprint for the certificate, verify it.
1996 if (pPayload->pbCertificateRootThumbprint)
1997 {
1998 hr = CertReadProperty(pChainElementCertContext, CERT_SHA1_HASH_PROP_ID, &pbThumbprint, &cbThumbprint);
1999 ExitOnFailure(hr, "Failed to read certificate thumbprint.");
2000
2001 if (pPayload->cbCertificateRootThumbprint == cbThumbprint &&
2002 0 == memcmp(pPayload->pbCertificateRootThumbprint, pbThumbprint, cbThumbprint))
2003 {
2004 // If we got here, we found that our payload public key identifier and thumbprint
2005 // matched an element in the certficate chain.
2006 hrChainVerification = S_OK;
2007 break;
2008 }
2009
2010 ReleaseNullMem(pbThumbprint);
2011 }
2012 else // no thumbprint match necessary so we're good to go.
2013 {
2014 hrChainVerification = S_OK;
2015 break;
2016 }
2017 }
2018 }
2019 hr = hrChainVerification;
2020 ExitOnFailure(hr, "Failed to find expected public key in certificate chain.");
2021
2022LExit:
2023 ReleaseMem(pbThumbprint);
2024
2025 return hr;
2026}