master
cpp 846 lines 37.9 KB
Raw
1 // Copyright (C) Microsoft Corporation. All rights reserved.
2
3 #include "WslCoreFirewallSupport.h"
4
5 #include <ComputeNetwork.h>
6 #include <wil/com.h>
7
8 #include "string.hpp"
9 #include "WslCoreNetworkingSupport.h"
10
11 static constexpr auto c_hyperVFirewallLoopbackRuleIdPrefix_Old = L"WSA-IP-Loopback-Allow-Rule-1-";
12 static constexpr auto c_hyperVFirewallLoopbackRuleIdPrefix = L"WslCore-IP-Loopback-Allow-Rule-1-";
13 static constexpr auto c_hyperVFirewallLoopbackRuleName = L"WslCore Loopback Allow Rule";
14
15 static constexpr auto c_hyperVFirewallLocalSubnetRuleIdPrefix = L"WslCore-LocalSubnet-Allow-Rule-1-";
16 static constexpr auto c_hyperVFirewallLocalSubnetRuleName = L"WslCore LocalSubnet Allow Rule";
17
18 static constexpr auto c_hyperVFirewallIcmpV6RuleIdPrefix = L"WslCore-Allow-Inbound-ICMPv6-1-";
19 static constexpr auto c_hyperVFirewallIcmpV6RuleName = L"WslCore Inbound ICMPv6 Default Allow Rule";
20
21 static constexpr auto c_hyperVFirewallIcmpV4RuleIdPrefix = L"WslCore-Allow-Inbound-ICMPv4-1-";
22 static constexpr auto c_hyperVFirewallIcmpV4RuleName = L"WslCore Inbound ICMPv4 Default Allow Rule";
23
24 // Host Firewall rule to allow traffic to SharedAccess service.
25 static constexpr auto c_sharedAccessRuleId = L"WSLCore-SharedAccess-Allow-Rule";
26 static constexpr auto c_sharedAccessRuleName = L"WSLCore SharedAccess Allow Rule";
27 static constexpr auto c_sharedAccessService = L"SharedAccess";
28
29 static constexpr auto c_protocolUDP = L"UDP";
30 static constexpr auto c_svchostApplication = L"%SYSTEMROOT%\\System32\\svchost.exe";
31
32 // Regkey to control hyper-v firewall being disabled
33 static constexpr auto c_mpssvcRegPath = L"SYSTEM\\CurrentControlSet\\Services\\MpsSvc\\Parameters";
34 static constexpr auto c_mpssvcRegDisableKey = L"HyperVFirewallDisable";
35
36 // Constants corresponding to firewall WMI values
37 static constexpr auto c_directionInbound = 1;
38 static constexpr auto c_actionAllow = 2;
39 static constexpr auto c_ruleEnabled = 1;
40 static constexpr auto c_ruleDisabled = 0;
41 static constexpr auto c_true = 1;
42
43 // ICMP "port" constants
44 static constexpr auto c_icmpv6NeighborSolicitation = L"135";
45 static constexpr auto c_icmpv6NeighborAdvertisement = L"136";
46 static constexpr auto c_icmpv6PortDestinationUnreachable = L"1";
47 static constexpr auto c_icmpv6PortTimeExceeded = L"3";
48 static constexpr auto c_icmpv4PortDestinationUnreachable = L"3";
49 static constexpr auto c_icmpv4PortTimeExceeded = L"11";
50
51 // mDNS related constants
52 static constexpr auto c_hyperVFirewallMdnsIpv4RuleIdPrefix = L"WslCore-Allow-Inbound-mDNS-IPv4-1-";
53 static constexpr auto c_hyperVFirewallMdnsIpv4RuleName = L"WslCore Inbound IPv4 mDNS Default Allow Rule";
54
55 static constexpr auto c_hyperVFirewallMdnsIpv6RuleIdPrefix = L"WslCore-Allow-Inbound-mDNS-IPv6-1-";
56 static constexpr auto c_hyperVFirewallMdnsIpv6RuleName = L"WslCore Inbound IPv6 mDNS Default Allow Rule";
57
58 static constexpr auto c_mdnsPort = L"5353";
59 static constexpr auto c_mdnsIpv4Address = L"224.0.0.251";
60 static constexpr auto c_mdnsIpv6Address = L"ff02::fb";
61
62 namespace wsl::core::networking {
63 std::wstring MakeLoopbackFirewallRuleId(const GUID& guid)
64 {
65 return c_hyperVFirewallLoopbackRuleIdPrefix +
66 wsl::shared::string::GuidToString<wchar_t>(guid, wsl::shared::string::GuidToStringFlags::None);
67 }
68
69 std::wstring MakeLocalSubnetFirewallRuleId(const GUID& guid)
70 {
71 return c_hyperVFirewallLocalSubnetRuleIdPrefix +
72 wsl::shared::string::GuidToString<wchar_t>(guid, wsl::shared::string::GuidToStringFlags::None);
73 }
74
75 std::wstring MakeICMPv6FirewallRuleId(const GUID& guid)
76 {
77 return c_hyperVFirewallIcmpV6RuleIdPrefix + wsl::shared::string::GuidToString<wchar_t>(guid, wsl::shared::string::GuidToStringFlags::None);
78 }
79
80 std::wstring MakeICMPv4FirewallRuleId(const GUID& guid)
81 {
82 return c_hyperVFirewallIcmpV4RuleIdPrefix + wsl::shared::string::GuidToString<wchar_t>(guid, wsl::shared::string::GuidToStringFlags::None);
83 }
84
85 std::wstring MakeMdnsIpv4FirewallRuleId(const GUID& guid)
86 {
87 return c_hyperVFirewallMdnsIpv4RuleIdPrefix +
88 wsl::shared::string::GuidToString<wchar_t>(guid, wsl::shared::string::GuidToStringFlags::None);
89 }
90
91 std::wstring MakeMdnsIpv6FirewallRuleId(const GUID& guid)
92 {
93 return c_hyperVFirewallMdnsIpv6RuleIdPrefix +
94 wsl::shared::string::GuidToString<wchar_t>(guid, wsl::shared::string::GuidToStringFlags::None);
95 }
96
97 // if enabling Hyper-V Firewall, ensure the following rules are always added:
98 // a) ICMP rules for inbound responses, without these we risk breaking basic connectivity and/or app compat
99 // b) inbound rules to allow mDNS traffic. Note: Host firewall also has rules to allow inbound mDNS traffic but those
100 // are scoped to the Windows dnscache service so they can't be automatically translated to Hyper-V firewall
101 std::vector<FirewallRuleConfiguration> MakeDefaultFirewallRuleConfiguration(const GUID& guid)
102 {
103 std::vector<FirewallRuleConfiguration> firewallConfiguration;
104
105 FirewallRuleConfiguration icmpV6AllowRule{MakeICMPv6FirewallRuleId(guid).c_str()};
106 icmpV6AllowRule.RuleName = wil::make_bstr(c_hyperVFirewallIcmpV6RuleName);
107 icmpV6AllowRule.Protocol = wil::make_bstr(L"ICMPv6");
108 icmpV6AllowRule.LocalPorts.emplace_back(wil::make_bstr(c_icmpv6NeighborSolicitation));
109 icmpV6AllowRule.LocalPorts.emplace_back(wil::make_bstr(c_icmpv6NeighborAdvertisement));
110 icmpV6AllowRule.LocalPorts.emplace_back(wil::make_bstr(c_icmpv6PortDestinationUnreachable));
111 icmpV6AllowRule.LocalPorts.emplace_back(wil::make_bstr(c_icmpv6PortTimeExceeded));
112 icmpV6AllowRule.RemoteAddresses.clear(); // all remote addresses
113 icmpV6AllowRule.RuleOperation = FirewallRuleOperation::Add;
114 firewallConfiguration.emplace_back(icmpV6AllowRule);
115
116 FirewallRuleConfiguration icmpV4AllowRule{MakeICMPv4FirewallRuleId(guid).c_str()};
117 icmpV4AllowRule.RuleName = wil::make_bstr(c_hyperVFirewallIcmpV4RuleName);
118 icmpV4AllowRule.Protocol = wil::make_bstr(L"ICMPv4");
119 icmpV4AllowRule.LocalPorts.emplace_back(wil::make_bstr(c_icmpv4PortDestinationUnreachable));
120 icmpV4AllowRule.LocalPorts.emplace_back(wil::make_bstr(c_icmpv4PortTimeExceeded));
121 icmpV4AllowRule.RemoteAddresses.clear(); // all remote addresses
122 icmpV4AllowRule.RuleOperation = FirewallRuleOperation::Add;
123 firewallConfiguration.emplace_back(icmpV4AllowRule);
124
125 FirewallRuleConfiguration mdnsIPv4AllowRule{MakeMdnsIpv4FirewallRuleId(guid).c_str()};
126 mdnsIPv4AllowRule.RuleName = wil::make_bstr(c_hyperVFirewallMdnsIpv4RuleName);
127 mdnsIPv4AllowRule.Protocol = wil::make_bstr(c_protocolUDP);
128 mdnsIPv4AllowRule.LocalPorts.emplace_back(wil::make_bstr(c_mdnsPort));
129 mdnsIPv4AllowRule.LocalAddresses.emplace_back(wil::make_bstr(c_mdnsIpv4Address));
130 mdnsIPv4AllowRule.RemoteAddresses.clear(); // all remote addresses
131 mdnsIPv4AllowRule.RuleOperation = FirewallRuleOperation::Add;
132 firewallConfiguration.emplace_back(mdnsIPv4AllowRule);
133
134 FirewallRuleConfiguration mdnsIPv6AllowRule{MakeMdnsIpv6FirewallRuleId(guid).c_str()};
135 mdnsIPv6AllowRule.RuleName = wil::make_bstr(c_hyperVFirewallMdnsIpv6RuleName);
136 mdnsIPv6AllowRule.Protocol = wil::make_bstr(c_protocolUDP);
137 mdnsIPv6AllowRule.LocalPorts.emplace_back(wil::make_bstr(c_mdnsPort));
138 mdnsIPv6AllowRule.LocalAddresses.emplace_back(wil::make_bstr(c_mdnsIpv6Address));
139 mdnsIPv6AllowRule.RemoteAddresses.clear(); // all remote addresses
140 mdnsIPv6AllowRule.RuleOperation = FirewallRuleOperation::Add;
141 firewallConfiguration.emplace_back(mdnsIPv6AllowRule);
142
143 return firewallConfiguration;
144 }
145
146 FirewallRuleConfiguration MakeLoopbackFirewallRuleConfiguration(const std::wstring& ruleId)
147 {
148 return {ruleId.c_str(), c_hyperVFirewallLoopbackRuleName};
149 }
150
151 FirewallRuleConfiguration MakeLocalSubnetFirewallRuleConfiguration(const std::wstring& ruleId)
152 {
153 return {ruleId.c_str(), c_hyperVFirewallLocalSubnetRuleName};
154 }
155
156 // We can require the updated Firewall API be available (on all OS's that get the update)
157 // Thus we must indicate to the caller what version of Hyper-V Firewall is currently running.
158 HyperVFirewallSupport GetHyperVFirewallSupportVersion(const FirewallConfiguration& firewallConfig) noexcept
159 try
160 {
161 // Check to see if Hyper-V firewall is disabled via the registry.
162 DWORD localFirewallDisabled = 0;
163 wil::ResultFromException([&] {
164 localFirewallDisabled = windows::common::registry::ReadDword(HKEY_LOCAL_MACHINE, c_mpssvcRegPath, c_mpssvcRegDisableKey, 0);
165 });
166 if (localFirewallDisabled == 1)
167 {
168 WSL_LOG("GetHyperVFirewallSupportVersion: disabled by registry [HyperVFirewallSupport::None]");
169 return HyperVFirewallSupport::None;
170 }
171
172 // There are no APIs to directly query which level of Hyper-V firewall support we have.
173 // Instead, we check for availability of specific firewall objects/fields present to
174 // determine if the requested functionality is supported or not.
175 //
176 // Currently, there are 3 possible levels of Hyper-V firewall OS support:
177 // 1 - No Hyper-V firewall OS support.
178 // 2 - Initial Hyper-V firewall support (Support for mirrored mode only).
179 // To check for this support, we query for the 'MSFT_NetFirewallHyperVVMCreator' object.
180 // 3 - Enterprise Hyper-V firewall support (Support for NAT mode, configuring default settings values, and configuring per-profile configs).
181 // To check for this support, we query for the 'MSFT_NetFirewallHyperVProfile' object.
182
183 // Connect to the root\standardcimv2 namespace with the current user and obtain pointer to make IWbemServices calls.
184 const auto locator = wil::CoCreateInstance<WbemLocator, IWbemLocator>();
185 wil::com_ptr<IWbemServices> wbemService;
186 THROW_IF_FAILED(locator->ConnectServer(
187 wil::make_bstr(L"ROOT\\standardcimv2").get(), nullptr, nullptr, nullptr, 0, nullptr, nullptr, &wbemService));
188
189 // Set the IWbemServices proxy so that impersonation of the user (client) occurs.
190 THROW_IF_FAILED(CoSetProxyBlanket(
191 wbemService.get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_NONE));
192
193 HRESULT hr{};
194 wil::com_ptr<IWbemClassObject> baseObject;
195
196 // Query for initial Hyper-V firewall OS support.
197 hr = wbemService->GetObjectW(
198 wil::make_bstr(L"MSFT_NetFirewallHyperVVMCreator").get(), WBEM_FLAG_RETURN_WBEM_COMPLETE, nullptr, &baseObject, nullptr);
199 if (FAILED(hr))
200 {
201 WSL_LOG(
202 "GetHyperVFirewallSupportVersion: MSFT_NetFirewallHyperVVMCreator failed to be instantiated "
203 "[HyperVFirewallSupport::None]",
204 TraceLoggingValue(hr));
205 return HyperVFirewallSupport::None;
206 }
207
208 // Query for version 2 of the Hyper-V Firewall
209 // We query for object instances instead of only getting the object class as this will return an error
210 // if the OS changes are present but the Hyper-V Firewall feature is disabled.
211 wil::com_ptr<IEnumWbemClassObject> enumObjects;
212 hr = wbemService->ExecQuery(
213 wil::make_bstr(L"WQL").get(), wil::make_bstr(L"SELECT * FROM MSFT_NetFirewallHyperVProfile").get(), WBEM_FLAG_RETURN_WBEM_COMPLETE, nullptr, &enumObjects);
214 if (FAILED(hr))
215 {
216 WSL_LOG(
217 "GetHyperVFirewallSupportVersion: Query MSFT_NetFirewallHyperVProfile instances failed "
218 "[HyperVFirewallSupport::Version1]",
219 TraceLoggingValue(hr));
220 return HyperVFirewallSupport::Version1;
221 }
222
223 // If we reached here, we were able to query the Version2 objects
224 WSL_LOG("GetHyperVFirewallSupportVersion [HyperVFirewallSupport::Version2]");
225 return HyperVFirewallSupport::Version2;
226 }
227 catch (...)
228 {
229 LOG_CAUGHT_EXCEPTION();
230 WSL_LOG(
231 "wsl::core::networking::GetHyperVFirewallSupportVersion [HyperVFirewallSupport::None]",
232 TraceLoggingValue(ToString(firewallConfig.DefaultLoopbackPolicy), "defaultLoopbackPolicy"));
233
234 return HyperVFirewallSupport::None;
235 }
236
237 wil::com_ptr<IWbemClassObject> SpawnWbemObjectInstance(
238 _In_ PCWSTR className, const wil::shared_bstr& instanceId, _In_opt_ IWbemContext* wbemContext, const wil::com_ptr<IWbemServices>& wbemService)
239 {
240 // Fetch the class definition
241 wil::com_ptr<IWbemClassObject> baseObject;
242 THROW_IF_FAILED(wbemService->GetObject(wil::make_bstr(className).get(), WBEM_FLAG_RETURN_WBEM_COMPLETE, wbemContext, &baseObject, nullptr));
243
244 // Create the new object instance
245 wil::com_ptr<IWbemClassObject> newObject;
246 THROW_IF_FAILED(baseObject->SpawnInstance(0, &newObject));
247
248 // Non-RAII variant as we are not owning the resource here
249 VARIANT v{};
250 v.vt = VT_BSTR;
251 v.bstrVal = instanceId.get();
252 THROW_IF_FAILED(newObject->Put(L"InstanceID", 0, &v, 0));
253
254 return newObject;
255 }
256
257 void WriteWMIInstance(_In_opt_ IWbemContext* wbemContext, const wil::com_ptr<IWbemServices>& wbemService, const wil::com_ptr<IWbemClassObject>& newObject)
258 {
259 constexpr WBEM_CHANGE_FLAG_TYPE changeType = WBEM_FLAG_CREATE_OR_UPDATE;
260 wil::com_ptr<IWbemCallResult> wmiResult;
261 THROW_IF_FAILED_MSG(
262 wbemService->PutInstance(newObject.get(), changeType, wbemContext, &wmiResult), "Failed to execute PutInstance WMI call");
263
264 long callStatus;
265 THROW_IF_FAILED_MSG(wmiResult->GetCallStatus(WBEM_INFINITE, &callStatus), "Failed to retrieve the WMI call status");
266 THROW_IF_FAILED_MSG(callStatus, "Failed to create object instance");
267 }
268
269 HRESULT RegisterHyperVFirewallVmCreator(const GUID& vmCreatorId, const std::wstring& vmCreatorFriendlyName) noexcept
270 {
271 PCSTR executionStep = "";
272 try
273 {
274 executionStep = "CoCreateInstance";
275 auto locator = wil::CoCreateInstance<WbemLocator, IWbemLocator>();
276
277 executionStep = "ConnectServer";
278 // Connect to the root\standardcimv2 namespace with the current user and obtain pointer to make IWbemServices calls.
279 wil::com_ptr<IWbemServices> wbemService;
280 THROW_IF_FAILED(locator->ConnectServer(
281 wil::make_bstr(L"ROOT\\standardcimv2").get(), nullptr, nullptr, nullptr, 0, nullptr, nullptr, &wbemService));
282
283 executionStep = "CoSetProxyBlanket";
284 // Set the IWbemServices proxy so that impersonation of the user (client) occurs.
285 THROW_IF_FAILED(CoSetProxyBlanket(
286 wbemService.get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_NONE));
287
288 executionStep = "GetNetFirewallHyperVVMCreator";
289 // Fetch the class definition
290 wil::com_ptr<IWbemClassObject> baseObject;
291 THROW_IF_FAILED(wbemService->GetObject(
292 wil::make_bstr(L"MSFT_NetFirewallHyperVVMCreator").get(), WBEM_FLAG_RETURN_WBEM_COMPLETE, nullptr, &baseObject, nullptr));
293
294 executionStep = "GetRegisterHyperVVMCreator";
295 // Get the parameter object
296 wil::com_ptr<IWbemClassObject> paramsObject;
297 THROW_IF_FAILED(baseObject->GetMethod(wil::make_bstr(L"RegisterHyperVVMCreator").get(), 0, &paramsObject, nullptr));
298
299 executionStep = "SpawnInstance";
300 // Spawn instance for the parameter object
301 wil::com_ptr<IWbemClassObject> paramsInstance;
302 THROW_IF_FAILED(paramsObject->SpawnInstance(0, &paramsInstance));
303
304 // Fill the parameter object
305 wil::unique_variant v;
306
307 executionStep = "PutVMCreatorId";
308 // VM Creator Id
309 std::wstring vmCreatorIdString =
310 wsl::shared::string::GuidToString<wchar_t>(vmCreatorId, wsl::shared::string::GuidToStringFlags::AddBraces);
311 v.vt = VT_BSTR;
312 v.bstrVal = wil::make_bstr(vmCreatorIdString.c_str()).release();
313 THROW_IF_FAILED(paramsInstance->Put(L"VMCreatorId", 0, &v, 0));
314 v.reset();
315
316 executionStep = "PutFriendlyName";
317 // Friendly name
318 v.vt = VT_BSTR;
319 v.bstrVal = wil::make_bstr(vmCreatorFriendlyName.c_str()).release();
320 THROW_IF_FAILED(paramsInstance->Put(L"FriendlyName", 0, &v, 0));
321 v.reset();
322
323 executionStep = "NetFirewallHyperVVMCreator::RegisterHyperVVMCreator";
324 // making the recommended semi-synchronous call into WMI
325 // which requires waiting for the completion with the resultObject
326 wil::com_ptr<IWbemCallResult> resultObject;
327 THROW_IF_FAILED(wbemService->ExecMethod(
328 wil::make_bstr(L"MSFT_NetFirewallHyperVVMCreator").get(),
329 wil::make_bstr(L"RegisterHyperVVMCreator").get(),
330 WBEM_FLAG_RETURN_IMMEDIATELY,
331 nullptr,
332 paramsInstance.get(),
333 nullptr,
334 &resultObject));
335
336 executionStep = "GetResultObject";
337 wil::com_ptr<IWbemClassObject> outParams;
338 auto result = resultObject->GetResultObject(WBEM_INFINITE, &outParams);
339 WSL_LOG(
340 "RegisterHyperVFirewallVmCreator [GetResultObject]",
341 TraceLoggingValue(
342 result == WBEM_E_ALREADY_EXISTS ? "WBEM_E_ALREADY_EXISTS" : std::to_string(result).c_str(), "result"));
343
344 if (result == WBEM_E_ALREADY_EXISTS)
345 {
346 // the method immediately returned already exists - we're fine to return now.
347 result = S_OK;
348 }
349 THROW_IF_FAILED_MSG(result, "Failed to register hyper-v firewall vm creator");
350 return S_OK;
351 }
352 catch (...)
353 {
354 auto hr = wil::ResultFromCaughtException();
355 WSL_LOG(
356 "RegisterHyperVFirewallVmCreatorFailed",
357 TraceLoggingValue(hr, "result"),
358 TraceLoggingValue(executionStep, "executionStep"));
359
360 return hr;
361 }
362 }
363
364 HRESULT ConfigureHyperVFirewallLoopbackAllow(const GUID& vmCreatorId) noexcept
365 {
366 PCSTR executionStep = "";
367 try
368 {
369 executionStep = "CoCreateInstance";
370 auto locator = wil::CoCreateInstance<WbemLocator, IWbemLocator>();
371
372 executionStep = "CoCreateInstanceWbemContext";
373 // Create WbemContext for SystemDefaults
374 // SystemDefaults are configured with lowest priority, so admin configuration can overwrite it
375 auto wbemContext = wil::CoCreateInstance<WbemContext, IWbemContext>();
376 wil::unique_variant v;
377 v.vt = VT_BSTR;
378 v.bstrVal = wil::make_bstr(L"SystemDefaults").release();
379 executionStep = "SetPolicyStore";
380 THROW_IF_FAILED(wbemContext->SetValue(L"PolicyStore", 0, &v));
381 v.reset();
382
383 executionStep = "ConnectServer";
384 // Connect to the root\standardcimv2 namespace with the current user and obtain pointer to make IWbemServices calls.
385 wil::com_ptr<IWbemServices> wbemService;
386 THROW_IF_FAILED(locator->ConnectServer(
387 wil::make_bstr(L"ROOT\\standardcimv2").get(), nullptr, nullptr, nullptr, 0, nullptr, wbemContext.get(), &wbemService));
388
389 executionStep = "CoSetProxyBlanket";
390 // Set the IWbemServices proxy so that impersonation of the user (client) occurs.
391 THROW_IF_FAILED(CoSetProxyBlanket(
392 wbemService.get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_NONE));
393
394 executionStep = "SpawnNetFirewallHyperVVMSetting";
395 // Spawn instance
396 wil::shared_bstr vmCreatorIdString = wil::make_bstr(
397 wsl::shared::string::GuidToString<wchar_t>(vmCreatorId, wsl::shared::string::GuidToStringFlags::AddBraces).c_str());
398 wil::com_ptr<IWbemClassObject> settingsObject =
399 SpawnWbemObjectInstance(L"MSFT_NetFirewallHyperVVMSetting", vmCreatorIdString, wbemContext.get(), wbemService);
400
401 executionStep = "PutName";
402 v.vt = VT_BSTR;
403 v.bstrVal = vmCreatorIdString.get();
404 const auto hr = settingsObject->Put(L"Name", 0, &v, 0);
405 v.release(); // the variant should not free the bstr, it's owned by the wil::shared_bstr
406 THROW_IF_FAILED(hr);
407
408 executionStep = "PutLoopbackEnabled";
409 v.vt = VT_I4;
410 v.lVal = c_true;
411 THROW_IF_FAILED(settingsObject->Put(L"LoopbackEnabled", 0, &v, 0));
412 v.reset();
413
414 executionStep = "WriteWMIInstance";
415 WriteWMIInstance(wbemContext.get(), wbemService, settingsObject);
416
417 return S_OK;
418 }
419 catch (...)
420 {
421 auto hr = wil::ResultFromCaughtException();
422 WSL_LOG(
423 "ConfigureHyperVFirewallLoopbackAllowFailed",
424 TraceLoggingValue(hr, "result"),
425 TraceLoggingValue(executionStep, "executionStep"));
426
427 return hr;
428 }
429 }
430
431 void ConfigureHyperVFirewall(const FirewallConfiguration& firewallConfig, const std::wstring& vmCreatorFriendlyName) noexcept
432 try
433 {
434 if (!firewallConfig.Enabled())
435 {
436 return;
437 }
438 const auto coInit = InitializeCOMState();
439
440 // Register the input ID with the firewall service.
441 // If this fails, still proceed with rule creation, as the rules
442 // will still be enforced without the vm creator registered
443 LOG_IF_FAILED_MSG(
444 RegisterHyperVFirewallVmCreator(firewallConfig.VmCreatorId.value(), vmCreatorFriendlyName),
445 "RegisterHyperVFirewallVmCreator");
446
447 // Configure firewall settings
448 // The OS default is to block loopback. Configure the loopback setting only if the client requests a configuration different than OS default.
449 if (FirewallAction::Allow == firewallConfig.DefaultLoopbackPolicy)
450 {
451 LOG_IF_FAILED_MSG(
452 ConfigureHyperVFirewallLoopbackAllow(firewallConfig.VmCreatorId.value()), "ConfigureHyperVFirewallLoopbackAllow");
453 }
454
455 // Configure firewall rules
456 for (const auto& firewallRule : firewallConfig.Rules)
457 {
458 if (firewallRule.RuleOperation == wsl::core::FirewallRuleOperation::Add)
459 {
460 if (FAILED_LOG(AddHyperVFirewallRule(firewallConfig.VmCreatorId.value(), firewallRule)))
461 {
462 // Due to a Windows bug, certain rules do not accept local ports.
463 // If this error is encountered here, we try to instead add a less scoped
464 // version of the rule to ensure necessary traffic is still allowed
465 FirewallRuleConfiguration currFirewallRule = firewallRule;
466 currFirewallRule.LocalPorts.clear();
467 LOG_IF_FAILED_MSG(
468 AddHyperVFirewallRule(firewallConfig.VmCreatorId.value(), currFirewallRule),
469 "AddHyperVFirewallRule for %ls",
470 currFirewallRule.RuleId.get());
471 }
472 }
473 else if (firewallRule.RuleOperation == wsl::core::FirewallRuleOperation::Delete)
474 {
475 LOG_IF_FAILED_MSG(
476 RemoveHyperVFirewallRule(firewallRule.RuleId.get()), "RemoveHyperVFirewallRule for %ls", firewallRule.RuleId.get());
477 }
478 else
479 {
480 // Unexpected rule operation type
481 WI_ASSERT(false);
482 }
483 }
484
485 // WSL may have previously added this rule (which has since been renamed). Remove it if it is present.
486 const auto oldLoopbackRuleId =
487 c_hyperVFirewallLoopbackRuleIdPrefix_Old +
488 wsl::shared::string::GuidToString<wchar_t>(firewallConfig.VmCreatorId.value(), wsl::shared::string::GuidToStringFlags::None);
489
490 LOG_IF_FAILED_MSG(RemoveHyperVFirewallRule(oldLoopbackRuleId), "RemoveHyperVFirewallRule for %ls", oldLoopbackRuleId.c_str());
491 }
492 CATCH_LOG()
493
494 HRESULT AddHyperVFirewallRule(const GUID& vmCreatorId, const wsl::core::FirewallRuleConfiguration& firewallRule) noexcept
495 {
496 PCSTR executionStep = "";
497 try
498 {
499 executionStep = "CoCreateInstance";
500 auto locator = wil::CoCreateInstance<WbemLocator, IWbemLocator>();
501
502 executionStep = "ConnectServer";
503 // Connect to the root\standardcimv2 namespace with the current user and obtain pointer to make IWbemServices calls.
504 wil::com_ptr<IWbemServices> wbemService;
505 THROW_IF_FAILED(locator->ConnectServer(
506 wil::make_bstr(L"ROOT\\standardcimv2").get(), nullptr, nullptr, nullptr, 0, nullptr, nullptr, &wbemService));
507
508 executionStep = "CoSetProxyBlanket";
509 // Set the IWbemServices proxy so that impersonation of the user (client) occurs.
510 THROW_IF_FAILED(CoSetProxyBlanket(
511 wbemService.get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_NONE));
512
513 executionStep = "SpawnNetFirewallHyperVRule";
514 wil::com_ptr<IWbemClassObject> ruleObject =
515 SpawnWbemObjectInstance(L"MSFT_NetFirewallHyperVRule", firewallRule.RuleId, nullptr, wbemService);
516
517 executionStep = "PutInstanceID";
518 // Fill the object
519 wil::unique_variant v;
520 v.vt = VT_BSTR;
521 v.bstrVal = firewallRule.RuleId.get();
522 HRESULT hr = ruleObject->Put(L"InstanceID", 0, &v, 0);
523 v.release(); // the variant should not free the bstr, it's owned by the wil::shared_bstr
524 THROW_IF_FAILED(hr);
525
526 executionStep = "PutElementName";
527 v.vt = VT_BSTR;
528 v.bstrVal = firewallRule.RuleName.get();
529 hr = ruleObject->Put(L"ElementName", 0, &v, 0);
530 v.release(); // the variant should not free the bstr, it's owned by the wil::shared_bstr
531 THROW_IF_FAILED(hr);
532
533 executionStep = "PutDirection";
534 v.vt = VT_I4;
535 v.lVal = c_directionInbound;
536 THROW_IF_FAILED(ruleObject->Put(L"Direction", 0, &v, 0));
537 v.reset();
538
539 executionStep = "PutVMCreatorId";
540 std::wstring vmCreatorIdString =
541 wsl::shared::string::GuidToString<wchar_t>(vmCreatorId, wsl::shared::string::GuidToStringFlags::AddBraces);
542 v.vt = VT_BSTR;
543 v.bstrVal = wil::make_bstr(vmCreatorIdString.c_str()).release();
544 THROW_IF_FAILED(ruleObject->Put(L"VMCreatorId", 0, &v, 0));
545 v.reset();
546
547 executionStep = "PutAction";
548 v.vt = VT_I4;
549 v.lVal = c_actionAllow;
550 THROW_IF_FAILED(ruleObject->Put(L"Action", 0, &v, 0));
551 v.reset();
552
553 executionStep = "PutEnabled";
554 v.vt = VT_I4;
555 v.lVal = c_ruleEnabled;
556 THROW_IF_FAILED(ruleObject->Put(L"Enabled", 0, &v, 0));
557 v.reset();
558
559 executionStep = "PutProtocol";
560 if (firewallRule.Protocol.is_valid())
561 {
562 v.vt = VT_BSTR;
563 v.bstrVal = firewallRule.Protocol.get();
564 hr = ruleObject->Put(L"Protocol", 0, &v, 0);
565 v.release(); // the variant should not free the bstr, it's owned by the wil::shared_bstr
566 THROW_IF_FAILED(hr);
567 }
568
569 executionStep = "PutLocalPorts";
570 if (!firewallRule.LocalPorts.empty())
571 {
572 // Convert to a safe array for usage in WMI
573 CComSafeArray<BSTR> localPortsArray;
574 THROW_IF_FAILED(localPortsArray.Create());
575 for (const auto& localPort : firewallRule.LocalPorts)
576 {
577 THROW_IF_FAILED(localPortsArray.Add(localPort.get()));
578 }
579 v.vt = (VT_BSTR | VT_ARRAY);
580 v.parray = localPortsArray.Detach();
581 THROW_IF_FAILED(ruleObject->Put(L"LocalPorts", 0, &v, 0));
582 v.reset();
583 }
584
585 executionStep = "PutLocalAddresses";
586 if (!firewallRule.LocalAddresses.empty())
587 {
588 // Convert to a safe array for usage in WMI
589 CComSafeArray<BSTR> localAddressesArray;
590 THROW_IF_FAILED(localAddressesArray.Create());
591 for (const auto& localAddress : firewallRule.LocalAddresses)
592 {
593 THROW_IF_FAILED(localAddressesArray.Add(localAddress.get()));
594 }
595 v.vt = (VT_BSTR | VT_ARRAY);
596 v.parray = localAddressesArray.Detach();
597 THROW_IF_FAILED(ruleObject->Put(L"LocalAddresses", 0, &v, 0));
598 v.reset();
599 }
600
601 executionStep = "PutRemoteAddresses";
602 if (!firewallRule.RemoteAddresses.empty())
603 {
604 // Convert to a safe array for usage in WMI
605 CComSafeArray<BSTR> remoteAddressesArray;
606 THROW_IF_FAILED(remoteAddressesArray.Create());
607 for (const auto& remoteAddress : firewallRule.RemoteAddresses)
608 {
609 THROW_IF_FAILED(remoteAddressesArray.Add(remoteAddress.get()));
610 }
611 v.vt = (VT_BSTR | VT_ARRAY);
612 v.parray = remoteAddressesArray.Detach();
613 THROW_IF_FAILED(ruleObject->Put(L"RemoteAddresses", 0, &v, 0));
614 v.reset();
615 }
616
617 executionStep = "WriteWMIInstance";
618 WriteWMIInstance(nullptr, wbemService, ruleObject);
619
620 return S_OK;
621 }
622 catch (...)
623 {
624 auto hr = wil::ResultFromCaughtException();
625 WSL_LOG("AddHyperVFirewallRuleFailed", TraceLoggingValue(hr, "result"), TraceLoggingValue(executionStep, "executionStep"));
626 return hr;
627 }
628 }
629
630 HRESULT RemoveHyperVFirewallRule(const std::wstring& ruleId) noexcept
631 {
632 PCSTR executionStep = "";
633 try
634 {
635 executionStep = "CoCreateInstance";
636 const auto locator = wil::CoCreateInstance<WbemLocator, IWbemLocator>();
637
638 executionStep = "ConnectServer";
639 // Connect to the root\standardcimv2 namespace with the current user and obtain pointer to make IWbemServices calls.
640 wil::com_ptr<IWbemServices> wbemService;
641 THROW_IF_FAILED(locator->ConnectServer(
642 wil::make_bstr(L"ROOT\\standardcimv2").get(), nullptr, nullptr, nullptr, 0, nullptr, nullptr, &wbemService));
643
644 executionStep = "CoSetProxyBlanket";
645 // Set the IWbemServices proxy so that impersonation of the user (client) occurs.
646 THROW_IF_FAILED(CoSetProxyBlanket(
647 wbemService.get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_NONE));
648
649 // Create the rule deletion query string
650 const std::wstring ruleDeletionString = std::format(L"MSFT_NetFirewallHyperVRule.InstanceId=\"{}\"", ruleId);
651 const wil::unique_bstr ruleDeletionBstr = wil::make_bstr(ruleDeletionString.c_str());
652
653 executionStep = "DeleteInstance";
654 // Delete the instance to WMI
655 wil::com_ptr<IWbemCallResult> wmiResult;
656 THROW_IF_FAILED_MSG(
657 wbemService->DeleteInstance(ruleDeletionBstr.get(), 0, nullptr, &wmiResult),
658 "Failed to execute the WMI call for deleting the hyper-v firewall ruleId=%ws",
659 ruleId.c_str());
660
661 executionStep = "GetCallStatus";
662 long callStatus;
663 THROW_IF_FAILED_MSG(
664 wmiResult->GetCallStatus(WBEM_INFINITE, &callStatus),
665 "Failed to retrieve the WMI call status for deleting the hyper-v firewall ruleId=%ws",
666 ruleId.c_str());
667
668 // Ignore error not found, as this indicates the rule is already deleted
669 if (callStatus == WBEM_E_NOT_FOUND)
670 {
671 callStatus = S_OK;
672 }
673 THROW_IF_FAILED_MSG(callStatus, "Failed to delete hyper-v firewall rule with ruleId=%ws", ruleId.c_str());
674 return S_OK;
675 }
676 catch (...)
677 {
678 auto hr = wil::ResultFromCaughtException();
679 WSL_LOG(
680 "RemoveHyperVFirewallRuleFailed", TraceLoggingValue(hr, "result"), TraceLoggingValue(executionStep, "executionStep"));
681
682 return hr;
683 }
684 }
685
686 HRESULT AddHostFirewallRule(const wsl::core::FirewallRuleConfiguration& firewallRule) noexcept
687 try
688 {
689 auto wbemLocator = wil::CoCreateInstance<WbemLocator, IWbemLocator>();
690
691 // Create WbemContext for ActiveStore
692 // ActiveStore is used so that the rules are not persisted and therefore not leaked upon uninstall
693 auto wbemContext = wil::CoCreateInstance<WbemContext, IWbemContext>();
694 wil::unique_variant v;
695 v.vt = VT_BSTR;
696 v.bstrVal = wil::make_bstr(L"ActiveStore").release();
697 THROW_IF_FAILED(wbemContext->SetValue(L"PolicyStore", 0, &v));
698 v.reset();
699
700 // Connect to the root\standardcimv2 namespace with the current user and obtain pointer to make IWbemServices calls.
701 wil::com_ptr<IWbemServices> wbemService;
702 THROW_IF_FAILED(wbemLocator->ConnectServer(
703 wil::make_bstr(L"ROOT\\standardcimv2").get(), nullptr, nullptr, nullptr, 0, nullptr, wbemContext.get(), &wbemService));
704
705 // Set the IWbemServices proxy so that impersonation of the user (client) occurs.
706 THROW_IF_FAILED(CoSetProxyBlanket(
707 wbemService.get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_NONE));
708
709 wil::com_ptr<IWbemClassObject> ruleObject =
710 SpawnWbemObjectInstance(L"MSFT_NetFirewallRule", firewallRule.RuleId, wbemContext.get(), wbemService);
711
712 v.vt = VT_BSTR;
713 v.bstrVal = firewallRule.RuleName.get();
714 auto hr = ruleObject->Put(L"ElementName", 0, &v, 0);
715 v.release(); // the variant should not free the bstr, it's owned by the wil::shared_bstr
716 THROW_IF_FAILED(hr);
717
718 v.vt = VT_I4;
719 v.lVal = c_directionInbound;
720 THROW_IF_FAILED(ruleObject->Put(L"Direction", 0, &v, 0));
721 v.reset();
722
723 v.vt = VT_I4;
724 v.lVal = c_actionAllow;
725 THROW_IF_FAILED(ruleObject->Put(L"Action", 0, &v, 0));
726 v.reset();
727
728 // Create the rule initially in the disabled state so that we can add the proper associated objects with the correct scoping of the rule
729 v.vt = VT_I4;
730 v.lVal = c_ruleDisabled;
731 THROW_IF_FAILED(ruleObject->Put(L"Enabled", 0, &v, 0));
732 v.reset();
733
734 v.vt = VT_BSTR;
735 v.bstrVal = wil::make_bstr(L"ActiveStore").release();
736 THROW_IF_FAILED(ruleObject->Put(L"PolicyStoreSource", 0, &v, 0));
737 v.reset();
738
739 WriteWMIInstance(wbemContext.get(), wbemService, ruleObject);
740
741 // Firewall WMI uses associated instances for many rule conditions. These are created as separate objects (if the input parameter uses the fields)
742
743 if (firewallRule.Protocol.is_valid() || !firewallRule.LocalPorts.empty())
744 {
745 wil::com_ptr<IWbemClassObject> protocolPortObject =
746 SpawnWbemObjectInstance(L"MSFT_NetProtocolPortFilter", firewallRule.RuleId, wbemContext.get(), wbemService);
747
748 if (firewallRule.Protocol.is_valid())
749 {
750 v.vt = VT_BSTR;
751 v.bstrVal = firewallRule.Protocol.get();
752 hr = protocolPortObject->Put(L"Protocol", 0, &v, 0);
753 v.release(); // the variant should not free the bstr, it's owned by the wil::shared_bstr
754 THROW_IF_FAILED(hr);
755 }
756
757 if (!firewallRule.LocalPorts.empty())
758 {
759 // Convert to a safe array for usage in WMI
760 CComSafeArray<BSTR> localPortsArray;
761 THROW_IF_FAILED(localPortsArray.Create());
762 for (const auto& localPort : firewallRule.LocalPorts)
763 {
764 THROW_IF_FAILED(localPortsArray.Add(localPort.get()));
765 }
766 v.vt = (VT_BSTR | VT_ARRAY);
767 v.parray = localPortsArray.Detach();
768 THROW_IF_FAILED(protocolPortObject->Put(L"LocalPort", 0, &v, 0));
769 v.reset();
770 }
771
772 WriteWMIInstance(wbemContext.get(), wbemService, protocolPortObject);
773 }
774
775 if (firewallRule.LocalApplication.is_valid())
776 {
777 wil::com_ptr<IWbemClassObject> applicationObject =
778 SpawnWbemObjectInstance(L"MSFT_NetApplicationFilter", firewallRule.RuleId, wbemContext.get(), wbemService);
779
780 v.vt = VT_BSTR;
781 v.bstrVal = firewallRule.LocalApplication.get();
782 hr = applicationObject->Put(L"AppPath", 0, &v, 0);
783 v.release(); // the variant should not free the bstr, it's owned by the wil::shared_bstr
784 THROW_IF_FAILED(hr);
785
786 WriteWMIInstance(wbemContext.get(), wbemService, applicationObject);
787 }
788
789 if (firewallRule.LocalService.is_valid())
790 {
791 wil::com_ptr<IWbemClassObject> serviceObject =
792 SpawnWbemObjectInstance(L"MSFT_NetServiceFilter", firewallRule.RuleId, wbemContext.get(), wbemService);
793
794 v.vt = VT_BSTR;
795 v.bstrVal = firewallRule.LocalService.get();
796 hr = serviceObject->Put(L"ServiceName", 0, &v, 0);
797 v.release(); // the variant should not free the bstr, it's owned by the wil::shared_bstr
798 THROW_IF_FAILED(hr);
799
800 WriteWMIInstance(wbemContext.get(), wbemService, serviceObject);
801 }
802
803 if (!firewallRule.RemoteAddresses.empty())
804 {
805 wil::com_ptr<IWbemClassObject> addressObject =
806 SpawnWbemObjectInstance(L"MSFT_NetAddressFilter", firewallRule.RuleId, wbemContext.get(), wbemService);
807
808 // Convert to a safe array for usage in WMI
809 CComSafeArray<BSTR> remoteAddressesArray;
810 THROW_IF_FAILED(remoteAddressesArray.Create());
811 for (const auto& remoteAddress : firewallRule.RemoteAddresses)
812 {
813 THROW_IF_FAILED(remoteAddressesArray.Add(remoteAddress.get()));
814 }
815 v.vt = (VT_BSTR | VT_ARRAY);
816 v.parray = remoteAddressesArray.Detach();
817 THROW_IF_FAILED(addressObject->Put(L"RemoteAddress", 0, &v, 0));
818 v.reset();
819
820 WriteWMIInstance(wbemContext.get(), wbemService, addressObject);
821 }
822
823 // After necessary associated objects are created, we can now enable the rule
824 ruleObject = SpawnWbemObjectInstance(L"MSFT_NetFirewallRule", firewallRule.RuleId, wbemContext.get(), wbemService);
825
826 v.vt = VT_I4;
827 v.lVal = c_ruleEnabled;
828 THROW_IF_FAILED(ruleObject->Put(L"Enabled", 0, &v, 0));
829 v.reset();
830
831 WriteWMIInstance(wbemContext.get(), wbemService, ruleObject);
832 return S_OK;
833 }
834 CATCH_RETURN()
835
836 void ConfigureSharedAccessFirewallRule() noexcept
837 {
838 // Configures necessary host firewall rules:
839 // -Inbound rule to allow UDP traffic to port 53 for the SharedAccess service. This allows the proxied DNS requests to the host.
840 LPCWSTR sharedAccessRulePorts[] = {L"53"};
841 const wsl::core::FirewallRuleConfiguration sharedAccessRule(
842 c_sharedAccessRuleId, c_sharedAccessRuleName, c_protocolUDP, 1, sharedAccessRulePorts, 0, nullptr, 0, nullptr, c_sharedAccessService, c_svchostApplication);
843 LOG_IF_FAILED_MSG(AddHostFirewallRule(sharedAccessRule), "AddHostFirewallRule::sharedAccessRule");
844 }
845
846 } // namespace wsl::core::networking