Use unified kernel artifacts VHD layout (#41267)
Update the packaged kernel dependency from modules.vhd to artifacts.vhd and load kernel modules from the versioned <release>/modules directory. Preserve support for legacy flat module VHDs in the standard WSL boot path and teach WSLC sessions to mount modules from the nested layout. Update development paths and kernel module tests for the renamed VHD and new mount source. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Copilot-Session: a7cc3086-6311-4bc8-a591-49fd0e6b03fd
Ben Hillis committed
Aug 20, 2026 at 10:57 UTC
9540481e9d276a3a732b22057c872f9bc6d41a0c
12 files changed
+120
-39
CMakeLists.txt
+1
-1
@@ -518,7 +518,7 @@ endif()
518
if (DEFINED WSL_DEV_BINARY_PATH) # Development shortcut to make the package smaller
519
add_compile_definitions(WSL_SYSTEM_DISTRO_PATH="${WSL_DEV_BINARY_PATH}/system.vhd"
520
WSL_KERNEL_PATH="${WSL_DEV_BINARY_PATH}/kernel"
521
- WSL_KERNEL_MODULES_PATH="${WSL_DEV_BINARY_PATH}/modules.vhd"
521
+ WSL_KERNEL_MODULES_PATH="${WSL_DEV_BINARY_PATH}/artifacts.vhd"
522
WSL_DEV_INSTALL_PATH="${WSL_DEV_BINARY_PATH}"
523
WSL_GPU_LIB_PATH="${WSL_DEV_BINARY_PATH}/lib")
524
endif()
UserConfig.cmake.sample
+2
-2
@@ -14,12 +14,12 @@ if(WSL_DEV_BINARY_PATH)
14
file(MAKE_DIRECTORY ${WSL_DEV_BINARY_PATH})
15
file(CREATE_LINK "${KERNEL_SOURCE_DIR}/bin/${TARGET_PLATFORM}/kernel" "${WSL_DEV_BINARY_PATH}/kernel" SYMBOLIC)
16
file(COPY_FILE "${WSLG_SOURCE_DIR}/${TARGET_PLATFORM}/system.vhd" "${WSL_DEV_BINARY_PATH}/system.vhd" ONLY_IF_DIFFERENT)
17
- file(COPY_FILE "${KERNEL_SOURCE_DIR}/bin/${TARGET_PLATFORM}/modules.vhd" "${WSL_DEV_BINARY_PATH}/modules.vhd" ONLY_IF_DIFFERENT)
17
+ file(COPY_FILE "${KERNEL_SOURCE_DIR}/bin/${TARGET_PLATFORM}/artifacts.vhd" "${WSL_DEV_BINARY_PATH}/artifacts.vhd" ONLY_IF_DIFFERENT)
18
19
# read-only VHDs need to be world readable to mount successfully.
20
execute_process(
21
COMMAND icacls.exe "${WSL_DEV_BINARY_PATH}/system.vhd" "/grant:r" "Everyone:(R)" /Q
22
- COMMAND icacls.exe "${WSL_DEV_BINARY_PATH}/modules.vhd" "/grant:r" "Everyone:(R)" /Q
22
+ COMMAND icacls.exe "${WSL_DEV_BINARY_PATH}/artifacts.vhd" "/grant:r" "Everyone:(R)" /Q
23
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
24
COMMAND_ERROR_IS_FATAL ANY)
25
msipackage/package.wix.in
+1
-1
@@ -539,7 +539,7 @@
539
540
<?if "${WSL_DEV_BINARY_PATH}" = "" ?>
541
<File Id="kernel" Source="${KERNEL_SOURCE_DIR}/bin/${TARGET_PLATFORM}/kernel"/>
542
- <File Id="modules.vhd" Source="${KERNEL_SOURCE_DIR}/bin/${TARGET_PLATFORM}/modules.vhd"/>
542
+ <File Id="artifacts.vhd" Source="${KERNEL_SOURCE_DIR}/bin/${TARGET_PLATFORM}/artifacts.vhd"/>
543
<?endif?>
544
</Component>
545
</DirectoryRef>
packages.config
+1
-1
@@ -20,7 +20,7 @@
20
<package id="Microsoft.WSL.Dependencies.amd64fre" version="10.0.27820.1000-250318-1700.rs-base2-hyp" targetFramework="native" />
21
<package id="Microsoft.WSL.Dependencies.arm64fre" version="10.0.27820.1000-250318-1700.rs-base2-hyp" targetFramework="native" />
22
<package id="Microsoft.WSL.DeviceHost" version="1.2.60-0" />
23
- <package id="Microsoft.WSL.Kernel" version="6.18.35.2-1" targetFramework="native" />
23
+ <package id="Microsoft.WSL.Kernel" version="6.18.40.1-1" targetFramework="native" />
24
<package id="Microsoft.WSL.LinuxSdk" version="1.23.0" targetFramework="native" />
25
<package id="Microsoft.WSL.TestData" version="0.5.0" />
26
<package id="Microsoft.WSL.TestDistro" version="2.7.1-1" />
src/linux/init/WSLCInit.cpp
+44
-19
@@ -69,6 +69,8 @@ struct WSLCState
69
70
static WSLCState g_state;
71
72
+constexpr auto c_kernelModulesVhdMountPoint = "/kernel_modules_vhd";
73
+
74
void WriteWslcCdiSpec()
75
try
76
{
@@ -696,24 +698,7 @@ void HandleMountMessage(
698
}
699
700
const char* source = readField(Message.SourceIndex);
699
-
700
- const char* target{};
701
- if (WI_IsFlagSet(Message.Flags, WSLC_MOUNT::KernelModules))
702
- {
703
- assert(!g_state.ModulesMountPoint.has_value());
704
-
705
- // Modules need to be mounted to a specific path that depends on the kernel version.
706
-
707
- utsname UnameBuffer{};
708
- THROW_LAST_ERROR_IF(uname(&UnameBuffer) < 0);
709
-
710
- g_state.ModulesMountPoint = std::format("/lib/modules/{}", UnameBuffer.release);
711
- target = g_state.ModulesMountPoint->c_str();
712
- }
713
- else
714
- {
715
- target = readField(Message.DestinationIndex);
716
- }
701
+ const char* target = readField(Message.DestinationIndex);
702
703
// Chroot without OverlayFs is not supported — the chroot logic depends on the overlay target path.
704
THROW_ERRNO_IF(EINVAL, WI_IsFlagSet(Message.Flags, WSLC_MOUNT::Chroot) && !WI_IsFlagSet(Message.Flags, WSLC_MOUNT::OverlayFs));
@@ -842,6 +827,46 @@ void HandleMessageImpl(
827
HandleMountMessage(Channel, Transaction, Message, Buffer);
828
}
829
830
+void HandleMessageImpl(
831
+ wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_MOUNT_MODULES& Message, const gsl::span<gsl::byte>& Buffer)
832
+{
833
+ WSLC_MOUNT_RESULT response{};
834
+ response.Header.MessageType = WSLC_MOUNT_RESULT::Type;
835
+ response.Header.MessageSize = sizeof(response);
836
+
837
+ try
838
+ {
839
+ assert(!g_state.ModulesMountPoint.has_value());
840
+
841
+ utsname unameBuffer{};
842
+ THROW_LAST_ERROR_IF(uname(&unameBuffer) < 0);
843
+
844
+ const char* source = wsl::shared::string::FromSpan(Buffer, Message.SourceIndex);
845
+ THROW_LAST_ERROR_IF(UtilMount(source, c_kernelModulesVhdMountPoint, "ext4", MS_RDONLY, nullptr, c_defaultRetryTimeout) < 0);
846
+
847
+ auto unmountVhd = wil::scope_exit([&]() {
848
+ if (umount(c_kernelModulesVhdMountPoint) < 0)
849
+ {
850
+ LOG_ERROR("umount({}) failed {}", c_kernelModulesVhdMountPoint, errno);
851
+ }
852
+ });
853
+
854
+ g_state.ModulesMountPoint = std::format("/lib/modules/{}", unameBuffer.release);
855
+ const std::string modulesSource = std::format("{}/{}/modules", c_kernelModulesVhdMountPoint, unameBuffer.release);
856
+ THROW_LAST_ERROR_IF(
857
+ UtilMount(modulesSource.c_str(), g_state.ModulesMountPoint->c_str(), nullptr, (MS_BIND | MS_REC), nullptr, c_defaultRetryTimeout) < 0);
858
+
859
+ response.Result = 0;
860
+ }
861
+ catch (...)
862
+ {
863
+ LOG_CAUGHT_EXCEPTION();
864
+ response.Result = wil::ResultFromCaughtException();
865
+ }
866
+
867
+ Transaction.Send<WSLC_MOUNT_RESULT>(response);
868
+}
869
+
870
void HandleMessageImpl(
871
wsl::shared::SocketChannel& Channel, wsl::shared::Transaction& Transaction, const WSLC_EXEC& Message, const gsl::span<gsl::byte>& Buffer)
872
{
@@ -1079,7 +1104,7 @@ void ProcessMessage(wsl::shared::SocketChannel& Channel, wsl::shared::Transactio
1104
{
1105
try
1106
{
1082
- HandleMessage<WSLC_GET_DISK, WSLC_MOUNT, WSLC_MOUNT_VIRTIOFS, WSLC_EXEC, WSLC_FORK, WSLC_CONNECT, WSLC_SIGNAL, WSLC_TTY_RELAY, WSLC_PORT_RELAY, WSLC_UNMOUNT, WSLC_DETACH, WSLC_ACCEPT, WSLC_WATCH_PROCESSES, WSLC_UNIX_CONNECT, WSLC_GET_GUEST_CAPABILITIES, WSLC_LISTDIR, WSLC_WRITE_FILE>(
1107
+ HandleMessage<WSLC_GET_DISK, WSLC_MOUNT, WSLC_MOUNT_VIRTIOFS, WSLC_MOUNT_MODULES, WSLC_EXEC, WSLC_FORK, WSLC_CONNECT, WSLC_SIGNAL, WSLC_TTY_RELAY, WSLC_PORT_RELAY, WSLC_UNMOUNT, WSLC_DETACH, WSLC_ACCEPT, WSLC_WATCH_PROCESSES, WSLC_UNIX_CONNECT, WSLC_GET_GUEST_CAPABILITIES, WSLC_LISTDIR, WSLC_WRITE_FILE>(
1108
Channel, Transaction, Type, Buffer);
1109
}
1110
catch (...)
src/linux/init/main.cpp
+30
-3
@@ -3223,7 +3223,10 @@ try
3223
// N.B. The VHD is mounted as read-only but with a writable overlayfs layer. The modules
3224
// directory must be writable for tools like depmod to work.
3225
//
3226
-
3226
+ // N.B. The artifacts VHD nests the modules under <release>/modules.
3227
+ // Older module-only VHDs place the modules tree at the filesystem root; fall back to that
3228
+ // layout when the nested modules directory is not present.
3229
+ //
3230
if (EarlyConfig->KernelModulesDeviceId != UINT_MAX)
3231
{
3232
THROW_LAST_ERROR_IF(
@@ -3232,9 +3235,33 @@ try
3235
3236
utsname UnameBuffer{};
3237
THROW_LAST_ERROR_IF(uname(&UnameBuffer) < 0);
3238
+ const std::string Release{UnameBuffer.release};
3239
+
3240
+ const std::string ArtifactsBase = std::format("{}/{}", KERNEL_MODULES_VHD_PATH, Release);
3241
+ const std::string NestedModules = ArtifactsBase + "/modules";
3242
+
3243
+ std::error_code Error{};
3244
+ const bool NestedLayout = std::filesystem::is_directory(NestedModules, Error);
3245
+ const std::string ModulesLower = NestedLayout ? NestedModules : std::string{KERNEL_MODULES_VHD_PATH};
3246
+ const bool LegacyLayout = !NestedLayout && std::filesystem::is_regular_file(ModulesLower + "/modules.dep", Error);
3247
+
3248
+ //
3249
+ // A valid artifacts VHD nests the tree under <release>/modules; a legacy module-only VHD
3250
+ // places it at the root.
3251
+ //
3252
+ if (LegacyLayout)
3253
+ {
3254
+ LOG_WARNING(
3255
+ "kernel modules VHD uses the legacy flat layout; support for the legacy modules VHD format will be "
3256
+ "removed in a future version");
3257
+ }
3258
+ else if (!NestedLayout)
3259
+ {
3260
+ LOG_WARNING("kernel modules VHD does not contain modules for {}", Release);
3261
+ }
3262
3236
- std::string Target = std::format("{}/{}", KERNEL_MODULES_PATH, UnameBuffer.release);
3237
- THROW_LAST_ERROR_IF(UtilMountOverlayFs(Target.c_str(), KERNEL_MODULES_VHD_PATH, (MS_NOATIME | MS_NOSUID | MS_NODEV)) < 0);
3263
+ std::string Target = std::format("{}/{}", KERNEL_MODULES_PATH, Release);
3264
+ THROW_LAST_ERROR_IF(UtilMountOverlayFs(Target.c_str(), ModulesLower.c_str(), (MS_NOATIME | MS_NOSUID | MS_NODEV)) < 0);
3265
3266
const std::string KernelModulesList = wsl::shared::string::FromSpan(Buffer, EarlyConfig->KernelModulesListOffset);
3267
for (const auto& Module : wsl::shared::string::Split(KernelModulesList, ','))
src/shared/inc/lxinitshared.h
+17
-2
@@ -417,6 +417,7 @@ typedef enum _LX_MESSAGE_TYPE
417
LxMessageWSLCWriteFile,
418
LxMiniInitMessageTrimDistribution,
419
LxMiniInitMessageTrimDistributionResponse,
420
+ LxMessageWSLCMountModules,
421
} LX_MESSAGE_TYPE,
422
*PLX_MESSAGE_TYPE;
423
@@ -535,6 +536,7 @@ inline auto ToString(LX_MESSAGE_TYPE messageType)
536
X(LxMessageWSLCWriteFile)
537
X(LxMiniInitMessageTrimDistribution)
538
X(LxMiniInitMessageTrimDistributionResponse)
539
+ X(LxMessageWSLCMountModules)
540
541
default:
542
return "<unexpected LX_MESSAGE_TYPE>";
@@ -1676,8 +1678,7 @@ struct WSLC_MOUNT
1678
None,
1679
ReadOnly = 1,
1680
Chroot = 2,
1679
- OverlayFs = 4,
1680
- KernelModules = 8
1681
+ OverlayFs = 4
1682
};
1683
1684
char Buffer[];
@@ -1704,6 +1705,20 @@ struct WSLC_MOUNT_VIRTIOFS
1705
PRETTY_PRINT(FIELD(Header), STRING_FIELD(SourceIndex), STRING_FIELD(DestinationIndex), STRING_FIELD(TypeIndex), STRING_FIELD(OptionsIndex), STRING_FIELD(ChildNameIndex));
1706
};
1707
1708
+struct WSLC_MOUNT_MODULES
1709
+{
1710
+ static inline auto Type = LxMessageWSLCMountModules;
1711
+ using TResponse = WSLC_MOUNT_RESULT;
1712
+
1713
+ DECLARE_MESSAGE_CTOR(WSLC_MOUNT_MODULES);
1714
+
1715
+ MESSAGE_HEADER Header{};
1716
+ unsigned int SourceIndex{};
1717
+ char Buffer[];
1718
+
1719
+ PRETTY_PRINT(FIELD(Header), STRING_FIELD(SourceIndex));
1720
+};
1721
+
1722
struct WSLC_EXEC
1723
{
1724
static inline auto Type = LxMessageWSLCExec;
src/windows/service/exe/HcsVirtualMachine.cpp
+1
-1
@@ -238,7 +238,7 @@ HcsVirtualMachine::HcsVirtualMachine(_In_ const WSLCSessionSettings* Settings)
238
#ifdef WSL_KERNEL_MODULES_PATH
239
auto kernelModulesPath = std::filesystem::path(TEXT(WSL_KERNEL_MODULES_PATH));
240
#else
241
- auto kernelModulesPath = basePath / L"tools" / L"modules.vhd";
241
+ auto kernelModulesPath = basePath / L"tools" / L"artifacts.vhd";
242
#endif
243
244
// Get root VHD path
src/windows/service/exe/WslCoreVm.cpp
+1
-1
@@ -236,7 +236,7 @@ void WslCoreVm::Initialize(const GUID& VmId, const wil::shared_handle& UserToken
236
237
#else
238
239
- m_vmConfig.KernelModulesPath = m_rootFsPath / L"modules.vhd";
239
+ m_vmConfig.KernelModulesPath = m_rootFsPath / L"artifacts.vhd";
240
241
#endif
242
}
src/windows/wslcsession/WSLCVirtualMachine.cpp
+13
-1
@@ -364,7 +364,7 @@ void WSLCVirtualMachine::Initialize()
364
Mount(m_initChannel, rootDevice.c_str(), "/mnt", m_rootVhdType.c_str(), "ro", WSLC_MOUNT::Chroot | WSLC_MOUNT::OverlayFs);
365
366
const auto modulesDevice = GetVhdDevicePath(1);
367
- Mount(m_initChannel, modulesDevice.c_str(), "", "ext4", "ro", WSLC_MOUNT::KernelModules);
367
+ MountModules(m_initChannel, modulesDevice.c_str());
368
369
// Discover the per-VM guest capabilities (currently the hv_pci swiotlb pool) and forward them
370
// to the service before virtiofs shares or Consomme networking devices are created.
@@ -984,6 +984,18 @@ void WSLCVirtualMachine::Mount(shared::SocketChannel& Channel, LPCSTR Source, LP
984
THROW_HR_IF(E_FAIL, response.Result != 0);
985
}
986
987
+void WSLCVirtualMachine::MountModules(shared::SocketChannel& Channel, LPCSTR Source)
988
+{
989
+ wsl::shared::MessageWriter<WSLC_MOUNT_MODULES> message;
990
+ message.WriteString(message->SourceIndex, Source);
991
+
992
+ const auto& response = Channel.Transaction<WSLC_MOUNT_MODULES>(message.Span());
993
+
994
+ WSL_LOG("WSLCMountModules", TraceLoggingValue(Source, "Source"), TraceLoggingValue(response.Result, "Result"));
995
+
996
+ THROW_HR_IF(E_FAIL, response.Result != 0);
997
+}
998
+
999
void WSLCVirtualMachine::MountVirtioFsChild(shared::SocketChannel& Channel, LPCSTR Source, LPCSTR ChildName, LPCSTR Target, LPCSTR Options, ULONG Flags)
1000
{
1001
wsl::shared::MessageWriter<WSLC_MOUNT_VIRTIOFS> message;
src/windows/wslcsession/WSLCVirtualMachine.h
+1
@@ -237,6 +237,7 @@ private:
237
void ConfigureBuildKitPolicy();
238
239
static void Mount(wsl::shared::SocketChannel& Channel, LPCSTR Source, _In_ LPCSTR Target, _In_ LPCSTR Type, _In_ LPCSTR Options, _In_ ULONG Flags);
240
+ static void MountModules(wsl::shared::SocketChannel& Channel, _In_ LPCSTR Source);
241
static void MountVirtioFsChild(
242
wsl::shared::SocketChannel& Channel, _In_ LPCSTR Source, _In_ LPCSTR ChildName, _In_ LPCSTR Target, _In_ LPCSTR Options, _In_ ULONG Flags);
243
void MountGpuLibraries(_In_ LPCSTR LibrariesMountPoint, _In_ LPCSTR DriversMountpoint);
test/windows/UnitTests.cpp
+8
-7
@@ -2923,10 +2923,11 @@ Error code: Wsl/InstallDistro/WSL_E_DISTRO_NOT_FOUND
2923
// Ensure the kernel modules folder is mounted correctly.
2924
std::wstring command = std::format(
2925
L"mount | grep -iF 'none on /usr/lib/modules/{} type overlay "
2926
- L"(rw,nosuid,nodev,noatime,lowerdir=/modules,upperdir=/lib/modules/{}/rw/upper,workdir=/lib/modules/{}/rw/"
2926
+ L"(rw,nosuid,nodev,noatime,lowerdir=/modules/{}/modules,upperdir=/lib/modules/{}/rw/upper,workdir=/lib/modules/{}/rw/"
2927
L"work,uuid=on)'",
2928
kernelVersion,
2929
kernelVersion,
2930
+ kernelVersion,
2931
kernelVersion);
2932
2933
VERIFY_ARE_EQUAL(LxsstuLaunchWsl(command.c_str(), nullptr, nullptr, nullptr, nullptr), 0u);
@@ -2953,7 +2954,7 @@ Error code: Wsl/InstallDistro/WSL_E_DISTRO_NOT_FOUND
2954
#ifdef WSL_DEV_INSTALL_PATH
2955
2956
std::wstring kernelPath = WSL_DEV_INSTALL_PATH L"/kernel";
2956
- std::wstring kernelModulesPath = WSL_DEV_INSTALL_PATH L"/modules.vhd";
2957
+ std::wstring kernelModulesPath = WSL_DEV_INSTALL_PATH L"/artifacts.vhd";
2958
2959
#else
2960
@@ -2963,7 +2964,7 @@ Error code: Wsl/InstallDistro/WSL_E_DISTRO_NOT_FOUND
2964
std::filesystem::path wslInstallPath(installPath.value());
2965
2966
std::wstring kernelPath = wslInstallPath / "tools" / "kernel";
2966
- std::wstring kernelModulesPath = wslInstallPath / "tools" / "modules.vhd";
2967
+ std::wstring kernelModulesPath = wslInstallPath / "tools" / "artifacts.vhd";
2968
2969
#endif
2970
@@ -6809,13 +6810,13 @@ Error code: Wsl/InstallDistro/WSL_E_INVALID_JSON\r\n",
6810
// systemDistro VHDs live under the user profile and VMWP wasn't granted access.
6811
#ifdef WSL_DEV_INSTALL_PATH
6812
6812
- const auto modulesPath = std::format(L"{}\\modules.vhd", WSL_DEV_INSTALL_PATH);
6813
+ const auto modulesPath = std::format(L"{}\\artifacts.vhd", WSL_DEV_INSTALL_PATH);
6814
const auto kernelPath = std::format(L"{}\\kernel", WSL_DEV_INSTALL_PATH);
6815
const auto systemDistroPath = std::format(L"{}\\system.vhd", WSL_DEV_INSTALL_PATH);
6816
6817
#else
6818
const auto installPath = wsl::windows::common::wslutil::GetMsiPackagePath().value();
6818
- const auto modulesPath = std::format(L"{}\\tools\\modules.vhd", installPath);
6819
+ const auto modulesPath = std::format(L"{}\\tools\\artifacts.vhd", installPath);
6820
const auto kernelPath = std::format(L"{}\\tools\\kernel", installPath);
6821
const auto systemDistroPath = std::format(L"{}\\system.vhd", installPath);
6822
@@ -6862,13 +6863,13 @@ Error code: Wsl/InstallDistro/WSL_E_INVALID_JSON\r\n",
6863
// impersonated user lacks WRITE_DAC for HcsGrantVmAccess.
6864
#ifdef WSL_DEV_INSTALL_PATH
6865
6865
- const auto modulesPath = std::format(L"{}\\modules.vhd", WSL_DEV_INSTALL_PATH);
6866
+ const auto modulesPath = std::format(L"{}\\artifacts.vhd", WSL_DEV_INSTALL_PATH);
6867
const auto kernelPath = std::format(L"{}\\kernel", WSL_DEV_INSTALL_PATH);
6868
const auto systemDistroPath = std::format(L"{}\\system.vhd", WSL_DEV_INSTALL_PATH);
6869
6870
#else
6871
const auto installPath = wsl::windows::common::wslutil::GetMsiPackagePath().value();
6871
- const auto modulesPath = std::format(L"{}\\tools\\modules.vhd", installPath);
6872
+ const auto modulesPath = std::format(L"{}\\tools\\artifacts.vhd", installPath);
6873
const auto kernelPath = std::format(L"{}\\tools\\kernel", installPath);
6874
const auto systemDistroPath = std::format(L"{}\\system.vhd", installPath);
6875