Improves virtiofs and VirtioProxy performance by giving each virtio
device its own SWIOTLB aperture instead of sharing a single global
pool. The guest kernel reserves a contiguous physical range at boot,
publishes the (base, size), and the host programs a matching
per-device aperture in wsldevicehost.
1. The WSL kernel allocates a contiguous range at boot
(alloc_contig_pages with __GFP_DMA32 | __GFP_ZERO) and exposes
the chosen physical (base, size) under
/sys/bus/vmbus/drivers/hv_pci/swiotlb_{base,size}
2. mini_init (WSL2) and the WSLC init handler read those sysfs files
and return the values in LX_INIT_GUEST_CAPABILITIES and
WSLC_GET_GUEST_CAPABILITIES_RESULT respectively.
3. WslCoreVm::ReadGuestCapabilities and
WSLCVirtualMachine::ReadGuestCapabilities capture the values.
WSLC forwards them to wslservice via the new
HcsVirtualMachine::ApplyGuestCapabilities IDL method (with a
WSLCGuestCapabilities struct so future kernel-published values
can be added without bumping the interface IID).
4. Both VM owners format "swiotlb=0x{base:x},{size}" once into
m_swiotlbOption and pass it verbatim to AddGuestDevice /
AddSharePath for every virtiofs share and virtio-net adapter
(VirtioProxy networking). wsldevicehost consumes the token and
creates the per-device SWIOTLB aperture.
If the kernel does not publish the sysfs files (older kernel) both
values come back as zero, the host omits the device-options token,
and the WSL2 path emits a one-time user warning via
MessageSwiotlbKernelUnsupported so users understand why performance
is degraded. (The WSLC path always uses the bundled kernel, so the
warning does not apply there.)
Other changes:
* Bump Microsoft.WSL.Kernel to 6.18.26.3-1, which is the first
official kernel that publishes the hv_pci swiotlb_{base,size}
sysfs files this PR consumes.
* Bump Microsoft.WSL.DeviceHost to 1.2.29-0 for the device-side
SWIOTLB aperture support.
* Default pool sizing moves to helpers::ComputeDefaultSwiotlbConfig
and is only requested on the kernel command line when a virtio
device that needs bounce buffers (VirtioFs / Virtio9p /
VirtioProxy) is in use.
* Telemetry: emit GuestKernelInfo / WSLCReadGuestCapabilities /
WSLCApplyGuestCapabilities events with the kernel-chosen base
and size so we can validate the handshake in CI.
Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* CLI: Add container prune command
Implement the 'wslc container prune' command to remove all stopped
containers. The backend IWSLCSession::PruneContainers API already
exists; this adds the CLI frontend.
Changes:
- ContainerPruneCommand: new command class with --session arg
- ContainerService::Prune(): service layer using RAII PruneResult
- PruneContainers task: prints pruned container IDs and reclaimed space
- PruneContainersResult model struct
- 3 localization strings (desc, long desc, space reclaimed)
- CLI parsing unit tests in CommandLineTestCases.h
- E2E tests: help, no-stopped, stopped, running-preserved, multi-stopped
- Updated container help output test to include prune subcommand
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR review: use shared helpers in prune tests
- Add StdoutContainsSubstring() to WSLCExecutionResult for reusable
substring matching (per reviewer suggestion to move to WSLCExecutor)
- Replace manual container list loops with VerifyContainerIsNotListed()
and VerifyContainerIsListed() helpers
- Use exact localized string match for zero-reclaimed-space assertion
- Remove private VerifyStdoutContains helper (now in WSLCExecutor)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Container Prune e2 test: Fix clang formatting errors
* Fix PruneContainers call to match 3-parameter interface
Remove extra argument that doesn't match the IWSLCSession::PruneContainers
signature (Filters, FiltersCount, Result).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address Copilot review feedback
- Rename Containers -> PrunedContainers in PruneContainersResult for clarity
- Add reserve() before loop to avoid repeated reallocations
- Add per-test cleanup in ClassSetup to prevent flaky tests from leftovers
- Assert pruned container IDs appear in prune output
- Remove hardcoded English strings; use localization API for assertions
- Simplify StdoutContainsSubstring to search raw buffer directly
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Pooja Trivedi <trivedipooja@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Implement WSLC container group policies
Adds enforcement for two group policies whose ADMX templates landed in
PR #40422:
- AllowWSLContainer (DWORD): master switch for wslc.exe and the
WSLCSessionManager COM API. Enforced inside WSLCSessionManagerFactory
so CoCreateInstance returns WSL_E_CONTAINER_DISABLED directly when the
policy is off; every caller (wslc.exe, WslcSDK, plugins) fails fast
through one code path rather than getting a manager whose every method
errors out individually.
- WSLContainerRegistryAllowlist: ADMX `<list valuePrefix=\"AllowedRegistry\">`
policy stored as a sub-key whose REG_SZ value data is each allowed
registry hostname. Enforced in PullImage and PushImage via a shared
helper. BuildImage is rejected outright when an allowlist is
configured, since the in-VM docker daemon fetches FROM base images
through its own pull mechanism and cannot be reliably gated
per-registry. The policy is fail-open: an absent or empty sub-key is
treated as no restriction, and registry I/O errors fall through to
allow rather than break all container operations on a transient
hiccup.
Adds two new HRESULTs:
- WSL_E_CONTAINER_DISABLED (0x33)
- WSL_E_REGISTRY_BLOCKED_BY_POLICY (0x34)
Adds PolicyTests covering disabled-state, CLI surfacing, allowlist
denial, build rejection, and pure-function unit tests for the allowlist
evaluator.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback on PolicyTests
* SetRegistryAllowlist: delete the WSLContainerRegistryAllowlist
sub-key before recreating it so stale AllowedRegistryN values from
a previous (possibly interrupted) test run can't leak into the
current test.
* GetWslcExePath: avoid std::optional::value() throwing
bad_optional_access; use THROW_HR_IF_MSG with a clear diagnostic
when the MSI install location can't be read from the registry.
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>
Co-authored-by: benhillis <17727402+benhillis@users.noreply.github.com>
* Add Uid/Gid/Mode/Fixed driver options to WSLC VHD volumes; expose via SDK
The WSLC named-volume "vhd" driver only supported a single SizeBytes
option, so containers running as a non-root user could not write to
their own persistent volumes (mkfs.ext4 leaves the root owned by
root:root with mode 0755). It also could not produce a fully-allocated
VHD, which some workloads need for predictable I/O.
Service side
============
* Adds new VHD driver options on top of SizeBytes:
- Fixed=true|false pre-allocate the underlying VHD
- Uid=<n> chown the volume root to uid (paired with Gid)
- Gid=<n> chown the volume root to gid (paired with Uid)
- Mode=<octal> chmod the volume root, max 07777, must be > 0
* Extracts a reusable OptionParser helper for typed option parsing
with errno-capture, end-pointer validation, leading-sign rejection,
consumed-key tracking, and a final RejectUnknown() pass. Used by
WSLCVhdVolume's Create and Open paths so persisted metadata is
validated identically on reload.
Public C SDK
============
WslcVhdRequirements grows three new uint32_t fields (uid/gid/mode) and
a WslcVhdRequirementsFlags bitmask. WslcCreateSessionVhdVolume:
* honors WSLC_VHD_TYPE_FIXED (was previously E_NOTIMPL)
* dynamically builds WSLCDriverOption[] based on which flags are set
* rejects unknown type values, unknown flag bits, and mode == 0 with
E_INVALIDARG so future flag additions cannot be silently ignored
by older SDK versions and obvious foot-guns are caught client-side.
WslcSetSessionSettingsVhd does NOT plumb owner/mode/fixed through the
session rootfs VHD path, and now rejects flags != NONE with
E_INVALIDARG instead of silently ignoring them.
WSLC_SESSION_OPTIONS_SIZE bumps 80 -> 96 to match the wider embedded
WslcVhdRequirements; this is an ABI break, callers must recompile.
WinRT projection
================
VhdRequirements gains:
void SetOwner(UInt32 uid, UInt32 gid);
void SetMode(UInt32 mode);
These set the corresponding flag bit and field on the underlying
struct. Pair-based SetOwner avoids the half-set foot-gun that
per-property setters would create.
Tests
=====
* WSLCTests.cpp: NamedVolumeVhdOptionsParseTest covers SizeBytes,
unknown keys, sign rejection, range and base validation; a
positive owner+mode test exercises chown/chmod end-to-end; a
Fixed-allocation test asserts on-disk file_size >= requested size.
* WslcSdkTests.cpp adds invalid-type, fixed-allocation, owner+mode
positive, mode-out-of-range negative, mode==0 negative, unknown
flag negative, and flags=NONE-ignores-uid/gid positive cases.
The WinRT projection has no test infrastructure in the repo and is
not unit-tested; behavior is covered at the C SDK layer that the
projection delegates to.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address Copilot review feedback on PR #40476
Five findings from the Copilot pull-request reviewer:
1. wslcsdk.cpp: WslcCreateSessionVhdVolume unconditionally formatted
options->uid / gid / mode via std::to_string and std::format even
when the corresponding flag was not set. The header documents those
fields as honored only when the flag is set, so a defensive caller
could leave them uninitialized. Reading uninitialized memory is UB.
Now only materialize uid/gid strings when FLAG_OWNER is set, and
mode string when FLAG_MODE is set.
2. wslcsdk.idl: SetOwner/SetMode comments said they 'have no effect'
on a VhdRequirements used with the session rootfs VHD. With the
newly-strict WslcSetSessionSettingsVhd those flags now produce
E_INVALIDARG instead of being silently ignored. Updated the IDL
doc-comments to say the assignment will fail.
3. WSLCVhdVolume.cpp: service-side parser still accepted Mode=0,
leaving direct COM callers (and persisted metadata reload) able
to bypass the SDK-side check. Mode==0 is now rejected by Parse()
for parity across all entry points.
4. WslcSdkTests.cpp: the owner+mode positive case only created and
deleted the volume; nothing actually verified that chown/chmod
were applied. Now mounts the volume into a debian:latest container
and runs 'stat -c %u %g %a /data', asserting the output matches
the requested 65534 65534 750.
5. OptionParser.h: lifetime-contract doc-comment was misleading —
it implied accessors return references into the input map. In
practice only Find() returns a pointer (used internally); the
numeric/bool accessors return parsed values by value. Reworded.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add Mode=0 negative test for WSLC vhd parser
Reviewer pointed out the service-side Mode parse tests had thorough
coverage for non-octal, too-large, signed, and empty values, but no
explicit case for the documented invalid value Mode=0 (spec is
1..07777). Mode==0 was already rejected by Parse() in the prior
commit; this just locks the behavior in place.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Validate VhdRequirements::SetMode arguments at WinRT boundary
Reviewer noted that the IDL doc-comment promised SetMode rejected
out-of-range/zero values, but the WinRT setter blindly stored the
value and validation only fired hours later inside CreateVolume.
SetMode now throws hresult_invalid_argument for mode == 0 or
mode > 07777 so callers see immediate failure at the API boundary.
SetOwner doesn't need a parallel check — uid/gid are uint32_t and
all values are valid POSIX user/group IDs.
Also tightened the IDL comment to say validation happens at the
setter (not deferred to creation).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Echo caller-provided value in SizeBytes/Mode validation errors
Reviewer noted the SizeBytes==0 and Mode==0 rejection paths in
VhdVolumeOptions::Parse hard-coded the literal 0 in their error
messages instead of echoing the original input from DriverOpts.
If a caller passed SizeBytes=00 or Mode=000, the error said '0',
diverging from OptionParser's usual 'Invalid value for option
<name>: <original>' wording.
Both keys are guaranteed present in DriverOpts when these checks
fire (Required<> already succeeded for SizeBytes; Mode.has_value()
is the precondition for the Mode check), so .at() will not throw.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Reject mode>07777 in C SDK and trim verbose comments
The C SDK only rejected mode==0; the WinRT setter and the public
header both promise mode<=07777 too. Aligning all three layers so
callers see immediate, consistent E_INVALIDARG.
Also a comment-bloat pass on this PR: kept "why" notes (uid/gid
foot-gun, chmod 0 rationale, c_str lifetime), dropped restatements
of what the code already says.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Move volume Uid/Gid into mkfs -E root_owner; drop Mode option
Per OneBlue review feedback: bake ownership into the ext4 root inode at format
time (mkfs.ext4 -E root_owner=UID:GID) instead of spawning a post-mount chown
helper inside the VM. For a fresh volume the root is the only user-visible
inode so this is equivalent — anything the container later creates inherits
its own uid/gid.
Drop the Mode option entirely. Containers that need non-default permissions
can chmod from inside (it's a per-process concern); the SDK surface stays
minimal. Also drops the now-unused Base parameter from OptionParser.
ABI: WslcVhdRequirements shrinks 40 -> 32 bytes; WSLC_SESSION_OPTIONS_SIZE
96 -> 88. WSLC_VHD_REQ_FLAG_MODE and VhdRequirements::SetMode are removed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* WSLC: tidy comments and add Ext4Format Uid/Gid contract assert
* wslcsdk.cpp: drop stale 'Owner / mode' wording from VHD-rootfs rejection comment.
* wslcsdk.idl: clarify that owner-on-rootfs fails at property-set time (via SetSessionSettingsVhd), not at session creation.
* WSLCVirtualMachine.cpp::Ext4Format: assert Uid.has_value() == Gid.has_value() so a future caller bypassing the parser can't silently drop ownership.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* WSLC: drop misleading std::move on Ext4Format mkfs args
WSLCProcessLauncher's constructor takes its arguments vector by
const-ref, so std::move(args) here is a no-op and only obscures intent.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* WSLC: trim verbose comments around VHD options
Compress over-explained rationale comments in WSLCVhdVolume.cpp,
WSLCVirtualMachine.cpp, OptionParser.h, wslcsdk.cpp/idl, and the
matching tests. No behavior change.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* WSLC: address reviewer feedback on VHD option parser
* OptionParser.h: include <cerrno> directly so the header is self-contained.
* OptionParser: distinguish unknown keys from invalid values. RejectUnknown
now throws via ThrowUnknown with a new MessageWslcUnknownVolumeOption
string ('Unknown option: ...') instead of the misleading 'Invalid value
for option ...' message.
* WSLCVirtualMachine::Ext4Format: replace WI_ASSERT with THROW_HR_IF so a
paired-Uid/Gid contract violation surfaces as a structured failure
instead of a process-termination assert in production builds.
* WslcSdkTests::SessionCreateVhd: add wil::scope_exit cleanup for the
Fixed-VHD sub-test so a mid-test VERIFY failure can't leak the volume.
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>
* fix(l10n): add localization guidance comments for technical terms
Add <comment> elements to en-US/Resources.resw providing translation
guidance for technical terms that are frequently mistranslated:
- File formats (tar, VHD) should not be translated
- Technical terms (mount, swap, sparse, DNS, proxy) need locale-appropriate terms
- Safe mode should use standard OS terminology
These comments help the localization team produce more accurate
translations without directly modifying locale-specific files.
Ref #14090
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(l10n): add period separators and glossary comment
- Add '.' between existing guidance and appended technical term
guidance in 16 comment lines (fixes run-on sentences)
- Add top-level XML glossary comment listing all technical terms
(mount, VHD, tar, swap, sparse, DNS, proxy, safe mode) for
developer/reviewer reference
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Save state
* Merge remote-tracking branch 'origin/feature/wsl-for-apps' into user/oneblue/wslc-gpu
* Save state
* Save state
* Change --gpu flag to --gpus all for GPU container support
- Rename --gpu (boolean flag) to --gpus (value argument) matching Docker CLI
- Only accept 'all' as value (case-insensitive); display localized error otherwise
- Add argument validation in ArgumentValidation.cpp (early rejection)
- Add GPU LD_LIBRARY_PATH tests for containers (set, pre-existing, trailing colon)
- Add GPU LD_LIBRARY_PATH tests for exec on GPU containers
- Add CLI argument validation unit tests for --gpus
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Improve tests
* Add SDK test coverage for GPU container support
Validate that containers created via the WSLC SDK with both session
(WSLC_SESSION_FEATURE_FLAG_ENABLE_GPU) and container
(WSLC_CONTAINER_FLAG_ENABLE_GPU) flags have:
- /dev/dxg character device available
- GPU drivers directory mounted at /usr/lib/wsl/drivers
- GPU libraries directory mounted at /usr/lib/wsl/lib
- LD_LIBRARY_PATH set correctly for init and exec processes
- LD_LIBRARY_PATH appended when pre-existing value is provided
- No double colon when pre-existing value has trailing colon
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add SDK test coverage
* Simplify tests
* Apply PR feedback
* Fix e2e HelpCommand tests for --gpus rename
Add --gpus option to expected help output in container create and run
e2e tests. The option was renamed from --gpu (flag) to --gpus (value).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Update tests
* Apply PR suggestions
* Update localization
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add admin protection error message for shadow admin scenarios
When Windows Admin Protection is enabled, the elevated process runs as a
shadow admin with a different SID, so distributions registered under the
real user are not visible. Surface an informational message in two cases:
1. Launching a distribution by name that is not found (WSL_E_DISTRO_NOT_FOUND)
2. Listing distributions when none are registered (WSL_E_DEFAULT_DISTRO_NOT_FOUND)
* formatting
* Show admin protection message for non-elevated users too
When Admin Protection creates a shadow admin, distros registered under
the real user are invisible to the shadow admin and vice versa. Remove
the elevation check so the informational message appears for both
elevated and non-elevated callers.
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>