master
cpp 716 lines 28.9 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 WSLCSessionManager.cpp
8
9 Abstract:
10
11 Implementation for WSLCSessionManager.
12
13 Sessions run in a per-user COM server process for security isolation.
14 The SYSTEM service creates sessions via IWSLCSessionFactory which returns
15 both the session interface (for clients) and an IWSLCSessionReference
16 (for the service to track sessions via weak references).
17
18 Session lifetime:
19 - Non-persistent sessions: tracked via IWSLCSessionReference which holds
20 weak references. Sessions are cleaned up when all client refs are released.
21 - Persistent sessions: the service holds an additional strong IWSLCSession
22 reference to keep them alive until explicitly terminated.
23
24 A job object with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE ensures that all
25 per-user COM server processes are automatically terminated if wslservice
26 crashes or exits unexpectedly.
27
28 --*/
29
30 #include "WSLCSessionManager.h"
31 #include "HcsVirtualMachine.h"
32 #include "WSLCUserSettings.h"
33 #include "WSLCSessionDefaults.h"
34 #include "WSLCPluginNotifier.h"
35 #include "PluginManager.h"
36 #include "ExecutionContext.h"
37 #include "helpers.hpp"
38 #include "wslutil.h"
39 #include "filesystem.hpp"
40 #include "APICompat.h"
41 #include "Localization.h"
42
43 extern wsl::windows::service::PluginManager g_pluginManager;
44
45 using wsl::windows::common::COMServiceExecutionContext;
46 using wsl::windows::service::wslc::CallingProcessTokenInfo;
47 using wsl::windows::service::wslc::HcsVirtualMachine;
48 using wsl::windows::service::wslc::WSLCPluginNotifier;
49 using wsl::windows::service::wslc::WSLCSessionManagerImpl;
50 using wsl::windows::service::wslc::WSLCVirtualMachineFactory;
51 namespace wslutil = wsl::windows::common::wslutil;
52 namespace apicompat = wsl::windows::common::apicompat;
53 namespace settings = wsl::windows::wslc::settings;
54
55 namespace {
56
57 std::atomic<wsl::windows::service::wslc::WSLCSessionManagerImpl*> g_managerInstance{nullptr};
58
59 // Session settings built server-side from the caller's settings.yaml.
60 struct SessionSettings
61 {
62 std::wstring DisplayName;
63 std::wstring StoragePath;
64 std::string HostLoopback;
65 WSLCSessionSettings Settings{};
66
67 NON_COPYABLE(SessionSettings);
68 NON_MOVABLE(SessionSettings);
69
70 // Load user settings under impersonation.
71 static settings::UserSettings LoadUserSettings(HANDLE UserToken)
72 {
73 auto localAppData = wsl::windows::common::filesystem::GetLocalAppDataPath(UserToken);
74 auto runAsUser = wil::impersonate_token(UserToken);
75 return settings::UserSettings(localAppData / L"wslc");
76 }
77
78 // Get default memory size. Half of available memory.
79 static uint32_t DefaultMemoryMb()
80 {
81 MEMORYSTATUSEX memInfo{sizeof(MEMORYSTATUSEX)};
82 THROW_IF_WIN32_BOOL_FALSE(GlobalMemoryStatusEx(&memInfo));
83 return static_cast<uint32_t>(memInfo.ullTotalPhys / (2 * _1MB));
84 }
85
86 // Default session: name and storage path determined from caller's token.
87 static std::unique_ptr<SessionSettings> Default(HANDLE UserToken, const std::wstring& ResolvedName)
88 {
89 auto userSettings = LoadUserSettings(UserToken);
90
91 auto configuredStorageBase = userSettings.Get<settings::Setting::SessionStoragePath>();
92 const bool customConfigured = !configuredStorageBase.empty();
93 const std::filesystem::path defaultBase = wsl::windows::common::filesystem::GetLocalAppDataPath(UserToken);
94 const std::filesystem::path storageBase =
95 customConfigured ? std::filesystem::path(wsl::shared::string::MultiByteToWide(configuredStorageBase)) : defaultBase;
96
97 const auto storageDir = storageBase / wsl::windows::wslc::DefaultStorageSubPath / ResolvedName;
98
99 // wslcsession emits the custom-location warning when it actually creates the VHD, so the notice
100 // fires once at creation without a service-side callback that could stall CreateSession.
101 const auto storageFlags = customConfigured ? WSLCSessionStorageFlagsWarnCustomLocation : WSLCSessionStorageFlagsNone;
102
103 return std::unique_ptr<SessionSettings>(new SessionSettings(std::wstring(ResolvedName), storageDir.wstring(), storageFlags, userSettings));
104 }
105
106 // Custom session: caller provides name and storage path.
107 static SessionSettings Custom(HANDLE UserToken, LPCWSTR Name, LPCWSTR Path, WSLCSessionStorageFlags StorageFlags = WSLCSessionStorageFlagsNone)
108 {
109 auto userSettings = LoadUserSettings(UserToken);
110 return SessionSettings(Name, Path, StorageFlags, userSettings);
111 }
112
113 private:
114 SessionSettings(std::wstring name, std::wstring path, WSLCSessionStorageFlags storageFlags, const settings::UserSettings& userSettings) :
115 DisplayName(std::move(name)), StoragePath(std::move(path)), HostLoopback(userSettings.Get<settings::Setting::SessionHostLoopback>())
116 {
117 Settings.DisplayName = DisplayName.c_str();
118 Settings.StoragePath = StoragePath.c_str();
119 Settings.HostLoopback = HostLoopback.empty() ? nullptr : HostLoopback.c_str();
120 auto cpuCount = userSettings.Get<settings::Setting::SessionCpuCount>();
121 Settings.CpuCount = cpuCount > 0 ? cpuCount : wsl::windows::common::wslutil::GetLogicalProcessorCount();
122 auto memoryMb = userSettings.Get<settings::Setting::SessionMemoryMb>();
123 Settings.MemoryMb = memoryMb > 0 ? memoryMb : SessionSettings::DefaultMemoryMb();
124 Settings.MaximumStorageSizeMb = userSettings.Get<settings::Setting::SessionStorageSizeMb>();
125 Settings.BootTimeoutMs = wsl::windows::wslc::DefaultBootTimeoutMs;
126 Settings.IdleTimeoutSec = userSettings.Get<settings::Setting::SessionIdleTimeout>();
127 Settings.NetworkingMode = userSettings.Get<settings::Setting::SessionNetworkingMode>();
128
129 // TODO: Add a config setting to opt-out of GPU support.
130 Settings.FeatureFlags = WslcFeatureFlagsGPU;
131 WI_SetFlagIf(Settings.FeatureFlags, WslcFeatureFlagsDnsTunneling, userSettings.Get<settings::Setting::SessionDnsTunneling>());
132 WI_SetFlagIf(
133 Settings.FeatureFlags,
134 WslcFeatureFlagsVirtioFs,
135 userSettings.Get<settings::Setting::SessionHostFileShareMode>() == settings::HostFileShareMode::VirtioFs);
136 WI_SetFlagIf(
137 Settings.FeatureFlags,
138 WslcFeatureFlagsPortRelayWslRelay,
139 userSettings.Get<settings::Setting::SessionPortRelay>() == settings::PortRelayType::WslRelay);
140 Settings.StorageFlags = storageFlags;
141 }
142 };
143
144 } // namespace
145
146 WSLCSessionManagerImpl::WSLCSessionManagerImpl()
147 {
148 g_managerInstance.store(this);
149 }
150
151 WSLCSessionManagerImpl::~WSLCSessionManagerImpl()
152 {
153 g_managerInstance.store(nullptr);
154
155 // Terminate all sessions on shutdown.
156 // Call Terminate() directly rather than going through ForEachSession(),
157 // which would needlessly resolve weak references and call GetState().
158 // Terminate() already handles the "session is gone" case gracefully.
159 std::lock_guard lock(m_wslcSessionsLock);
160 for (auto& entry : m_sessions)
161 {
162 NotifySessionStoppingLockHeld(entry);
163 LOG_IF_FAILED(entry.Ref->Terminate());
164 }
165 }
166
167 void WSLCSessionManagerImpl::NotifySessionStoppingLockHeld(SessionEntry& entry) noexcept
168 try
169 {
170 if (entry.StoppingNotified)
171 {
172 return;
173 }
174
175 entry.StoppingNotified = true;
176 WSLCSessionInformation info{};
177 info.SessionId = static_cast<WSLCSessionId>(entry.SessionId);
178 info.DisplayName = entry.DisplayName.c_str();
179 info.ApplicationPid = entry.CreatorPid;
180 info.UserToken = entry.UserToken.get();
181 info.UserSid = entry.UserSid.data();
182 g_pluginManager.OnWslcSessionStopping(&info);
183 }
184 CATCH_LOG()
185
186 void WSLCSessionManagerImpl::CreateSession(
187 _In_ const WSLCSessionSettings* Settings, _In_ WSLCSessionFlags Flags, _In_opt_ IWarningCallback* WarningCallback, _Out_ IWSLCSession** WslcSession)
188 {
189 THROW_HR_IF_NULL(E_POINTER, WslcSession);
190
191 auto tokenInfo = GetCallingProcessTokenInfo();
192 const auto callerToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
193
194 // Resolve display name upfront (for both default and custom sessions).
195 std::wstring resolvedDisplayName;
196 if (Settings == nullptr)
197 {
198 // Default session: name determined from token, qualified with username.
199 resolvedDisplayName = ResolveDefaultSessionName(tokenInfo);
200 Flags = WSLCSessionFlagsOpenExisting | WSLCSessionFlagsPersistent;
201 }
202 else
203 {
204 THROW_HR_IF(WSLC_E_INVALID_SESSION_NAME, Settings->DisplayName == nullptr || wcslen(Settings->DisplayName) == 0);
205 THROW_HR_IF(E_INVALIDARG, Settings->StoragePath != nullptr && wcslen(Settings->StoragePath) == 0);
206 THROW_HR_IF(WSLC_E_INVALID_SESSION_NAME, wcslen(Settings->DisplayName) >= std::size(WSLCSessionListEntry{}.DisplayName));
207 THROW_HR_IF_MSG(E_INVALIDARG, WI_IsAnyFlagSet(Flags, ~WSLCSessionFlagsValid), "Invalid session flags: 0x%x", Flags);
208 THROW_HR_IF_MSG(
209 E_INVALIDARG, WI_IsAnyFlagSet(Settings->FeatureFlags, ~WSLCFeatureFlagsValid), "Invalid feature flags: 0x%x", Settings->FeatureFlags);
210 THROW_HR_IF_MSG(
211 E_INVALIDARG,
212 WI_IsAnyFlagSet(Settings->StorageFlags, ~WSLCSessionStorageFlagsValid),
213 "Invalid storage flags: %i",
214 Settings->StorageFlags);
215
216 // Reserved names can only be assigned server-side via null Settings.
217 THROW_HR_IF(WSLC_E_SESSION_RESERVED, IsReservedSessionName(Settings->DisplayName));
218
219 resolvedDisplayName = Settings->DisplayName;
220 }
221
222 std::lock_guard lock(m_wslcSessionsLock);
223
224 // Check for an existing session first.
225 auto result = ForEachSession<HRESULT>([&](auto& entry, const wil::com_ptr<IWSLCSession>& session) noexcept -> std::optional<HRESULT> {
226 if (!wsl::shared::string::IsEqual(entry.DisplayName.c_str(), resolvedDisplayName.c_str()))
227 {
228 return {};
229 }
230
231 RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), WI_IsFlagClear(Flags, WSLCSessionFlagsOpenExisting));
232
233 RETURN_IF_FAILED(CheckTokenAccess(entry, tokenInfo));
234
235 RETURN_IF_FAILED(wil::com_copy_to_nothrow(session, WslcSession));
236
237 return S_OK;
238 });
239
240 if (result.has_value())
241 {
242 THROW_IF_FAILED(result.value());
243 return; // Existing session was opened.
244 }
245
246 wslutil::StopWatch stopWatch;
247
248 // Initialize settings for the default session.
249 std::unique_ptr<SessionSettings> defaultSettings;
250 if (Settings == nullptr)
251 {
252 defaultSettings = SessionSettings::Default(callerToken.get(), resolvedDisplayName);
253 Settings = &defaultSettings->Settings;
254 }
255
256 std::wstring callerFileName;
257
258 HRESULT creationResult = wil::ResultFromException([&]() {
259 // Get caller info.
260 const auto callerProcess = wslutil::OpenCallingProcess(PROCESS_QUERY_LIMITED_INFORMATION);
261 const ULONG sessionId = m_nextSessionId++;
262 const DWORD creatorPid = GetProcessId(callerProcess.get());
263
264 // Query the full image path of the calling process and extract just the file name.
265 std::wstring callerFilePath;
266 if (SUCCEEDED_LOG(wil::QueryFullProcessImageNameW<std::wstring>(callerProcess.get(), 0, callerFilePath)))
267 {
268 callerFileName = std::filesystem::path(callerFilePath).filename().wstring();
269 }
270
271 const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
272
273 // Capture a duplicated user token + raw SID so PluginManager can build
274 // WSLCSessionInformation later (e.g. on shutdown) without re-impersonating.
275 // The token is shared between the SessionEntry and the WSLCPluginNotifier.
276 wil::unique_handle dupToken;
277 THROW_IF_WIN32_BOOL_FALSE(DuplicateTokenEx(
278 userToken.get(), TOKEN_QUERY | TOKEN_DUPLICATE, nullptr, SecurityImpersonation, TokenImpersonation, &dupToken));
279 wil::shared_handle sharedToken{dupToken.release()};
280
281 const DWORD sidLen = GetLengthSid(tokenInfo.TokenInfo->User.Sid);
282 std::vector<BYTE> storedSid(sidLen);
283 THROW_IF_WIN32_BOOL_FALSE(CopySid(sidLen, storedSid.data(), tokenInfo.TokenInfo->User.Sid));
284
285 // Build the plugin notifier service-side. Lifetime tracked via the SessionEntry.
286 Microsoft::WRL::ComPtr<IWSLCPluginNotifier> notifier;
287 notifier = wil::MakeOrThrow<WSLCPluginNotifier>(
288 g_pluginManager, sessionId, creatorPid, std::wstring(resolvedDisplayName), wil::shared_handle(sharedToken), std::vector<BYTE>(storedSid));
289
290 // Create the VM factory in the SYSTEM service (privileged). The per-user session
291 // uses it to create VMs on demand and recreate them after idle-termination.
292 auto vmFactory = Microsoft::WRL::Make<WSLCVirtualMachineFactory>(Settings);
293
294 // Launch per-user COM server factory and add it to a fresh per-session job object for crash cleanup.
295 auto factory = wslutil::CreateComServerAsUser<IWSLCSessionFactory>(__uuidof(WSLCSessionFactory), userToken.get());
296 wil::unique_handle sessionJob = CreateSessionProcessJob(factory.get());
297
298 const auto sessionSettings = CreateSessionSettings(sessionId, callerFileName.c_str(), Settings, resolvedDisplayName.c_str());
299 wil::com_ptr<IWSLCSession> session;
300 wil::com_ptr<IWSLCSessionReference> serviceRef;
301 const auto factoryHr =
302 factory->CreateSession(&sessionSettings, vmFactory.Get(), notifier.Get(), WarningCallback, &session, &serviceRef);
303 if (FAILED(factoryHr))
304 {
305 if (auto comError = wslutil::GetCOMErrorInfo(); comError && comError->Message)
306 {
307 THROW_HR_WITH_USER_ERROR(factoryHr, comError->Message.get());
308 }
309
310 THROW_HR(factoryHr);
311 }
312
313 // Track the session via its service ref, along with metadata and security info.
314 m_sessions.push_back(SessionEntry{
315 std::move(serviceRef), sessionId, creatorPid, resolvedDisplayName, std::move(tokenInfo), notifier, false, sharedToken, std::move(storedSid), std::move(sessionJob)});
316
317 // For persistent sessions, also hold a strong reference to keep them alive.
318 const bool persistent = WI_IsFlagSet(Flags, WSLCSessionFlagsPersistent);
319 if (persistent)
320 {
321 m_persistentSessions.emplace_back(sessionId, session);
322 }
323
324 // Notify plugins that the session was created. A failure here aborts session creation.
325 try
326 {
327 auto& entry = m_sessions.back();
328 WSLCSessionInformation info{};
329 info.SessionId = static_cast<WSLCSessionId>(entry.SessionId);
330 info.DisplayName = entry.DisplayName.c_str();
331 info.ApplicationPid = entry.CreatorPid;
332 info.UserToken = entry.UserToken.get();
333 info.UserSid = entry.UserSid.data();
334 g_pluginManager.OnWslcSessionCreated(&info);
335 }
336 catch (...)
337 {
338 const auto error = wil::ResultFromCaughtException();
339
340 // Plugin rejected the session: tear it down before propagating.
341 m_sessions.back().StoppingNotified = true; // Don't fire stopping for a session that never started successfully.
342 LOG_IF_FAILED(m_sessions.back().Ref->Terminate());
343 m_sessions.pop_back();
344
345 auto remove = std::ranges::remove_if(m_persistentSessions, [&](const auto& e) { return e.first == sessionId; });
346 m_persistentSessions.erase(remove.begin(), remove.end());
347
348 THROW_HR(error);
349 }
350
351 *WslcSession = session.detach();
352 });
353
354 // This telemetry event is used to keep track of session creation performance (via CreationTimeMs) and failure reasons (via Result).
355 WSL_LOG(
356 "WSLCCreateSession",
357 TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage),
358 TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA),
359 TraceLoggingValue(resolvedDisplayName.c_str(), "Name"),
360 TraceLoggingValue(WSL_PACKAGE_VERSION, "wslVersion"),
361 TraceLoggingValue(stopWatch.ElapsedMilliseconds(), "CreationTimeMs"),
362 TraceLoggingValue(creationResult, "Result"),
363 TraceLoggingValue(tokenInfo.Elevated, "Elevated"),
364 TraceLoggingValue(static_cast<uint32_t>(Flags), "Flags"),
365 TraceLoggingLevel(WINEVENT_LEVEL_INFO));
366
367 WSL_LOG(
368 "WSLCCreateSessionCaller",
369 TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage),
370 TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA),
371 TraceLoggingValue(callerFileName.c_str(), "CallerFileName"),
372 TraceLoggingLevel(WINEVENT_LEVEL_INFO));
373
374 THROW_IF_FAILED_MSG(creationResult, "Failed to create session: %ls", resolvedDisplayName.c_str());
375 }
376
377 void WSLCSessionManagerImpl::OpenSession(ULONG Id, IWSLCSession** Session)
378 {
379 THROW_HR_IF_NULL(E_POINTER, Session);
380
381 auto tokenInfo = GetCallingProcessTokenInfo();
382 auto result = ForEachSession<HRESULT>([&](auto& entry, const wil::com_ptr<IWSLCSession>& session) noexcept -> std::optional<HRESULT> {
383 if (entry.SessionId != Id)
384 {
385 return {};
386 }
387
388 RETURN_IF_FAILED(CheckTokenAccess(entry, tokenInfo));
389
390 RETURN_IF_FAILED(wil::com_copy_to_nothrow(session, Session));
391
392 return S_OK;
393 });
394
395 THROW_IF_FAILED_MSG(result.value_or(WSLC_E_SESSION_NOT_FOUND), "Session '%lu' not found", Id);
396 }
397
398 void WSLCSessionManagerImpl::OpenSessionByName(LPCWSTR DisplayName, IWSLCSession** Session)
399 {
400 THROW_HR_IF_NULL(E_POINTER, Session);
401
402 auto tokenInfo = GetCallingProcessTokenInfo();
403
404 // Null name = default session, resolved from caller's token + username.
405 std::wstring resolvedName;
406 if (DisplayName == nullptr)
407 {
408 resolvedName = ResolveDefaultSessionName(tokenInfo);
409 DisplayName = resolvedName.c_str();
410 }
411
412 auto result = ForEachSession<HRESULT>([&](auto& entry, const wil::com_ptr<IWSLCSession>& session) noexcept -> std::optional<HRESULT> {
413 if (!wsl::shared::string::IsEqual(entry.DisplayName.c_str(), DisplayName))
414 {
415 return {};
416 }
417
418 RETURN_IF_FAILED(CheckTokenAccess(entry, tokenInfo));
419
420 RETURN_IF_FAILED(wil::com_copy_to_nothrow(session, Session));
421
422 return S_OK;
423 });
424
425 THROW_HR_WITH_USER_ERROR_IF(
426 WSLC_E_SESSION_NOT_FOUND, wsl::shared::Localization::MessageWslcSessionNotFound(DisplayName), !result.has_value());
427
428 THROW_IF_FAILED_MSG(result.value(), "Failed to open session '%ls'", DisplayName);
429 }
430
431 void WSLCSessionManagerImpl::ListSessions(_Out_ WSLCSessionListEntry** Sessions, _Out_ ULONG* SessionsCount)
432 {
433 THROW_HR_IF_NULL(E_POINTER, Sessions);
434 THROW_HR_IF_NULL(E_POINTER, SessionsCount);
435
436 std::vector<WSLCSessionListEntry> sessionInfo;
437
438 ForEachSession<void>([&](auto& entry, const auto&) noexcept {
439 try
440 {
441 wil::unique_hlocal_string sidString;
442 THROW_IF_WIN32_BOOL_FALSE(ConvertSidToStringSidW(entry.Owner.TokenInfo->User.Sid, &sidString));
443
444 auto& it = sessionInfo.emplace_back(WSLCSessionListEntry{.SessionId = entry.SessionId, .CreatorPid = entry.CreatorPid});
445 wcscpy_s(it.Sid, _countof(it.Sid), sidString.get());
446 wcscpy_s(it.DisplayName, _countof(it.DisplayName), entry.DisplayName.c_str());
447 }
448 CATCH_LOG()
449 });
450
451 auto output = wil::make_unique_cotaskmem<WSLCSessionListEntry[]>(sessionInfo.size());
452 memcpy(output.get(), sessionInfo.data(), sessionInfo.size() * sizeof(WSLCSessionListEntry));
453
454 *Sessions = output.release();
455 *SessionsCount = static_cast<ULONG>(sessionInfo.size());
456 }
457
458 void WSLCSessionManagerImpl::EnterSession(
459 _In_ LPCWSTR DisplayName, _In_ LPCWSTR StoragePath, _In_opt_ IWarningCallback* WarningCallback, _Out_ IWSLCSession** WslcSession)
460 {
461 THROW_HR_IF(E_POINTER, DisplayName == nullptr || StoragePath == nullptr);
462 THROW_HR_IF(E_INVALIDARG, DisplayName[0] == L'\0' || StoragePath[0] == L'\0');
463
464 const auto callerToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
465 auto sessionSettings = SessionSettings::Custom(callerToken.get(), DisplayName, StoragePath, WSLCSessionStorageFlagsNoCreate);
466 CreateSession(&sessionSettings.Settings, WSLCSessionFlagsNone, WarningCallback, WslcSession);
467 }
468
469 WSLCSessionInitSettings WSLCSessionManagerImpl::CreateSessionSettings(
470 _In_ ULONG SessionId, _In_ LPCWSTR CreatorProcessName, _In_ const WSLCSessionSettings* Settings, _In_ LPCWSTR ResolvedDisplayName)
471 {
472 WSLCSessionInitSettings sessionSettings{};
473 sessionSettings.SessionId = SessionId;
474 sessionSettings.CreatorProcessName = CreatorProcessName;
475 sessionSettings.DisplayName = ResolvedDisplayName;
476 sessionSettings.StoragePath = Settings->StoragePath;
477 sessionSettings.MaximumStorageSizeMb = Settings->MaximumStorageSizeMb;
478 sessionSettings.BootTimeoutMs = Settings->BootTimeoutMs;
479 sessionSettings.NetworkingMode = Settings->NetworkingMode;
480 sessionSettings.FeatureFlags = Settings->FeatureFlags;
481 sessionSettings.RootVhdTypeOverride = Settings->RootVhdTypeOverride;
482 sessionSettings.StorageFlags = Settings->StorageFlags;
483 sessionSettings.SwapSizeMb = Settings->MemoryMb;
484 sessionSettings.IdleTimeoutSec = Settings->IdleTimeoutSec;
485 return sessionSettings;
486 }
487
488 wil::unique_handle WSLCSessionManagerImpl::CreateSessionProcessJob(_In_ IWSLCSessionFactory* Factory)
489 {
490 // Use a fresh job per session; reusing one fails intermittently with
491 // ERROR_ACCESS_DENIED once it's assigned to a process the system put in another job.
492 wil::unique_handle jobObject = wsl::windows::common::helpers::CreateKillOnCloseJob();
493
494 wil::unique_handle process;
495 THROW_IF_FAILED(Factory->GetProcessHandle(process.put()));
496
497 THROW_IF_WIN32_BOOL_FALSE(AssignProcessToJobObject(jobObject.get(), process.get()));
498
499 return jobObject;
500 }
501
502 CallingProcessTokenInfo WSLCSessionManagerImpl::GetCallingProcessTokenInfo()
503 {
504 const wil::unique_handle userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
505
506 auto tokenInfo = wil::get_token_information<TOKEN_USER>(userToken.get());
507 auto elevated = wil::test_token_membership(userToken.get(), SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS);
508
509 return {std::move(tokenInfo), elevated};
510 }
511
512 std::wstring WSLCSessionManagerImpl::ResolveDefaultSessionName(const CallingProcessTokenInfo& TokenInfo)
513 {
514 // Look up the username from the caller's SID so each user gets their own
515 // default session (e.g. "wslc-cli-alice", "wslc-cli-admin-bob").
516 wchar_t username[256 + 1] = {};
517 DWORD usernameLen = ARRAYSIZE(username);
518 wchar_t domain[MAX_PATH] = {};
519 DWORD domainLen = ARRAYSIZE(domain);
520 SID_NAME_USE sidType;
521 THROW_IF_WIN32_BOOL_FALSE(LookupAccountSidW(nullptr, TokenInfo.TokenInfo->User.Sid, username, &usernameLen, domain, &domainLen, &sidType));
522
523 auto baseName = TokenInfo.Elevated ? wsl::windows::wslc::DefaultAdminSessionName : wsl::windows::wslc::DefaultSessionName;
524 return std::format(L"{}-{}", baseName, username);
525 }
526
527 bool WSLCSessionManagerImpl::IsReservedSessionName(LPCWSTR Name)
528 {
529 // Block any name that is exactly "wslc-cli" or starts with "wslc-cli-",
530 // which covers the admin variant and all per-user resolved names.
531 constexpr std::wstring_view prefix{wsl::windows::wslc::DefaultSessionName};
532 std::wstring_view name{Name};
533 if (name.size() < prefix.size())
534 {
535 return false;
536 }
537
538 if (!wsl::shared::string::IsEqual(name.substr(0, prefix.size()), prefix, true))
539 {
540 return false;
541 }
542
543 return name.size() == prefix.size() || name[prefix.size()] == L'-';
544 }
545
546 HRESULT WSLCSessionManagerImpl::CheckTokenAccess(const SessionEntry& Entry, const CallingProcessTokenInfo& TokenInfo)
547 {
548 // Allow elevated tokens to access all sessions.
549 // Otherwise a token can only access sessions from the same SID and elevation status.
550 // TODO: Offer proper ACL checks.
551
552 if (TokenInfo.Elevated)
553 {
554 return S_OK; // Token is elevated, allow access.
555 }
556
557 RETURN_HR_IF(E_ACCESSDENIED, !EqualSid(Entry.Owner.TokenInfo->User.Sid, TokenInfo.TokenInfo->User.Sid)); // Different account, deny access.
558
559 RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_ELEVATION_REQUIRED), Entry.Owner.Elevated); // Non-elevated token trying to access elevated session, deny access.
560
561 return S_OK;
562 }
563
564 WSLCSessionManager::WSLCSessionManager(WSLCSessionManagerImpl* Impl)
565 {
566 Initialize(Impl);
567 }
568
569 HRESULT WSLCSessionManager::GetVersion(_Out_ WSLCVersion* Version)
570 try
571 {
572 RETURN_HR_IF(E_POINTER, Version == nullptr);
573
574 Version->Major = WSL_PACKAGE_VERSION_MAJOR;
575 Version->Minor = WSL_PACKAGE_VERSION_MINOR;
576 Version->Revision = WSL_PACKAGE_VERSION_REVISION;
577
578 return S_OK;
579 }
580 CATCH_RETURN();
581
582 HRESULT WSLCSessionManager::IsClientVersionSupported(_In_ const WSLCCompatVersion* ClientVersion, _Out_ BOOL* IsSupported)
583 try
584 {
585 RETURN_HR_IF(E_POINTER, ClientVersion == nullptr || IsSupported == nullptr);
586
587 WSL_LOG(
588 "ClientVersionCheck",
589 TraceLoggingValue(ClientVersion->Major, "Major"),
590 TraceLoggingValue(ClientVersion->Minor, "Minor"),
591 TraceLoggingValue(ClientVersion->Revision, "Revision"));
592
593 // Moved to 2.9.5 in https://github.com/microsoft/WSL/pull/41199 to add OpenContainer to a compat interface.
594 // While we could have prevented moving this forward, as we are still in preview it should be acceptable to break.
595 // This also forces callers to experience the SDK support error early, hopefully leading to better support if a post-release support floor needs to be raised.
596 constexpr std::tuple<uint32_t, uint32_t, uint32_t> c_minClientVersion{2, 9, 5};
597
598 const std::tuple<uint32_t, uint32_t, uint32_t> clientVersion{ClientVersion->Major, ClientVersion->Minor, ClientVersion->Revision};
599
600 // Support anything at or above the minimum and when the client version exactly matches ours to cover dev builds before the break is released.
601 *IsSupported = (clientVersion >= c_minClientVersion || wsl::shared::PackageVersion == clientVersion);
602
603 return S_OK;
604 }
605 CATCH_RETURN();
606
607 HRESULT WSLCSessionManager::CreateSession(
608 const WSLCSessionSettings* WslcSessionSettings, WSLCSessionFlags Flags, IWarningCallback* WarningCallback, IWSLCSession** WslcSession)
609 try
610 {
611 COMServiceExecutionContext context;
612
613 return CallImpl(&WSLCSessionManagerImpl::CreateSession, WslcSessionSettings, Flags, WarningCallback, WslcSession);
614 }
615 CATCH_RETURN();
616
617 HRESULT WSLCSessionManager::EnterSession(_In_ LPCWSTR DisplayName, _In_ LPCWSTR StoragePath, IWarningCallback* WarningCallback, IWSLCSession** WslcSession)
618 {
619 COMServiceExecutionContext context;
620
621 return CallImpl(&WSLCSessionManagerImpl::EnterSession, DisplayName, StoragePath, WarningCallback, WslcSession);
622 }
623
624 HRESULT WSLCSessionManager::ListSessions(_Out_ WSLCSessionListEntry** Sessions, _Out_ ULONG* SessionsCount)
625 {
626 COMServiceExecutionContext context;
627
628 return CallImpl(&WSLCSessionManagerImpl::ListSessions, Sessions, SessionsCount);
629 }
630
631 HRESULT WSLCSessionManager::OpenSession(_In_ ULONG Id, _Out_ IWSLCSession** Session)
632 {
633 COMServiceExecutionContext context;
634
635 return CallImpl(&WSLCSessionManagerImpl::OpenSession, Id, Session);
636 }
637
638 HRESULT WSLCSessionManager::OpenSessionByName(_In_ LPCWSTR DisplayName, _Out_ IWSLCSession** Session)
639 {
640 COMServiceExecutionContext context;
641
642 return CallImpl(&WSLCSessionManagerImpl::OpenSessionByName, DisplayName, Session);
643 }
644
645 HRESULT WSLCSessionManager::InterfaceSupportsErrorInfo(_In_ REFIID riid)
646 {
647 return riid == __uuidof(IWSLCSessionManager) ? S_OK : S_FALSE;
648 }
649
650 HRESULT WSLCSessionManager::GetVersion(_Out_ WSLCCompatVersion* Version)
651 try
652 {
653 RETURN_HR_IF_NULL(E_POINTER, Version);
654
655 WSLCVersion version{};
656 RETURN_IF_FAILED(GetVersion(&version));
657
658 *Version = apicompat::Convert(version);
659 return S_OK;
660 }
661 CATCH_RETURN();
662
663 HRESULT WSLCSessionManager::CreateSession(
664 const WSLCCompatSessionSettings* Settings, WSLCSessionFlags Flags, IWSLCCompatWarningCallback* WarningCallback, IWSLCCompatSession** Session)
665 try
666 {
667 RETURN_HR_IF_NULL(E_POINTER, Session);
668 *Session = nullptr;
669
670 const auto warning = apicompat::Convert(WarningCallback);
671
672 Microsoft::WRL::ComPtr<IWSLCSession> session;
673 if (Settings == nullptr)
674 {
675 RETURN_IF_FAILED(CreateSession(static_cast<const WSLCSessionSettings*>(nullptr), Flags, warning.Get(), &session));
676 }
677 else
678 {
679 const auto settings = apicompat::Convert(*Settings);
680 RETURN_IF_FAILED(CreateSession(settings.Get(), Flags, warning.Get(), &session));
681 }
682
683 RETURN_HR_IF_NULL(E_UNEXPECTED, session);
684
685 return session.CopyTo(Session);
686 }
687 CATCH_RETURN();
688
689 namespace wsl::windows::service::wslc {
690
691 WSLCSessionManagerImpl* WSLCSessionManagerImpl::Instance() noexcept
692 {
693 return g_managerInstance.load();
694 }
695
696 wil::com_ptr<IWSLCSession> WSLCSessionManagerImpl::FindSession(ULONG Id)
697 {
698 wil::com_ptr<IWSLCSession> result;
699
700 ForEachSession<HRESULT>(
701 [&](SessionEntry& entry, const wil::com_ptr<IWSLCSession>& session) noexcept -> std::optional<HRESULT> {
702 if (entry.SessionId != Id)
703 {
704 return std::nullopt;
705 }
706
707 result = session;
708 return S_OK;
709 },
710 PluginManager::IsInWslcNotification());
711
712 THROW_HR_IF_MSG(WSLC_E_SESSION_NOT_FOUND, !result, "WSLC session %lu not found", Id);
713 return result;
714 }
715
716 } // namespace wsl::windows::service::wslc