Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .pipelines/build-stage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ parameters:
- name: targets
type: object
default:
- target: "wsl;libwsl;wslg;wslservice;wslhost;wslrelay;wslinstaller;wslinstall;initramfs;wslserviceproxystub;wslsettings;wslinstallerproxystub;testplugin"
pattern: "wsl.exe,libwsl.dll,wslg.exe,wslservice.exe,wslhost.exe,wslrelay.exe,wslinstaller.exe,wslinstall.dll,wslserviceproxystub.dll,wslsettings/wslsettings.dll,wslsettings/wslsettings.exe,wslinstallerproxystub.dll,WSLDVCPlugin.dll,testplugin.dll,wsldeps.dll"
- target: "wsl;libwsl;wslg;wslservice;wslhost;wslrelay;wslpluginhost;wslinstaller;wslinstall;initramfs;wslserviceproxystub;wslsettings;wslinstallerproxystub;testplugin"
pattern: "wsl.exe,libwsl.dll,wslg.exe,wslservice.exe,wslhost.exe,wslrelay.exe,wslpluginhost.exe,wslinstaller.exe,wslinstall.dll,wslserviceproxystub.dll,wslsettings/wslsettings.dll,wslsettings/wslsettings.exe,wslinstallerproxystub.dll,WSLDVCPlugin.dll,testplugin.dll,wsldeps.dll"
- target: "msixgluepackage"
pattern: "gluepackage.msix"
- target: "msipackage"
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,7 @@ add_subdirectory(src/windows/wsl)
add_subdirectory(src/windows/wslg)
add_subdirectory(src/windows/wslhost)
add_subdirectory(src/windows/wslrelay)
add_subdirectory(src/windows/wslpluginhost)
add_subdirectory(src/windows/wslinstall)

