diff --git a/src/windows/service/exe/HcsVirtualMachine.cpp b/src/windows/service/exe/HcsVirtualMachine.cpp index 0bfb5062b..5e4f41b83 100644 --- a/src/windows/service/exe/HcsVirtualMachine.cpp +++ b/src/windows/service/exe/HcsVirtualMachine.cpp @@ -580,7 +580,9 @@ try } else { - std::wstring options = ReadOnly ? L"ro" : L""; + // N.B. The 'metadata' option is required so the virtiofs device host persists per-file + // uid/gid in NTFS extended attributes. Without it, all files appear as root-owned. + std::wstring options = ReadOnly ? L"ro;metadata" : L"metadata"; if (!m_swiotlbOption.empty()) { if (!options.empty()) diff --git a/test/windows/WSLCTests.cpp b/test/windows/WSLCTests.cpp index 879e55495..81d4451fa 100644 --- a/test/windows/WSLCTests.cpp +++ b/test/windows/WSLCTests.cpp @@ -3388,6 +3388,43 @@ class WSLCTests ValidateWindowsMounts(true); } + // Validates that virtiofs mounts preserve file ownership for non-root users (regression test for #40719). + WSLC_TEST_METHOD(WindowsMountsVirtioFsFileOwnership) + { + auto settings = GetDefaultSessionSettings(L"virtiofs-ownership-test"); + WI_SetFlag(settings.FeatureFlags, WslcFeatureFlagsVirtioFs); + + auto createNewSession = !WI_IsFlagSet(m_defaultSessionSettings.FeatureFlags, WslcFeatureFlagsVirtioFs); + auto session = createNewSession ? CreateSession(settings) : m_defaultSession; + + auto testFolder = std::filesystem::current_path() / "test-folder-virtiofs-ownership"; + std::filesystem::create_directories(testFolder); + auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { std::filesystem::remove_all(testFolder); }); + + static constexpr auto mountPoint = "/virtiofs-ownership-test"; + + VERIFY_SUCCEEDED(session->MountWindowsFolder(testFolder.c_str(), mountPoint, false)); + auto unmount = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { session->UnmountWindowsFolder(mountPoint); }); + + // Create a file and chown to uid 1000:100, then verify ownership is preserved. + // Without the 'metadata' option on the virtiofs share, chown appears to succeed but + // subsequent stat reports uid=0/gid=0 because ownership is not persisted. + auto result = ExpectCommandResult( + session.get(), + {"/bin/sh", "-c", "touch /virtiofs-ownership-test/owned.txt && chown 1000:100 /virtiofs-ownership-test/owned.txt && stat -c '%u %g' /virtiofs-ownership-test/owned.txt"}, + 0); + + VERIFY_ARE_EQUAL(result.Output[1], std::string("1000 100\n")); + + // Verify that a file created by a non-root user retains the creator's ownership. + result = ExpectCommandResult( + session.get(), + {"/bin/sh", "-c", "rm -f /virtiofs-ownership-test/nonroot.txt && su -s /bin/sh nobody -c 'touch /virtiofs-ownership-test/nonroot.txt' && stat -c '%u' /virtiofs-ownership-test/nonroot.txt"}, + 0); + + VERIFY_ARE_EQUAL(result.Output[1], std::string("65534\n")); + } + // Validates that VirtioFs shares are reused across mount/unmount cycles for the same Windows folder. WSLC_TEST_METHOD(WindowsMountsVirtioFsShareReuse) {