Skip to content

Add registry allowlist support to image builds - #41211

Draft
beena352 wants to merge 5 commits into
microsoft:masterfrom
beena352:users/beenachauhan/wslc-registry-allowlist-buildkit-policy
Draft

Add registry allowlist support to image builds#41211
beena352 wants to merge 5 commits into
microsoft:masterfrom
beena352:users/beenachauhan/wslc-registry-allowlist-buildkit-policy

Conversation

@beena352

@beena352 beena352 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary of the Pull Request

The WSLContainerRegistryAllowlist group policy was not enforced by wslc image build - only wslc image pull respected it, so a Dockerfile's FROM/COPY --from could pull from a registry the admin had explicitly disallowed. This PR closes that gap by generating a BuildKit source-policy document (EXPERIMENTAL_BUILDKIT_SOURCE_POLICY) that denies all image sources except the allowlisted hosts, and passes it to docker buildx build.

PR Checklist

  • Closes: Link to issue #xxx
  • Communication: I've discussed this with core contributors already. If work hasn't been agreed, this work might be rejected
  • Tests: Added/updated if needed and all pass
  • Localization: All end user facing strings can be localized
  • Dev docs: Added/updated if needed
  • Documentation updated: If checked, please file a pull request on our docs repo and link it here: #xxx

Detailed Description of the Pull Request / Additional comments

  • Added IWSLCVirtualMachine::PrepareBuildKitSourcePolicy/CleanupBuildKitSourcePolicy - the SYSTEM-side service materialises policy.json (DENY-all + per-allowlisted-host ALLOW, REGEX match, last-rule-wins) in %SystemRoot%\Temp\wslc-buildkit-policy-, mounts it read-only into the guest, and injects EXPERIMENTAL_BUILDKIT_SOURCE_POLICY into the build environment.
  • The folder's DACL grants SYSTEM/Administrators full control and the calling user read+traverse only (no write) - read is required because the plan9/virtiofs file server backing the guest mount impersonates the user token when opening files from the guest side.
  • Fail-closed: if the allowlist policy is configured but can't be read (e.g. registry access denied), the build is refused with WSLC_E_REGISTRY_BLOCKED_BY_POLICY rather than silently proceeding without enforcement.
  • The service tracks folders it creates (m_preparedPolicyFolders) so CleanupBuildKitSourcePolicy only deletes paths it handed out, and a destructor-time sweep removes any folder left behind if the client crashes mid-build (e.g. between prepare and cleanup).
  • policyCleanup is declared before unmountAll in WSLCSession::BuildImage so the plan9/virtiofs share is torn down before the SYSTEM service attempts to delete the host folder, avoiding a sharing violation.

Validation Steps Performed

  • Automated: PolicyTests.cpp - RegistryAllowlistDenies, RegistryAllowlistBlocksImageBuild, RegistryAllowlistAllowsImageBuild, RegistryAllowlistImageBuildIsCaseInsensitive, RegistryAllowlistBlocksImageBuildCopyFrom, RegistryAllowlistBlocksImageBuildImplicitDockerIo, ReadRegistryAllowlistSnapshotFromPoliciesRoot_Logic.
  • Manual: verified an allowed FROM builds successfully and a disallowed FROM/COPY --from is blocked with the policy-block error surfaced to the user.
  • Manual: killed the client mid-build and confirmed the SYSTEM-owned policy folder is left in %SystemRoot%\Temp but is inaccessible/unwritable to the standard user, then confirmed wsl --shutdown triggers the destructor sweep and removes it.

Copilot AI review requested due to automatic review settings July 30, 2026 20:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds enforcement of the WSLContainerRegistryAllowlist policy for wslc image build by generating a BuildKit source-policy JSON on the SYSTEM side, mounting it into the VM, and passing it to BuildKit via EXPERIMENTAL_BUILDKIT_SOURCE_POLICY. This replaces the previous behavior that rejected image builds outright whenever an allowlist was configured.

Changes:

  • Route wslc image build allowlist enforcement through a SYSTEM-owned BuildKit source policy (prepare + cleanup) instead of hard-blocking builds.
  • Add SYSTEM-side implementation to materialize and validate lifecycle of the policy folder, and wire new COM methods through session/VM layers.
  • Expand policy tests to cover allow/deny/case-insensitivity/multistage/implicit-docker.io scenarios and add snapshot logic tests; update the localized fail-closed message.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
test/windows/PolicyTests.cpp Updates/extends policy tests for BuildKit source-policy based allowlist enforcement and snapshot reading logic.
src/windows/wslcsession/WSLCVirtualMachine.h Adds VM wrapper method declarations for preparing/cleaning the BuildKit source policy.
src/windows/wslcsession/WSLCVirtualMachine.cpp Implements VM wrapper forwarding calls for the new BuildKit source policy methods.
src/windows/wslcsession/WSLCSession.cpp Uses SYSTEM-prepared policy folder, mounts it, and sets EXPERIMENTAL_BUILDKIT_SOURCE_POLICY for docker buildx build.
src/windows/service/inc/wslc.idl Extends IWSLCVirtualMachine with PrepareBuildKitSourcePolicy / CleanupBuildKitSourcePolicy.
src/windows/service/exe/HcsVirtualMachine.h Declares new COM methods and adds tracking for prepared policy folders.
src/windows/service/exe/HcsVirtualMachine.cpp Implements BuildKit source-policy JSON generation, secure policy folder creation, and lifecycle management.
src/windows/inc/wslpolicies.h Adds registry allowlist snapshot APIs to distinguish not-configured vs read-failed (fail closed).
localization/strings/en-US/Resources.resw Updates the user-facing message for the fail-closed “policy could not be evaluated” path.

