Fix WSLC Plan9 mount and image-build failures (#41535)
* Fix WSLC Plan9 mounts using a per-user server * remove debug code
Feng Wang committed
Sep 8, 2026 at 22:52 UTC
c85de0d4226730f5277df51e809af51945ffd2dd
4 files changed
+65
-9
src/windows/service/exe/HcsVirtualMachine.cpp
+18
-9
@@ -373,6 +373,11 @@ HcsVirtualMachine::~HcsVirtualMachine()
373
// GuestDeviceManager, so it must be released first for the device manager reset to be effective.
374
m_networkEngine.reset();
375
m_guestDeviceManager.reset();
376
+ if (m_plan9Server)
377
+ {
378
+ LOG_IF_FAILED(m_plan9Server->Teardown());
379
+ m_plan9Server.reset();
380
+ }
381
m_computeSystem.reset();
382
383
// Revoke VM access for attached disks
@@ -610,16 +615,19 @@ try
615
616
if (!FeatureEnabled(WslcFeatureFlagsVirtioFs))
617
{
618
+ auto runAsUser = wil::impersonate_token(m_userToken.get());
619
+ if (!m_plan9Server)
620
+ {
621
+ auto server =
622
+ wsl::windows::common::wslutil::CreateComServerAsUser<p9fs::Plan9FileSystem, IPlan9FileSystem>(m_userToken.get());
623
+ THROW_IF_FAILED(server->Init(&m_vmId, LX_INIT_UTILITY_VM_PLAN9_PORT));
624
+ THROW_IF_FAILED(server->Resume());
625
+ m_plan9Server = std::move(server);
626
+ }
627
+
628
auto flags = hcs::Plan9ShareFlags::AllowOptions;
629
WI_SetFlagIf(flags, hcs::Plan9ShareFlags::ReadOnly, ReadOnly);
615
- hcs::AddPlan9Share(
616
- m_computeSystem.get(),
617
- shareName.c_str(),
618
- shareName.c_str(),
619
- WindowsPath,
620
- LX_INIT_UTILITY_VM_PLAN9_PORT,
621
- flags,
622
- m_userToken.get());
630
+ THROW_IF_FAILED(m_plan9Server->AddSharePath(shareName.c_str(), WindowsPath, static_cast<UINT32>(flags)));
631
}
632
else
633
{
@@ -653,8 +661,9 @@ try
661
662
if (!it->second.has_value())
663
{
664
+ auto runAsUser = wil::impersonate_token(m_userToken.get());
665
auto shareName = wsl::shared::string::GuidToString<wchar_t>(it->first, wsl::shared::string::None);
657
- hcs::RemovePlan9Share(m_computeSystem.get(), shareName.c_str(), LX_INIT_UTILITY_VM_PLAN9_PORT);
666
+ THROW_IF_FAILED(m_plan9Server->RemoveShare(shareName.c_str()));
667
}
668
else
669
{
src/windows/service/exe/HcsVirtualMachine.h
+1
@@ -100,6 +100,7 @@ private:
100
101
// Shares: key is ShareId, value is nullopt for Plan9 or the aggregate DeviceInstanceId for VirtioFS.
102
std::map<GUID, std::optional<GUID>, wsl::windows::common::helpers::GuidLess> m_shares;
103
+ wil::com_ptr<IPlan9FileSystem> m_plan9Server;
104
std::optional<GUID> m_virtioFsDevice;
105
106
std::filesystem::path m_vmSavedStateFile;
test/windows/PluginTests.cpp
+1
@@ -667,6 +667,7 @@ class PluginTests
667
WSLCProcessGetExitCode(<running>): {}
668
WSLC RW folder mounted at: /mnt/wsl-plugin/plugin-rw-test
669
Command: 'cat /mnt/wsl-plugin/plugin-rw-test/plugin-test.txt', status=0, stdout: Windows-content, stderr:
670
+ Command: 'cat /mnt/wsl-plugin/plugin-rw-test/plugin-denied.txt', status=1, stdout: , stderr: *
671
WSLC RO folder mounted at: /mnt/wsl-plugin/plugin-ro-test
672
Command: 'echo fail > /mnt/wsl-plugin/plugin-ro-test/should-not-exist.txt', status=1, stdout: , stderr: *
673
WSLCMountFolder(nonexistent): {}
test/windows/testplugin/Plugin.cpp
+45
@@ -496,6 +496,48 @@ void RunWslcSuccessChecks(const WSLCSessionInformation* Session)
496
file << "Windows-content";
497
}
498
499
+ const auto deniedFilePath = std::wstring(testFolder) + L"plugin-denied.txt";
500
+ {
501
+ wil::unique_hfile deniedFile{
502
+ CreateFileW(deniedFilePath.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr)};
503
+ THROW_LAST_ERROR_IF(!deniedFile);
504
+ }
505
+
506
+ auto deniedFileCleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { std::filesystem::remove(deniedFilePath); });
507
+
508
+ PACL originalAcl = nullptr;
509
+ wil::unique_hlocal originalDescriptor;
510
+ THROW_IF_WIN32_ERROR(GetNamedSecurityInfoW(
511
+ deniedFilePath.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, &originalAcl, nullptr, &originalDescriptor));
512
+
513
+ auto restoreAcl = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
514
+ THROW_IF_WIN32_ERROR(SetNamedSecurityInfoW(
515
+ const_cast<LPWSTR>(deniedFilePath.c_str()), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, originalAcl, nullptr));
516
+ });
517
+
518
+ EXPLICIT_ACCESSW deniedAccess{};
519
+ deniedAccess.grfAccessPermissions = FILE_READ_DATA;
520
+ deniedAccess.grfAccessMode = DENY_ACCESS;
521
+ deniedAccess.grfInheritance = NO_INHERITANCE;
522
+ deniedAccess.Trustee.TrusteeForm = TRUSTEE_IS_SID;
523
+ deniedAccess.Trustee.ptstrName = static_cast<LPWSTR>(Session->UserSid);
524
+
525
+ wsl::windows::common::security::unique_acl deniedAcl;
526
+ THROW_IF_WIN32_ERROR(SetEntriesInAclW(1, &deniedAccess, originalAcl, &deniedAcl));
527
+ THROW_IF_WIN32_ERROR(SetNamedSecurityInfoW(
528
+ const_cast<LPWSTR>(deniedFilePath.c_str()), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, deniedAcl.get(), nullptr));
529
+
530
+ {
531
+ wil::unique_handle impersonationToken;
532
+ THROW_LAST_ERROR_IF(!DuplicateTokenEx(
533
+ Session->UserToken, TOKEN_IMPERSONATE | TOKEN_QUERY, nullptr, SecurityImpersonation, TokenImpersonation, &impersonationToken));
534
+ auto revert = wil::impersonate_token(impersonationToken.get());
535
+ wil::unique_hfile deniedFile{
536
+ CreateFileW(deniedFilePath.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)};
537
+ const auto openError = GetLastError();
538
+ THROW_HR_IF(E_UNEXPECTED, deniedFile || openError != ERROR_ACCESS_DENIED);
539
+ }
540
+
541
// Mount read-write and verify the file can be read from Linux.
542
THROW_IF_FAILED(g_api->WSLCMountFolder(Session->SessionId, testFolder, rwMountpoint, false));
543
@@ -504,6 +546,9 @@ void RunWslcSuccessChecks(const WSLCSessionInformation* Session)
546
auto readCmd = std::format("cat {}/{}", rwMountpoint, testFileName);
547
runCommand(readCmd.c_str());
548
549
+ auto deniedReadCmd = std::format("cat {}/plugin-denied.txt", rwMountpoint);
550
+ runCommand(deniedReadCmd.c_str());
551
+
552
THROW_IF_FAILED(g_api->WSLCUnmountFolder(Session->SessionId, rwMountpoint));
553
}
554