-
Notifications
You must be signed in to change notification settings - Fork 1.7k
wslc: prevent session name squatting for default WSLc sessions #40144
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feature/wsl-for-apps
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| /*++ | ||
|
|
||
| Copyright (c) Microsoft. All rights reserved. | ||
|
|
||
| Module Name: | ||
|
|
||
| WSLCSessionDefaults.h | ||
|
|
||
| Abstract: | ||
|
|
||
| Shared constants for WSLc session naming and storage. | ||
|
|
||
| --*/ | ||
| #pragma once | ||
|
|
||
| #include <cstdint> | ||
|
|
||
| namespace wsl::windows::wslc { | ||
|
|
||
| inline constexpr const wchar_t DefaultSessionName[] = L"wslc-cli"; | ||
| inline constexpr const wchar_t DefaultAdminSessionName[] = L"wslc-cli-admin"; | ||
| inline constexpr const wchar_t DefaultStorageSubPath[] = L"wslc\\sessions"; | ||
| inline constexpr uint32_t DefaultBootTimeoutMs = 30000; | ||
|
|
||
| } // namespace wsl::windows::wslc |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,12 +29,71 @@ Module Name: | |
|
|
||
| #include "WSLCSessionManager.h" | ||
| #include "HcsVirtualMachine.h" | ||
| #include "WSLCUserSettings.h" | ||
| #include "WSLCSessionDefaults.h" | ||
| #include "wslutil.h" | ||
| #include "filesystem.hpp" | ||
|
|
||
| using wsl::windows::service::wslc::CallingProcessTokenInfo; | ||
| using wsl::windows::service::wslc::HcsVirtualMachine; | ||
| using wsl::windows::service::wslc::WSLCSessionManagerImpl; | ||
| namespace wslutil = wsl::windows::common::wslutil; | ||
| namespace settings = wsl::windows::wslc::settings; | ||
|
|
||
| namespace { | ||
|
|
||
| // Session settings built server-side from the caller's settings.yaml. | ||
| struct SessionSettings | ||
| { | ||
| std::wstring DisplayName; | ||
| std::wstring StoragePath; | ||
| WSLCSessionSettings Settings{}; | ||
|
|
||
| NON_COPYABLE(SessionSettings); | ||
| NON_MOVABLE(SessionSettings); | ||
|
|
||
| // Default session: name and storage path determined from caller's token. | ||
| static std::unique_ptr<SessionSettings> Default(HANDLE UserToken, bool Elevated) | ||
| { | ||
| auto localAppData = wsl::windows::common::filesystem::GetLocalAppDataPath(UserToken); | ||
| settings::UserSettings userSettings(localAppData / L"wslc"); | ||
|
|
||
| std::wstring name = Elevated ? wsl::windows::wslc::DefaultAdminSessionName : wsl::windows::wslc::DefaultSessionName; | ||
|
|
||
| auto customPath = userSettings.Get<settings::Setting::SessionStoragePath>(); | ||
| std::filesystem::path basePath = | ||
| customPath.empty() ? (localAppData / wsl::windows::wslc::DefaultStorageSubPath) : std::filesystem::path{customPath}; | ||
| auto storagePath = (basePath / name).wstring(); | ||
|
|
||
| return std::unique_ptr<SessionSettings>( | ||
| new SessionSettings(std::move(name), std::move(storagePath), WSLCSessionStorageFlagsNone, userSettings)); | ||
| } | ||
|
|
||
| // Custom session: caller provides name and storage path. | ||
| static SessionSettings Custom(HANDLE UserToken, LPCWSTR Name, LPCWSTR Path, WSLCSessionStorageFlags StorageFlags = WSLCSessionStorageFlagsNone) | ||
| { | ||
| auto localAppData = wsl::windows::common::filesystem::GetLocalAppDataPath(UserToken); | ||
| settings::UserSettings userSettings(localAppData / L"wslc"); | ||
| return SessionSettings(Name, Path, StorageFlags, userSettings); | ||
| } | ||
|
|
||
| private: | ||
| SessionSettings(std::wstring name, std::wstring path, WSLCSessionStorageFlags storageFlags, const settings::UserSettings& userSettings) : | ||
| DisplayName(std::move(name)), StoragePath(std::move(path)) | ||
| { | ||
| Settings.DisplayName = DisplayName.c_str(); | ||
| Settings.StoragePath = StoragePath.c_str(); | ||
| Settings.CpuCount = userSettings.Get<settings::Setting::SessionCpuCount>(); | ||
| Settings.MemoryMb = userSettings.Get<settings::Setting::SessionMemoryMb>(); | ||
| Settings.MaximumStorageSizeMb = userSettings.Get<settings::Setting::SessionStorageSizeMb>(); | ||
| Settings.BootTimeoutMs = wsl::windows::wslc::DefaultBootTimeoutMs; | ||
| Settings.NetworkingMode = userSettings.Get<settings::Setting::SessionNetworkingMode>(); | ||
| Settings.FeatureFlags = userSettings.BuildFeatureFlags(); | ||
| Settings.StorageFlags = storageFlags; | ||
| } | ||
| }; | ||
|
|
||
| } // namespace | ||
|
|
||
| WSLCSessionManagerImpl::~WSLCSessionManagerImpl() | ||
| { | ||
|
|
@@ -51,22 +110,42 @@ WSLCSessionManagerImpl::~WSLCSessionManagerImpl() | |
|
|
||
| void WSLCSessionManagerImpl::CreateSession(const WSLCSessionSettings* Settings, WSLCSessionFlags Flags, IWSLCSession** WslcSession) | ||
| { | ||
| // Ensure that the session display name is non-null and not too long. | ||
| THROW_HR_IF(E_INVALIDARG, Settings->DisplayName == nullptr); | ||
| THROW_HR_IF(E_INVALIDARG, wcslen(Settings->DisplayName) >= std::size(WSLCSessionInformation{}.DisplayName)); | ||
| THROW_HR_IF_MSG( | ||
| E_INVALIDARG, | ||
| WI_IsAnyFlagSet(Settings->StorageFlags, ~WSLCSessionStorageFlagsValid), | ||
| "Invalid storage flags: %i", | ||
| Settings->StorageFlags); | ||
|
|
||
| auto tokenInfo = GetCallingProcessTokenInfo(); | ||
| const auto callerToken = wsl::windows::common::security::GetUserToken(TokenImpersonation); | ||
|
|
||
| // Resolve display name upfront (for both default and custom sessions). | ||
| std::wstring resolvedDisplayName; | ||
| if (Settings == nullptr) | ||
| { | ||
| // Default session: name determined from token. | ||
| resolvedDisplayName = tokenInfo.Elevated ? wsl::windows::wslc::DefaultAdminSessionName : wsl::windows::wslc::DefaultSessionName; | ||
| Flags = WSLCSessionFlagsOpenExisting | WSLCSessionFlagsPersistent; | ||
| } | ||
benhillis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| else | ||
| { | ||
| THROW_HR_IF(E_INVALIDARG, Settings->DisplayName == nullptr || wcslen(Settings->DisplayName) == 0); | ||
| THROW_HR_IF(E_INVALIDARG, Settings->StoragePath != nullptr && wcslen(Settings->StoragePath) == 0); | ||
| THROW_HR_IF(E_INVALIDARG, wcslen(Settings->DisplayName) >= std::size(WSLCSessionInformation{}.DisplayName)); | ||
| THROW_HR_IF_MSG( | ||
| E_INVALIDARG, | ||
| WI_IsAnyFlagSet(Settings->StorageFlags, ~WSLCSessionStorageFlagsValid), | ||
| "Invalid storage flags: %i", | ||
| Settings->StorageFlags); | ||
|
|
||
benhillis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // Reserved names can only be assigned server-side via null Settings. | ||
| THROW_HR_IF( | ||
| E_ACCESSDENIED, | ||
| wsl::shared::string::IsEqual(Settings->DisplayName, wsl::windows::wslc::DefaultSessionName, true) || | ||
| wsl::shared::string::IsEqual(Settings->DisplayName, wsl::windows::wslc::DefaultAdminSessionName, true)); | ||
|
|
||
| resolvedDisplayName = Settings->DisplayName; | ||
| } | ||
|
|
||
| std::lock_guard lock(m_wslcSessionsLock); | ||
|
|
||
| // Check for an existing session first. | ||
| auto result = ForEachSession<HRESULT>([&](auto& entry, const wil::com_ptr<IWSLCSession>& session) noexcept -> std::optional<HRESULT> { | ||
| if (!wsl::shared::string::IsEqual(entry.DisplayName.c_str(), Settings->DisplayName)) | ||
| if (!wsl::shared::string::IsEqual(entry.DisplayName.c_str(), resolvedDisplayName.c_str())) | ||
| { | ||
| return {}; | ||
| } | ||
|
|
@@ -88,6 +167,14 @@ void WSLCSessionManagerImpl::CreateSession(const WSLCSessionSettings* Settings, | |
|
|
||
| wslutil::StopWatch stopWatch; | ||
|
|
||
| // Initialize settings for the default session. | ||
| std::unique_ptr<SessionSettings> defaultSettings; | ||
| if (Settings == nullptr) | ||
| { | ||
| defaultSettings = SessionSettings::Default(callerToken.get(), tokenInfo.Elevated); | ||
| Settings = &defaultSettings->Settings; | ||
| } | ||
|
Comment on lines
144
to
+176
|
||
|
|
||
| HRESULT creationResult = wil::ResultFromException([&]() { | ||
| // Get caller info. | ||
| const auto callerProcess = wslutil::OpenCallingProcess(PROCESS_QUERY_LIMITED_INFORMATION); | ||
|
|
@@ -103,13 +190,13 @@ void WSLCSessionManagerImpl::CreateSession(const WSLCSessionSettings* Settings, | |
| AddSessionProcessToJobObject(factory.get()); | ||
|
|
||
| // Create the session via the factory. | ||
| const auto sessionSettings = CreateSessionSettings(sessionId, creatorPid, Settings); | ||
| const auto sessionSettings = CreateSessionSettings(sessionId, creatorPid, Settings, resolvedDisplayName.c_str()); | ||
| wil::com_ptr<IWSLCSession> session; | ||
| wil::com_ptr<IWSLCSessionReference> serviceRef; | ||
| THROW_IF_FAILED(factory->CreateSession(&sessionSettings, vm.Get(), &session, &serviceRef)); | ||
|
|
||
| // Track the session via its service ref, along with metadata and security info. | ||
| m_sessions.push_back({std::move(serviceRef), sessionId, creatorPid, Settings->DisplayName, std::move(tokenInfo)}); | ||
| m_sessions.push_back({std::move(serviceRef), sessionId, creatorPid, resolvedDisplayName, std::move(tokenInfo)}); | ||
|
|
||
| // For persistent sessions, also hold a strong reference to keep them alive. | ||
| const bool persistent = WI_IsFlagSet(Flags, WSLCSessionFlagsPersistent); | ||
|
|
@@ -122,19 +209,18 @@ void WSLCSessionManagerImpl::CreateSession(const WSLCSessionSettings* Settings, | |
| }); | ||
|
|
||
| // This telemetry event is used to keep track of session creation performance (via CreationTimeMs) and failure reasons (via Result). | ||
|
|
||
| WSL_LOG_TELEMETRY( | ||
| "WSLCCreateSession", | ||
| PDT_ProductAndServicePerformance, | ||
| TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA), | ||
| TraceLoggingValue(Settings->DisplayName, "Name"), | ||
| TraceLoggingValue(resolvedDisplayName.c_str(), "Name"), | ||
| TraceLoggingValue(stopWatch.ElapsedMilliseconds(), "CreationTimeMs"), | ||
| TraceLoggingValue(creationResult, "Result"), | ||
| TraceLoggingValue(tokenInfo.Elevated, "Elevated"), | ||
| TraceLoggingValue(static_cast<uint32_t>(Flags), "Flags"), | ||
| TraceLoggingLevel(WINEVENT_LEVEL_INFO)); | ||
|
|
||
| THROW_IF_FAILED_MSG(creationResult, "Failed to create session: %ls", Settings->DisplayName); | ||
| THROW_IF_FAILED_MSG(creationResult, "Failed to create session: %ls", resolvedDisplayName.c_str()); | ||
| } | ||
|
|
||
| void WSLCSessionManagerImpl::OpenSession(ULONG Id, IWSLCSession** Session) | ||
|
|
@@ -160,6 +246,14 @@ void WSLCSessionManagerImpl::OpenSessionByName(LPCWSTR DisplayName, IWSLCSession | |
| { | ||
| auto tokenInfo = GetCallingProcessTokenInfo(); | ||
|
|
||
| // Null name = default session, resolved from caller's token. | ||
| std::wstring resolvedName; | ||
| if (DisplayName == nullptr) | ||
| { | ||
| resolvedName = tokenInfo.Elevated ? wsl::windows::wslc::DefaultAdminSessionName : wsl::windows::wslc::DefaultSessionName; | ||
| DisplayName = resolvedName.c_str(); | ||
| } | ||
|
|
||
| auto result = ForEachSession<HRESULT>([&](auto& entry, const wil::com_ptr<IWSLCSession>& session) noexcept -> std::optional<HRESULT> { | ||
| if (!wsl::shared::string::IsEqual(entry.DisplayName.c_str(), DisplayName)) | ||
| { | ||
|
|
@@ -207,12 +301,23 @@ void WSLCSessionManagerImpl::GetVersion(_Out_ WSLCVersion* Version) | |
| Version->Revision = WSL_PACKAGE_VERSION_REVISION; | ||
| } | ||
|
|
||
| WSLCSessionInitSettings WSLCSessionManagerImpl::CreateSessionSettings(_In_ ULONG SessionId, _In_ DWORD CreatorPid, _In_ const WSLCSessionSettings* Settings) | ||
| void WSLCSessionManagerImpl::EnterSession(_In_ LPCWSTR DisplayName, _In_ LPCWSTR StoragePath, IWSLCSession** WslcSession) | ||
| { | ||
benhillis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| THROW_HR_IF(E_POINTER, DisplayName == nullptr || StoragePath == nullptr); | ||
| THROW_HR_IF(E_INVALIDARG, DisplayName[0] == L'\0' || StoragePath[0] == L'\0'); | ||
|
|
||
| const auto callerToken = wsl::windows::common::security::GetUserToken(TokenImpersonation); | ||
| auto sessionSettings = SessionSettings::Custom(callerToken.get(), DisplayName, StoragePath, WSLCSessionStorageFlagsNoCreate); | ||
| CreateSession(&sessionSettings.Settings, WSLCSessionFlagsNone, WslcSession); | ||
| } | ||
|
|
||
| WSLCSessionInitSettings WSLCSessionManagerImpl::CreateSessionSettings( | ||
| _In_ ULONG SessionId, _In_ DWORD CreatorPid, _In_ const WSLCSessionSettings* Settings, _In_ LPCWSTR ResolvedDisplayName) | ||
| { | ||
| WSLCSessionInitSettings sessionSettings{}; | ||
| sessionSettings.SessionId = SessionId; | ||
| sessionSettings.CreatorPid = CreatorPid; | ||
| sessionSettings.DisplayName = Settings->DisplayName; | ||
| sessionSettings.DisplayName = ResolvedDisplayName; | ||
| sessionSettings.StoragePath = Settings->StoragePath; | ||
| sessionSettings.MaximumStorageSizeMb = Settings->MaximumStorageSizeMb; | ||
| sessionSettings.BootTimeoutMs = Settings->BootTimeoutMs; | ||
|
|
@@ -292,6 +397,11 @@ HRESULT WSLCSessionManager::CreateSession(const WSLCSessionSettings* WslcSession | |
| return CallImpl(&WSLCSessionManagerImpl::CreateSession, WslcSessionSettings, Flags, WslcSession); | ||
| } | ||
|
|
||
| HRESULT WSLCSessionManager::EnterSession(_In_ LPCWSTR DisplayName, _In_ LPCWSTR StoragePath, IWSLCSession** WslcSession) | ||
| { | ||
| return CallImpl(&WSLCSessionManagerImpl::EnterSession, DisplayName, StoragePath, WslcSession); | ||
| } | ||
|
|
||
| HRESULT WSLCSessionManager::ListSessions(_Out_ WSLCSessionInformation** Sessions, _Out_ ULONG* SessionsCount) | ||
| { | ||
| return CallImpl(&WSLCSessionManagerImpl::ListSessions, Sessions, SessionsCount); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New user-facing resource keys were added only to
en-US/Resources.resw. Other locales currently lack these entries (the localization validator only warns), which can cause missing translations/fallback-to-English behavior for non-en-US users. Consider adding placeholder entries for these keys in the other locale.reswfiles so localization can pick them up consistently.