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