if (WSL_BUILD_WSL_SETTINGS)
Expand Down
4 changes: 2 additions & 2 deletions msipackage/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ set(OUTPUT_PACKAGE ${BIN}/wsl.msi)
set(PACKAGE_WIX_IN ${CMAKE_CURRENT_LIST_DIR}/package.wix.in)
set(PACKAGE_WIX ${BIN}/package.wix)
set(CAB_CACHE ${BIN}/cab)
set(WINDOWS_BINARIES wsl.exe;wslg.exe;wslhost.exe;wslrelay.exe;wslservice.exe;wslserviceproxystub.dll;wslinstall.dll)
set(WINDOWS_BINARIES wsl.exe;wslg.exe;wslhost.exe;wslrelay.exe;wslpluginhost.exe;wslservice.exe;wslserviceproxystub.dll;wslinstall.dll)
if (WSL_BUILD_WSL_SETTINGS)
list(APPEND WINDOWS_BINARIES "wslsettings/wslsettings.dll;wslsettings/wslsettings.exe;libwsl.dll")
endif()
Expand Down Expand Up @@ -52,7 +52,7 @@ add_custom_command(

add_custom_target(msipackage DEPENDS ${OUTPUT_PACKAGE})
set_target_properties(msipackage PROPERTIES EXCLUDE_FROM_ALL FALSE SOURCES ${PACKAGE_WIX_IN})
add_dependencies(msipackage wsl wslg wslservice wslhost wslrelay wslserviceproxystub init initramfs wslinstall msixgluepackage)
add_dependencies(msipackage wsl wslg wslservice wslhost wslrelay wslpluginhost wslserviceproxystub init initramfs wslinstall msixgluepackage)

if (WSL_BUILD_WSL_SETTINGS)
add_dependencies(msipackage wslsettings libwsl)
Expand Down
30 changes: 30 additions & 0 deletions msipackage/package.wix.in
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<File Id="wslg.exe" Name="wslg.exe" Source="${PACKAGE_INPUT_DIR}/wslg.exe" />
<File Id="wslhost.exe" Name="wslhost.exe" Source="${PACKAGE_INPUT_DIR}/wslhost.exe" />
<File Id="wslrelay.exe" Name="wslrelay.exe" Source="${PACKAGE_INPUT_DIR}/wslrelay.exe" />
<File Id="wslpluginhost.exe" Name="wslpluginhost.exe" Source="${PACKAGE_INPUT_DIR}/wslpluginhost.exe" />
<File Id="wslserviceproxystub.dll" Name="wslserviceproxystub.dll" Source="${PACKAGE_INPUT_DIR}/wslserviceproxystub.dll" />
<File Id="wsldeps.dll" Name="wsldeps.dll" Source="${PACKAGE_INPUT_DIR}/wsldeps.dll" />

Expand Down Expand Up @@ -159,6 +160,35 @@
<RegistryValue Value='"[INSTALLDIR]wslhost.exe"' Type="string" />
</RegistryKey>

<!-- WslPluginHost - out-of-process plugin isolation (process spawned by service) -->
<!-- WslPluginHost AppID — SYSTEM-only activation -->
<!-- O:SYG:SYD:(A;;CCDCSW;;;SY) -->
<RegistryKey Root="HKCR" Key="AppID\{7a1d2c3e-4b5f-6a7d-8e9f-0a1b2c3d4e5f}">
<RegistryValue Name="AccessPermission" Value="010004801400000020000000000000002C00000001010000000000051200000001010000000000051200000002001C0001000000000014000B000000010100000000000512000000" Type="binary" />
<RegistryValue Name="LaunchPermission" Value="010004801400000020000000000000002C00000001010000000000051200000001010000000000051200000002001C0001000000000014000B000000010100000000000512000000" Type="binary" />
</RegistryKey>
<RegistryKey Root="HKCR" Key="CLSID\{7a1d2c3e-4b5f-6a7d-8e9f-0a1b2c3d4e5f}">
<RegistryValue Name="AppId" Value="{7a1d2c3e-4b5f-6a7d-8e9f-0a1b2c3d4e5f}" Type="string" />
<RegistryValue Value="WslPluginHost" Type="string" />
</RegistryKey>
<RegistryKey Root="HKCR" Key="CLSID\{7a1d2c3e-4b5f-6a7d-8e9f-0a1b2c3d4e5f}\LocalServer32">
<RegistryValue Value='"[INSTALLDIR]wslpluginhost.exe"' Type="string" />
</RegistryKey>

<!-- WslPluginHost interfaces — use the shared wslserviceproxystub.dll -->
<RegistryKey Root="HKCR" Key="Interface\{A2B3C4D5-E6F7-4890-AB12-CD34EF56A789}">
<RegistryValue Value="IWslPluginHostCallback" Type="string" />
<RegistryKey Key="ProxyStubClsid32">
<RegistryValue Value="{4EA0C6DD-E9FF-48E7-994E-13A31D10DC60}" Type="string" />
</RegistryKey>
</RegistryKey>
<RegistryKey Root="HKCR" Key="Interface\{B3C4D5E6-F7A8-4901-BC23-DE45FA67B890}">
<RegistryValue Value="IWslPluginHost" Type="string" />
<RegistryKey Key="ProxyStubClsid32">
<RegistryValue Value="{4EA0C6DD-E9FF-48E7-994E-13A31D10DC60}" Type="string" />
</RegistryKey>
</RegistryKey>

<!-- wsldevicehost.dll -->
<RegistryKey Root="HKCR" Key="AppID\{17696EAC-9568-4CF5-BB8C-82515AAD6C09}">
<RegistryValue Name="DllSurrogate" Value="" Type="string" />
Expand Down
1 change: 1 addition & 0 deletions src/windows/common/precomp.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ Module Name:
#include <optional>
#include <filesystem>
#include <mutex>
#include <shared_mutex>
#include <chrono>
#include <codecvt>
#include <random>
Expand Down
2 changes: 1 addition & 1 deletion src/windows/service/exe/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ set(HEADERS
WslCoreVm.h)

add_executable(wslservice ${SOURCES} ${HEADERS})
add_dependencies(wslservice wslserviceidl wslservicemc)
add_dependencies(wslservice wslserviceidl wslservicemc wslpluginhostidl)
add_compile_definitions(__WRL_CLASSIC_COM__)
add_compile_definitions(__WRL_DISABLE_STATIC_INITIALIZE__)
add_compile_definitions(USE_COM_CONTEXT_DEF=1)
Expand Down
65 changes: 48 additions & 17 deletions src/windows/service/exe/LxssUserSession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2604,13 +2604,18 @@ std::shared_ptr<LxssRunningInstance> LxssUserSessionImpl::_CreateInstance(_In_op
registration.Write(Property::OsVersion, distributionInfo->Version);
}

// This needs to be done before plugins are notifed because they might try to run a command inside the distribution.
m_runningInstances[registration.Id()] = instance;
// This needs to be done before plugins are notified because they might try to run a command inside the distribution.
{
std::unique_lock callbackLock(m_callbackLock);
m_runningInstances[registration.Id()] = instance;
}

if (version == LXSS_WSL_VERSION_2)
{
auto cleanupOnFailure =
wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { m_runningInstances.erase(registration.Id()); });
auto cleanupOnFailure = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
std::unique_lock callbackLock(m_callbackLock);
m_runningInstances.erase(registration.Id());
});
m_pluginManager.OnDistributionStarted(&m_session, instance->DistributionInformation());
cleanupOnFailure.release();
}
Expand Down Expand Up @@ -3577,17 +3582,26 @@ bool LxssUserSessionImpl::_TerminateInstanceInternal(_In_ LPCGUID DistroGuid, _I
m_pluginManager.OnDistributionStopping(&m_session, wslcoreInstance->DistributionInformation());
}

