Repository: microsoft/terminal (language: cpp)

Issue:
wsl.exe attempts to load despite Windows Subsystem for Linux being disabled
### Windows Terminal version (or Windows build number)

1.11.2921.0

### Other Software

wsl.exe (10.0.22000.1)

### Steps to reproduce

Loading Windows Terminal will launch `wsl.exe` despite Windows Subsystem for Linux (WSL) being disabled.

This is problematic as I explicitly deny execution of `wsl.exe` per [Microsoft recommended block rules](https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/microsoft-recommended-block-rules) in my Windows Defender Application Control (WDAC) policy. As a result, every time I launch Windows Terminal, a `Microsoft-Windows-CodeIntegrity/Operational` event ID 3077 event is generated. Example event message:

```
Code Integrity determined that a process (\Device\HarddiskVolume3\Program Files\WindowsApps\Microsoft.WindowsTerminal_1.11.2921.0_x64__8wekyb3d8bbwe\WindowsTerminal.exe) attempted to load \Device\HarddiskVolume3\Windows\System32\wsl.exe that did not meet the Enterprise signing level requirements or violated code integrity policy (Policy ID:{5dc289d3-65e7-47a7-806f-4d092b07a36a}).
```

Steps to confirm that WSL is disabled:

```
PS> Get-WindowsOptionalFeature -FeatureName Microsoft-Windows-Subsystem-Linux -Online

FeatureName      : Microsoft-Windows-Subsystem-Linux
DisplayName      : Windows Subsystem for Linux
Description      : Provides services and environments for running native user-mode Linux shells and tools on Windows.
RestartRequired  : Possible
State            : Disabled
CustomProperties :
    ServerComponent\Description : Provides services and environments for running native user-mode Linux shells and tools on Windows.
    ServerComponent\DisplayName : Windows Subsystem for Linux
    ServerComponent\Id : 1033
    ServerComponent\Type : Feature
    ServerComponent\UniqueName : Microsoft-Windows-Subsystem-Linux
    ServerComponent\Deploys\Update\Name : Microsoft-Windows-Subsystem-Linux
```

There is also nothing in my `settings.json` configuration that would explicitly launch any component of WSL. Here is my `settings.json` for reference:

```json
{
    "$schema": "https://aka.ms/terminal-profiles-schema",
    "actions": 
    [
        {
            "command": 
            {
                "action": "copy",
                "singleLine": false
            },
            "keys": "ctrl+c"
        },
        {
            "command": "find",
            "keys": "ctrl+shift+f"
        },
        {
            "command": "paste",
            "keys": "ctrl+v"
        },
        {
            "command": 
            {
                "action": "splitPane",
                "split": "auto",
                "splitMode": "duplicate"
            },
            "keys": "alt+shift+d"
        }
    ],
    "centerOnLaunch": true,
    "copyFormatting": "none",
    "copyOnSelect": false,
    "defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
    "experimental.detectURLs": false,
    "initialCols": 150,
    "initialRows": 42,
    "profiles": 
    {
        "defaults": {},
        "list": 
        [
            {
                "antialiasingMode": "cleartype",
                "colorScheme": "Campbell",
                "commandline": "powershell.exe -NoLogo",
                "guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
                "hidden": false,
                "name": "Windows PowerShell",
                "startingDirectory": "%USERPROFILE%\\Desktop",
                "useAcrylic": false
            },
            {
                "commandline": "cmd.exe",
                "guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
                "hidden": false,
                "name": "Command Prompt",
                "startingDirectory": "%USERPROFILE%\\Desktop"
            },
            {
                "guid": "{b453ae62-4e3d-5e58-b989-0a998ec441b8}",
                "hidden": true,
                "name": "Azure Cloud Shell",
                "source": "Windows.Terminal.Azure"
            }
        ]
    },
    "schemes": 
    [
        {
            "background": "#0C0C0C",
            "black": "#0C0C0C",
            "blue": "#0037DA",
            "brightBlack": "#767676",
            "brightBlue": "#3B78FF",
            "brightCyan": "#61D6D6",
            "brightGreen": "#16C60C",
            "brightPurple": "#B4009E",
            "brightRed": "#E74856",
            "brightWhite": "#F2F2F2",
            "brightYellow": "#F9F1A5",
            "cursorColor": "#FFFFFF",
            "cyan": "#3A96DD",
            "foreground": "#CCCCCC",
            "green": "#13A10E",
            "name": "Campbell",
            "purple": "#881798",
            "red": "#C50F1F",
            "selectionBackground": "#FFFFFF",
            "white": "#CCCCCC",
            "yellow": "#C19C00"
        }
    ]
}
```

