@samitouri / QOSAMI-WSL / commits / 81839b3f

Add admin protection error message for shadow admin scenarios (#40170)

* Add admin protection error message for shadow admin scenarios When Windows Admin Protection is enabled, the elevated process runs as a shadow admin with a different SID, so distributions registered under the real user are not visible. Surface an informational message in two cases: 1. Launching a distribution by name that is not found (WSL_E_DISTRO_NOT_FOUND) 2. Listing distributions when none are registered (WSL_E_DEFAULT_DISTRO_NOT_FOUND) * formatting * Show admin protection message for non-elevated users too When Admin Protection creates a shadow admin, distros registered under the real user are invisible to the shadow admin and vice versa. Remove the elevation check so the informational message appears for both elevated and non-elevated callers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Ben Hillis committed Apr 22, 2026 at 17:33 UTC 81839b3fb09b4a6f2fde4be7b999e19a977f7490
2 files changed +55 -3
localization/strings/en-US/Resources.resw
+5
@@ -744,6 +744,11 @@ For information please visit https://aka.ms/wslinstall</value>
744 <data name="MessageAdministratorAccessRequiredForDebugShell" xml:space="preserve">
745 <value>Running the debug shell requires running wsl.exe as Administrator.</value>
746 </data>
747 + <data name="MessageAdminProtectionEnabled" xml:space="preserve">
748 + <value>Windows Admin Protection is enabled and your distributions may be registered under a different account.
749 +For more information on Admin Protection, please visit https://aka.ms/apdevguide</value>
750 + <comment>{Locked="Windows Admin Protection"}{Locked="Admin Protection"}{Locked="https://aka.ms/apdevguide"}Command line arguments, file names and string inserts should not be translated</comment>
751 + </data>
752 <data name="MessageInstallProcessFailed" xml:space="preserve">
753 <value>The installation process for distribution '{}' failed with exit code: {}.</value>
754 <comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
src/windows/common/wslutil.cpp
+50 -3
@@ -522,10 +522,36 @@ wsl::windows::common::wslutil::GetDefaultVersion(void)
522 return version;
523 }
524
525 +namespace {
526 +
527 +// Returns true if Windows Admin Protection (shadow admin) is enabled on
528 +// this system. The message is shown for both elevated and non-elevated
529 +// callers because either side may be missing the other's distributions.
530 +// Caches the DLL lookup on first call.
531 +bool IsAdminProtectionEnabled()
532 +{
533 + using ShadowAdminEnabledFn = BOOL(WINAPI)();
534 + static std::optional<LxssDynamicFunction<ShadowAdminEnabledFn>> s_fn;
535 + static std::once_flag s_initFlag;
536 +
537 + std::call_once(s_initFlag, []() {
538 + LxssDynamicFunction<ShadowAdminEnabledFn> fn{DynamicFunctionErrorLogs::None};
539 + if (SUCCEEDED(fn.load(L"SecurityHealthUdk.dll", "Shield_LUAIsShadowAdminEnabled")))
540 + {
541 + s_fn.emplace(std::move(fn));
542 + }
543 + });
544 +
545 + return s_fn.has_value() && (*s_fn)();
546 +}
547 +
548 +} // anonymous namespace
549 +
550 std::wstring wsl::windows::common::wslutil::GetErrorString(HRESULT result)
551 {
552 ULONG buildNumber = 0;
553 std::wstring kbUrl;
554 + std::wstring errorString;
555
556 switch (result)
557 {
@@ -545,14 +571,16 @@ std::wstring wsl::windows::common::wslutil::GetErrorString(HRESULT result)
571 return Localization::MessageHigherIntegrity();
572
573 case WSL_E_DEFAULT_DISTRO_NOT_FOUND:
548 - return Localization::MessageNoDefaultDistro();
574 + errorString = Localization::MessageNoDefaultDistro();
575 + break;
576
577 case HRESULT_FROM_WIN32(WSAECONNABORTED):
578 case HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IN_PROGRESS):
579 return Localization::MessageInstanceTerminated();
580
581 case WSL_E_DISTRO_NOT_FOUND:
555 - return Localization::MessageDistroNotFound();
582 + errorString = Localization::MessageDistroNotFound();
583 + break;
584
585 case HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS):
586 return Localization::MessageDistroNameAlreadyExists();
@@ -695,7 +723,26 @@ std::wstring wsl::windows::common::wslutil::GetErrorString(HRESULT result)
723 }
724 }
725
698 - return GetSystemErrorString(result);
726 + if (errorString.empty())
727 + {
728 + return GetSystemErrorString(result);
729 + }
730 +
731 + // If Admin Protection is enabled, prepend an informational message for
732 + // errors that may be caused by the shadow admin's separate registry hive.
733 + try
734 + {
735 + if (IsAdminProtectionEnabled())
736 + {
737 + auto message = Localization::MessageAdminProtectionEnabled();
738 + message += L"\n\n";
739 + message += errorString;
740 + return message;
741 + }
742 + }
743 + CATCH_LOG()
744 +
745 + return errorString;
746 }
747
748 std::optional<std::pair<std::wstring, GitHubReleaseAsset>> wsl::windows::common::wslutil::GetGitHubAssetFromRelease(const GitHubRelease& Release)