master
cpp 281 lines 9.33 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 Redirector.cpp
8
9 Abstract:
10
11 This file contains helpers for controlling the Plan 9 Redirector.
12
13 --*/
14
15 #include "precomp.h"
16 #include <p9rdr.h>
17 #include <afunix.h>
18 #include "Redirector.h"
19
20 namespace {
21 struct ConnectionSecurityContext
22 {
23 LUID LogonId;
24 LUID LinkedLogonId;
25 };
26
27 ConnectionSecurityContext GetUserLogonIds(_In_ HANDLE token)
28 {
29 const auto tokenGroups = wil::get_token_information<TOKEN_GROUPS_AND_PRIVILEGES>(token);
30
31 // Try to get the linked token. If that fails, just use the one token.
32 wil::unique_token_linked_token tokenInfo{};
33 if (FAILED(wil::get_token_information_nothrow(tokenInfo, token)))
34 {
35 return {tokenGroups->AuthenticationId, tokenGroups->AuthenticationId};
36 }
37
38 const auto linkedTokenGroups = wil::get_token_information<TOKEN_GROUPS_AND_PRIVILEGES>(tokenInfo.LinkedToken);
39 return {tokenGroups->AuthenticationId, linkedTokenGroups->AuthenticationId};
40 }
41 } // namespace
42
43 namespace wsl::windows::common::redirector {
44
45 constexpr wchar_t c_RedirectorServiceName[] = L"P9Rdr";
46
47 // Removes all connection targets.
48 void ClearConnectionTargets(HANDLE device)
49 {
50 filesystem::DeviceIoControl(device, IOCTL_P9RDR_CLEAR_CONNECTION_TARGETS);
51 }
52
53 // Opens the device object for the Plan 9 redirector.
54 wil::unique_hfile OpenRedirector()
55 {
56 UNICODE_STRING name{};
57 RtlInitUnicodeString(&name, P9RDR_DEVICE_NAME);
58 return filesystem::OpenRelativeFile(nullptr, &name, GENERIC_READ, FILE_OPEN, 0);
59 }
60
61 // Starts the Plan 9 mini-redirector.
62 bool StartRedirector(HANDLE device)
63 {
64 const NTSTATUS status = filesystem::DeviceIoControlNoThrow(device, IOCTL_P9RDR_START);
65 if (!NT_SUCCESS(status))
66 {
67 if (status != STATUS_REDIRECTOR_STARTED)
68 {
69 THROW_NTSTATUS(status);
70 }
71
72 return false;
73 }
74
75 return true;
76 }
77
78 // Starts the Plan 9 mini-redirector.
79 bool StartRedirector()
80 {
81 const auto rdr = OpenRedirector();
82 return StartRedirector(rdr.get());
83 }
84
85 // Starts the Plan 9 redirector system service.
86 bool StartRedirectorService()
87 {
88 const wil::unique_schandle manager{OpenSCManager(nullptr, nullptr, SC_MANAGER_CONNECT)};
89 THROW_LAST_ERROR_IF(!manager);
90
91 const wil::unique_schandle service{OpenService(manager.get(), c_RedirectorServiceName, SERVICE_START)};
92 THROW_LAST_ERROR_IF(!service);
93 if (!StartService(service.get(), 0, nullptr))
94 {
95 THROW_LAST_ERROR_IF(GetLastError() != ERROR_SERVICE_ALREADY_RUNNING);
96 return false;
97 }
98
99 return true;
100 }
101
102 // Make sure the Plan 9 Redirector device is present, the mini-redirector is started, and is in a
103 // clean state.
104 void EnsureRedirectorStarted()
105 {
106 const bool serviceStarted = StartRedirectorService();
107 const auto rdr = OpenRedirector();
108
109 // Clear any connection targets that may be left over e.g. if the WSL service crashed
110 // before.
111 // N.B. This isn't necessary if the redirector service was just started.
112 if (!serviceStarted)
113 {
114 ClearConnectionTargets(rdr.get());
115 }
116
117 // Always send the start ioctl, because even if the service was running this might not have been
118 // sent before.
119 StartRedirector(rdr.get());
120 }
121
122 // Adds a connection target to the Plan 9 Redirector.
123 void AddConnectionTarget(std::wstring_view name, LUID logonId, std::string_view aname, LX_UID_T uid, std::wstring_view unixSocketPath, const GUID& instanceId, ULONG port)
124 {
125 const auto nameBytes = as_bytes(gsl::make_span(name.data(), name.size()));
126 const auto anameBytes = as_bytes(gsl::make_span(aname.data(), aname.size()));
127 const auto unixSocketPathBytes = as_bytes(gsl::make_span(unixSocketPath.data(), unixSocketPath.size()));
128
129 const auto size = sizeof(P9RDR_ADD_CONNECTION_TARGET_INPUT) + nameBytes.size() + anameBytes.size() + unixSocketPathBytes.size();
130 std::vector<gsl::byte> buffer(size);
131
132 const auto addConnection = gslhelpers::get_struct<P9RDR_ADD_CONNECTION_TARGET_INPUT>(gsl::make_span(buffer));
133 if (!unixSocketPathBytes.empty())
134 {
135 // This is regular WSL, which uses a Unix socket.
136 const auto unixAddress = reinterpret_cast<PSOCKADDR_UN>(&addConnection->Address);
137
138 // The path in the sockaddr_un is not used, but it should not be empty. Just put the
139 // unqualified file name in there.
140 unixAddress->sun_family = AF_UNIX;
141 strcpy_s(unixAddress->sun_path, LXSS_PLAN9_UNIX_SOCKET_A);
142 }
143 else
144 {
145 // This is a VM mode instance, so use a Hyper-V socket.
146 const auto hvAddress = reinterpret_cast<PSOCKADDR_HV>(&addConnection->Address);
147 hvAddress->Family = AF_HYPERV;
148 hvAddress->VmId = instanceId;
149 hvAddress->ServiceId = HV_GUID_VSOCK_TEMPLATE;
150 hvAddress->ServiceId.Data1 = port;
151 }
152
153 addConnection->Uid = uid;
154 addConnection->LogonId = logonId;
155 addConnection->ShareNameLength = gsl::narrow_cast<USHORT>(name.length() * sizeof(wchar_t));
156 addConnection->ANameLength = gsl::narrow_cast<USHORT>(aname.length());
157
158 // Copy over the share name.
159 auto stringSpan = gsl::make_span(buffer).subspan(sizeof(P9RDR_ADD_CONNECTION_TARGET_INPUT));
160 gsl::copy(nameBytes, stringSpan);
161 stringSpan = stringSpan.subspan(nameBytes.size());
162
163 // Copy over the aname.
164 if (aname.size() > 0)
165 {
166 gsl::copy(anameBytes, stringSpan);
167 stringSpan = stringSpan.subspan(anameBytes.size());
168 }
169
170 // Copy over the unix socket path.
171 if (unixSocketPathBytes.size() > 0)
172 {
173 gsl::copy(unixSocketPathBytes, stringSpan);
174 }
175
176 // Send the command to the driver.
177 const auto rdr = OpenRedirector();
178 filesystem::DeviceIoControl(rdr.get(), IOCTL_P9RDR_ADD_CONNECTION_TARGET, buffer);
179 }
180
181 // Removes a connection target from the Plan 9 Redirector.
182 void RemoveConnectionTarget(std::wstring_view name, LUID logonId)
183 {
184 const auto nameBytes = as_bytes(gsl::make_span(name.data(), name.length()));
185 std::vector<gsl::byte> buffer(sizeof(P9RDR_REMOVE_CONNECTION_TARGET_INPUT) + nameBytes.size());
186
187 const auto removeConnection = gslhelpers::get_struct<P9RDR_REMOVE_CONNECTION_TARGET_INPUT>(gsl::make_span(buffer));
188 removeConnection->LogonId = logonId;
189
190 // Copy over the share name.
191 const auto stringSpan = gsl::make_span(buffer).subspan(sizeof(P9RDR_REMOVE_CONNECTION_TARGET_INPUT));
192 gsl::copy(nameBytes, stringSpan);
193
194 // Send the command to the driver.
195 const auto rdr = OpenRedirector();
196 const NTSTATUS status = filesystem::DeviceIoControlNoThrow(rdr.get(), IOCTL_P9RDR_REMOVE_CONNECTION_TARGET, buffer);
197
198 // If the share didn't exist, that's weird but not a failure.
199 if (!NT_SUCCESS(status) && status != STATUS_OBJECT_NAME_NOT_FOUND)
200 {
201 THROW_NTSTATUS(status);
202 }
203 }
204
205 // Registers a user-mode callback with the Plan 9 Redirector.
206 void RegisterUserCallback(HANDLE handle, gsl::span<gsl::byte> outputBuffer, LPOVERLAPPED overlapped)
207 {
208 if (!DeviceIoControl(
209 handle, IOCTL_P9RDR_REGISTER_USER_CALLBACK, nullptr, 0, outputBuffer.data(), gsl::narrow_cast<DWORD>(outputBuffer.size()), nullptr, overlapped))
210 {
211 THROW_LAST_ERROR_IF(GetLastError() != ERROR_IO_PENDING);
212 }
213 }
214
215 ConnectionTargetManager::ConnectionTargetManager(std::wstring_view name) : m_name{name}
216 {
217 }
218
219 // Registers connection targets for the specified logon ID and linked logon ID, if they're not
220 // already registered.
221 void ConnectionTargetManager::AddConnectionTarget(
222 HANDLE userToken, std::string_view aname, LX_UID_T uid, std::wstring_view unixSocketPath, const GUID& instanceId, ULONG port)
223 {
224 const auto security = GetUserLogonIds(userToken);
225 auto lock = m_lock.lock_exclusive();
226 if (!Contains(security.LogonId))
227 {
228 redirector::AddConnectionTarget(m_name, security.LogonId, aname, uid, unixSocketPath, instanceId, port);
229 m_targets.emplace_back(security.LogonId, std::string(aname), uid, std::wstring(unixSocketPath), instanceId, port);
230 }
231
232 // Checking the list also catches the case where the logon ID and linked logon ID are equal.
233 if (!Contains(security.LinkedLogonId))
234 {
235 redirector::AddConnectionTarget(m_name, security.LinkedLogonId, aname, uid, unixSocketPath, instanceId, port);
236 m_targets.emplace_back(security.LinkedLogonId, std::string(aname), uid, std::wstring(unixSocketPath), instanceId, port);
237 }
238 }
239
240 // Removes all connection targets associated with the instance.
241 void ConnectionTargetManager::RemoveAll()
242 {
243 auto lock = m_lock.lock_exclusive();
244 for (const auto& target : m_targets)
245 {
246 RemoveConnectionTarget(m_name, target.logonId);
247 }
248
249 m_targets.clear();
250 }
251
252 // Checks whether the list of targets contains the specified ID.
253 bool ConnectionTargetManager::Contains(LUID luid) const
254 {
255 const auto it =
256 std::find_if(m_targets.begin(), m_targets.end(), [&luid](auto& item) { return RtlEqualLuid(&luid, &item.logonId); });
257
258 return it != m_targets.end();
259 }
260
261 // Re-add all connection targets with the new UID.
262 void ConnectionTargetManager::UpdateUid(LX_UID_T uid)
263 {
264 auto lock = m_lock.lock_exclusive();
265
266 for (auto& target : m_targets)
267 {
268 if (target.uid == uid)
269 {
270 continue;
271 }
272
273 redirector::RemoveConnectionTarget(m_name, target.logonId);
274 redirector::AddConnectionTarget(
275 m_name, target.logonId, target.aname, uid, target.unixSocketPath, target.instanceId, target.port);
276
277 target.uid = uid;
278 }
279 }
280
281 } // namespace wsl::windows::common::redirector