### Expected Behavior

When Windows Subsystem for Linux is disabled, Windows Terminal _will not execute_ `wsl.exe`.

### Actual Behavior

When Windows Subsystem for Linux is disabled, Windows Terminal _executes_ `wsl.exe`.


Discussion / hints:
This is by-design. We need to call `wsl.exe` to be able to enumerate the list of distros that are installed. Though, that should have been fixed in #10967, where we're now doing that lookup in the registry. Hmm. The comments in that thread seem to suggest that's only in 1.11 _Preview_, and we didn't roll that forward to Stable?

Hmm. Okay well the bot must have been on a nap because https://github.com/microsoft/terminal/compare/v1.10.2714.0...v1.11.2921.0 suggests that f9a844dbdacff8b7b6b0a1221de7634668fa8ca7 _is_ in 1.11 stable. 
UNLESS the registry lookup failed, and that caused us to fallback to the old method. I totally forgot I left that in there. What's in `Computer\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Lxss`? Does that key even exist? 
Thanks for the quick response, @zadjii-msft. The `Lxss` key doesn't exist in HKCU but it does in HKLM but doesn't have any values defined:

```
PS> Get-Item -Path HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss
Get-Item : Cannot find path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss' because it does not exist.
At line:1 char:1
+ Get-Item -Path HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (HKCU:\Software\...entVersion\Lxss:String) [Get-Item], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetItemCommand

PS> Get-Item -Path HKLM:\Software\Microsoft\Windows\CurrentVersion\Lxss


    Hive: HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion


Name                           Property
----                           --------
Lxss
```

The path of Windows Terminal that is attempting to launch `wsl.exe` is `C:\Program Files\WindowsApps\Microsoft.WindowsTerminal_1.11.2921.0_x64__8wekyb3d8bbwe\WindowsTerminal.exe`. Confirming the version I'm running:

```
PS> $File = Get-Item -Path 'C:\Program Files\WindowsApps\Microsoft.WindowsTerminal_1.11.2921.0_x64__8wekyb3d8bbwe\WindowsTerminal.exe'

PS> $File.VersionInfo.ProductVersion
1.11.211019001-release1.11

PS> $File.VersionInfo.FileVersion
1.11.2110.19001
```

