Skip to content

Add Uid/Gid/Mode/Fixed driver options to WSLC VHD volumes; expose via SDK#40476

Draft
benhillis wants to merge 4 commits intomicrosoft:masterfrom
benhillis:wslc-vhd-options-uid-gid-mode-fixed
Draft

Add Uid/Gid/Mode/Fixed driver options to WSLC VHD volumes; expose via SDK#40476
benhillis wants to merge 4 commits intomicrosoft:masterfrom
benhillis:wslc-vhd-options-uid-gid-mode-fixed

Conversation

@benhillis
Copy link
Copy Markdown
Member

Summary

Adds POSIX Uid/Gid/Mode and pre-allocated Fixed driver options to the WSLC named-volume vhd driver, and plumbs them through the public C SDK and the WinRT projection.

Today, containers running as a non-root user cannot write to their own persistent volumes — mkfs.ext4 leaves the root owned by root:root with mode 0755, and the vhd driver only accepted SizeBytes. The new options let the SDK consumer create a volume that is immediately usable by the in-container user.

Fixed lets workloads that need predictable I/O pre-allocate the VHD instead of growing it dynamically.

Driver options (named volumes)

Option Type Notes
SizeBytes uint64 (decimal) required; >0
Fixed bool (true/false/1/0) optional; defaults to false
Uid uint32 (decimal) optional; must be paired with Gid
Gid uint32 (decimal) optional; must be paired with Uid
Mode uint32 (octal) optional; 1..07777

All option parsing now goes through a new shared OptionParser helper (Required<T>/Optional<T>/OptionalBool/RejectUnknown) with errno-capture, end-pointer validation, leading-sign rejection, and consumed-key tracking. The Open path uses the same parser so persisted metadata is validated identically on reload.

C SDK (wslcsdk.h / wslcsdk.dll)

typedef enum WslcVhdRequirementsFlags {
    WSLC_VHD_REQ_FLAG_NONE  = 0,
    WSLC_VHD_REQ_FLAG_OWNER = 1,
    WSLC_VHD_REQ_FLAG_MODE  = 2,
} WslcVhdRequirementsFlags;

typedef struct WslcVhdRequirements {
    PCSTR    name;
    uint64_t sizeBytes;
    WslcVhdType type;
    WslcVhdRequirementsFlags flags;
    uint32_t uid;
    uint32_t gid;
    uint32_t mode;
} WslcVhdRequirements;

WslcCreateSessionVhdVolume:

  • honors WSLC_VHD_TYPE_FIXED (was previously E_NOTIMPL)
  • dynamically builds the underlying WSLCDriverOption[] based on the flag bits
  • rejects unknown type values, unknown flag bits, and mode == 0 with E_INVALIDARG so future additions cannot be silently fail-open and obvious foot-guns are caught client-side
  • formats mode as octal (std::format("{:o}", mode)) so the service-side base-8 parser round-trips it correctly

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 those fields.

Important

ABI break: WSLC_SESSION_OPTIONS_SIZE bumps 80 → 96 to match the wider embedded WslcVhdRequirements. Callers of wslcsdk.lib/wslcsdk.dll must recompile against the new header.

Caller usage

WslcVhdRequirements vhd = {0};
vhd.name      = "data";
vhd.sizeBytes = 64ULL << 20;
vhd.type      = WSLC_VHD_TYPE_FIXED;
vhd.flags     = WSLC_VHD_REQ_FLAG_OWNER | WSLC_VHD_REQ_FLAG_MODE;
vhd.uid       = 65534;
vhd.gid       = 65534;
vhd.mode      = 0750;
WslcCreateSessionVhdVolume(session, &vhd, &errorMsg);

WinRT projection (Microsoft.WSL.Containers)

runtimeclass VhdRequirements {
    VhdRequirements(String name, UInt64 sizeInBytes, VhdType type);
    String  Name        { get; };
    UInt64  SizeInBytes { get; };
    VhdType Type        { get; };

    void SetOwner(UInt32 uid, UInt32 gid);
    void SetMode(UInt32 mode);
}

Pair-based SetOwner avoids the half-set foot-gun that per-property setters would create. Default-constructed VhdRequirements (or any path that does not call the new setters) gets flags = NONE = old behavior.

var req = new VhdRequirements("data", 64UL << 20, VhdType.Fixed);
req.SetOwner(65534, 65534);
req.SetMode(0x1E8); // 0o750

The WinRT projection has no test infrastructure in the repo (the existing Name/SizeInBytes/Type projection has never had tests either). Behavior is fully covered at the C SDK layer that the projection delegates to.

Tests

  • WSLCTests.cpp
    • NamedVolumeVhdOptionsParseTest — SizeBytes presence/range/base, unknown-key rejection (Bogus=...), sign rejection
    • NamedVolumesVhdOwnership — chown/chmod end-to-end
    • NamedVolumesVhdFixed — asserts on-disk file_size >= requested
  • WslcSdkTests.cpp
    • Invalid type → E_INVALIDARG
    • Fixed positive (size verification on disk)
    • OWNER | MODE positive (uid=65534, gid=65534, mode=0750)
    • Mode out-of-range (> 07777) → E_INVALIDARG
    • Mode == 0 → E_INVALIDARG
    • Unknown flag bit → E_INVALIDARG
    • flags=NONE with non-zero uid/gid silently ignored

