@samitouri / QOSAMI-WSL / commits / d89742f3

Address WSLC policy review follow-ups from #40466 (#40529)

* Address WSLC policy review follow-ups from #40466 Three items @OneBlue flagged in the merged PR were tagged "follow-up"; this change addresses all of them. 1. Refactor EnumerateRegistryAllowlist to use shared registry helpers - Add wsl::windows::common::registry::EnumStringValues(HKEY) returning a name->value map for REG_SZ/REG_EXPAND_SZ values (skipping other types). Mirrors the suggestion to centralise the EnumValues+ReadString pattern used by PluginManager::LoadPlugins. - wslpolicies.h's EnumerateRegistryAllowlist now calls EnumStringValues instead of hand-rolling RegQueryInfoKeyW + RegEnumValueW. Empty-entry filter and fail-open catch are preserved. - wslpolicies.h now explicitly includes registry.hpp instead of relying on precomp include order. 2. Reclassify the new HRESULTs as WSLC_E_* and surface them in wslcsdk.h - Move WSL_E_CONTAINER_DISABLED / WSL_E_REGISTRY_BLOCKED_BY_POLICY out of wslservice.idl and redefine them as WSLC_E_CONTAINER_DISABLED (0x8004060C) and WSLC_E_REGISTRY_BLOCKED_BY_POLICY (0x8004060D) in wslc.idl alongside the rest of the WSLC_E_* block. - Mirror the definitions in wslcsdk.h so SDK consumers can reference them by name without depending on the generated wslservice_h.h. - Update the service factory, wslcsession, wslutil error-code map, and PolicyTests to use the new names. 3. Tighten WSLContainerDisabledCli test - Validate stdoutText is empty (locks down which HANDLE the disabled message goes to). - Validate stderrText equals exactly MessageWSLContainerDisabled() + "\r\nError code: WSLC_E_CONTAINER_DISABLED\r\n" using the localization helper, so the message text and the error-code mapping are both locked in. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove unused CreatePoliciesKey helper from wslpolicies.h Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Ben Hillis committed May 13, 2026 at 16:33 UTC d89742f3cdf4c43aae8707a14ad9ce0442a358de
10 files changed +62 -68
src/windows/WslcSDK/wslcsdk.h
+13 -11
@@ -27,17 +27,19 @@ EXTERN_C_START
27
28 // WSLC specific error codes
29 #define WSLC_E_BASE (0x0600)
30 -#define WSLC_E_IMAGE_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 1) /* 0x80040601 */
31 -#define WSLC_E_CONTAINER_PREFIX_AMBIGUOUS MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 2) /* 0x80040602 */
32 -#define WSLC_E_CONTAINER_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 3) /* 0x80040603 */
33 -#define WSLC_E_VOLUME_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 4) /* 0x80040604 */
34 -#define WSLC_E_CONTAINER_NOT_RUNNING MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 5) /* 0x80040605 */
35 -#define WSLC_E_CONTAINER_IS_RUNNING MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 6) /* 0x80040606 */
36 -#define WSLC_E_SESSION_RESERVED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 7) /* 0x80040607 */
37 -#define WSLC_E_INVALID_SESSION_NAME MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 8) /* 0x80040608 */
38 -#define WSLC_E_NETWORK_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 9) /* 0x80040609 */
39 -#define WSLC_E_WU_SEARCH_FAILED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 10) /* 0x8004060A */
40 -#define WSLC_E_SDK_UPDATE_NEEDED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 11) /* 0x8004060B */
30 +#define WSLC_E_IMAGE_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 1) /* 0x80040601 */
31 +#define WSLC_E_CONTAINER_PREFIX_AMBIGUOUS MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 2) /* 0x80040602 */
32 +#define WSLC_E_CONTAINER_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 3) /* 0x80040603 */
33 +#define WSLC_E_VOLUME_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 4) /* 0x80040604 */
34 +#define WSLC_E_CONTAINER_NOT_RUNNING MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 5) /* 0x80040605 */
35 +#define WSLC_E_CONTAINER_IS_RUNNING MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 6) /* 0x80040606 */
36 +#define WSLC_E_SESSION_RESERVED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 7) /* 0x80040607 */
37 +#define WSLC_E_INVALID_SESSION_NAME MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 8) /* 0x80040608 */
38 +#define WSLC_E_NETWORK_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 9) /* 0x80040609 */
39 +#define WSLC_E_WU_SEARCH_FAILED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 10) /* 0x8004060A */
40 +#define WSLC_E_SDK_UPDATE_NEEDED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 11) /* 0x8004060B */
41 +#define WSLC_E_CONTAINER_DISABLED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 12) /* 0x8004060C */
42 +#define WSLC_E_REGISTRY_BLOCKED_BY_POLICY MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 13) /* 0x8004060D */
43
44 // Session values
45 #define WSLC_SESSION_OPTIONS_SIZE 88
src/windows/common/registry.cpp
+21
@@ -230,6 +230,27 @@ std::vector<std::pair<std::wstring, DWORD>> wsl::windows::common::registry::Enum
230 return values;
231 }
232
233 +std::map<std::wstring, std::wstring> wsl::windows::common::registry::EnumStringValues(_In_ HKEY Key)
234 +{
235 + std::map<std::wstring, std::wstring> values;
236 + for (const auto& [name, type] : EnumValues(Key))
237 + {
238 + // Only return string values; callers that need other types should use EnumValues() directly.
239 + // REG_EXPAND_SZ values are returned with environment variables expanded (per ReadOptionalString).
240 + if (type != REG_SZ && type != REG_EXPAND_SZ)
241 + {
242 + continue;
243 + }
244 +
245 + if (auto value = ReadOptionalString(Key, nullptr, name.c_str()))
246 + {
247 + values.emplace(name, std::move(*value));
248 + }
249 + }
250 +
251 + return values;
252 +}
253 +
254 bool wsl::windows::common::registry::IsKeyVolatile(_In_ HKEY Key)
255 {
256 KEY_FLAGS_INFORMATION info{};
src/windows/common/registry.hpp
+2
@@ -35,6 +35,8 @@ std::map<std::wstring, wil::unique_hkey> EnumKeys(_In_ HKEY Key, _In_ DWORD Subk
35
36 std::vector<std::pair<std::wstring, DWORD>> EnumValues(_In_ HKEY Key);
37
38 +std::map<std::wstring, std::wstring> EnumStringValues(_In_ HKEY Key);
39 +
40 DWORD GetMachinePolicyValue(_In_ LPCWSTR Name, HKEY lxssKey);
41
42 bool IsKeyVolatile(_In_ HKEY Key);
src/windows/common/wslutil.cpp
+3 -3
@@ -88,8 +88,8 @@ static const std::map<HRESULT, LPCWSTR> g_commonErrors{
88 X(WSL_E_INVALID_JSON),
89 X(WSL_E_VM_CRASHED),
90 X(WSL_E_NOT_A_LINUX_DISTRO),
91 - X(WSL_E_CONTAINER_DISABLED),
92 - X(WSL_E_REGISTRY_BLOCKED_BY_POLICY),
91 + X(WSLC_E_CONTAINER_DISABLED),
92 + X(WSLC_E_REGISTRY_BLOCKED_BY_POLICY),
93 X(WSLC_E_CONTAINER_PREFIX_AMBIGUOUS),
94 X(E_ACCESSDENIED),
95 X_WIN32(ERROR_NOT_FOUND),
@@ -785,7 +785,7 @@ std::wstring wsl::windows::common::wslutil::GetErrorString(HRESULT result)
785 case WSL_E_NOT_A_LINUX_DISTRO:
786 return Localization::MessageInvalidDistributionTar();
787
788 - case WSL_E_CONTAINER_DISABLED:
788 + case WSLC_E_CONTAINER_DISABLED:
789 return Localization::MessageWSLContainerDisabled();
790
791 case WSL_E_INVALID_USAGE:
src/windows/inc/wslpolicies.h
+5 -42
@@ -14,6 +14,8 @@ Abstract:
14
15 #pragma once
16
17 +#include "registry.hpp"
18 +
19 #define ROOT_POLICIES_KEY L"Software\\Policies"
20
21 namespace wsl::windows::policies {
@@ -34,15 +36,6 @@ inline constexpr auto c_defaultNetworkingMode = L"DefaultNetworkingMode";
36 inline constexpr auto c_allowWSLContainer = L"AllowWSLContainer";
37 inline constexpr auto c_wslContainerRegistryAllowlist = L"WSLContainerRegistryAllowlist";
38
37 -inline wil::unique_hkey CreatePoliciesKey(DWORD desiredAccess)
38 -{
39 - wil::unique_hkey key;
40 - LOG_IF_WIN32_ERROR(
41 - RegCreateKeyExW(HKEY_LOCAL_MACHINE, c_registryKey, 0, nullptr, REG_OPTION_NON_VOLATILE, desiredAccess, nullptr, &key, nullptr));
42 -
43 - return key;
44 -}
45 -
39 inline std::optional<DWORD> GetPolicyValue(HKEY key, LPCWSTR name)
40 try
41 {
@@ -143,46 +136,16 @@ try
136 return entries;
137 }
138
146 - DWORD maxValueNameChars = 0;
147 - DWORD maxValueDataBytes = 0;
148 - THROW_IF_WIN32_ERROR(RegQueryInfoKeyW(
149 - subKey, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &maxValueNameChars, &maxValueDataBytes, nullptr, nullptr));
150 -
151 - std::wstring nameBuffer(static_cast<size_t>(maxValueNameChars) + 1, L'\0');
152 - std::wstring dataBuffer(maxValueDataBytes / sizeof(wchar_t) + 1, L'\0');
153 - for (DWORD index = 0;; ++index)
139 + for (auto& [name, value] : wsl::windows::common::registry::EnumStringValues(subKey))
140 {
155 - DWORD nameSize = static_cast<DWORD>(nameBuffer.size());
156 - DWORD dataSize = static_cast<DWORD>(dataBuffer.size() * sizeof(wchar_t));
157 - DWORD type = 0;
158 - const auto status =
159 - RegEnumValueW(subKey, index, nameBuffer.data(), &nameSize, nullptr, &type, reinterpret_cast<BYTE*>(dataBuffer.data()), &dataSize);
160 - if (status == ERROR_NO_MORE_ITEMS)
161 - {
162 - break;
163 - }
164 -
165 - THROW_IF_WIN32_ERROR(status);
166 - if (type != REG_SZ && type != REG_EXPAND_SZ)
167 - {
168 - continue;
169 - }
170 -
171 - const size_t chars = dataSize / sizeof(wchar_t);
172 - std::wstring_view entry{dataBuffer.data(), chars};
173 - if (!entry.empty() && entry.back() == L'\0')
174 - {
175 - entry.remove_suffix(1);
176 - }
177 -
141 // Skip empty entries so a stray blank list item in the GP editor doesn't make the
142 // allowlist non-empty (which would otherwise deny every registry).
180 - if (entry.empty())
143 + if (value.empty())
144 {
145 continue;
146 }
147
185 - entries.emplace_back(entry);
148 + entries.emplace_back(std::move(value));
149 }
150
151 return entries;
src/windows/service/exe/WSLCSessionManagerFactory.cpp
+1 -1
@@ -42,7 +42,7 @@ HRESULT WSLCSessionManagerFactory::CreateInstance(_In_ IUnknown* pUnkOuter, _In_
42
43 namespace policies = wsl::windows::policies;
44 THROW_HR_WITH_USER_ERROR_IF(
45 - WSL_E_CONTAINER_DISABLED,
45 + WSLC_E_CONTAINER_DISABLED,
46 wsl::shared::Localization::MessageWSLContainerDisabled(),
47 !policies::IsFeatureAllowed(policies::OpenPoliciesKey().get(), policies::c_allowWSLContainer));
48
src/windows/service/inc/wslc.idl
+2
@@ -865,3 +865,5 @@ cpp_quote("#define WSLC_E_INVALID_SESSION_NAME MAKE_HRESULT(SEVERITY_ERROR, FACI
865 cpp_quote("#define WSLC_E_NETWORK_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 9) /* 0x80040609 */")
866 cpp_quote("#define WSLC_E_WU_SEARCH_FAILED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 10) /* 0x8004060A */")
867 cpp_quote("#define WSLC_E_SDK_UPDATE_NEEDED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 11) /* 0x8004060B */")
868 +cpp_quote("#define WSLC_E_CONTAINER_DISABLED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 12) /* 0x8004060C */")
869 +cpp_quote("#define WSLC_E_REGISTRY_BLOCKED_BY_POLICY MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 13) /* 0x8004060D */")
src/windows/service/inc/wslservice.idl
-2
@@ -395,5 +395,3 @@ cpp_quote("#define WSL_E_DISK_CORRUPTED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_IT
395 cpp_quote("#define WSL_E_DISTRIBUTION_NAME_NEEDED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSL_E_BASE + 0x30) /* 0x80040330 */")
396 cpp_quote("#define WSL_E_INVALID_JSON MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSL_E_BASE + 0x31) /* 0x80040331 */")
397 cpp_quote("#define WSL_E_VM_CRASHED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSL_E_BASE + 0x32) /* 0x80040332 */")
398 -cpp_quote("#define WSL_E_CONTAINER_DISABLED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSL_E_BASE + 0x33) /* 0x80040333 */")
399 -cpp_quote("#define WSL_E_REGISTRY_BLOCKED_BY_POLICY MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSL_E_BASE + 0x34) /* 0x80040334 */")
src/windows/wslcsession/WSLCSession.cpp
+2 -2
@@ -53,7 +53,7 @@ void EnforceRegistryAllowlist(const std::string& Repo)
53 return;
54 }
55
56 - THROW_HR_WITH_USER_ERROR(WSL_E_REGISTRY_BLOCKED_BY_POLICY, Localization::MessageRegistryBlockedByPolicy(serverWide));
56 + THROW_HR_WITH_USER_ERROR(WSLC_E_REGISTRY_BLOCKED_BY_POLICY, Localization::MessageRegistryBlockedByPolicy(serverWide));
57 }
58
59 std::string IndentLines(const std::string& input, const std::string& prefix)
@@ -700,7 +700,7 @@ try
700 // build outright since we cannot reliably attribute its registry traffic.
701 if (wsl::windows::policies::HasRegistryAllowlist(wsl::windows::policies::OpenPoliciesKey().get()))
702 {
703 - THROW_HR_WITH_USER_ERROR(WSL_E_REGISTRY_BLOCKED_BY_POLICY, Localization::MessageImageBuildBlockedByPolicy());
703 + THROW_HR_WITH_USER_ERROR(WSLC_E_REGISTRY_BLOCKED_BY_POLICY, Localization::MessageImageBuildBlockedByPolicy());
704 }
705
706 auto buildFileHandle = OpenUserHandle(Options->DockerfileHandle);
test/windows/PolicyTests.cpp
+13 -7
@@ -437,11 +437,13 @@ class PolicyTest
437
438 wil::com_ptr<IWSLCSessionManager> sessionManager;
439 HRESULT hr = CoCreateInstance(__uuidof(WSLCSessionManager), nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&sessionManager));
440 - VERIFY_ARE_EQUAL(WSL_E_CONTAINER_DISABLED, hr);
440 + VERIFY_ARE_EQUAL(WSLC_E_CONTAINER_DISABLED, hr);
441 VERIFY_IS_NULL(sessionManager.get());
442 }
443
444 - // Verifies AllowWSLContainer=0 gates wslc.exe at startup with a friendly message.
444 + // Verifies AllowWSLContainer=0 gates wslc.exe at startup with a friendly message that is
445 + // surfaced on stderr (and not stdout). Locks down both the exact rendered text and the
446 + // handle the message is written to so future regressions show up here.
447 WSLC_TEST_METHOD(WSLContainerDisabledCli)
448 {
449 auto revert = SetPolicy(c_allowWSLContainer, 0);
@@ -450,11 +452,15 @@ class PolicyTest
452 auto [stdoutText, stderrText, exitCode] = LxsstuLaunchCommandAndCaptureOutputWithResult(cmd.data(), nullptr, nullptr);
453
454 VERIFY_ARE_EQUAL(1, exitCode);
453 - if (stderrText.find(L"WSL container is disabled by the computer policy") == std::wstring::npos)
454 - {
455 - LogError("Expected stderr to contain disabled message, got: '%ls'", stderrText.c_str());
456 - VERIFY_FAIL();
457 - }
455 +
456 + // The disabled message must go to stderr only -- never to stdout.
457 + VERIFY_ARE_EQUAL(L"", stdoutText);
458 +
459 + // The wslc CLI renders failures via MessageErrorCode("{}\nError code: {}") and
460 + // PrintMessage adds a trailing newline; line endings are \r\n through console pipes.
461 + const auto expected =
462 + wsl::shared::Localization::MessageWSLContainerDisabled() + L"\r\nError code: WSLC_E_CONTAINER_DISABLED\r\n";
463 + VERIFY_ARE_EQUAL(expected, stderrText);
464 }
465
466 // Verifies the WSLContainerRegistryAllowlist denies image pulls from registries not in the