Are there any additional repro steps you'd recommend I try? Thanks!
Ah okay. I see the issue now. The `Lxss` key is [only checked in HKCU](https://github.com/microsoft/terminal/blob/479ef264b2d9e284db1fc8457f3897d1b19ac32e/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp#L181) and not checked in HKLM. And so because `openWslRegKey` returns null, it falls back to calling `legacyGenerate` which calls `wsl.exe`.

So perhaps a durable fix would be to also check in HKLM. A s short term fix for me personally, I confirmed that the event no longer surfaces after I created an `Lxss` key in `HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion`. I'd love the more durable fix but I'm accepting of this short-term fix as I am likely among a small group of people who considers this to be an actual issue. Thanks!
> Ah okay. I see the issue now. The `Lxss` key is [only checked in HKCU](https://github.com/microsoft/terminal/blob/479ef264b2d9e284db1fc8457f3897d1b19ac32e/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp#L181) and not checked in HKLM. And so because `openWslRegKey` returns null, it falls back to calling `legacyGenerate` which calls `wsl.exe`.
> 
> So perhaps a durable fix would be to also check in HKLM. A s short term fix for me personally, I confirmed that the event no longer surfaces after I created an `Lxss` key in `HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion`. I'd love the more durable fix but I'm accepting of this short-term fix as I am likely among a small group of people who considers this to be an actual issue. Thanks!

Thanks a lot for that hint Matt - i really needed that, too, because WDAC rocks and this was really annoying in the Operational Log ;-)
Thanks for chiming in, @SebCT. I'm glad I wasn't the only one who found this to be obnoxious. And good on you for deploying Microsoft's recommended block list! 🤜🤛
> Thanks for chiming in, @SebCT. I'm glad I wasn't the only one who found this to be obnoxious. And good on you for deploying Microsoft's recommended block list! 🤜🤛

Hello Matt! 

Thank you, without your Twitter and blog postings i couldn't do WDAC - so it makes lots of fun now ;-)

Actually i found out, that VSCODE also triggers WSL.exe (on my WDAC test system) if the following happens:

I start my VS Code with the "PowerShell integrated console V2021.10.2" because i installed the official PowerShell VS Code Add-On from Microsoft for scripting in PowerShell ISE Style on VS Code - in the operational log of WDAC i get a 3076 event (only once when starting):

"Code Integrity determined that a process (\Device\HarddiskVolume15\Windows\System32\cmd.exe) attempted to load \Device\HarddiskVolume15\Windows\System32\wsl.exe that did not meet the Enterprise signing level requirements or violated code integrity policy"

Can you confirm that behaviour on your system(s), too (in case you use the VS Code PS Add-On)? 

I don't have a solution now for finding out why that happens, because the LXSS Regkey is set, but it doesn't help in this case. 

Of course it's not a big problem and i can live with it, but would be curious why that happens and if you can confirm that.

Thanks in advance for your help and looking forward for your answer 😊

Update: found out the problem: i had wrong/old profile settings in my extension, i resetted it with the help of this article: https://code.visualstudio.com/docs/editor/integrated-terminal#_terminal-profiles - now no more WSL.exe in WDAC Event Log :-)
Good to hear you discovered the solution, @SebCT! I have yet to attempt building an allowlist for VSCode. You are brave! Haha. I run it in a VM when I need to do stuff locally. Otherwise, [vscode.dev](vscode.dev) in meeting my basic needs.
> Good to hear you discovered the solution, @SebCT! I have yet to attempt building an allowlist for VSCode. You are brave! Haha. I run it in a VM when I need to do stuff locally. Otherwise, [vscode.dev](vscode.dev) in meeting my basic needs.

Thanks for that hint with vscode.dev - totally forgot that, it's a very interesting solution for locked down devices, nice! 👌👍

Well, unfortunately my workaround didn't work, after a restart or two if VSCode i had the same thing again - so i used SysMon to find out wht's going on - here is the result, maybe you will find it interesting. I must admit i don't know how to solve it now, but will live with it - i think it's a VSCode thing, in bold text you see the Command Line in the event log:

Sysmon Event Log:

Image: C:\Windows\System32\cmd.exe
FileVersion: 10.0.19041.746 (WinBuild.160101.0800)
Description: Windows Command Processor
Product: Microsoft® Windows® Operating System
Company: Microsoft Corporation
OriginalFileName: Cmd.Exe
**CommandLine: C:\WINDOWS\system32\cmd.exe /d /s /c "wsl.exe -l -q"**
CurrentDirectory: C:\Program Files\Microsoft VS Code\
User: StandardUser
LogonGuid: {8a12ecd4-2ce5-61ad-e692-140000000000}
LogonId: 0x1492E6
TerminalSessionId: 1
IntegrityLevel: Medium
Hashes: MD5=8A2122E8162DBEF04694B9C3E0B6CDEE,SHA256=B99D61D874728EDC0918CA0EB10EAB93D381E7367E377406E65963366C874450,IMPHASH=272245E2988E1E430500B852C4FB5E18
ParentProcessGuid: {8a12ecd4-8353-61ad-bb08-000000001000}
ParentProcessId: 6244
ParentImage: C:\Program Files\Microsoft VS Code\Code.exe
ParentCommandLine: "C:\Program Files\Microsoft VS Code\Code.exe" --type=renderer --disable-color-correct-rendering --field-trial-handle=1540,11676687341578546723,3858864379961801446,131072 --disable-features=CookiesWithoutSameSiteMustBeSecure,SameSiteByDefaultCookies,SpareRendererForSitePerProcess,WinRetrieveSuggestionsOnlyOnDemand --lang=de --user-data-dir="C:\Users\StandardUser\AppData\Roaming\Code" --standard-schemes=vscode-webview,vscode-file --secure-schemes=vscode-webview,vscode-file --bypasscsp-schemes --cors-schemes=vscode-webview,vscode-file --fetch-schemes=vscode-webview,vscode-file --service-worker-schemes=vscode-webview --streaming-schemes --app-user-model-id=Microsoft.VisualStudioCode --app-path="C:\Program Files\Microsoft VS Code\resources\app" --no-sandbox --no-zygote --disable-blink-features=Auxclick --node-integration-in-worker --device-scale-factor=1.5 --num-raster-threads=4 --enable-main-frame-before-activation --renderer-client-id=7 --no-v8-untrusted-code-mitigations --mojo-platform-channel-handle=2980 /prefetch:1 --vscode-window-config=vscode:47b0147a-7eea-4b40-af6a-a4833063640c
ParentUser: StandardUser


It's invoked because of the PowerShell Add-On, which starts the VSCode Terminal - interesting thing....
Interesting. Thanks for that context, @SebCT. To an extent, I've accepted that some things will fill up the logs. For example, I get events all the time of mscorsvw.exe being prevented from loading Microsoft.Build.Framework.dll. When I audit events from the CodeIntegrity event log, I can filter those events out based on a verification error code of 26 (Explicitly denied by WDAC policy) in the 3089 events. In the WDACTools module, I can filter them out with the following:

```powershell
Get-WDACCodeIntegrityEvent -SinceLastPolicyRefresh -Enforce -SignerInformation -IgnoreDenyEvents
```

Note: there's a bug where VerificationError 26 doesn't populate when you're in audit mode which is annoying. I hope that helps!
> Interesting. Thanks for that context, @SebCT. To an extent, I've accepted that some things will fill up the logs. For example, I get events all the time of mscorsvw.exe being prevented from loading Microsoft.Build.Framework.dll. When I audit events from the CodeIntegrity event log, I can filter those events out based on a verification error code of 26 (Explicitly denied by WDAC policy) in the 3089 events. In the WDACTools module, I can filter them out with the following:
> 
> ```powershell
> Get-WDACCodeIntegrityEvent -SinceLastPolicyRefresh -Enforce -SignerInformation -IgnoreDenyEvents
> ```
> 
> Note: there's a bug where VerificationError 26 doesn't populate when you're in audit mode which is annoying. I hope that helps!

Thanks for that Matt, that really helps me for future Audits, very nice! 👍👌😊 

Fix patch:
diff --git a/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp b/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp
index a40a7f853be..840835cf1f3 100644
--- a/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp
+++ b/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp
@@ -7,8 +7,6 @@
 #include "LegacyProfileGeneratorNamespaces.h"
 #include "../../inc/DefaultSettings.h"
 
-#include <io.h>
-#include <fcntl.h>
 #include "DynamicProfileUtils.h"
 
 static constexpr std::wstring_view WslHomeDirectory{ L"~" };
@@ -68,105 +66,6 @@ static winrt::com_ptr<implementation::Profile> makeProfile(const std::wstring& d
     return WSLDistro;
 }
 
-// Method Description:
-// -  Enumerates all the installed WSL distros to create profiles for them.
-// Arguments:
-// - <none>
-// Return Value:
-// - a vector with all distros for all the installed WSL distros
-static void legacyGenerate(std::vector<winrt::com_ptr<implementation::Profile>>& profiles)
-{
-    wil::unique_handle readPipe;
-    wil::unique_handle writePipe;
-    SECURITY_ATTRIBUTES sa{ sizeof(sa), nullptr, true };
-    THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&readPipe, &writePipe, &sa, 0));
-    STARTUPINFO si{ 0 };
-    si.cb = sizeof(si);
-    si.dwFlags = STARTF_USESTDHANDLES;
-    si.hStdOutput = writePipe.get();
-    si.hStdError = writePipe.get();
-    wil::unique_process_information pi;
-    wil::unique_cotaskmem_string systemPath;
-    THROW_IF_FAILED(wil::GetSystemDirectoryW(systemPath));
-    std::wstring command(systemPath.get());
-    command += L"\\wsl.exe --list";
-
-    THROW_IF_WIN32_BOOL_FALSE(CreateProcessW(nullptr,
-                                             const_cast<LPWSTR>(command.c_str()),
-                                             nullptr,
-                                             nullptr,
-                                             TRUE,
-                                             CREATE_NO_WINDOW,
-                                             nullptr,
-                                             nullptr,
-                                             &si,
-                                             &pi));
-    switch (WaitForSingleObject(pi.hProcess, 2000))
-    {
-    case WAIT_OBJECT_0:
-        break;
-    case WAIT_ABANDONED:
-    case WAIT_TIMEOUT:
-        return;
-    case WAIT_FAILED:
-        THROW_LAST_ERROR();
-    default:
-        THROW_HR(ERROR_UNHANDLED_EXCEPTION);
-    }
-    DWORD exitCode;
-    if (!GetExitCodeProcess(pi.hProcess, &exitCode))
-    {
-        THROW_HR(E_INVALIDARG);
-    }
-    else if (exitCode != 0)
-    {
-        return;
-    }
-    DWORD bytesAvailable;
-    THROW_IF_WIN32_BOOL_FALSE(PeekNamedPipe(readPipe.get(), nullptr, NULL, nullptr, &bytesAvailable, nullptr));
-    // "The _open_osfhandle call transfers ownership of the Win32 file handle to the file descriptor."
-    // (https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/open-osfhandle?view=vs-2019)
-    // so, we detach_from_smart_pointer it -- but...
-    // "File descriptors passed into _fdopen are owned by the returned FILE * stream.
-    // If _fdopen is successful, do not call _close on the file descriptor.
-    // Calling fclose on the returned FILE * also closes the file descriptor."
-    // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/fdopen-wfdopen?view=vs-2019
-    auto stdioPipeHandle = _wfdopen(_open_osfhandle((intptr_t)wil::detach_from_smart_pointer(readPipe), _O_WTEXT | _O_RDONLY), L"r");
-    auto closeFile = wil::scope_exit([&]() { fclose(stdioPipeHandle); });
-
-    std::wfstream pipe{ stdioPipeHandle };
-
-    std::wstring wline;
-    std::getline(pipe, wline); // remove the header from the output.
-    while (pipe.tellp() < bytesAvailable)
-    {
-        std::getline(pipe, wline);
-        std::wstringstream wlinestream(wline);
-        if (wlinestream)
-        {
-            std::wstring distName;
-            std::getline(wlinestream, distName, L'\r');
-
-            if (til::starts_with(distName, DockerDistributionPrefix))
-            {
-                // Docker for Windows creates some utility distributions to handle Docker commands.
-                // Pursuant to GH#3556, because they are _not_ user-facing we want to hide them.
-                continue;
-            }
-
-            const auto firstChar = distName.find_first_of(L"( ");
-            // Some localizations don't have a space between the name and "(Default)"
-            // https://github.com/microsoft/terminal/issues/1168#issuecomment-500187109
-            if (firstChar < distName.size())
-            {
-                distName.resize(firstChar);
-            }
-
-            profiles.emplace_back(makeProfile(distName));
-        }
-    }
-}
-
 // Function Description:
 // - Create a list of Profiles for each distro listed in names.
 // - Skips distros that are utility distros for docker (see GH#3556)
@@ -310,9 +209,9 @@ static bool getWslNames(const wil::unique_hkey& wslRootKey,
 // Method Description:
 // - Generate a list of profiles for each on the installed WSL distros. This
 //   will first try to read the installed distros from the registry. If that
-//   fails, we'll fall back to the legacy way of launching WSL.exe to read the
-//   distros from the commandline. Reading the registry is slightly more stable
-//   (see GH#7199, GH#9905), but it is certainly BODGY
+//   fails, we'll assume there are no WSL distributions installed.
+//   Reading the registry is slightly more stable (see GH#7199, GH#9905),
+//   but it is certainly BODGY
 // Arguments:
 // - <none>
 // Return Value:
@@ -333,6 +232,4 @@ void WslDistroGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementat
             }
         }
     }
-
-    legacyGenerate(profiles);
 }


Test patch: