When the embedded MSI in the Microsoft Store MSIX cannot replace a locked
file (most commonly system.vhd, which is mounted whenever a WSL2 instance
is running), Windows Installer renames the existing file to
C:\Windows\Installer\Config.Msi\<hex>.rbf, schedules the new file via
MoveFileEx(MOVEFILE_DELAY_UNTIL_REBOOT), and returns
ERROR_SUCCESS_REBOOT_REQUIRED (3010).
InstallMsipackageImpl in wslinstaller silently converted 3010 to
ERROR_SUCCESS and returned success to its caller. The user was told
nothing; their next wsl invocation hit a now-empty C:\Program Files\WSL
install dir (system.vhd physically gone until reboot) and produced a
confusing "vhd missing" failure - perceived as data loss.
This change:
* Stops swallowing 3010 in InstallMsipackageImpl. The MSI log is now
preserved on 3010 (previously deleted) to aid diagnostics.
* Sets a volatile registry marker
(HKLM\Software\Microsoft\Windows\CurrentVersion\Lxss\MSI\RebootPending)
using REG_OPTION_VOLATILE so the kernel auto-clears it on reboot. No
cleanup path is needed; the marker is gone iff the user has rebooted.
* Adds a marker check in LxssUserSession::_CreateInstance (service
side) which throws a localized user-facing error
(MessageUpdateRebootRequired) any time a client tries to launch a
distro while the reboot is pending. This catches all distro-launching
client paths through the single service entry point: wsl.exe (lifted
and MSI-installed), wslg.exe, bash.exe, VS Code Remote-WSL, etc.
* Also checks the marker on entry to CallMsiPackage and throws on 3010
in WaitForMsiInstall, so the wsl --update / lifted-client paths
surface the same error.
User-visible behavior:
> wsl
WSL was updated but a system restart is required to complete the
installation. Please reboot your machine and try again.
Error code: Wsl/Service/CreateInstance/0x80070bc2
The user reboots; the volatile key is destroyed by the kernel; Windows'
pending file-rename queue swaps the staged file into place; WSL works
again.
Adds an integration test, InstallerTests::UpgradeWithLockedFileReportsRebootRequired,
that exercises the full path: uninstalls the MSI, memory-maps a dummy
file at the install path to make it unrenameable, runs the MSIX
installer to drive the WslInstaller service, polls for the marker, then
verifies wsl echo OK fails with the expected message before cleaning up.
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>
* Fallback to NAT when IPv6 is disabled via registry for mirrored networking
When the registry key HKLM\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters
has DisabledComponents set to 0xFF (all IPv6 components disabled), mirrored
networking mode cannot mirror host interfaces. This adds a check in
ValidateNetworkingMode() that detects this condition and falls back to NAT
networking mode with a user-facing warning.
Only mirrored networking mode is affected by this registry key; other
networking modes (NAT, Bridged, VirtioProxy) are not checked.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* add registry check
* pr review
* remove shutdown
* re-add shutdown
---------
Co-authored-by: Catalin-Emil Fetoiu <cfetoiu@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>