Comment thread src/windows/service/exe/HcsVirtualMachine.cpp
Copilot AI review requested due to automatic review settings July 30, 2026 22:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

src/windows/service/exe/HcsVirtualMachine.cpp:937

  • The WriteFile call doesn’t capture/validate the number of bytes written. If a short write occurs, policy.json can be truncated and policy enforcement may behave unpredictably (invalid JSON / partial rules).
        wil::unique_handle file{CreateFileW(policyFile.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)};
        THROW_LAST_ERROR_IF(!file);
        THROW_IF_WIN32_BOOL_FALSE(WriteFile(file.get(), policyJson.data(), gsl::narrow_cast<DWORD>(policyJson.size()), nullptr, nullptr));
    }

src/windows/service/exe/HcsVirtualMachine.cpp:15

  • This .cpp file doesn’t include the Windows-side precompiled header first. In this repo’s Windows service code, .cpp files include precomp.h first (e.g. src/windows/service/exe/LxssUserSession.cpp:15), and this file now relies on standard library headers (e.g. <algorithm>, <ranges>) that are typically pulled in via the precompiled header.
#include "HcsVirtualMachine.h"

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

src/windows/service/exe/HcsVirtualMachine.cpp:936

  • WriteFile is being called with lpNumberOfBytesWritten == nullptr while lpOverlapped is also nullptr. Per Win32 WriteFile contract, lpNumberOfBytesWritten must be non-null for synchronous writes; as written this will fail (typically ERROR_INVALID_PARAMETER) and prevent BuildKit policy materialization whenever the allowlist is configured.
        wil::unique_handle file{CreateFileW(policyFile.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)};
        THROW_LAST_ERROR_IF(!file);
        THROW_IF_WIN32_BOOL_FALSE(WriteFile(file.get(), policyJson.data(), gsl::narrow_cast<DWORD>(policyJson.size()), nullptr, nullptr));

Copilot AI review requested due to automatic review settings July 30, 2026 23:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

src/windows/service/exe/HcsVirtualMachine.cpp:428

  • The destructor comment says the policy folder DACL is "SYSTEM-only", but CreatePolicyFolder explicitly grants the calling user read+traverse (FRFX) so the guest file server can impersonate the user. This is a documentation mismatch that could confuse future audits/reviews.
    // Sweep any BuildKit source-policy folders that were prepared but never cleaned up (e.g. the
    // client crashed between PrepareBuildKitSourcePolicy and CleanupBuildKitSourcePolicy). The
    // DACL is SYSTEM-only, so a leak is not exploitable, but folders would otherwise accumulate
    // in %SystemRoot%\Temp across crashes.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

src/windows/service/exe/HcsVirtualMachine.cpp:934

  • CreateFileW returns INVALID_HANDLE_VALUE on failure, but wil::unique_handle treats nullptr as the invalid value. This means failures won’t be detected and CloseHandle(INVALID_HANDLE_VALUE) may run. Use wil::unique_hfile (or explicitly check for INVALID_HANDLE_VALUE).
        wil::unique_handle file{CreateFileW(policyFile.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)};

src/windows/service/exe/HcsVirtualMachine.cpp:435

  • The destructor sweep deletes policy folders while the VM/shares may still be alive, ignores any remove_all error, and then clears m_preparedPolicyFolders unconditionally. If deletion fails due to an in-use Plan9/VirtioFS share, the folder will leak (and won’t be retried later in the destructor), which conflicts with the intended “retry at shutdown” behavior described elsewhere in this change.
    // Sweep any BuildKit source-policy folders that were prepared but never cleaned up (e.g. the
    // client crashed between PrepareBuildKitSourcePolicy and CleanupBuildKitSourcePolicy). The
    // DACL is SYSTEM-only, so a leak is not exploitable, but folders would otherwise accumulate
    // in %SystemRoot%\Temp across crashes.
    for (const auto& folder : m_preparedPolicyFolders)
    {
        std::error_code ec;
        std::filesystem::remove_all(folder, ec);
    }
    m_preparedPolicyFolders.clear();

Comment thread src/windows/wslcsession/WSLCSession.cpp
Copilot AI review requested due to automatic review settings July 31, 2026 20:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/windows/service/exe/HcsVirtualMachine.cpp:21

  • This file uses std::transform, std::ranges::transform, std::back_inserter, and std::tolower, but it doesn't explicitly include the standard headers that declare them. Relying on transitive includes makes the build brittle and can break with toolset/header changes. Add the missing headers (<algorithm>, <iterator>, <cctype>) near the top of the file.
#include "HcsVirtualMachine.h"
#include <format>
#include <fstream>
#include <string>
#include <string_view>


if (policyHostPath)
{
auto policyMountPath = mountInVm(policyHostPath.get(), TRUE);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of writing the policy file on Windows and then mounting it, could we directly writing inside the VM during boot ? That would simplify things quite a bit

// https://github.com/moby/buildkit/blob/master/docs/sourcepolicy.md
std::string BuildBuildKitSourcePolicyJson(const std::vector<std::string>& allowedHosts)
{
nlohmann::json rules = nlohmann::json::array();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@craigloewen-msft : Given that we can use buildkit policies, what do you think of putting the entire policy in the registry / well known file path ? That would both allow users to do more powerful things without having us to parse / validate the policies ourselves

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants