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
}