Fix: Reattach stale cached device after backing volume is reattached (#41379)

The current disk attach caching logic only checks the vhd path. When the backing volume is detached than reattached, the check will pass but the stale reference won't work inside the VM. The backing volume reattaching can happen after bitlocker locking and unlocking #12599, or vhdx detaching then reattaching. This PR checks if the cached vhd is stale by holding a handle to the vhd file. And checks if it's still valid before reuse. If not, the vhd will be reattached.

Feng Wang committed Aug 21, 2026 at 10:24 UTC 93f74a35817beb9f6d771f0d8be4d90f7f74072e
3 files changed +38 -4
src/windows/service/exe/WslCoreVm.cpp
+36 -3
@@ -73,6 +73,21 @@ RequiredExtraMmioSpaceForPmemFileInMb(_In_ PCWSTR FilePath)
73 // Convert from bytes to megabytes. Ensure that we don't truncate a 512kb file to 0mb.
74 return std::max(fileSizeBytes.QuadPart / static_cast<INT64>(_1MB), 1i64);
75 }
76 +
77 +wil::unique_hfile OpenVhdBackingFile(_In_ PCWSTR Path)
78 +{
79 + wil::unique_hfile file{CreateFileW(
80 + Path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)};
81 + THROW_LAST_ERROR_IF(!file);
82 +
83 + return file;
84 +}
85 +
86 +bool IsBackingVolumeMounted(_In_ HANDLE File)
87 +{
88 + DWORD bytesReturned{};
89 + return DeviceIoControl(File, FSCTL_IS_VOLUME_MOUNTED, nullptr, 0, nullptr, 0, &bytesReturned, nullptr);
90 +}
91 } // namespace
92
93 WslCoreVm::WslCoreVm(_In_ wsl::core::Config&& VmConfig, _In_ InitializeDrvFsCallback InitializeDrvFs) :
@@ -993,6 +1008,7 @@ ULONG WslCoreVm::AttachDiskLockHeld(
1008
1009 // Set a scope exit variable to perform cleanup if attaching the disk fails.
1010 DiskStateFlags diskFlags{};
1011 + wil::unique_hfile backingFile;
1012 auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
1013 FreeLun(Lun.value());
1014 if (WI_IsFlagSet(diskFlags, DiskStateFlags::AccessGranted))
@@ -1050,9 +1066,25 @@ ULONG WslCoreVm::AttachDiskLockHeld(
1066 // Prevent user from launching a distro vhd after manually mounting it; otherwise, return the LUN of the mounted disk.
1067 THROW_HR_IF(WSL_E_USER_VHD_ALREADY_ATTACHED, found->first.User);
1068
1053 - return found->second.Lun;
1069 + // Check if the lun is still valid. It could be stale if the backing volume is reattached.
1070 + if (IsBackingVolumeMounted(found->second.BackingFile.get()))
1071 + {
1072 + return found->second.Lun;
1073 + }
1074 +
1075 + const auto staleLun = found->second.Lun;
1076 + wsl::windows::common::hcs::RemoveScsiDisk(m_system.get(), staleLun);
1077 + if (WI_IsFlagSet(found->second.Flags, DiskStateFlags::AccessGranted))
1078 + {
1079 + wsl::windows::common::hcs::RevokeVmAccess(m_machineId.c_str(), found->first.Path.c_str());
1080 + }
1081 +
1082 + m_attachedDisks.erase(found);
1083 + FreeLun(staleLun);
1084 }
1085
1086 + backingFile = OpenVhdBackingFile(Disk);
1087 +
1088 auto grantDiskAccess = [&]() {
1089 auto runAsUser = wil::impersonate_token(UserToken);
1090 wsl::windows::common::hcs::GrantVmAccess(m_machineId.c_str(), Disk);
@@ -1087,7 +1119,7 @@ ULONG WslCoreVm::AttachDiskLockHeld(
1119 result, Localization::MessageFailedToAttachDisk(Disk, wsl::windows::common::wslutil::GetSystemErrorString(result)));
1120 }
1121
1090 - m_attachedDisks.emplace(AttachedDisk{Type, Disk, IsUserDisk}, DiskState{Lun.value(), {}, diskFlags});
1122 + m_attachedDisks.emplace(AttachedDisk{Type, Disk, IsUserDisk}, DiskState{Lun.value(), {}, diskFlags, std::move(backingFile)});
1123 cleanup.release();
1124
1125 return Lun.value();
@@ -1747,6 +1779,7 @@ std::wstring WslCoreVm::GenerateConfigJson()
1779 // inherited ACLs; otherwise StartComputeSystem will surface E_ACCESSDENIED.
1780 auto attachDisk = [&](PCWSTR path, bool grantVmAccess) {
1781 auto lun = ReserveLun();
1782 + auto backingFile = OpenVhdBackingFile(path);
1783 hcs::Attachment disk{};
1784 disk.Type = hcs::AttachmentType::VirtualDisk;
1785 disk.Path = path;
@@ -1768,7 +1801,7 @@ std::wstring WslCoreVm::GenerateConfigJson()
1801 CATCH_LOG()
1802 }
1803
1771 - m_attachedDisks.emplace(AttachedDisk{DiskType::VHD, path, false}, DiskState{lun, {}, diskFlags});
1804 + m_attachedDisks.emplace(AttachedDisk{DiskType::VHD, path, false}, DiskState{lun, {}, diskFlags, std::move(backingFile)});
1805 return lun;
1806 };
1807
src/windows/service/exe/WslCoreVm.h
+1
@@ -163,6 +163,7 @@ private:
163 ULONG Lun;
164 std::map<ULONG, Mount> Mounts;
165 DiskStateFlags Flags;
166 + wil::unique_hfile BackingFile;
167 };
168
169 struct VirtioFsShare
test/windows/UnitTests.cpp
+1 -1
@@ -1503,7 +1503,7 @@ class UnitTests
1503 L"-d DummyBrokenDistro",
1504 L"Failed to attach disk 'C:\\DoesNotExit\\ext4.vhdx' to WSL2: The system cannot find the path "
1505 L"specified. ",
1506 - L"Wsl/Service/CreateInstance/MountDisk/HCS/ERROR_PATH_NOT_FOUND");
1506 + L"Wsl/Service/CreateInstance/MountDisk/ERROR_PATH_NOT_FOUND");
1507
1508 // Purposefully set an incorrect value type to validate registry error handling.
1509 wsl::windows::common::registry::WriteString(distroKey.get(), nullptr, L"Version", L"Broken");