@samitouri / QOSAMI-WSL / commits / f931e581

Update the WSLC plugin API to allow to mount arbitrary paths (#40693)

* Save state * Reject relative mountpoints * Format

Blue committed Jun 5, 2026 at 12:38 UTC f931e5810dc300cf4c10fcd0b64a1db69efe1cde
6 files changed +22 -54
src/windows/inc/WslPluginApi.h
+2 -6
@@ -26,9 +26,6 @@ extern "C" {
26 #define WSLPLUGINAPI_ENTRYPOINTV1 WSLPluginAPIV1_EntryPoint
27 #define WSL_E_PLUGIN_REQUIRES_UPDATE MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, 0x032A)
28
29 -// Maximum size for mount points returned by WSLCPluginAPI_MountFolder. This includes the null terminator.
30 -#define WSLC_MOUNTPOINT_LENGTH 256
31 -
29 #define WSL_PLUGIN_REQUIRE_VERSION(_Major, _Minor, _Revision, Api) \
30 if (Api->Version.Major < (_Major) || (Api->Version.Major == (_Major) && Api->Version.Minor < (_Minor)) || \
31 (Api->Version.Major == (_Major) && Api->Version.Minor == (_Minor) && Api->Version.Revision < (_Revision))) \
@@ -148,9 +145,8 @@ typedef HRESULT (*WSLPluginAPI_ImageDeleted)(const struct WSLCSessionInformation
145 // WSLC plugin API calls.
146 //
147
151 -// Mount a Windows folder into the WSLC session VM. The mount path is returned via 'Mountpoint'.
152 -// 'Mountpoint' must point to a buffer of at least WSLC_MOUNTPOINT_LENGTH chars, including the null terminator.
153 -typedef HRESULT (*WSLCPluginAPI_MountFolder)(WSLCSessionId Session, LPCWSTR WindowsPath, BOOL ReadOnly, LPCWSTR Name, LPSTR Mountpoint);
148 +// Mount a Windows folder into the WSLC session VM at the given 'Mountpoint' path. If the 'Mountpoint' doesn't exist, it will be created.
149 +typedef HRESULT (*WSLCPluginAPI_MountFolder)(WSLCSessionId Session, LPCWSTR WindowsPath, LPCSTR Mountpoint, BOOL ReadOnly);
150
151 // Unmount a folder previously mounted via WSLCPluginAPI_MountFolder.
152 typedef HRESULT (*WSLCPluginAPI_UnmountFolder)(WSLCSessionId Session, LPCSTR Mountpoint);
src/windows/service/exe/PluginManager.cpp
+6 -24
@@ -120,42 +120,23 @@ wil::com_ptr<IWSLCSession> ResolveWslcSession(WSLCSessionId Session)
120
121 extern "C" {
122
123 -HRESULT WSLCMountFolder(WSLCSessionId Session, LPCWSTR WindowsPath, BOOL ReadOnly, LPCWSTR Name, LPSTR Mountpoint)
123 +HRESULT WSLCMountFolder(WSLCSessionId Session, LPCWSTR WindowsPath, LPCSTR Mountpoint, BOOL ReadOnly)
124 try
125 {
126 - RETURN_HR_IF(E_POINTER, WindowsPath == nullptr || Name == nullptr || Mountpoint == nullptr);
127 - auto nameLength = wcslen(Name);
128 -
129 - RETURN_HR_IF_MSG(
130 - E_INVALIDARG,
131 - nameLength == 0 ||
132 - !std::ranges::all_of(Name, Name + nameLength, [&](auto c) { return c == '-' || c == '_' || iswalnum(c); }),
133 - "Invalid mount name: %ls",
134 - Name);
126 + // TODO: Once plugins are out of proc, add logic to validate that the mountpoint isn't in use by another plugin.
127 + RETURN_HR_IF(E_POINTER, WindowsPath == nullptr || Mountpoint == nullptr);
128
129 auto session = ResolveWslcSession(Session);
137 -
138 - // Mount the folder under /mnt/wsl-plugin/<Name>. Convert Name to UTF-8 for the Linux path.
139 - const auto linuxPath = std::format("/mnt/wsl-plugin/{}", Name);
140 -
141 - THROW_HR_IF_MSG(E_INVALIDARG, linuxPath.length() >= WSLC_MOUNTPOINT_LENGTH, "Mountpoint too long: %hs", linuxPath.c_str());
142 -
143 - auto result = session->MountWindowsFolder(WindowsPath, linuxPath.c_str(), ReadOnly);
130 + auto result = session->MountWindowsFolder(WindowsPath, Mountpoint, ReadOnly);
131
132 WSL_LOG(
133 "WslcPluginMountFolderCall",
134 TraceLoggingValue(Session, "SessionId"),
135 TraceLoggingValue(WindowsPath, "WindowsPath"),
149 - TraceLoggingValue(linuxPath.c_str(), "LinuxPath"),
136 + TraceLoggingValue(Mountpoint, "Mountpoint"),
137 TraceLoggingValue(ReadOnly, "ReadOnly"),
151 - TraceLoggingValue(Name, "Name"),
138 TraceLoggingValue(result, "Result"));
139
154 - if (SUCCEEDED(result))
155 - {
156 - THROW_HR_IF(E_UNEXPECTED, strcpy_s(Mountpoint, WSLC_MOUNTPOINT_LENGTH, linuxPath.c_str()) != 0);
157 - }
158 -
140 return result;
141 }
142 CATCH_RETURN();
@@ -163,6 +144,7 @@ CATCH_RETURN();
144 HRESULT WSLCUnmountFolder(WSLCSessionId Session, LPCSTR Mountpoint)
145 try
146 {
147 + // TODO: Once plugins are out of proc, add logic to validate that the mountpoint is actually owned by the plugin.
148 RETURN_HR_IF(E_POINTER, Mountpoint == nullptr);
149
150 auto session = ResolveWslcSession(Session);
src/windows/wslcsession/WSLCVirtualMachine.cpp
+2
@@ -1008,6 +1008,8 @@ try
1008 THROW_HR_IF_MSG(
1009 HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND), !std::filesystem::is_directory(path), "Path is not a directory: '%ls'", WindowsPath);
1010
1011 + THROW_HR_IF_MSG(E_INVALIDARG, LinuxPath[0] != '/', "Mountpoint is not absolute: '%hs'", LinuxPath);
1012 +
1013 const bool readOnly = WI_IsFlagSet(Flags, WSLCMountFlagsReadOnly);
1014 auto normalizedPath = std::filesystem::weakly_canonical(path).wstring();
1015 GUID shareGuid{};
test/windows/PluginTests.cpp
+1 -3
@@ -664,8 +664,7 @@ class PluginTests
664 WSLC RO folder mounted at: /mnt/wsl-plugin/plugin-ro-test
665 Command: 'echo fail > /mnt/wsl-plugin/plugin-ro-test/should-not-exist.txt', status=1, stdout: , stderr: *
666 WSLCMountFolder(nonexistent): {}
667 - WSLCMountFolder(../escape): {}
668 - WSLCMountFolder(): {}
667 + WSLCMountFolder(relative): {}
668 Test completed
669 WSLC Container started, session=*, id=*, name=wslc-plugin-container, image=debian:latest, state=*
670 WSLC Container stopping, session=*, id=*
@@ -675,7 +674,6 @@ class PluginTests
674 E_INVALIDARG,
675 HRESULT_FROM_WIN32(ERROR_INVALID_STATE),
676 HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND),
678 - E_INVALIDARG,
677 E_INVALIDARG);
678
679 ValidateLogFile(ExpectedOutput.c_str());
test/windows/WSLCTests.cpp
+2
@@ -3233,6 +3233,8 @@ class WSLCTests
3233 {
3234 VERIFY_ARE_EQUAL(session->MountWindowsFolder(L"relative-path", "/win-path", true), E_INVALIDARG);
3235 VERIFY_ARE_EQUAL(session->MountWindowsFolder(L"C:\\does-not-exist", "/win-path", true), HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND));
3236 + VERIFY_ARE_EQUAL(session->MountWindowsFolder(testFolder.c_str(), "relative-mountpoint", true), E_INVALIDARG);
3237 + VERIFY_ARE_EQUAL(session->MountWindowsFolder(testFolder.c_str(), "", true), E_INVALIDARG);
3238 VERIFY_ARE_EQUAL(session->UnmountWindowsFolder("/not-mounted"), HRESULT_FROM_WIN32(ERROR_NOT_FOUND));
3239 VERIFY_ARE_EQUAL(session->UnmountWindowsFolder("/proc"), HRESULT_FROM_WIN32(ERROR_NOT_FOUND));
3240
test/windows/testplugin/Plugin.cpp
+9 -21
@@ -441,6 +441,8 @@ try
441
442 const auto testFolder = L"C:\\";
443 constexpr auto testFileName = L"plugin-test.txt";
444 + constexpr auto rwMountpoint = "/mnt/wsl-plugin/plugin-rw-test";
445 + constexpr auto roMountpoint = "/mnt/wsl-plugin/plugin-ro-test";
446
447 // Validate rw mounts.
448 {
@@ -453,8 +455,7 @@ try
455 }
456
457 // Mount read-write and verify the file can be read from Linux.
456 - char rwMountpoint[WSLC_MOUNTPOINT_LENGTH] = {};
457 - THROW_IF_FAILED(g_api->WSLCMountFolder(Session->SessionId, testFolder, false, L"plugin-rw-test", rwMountpoint));
458 + THROW_IF_FAILED(g_api->WSLCMountFolder(Session->SessionId, testFolder, rwMountpoint, false));
459
460 g_logfile << "WSLC RW folder mounted at: " << rwMountpoint << std::endl;
461
@@ -466,8 +467,7 @@ try
467
468 // Validate ro mounts.
469 {
469 - char roMountpoint[WSLC_MOUNTPOINT_LENGTH] = {};
470 - THROW_IF_FAILED(g_api->WSLCMountFolder(Session->SessionId, L"C:\\", TRUE, L"plugin-ro-test", roMountpoint));
470 + THROW_IF_FAILED(g_api->WSLCMountFolder(Session->SessionId, L"C:\\", roMountpoint, TRUE));
471
472 g_logfile << "WSLC RO folder mounted at: " << roMountpoint << std::endl;
473
@@ -479,24 +479,12 @@ try
479 }
480
481 // Validate that trying to mount a folder that doesn't exist fails with the expected error code.
482 - {
483 - char mountpoint[WSLC_MOUNTPOINT_LENGTH] = {};
484 - g_logfile << "WSLCMountFolder(nonexistent): "
485 - << g_api->WSLCMountFolder(Session->SessionId, L"C:\\nonexistent", TRUE, L"plugin-ro-test", mountpoint) << std::endl;
486 - }
487 -
488 - // Validate that trying to escape the /mnt folder fails.
489 - {
490 - char mountpoint[WSLC_MOUNTPOINT_LENGTH] = {};
491 - g_logfile << "WSLCMountFolder(../escape): " << g_api->WSLCMountFolder(Session->SessionId, L"C:\\", TRUE, L"../escape", mountpoint)
492 - << std::endl;
493 - }
482 + g_logfile << "WSLCMountFolder(nonexistent): " << g_api->WSLCMountFolder(Session->SessionId, L"C:\\nonexistent", roMountpoint, TRUE)
483 + << std::endl;
484
495 - // Validate that empty names are rejected.
496 - {
497 - char mountpoint[WSLC_MOUNTPOINT_LENGTH] = {};
498 - g_logfile << "WSLCMountFolder(): " << g_api->WSLCMountFolder(Session->SessionId, L"C:\\", TRUE, L"", mountpoint) << std::endl;
499 - }
485 + // Validate that non-absolute mountpoints are rejected.
486 + g_logfile << "WSLCMountFolder(relative): " << g_api->WSLCMountFolder(Session->SessionId, L"C:\\", "relative-mountpoint", TRUE)
487 + << std::endl;
488
489 g_logfile << "Test completed" << std::endl;
490 }