CVE-2024-29188
Description
WiX toolset lets developers create installers for Windows Installer, the Windows installation engine. The custom action behind WiX's RemoveFolderEx functionality could allow a standard user to delete protected directories. RemoveFolderEx deletes an entire directory tree during installation or uninstallation. It does so by recursing every subdirectory starting at a specified directory and adding each subdirectory to the list of directories Windows Installer should delete. If the setup author instructed RemoveFolderEx to delete a per-user folder from a per-machine installer, an attacker could create a directory junction in that per-user folder pointing to a per-machine, protected directory. Windows Installer, when executing the per-machine installer after approval by an administrator, would delete the target of the directory junction. This vulnerability is fixed in 3.14.1 and 4.0.5.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
wixNuGet | < 3.14.1 | 3.14.1 |
wixNuGet | >= 4.0.0, < 4.0.5 | 4.0.5 |
WixToolset.Util.wixextNuGet | < 4.0.5 | 4.0.5 |
Patches
22e5960b57588Don't follow junctions when recursing directories.
2 files changed · +13 −3
src/dtf/SfxCA/SfxUtil.cpp+3 −1 modified@@ -93,7 +93,9 @@ bool DeleteDirectory(const wchar_t* szDir) StringCchCopy(szPath + cchDir + 1, cchPathBuf - (cchDir + 1), fd.cFileName); if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { - if (wcscmp(fd.cFileName, L".") != 0 && wcscmp(fd.cFileName, L"..") != 0) + if (wcscmp(fd.cFileName, L".") != 0 + && wcscmp(fd.cFileName, L"..") != 0 + && ((fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0)) { DeleteDirectory(szPath); }
src/ext/Util/ca/RemoveFoldersEx.cpp+10 −2 modified@@ -38,6 +38,14 @@ static HRESULT RecursePath( } #endif + // Do NOT follow junctions. + DWORD dwAttributes = ::GetFileAttributesW(wzPath); + if (dwAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + { + WcaLog(LOGMSG_STANDARD, "Path is a junction; skipping: %ls", wzPath); + ExitFunction(); + } + // First recurse down to all the child directories. hr = StrAllocFormatted(&sczSearch, L"%s*", wzPath); ExitOnFailure(hr, "Failed to allocate file search string in path: %S", wzPath); @@ -210,10 +218,10 @@ extern "C" UINT WINAPI WixRemoveFoldersEx( hr = PathExpand(&sczExpandedPath, sczPath, PATH_EXPAND_ENVIRONMENT); ExitOnFailure(hr, "Failed to expand path: %S for row: %S", sczPath, sczId); - + hr = PathBackslashTerminate(&sczExpandedPath); ExitOnFailure(hr, "Failed to backslash-terminate path: %S", sczExpandedPath); - + WcaLog(LOGMSG_STANDARD, "Recursing path: %S for row: %S.", sczExpandedPath, sczId); hr = RecursePath(sczExpandedPath, sczId, sczComponent, sczProperty, iMode, f64BitComponent, &dwCounter, &hTable, &hColumns); ExitOnFailure(hr, "Failed while navigating path: %S for row: %S", sczPath, sczId);
93eeb5f68357Don't follow junctions when recursing directories.
2 files changed · +13 −3
src/DTF/Tools/SfxCA/SfxUtil.cpp+3 −1 modified@@ -93,7 +93,9 @@ bool DeleteDirectory(const wchar_t* szDir) StringCchCopy(szPath + cchDir + 1, cchPathBuf - (cchDir + 1), fd.cFileName); if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { - if (wcscmp(fd.cFileName, L".") != 0 && wcscmp(fd.cFileName, L"..") != 0) + if (wcscmp(fd.cFileName, L".") != 0 + && wcscmp(fd.cFileName, L"..") != 0 + && ((fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0)) { DeleteDirectory(szPath); }
src/ext/ca/wixca/dll/RemoveFoldersEx.cpp+10 −2 modified@@ -24,6 +24,14 @@ static HRESULT RecursePath( WIN32_FIND_DATAW wfd; LPWSTR sczNext = NULL; + // Do NOT follow junctions. + DWORD dwAttributes = ::GetFileAttributesW(wzPath); + if (dwAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + { + WcaLog(LOGMSG_STANDARD, "Path is a junction; skipping: %ls", wzPath); + ExitFunction(); + } + // First recurse down to all the child directories. hr = StrAllocFormatted(&sczSearch, L"%s*", wzPath); ExitOnFailure1(hr, "Failed to allocate file search string in path: %S", wzPath); @@ -159,10 +167,10 @@ extern "C" UINT WINAPI WixRemoveFoldersEx( hr = PathExpand(&sczExpandedPath, sczPath, PATH_EXPAND_ENVIRONMENT); ExitOnFailure2(hr, "Failed to expand path: %S for row: %S", sczPath, sczId); - + hr = PathBackslashTerminate(&sczExpandedPath); ExitOnFailure1(hr, "Failed to backslash-terminate path: %S", sczExpandedPath); - + WcaLog(LOGMSG_STANDARD, "Recursing path: %S for row: %S.", sczExpandedPath, sczId); hr = RecursePath(sczExpandedPath, sczId, sczComponent, sczProperty, iMode, &dwCounter, &hTable, &hColumns); ExitOnFailure2(hr, "Failed while navigating path: %S for row: %S", sczPath, sczId);
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-jx4p-m4wm-vvjgghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-29188ghsaADVISORY
- github.com/wixtoolset/issues/security/advisories/GHSA-jx4p-m4wm-vvjgnvdWEB
- github.com/wixtoolset/wix/commit/2e5960b575881567a8807e6b8b9c513138b19742nvdWEB
- github.com/wixtoolset/wix3/commit/93eeb5f6835776694021f66d4226c262c67d487anvdWEB
News mentions
0No linked articles in our index yet.