Skip to content

Commit

Permalink
wsl: detect host Windows session 0 and disable browser
Browse files Browse the repository at this point in the history
In order to detect if we have an interactive Windows desktop session
from inside WSL we need to 'punch out' from WSL to determine the session
ID and window station.

Strictly speaking, except for session 0 (from Vista onwards), any
Windows session can have exactly one interactive window station (always
called WinSta0). However, because we cannot easily determine the window
station name from a simple cmd/powershell script, we take a simplified
approach which isn't 100% accurate.

Instead, we only permit browser auth methods if we are NOT in Windows
session 0; any other Windows session we assume we are in WinSta0.

The default OpenSSH Server configuration (Windows 10+) has `sshd`
running as the built-in NT user NETWORK_SERVICE, which means it runs in
session 0 (the services session). This is most common scenario, other
than using WSL from a 'real', interactive Windows session that we're
likely to face.
  • Loading branch information
mjcheetham committed Mar 10, 2023
1 parent f0a1541 commit 2e08a83
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 20 deletions.
83 changes: 63 additions & 20 deletions src/shared/Core/BrowserUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,43 @@ public static class BrowserUtils

private static bool GetWebBrowserAvailable(IEnvironment env, IFileSystem fs, ISessionManager sm)
{
// If this is a Windows Subsystem for Linux distribution we may
// be able to launch the web browser of the host Windows OS.
if (PlatformUtils.IsLinux() && WslUtils.IsWslDistribution(env, fs, out _))
{
// We need a shell execute handler to be able to launch to browser
if (!TryGetLinuxShellExecuteHandler(env, out _))
{
return false;
}

//
// If we are in Windows logon session 0 then the user can never interact,
// even in the WinSta0 window station. This is typical when SSH-ing into a
// Windows 10+ machine using the default OpenSSH Server configuration,
// which runs in the 'services' session 0.
//
// If we're in any other session, and in the WinSta0 window station then
// the user can possibly interact. However, since it's hard to determine
// the window station from PowerShell cmdlets (we'd need to write P/Invoke
// code and that's just messy and too many levels of indirection quite
// frankly!) we just assume any non session 0 is interactive.
//
// This assumption doesn't hold true if the user has changed the user that
// the OpenSSH Server service runs as (not a built-in NT service) *AND*
// they've SSH-ed into the Windows host (and then started a WSL shell).
// This feels like a very small subset of users...
//
if (WslUtils.GetWindowsSessionId(fs) == 0)
{
return false;
}

// If we are not in session 0, or we cannot get the Windows session ID,
// assume that we *CAN* launch the browser so that users are never blocked.
return true;
}

// We require an interactive desktop session to be able to launch a browser
return sm.IsDesktopSession;
}
Expand All @@ -39,7 +76,7 @@ public static void OpenDefaultBrowser(IEnvironment environment, Uri uri)

string url = uri.ToString();

ProcessStartInfo psi = null;
ProcessStartInfo psi;
if (PlatformUtils.IsLinux())
{
//
Expand All @@ -55,29 +92,17 @@ public static void OpenDefaultBrowser(IEnvironment environment, Uri uri)
// We try and use the same 'shell execute' utilities as the Framework does,
// searching for them in the same order until we find one.
//
// One additional 'shell execute' utility we also attempt to use is `wslview`
// that is commonly found on WSL (Windows Subsystem for Linux) distributions that
// opens the browser on the Windows host.
foreach (string shellExec in new[] {"xdg-open", "gnome-open", "kfmclient", "wslview"})
if (!TryGetLinuxShellExecuteHandler(environment, out string shellExecPath))
{
if (environment.TryLocateExecutable(shellExec, out string shellExecPath))
{
psi = new ProcessStartInfo(shellExecPath, url)
{
RedirectStandardOutput = true,
// Ok to redirect stderr for non-git-related processes
RedirectStandardError = true
};

// We found a way to open the URI; stop searching!
break;
}
throw new Exception("Failed to locate a utility to launch the default web browser.");
}

if (psi is null)
psi = new ProcessStartInfo(shellExecPath, url)
{
throw new Exception("Failed to locate a utility to launch the default web browser.");
}
RedirectStandardOutput = true,
// Ok to redirect stderr for non-git-related processes
RedirectStandardError = true
};
}
else
{
Expand All @@ -92,5 +117,23 @@ public static void OpenDefaultBrowser(IEnvironment environment, Uri uri)
// is no need to add the extra overhead associated with ChildProcess here.
Process.Start(psi);
}

private static bool TryGetLinuxShellExecuteHandler(IEnvironment env, out string shellExecPath)
{
// One additional 'shell execute' utility we also attempt to use over the Framework
// is `wslview` that is commonly found on WSL (Windows Subsystem for Linux) distributions
// that opens the browser on the Windows host.
string[] shellHandlers = { "xdg-open", "gnome-open", "kfmclient", WslUtils.WslViewShellHandlerName };
foreach (string shellExec in shellHandlers)
{
if (env.TryLocateExecutable(shellExec, out shellExecPath))
{
return true;
}
}

shellExecPath = null;
return false;
}
}
}
36 changes: 36 additions & 0 deletions src/shared/Core/WslUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ public static class WslUtils
private const string DefaultWslMountPrefix = "/mnt";
private const string DefaultWslSysDriveMountName = "c";

internal const string WslViewShellHandlerName = "wslview";

/// <summary>
/// Cached Windows host session ID.
/// </summary>
/// <remarks>A value less than 0 represents "unknown".</remarks>
private static int _windowsSessionId = -1;

/// <summary>
/// Cached WSL version.
/// </summary>
Expand Down Expand Up @@ -176,6 +184,34 @@ public static bool IsWslPath(string path)
return new Process { StartInfo = psi };
}

/// <summary>
/// Get the host Windows session ID.
/// </summary>
/// <returns>Windows session ID, or a negative value if it is not known.</returns>
public static int GetWindowsSessionId(IFileSystem fs)
{
if (_windowsSessionId < 0)
{
const string script = @"(Get-Process -ID $PID).SessionId";
using (Process proc = CreateWindowsShellProcess(fs, WindowsShell.PowerShell, script))
{
proc.Start();
proc.WaitForExit();

if (proc.ExitCode == 0)
{
string output = proc.StandardOutput.ReadToEnd().Trim();
if (int.TryParse(output, out int sessionId))
{
_windowsSessionId = sessionId;
}
}
}
}

return _windowsSessionId;
}

private static string GetSystemDriveMountPath(IFileSystem fs)
{
if (_sysDriveMountPath is null)
Expand Down

0 comments on commit 2e08a83

Please sign in to comment.