Skip to content

Commit 34d21ae

Browse files
Ben HillisCopilot
andcommitted
Prevent session name squatting for default WSLc sessions
- Server now determines default session name and settings from caller's token, preventing malicious users from squatting reserved session names - CreateSession rejects explicit use of reserved names (wslc-cli, wslc-cli-admin) with case-insensitive E_ACCESSDENIED check - Null StoragePath remains valid for ephemeral sessions; empty string is rejected as E_INVALIDARG - Add dedicated EnterSession API with null/empty parameter validation - Early return optimization: skip YAML parse if default session exists - Inline BuildFeatureFlags into SessionSettings constructor - Extract UserSettings into shared wslcsettings library used by both wslc.exe and wslservice.exe - Move EnumVariantMap.h to common - Fix std::terminate crash in CustomDmesgOutput test when CreateSession fails by adding a scope_exit guard to join the reader thread - Add session name squatting E2E test with case-insensitive coverage Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent ecfa443 commit 34d21ae

25 files changed

+361
-207
lines changed

localization/strings/en-US/Resources.resw

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1935,6 +1935,15 @@ Usage:
19351935
<value>Session termination failed: '{}'</value>
19361936
<comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
19371937
</data>
1938+
<data name="MessageWslcDefaultSessionNotFound" xml:space="preserve">
1939+
<value>Default session not found</value>
1940+
</data>
1941+
<data name="MessageWslcOpenDefaultSessionFailed" xml:space="preserve">
1942+
<value>Failed to open default session</value>
1943+
</data>
1944+
<data name="MessageWslcTerminateDefaultSessionFailed" xml:space="preserve">
1945+
<value>Default session termination failed</value>
1946+
</data>
19381947
<data name="MessageWslcShellExited" xml:space="preserve">
19391948
<value>{} exited with: {}</value>
19401949
<comment>{FixedPlaceholder="{}"}{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>

src/windows/common/CMakeLists.txt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ set(SOURCES
4949
WslTelemetry.cpp
5050
wslutil.cpp
5151
install.cpp
52+
WSLCUserSettings.cpp
5253
)
5354

5455
set(HEADERS
@@ -130,11 +131,19 @@ set(HEADERS
130131
WslSecurity.h
131132
WslTelemetry.h
132133
wslutil.h
134+
EnumVariantMap.h
135+
WSLCUserSettings.h
136+
WSLCSessionDefaults.h
133137
)
134138

135139
add_library(common STATIC ${SOURCES} ${HEADERS})
136-
add_dependencies(common wslserviceidl localization wslservicemc wslinstalleridl)
140+
add_dependencies(common wslserviceidl localization wslservicemc wslinstalleridl yaml-cpp)
137141

138142
target_precompile_headers(common PRIVATE precomp.h)
139143
set_target_properties(common PROPERTIES FOLDER windows)
140144
target_include_directories(common PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/../service/mc/${TARGET_PLATFORM}/${CMAKE_BUILD_TYPE})
145+
146+
# WSLCUserSettings.cpp uses yaml-cpp headers.
147+
set_source_files_properties(WSLCUserSettings.cpp PROPERTIES
148+
INCLUDE_DIRECTORIES "${yaml-cpp_SOURCE_DIR}/include"
149+
COMPILE_DEFINITIONS "YAML_CPP_STATIC_DEFINE")
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*++
2+
3+
Copyright (c) Microsoft. All rights reserved.
4+
5+
Module Name:
6+
7+
WSLCSessionDefaults.h
8+
9+
Abstract:
10+
11+
Shared constants for WSLc session naming and storage.
12+
13+
--*/
14+
#pragma once
15+
16+
#include <cstdint>
17+
18+
namespace wsl::windows::wslc {
19+
20+
inline constexpr const wchar_t DefaultSessionName[] = L"wslc-cli";
21+
inline constexpr const wchar_t DefaultAdminSessionName[] = L"wslc-cli-admin";
22+
inline constexpr const wchar_t DefaultStorageSubPath[] = L"wslc\\sessions";
23+
inline constexpr uint32_t DefaultBootTimeoutMs = 30000;
24+
25+
} // namespace wsl::windows::wslc

src/windows/wslc/settings/UserSettings.cpp renamed to src/windows/common/WSLCUserSettings.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,23 @@ Copyright (c) Microsoft. All rights reserved.
44
55
Module Name:
66
7-
UserSettings.cpp
7+
WSLCUserSettings.cpp
88
99
Abstract:
1010
1111
Implementation of UserSettings — YAML loading and validation.
1212
1313
--*/
14-
#include "UserSettings.h"
14+
#include "precomp.h"
15+
#include "WSLCUserSettings.h"
1516
#include "filesystem.hpp"
1617
#include "string.hpp"
1718
#include "wslutil.h"
19+
20+
#pragma warning(push)
21+
#pragma warning(disable : 4251 4275)
1822
#include <yaml-cpp/yaml.h>
23+
#pragma warning(pop)
1924
#include <algorithm>
2025
#include <format>
2126
#include <fstream>
@@ -25,7 +30,6 @@ using namespace wsl::windows::common::string;
2530

2631
namespace wsl::windows::wslc::settings {
2732

28-
// Default settings file template — written on first run.
2933
// All entries are commented out; the values shown are the built-in defaults.
3034
// TODO: localization for comments needed?
3135
static constexpr std::string_view s_DefaultSettingsTemplate =
Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Copyright (c) Microsoft. All rights reserved.
44
55
Module Name:
66
7-
UserSettings.h
7+
WSLCUserSettings.h
88
99
Abstract:
1010
@@ -156,9 +156,7 @@ class UserSettings
156156
// Overwrites the settings file with the commented-out defaults template.
157157
void Reset() const;
158158

159-
protected:
160-
// Loads settings from an explicit directory. Used by the singleton (via
161-
// the private zero-arg constructor) and by test subclasses.
159+
// Loads settings from an explicit directory.
162160
explicit UserSettings(const std::filesystem::path& settingsDir);
163161
~UserSettings() = default;
164162

src/windows/service/exe/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ target_link_libraries(wslservice
7171
legacy_stdio_definitions
7272
VirtDisk.lib
7373
Winhttp.lib
74-
Synchronization.lib)
74+
Synchronization.lib
75+
yaml-cpp)
7576

7677
target_precompile_headers(wslservice REUSE_FROM common)
7778
set_target_properties(wslservice PROPERTIES FOLDER windows)

src/windows/service/exe/WSLCSessionManager.cpp

Lines changed: 129 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,73 @@ Module Name:
2929

3030
#include "WSLCSessionManager.h"
3131
#include "HcsVirtualMachine.h"
32+
#include "WSLCUserSettings.h"
33+
#include "WSLCSessionDefaults.h"
3234
#include "wslutil.h"
35+
#include "filesystem.hpp"
3336

3437
using wsl::windows::service::wslc::CallingProcessTokenInfo;
3538
using wsl::windows::service::wslc::HcsVirtualMachine;
3639
using wsl::windows::service::wslc::WSLCSessionManagerImpl;
3740
namespace wslutil = wsl::windows::common::wslutil;
41+
namespace settings = wsl::windows::wslc::settings;
42+
43+
namespace {
44+
45+
// Session settings built server-side from the caller's settings.yaml.
46+
struct SessionSettings
47+
{
48+
std::wstring DisplayName;
49+
std::wstring StoragePath;
50+
WSLCSessionSettings Settings{};
51+
52+
NON_COPYABLE(SessionSettings);
53+
NON_MOVABLE(SessionSettings);
54+
55+
// Default session: name and storage path determined from caller's token.
56+
static std::unique_ptr<SessionSettings> Default(HANDLE UserToken, bool Elevated)
57+
{
58+
auto localAppData = wsl::windows::common::filesystem::GetLocalAppDataPath(UserToken);
59+
settings::UserSettings userSettings(localAppData / L"wslc");
60+
61+
std::wstring name = Elevated ? wsl::windows::wslc::DefaultAdminSessionName : wsl::windows::wslc::DefaultSessionName;
62+
63+
auto customPath = userSettings.Get<settings::Setting::SessionStoragePath>();
64+
std::filesystem::path basePath =
65+
customPath.empty() ? (localAppData / wsl::windows::wslc::DefaultStorageSubPath) : std::filesystem::path{customPath};
66+
auto storagePath = (basePath / name).wstring();
67+
68+
return std::unique_ptr<SessionSettings>(
69+
new SessionSettings(std::move(name), std::move(storagePath), WSLCSessionStorageFlagsNone, userSettings));
70+
}
71+
72+
// Custom session: caller provides name and storage path.
73+
static SessionSettings Custom(HANDLE UserToken, LPCWSTR Name, LPCWSTR Path, WSLCSessionStorageFlags StorageFlags = WSLCSessionStorageFlagsNone)
74+
{
75+
auto localAppData = wsl::windows::common::filesystem::GetLocalAppDataPath(UserToken);
76+
settings::UserSettings userSettings(localAppData / L"wslc");
77+
return SessionSettings(Name, Path, StorageFlags, userSettings);
78+
}
79+
80+
private:
81+
SessionSettings(std::wstring name, std::wstring path, WSLCSessionStorageFlags storageFlags, const settings::UserSettings& userSettings) :
82+
DisplayName(std::move(name)), StoragePath(std::move(path))
83+
{
84+
Settings.DisplayName = DisplayName.c_str();
85+
Settings.StoragePath = StoragePath.c_str();
86+
Settings.CpuCount = userSettings.Get<settings::Setting::SessionCpuCount>();
87+
Settings.MemoryMb = userSettings.Get<settings::Setting::SessionMemoryMb>();
88+
Settings.MaximumStorageSizeMb = userSettings.Get<settings::Setting::SessionStorageSizeMb>();
89+
Settings.BootTimeoutMs = wsl::windows::wslc::DefaultBootTimeoutMs;
90+
Settings.NetworkingMode = userSettings.Get<settings::Setting::SessionNetworkingMode>();
91+
Settings.FeatureFlags = WslcFeatureFlagsNone;
92+
WI_SetFlagIf(Settings.FeatureFlags, WslcFeatureFlagsDnsTunneling, userSettings.Get<settings::Setting::SessionDnsTunneling>());
93+
WI_SetFlagIf(Settings.FeatureFlags, WslcFeatureFlagsVirtioFs, userSettings.Get<settings::Setting::SessionHostFileShareMode>() == HostFileShareMode::VirtioFs);
94+
Settings.StorageFlags = storageFlags;
95+
}
96+
};
97+
98+
} // namespace
3899

39100
WSLCSessionManagerImpl::~WSLCSessionManagerImpl()
40101
{
@@ -51,22 +112,42 @@ WSLCSessionManagerImpl::~WSLCSessionManagerImpl()
51112

52113
void WSLCSessionManagerImpl::CreateSession(const WSLCSessionSettings* Settings, WSLCSessionFlags Flags, IWSLCSession** WslcSession)
53114
{
54-
// Ensure that the session display name is non-null and not too long.
55-
THROW_HR_IF(E_INVALIDARG, Settings->DisplayName == nullptr);
56-
THROW_HR_IF(E_INVALIDARG, wcslen(Settings->DisplayName) >= std::size(WSLCSessionInformation{}.DisplayName));
57-
THROW_HR_IF_MSG(
58-
E_INVALIDARG,
59-
WI_IsAnyFlagSet(Settings->StorageFlags, ~WSLCSessionStorageFlagsValid),
60-
"Invalid storage flags: %i",
61-
Settings->StorageFlags);
62-
63115
auto tokenInfo = GetCallingProcessTokenInfo();
116+
const auto callerToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
117+
118+
// Resolve display name upfront (for both default and custom sessions).
119+
std::wstring resolvedDisplayName;
120+
if (Settings == nullptr)
121+
{
122+
// Default session: name determined from token.
123+
resolvedDisplayName = tokenInfo.Elevated ? wsl::windows::wslc::DefaultAdminSessionName : wsl::windows::wslc::DefaultSessionName;
124+
Flags = WSLCSessionFlagsOpenExisting | WSLCSessionFlagsPersistent;
125+
}
126+
else
127+
{
128+
THROW_HR_IF(E_INVALIDARG, Settings->DisplayName == nullptr || wcslen(Settings->DisplayName) == 0);
129+
THROW_HR_IF(E_INVALIDARG, Settings->StoragePath != nullptr && wcslen(Settings->StoragePath) == 0);
130+
THROW_HR_IF(E_INVALIDARG, wcslen(Settings->DisplayName) >= std::size(WSLCSessionInformation{}.DisplayName));
131+
THROW_HR_IF_MSG(
132+
E_INVALIDARG,
133+
WI_IsAnyFlagSet(Settings->StorageFlags, ~WSLCSessionStorageFlagsValid),
134+
"Invalid storage flags: %i",
135+
Settings->StorageFlags);
136+
137+
// Reserved names can only be assigned server-side via null Settings.
138+
THROW_HR_IF(
139+
E_ACCESSDENIED,
140+
wsl::shared::string::IsEqual(Settings->DisplayName, wsl::windows::wslc::DefaultSessionName, true) ||
141+
wsl::shared::string::IsEqual(Settings->DisplayName, wsl::windows::wslc::DefaultAdminSessionName, true));
142+
143+
resolvedDisplayName = Settings->DisplayName;
144+
}
64145

65146
std::lock_guard lock(m_wslcSessionsLock);
66147

67148
// Check for an existing session first.
68149
auto result = ForEachSession<HRESULT>([&](auto& entry, const wil::com_ptr<IWSLCSession>& session) noexcept -> std::optional<HRESULT> {
69-
if (!wsl::shared::string::IsEqual(entry.DisplayName.c_str(), Settings->DisplayName))
150+
if (!wsl::shared::string::IsEqual(entry.DisplayName.c_str(), resolvedDisplayName.c_str()))
70151
{
71152
return {};
72153
}
@@ -88,6 +169,14 @@ void WSLCSessionManagerImpl::CreateSession(const WSLCSessionSettings* Settings,
88169

89170
wslutil::StopWatch stopWatch;
90171

172+
// Initialize settings for the default session.
173+
std::unique_ptr<SessionSettings> defaultSettings;
174+
if (Settings == nullptr)
175+
{
176+
defaultSettings = SessionSettings::Default(callerToken.get(), tokenInfo.Elevated);
177+
Settings = &defaultSettings->Settings;
178+
}
179+
91180
HRESULT creationResult = wil::ResultFromException([&]() {
92181
// Get caller info.
93182
const auto callerProcess = wslutil::OpenCallingProcess(PROCESS_QUERY_LIMITED_INFORMATION);
@@ -103,13 +192,13 @@ void WSLCSessionManagerImpl::CreateSession(const WSLCSessionSettings* Settings,
103192
AddSessionProcessToJobObject(factory.get());
104193

105194
// Create the session via the factory.
106-
const auto sessionSettings = CreateSessionSettings(sessionId, creatorPid, Settings);
195+
const auto sessionSettings = CreateSessionSettings(sessionId, creatorPid, Settings, resolvedDisplayName.c_str());
107196
wil::com_ptr<IWSLCSession> session;
108197
wil::com_ptr<IWSLCSessionReference> serviceRef;
109198
THROW_IF_FAILED(factory->CreateSession(&sessionSettings, vm.Get(), &session, &serviceRef));
110199

111200
// Track the session via its service ref, along with metadata and security info.
112-
m_sessions.push_back({std::move(serviceRef), sessionId, creatorPid, Settings->DisplayName, std::move(tokenInfo)});
201+
m_sessions.push_back({std::move(serviceRef), sessionId, creatorPid, resolvedDisplayName, std::move(tokenInfo)});
113202

114203
// For persistent sessions, also hold a strong reference to keep them alive.
115204
const bool persistent = WI_IsFlagSet(Flags, WSLCSessionFlagsPersistent);
@@ -122,19 +211,18 @@ void WSLCSessionManagerImpl::CreateSession(const WSLCSessionSettings* Settings,
122211
});
123212

124213
// This telemetry event is used to keep track of session creation performance (via CreationTimeMs) and failure reasons (via Result).
125-
126214
WSL_LOG_TELEMETRY(
127215
"WSLCCreateSession",
128216
PDT_ProductAndServicePerformance,
129217
TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA),
130-
TraceLoggingValue(Settings->DisplayName, "Name"),
218+
TraceLoggingValue(resolvedDisplayName.c_str(), "Name"),
131219
TraceLoggingValue(stopWatch.ElapsedMilliseconds(), "CreationTimeMs"),
132220
TraceLoggingValue(creationResult, "Result"),
133221
TraceLoggingValue(tokenInfo.Elevated, "Elevated"),
134222
TraceLoggingValue(static_cast<uint32_t>(Flags), "Flags"),
135223
TraceLoggingLevel(WINEVENT_LEVEL_INFO));
136224

137-
THROW_IF_FAILED_MSG(creationResult, "Failed to create session: %ls", Settings->DisplayName);
225+
THROW_IF_FAILED_MSG(creationResult, "Failed to create session: %ls", resolvedDisplayName.c_str());
138226
}
139227

140228
void WSLCSessionManagerImpl::OpenSession(ULONG Id, IWSLCSession** Session)
@@ -160,6 +248,14 @@ void WSLCSessionManagerImpl::OpenSessionByName(LPCWSTR DisplayName, IWSLCSession
160248
{
161249
auto tokenInfo = GetCallingProcessTokenInfo();
162250

251+
// Null name = default session, resolved from caller's token.
252+
std::wstring resolvedName;
253+
if (DisplayName == nullptr)
254+
{
255+
resolvedName = tokenInfo.Elevated ? wsl::windows::wslc::DefaultAdminSessionName : wsl::windows::wslc::DefaultSessionName;
256+
DisplayName = resolvedName.c_str();
257+
}
258+
163259
auto result = ForEachSession<HRESULT>([&](auto& entry, const wil::com_ptr<IWSLCSession>& session) noexcept -> std::optional<HRESULT> {
164260
if (!wsl::shared::string::IsEqual(entry.DisplayName.c_str(), DisplayName))
165261
{
@@ -207,12 +303,23 @@ void WSLCSessionManagerImpl::GetVersion(_Out_ WSLCVersion* Version)
207303
Version->Revision = WSL_PACKAGE_VERSION_REVISION;
208304
}
209305

210-
WSLCSessionInitSettings WSLCSessionManagerImpl::CreateSessionSettings(_In_ ULONG SessionId, _In_ DWORD CreatorPid, _In_ const WSLCSessionSettings* Settings)
306+
void WSLCSessionManagerImpl::EnterSession(_In_ LPCWSTR DisplayName, _In_ LPCWSTR StoragePath, IWSLCSession** WslcSession)
307+
{
308+
THROW_HR_IF(E_POINTER, DisplayName == nullptr || StoragePath == nullptr);
309+
THROW_HR_IF(E_INVALIDARG, DisplayName[0] == L'\0' || StoragePath[0] == L'\0');
310+
311+
const auto callerToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
312+
auto sessionSettings = SessionSettings::Custom(callerToken.get(), DisplayName, StoragePath, WSLCSessionStorageFlagsNoCreate);
313+
CreateSession(&sessionSettings.Settings, WSLCSessionFlagsNone, WslcSession);
314+
}
315+
316+
WSLCSessionInitSettings WSLCSessionManagerImpl::CreateSessionSettings(
317+
_In_ ULONG SessionId, _In_ DWORD CreatorPid, _In_ const WSLCSessionSettings* Settings, _In_ LPCWSTR ResolvedDisplayName)
211318
{
212319
WSLCSessionInitSettings sessionSettings{};
213320
sessionSettings.SessionId = SessionId;
214321
sessionSettings.CreatorPid = CreatorPid;
215-
sessionSettings.DisplayName = Settings->DisplayName;
322+
sessionSettings.DisplayName = ResolvedDisplayName;
216323
sessionSettings.StoragePath = Settings->StoragePath;
217324
sessionSettings.MaximumStorageSizeMb = Settings->MaximumStorageSizeMb;
218325
sessionSettings.BootTimeoutMs = Settings->BootTimeoutMs;
@@ -292,6 +399,11 @@ HRESULT WSLCSessionManager::CreateSession(const WSLCSessionSettings* WslcSession
292399
return CallImpl(&WSLCSessionManagerImpl::CreateSession, WslcSessionSettings, Flags, WslcSession);
293400
}
294401

402+
HRESULT WSLCSessionManager::EnterSession(_In_ LPCWSTR DisplayName, _In_ LPCWSTR StoragePath, IWSLCSession** WslcSession)
403+
{
404+
return CallImpl(&WSLCSessionManagerImpl::EnterSession, DisplayName, StoragePath, WslcSession);
405+
}
406+
295407
HRESULT WSLCSessionManager::ListSessions(_Out_ WSLCSessionInformation** Sessions, _Out_ ULONG* SessionsCount)
296408
{
297409
return CallImpl(&WSLCSessionManagerImpl::ListSessions, Sessions, SessionsCount);

src/windows/service/exe/WSLCSessionManager.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ class WSLCSessionManagerImpl
7373

7474
void GetVersion(_Out_ WSLCVersion* Version);
7575
void CreateSession(const WSLCSessionSettings* WslcSessionSettings, WSLCSessionFlags Flags, IWSLCSession** WslcSession);
76+
void EnterSession(_In_ LPCWSTR DisplayName, _In_ LPCWSTR StoragePath, IWSLCSession** WslcSession);
7677
void ListSessions(_Out_ WSLCSessionInformation** Sessions, _Out_ ULONG* SessionsCount);
7778
void OpenSession(_In_ ULONG Id, _Out_ IWSLCSession** Session);
7879
void OpenSessionByName(_In_ LPCWSTR DisplayName, _Out_ IWSLCSession** Session);
@@ -138,7 +139,8 @@ class WSLCSessionManagerImpl
138139
}
139140

140141
void AddSessionProcessToJobObject(_In_ IWSLCSessionFactory* Factory);
141-
WSLCSessionInitSettings CreateSessionSettings(_In_ ULONG SessionId, _In_ DWORD CreatorPid, _In_ const WSLCSessionSettings* Settings);
142+
WSLCSessionInitSettings CreateSessionSettings(
143+
_In_ ULONG SessionId, _In_ DWORD CreatorPid, _In_ const WSLCSessionSettings* Settings, _In_ LPCWSTR ResolvedDisplayName);
142144
void EnsureJobObjectCreated();
143145
static CallingProcessTokenInfo GetCallingProcessTokenInfo();
144146
static HRESULT CheckTokenAccess(const SessionEntry& Entry, const CallingProcessTokenInfo& TokenInfo);
@@ -173,6 +175,7 @@ class DECLSPEC_UUID("a9b7a1b9-0671-405c-95f1-e0612cb4ce8f") WSLCSessionManager
173175

174176
IFACEMETHOD(GetVersion)(_Out_ WSLCVersion* Version) override;
175177
IFACEMETHOD(CreateSession)(const WSLCSessionSettings* WslcSessionSettings, WSLCSessionFlags Flags, IWSLCSession** WslcSession) override;
178+
IFACEMETHOD(EnterSession)(_In_ LPCWSTR DisplayName, _In_ LPCWSTR StoragePath, IWSLCSession** WslcSession) override;
176179
IFACEMETHOD(ListSessions)(_Out_ WSLCSessionInformation** Sessions, _Out_ ULONG* SessionsCount) override;
177180
IFACEMETHOD(OpenSession)(_In_ ULONG Id, _Out_ IWSLCSession** Session) override;
178181
IFACEMETHOD(OpenSessionByName)(_In_ LPCWSTR DisplayName, _Out_ IWSLCSession** Session) override;

0 commit comments

Comments
 (0)