@joebigelow / wix-1 / commits / fed3d69e

Protect elevated working folder from malicious data

When running elevated, Burn uses the Windows Temp folder as its working folder to prevent normal processes from tampering with the files. Windows Temp does allow non-elevated processes to write to the folder but they cannot see the files there. Unfortunately, contrary to our belief, non-elevated processes can read the files in Windows Temp by watching for directory changes. This allows a malicious process to lie in wait, watching the Windows Temp folder until a Burn process is launched elevated, then attack the working folder. Mitigate that attack by protecting the working folder to only elevated users. Managed custom actions also fall back to using the Windows Temp folder in some cases and thus can be exposed in a similar fashion as an elevated Burn process. Remove that possibility.

Rob Mensching committed Mar 20, 2024 at 23:51 UTC fed3d69eb4da7fa2bafdd8f555ce5869c36925f7
6 files changed +43 -33
src/burn/engine/ba.h
+1
@@ -111,6 +111,7 @@ HRESULT BootstrapperApplicationInterpretExecuteResult(
111 );
112
113 HRESULT BootstrapperApplicationEnsureWorkingFolder(
114 + __in BOOL fElevated,
115 __in BURN_CACHE* pCache,
116 __deref_out_z LPWSTR* psczUserExperienceWorkingFolder
117 );
src/burn/engine/bootstrapperapplication.cpp
+2 -1
@@ -276,6 +276,7 @@ EXTERN_C HRESULT BootstrapperApplicationInterpretExecuteResult(
276 }
277
278 EXTERN_C HRESULT BootstrapperApplicationEnsureWorkingFolder(
279 + __in BOOL fElevated,
280 __in BURN_CACHE* pCache,
281 __deref_out_z LPWSTR* psczUserExperienceWorkingFolder
282 )
@@ -283,7 +284,7 @@ EXTERN_C HRESULT BootstrapperApplicationEnsureWorkingFolder(
284 HRESULT hr = S_OK;
285 LPWSTR sczWorkingFolder = NULL;
286
286 - hr = CacheEnsureBaseWorkingFolder(pCache, &sczWorkingFolder);
287 + hr = CacheEnsureBaseWorkingFolder(fElevated, pCache, &sczWorkingFolder);
288 ExitOnFailure(hr, "Failed to create working folder.");
289
290 hr = StrAllocFormatted(psczUserExperienceWorkingFolder, L"%ls%ls\\", sczWorkingFolder, L".ba");
src/burn/engine/cache.cpp
+29 -3
@@ -106,6 +106,7 @@ static HRESULT SecurePath(
106 __in LPCWSTR wzPath
107 );
108 static HRESULT CopyEngineToWorkingFolder(
109 + __in BOOL fElevated,
110 __in BURN_CACHE* pCache,
111 __in_z LPCWSTR wzSourcePath,
112 __in_z LPCWSTR wzWorkingFolderName,
@@ -330,6 +331,7 @@ LExit:
331 }
332
333 extern "C" HRESULT CacheEnsureBaseWorkingFolder(
334 + __in BOOL fElevated,
335 __in BURN_CACHE* pCache,
336 __deref_out_z_opt LPWSTR* psczBaseWorkingFolder
337 )
@@ -338,15 +340,32 @@ extern "C" HRESULT CacheEnsureBaseWorkingFolder(
340
341 HRESULT hr = S_OK;
342 LPWSTR sczPotential = NULL;
343 + PSECURITY_DESCRIPTOR psd = NULL;
344 + LPSECURITY_ATTRIBUTES pWorkingFolderAcl = NULL;
345
346 if (!pCache->fInitializedBaseWorkingFolder)
347 {
348 + // If elevated, allocate the pWorkingFolderAcl to protect the working folder to only SYSTEM and Admins.
349 + if (fElevated)
350 + {
351 + LPCWSTR wzSddl = L"D:PAI(A;;FA;;;BA)(A;OICIIO;GA;;;BA)(A;;FA;;;SY)(A;OICIIO;GA;;;SY)";
352 + if (!::ConvertStringSecurityDescriptorToSecurityDescriptorW(wzSddl, SDDL_REVISION_1, &psd, NULL))
353 + {
354 + ExitWithLastError(hr, "Failed to create the security descriptor for the working folder.");
355 + }
356 +
357 + pWorkingFolderAcl = reinterpret_cast<LPSECURITY_ATTRIBUTES>(MemAlloc(sizeof(SECURITY_ATTRIBUTES), TRUE));
358 + pWorkingFolderAcl->nLength = sizeof(SECURITY_ATTRIBUTES);
359 + pWorkingFolderAcl->lpSecurityDescriptor = psd;
360 + pWorkingFolderAcl->bInheritHandle = FALSE;
361 + }
362 +
363 for (DWORD i = 0; i < pCache->cPotentialBaseWorkingFolders; ++i)
364 {
365 hr = PathConcatRelativeToFullyQualifiedBase(pCache->rgsczPotentialBaseWorkingFolders[i], pCache->wzGuid, &sczPotential);
366 if (SUCCEEDED(hr))
367 {
349 - hr = DirEnsureExists(sczPotential, NULL);
368 + hr = DirEnsureExists(sczPotential, pWorkingFolderAcl);
369 if (SUCCEEDED(hr))
370 {
371 pCache->sczBaseWorkingFolder = sczPotential;
@@ -373,6 +392,11 @@ extern "C" HRESULT CacheEnsureBaseWorkingFolder(
392 }
393
394 LExit:
395 + ReleaseMem(pWorkingFolderAcl);
396 + if (psd)
397 + {
398 + ::LocalFree(psd);
399 + }
400 ReleaseStr(sczPotential);
401
402 return hr;
@@ -888,6 +912,7 @@ extern "C" HRESULT CachePreparePackage(
912 }
913
914 extern "C" HRESULT CacheBundleToWorkingDirectory(
915 + __in BOOL fElevated,
916 __in BURN_CACHE* pCache,
917 __in_z LPCWSTR wzExecutableName,
918 __in BURN_SECTION* pSection,
@@ -912,7 +937,7 @@ extern "C" HRESULT CacheBundleToWorkingDirectory(
937 }
938 else // otherwise, carry on putting the bundle in the working folder.
939 {
915 - hr = CopyEngineToWorkingFolder(pCache, sczSourcePath, BUNDLE_WORKING_FOLDER_NAME, wzExecutableName, pSection, psczEngineWorkingPath);
940 + hr = CopyEngineToWorkingFolder(fElevated, pCache, sczSourcePath, BUNDLE_WORKING_FOLDER_NAME, wzExecutableName, pSection, psczEngineWorkingPath);
941 ExitOnFailure(hr, "Failed to copy engine to working folder.");
942 }
943
@@ -2063,6 +2088,7 @@ LExit:
2088
2089
2090 static HRESULT CopyEngineToWorkingFolder(
2091 + __in BOOL fElevated,
2092 __in BURN_CACHE* pCache,
2093 __in_z LPCWSTR wzSourcePath,
2094 __in_z LPCWSTR wzWorkingFolderName,
@@ -2079,7 +2105,7 @@ static HRESULT CopyEngineToWorkingFolder(
2105 LPWSTR sczPayloadSourcePath = NULL;
2106 LPWSTR sczPayloadTargetPath = NULL;
2107
2082 - hr = CacheEnsureBaseWorkingFolder(pCache, &sczWorkingFolder);
2108 + hr = CacheEnsureBaseWorkingFolder(fElevated, pCache, &sczWorkingFolder);
2109 ExitOnFailure(hr, "Failed to create working path to copy engine.");
2110
2111 hr = PathConcatRelativeToFullyQualifiedBase(sczWorkingFolder, wzWorkingFolderName, &sczTargetDirectory);
src/burn/engine/cache.h
+2
@@ -96,6 +96,7 @@ HRESULT CacheEnsureAcquisitionFolder(
96 __in BURN_CACHE* pCache
97 );
98 HRESULT CacheEnsureBaseWorkingFolder(
99 + __in BOOL fElevated,
100 __in BURN_CACHE* pCache,
101 __deref_out_z_opt LPWSTR* psczBaseWorkingFolder
102 );
@@ -171,6 +172,7 @@ HRESULT CachePreparePackage(
172 __in BURN_PACKAGE* pPackage
173 );
174 HRESULT CacheBundleToWorkingDirectory(
175 + __in BOOL fElvated,
176 __in BURN_CACHE* pCache,
177 __in_z LPCWSTR wzExecutableName,
178 __in BURN_SECTION* pSection,
src/burn/engine/core.cpp
+3 -3
@@ -165,7 +165,7 @@ extern "C" HRESULT CoreInitialize(
165 if (BURN_MODE_NORMAL == pEngineState->internalCommand.mode || BURN_MODE_EMBEDDED == pEngineState->internalCommand.mode)
166 {
167 // Extract all UX payloads to working folder.
168 - hr = BootstrapperApplicationEnsureWorkingFolder(&pEngineState->cache, &pEngineState->userExperience.sczTempDirectory);
168 + hr = BootstrapperApplicationEnsureWorkingFolder(pEngineState->internalCommand.fInitiallyElevated, &pEngineState->cache, &pEngineState->userExperience.sczTempDirectory);
169 ExitOnFailure(hr, "Failed to get unique temporary folder for bootstrapper application.");
170
171 hr = PayloadExtractUXContainer(&pEngineState->userExperience.payloads, &containerContext, pEngineState->userExperience.sczTempDirectory);
@@ -588,7 +588,7 @@ extern "C" HRESULT CoreElevate(
588 // If the elevated companion pipe isn't created yet, let's make that happen.
589 if (!pEngineState->sczBundleEngineWorkingPath)
590 {
591 - hr = CacheBundleToWorkingDirectory(&pEngineState->cache, pEngineState->registration.sczExecutableName, &pEngineState->section, &pEngineState->sczBundleEngineWorkingPath);
591 + hr = CacheBundleToWorkingDirectory(pEngineState->internalCommand.fInitiallyElevated, &pEngineState->cache, pEngineState->registration.sczExecutableName, &pEngineState->section, &pEngineState->sczBundleEngineWorkingPath);
592 ExitOnFailure(hr, "Failed to cache engine to working directory.");
593 }
594
@@ -697,7 +697,7 @@ extern "C" HRESULT CoreApply(
697 // Ensure the engine is cached to the working path.
698 if (!pEngineState->sczBundleEngineWorkingPath)
699 {
700 - hr = CacheBundleToWorkingDirectory(&pEngineState->cache, pEngineState->registration.sczExecutableName, &pEngineState->section, &pEngineState->sczBundleEngineWorkingPath);
700 + hr = CacheBundleToWorkingDirectory(pEngineState->internalCommand.fInitiallyElevated, &pEngineState->cache, pEngineState->registration.sczExecutableName, &pEngineState->section, &pEngineState->sczBundleEngineWorkingPath);
701 ExitOnFailure(hr, "Failed to cache engine to working directory.");
702 }
703
src/dtf/SfxCA/SfxUtil.cpp
+6 -26
@@ -164,38 +164,18 @@ bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule,
164 StringCchCopy(szTempDir, cchTempDirBuf, szModule);
165 StringCchCat(szTempDir, cchTempDirBuf, L"-");
166
167 + BOOL fCreatedDirectory = FALSE;
168 DWORD cchTempDir = (DWORD) wcslen(szTempDir);
168 - for (int i = 0; DirectoryExists(szTempDir); i++)
169 + for (int i = 0; i < 10000 && !fCreatedDirectory; i++)
170 {
171 swprintf_s(szTempDir + cchTempDir, cchTempDirBuf - cchTempDir, L"%d", i);
172 + fCreatedDirectory = ::CreateDirectory(szTempDir, NULL);
173 }
174
173 - if (!CreateDirectory(szTempDir, NULL))
175 + if (!fCreatedDirectory)
176 {
175 - cchCopied = GetTempPath(cchTempDirBuf, szTempDir);
176 - if (cchCopied == 0 || cchCopied >= cchTempDirBuf)
177 - {
178 - Log(hSession, L"Failed to get temp directory. Error code %d", GetLastError());
179 - return false;
180 - }
181 -
182 - wchar_t* szModuleName = wcsrchr(szModule, L'\\');
183 - if (szModuleName == NULL) szModuleName = szModule;
184 - else szModuleName = szModuleName + 1;
185 - StringCchCat(szTempDir, cchTempDirBuf, szModuleName);
186 - StringCchCat(szTempDir, cchTempDirBuf, L"-");
187 -
188 - cchTempDir = (DWORD) wcslen(szTempDir);
189 - for (int i = 0; DirectoryExists(szTempDir); i++)
190 - {
191 - swprintf_s(szTempDir + cchTempDir, cchTempDirBuf - cchTempDir, L"%d", i);
192 - }
193 -
194 - if (!CreateDirectory(szTempDir, NULL))
195 - {
196 - Log(hSession, L"Failed to create temp directory. Error code %d", GetLastError());
197 - return false;
198 - }
177 + Log(hSession, L"Failed to create temp directory. Error code %d", ::GetLastError());
178 + return false;
179 }
180
181 Log(hSession, L"Extracting custom action to temporary directory: %s\\", szTempDir);