instance->second->Stop();
m_lifetimeManager.RemoveCallback(clientKey);

const auto clientId = instance->second->GetClientId();
// Stop the instance and remove it from m_runningInstances atomically
// under m_callbackLock. This prevents plugin callbacks (which hold
// m_callbackLock shared) from finding a stopped-but-still-listed
// instance between Stop() and erase.
ULONG clientId;
{
auto lock = m_terminatedInstanceLock.lock_exclusive();
m_terminatedInstances.push_back(std::move(instance->second));
}
std::unique_lock callbackLock(m_callbackLock);

m_lifetimeManager.RemoveCallback(clientKey);
instance->second->Stop();
clientId = instance->second->GetClientId();

{
auto lock = m_terminatedInstanceLock.lock_exclusive();
m_terminatedInstances.push_back(std::move(instance->second));
}

m_runningInstances.erase(instance);
m_runningInstances.erase(instance);
}
Comment thread
benhillis marked this conversation as resolved.

// If the instance that was terminated was a WSL2 instance,
// check if the VM is now idle.
Expand Down Expand Up @@ -3615,7 +3629,10 @@ void LxssUserSessionImpl::_UpdateInit(_In_ const LXSS_DISTRO_CONFIGURATION& Conf

HRESULT LxssUserSessionImpl::MountRootNamespaceFolder(_In_ LPCWSTR HostPath, _In_ LPCWSTR GuestPath, _In_ bool ReadOnly, _In_ LPCWSTR Name)
{
std::lock_guard lock(m_instanceLock);
// Shared lock prevents _VmTerminate from destroying the VM while we use it.
// Do NOT acquire m_instanceLock — callbacks arrive on a different COM thread
// from the notification thread that holds m_instanceLock.
std::shared_lock lock(m_callbackLock);
RETURN_HR_IF(E_NOT_VALID_STATE, !m_utilityVm);

m_utilityVm->MountRootNamespaceFolder(HostPath, GuestPath, ReadOnly, Name);
Expand All @@ -3624,7 +3641,9 @@ HRESULT LxssUserSessionImpl::MountRootNamespaceFolder(_In_ LPCWSTR HostPath, _In

HRESULT LxssUserSessionImpl::CreateLinuxProcess(_In_opt_ const GUID* Distro, _In_ LPCSTR Path, _In_ LPCSTR* Arguments, _Out_ SOCKET* Socket)
{
std::lock_guard lock(m_instanceLock);
// Shared lock prevents _VmTerminate from destroying the VM or instances
// while we use them. See MountRootNamespaceFolder for rationale.
std::shared_lock lock(m_callbackLock);
RETURN_HR_IF(E_NOT_VALID_STATE, !m_utilityVm);
Comment thread
benhillis marked this conversation as resolved.

if (Distro == nullptr)
Comment thread
benhillis marked this conversation as resolved.
Expand All @@ -3633,9 +3652,16 @@ HRESULT LxssUserSessionImpl::CreateLinuxProcess(_In_opt_ const GUID* Distro, _In
}
else
{
const auto distro = _RunningInstance(Distro);
THROW_HR_IF(WSL_E_VM_MODE_INVALID_STATE, !distro);

// Look up the running instance directly instead of calling _RunningInstance,
// which accesses m_lockedDistributions (guarded only by m_instanceLock).
// m_runningInstances is safe to read under m_callbackLock (shared).
// The _EnsureNotLocked check is unnecessary here: _ConversionBegin removes
// a distribution from m_runningInstances before adding it to m_lockedDistributions,
// so a locked distribution will never be found in this lookup.
const auto instance = m_runningInstances.find(*Distro);
THROW_HR_IF(WSL_E_VM_MODE_INVALID_STATE, instance == m_runningInstances.end());

const auto distro = instance->second;
const auto wsl2Distro = dynamic_cast<WslCoreInstance*>(distro.get());
THROW_HR_IF(WSL_E_WSL2_NEEDED, !wsl2Distro);

Expand Down Expand Up @@ -3871,7 +3897,12 @@ void LxssUserSessionImpl::_VmTerminate()
m_telemetryThread.join();
}

m_utilityVm.reset();
// Acquire exclusive callback lock to wait for any in-flight plugin callbacks
// (MountRootNamespaceFolder, CreateLinuxProcess) to complete before destroying the VM.
{
std::unique_lock callbackLock(m_callbackLock);
m_utilityVm.reset();
}
m_vmId.store(GUID_NULL);

// Reset the user's token since its lifetime is tied to the VM.
Expand Down
44 changes: 35 additions & 9 deletions src/windows/service/exe/LxssUserSession.h
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,10 @@ class DECLSPEC_UUID("a9b7a1b9-0671-405c-95f1-e0612cb4ce7e") LxssUserSession
/// </summary>
class LxssUserSessionImpl
{
// Plugin callbacks arrive on a different COM RPC thread and use m_callbackLock
// (shared) instead of m_instanceLock to access m_utilityVm and m_runningInstances.
friend class wsl::windows::service::PluginHostCallbackImpl;

public:
LxssUserSessionImpl(_In_ PSID userSid, _In_ DWORD sessionId, _Inout_ wsl::windows::service::PluginManager& pluginManager);
virtual ~LxssUserSessionImpl();
Expand Down Expand Up @@ -363,11 +367,6 @@ class LxssUserSessionImpl
/// </summary>
void ClearDiskStateInRegistry(_In_opt_ LPCWSTR Disk);

/// <summary>
/// Start a process in the root namespace or in a user distribution.
/// </summary>
HRESULT CreateLinuxProcess(_In_opt_ const GUID* Distro, _In_ LPCSTR Path, _In_ LPCSTR* Arguments, _Out_ SOCKET* socket);

/// <summary>
/// Enumerates registered distributions, optionally including ones that are
/// currently being registered, unregistered, or converted.
Expand Down Expand Up @@ -443,8 +442,6 @@ class LxssUserSessionImpl

HRESULT MoveDistribution(_In_ LPCGUID DistroGuid, _In_ LPCWSTR Location);

HRESULT MountRootNamespaceFolder(_In_ LPCWSTR HostPath, _In_ LPCWSTR GuestPath, _In_ bool ReadOnly, _In_ LPCWSTR Name);

/// <summary>
/// Registers a distribution.
/// </summary>
Expand Down Expand Up @@ -533,6 +530,18 @@ class LxssUserSessionImpl
static CreateLxProcessContext s_GetCreateProcessContext(_In_ const GUID& DistroGuid, _In_ bool SystemDistro);

private:
/// <summary>
/// Plugin callback methods — called from PluginHostCallbackImpl on a COM RPC
/// thread during plugin notifications. These acquire m_callbackLock (shared)
/// instead of m_instanceLock, preventing _VmTerminate from destroying the VM
/// while a callback is in-flight. Access is restricted via friend declaration.
/// </summary>
_Requires_lock_not_held_(m_instanceLock)
HRESULT MountRootNamespaceFolder(_In_ LPCWSTR HostPath, _In_ LPCWSTR GuestPath, _In_ bool ReadOnly, _In_ LPCWSTR Name);

_Requires_lock_not_held_(m_instanceLock)
HRESULT CreateLinuxProcess(_In_opt_ const GUID* Distro, _In_ LPCSTR Path, _In_ LPCSTR* Arguments, _Out_ SOCKET* socket);

/// <summary>
/// Adds a distro to the list of converting distros.
/// </summary>
Expand Down Expand Up @@ -794,7 +803,9 @@ class LxssUserSessionImpl
std::recursive_timed_mutex m_instanceLock;

/// <summary>
/// Contains the currently running utility VM's.
/// Contains the currently running instances.
/// Reads guarded by m_instanceLock OR m_callbackLock (shared).
/// Mutations require BOTH m_instanceLock AND m_callbackLock (exclusive).
/// </summary>
_Guarded_by_(m_instanceLock) std::map<GUID, std::shared_ptr<LxssRunningInstance>, wsl::windows::common::helpers::GuidLess> m_runningInstances;
Comment thread
benhillis marked this conversation as resolved.

Expand All @@ -811,9 +822,24 @@ class LxssUserSessionImpl

/// <summary>
/// The running utility vm for WSL2 distributions.
///
/// Reads guarded by m_instanceLock OR m_callbackLock (shared).
/// Mutations require BOTH m_instanceLock AND m_callbackLock (exclusive).
/// </summary>
_Guarded_by_(m_instanceLock) std::unique_ptr<WslCoreVm> m_utilityVm;

/// <summary>
/// Reader-writer lock protecting m_utilityVm and m_runningInstances for
/// plugin callbacks. Callbacks take a shared (read) lock; _VmTerminate and
/// instance mutations take an exclusive (write) lock.
///
/// Mutations of m_runningInstances/m_utilityVm require BOTH m_instanceLock
/// AND m_callbackLock (exclusive). Reads are safe under either lock alone.
///
/// Lock ordering: m_instanceLock → m_callbackLock (never reverse).
/// Callbacks must NEVER acquire m_instanceLock (deadlock with notification thread).
Comment thread
benhillis marked this conversation as resolved.
/// </summary>
std::shared_mutex m_callbackLock;
Comment thread
benhillis marked this conversation as resolved.

std::atomic<GUID> m_vmId{GUID_NULL};

/// <summary>
Expand Down
Loading
Loading