master
h 263 lines 8.27 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 wslpolicies.h
8
9 Abstract:
10
11 This file contains a helpers for querying WSL policies.
12
13 --*/
14
15 #pragma once
16
17 #include "registry.hpp"
18
19 #define ROOT_POLICIES_KEY L"Software\\Policies"
20
21 namespace wsl::windows::policies {
22 inline constexpr auto c_registryKey = ROOT_POLICIES_KEY L"\\WSL";
23 inline constexpr auto c_allowInboxWSL = L"AllowInboxWSL";
24 inline constexpr auto c_allowWSL = L"AllowWSL";
25 inline constexpr auto c_allowWSL1 = L"AllowWSL1";
26 inline constexpr auto c_allowCustomKernelUserSetting = L"AllowKernelUserSetting";
27 inline constexpr auto c_allowCustomSystemDistroUserSetting = L"AllowSystemDistroUserSetting";
28 inline constexpr auto c_allowCustomKernelCommandLineUserSetting = L"AllowKernelCommandLineUserSetting";
29 inline constexpr auto c_allowDebugShellUserSetting = L"AllowDebugShell";
30 inline constexpr auto c_allowNestedVirtualizationUserSetting = L"AllowNestedVirtualization";
31 inline constexpr auto c_allowKernelDebuggingUserSetting = L"AllowKernelDebugUserSetting";
32 inline constexpr auto c_allowDiskMount = L"AllowDiskMount";
33 inline constexpr auto c_allowCustomNetworkingModeUserSetting = L"AllowNetworkingModeUserSetting";
34 inline constexpr auto c_allowCustomFirewallUserSetting = L"AllowFirewallUserSetting";
35 inline constexpr auto c_defaultNetworkingMode = L"DefaultNetworkingMode";
36 inline constexpr auto c_allowWSLContainer = L"AllowWSLContainer";
37 inline constexpr auto c_allowWSLContainerPrivileged = L"AllowWSLContainerPrivileged";
38 inline constexpr auto c_wslContainerRegistryAllowlist = L"WSLContainerRegistryAllowlist";
39
40 inline std::optional<DWORD> GetPolicyValue(HKEY key, LPCWSTR name)
41 try
42 {
43 if (key == nullptr)
44 {
45 return std::nullopt;
46 }
47
48 DWORD value = 0;
49 DWORD size = sizeof(value);
50 const LONG result = RegGetValueW(key, nullptr, name, RRF_RT_REG_DWORD, nullptr, &value, &size);
51 if (result == ERROR_PATH_NOT_FOUND || result == ERROR_FILE_NOT_FOUND)
52 {
53 return std::nullopt;
54 }
55
56 THROW_IF_WIN32_ERROR(result);
57
58 return value;
59 }
60 catch (...)
61 {
62 LOG_CAUGHT_EXCEPTION_MSG("Error reading the policy value: %ls", name);
63 return std::nullopt;
64 }
65
66 inline bool IsFeatureAllowed(HKEY key, LPCWSTR name)
67 try
68 {
69 const auto policy = GetPolicyValue(key, name);
70 if (!policy.has_value())
71 {
72 return true;
73 }
74
75 const auto value = policy.value();
76 THROW_HR_IF_MSG(E_UNEXPECTED, value != 0 && value != 1, "Invalid value for policy: %ls: %lu", name, value);
77
78 return value == 1;
79 }
80 catch (...)
81 {
82 LOG_CAUGHT_EXCEPTION();
83 return true;
84 }
85
86 inline wil::unique_hkey OpenPoliciesKey()
87 {
88 wil::unique_hkey key;
89 const auto result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, c_registryKey, 0, KEY_READ, &key);
90 if (result == ERROR_PATH_NOT_FOUND || result == ERROR_FILE_NOT_FOUND)
91 {
92 // N.B. Return an empty result if the registry key doesn't exist to make it easier
93 // to check for policies without having a special code path for this case.
94 return {};
95 }
96
97 LOG_IF_WIN32_ERROR(result);
98 return key;
99 }
100
101 // Opens the WSLContainerRegistryAllowlist sub-key under the supplied policies key for
102 // read-only enumeration. Returns an empty handle when the policy is not configured (sub-key
103 // absent) or the parent key is null.
104 inline wil::unique_hkey OpenRegistryAllowlistKey(HKEY policiesKey)
105 {
106 if (policiesKey == nullptr)
107 {
108 return {};
109 }
110
111 wil::unique_hkey subKey;
112 const auto result = RegOpenKeyExW(policiesKey, c_wslContainerRegistryAllowlist, 0, KEY_READ, &subKey);
113 if (result == ERROR_PATH_NOT_FOUND || result == ERROR_FILE_NOT_FOUND)
114 {
115 return {};
116 }
117
118 LOG_IF_WIN32_ERROR(result);
119 return subKey;
120 }
121
122 // Returns the REG_SZ data of every value under the WSLContainerRegistryAllowlist sub-key
123 // (one entry per configured registry hostname). The ADMX uses
124 // `<list valuePrefix="AllowedRegistry"/>`, which the GP editor materialises by writing one
125 // REG_SZ value per entry: each value is named `AllowedRegistry1`, `AllowedRegistry2`, ... and
126 // the value's data is the actual hostname; the value names are therefore ignored here. Schema
127 // reference: https://learn.microsoft.com/en-us/previous-versions/windows/desktop/Policy/element-list
128 //
129 // Returns an empty list when the sub-key has no values or on any enumeration failure; either
130 // case means no effective restriction is in place.
131 inline std::vector<std::wstring> EnumerateRegistryAllowlist(HKEY subKey)
132 try
133 {
134 std::vector<std::wstring> entries;
135 if (subKey == nullptr)
136 {
137 return entries;
138 }
139
140 for (auto& [name, value] : wsl::windows::common::registry::EnumStringValues(subKey))
141 {
142 // Skip empty entries so a stray blank list item in the GP editor doesn't make the
143 // allowlist non-empty (which would otherwise deny every registry).
144 if (value.empty())
145 {
146 continue;
147 }
148
149 entries.emplace_back(std::move(value));
150 }
151
152 return entries;
153 }
154 catch (...)
155 {
156 LOG_CAUGHT_EXCEPTION();
157 return {};
158 }
159
160 // Evaluates the WSLContainerRegistryAllowlist policy for `server`. The policy only restricts
161 // traffic when the sub-key exists and contains at least one entry. With the sub-key absent or
162 // empty, every server is allowed; otherwise the server is allowed only when it case-insensitively
163 // matches one of the allowlist entries.
164 inline bool IsRegistryAllowed(HKEY policiesKey, std::wstring_view server)
165 {
166 auto subKey = OpenRegistryAllowlistKey(policiesKey);
167 if (!subKey)
168 {
169 return true;
170 }
171
172 const auto entries = EnumerateRegistryAllowlist(subKey.get());
173 if (entries.empty())
174 {
175 return true;
176 }
177
178 if (server.empty())
179 {
180 return true;
181 }
182
183 const std::wstring target{server};
184 for (const auto& entry : entries)
185 {
186 if (_wcsicmp(entry.c_str(), target.c_str()) == 0)
187 {
188 return true;
189 }
190 }
191 return false;
192 }
193
194 // Returns true when the WSLContainerRegistryAllowlist policy is in effect (sub-key present
195 // with at least one entry). Used by callers (e.g., `wslc image build`) that cannot attribute
196 // traffic to a specific registry and must therefore refuse the operation whenever any
197 // allowlist restriction is active.
198 inline bool HasRegistryAllowlist(HKEY policiesKey)
199 {
200 auto subKey = OpenRegistryAllowlistKey(policiesKey);
201 return subKey && !EnumerateRegistryAllowlist(subKey.get()).empty();
202 }
203
204 // Snapshot of the WSLContainerRegistryAllowlist policy captured in a single read. Callers use
205 // State to distinguish an unconfigured policy (fail open) from a policy that is present.
206 enum class RegistryAllowlistState
207 {
208 NotConfigured,
209 Configured
210 };
211
212 struct RegistryAllowlistSnapshot
213 {
214 RegistryAllowlistState State{RegistryAllowlistState::NotConfigured};
215 std::vector<std::wstring> Hosts{};
216 };
217
218 // subKey must be the WSLContainerRegistryAllowlist sub-key; enumeration failures throw MessageRegistryAllowlistPolicyInvalid.
219 inline RegistryAllowlistSnapshot ReadRegistryAllowlistSnapshot(HKEY subKey)
220 try
221 {
222 RegistryAllowlistSnapshot snapshot;
223 for (auto& [name, value] : wsl::windows::common::registry::EnumStringValues(subKey))
224 {
225 if (value.empty())
226 {
227 continue;
228 }
229
230 snapshot.Hosts.emplace_back(std::move(value));
231 }
232
233 if (!snapshot.Hosts.empty())
234 {
235 snapshot.State = RegistryAllowlistState::Configured;
236 }
237
238 return snapshot;
239 }
240 catch (...)
241 {
242 LOG_CAUGHT_EXCEPTION();
243 THROW_HR_WITH_USER_ERROR(wil::ResultFromCaughtException(), wsl::shared::Localization::MessageRegistryAllowlistPolicyInvalid());
244 }
245
246 // Throws MessageRegistryAllowlistPolicyInvalid on an unreadable sub-key so callers fail closed.
247 inline RegistryAllowlistSnapshot ReadRegistryAllowlistSnapshotFromPoliciesRoot()
248 {
249 const auto subKeyPath = std::wstring{c_registryKey} + L"\\" + c_wslContainerRegistryAllowlist;
250 wil::unique_hkey subKey;
251 const auto openResult = RegOpenKeyExW(HKEY_LOCAL_MACHINE, subKeyPath.c_str(), 0, KEY_READ, &subKey);
252 if (openResult == ERROR_PATH_NOT_FOUND || openResult == ERROR_FILE_NOT_FOUND)
253 {
254 return {};
255 }
256
257 THROW_HR_WITH_USER_ERROR_IF(
258 HRESULT_FROM_WIN32(openResult), wsl::shared::Localization::MessageRegistryAllowlistPolicyInvalid(), openResult != ERROR_SUCCESS);
259
260 return ReadRegistryAllowlistSnapshot(subKey.get());
261 }
262
263 } // namespace wsl::windows::policies