master
h 223 lines 8.96 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 WSLCSessionManager.h
8
9 Abstract:
10
11 Definition for WSLCSessionManager.
12
13 Session Lifetime Management:
14 ----------------------------
15 Sessions are created in per-user COM server processes via IWSLCSessionFactory.
16 The SYSTEM service holds IWSLCSessionReference objects that contain weak
17 references to the actual sessions.
18
19 - Non-persistent sessions: Lifetime is tied to client COM references.
20 When all clients release their IWSLCSession references, the session is
21 terminated and the weak reference in IWSLCSessionReference returns NULL.
22
23 - Persistent sessions: The service holds an additional strong IWSLCSession
24 reference to keep the session alive until explicitly terminated or service
25 shutdown.
26
27 The IWSLCSessionReference allows the service to:
28 - Check if a session is still alive (OpenSession fails if session is gone)
29 - Terminate sessions when requested by elevated callers
30
31 --*/
32
33 #pragma once
34 #include "wslc.h"
35 #include "WSLCCompat.h"
36 #include "COMImplClass.h"
37 #include "wslutil.h"
38 #include <atomic>
39 #include <algorithm>
40 #include <string>
41 #include <vector>
42 #include <mutex>
43 #include <type_traits>
44
45 namespace wslutil = wsl::windows::common::wslutil;
46
47 namespace wsl::windows::service::wslc {
48
49 struct CallingProcessTokenInfo
50 {
51 wil::unique_tokeninfo_ptr<TOKEN_USER> TokenInfo;
52 bool Elevated;
53 };
54
55 // Metadata for a tracked session, stored service-side at creation time.
56 // Security info is stored here (not queried from the per-user process) to prevent spoofing.
57 struct SessionEntry
58 {
59 wil::com_ptr<IWSLCSessionReference> Ref;
60 ULONG SessionId = 0;
61 DWORD CreatorPid = 0;
62 std::wstring DisplayName;
63 CallingProcessTokenInfo Owner;
64
65 Microsoft::WRL::ComPtr<IWSLCPluginNotifier> PluginNotifier;
66
67 // Whether OnSessionStopping has been fired already; ensures it is fired exactly once.
68 bool StoppingNotified = false;
69
70 wil::shared_handle UserToken;
71 std::vector<BYTE> UserSid;
72
73 wil::unique_handle JobObject;
74 };
75
76 class WSLCSessionManagerImpl
77 {
78 public:
79 NON_COPYABLE(WSLCSessionManagerImpl);
80 NON_MOVABLE(WSLCSessionManagerImpl);
81
82 WSLCSessionManagerImpl();
83 ~WSLCSessionManagerImpl();
84
85 void CreateSession(
86 _In_ const WSLCSessionSettings* WslcSessionSettings,
87 _In_ WSLCSessionFlags Flags,
88 _In_opt_ IWarningCallback* WarningCallback,
89 _Out_ IWSLCSession** WslcSession);
90 void EnterSession(_In_ LPCWSTR DisplayName, _In_ LPCWSTR StoragePath, _In_opt_ IWarningCallback* WarningCallback, _Out_ IWSLCSession** WslcSession);
91 void ListSessions(_Out_ WSLCSessionListEntry** Sessions, _Out_ ULONG* SessionsCount);
92 void OpenSession(_In_ ULONG Id, _Out_ IWSLCSession** Session);
93 void OpenSessionByName(_In_ LPCWSTR DisplayName, _Out_ IWSLCSession** Session);
94
95 // Resolves a session by ID for plugin->API calls. Throws ERROR_NOT_FOUND if no session matches.
96 wil::com_ptr<IWSLCSession> FindSession(ULONG Id);
97
98 static WSLCSessionManagerImpl* Instance() noexcept;
99
100 private:
101 // Resolves the default session name for a caller: appends the username
102 // from the token SID so different users don't collide.
103 static std::wstring ResolveDefaultSessionName(const CallingProcessTokenInfo& TokenInfo);
104
105 // Returns true if the name matches a reserved default session prefix.
106 static bool IsReservedSessionName(LPCWSTR Name);
107
108 // Iterates over all sessions, cleaning up released sessions.
109 // The routine receives a SessionEntry& and can return an optional<T> to stop iteration.
110 template <typename T>
111 inline auto ForEachSession(const auto& Routine, bool DeferSessionCleanup = false)
112 {
113 std::lock_guard lock(m_wslcSessionsLock);
114
115 // Enforce noexcept: remove_if leaves the container in an unspecified
116 // (partially-moved) state if the predicate throws. Callers must handle
117 // errors via return values, not exceptions.
118 static_assert(
119 std::is_nothrow_invocable_v<decltype(Routine), SessionEntry&, wil::com_ptr<IWSLCSession>&>,
120 "ForEachSession routine must be noexcept to preserve container invariants during remove_if");
121
122 using TResult = std::conditional_t<std::is_same_v<T, void>, nullptr_t, std::optional<T>>;
123 TResult result{};
124
125 auto each = [&](SessionEntry& entry) {
126 // Try to open the session via the service ref.
127 // Fails with ERROR_OBJECT_NO_LONGER_EXISTS if released,
128 // ERROR_INVALID_STATE if terminated, or RPC error if per-user process is dead.
129 wil::com_ptr<IWSLCSession> lockedSession;
130 if (FAILED_LOG(entry.Ref->OpenSession(&lockedSession)))
131 {
132 // FindSession is used by plugin callbacks into the API. Defer cleanup in that path so
133 // OnWslcSessionStopping is not nested inside another plugin notification.
134 if (DeferSessionCleanup)
135 {
136 return false; // Keep in tracking; clean up on a later pass.
137 }
138
139 // Session is gone: notify plugins (if not already), then drop persistent reference if any.
140 NotifySessionStoppingLockHeld(entry);
141
142 auto remove =
143 std::ranges::remove_if(m_persistentSessions, [&](const auto& e) { return e.first == entry.SessionId; });
144 m_persistentSessions.erase(remove.begin(), remove.end());
145 return true; // Remove from tracking
146 }
147
148 if constexpr (std::is_same_v<T, void>)
149 {
150 Routine(entry, lockedSession);
151 }
152 else
153 {
154 if (!result.has_value())
155 {
156 result = Routine(entry, lockedSession);
157 }
158 }
159
160 return false; // Keep in tracking
161 };
162
163 auto remove = std::ranges::remove_if(m_sessions, each);
164 m_sessions.erase(remove.begin(), remove.end());
165
166 if constexpr (std::is_same_v<T, void>)
167 {
168 return;
169 }
170 else
171 {
172 return result;
173 }
174 }
175
176 [[nodiscard]] wil::unique_handle CreateSessionProcessJob(_In_ IWSLCSessionFactory* Factory);
177 WSLCSessionInitSettings CreateSessionSettings(
178 _In_ ULONG SessionId, _In_ LPCWSTR CreatorProcessName, _In_ const WSLCSessionSettings* Settings, _In_ LPCWSTR ResolvedDisplayName);
179 static CallingProcessTokenInfo GetCallingProcessTokenInfo();
180 static HRESULT CheckTokenAccess(const SessionEntry& Entry, const CallingProcessTokenInfo& TokenInfo);
181
182 void NotifySessionStoppingLockHeld(SessionEntry& entry) noexcept;
183
184 std::atomic<ULONG> m_nextSessionId{1};
185 std::recursive_mutex m_wslcSessionsLock;
186
187 // All sessions tracked via SessionEntry (which holds weak refs and service-side security info).
188 // Sessions are automatically cleaned up when the underlying session is released.
189 std::vector<SessionEntry> m_sessions;
190
191 // Strong references to persistent sessions to keep them alive.
192 // Session ID is stored alongside so cleanup doesn't require cross-process COM calls.
193 std::vector<std::pair<ULONG, wil::com_ptr<IWSLCSession>>> m_persistentSessions;
194 };
195 } // namespace wsl::windows::service::wslc
196
197 class DECLSPEC_UUID("a9b7a1b9-0671-405c-95f1-e0612cb4ce8f") WSLCSessionManager
198 : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, IWSLCSessionManager, IWSLCCompatSessionManager, IFastRundown, ISupportErrorInfo>,
199 public wsl::windows::service::wslc::COMImplClass<wsl::windows::service::wslc::WSLCSessionManagerImpl>
200 {
201 public:
202 NON_COPYABLE(WSLCSessionManager);
203 NON_MOVABLE(WSLCSessionManager);
204
205 WSLCSessionManager(wsl::windows::service::wslc::WSLCSessionManagerImpl* Impl);
206
207 IFACEMETHOD(GetVersion)(_Out_ WSLCVersion* Version) override;
208 IFACEMETHOD(CreateSession)(
209 const WSLCSessionSettings* WslcSessionSettings, WSLCSessionFlags Flags, IWarningCallback* WarningCallback, IWSLCSession** WslcSession) override;
210 IFACEMETHOD(EnterSession)(_In_ LPCWSTR DisplayName, _In_ LPCWSTR StoragePath, IWarningCallback* WarningCallback, IWSLCSession** WslcSession) override;
211 IFACEMETHOD(ListSessions)(_Out_ WSLCSessionListEntry** Sessions, _Out_ ULONG* SessionsCount) override;
212 IFACEMETHOD(OpenSession)(_In_ ULONG Id, _Out_ IWSLCSession** Session) override;
213 IFACEMETHOD(OpenSessionByName)(_In_ LPCWSTR DisplayName, _Out_ IWSLCSession** Session) override;
214
215 // ISupportErrorInfo: enables IErrorInfo marshaling across COM boundaries.
216 IFACEMETHOD(InterfaceSupportsErrorInfo)(_In_ REFIID riid) override;
217
218 // IWSLCCompatSessionManager.
219 IFACEMETHOD(GetVersion)(_Out_ WSLCCompatVersion* Version) override;
220 IFACEMETHOD(IsClientVersionSupported)(_In_ const WSLCCompatVersion* ClientVersion, _Out_ BOOL* IsSupported) override;
221 IFACEMETHOD(CreateSession)(
222 const WSLCCompatSessionSettings* Settings, WSLCSessionFlags Flags, IWSLCCompatWarningCallback* WarningCallback, IWSLCCompatSession** Session) override;
223 };