Validation status

  • cmake --build clean (wslservice, wslcsession, wslcsdk, wslcsdkwinrt, wsltests)
  • FormatSource.ps1 -ModifiedOnly $true — clang-format reports no changes
  • bin\x64\Debug\test.bat /name:WSLCTests::*NamedVolumeVhd* — needs Administrator
  • bin\x64\Debug\test.bat /name:WSLCTests::*NamedVolumesVhd* — needs Administrator
  • SDK functional tests under WslcSdkTests — needs Administrator
  • python tools/devops/validate-localization.py — local Python is MS Store stub
  • python tools/devops/validate-copyright-headers.py — local Python is MS Store stub

Marking as draft until the admin-elevated test runs and python validators land.

… 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>
Copilot AI review requested due to automatic review settings May 8, 2026 23:22
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds support for additional vhd named-volume driver options (POSIX Uid/Gid/Mode and Fixed allocation) and exposes them through the public WSLC C SDK and WinRT projection so SDK callers can create volumes usable by non-root container users and optionally pre-allocate VHD space.

Changes:

  • Added a shared OptionParser utility and updated the VHD volume driver to parse/validate SizeBytes, Fixed, Uid/Gid, and Mode, applying ownership/permissions at volume creation time.
  • Extended the C SDK WslcVhdRequirements contract (new flags + fields) and updated WslcCreateSessionVhdVolume / WslcSetSessionSettingsVhd behavior accordingly.
  • Added/updated Windows tests for driver-option parsing and SDK behavior, plus a new localized error string for invalid volume options.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
test/windows/WSLCTests.cpp Expands VHD named-volume option parsing tests; adds end-to-end ownership/mode and fixed-allocation coverage.
test/windows/WslcSdkTests.cpp Updates SDK tests for fixed VHD support, new owner/mode flags, and validation behavior.
src/windows/wslcsession/WSLCVhdVolume.cpp Implements parsing/validation for new VHD driver options and applies chown/chmod during volume creation; uses shared parser.
src/windows/wslcsession/OptionParser.h Introduces typed driver-option parsing interface (templates + consumed-key tracking).
src/windows/wslcsession/OptionParser.cpp Implements lookup, bool parsing, unknown-option rejection, and localized error throwing for OptionParser.
src/windows/wslcsession/CMakeLists.txt Wires new OptionParser sources into the wslcsession build.
src/windows/WslcSDK/wslcsdk.h Extends public C SDK types (WslcVhdRequirementsFlags, larger WslcVhdRequirements, updated session options size).
src/windows/WslcSDK/wslcsdk.cpp Updates WslcCreateSessionVhdVolume to emit new driver options and WslcSetSessionSettingsVhd to reject unsupported flags.
src/windows/WslcSDK/winrt/wslcsdk.idl Extends WinRT VhdRequirements projection with SetOwner/SetMode.
src/windows/WslcSDK/winrt/VhdRequirements.h Adds WinRT wrapper method declarations for owner/mode setters.
src/windows/WslcSDK/winrt/VhdRequirements.cpp Implements WinRT owner/mode setters by setting C SDK struct fields and flags.
localization/strings/en-US/Resources.resw Adds localized MessageWslcInvalidVolumeOption string used for consistent option-validation errors.

Comment thread src/windows/WslcSDK/wslcsdk.cpp Outdated
Comment thread src/windows/WslcSDK/winrt/wslcsdk.idl Outdated
Comment thread src/windows/wslcsession/WSLCVhdVolume.cpp
Comment thread test/windows/WslcSdkTests.cpp
Comment thread src/windows/wslcsession/OptionParser.h Outdated
@benhillis benhillis requested a review from Copilot May 8, 2026 23:36
benhillis pushed a commit to benhillis/WSL that referenced this pull request May 8, 2026
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>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 14 out of 14 changed files in this pull request and generated 7 comments.

Comment thread src/windows/wslcsession/WSLCVhdVolume.cpp
Comment thread src/windows/WslcSDK/winrt/wslcsdk.idl
Comment thread src/windows/wslcsession/OptionParser.h
Comment thread test/windows/WSLCTests.cpp
Comment thread test/windows/WSLCTests.cpp
Comment thread test/windows/WSLCTests.cpp
Comment thread test/windows/WslcSdkTests.cpp
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>
@benhillis benhillis force-pushed the wslc-vhd-options-uid-gid-mode-fixed branch from b06edbb to a576eac Compare May 8, 2026 23:42
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>
Copilot AI review requested due to automatic review settings May 8, 2026 23:52
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 12 out of 12 changed files in this pull request and generated 1 comment.

Comment thread src/windows/WslcSDK/winrt/VhdRequirements.cpp
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>
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.

2 participants