master
cpp 3,033 lines 120 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 WslCoreVm.cpp
8
9 Abstract:
10
11 This file contains utility VM function definitions.
12
13 --*/
14
15 #include "precomp.h"
16 #include "WslCoreVm.h"
17 #include "WslCoreNetworkingSupport.h"
18 #include <lxfsshares.h>
19 #include "disk.hpp"
20 #include "WslCoreInstance.h"
21 #include "NatNetworking.h"
22 #include "BridgedNetworking.h"
23 #include "MirroredNetworking.h"
24 #include "WslCoreFirewallSupport.h"
25 #include "DnsResolver.h"
26 #include "ConsommeNetworking.h"
27
28 #include <TraceLoggingProvider.h>
29
30 using msl::utilities::SafeInt;
31 using wsl::windows::common::helpers::WindowsBuildNumbers;
32 using namespace wsl::windows::common::registry;
33 using namespace wsl::windows::common::string;
34 using namespace std::string_literals;
35
36 // The default high-gap MMIO space is 16GB
37 #define DEFAULT_HIGH_MMIO_GAP_IN_MB (16 * _1KB)
38
39 // Start of unaddressable memory if guest only supports the minimum 36-bit addressing.
40 #define MAX_36_BIT_PAGE_IN_MB (0x1000000000 / _1MB)
41
42 #define WSLG_SHARED_MEMORY_SIZE_MB 8192
43 #define PAGE_SIZE 0x1000
44
45 static constexpr size_t c_bootEntropy = 0x1000;
46 static constexpr auto c_localDevicesKey = L"SOFTWARE\\Microsoft\\Terminal Server Client\\LocalDevices";
47
48 #define LXSS_ENABLE_GUI_APPS() (m_vmConfig.EnableGuiApps && (m_systemDistroDeviceId != ULONG_MAX))
49
50 using namespace wsl::windows::common;
51 using wsl::core::NetworkingMode;
52 using wsl::core::networking::NetworkEndpoint;
53 using wsl::core::networking::NetworkSettings;
54 using wsl::shared::Localization;
55 using wsl::windows::common::Context;
56 using wsl::windows::common::ExecutionContext;
57
58 namespace {
59 INT64
60 RequiredExtraMmioSpaceForPmemFileInMb(_In_ PCWSTR FilePath)
61 {
62 // Open the file and retrieve the file's size.
63 const wil::unique_hfile fileHandle{CreateFile(FilePath, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr)};
64 THROW_LAST_ERROR_IF(!fileHandle);
65
66 LARGE_INTEGER fileSizeBytes;
67 THROW_IF_WIN32_BOOL_FALSE(GetFileSizeEx(fileHandle.get(), &fileSizeBytes));
68
69 // The file is mapped to the VM using PCI BARs, which can only be a power of two. Therefore,
70 // round the file size up to the nearest power of two.
71 fileSizeBytes.QuadPart = wsl::windows::common::helpers::RoundUpToNearestPowerOfTwo(fileSizeBytes.QuadPart);
72
73 // Convert from bytes to megabytes. Ensure that we don't truncate a 512kb file to 0mb.
74 return std::max(fileSizeBytes.QuadPart / static_cast<INT64>(_1MB), 1i64);
75 }
76
77 wil::unique_hfile OpenVhdBackingFile(_In_ PCWSTR Path)
78 {
79 wil::unique_hfile file{CreateFileW(
80 Path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)};
81 THROW_LAST_ERROR_IF(!file);
82
83 return file;
84 }
85
86 bool IsBackingVolumeMounted(_In_ HANDLE File)
87 {
88 DWORD bytesReturned{};
89 return DeviceIoControl(File, FSCTL_IS_VOLUME_MOUNTED, nullptr, 0, nullptr, 0, &bytesReturned, nullptr);
90 }
91 } // namespace
92
93 WslCoreVm::WslCoreVm(_In_ wsl::core::Config&& VmConfig, _In_ InitializeDrvFsCallback InitializeDrvFs) :
94 m_vmConfig(std::move(VmConfig)), m_initializeDrvFs(std::move(InitializeDrvFs)), m_traceClient(m_vmConfig.EnableTelemetry)
95 {
96 // Create a job object that will terminate child processes (wslhost.exe, wslrelay.exe)
97 // when the VM is destroyed.
98 m_processJobObject = wsl::windows::common::helpers::CreateKillOnCloseJob();
99 }
100
101 std::unique_ptr<WslCoreVm> WslCoreVm::Create(
102 _In_ const wil::shared_handle& UserToken, _In_ wsl::core::Config&& VmConfig, _In_ const GUID& VmId, _In_ InitializeDrvFsCallback InitializeDrvFs)
103 {
104 THROW_HR_IF(E_INVALIDARG, !InitializeDrvFs);
105
106 auto newInstance = std::unique_ptr<WslCoreVm>{new WslCoreVm{std::move(VmConfig), std::move(InitializeDrvFs)}};
107 try
108 {
109 const auto startTimeMs = GetTickCount64();
110 auto privateKernel = !newInstance->m_vmConfig.KernelPath.empty();
111 // Log telemetry on how long it took to create the VM
112 WSL_LOG_TELEMETRY(
113 "CreateVmBegin", PDT_ProductAndServicePerformance, TraceLoggingValue(VmId, "vmId"), CONFIG_TELEMETRY(newInstance->m_vmConfig));
114
115 newInstance->Initialize(VmId, UserToken);
116
117 const auto timeToCreateVmMs = GetTickCount64() - startTimeMs;
118 WSL_LOG_TELEMETRY(
119 "CreateVmEnd",
120 PDT_ProductAndServicePerformance,
121 TraceLoggingValue(privateKernel, "privateKernel"),
122 TraceLoggingValue(newInstance->m_kernelVersionString.c_str(), "kernelVersion"),
123 TraceLoggingValue(newInstance->m_runtimeId, "vmId"),
124 TraceLoggingValue(timeToCreateVmMs, "timeToCreateVmMs"),
125 CONFIG_TELEMETRY(newInstance->m_vmConfig));
126 }
127 catch (...)
128 {
129 const auto hr = wil::ResultFromCaughtException();
130
131 // Log telemetry when the WSL VM fails to start including the error
132 WSL_LOG_TELEMETRY(
133 "FailedToStartVm",
134 PDT_ProductAndServicePerformance,
135 TraceLoggingValue(VmId, "vmId"),
136 TraceLoggingValue(hr, "error"),
137 CONFIG_TELEMETRY(newInstance->m_vmConfig));
138
139 if (hr == HRESULT_FROM_WIN32(WSAENOTCONN) || hr == HRESULT_FROM_WIN32(WSAECONNRESET) || hr == HRESULT_FROM_WIN32(WSAETIMEDOUT))
140 {
141 // A kernel panic can cause an hvsocket error. If we hit this, wait one second for an HCS notification to give a better error for the user.
142 if (newInstance->m_vmCrashEvent.wait(1000))
143 {
144 if (newInstance->m_vmCrashLogFile.has_value())
145 {
146 THROW_HR_WITH_USER_ERROR(
147 WSL_E_VM_CRASHED,
148 wsl::shared::Localization::MessageWSL2Crashed() + L"\r\n" +
149 Localization::MessageWSL2CrashedStackTrace(newInstance->m_vmCrashLogFile.value()));
150 }
151 else
152 {
153 THROW_HR_WITH_USER_ERROR(WSL_E_VM_CRASHED, wsl::shared::Localization::MessageWSL2Crashed());
154 }
155 }
156 }
157
158 throw;
159 }
160
161 return newInstance;
162 }
163
164 void WslCoreVm::Initialize(const GUID& VmId, const wil::shared_handle& UserToken)
165 {
166 auto signalEarlyTermination = wil::scope_exit([&] { m_terminatingEvent.SetEvent(); });
167
168 // create a restricted version of the token.
169 m_userToken = UserToken;
170 m_restrictedToken = wsl::windows::common::security::CreateRestrictedToken(m_userToken.get());
171
172 // Make a copy of the user sid.
173 auto tokenUser = wil::get_token_information<TOKEN_USER>(m_userToken.get());
174 THROW_IF_WIN32_BOOL_FALSE(::CopySid(sizeof(m_userSid), &m_userSid.Sid, tokenUser->User.Sid));
175
176 // Generate a machine ID string based on the VM ID. This is used for some HCS APIs.
177 m_machineId = wsl::shared::string::GuidToString<wchar_t>(VmId, wsl::shared::string::GuidToStringFlags::Uppercase);
178
179 // Set the install path of the package.
180 m_installPath = wsl::windows::common::wslutil::GetBasePath();
181
182 // Initialize the path to the tools folder which also serves as the default rootfs path.
183 m_rootFsPath = m_installPath / LXSS_TOOLS_DIRECTORY;
184
185 // Store the path of the user profile.
186 m_userProfile = wsl::windows::common::helpers::GetUserProfilePath(m_userToken.get());
187
188 // Query the Windows version.
189 m_windowsVersion = wsl::windows::common::helpers::GetWindowsVersion();
190
191 // Create a temporary folder for the VM.
192 try
193 {
194 const auto runAsUser = wil::impersonate_token(m_userToken.get());
195 m_tempPath = wsl::windows::common::filesystem::GetTempFolderPath(m_userToken.get()) / m_machineId;
196
197 wil::CreateDirectoryDeep(m_tempPath.c_str());
198 m_tempDirectoryCreated = true;
199 }
200 CATCH_LOG();
201
202 // If a private kernel was not specified, use the default.
203 m_defaultKernel = m_vmConfig.KernelPath.empty();
204 if (m_defaultKernel)
205 {
206 #ifdef WSL_KERNEL_PATH
207
208 m_vmConfig.KernelPath = TEXT(WSL_KERNEL_PATH);
209
210 #else
211
212 m_vmConfig.KernelPath = m_rootFsPath / LXSS_VM_MODE_KERNEL_NAME;
213
214 #endif
215 }
216 else
217 {
218 if (!wsl::windows::common::filesystem::FileExists(m_vmConfig.KernelPath.c_str()))
219 {
220 THROW_HR_WITH_USER_ERROR(
221 WSL_E_CUSTOM_KERNEL_NOT_FOUND,
222 Localization::MessageCustomKernelNotFound(
223 wsl::windows::common::helpers::GetWslConfigPath(m_userToken.get()), m_vmConfig.KernelPath.c_str()));
224 }
225
226 // Direct boot is not supported on ARM64. Modify the rootfs directory to be a temporary directory that contains
227 // copies of the initrd file and private kernel.
228 if constexpr (wsl::shared::Arm64)
229 {
230 auto impersonate = wil::impersonate_token(m_userToken.get());
231
232 m_rootFsPath = m_tempPath / LXSS_ROOTFS_DIRECTORY;
233 wil::CreateDirectoryDeep(m_rootFsPath.c_str());
234 auto initRdPath = m_installPath / LXSS_TOOLS_DIRECTORY / LXSS_VM_MODE_INITRD_NAME;
235
236 auto targetPath = m_rootFsPath / LXSS_VM_MODE_INITRD_NAME;
237 THROW_IF_WIN32_BOOL_FALSE(CopyFileW(initRdPath.c_str(), targetPath.c_str(), TRUE));
238
239 targetPath = m_rootFsPath / LXSS_VM_MODE_KERNEL_NAME;
240 THROW_IF_WIN32_BOOL_FALSE(CopyFileW(m_vmConfig.KernelPath.c_str(), targetPath.c_str(), TRUE));
241 }
242 }
243
244 // If the user did not specify custom modules, use the default modules only if using the default kernel.
245 m_privateKernelModules = !m_vmConfig.KernelModulesPath.empty();
246 if (m_vmConfig.KernelModulesPath.empty())
247 {
248 if (m_defaultKernel)
249 {
250 #ifdef WSL_KERNEL_MODULES_PATH
251
252 m_vmConfig.KernelModulesPath = std::wstring(TEXT(WSL_KERNEL_MODULES_PATH));
253 m_privateKernelModules = true;
254
255 #else
256
257 m_vmConfig.KernelModulesPath = m_rootFsPath / L"artifacts.vhd";
258
259 #endif
260 }
261 }
262 else
263 {
264 if (!wsl::windows::common::filesystem::FileExists(m_vmConfig.KernelModulesPath.c_str()))
265 {
266 THROW_HR_WITH_USER_ERROR(
267 WSL_E_CUSTOM_KERNEL_NOT_FOUND,
268 Localization::MessageCustomKernelModulesNotFound(
269 wsl::windows::common::helpers::GetWslConfigPath(m_userToken.get()), m_vmConfig.KernelModulesPath.c_str()));
270 }
271
272 if (m_defaultKernel)
273 {
274 THROW_HR_WITH_USER_ERROR(WSL_E_CUSTOM_KERNEL_NOT_FOUND, Localization::MessageMismatchedKernelModulesError());
275 }
276 }
277
278 // If debug console was requested, create a randomly-named pipe and spawn a wslhost process to read from the pipe.
279 //
280 // N.B. wslhost.exe is launched at medium integrity level and its lifetime
281 // is tied to the lifetime of the utility VM.
282 if (m_vmConfig.EnableDebugConsole || !m_vmConfig.DebugConsoleLogFile.empty())
283 {
284 try
285 {
286 m_vmConfig.EnableDebugConsole = true;
287 m_comPipe0 = wsl::windows::common::helpers::GetUniquePipeName();
288 }
289 CATCH_LOG()
290 }
291
292 // If the system supports virtio console serial ports, use dmesg capture for telemetry and/or debug output.
293 // Legacy serial is much slower, so this is not enabled without virtio console support.
294 auto enableVirtioSerial = m_vmConfig.EnableVirtio && helpers::IsVirtioSerialConsoleSupported();
295 m_vmConfig.EnableDebugShell &= enableVirtioSerial;
296 if (enableVirtioSerial)
297 {
298 try
299 {
300 bool enableTelemetry = TraceLoggingProviderEnabled(g_hTraceLoggingProvider, WINEVENT_LEVEL_INFO, 0);
301 m_dmesgCollector = DmesgCollector::Create(
302 VmId, m_vmExitEvent.get(), enableTelemetry, m_vmConfig.EnableDebugConsole, m_comPipe0, m_vmConfig.EnableEarlyBootLogging, {});
303
304 WSL_LOG("DMESG collector created");
305
306 if (m_vmConfig.EnableDebugShell)
307 {
308 m_debugShellPipe = wsl::windows::common::wslutil::GetDebugShellPipeName(&m_userSid.Sid);
309 }
310
311 // Initialize the guest telemetry logger.
312 m_gnsTelemetryLogger = GuestTelemetryLogger::Create(VmId, m_vmExitEvent);
313 }
314 CATCH_LOG()
315 }
316
317 if (m_vmConfig.EnableDebugConsole)
318 {
319 try
320 {
321 // If specified, create a file to log the debug console output.
322 wil::unique_hfile logFile;
323 if (!m_vmConfig.DebugConsoleLogFile.empty())
324 {
325 auto impersonate = wil::impersonate_token(m_userToken.get());
326 logFile.reset(CreateFileW(
327 m_vmConfig.DebugConsoleLogFile.c_str(), FILE_APPEND_DATA, (FILE_SHARE_READ | FILE_SHARE_WRITE), nullptr, OPEN_ALWAYS, 0, nullptr));
328
329 LOG_LAST_ERROR_IF(!logFile);
330 }
331
332 wsl::windows::common::helpers::LaunchDebugConsole(
333 m_comPipe0.c_str(),
334 !!m_dmesgCollector,
335 m_restrictedToken.get(),
336 logFile ? logFile.get() : nullptr,
337 !m_vmConfig.EnableTelemetry,
338 m_processJobObject.get());
339 }
340 CATCH_LOG()
341 }
342
343 // Create the utility VM and store the runtime ID.
344 std::wstring json = GenerateConfigJson();
345 {
346 SlowOperationWatcher slowOperation{"HcsCreateSystem"};
347 m_system = wsl::windows::common::hcs::CreateComputeSystem(m_machineId.c_str(), json.c_str());
348 }
349 m_runtimeId = wsl::windows::common::hcs::GetRuntimeId(m_system.get());
350 WI_ASSERT(IsEqualGUID(VmId, m_runtimeId));
351
352 // Initialize the guest device manager.
353 m_guestDeviceManager = std::make_shared<GuestDeviceManager>(m_machineId, m_runtimeId, m_vmConfig.EnableTelemetry);
354
355 // Create a socket listening for connections from mini_init.
356 m_listenSocket = wsl::windows::common::hvsocket::Listen(m_runtimeId, LX_INIT_UTILITY_VM_INIT_PORT);
357
358 if (m_vmConfig.MaxCrashDumpCount >= 0)
359 {
360 auto crashDumpSocket = wsl::windows::common::hvsocket::Listen(m_runtimeId, LX_INIT_UTILITY_VM_CRASH_DUMP_PORT);
361 THROW_LAST_ERROR_IF(!crashDumpSocket);
362 m_crashDumpCollectionThread =
363 std::thread{[this, socket = std::move(crashDumpSocket)]() mutable { CollectCrashDumps(std::move(socket)); }};
364 }
365
366 // Register a callback to detect if the utility VM exits unexpectedly.
367 wsl::windows::common::hcs::RegisterCallback(m_system.get(), s_OnExit, this);
368 signalEarlyTermination.release();
369
370 // Start the utility VM.
371 try
372 {
373 SlowOperationWatcher slowOperation{"HcsStartSystem"};
374 wsl::windows::common::hcs::StartComputeSystem(m_system.get(), json.c_str());
375 }
376 catch (...)
377 {
378 // Reset m_system so we don't try to wait for termination in the destructor, since the VM isn't even running.
379 m_system.reset();
380 throw;
381 }
382
383 // Add GPUs to the utility VM.
384 if (m_vmConfig.EnableGpuSupport)
385 {
386 ExecutionContext context(Context::ConfigureGpu);
387
388 hcs::ModifySettingRequest<hcs::GpuConfiguration> gpuRequest{};
389 gpuRequest.ResourcePath = L"VirtualMachine/ComputeTopology/Gpu";
390 gpuRequest.RequestType = hcs::ModifyRequestType::Update;
391 gpuRequest.Settings.AssignmentMode = hcs::GpuAssignmentMode::Mirror;
392 gpuRequest.Settings.AllowVendorExtension = true;
393 if (wsl::windows::common::hcs::IsDisableVgpuSettingsSupported())
394 {
395 gpuRequest.Settings.DisableGdiAcceleration = true;
396 gpuRequest.Settings.DisablePresentation = true;
397 }
398
399 wsl::windows::common::hcs::ModifyComputeSystem(m_system.get(), wsl::shared::ToJsonW(gpuRequest).c_str());
400
401 // Also add 9p shares for the library directories.
402 // N.B. These are not hosted by the out-of-proc drvfs 9p server because the GPU shares
403 // should work even if drvfs is disabled.
404 auto addShare = [&](PCWSTR name, PCWSTR path) {
405 constexpr auto flags = (hcs::Plan9ShareFlags::ReadOnly | hcs::Plan9ShareFlags::AllowOptions);
406 wsl::windows::common::hcs::AddPlan9Share(m_system.get(), name, name, path, LX_INIT_UTILITY_VM_PLAN9_PORT, flags);
407 };
408
409 std::wstring path;
410 THROW_IF_FAILED(wil::ExpandEnvironmentStringsW(L"%SystemRoot%\\System32\\DriverStore\\FileRepository", path));
411 addShare(TEXT(LXSS_GPU_DRIVERS_SHARE), path.c_str());
412
413 // N.B. There are inbox and packaged versions of the Direct 3D libraries. The packaged
414 // versions take presidence by using overlayfs in the guest.
415 THROW_IF_FAILED(wil::ExpandEnvironmentStringsW(L"%SystemRoot%\\System32\\lxss\\lib", path));
416
417 if (wsl::windows::common::filesystem::FileExists(path.c_str()))
418 {
419 try
420 {
421 addShare(TEXT(LXSS_GPU_INBOX_LIB_SHARE), path.c_str());
422 m_enableInboxGpuLibs = true;
423 }
424 CATCH_LOG()
425 }
426
427 #ifdef WSL_GPU_LIB_PATH
428
429 path = TEXT(WSL_GPU_LIB_PATH);
430
431 #else
432
433 path = m_installPath / L"lib";
434
435 #endif
436
437 addShare(TEXT(LXSS_GPU_PACKAGED_LIB_SHARE), path.c_str());
438 }
439
440 // Accept a connection from mini_init with a receive timeout so the service does not get stuck waiting for a response from the VM.
441 {
442 SlowOperationWatcher slowOperation{"WaitForMiniInitConnect"};
443 m_miniInitChannel =
444 wsl::shared::SocketChannel{AcceptConnection(m_vmConfig.KernelBootTimeout), "mini_init", {m_terminatingEvent.get()}};
445 }
446
447 // Accept the connection from the Linux guest for notifications.
448 m_notifyChannel = AcceptConnection(m_vmConfig.KernelBootTimeout);
449
450 // Receive and parse the guest kernel version
451 {
452 SlowOperationWatcher slowOperation{"ReadGuestCapabilities"};
453 ReadGuestCapabilities();
454 }
455
456 // Cache the effective swiotlb configuration. The kernel picks a valid GPA, allocates the pool,
457 // and publishes the actual (base, size) via sysfs. Only warn when swiotlb was actually
458 // requested via the kernel command line; otherwise the kernel correctly doesn't allocate.
459 if (m_hvPciSwiotlbBase != 0 && m_hvPciSwiotlbSize != 0)
460 {
461 m_guestDeviceManager->SetSwiotlb(m_hvPciSwiotlbBase, m_hvPciSwiotlbSize);
462 }
463 else if (m_vmConfig.SwiotlbSizeBytes != 0)
464 {
465 EMIT_USER_WARNING(wsl::shared::Localization::MessageSwiotlbKernelUnsupported());
466 }
467
468 // Asynchronously add drvfs devices if supported.
469 if (m_vmConfig.EnableHostFileSystemAccess)
470 {
471 std::promise<bool> initialResult;
472 m_drvfsInitialResult = initialResult.get_future();
473 auto guestDeviceLock = m_guestDeviceLock.lock_exclusive();
474 std::thread([this, guestDeviceLock = std::move(guestDeviceLock), initialResult = std::move(initialResult)]() mutable {
475 try
476 {
477 wsl::windows::common::wslutil::SetThreadDescription(L"InitializeDrvfs");
478 initialResult.set_value(InitializeDrvFsLockHeld(m_userToken.get()));
479 }
480 catch (...)
481 {
482 try
483 {
484 initialResult.set_exception(std::current_exception());
485 }
486 CATCH_LOG()
487 }
488 }).detach();
489 }
490
491 // Mount the system distro.
492 // N.B. If using SCSI, the system distro is added during VM creation.
493 switch (m_systemDistroDeviceType)
494 {
495 case LxMiniInitMountDeviceTypePmem:
496 m_systemDistroDeviceId = MountFileAsPersistentMemory(m_vmConfig.SystemDistroPath.c_str(), true);
497 break;
498 }
499
500 // Attempt to create and mount the swap vhd.
501 //
502 // N.B. This can fail if the target directory is compressed, encrypted, or if
503 // the user does not have write access.
504 ULONG swapLun = ULONG_MAX;
505 if ((m_systemDistroDeviceId != ULONG_MAX) && (m_vmConfig.SwapSizeBytes > 0))
506 {
507 try
508 {
509 {
510 // If no user-specified swap vhd file path was specified, use a
511 // path in the temp directory.
512 auto runAsUser = wil::impersonate_token(m_userToken.get());
513 if (m_vmConfig.SwapFilePath.empty())
514 {
515 m_vmConfig.SwapFilePath = m_tempPath / L"swap";
516 }
517
518 // Ensure the swap vhd ends with the vhdx file extension.
519 if (!wsl::windows::common::string::IsPathComponentEqual(
520 m_vmConfig.SwapFilePath.extension().native(), wsl::windows::common::wslutil::c_vhdxFileExtension))
521 {
522 m_vmConfig.SwapFilePath += wsl::windows::common::wslutil::c_vhdxFileExtension;
523 }
524
525 // Create the VHD with an additional page for swap overhead.
526 m_vmConfig.SwapSizeBytes += PAGE_SIZE;
527 auto result = wil::ResultFromException([&]() {
528 wsl::core::filesystem::CreateVhd(m_vmConfig.SwapFilePath.c_str(), m_vmConfig.SwapSizeBytes, &m_userSid.Sid, false, false);
529 m_swapFileCreated = true;
530 });
531
532 if (result == HRESULT_FROM_WIN32(ERROR_FILE_EXISTS))
533 {
534 auto handle = wsl::core::filesystem::OpenVhd(
535 m_vmConfig.SwapFilePath.c_str(), VIRTUAL_DISK_ACCESS_CREATE | VIRTUAL_DISK_ACCESS_METAOPS | VIRTUAL_DISK_ACCESS_GET_INFO);
536 wsl::core::filesystem::ResizeExistingVhd(handle.get(), m_vmConfig.SwapSizeBytes, RESIZE_VIRTUAL_DISK_FLAG_ALLOW_UNSAFE_VIRTUAL_SIZE);
537 }
538 else if (FAILED(result))
539 {
540 EMIT_USER_WARNING(wsl::shared::Localization::MessagedFailedToCreateSwapVhd(
541 m_vmConfig.SwapFilePath.c_str(), wsl::windows::common::wslutil::GetSystemErrorString(result).c_str()));
542
543 THROW_HR(result);
544 }
545 }
546
547 swapLun = AttachDiskLockHeld(m_vmConfig.SwapFilePath.c_str(), DiskType::VHD, MountFlags::None, {}, false, m_userToken.get());
548 }
549 CATCH_LOG()
550 }
551
552 // Validate that the requesting network mode is supported.
553 //
554 // N.B. This must be done before sending the initial configuration message because some guest
555 // behavior is determined by the networking mode.
556 ValidateNetworkingMode();
557
558 // Send the early configuration message.
559 wsl::shared::MessageWriter<LX_MINI_INIT_EARLY_CONFIG_MESSAGE> message(LxMiniInitMessageEarlyConfig);
560 message->SwapLun = swapLun;
561 message->SystemDistroDeviceType = m_systemDistroDeviceType;
562 message->SystemDistroDeviceId = m_systemDistroDeviceId;
563 message->MemoryReclaimMode = static_cast<LX_MINI_INIT_MEMORY_RECLAIM_MODE>(m_vmConfig.MemoryReclaim);
564 message->EnableDebugShell = m_vmConfig.EnableDebugShell;
565 message->EnableSafeMode = m_vmConfig.EnableSafeMode;
566 // Consomme forwards DNS via the host proxy, so the dedicated DNS hvsocket is only used by NAT and Mirrored modes.
567 message->EnableDnsTunneling = m_vmConfig.EnableDnsTunneling && m_vmConfig.NetworkingMode != NetworkingMode::Consomme;
568 message->DefaultKernel = m_defaultKernel;
569 message->IsolateDistroCgroup = m_vmConfig.IsolateDistroCgroup;
570 message->KernelModulesDeviceId = m_kernelModulesDeviceId;
571 message.WriteString(message->HostnameOffset, wsl::windows::common::filesystem::GetLinuxHostName());
572 message.WriteString(message->KernelModulesListOffset, m_vmConfig.KernelModulesList);
573 message->DnsTunnelingIpAddress = m_vmConfig.DnsTunnelingIpAddress.value_or(0);
574
575 auto transaction = m_miniInitChannel.StartTransaction();
576 transaction.Send<LX_MINI_INIT_EARLY_CONFIG_MESSAGE>(message.Span());
577
578 {
579 ExecutionContext context(Context::ConfigureNetworking);
580
581 // Accept the connection from the guest network service and create the channel.
582 wsl::core::GnsChannel gnsChannel(AcceptConnection(m_vmConfig.KernelBootTimeout));
583
584 // Create hvsocket connection for DNS tunneling if enabled.
585 wil::unique_socket dnsTunnelingSocket;
586 if (message->EnableDnsTunneling)
587 {
588 dnsTunnelingSocket = AcceptConnection(m_vmConfig.KernelBootTimeout);
589 }
590
591 // Record the start time of the networking engine initialization so the duration can be logged.
592 const auto startTime = std::chrono::steady_clock::now();
593
594 // For NAT networking, ensure the network can be created. If creating the network fails, fall back to
595 // Consomme networking mode.
596 wsl::windows::common::hcs::unique_hcn_network natNetwork;
597 if (m_vmConfig.NetworkingMode == NetworkingMode::Nat)
598 {
599 {
600 SlowOperationWatcher slowOperation{"CreateNatNetwork"};
601 natNetwork = wsl::core::NatNetworking::CreateNetwork(m_vmConfig);
602 }
603 if (!natNetwork)
604 {
605 EMIT_USER_WARNING(wsl::shared::Localization::MessageNetworkInitializationFailedFallback2(
606 ToString(m_vmConfig.NetworkingMode), ToString(NetworkingMode::Consomme)));
607
608 m_vmConfig.NetworkingMode = NetworkingMode::Consomme;
609 }
610 }
611
612 // Create and initialize the networking engine.
613 const auto result = wil::ResultFromException(WI_DIAGNOSTICS_INFO, [&] {
614 if (m_vmConfig.NetworkingMode == NetworkingMode::Mirrored)
615 {
616 m_networkingEngine = std::make_unique<wsl::core::MirroredNetworking>(
617 m_system.get(), std::move(gnsChannel), m_vmConfig, m_runtimeId, std::move(dnsTunnelingSocket));
618 }
619 else if (m_vmConfig.NetworkingMode == NetworkingMode::Nat)
620 {
621 WI_ASSERT(natNetwork);
622
623 m_networkingEngine = std::make_unique<wsl::core::NatNetworking>(
624 m_system.get(), std::move(natNetwork), std::move(gnsChannel), m_vmConfig, std::move(dnsTunnelingSocket));
625 }
626 else if (m_vmConfig.NetworkingMode == NetworkingMode::Consomme)
627 {
628 wsl::core::ConsommeNetworkingFlags flags =
629 wsl::core::ConsommeNetworkingFlags::Ipv6 | wsl::core::ConsommeNetworkingFlags::LoopbackClientIp;
630 WI_SetFlagIf(flags, wsl::core::ConsommeNetworkingFlags::LocalhostRelay, m_vmConfig.EnableLocalhostRelay);
631 WI_SetFlagIf(flags, wsl::core::ConsommeNetworkingFlags::DnsTunneling, m_vmConfig.EnableDnsTunneling);
632 // NAT may have fallen back to Consomme after the early-config message; drop the unused DNS hvsocket.
633 dnsTunnelingSocket.reset();
634
635 m_networkingEngine = std::make_unique<wsl::core::ConsommeNetworking>(
636 std::move(gnsChannel), flags, LX_INIT_RESOLVCONF_FULL_HEADER, nullptr, m_guestDeviceManager, m_userToken);
637 }
638 else if (m_vmConfig.NetworkingMode == NetworkingMode::Bridged)
639 {
640 m_networkingEngine = std::make_unique<wsl::core::BridgedNetworking>(m_system.get(), m_vmConfig);
641 }
642 else
643 {
644 WI_ASSERT(m_vmConfig.NetworkingMode == NetworkingMode::None);
645 }
646
647 if (m_networkingEngine)
648 {
649 m_networkingEngine->Initialize();
650 }
651 });
652
653 // Find the interface type of the host interface that is most likely to give Internet connectivity
654 const auto bestInterfaceIndex = wsl::core::networking::GetBestInterface();
655 MIB_IFROW row{};
656 row.dwIndex = bestInterfaceIndex;
657 IFTYPE bestInterfaceType{};
658 // Ignore failures
659 if (row.dwIndex != 0 && SUCCEEDED_WIN32(GetIfEntry(&row)))
660 {
661 bestInterfaceType = row.dwType;
662 }
663
664 const auto endTime = std::chrono::steady_clock::now();
665
666 // Log telemetry on the VM initialization including some of its key settings
667 WSL_LOG_TELEMETRY(
668 "WslCoreVmInitialize",
669 PDT_ProductAndServicePerformance,
670 TraceLoggingValue(m_runtimeId, "vmId"),
671 TraceLoggingValue(ToString(m_vmConfig.NetworkingMode), "networkingMode"),
672 TraceLoggingValue(m_vmConfig.FirewallConfig.Enabled(), "firewallEnabled"),
673 TraceLoggingValue(m_vmConfig.EnableDnsTunneling, "dnsTunnelingEnabled"),
674 TraceLoggingValue(
675 m_vmConfig.DnsTunnelingIpAddress.has_value()
676 ? wsl::windows::common::string::IntegerIpv4ToWstring(m_vmConfig.DnsTunnelingIpAddress.value()).c_str()
677 : L"",
678 "dnsTunnelingIpAddress"),
679 TraceLoggingValue(bestInterfaceType, "bestInterfaceType"),
680 TraceLoggingValue(result, "result"),
681 TraceLoggingValue((std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime)).count(), "durationMs"));
682
683 if (FAILED(result))
684 {
685 const auto* context = ExecutionContext::Current();
686 if (context != nullptr)
687 {
688 // We already have a specialized error message, display it to the user.
689 const auto& currentError = context->ReportedError();
690 if (currentError.has_value())
691 {
692 auto strings = wsl::windows::common::wslutil::ErrorToString(currentError.value());
693 EMIT_USER_WARNING(Localization::MessageErrorCode(strings.Message, strings.Code));
694 }
695 }
696
697 // If something failed during initialization that indicates a dependent service is not running,
698 // inform the user to install the Virtual Machine Platform optional component.
699 if (wsl::core::networking::IsNetworkErrorForMissingServices(result) &&
700 !wsl::windows::common::wslutil::IsVirtualMachinePlatformInstalled())
701 {
702 wsl::windows::common::notifications::DisplayOptionalComponentsNotification();
703 EMIT_USER_WARNING(Localization::MessageVirtualMachinePlatformRequiredForNetworking());
704 }
705
706 // Fall back to no networking.
707 EMIT_USER_WARNING(wsl::shared::Localization::MessageNetworkInitializationFailedFallback2(
708 ToString(m_vmConfig.NetworkingMode), ToString(NetworkingMode::None)));
709
710 m_vmConfig.NetworkingMode = NetworkingMode::None;
711 m_networkingEngine.reset();
712 }
713 }
714
715 // Perform additional initialization.
716 InitializeGuest();
717 }
718
719 WslCoreVm::~WslCoreVm() noexcept
720 {
721 TraceLoggingActivity<g_hTraceLoggingProvider, MICROSOFT_KEYWORD_MEASURES> activity;
722 TraceLoggingWriteStart(
723 activity,
724 "TerminateVmStart",
725 TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance),
726 TraceLoggingValue(m_runtimeId, "vmId"));
727
728 m_networkingEngine.reset();
729
730 auto lock = m_lock.lock_exclusive();
731
732 if (m_drvfsInitialResult.valid())
733 {
734 try
735 {
736 m_drvfsInitialResult.get();
737 }
738 CATCH_LOG()
739 }
740
741 // Clear out the exit callback.
742 {
743 auto exitLock = m_exitCallbackLock.lock_exclusive();
744 m_onExit = nullptr;
745
746 // Signal that the vm is terminating
747 // N.B. This might have already been signaled if the VM exited abnormally.
748 m_terminatingEvent.SetEvent();
749 }
750
751 if (m_system)
752 {
753 bool unexpectedTerminate = m_vmExitEvent.is_signaled();
754 bool forcedTerminate = false;
755
756 // Close the socket to mini_init. This will cause mini_init to break out
757 // of its message processing loop and perform a clean shutdown.
758 m_miniInitChannel.Close();
759
760 if (!unexpectedTerminate)
761 {
762 // Wait to receive the notification that the VM has exited.
763 forcedTerminate = !m_vmExitEvent.wait(UTILITY_VM_SHUTDOWN_TIMEOUT);
764
765 // If the notification did not arrive within the timeout, the VM is
766 // forcefully terminated.
767 if (forcedTerminate)
768 {
769 try
770 {
771 wsl::windows::common::hcs::TerminateComputeSystem(m_system.get());
772 }
773 CATCH_LOG()
774 }
775 }
776
777 m_vmExitEvent.wait(UTILITY_VM_TERMINATE_TIMEOUT);
778
779 TraceLoggingWriteTagged(
780 activity,
781 "TerminateVm",
782 TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
783 TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance),
784 TraceLoggingValue(WSL_PACKAGE_VERSION, "wslVersion"),
785 TraceLoggingValue(m_runtimeId, "vmId"),
786 TraceLoggingValue(forcedTerminate, "forceTerminate"),
787 TraceLoggingValue(unexpectedTerminate, "unexpectedTerminate"),
788 TraceLoggingValue(m_vmExitEvent.is_signaled(), "terminationCallbackReceived"),
789 TraceLoggingValue(m_exitDetails.c_str(), "exitDetails"));
790 }
791
792 // Wait for the distro exit callback thread to exit.
793 // The thread might not have been started, in that case joinable() returns false.
794 if (m_distroExitThread.joinable())
795 {
796 m_distroExitThread.join();
797 }
798
799 if (m_virtioFsThread.joinable())
800 {
801 m_virtioFsThread.join();
802 }
803
804 if (m_crashDumpCollectionThread.joinable())
805 {
806 m_crashDumpCollectionThread.join();
807 }
808
809 // Close the handle to the VM. This will wait for any outstanding callbacks.
810 m_system.reset();
811
812 // This loops helps against a potential crash in build <= Windows 11 22H2.
813 for (const auto& e : m_plan9Servers)
814 {
815 LOG_IF_FAILED(e.second->Teardown());
816 }
817
818 // Shutdown virtio device hosts.
819 m_guestDeviceManager.reset();
820
821 // Call RevokeVmAccess on each VHD that was added to the utility VM. This
822 // ensures that the ACL on the VHD does not grow unbounded.
823 std::for_each(m_attachedDisks.begin(), m_attachedDisks.end(), [&](const auto& Entry) {
824 if ((Entry.first.Type == DiskType::PassThrough) && (WI_IsFlagSet(Entry.second.Flags, DiskStateFlags::Online)))
825 {
826 RestorePassthroughDiskState(Entry.first.Path.c_str());
827 }
828
829 if (WI_IsFlagSet(Entry.second.Flags, DiskStateFlags::AccessGranted))
830 {
831 try
832 {
833 wsl::windows::common::hcs::RevokeVmAccess(m_machineId.c_str(), Entry.first.Path.c_str());
834 }
835 CATCH_LOG()
836 }
837 });
838
839 // Delete the swap vhd if one was created.
840 if (m_swapFileCreated)
841 {
842 try
843 {
844 const auto runAsUser = wil::impersonate_token(m_userToken.get());
845 LOG_IF_WIN32_BOOL_FALSE(DeleteFileW(m_vmConfig.SwapFilePath.c_str()));
846 }
847 CATCH_LOG()
848 }
849
850 // Delete the temp folder if it was created.
851 if (m_tempDirectoryCreated)
852 {
853 try
854 {
855 const auto runAsUser = wil::impersonate_token(m_userToken.get());
856 wil::RemoveDirectoryRecursive(m_tempPath.c_str());
857 }
858 CATCH_LOG()
859 }
860
861 // Delete the mstsc.exe local devices key if one was created.
862 if (m_localDevicesKeyCreated)
863 {
864 try
865 {
866 const auto runAsUser = wil::impersonate_token(m_userToken.get());
867 const auto userKey = wsl::windows::common::registry::OpenCurrentUser();
868 const auto key = wsl::windows::common::registry::CreateKey(userKey.get(), c_localDevicesKey, KEY_SET_VALUE);
869 THROW_IF_WIN32_ERROR(::RegDeleteKeyValueW(key.get(), nullptr, m_machineId.c_str()));
870 }
871 CATCH_LOG()
872 }
873
874 WSL_LOG("TerminateVmStop");
875 }
876
877 wil::unique_socket WslCoreVm::AcceptConnection(_In_ DWORD ReceiveTimeout, _In_ const std::source_location& Location) const
878 {
879 auto socket = wsl::windows::common::socket::CancellableAccept(
880 m_listenSocket.get(), m_vmConfig.KernelBootTimeout, m_terminatingEvent.get(), Location);
881 THROW_HR_IF(E_ABORT, !socket.has_value());
882
883 if (ReceiveTimeout != 0)
884 {
885 THROW_LAST_ERROR_IF(setsockopt(socket->get(), SOL_SOCKET, SO_RCVTIMEO, (const char*)&ReceiveTimeout, sizeof(ReceiveTimeout)) == SOCKET_ERROR);
886 }
887
888 return std::move(socket.value());
889 }
890
891 _Requires_lock_held_(m_guestDeviceLock)
892 void WslCoreVm::AddDrvFsShare(_In_ bool Admin, _In_ HANDLE UserToken)
893 {
894 THROW_HR_IF(HCS_E_TERMINATED, !m_system);
895
896 // Allow the Plan 9 server to create NT symlinks.
897 //
898 // N.B. This may fail for unelevated users, however symlink creation will
899 // succeed even without this privilege if developer mode is enabled.
900 wsl::windows::common::security::EnableTokenPrivilege(UserToken, SE_CREATE_SYMBOLIC_LINK_NAME);
901
902 // Set the 9p port and virtio tag.
903 const UINT32 port = Admin ? LX_INIT_UTILITY_VM_PLAN9_DRVFS_ADMIN_PORT : LX_INIT_UTILITY_VM_PLAN9_DRVFS_PORT;
904 const PCWSTR tag = Admin ? TEXT(LX_INIT_DRVFS_ADMIN_VIRTIO_TAG) : TEXT(LX_INIT_DRVFS_VIRTIO_TAG);
905 AddPlan9Share(
906 TEXT(LX_INIT_UTILITY_VM_DRVFS_SHARE_NAME), L"\\\\?", port, (hcs::Plan9ShareFlags::AllowOptions | hcs::Plan9ShareFlags::AllowSubPaths), UserToken, tag);
907
908 const auto virtiofsInitialized = Admin ? m_adminDrvfsToken.is_valid() : m_drvfsToken.is_valid();
909 if (m_vmConfig.EnableVirtioFs && !virtiofsInitialized)
910 {
911 // Add virtiofs devices associating indices with paths from the fixed drive bitmap. These devices support
912 // multiple mounts in the guest, so this only needs to be done once.
913 auto fixedDrives = wsl::windows::common::filesystem::EnumerateFixedDrives(UserToken).first;
914 while (fixedDrives != 0)
915 {
916 ULONG index;
917 WI_VERIFY(_BitScanForward(&index, fixedDrives) != FALSE);
918 const wchar_t fixedDrivePath[] = {gsl::narrow_cast<wchar_t>(L'A' + index), L':', L'\\', L'\0'};
919 try
920 {
921 AddVirtioFsShare(Admin, fixedDrivePath, TEXT(LX_INIT_DEFAULT_PLAN9_MOUNT_OPTIONS), UserToken);
922 }
923 catch (...)
924 {
925 const auto result = wil::ResultFromCaughtException();
926 WSL_LOG(
927 "AddVirtioFsShareError", TraceLoggingValue(fixedDrivePath, "DrivePath"), TraceLoggingValue(result, "result"));
928 }
929 fixedDrives ^= (1 << index);
930 }
931 }
932 }
933
934 _Requires_lock_held_(m_guestDeviceLock)
935 void WslCoreVm::AddPlan9Share(
936 _In_ PCWSTR AccessName, _In_ PCWSTR Path, [[maybe_unused]] _In_ UINT32 Port, _In_ hcs::Plan9ShareFlags Flags, _In_ HANDLE UserToken, _In_opt_ PCWSTR VirtIoTag)
937 {
938 bool addNewDevice = false;
939 wil::com_ptr<IPlan9FileSystem> server;
940
941 {
942 auto revert = wil::impersonate_token(UserToken);
943
944 // This is called from AddDrvFsShare, which is called from InitializeDrvFs, so m_guestDeviceLock is
945 // already held.
946
947 if (m_vmConfig.EnableVirtio9p)
948 {
949 server = m_guestDeviceManager->GetRemoteFileSystem(__uuidof(p9fs::Plan9FileSystem), VirtIoTag);
950 }
951 else
952 {
953 const auto existingServer = m_plan9Servers.find(Port);
954 if (existingServer != m_plan9Servers.end())
955 {
956 server = existingServer->second;
957 }
958 }
959
960 if (!server)
961 {
962 server = wsl::windows::common::wslutil::CreateComServerAsUser<p9fs::Plan9FileSystem, IPlan9FileSystem>(UserToken);
963 if (m_vmConfig.EnableVirtio9p)
964 {
965 m_guestDeviceManager->AddRemoteFileSystem(__uuidof(p9fs::Plan9FileSystem), VirtIoTag, server);
966
967 // Start with one device to handle the first mount request. After
968 // each mount, the Plan9 file-system will request additional
969 // devices via the IPlan9FileSystemHost::NotifyAllDevicesInUse
970 // callback.
971 addNewDevice = true;
972 }
973 else
974 {
975 THROW_IF_FAILED(server->Init(&m_runtimeId, Port));
976 THROW_IF_FAILED(server->Resume());
977 m_plan9Servers.insert(std::make_pair(Port, server));
978 }
979 }
980
981 HRESULT result = server->AddSharePath(AccessName, Path, static_cast<UINT32>(Flags));
982 if (result == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS))
983 {
984 result = S_OK;
985 }
986
987 THROW_IF_FAILED(result);
988 }
989
990 if (addNewDevice)
991 {
992 // This requires more privileges than the user may have, so impersonation is disabled.
993 (void)m_guestDeviceManager->AddNewDevice(VIRTIO_PLAN9_DEVICE_ID, server, VirtIoTag);
994 }
995 }
996
997 ULONG WslCoreVm::AttachDisk(_In_ PCWSTR Disk, _In_ DiskType Type, _In_ std::optional<ULONG> Lun, _In_ bool IsUserDisk, _In_ HANDLE UserToken)
998 {
999 auto lock = m_lock.lock_exclusive();
1000 return AttachDiskLockHeld(Disk, Type, MountFlags::None, Lun, IsUserDisk, UserToken);
1001 }
1002
1003 ULONG WslCoreVm::AttachDiskLockHeld(
1004 _In_ PCWSTR Disk, _In_ DiskType Type, _In_ MountFlags Flags, _In_ std::optional<ULONG> Lun, _In_ bool IsUserDisk, _In_opt_ HANDLE UserToken)
1005 {
1006 ExecutionContext context(Context::MountDisk);
1007
1008 Lun = ReserveLun(Lun);
1009
1010 // Set a scope exit variable to perform cleanup if attaching the disk fails.
1011 DiskStateFlags diskFlags{};
1012 wil::unique_hfile backingFile;
1013 auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
1014 FreeLun(Lun.value());
1015 if (WI_IsFlagSet(diskFlags, DiskStateFlags::AccessGranted))
1016 {
1017 wsl::windows::common::hcs::RevokeVmAccess(m_machineId.c_str(), Disk);
1018 }
1019
1020 if (WI_IsFlagSet(diskFlags, DiskStateFlags::Online))
1021 {
1022 const auto diskHandle = wsl::windows::common::disk::OpenDevice(Disk, GENERIC_READ | GENERIC_WRITE, m_vmConfig.MountDeviceTimeout);
1023 wsl::windows::common::disk::SetOnline(diskHandle.get(), true, m_vmConfig.MountDeviceTimeout);
1024 }
1025 });
1026
1027 try
1028 {
1029 // Check if the disk is already attached.
1030 const auto found = m_attachedDisks.find({Type, Disk});
1031
1032 if (Type == DiskType::PassThrough)
1033 {
1034 if (found != m_attachedDisks.end())
1035 {
1036 THROW_HR_WITH_USER_ERROR(WSL_E_DISK_ALREADY_ATTACHED, Localization::MessageDiskAlreadyAttached(Disk));
1037 }
1038
1039 // Grant the VM access to the disk.
1040 GrantVmWorkerProcessAccessToDisk(Disk, UserToken);
1041 WI_SetFlag(diskFlags, DiskStateFlags::AccessGranted);
1042
1043 // Set the disk online if needed.
1044 //
1045 // N.B. The disk handle must be closed prior to adding the disk to the VM.
1046 {
1047 const auto diskHandle =
1048 wsl::windows::common::disk::OpenDevice(Disk, GENERIC_READ | GENERIC_WRITE, m_vmConfig.MountDeviceTimeout);
1049 if (wsl::windows::common::disk::IsDiskOnline(diskHandle.get()))
1050 {
1051 wsl::windows::common::disk::SetOnline(diskHandle.get(), false, m_vmConfig.MountDeviceTimeout);
1052 WI_SetFlag(diskFlags, DiskStateFlags::Online);
1053 }
1054 }
1055
1056 // Add the disk to the VM.
1057 wsl::shared::retry::RetryWithTimeout<void>(
1058 std::bind(wsl::windows::common::hcs::AddPassThroughDisk, m_system.get(), Disk, Lun.value()),
1059 wsl::windows::common::disk::c_diskOperationRetry,
1060 std::chrono::milliseconds(m_vmConfig.MountDeviceTimeout),
1061 []() { return wil::ResultFromCaughtException() == HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION); });
1062 }
1063 else
1064 {
1065 if (found != m_attachedDisks.end())
1066 {
1067 // Prevent user from launching a distro vhd after manually mounting it; otherwise, return the LUN of the mounted disk.
1068 THROW_HR_IF(WSL_E_USER_VHD_ALREADY_ATTACHED, found->first.User);
1069
1070 // Check if the lun is still valid. It could be stale if the backing volume is reattached.
1071 if (IsBackingVolumeMounted(found->second.BackingFile.get()))
1072 {
1073 return found->second.Lun;
1074 }
1075
1076 const auto staleLun = found->second.Lun;
1077 wsl::windows::common::hcs::RemoveScsiDisk(m_system.get(), staleLun);
1078 if (WI_IsFlagSet(found->second.Flags, DiskStateFlags::AccessGranted))
1079 {
1080 wsl::windows::common::hcs::RevokeVmAccess(m_machineId.c_str(), found->first.Path.c_str());
1081 }
1082
1083 m_attachedDisks.erase(found);
1084 FreeLun(staleLun);
1085 }
1086
1087 backingFile = OpenVhdBackingFile(Disk);
1088
1089 auto grantDiskAccess = [&]() {
1090 auto runAsUser = wil::impersonate_token(UserToken);
1091 wsl::windows::common::hcs::GrantVmAccess(m_machineId.c_str(), Disk);
1092 WI_SetFlag(diskFlags, DiskStateFlags::AccessGranted);
1093 };
1094
1095 // Grant the VM access to the disk.
1096 if (WI_IsFlagClear(Flags, MountFlags::ReadOnly))
1097 {
1098 grantDiskAccess();
1099 }
1100
1101 auto result = wil::ResultFromException([&]() {
1102 wsl::windows::common::hcs::AddVhd(m_system.get(), Disk, Lun.value(), WI_IsFlagSet(Flags, MountFlags::ReadOnly));
1103 });
1104
1105 if (result == HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED) && WI_IsFlagClear(diskFlags, DiskStateFlags::AccessGranted))
1106 {
1107 grantDiskAccess();
1108 wsl::windows::common::hcs::AddVhd(m_system.get(), Disk, Lun.value(), WI_IsFlagSet(Flags, MountFlags::ReadOnly));
1109 }
1110 else
1111 {
1112 THROW_IF_FAILED(result);
1113 }
1114 }
1115 }
1116 catch (...)
1117 {
1118 const auto result = wil::ResultFromCaughtException();
1119 THROW_HR_WITH_USER_ERROR(
1120 result, Localization::MessageFailedToAttachDisk(Disk, wsl::windows::common::wslutil::GetSystemErrorString(result)));
1121 }
1122
1123 m_attachedDisks.emplace(AttachedDisk{Type, Disk, IsUserDisk}, DiskState{Lun.value(), {}, diskFlags, std::move(backingFile)});
1124 cleanup.release();
1125
1126 return Lun.value();
1127 }
1128
1129 void WslCoreVm::CollectCrashDumps(wil::unique_socket&& listenSocket) const
1130 {
1131 wsl::windows::common::wslutil::SetThreadDescription(L"CrashDumpCollection");
1132
1133 while (!m_terminatingEvent.is_signaled())
1134 {
1135 try
1136 {
1137 auto socket = wsl::windows::common::socket::CancellableAccept(listenSocket.get(), INFINITE, m_terminatingEvent.get());
1138 if (!socket.has_value())
1139 {
1140 break; // VM is exiting.
1141 }
1142
1143 DWORD receiveTimeout = m_vmConfig.KernelBootTimeout;
1144 THROW_LAST_ERROR_IF(setsockopt(socket->get(), SOL_SOCKET, SO_RCVTIMEO, (const char*)&receiveTimeout, sizeof(receiveTimeout)) == SOCKET_ERROR);
1145
1146 auto channel = wsl::shared::SocketChannel{std::move(socket.value()), "crash_dump", {m_terminatingEvent.get()}};
1147
1148 auto transaction = channel.ReceiveTransaction();
1149 gsl::span<gsl::byte> responseSpan;
1150 const auto& message = transaction.Receive<LX_PROCESS_CRASH>(&responseSpan);
1151
1152 // Safely extract the process name from the flexible array member.
1153 // The buffer may not be NUL-terminated, so bound the length to the received span size.
1154 const auto bufferSize = responseSpan.size_bytes() - offsetof(LX_PROCESS_CRASH, Buffer);
1155 const std::string process(message.Buffer, strnlen(message.Buffer, bufferSize));
1156
1157 constexpr auto dumpExtension = ".dmp";
1158 constexpr auto dumpPrefix = "wsl-crash";
1159
1160 auto filename = std::format("{}-{}-{}-{}-{}{}", dumpPrefix, message.Timestamp, message.Pid, process, message.Signal, dumpExtension);
1161
1162 std::replace_if(
1163 filename.begin(),
1164 filename.end(),
1165 [](char e) { return !std::isalnum(static_cast<unsigned char>(e)) && e != '.' && e != '-'; },
1166 '_');
1167
1168 auto fullPath = m_vmConfig.CrashDumpFolder / filename;
1169
1170 // Log telemetry when there is a crash within the WSL VM
1171 WSL_LOG_TELEMETRY(
1172 "LinuxCrash",
1173 PDT_ProductAndServicePerformance,
1174 TraceLoggingValue(fullPath.c_str(), "FullPath"),
1175 TraceLoggingValue(message.Pid, "Pid"),
1176 TraceLoggingValue(message.Signal, "Signal"),
1177 TraceLoggingValue(process.c_str(), "process"));
1178
1179 auto runAsUser = wil::impersonate_token(m_userToken.get());
1180
1181 std::error_code error;
1182 std::filesystem::create_directories(m_vmConfig.CrashDumpFolder, error);
1183 if (error.value())
1184 {
1185 THROW_WIN32_MSG(error.value(), "Failed to create folder: %ls", m_vmConfig.CrashDumpFolder.c_str());
1186 }
1187
1188 // Only delete files that:
1189 // - have the temporary flag set
1190 // - start with 'wsl-crash'
1191 // - end in .dmp
1192 //
1193 // This logic is here to prevent accidental user file deletion
1194
1195 auto pred = [&dumpExtension, &dumpPrefix](const auto& e) {
1196 return WI_IsFlagSet(GetFileAttributes(e.path().c_str()), FILE_ATTRIBUTE_TEMPORARY) && e.path().has_extension() &&
1197 e.path().extension() == dumpExtension && e.path().has_filename() &&
1198 e.path().filename().string().find(dumpPrefix) == 0;
1199 };
1200
1201 wsl::windows::common::wslutil::EnforceFileLimit(m_vmConfig.CrashDumpFolder.c_str(), m_vmConfig.MaxCrashDumpCount, pred);
1202
1203 wil::unique_hfile file{CreateFileW(fullPath.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY, nullptr)};
1204 THROW_LAST_ERROR_IF(!file);
1205
1206 transaction.SendResultMessage<std::int32_t>(0);
1207
1208 wsl::windows::common::relay::InterruptableRelay(reinterpret_cast<HANDLE>(channel.Socket()), file.get(), nullptr);
1209 }
1210 CATCH_LOG();
1211 }
1212 }
1213
1214 std::shared_ptr<LxssRunningInstance> WslCoreVm::CreateInstance(
1215 _In_ const GUID& InstanceId,
1216 _In_ const LXSS_DISTRO_CONFIGURATION& Configuration,
1217 _In_ LX_MESSAGE_TYPE MessageType,
1218 _In_ DWORD ReceiveTimeout,
1219 _In_ ULONG DefaultUid,
1220 _In_ ULONG64 ClientLifetimeId,
1221 _In_ ULONG ExportFlags,
1222 _Out_opt_ ULONG* ConnectPort)
1223 {
1224 // Add the VHD to the machine.
1225 auto lock = m_lock.lock_exclusive();
1226 SlowOperationWatcher slowOperation{"AttachDistroVhd"};
1227 const auto lun = AttachDiskLockHeld(Configuration.VhdFilePath.c_str(), DiskType::VHD, MountFlags::None, {}, false, m_userToken.get());
1228 slowOperation.Reset();
1229
1230 // Launch the init daemon and create the instance.
1231 int flags = LxMiniInitMessageFlagNone;
1232 std::wstring sharedMemoryRoot{};
1233
1234 #ifdef WSL_DEV_INSTALL_PATH
1235
1236 std::wstring installPath = TEXT(WSL_DEV_INSTALL_PATH);
1237
1238 #else
1239
1240 std::wstring installPath = m_installPath.wstring();
1241
1242 #endif
1243
1244 std::wstring userProfile{};
1245 if (LXSS_ENABLE_GUI_APPS() && (MessageType == LxMiniInitMessageLaunchInit))
1246 {
1247 WI_SetFlag(flags, LxMiniInitMessageFlagLaunchSystemDistro);
1248 sharedMemoryRoot = m_sharedMemoryRoot;
1249
1250 userProfile = m_userProfile;
1251 }
1252
1253 WI_SetFlagIf(flags, LxMiniInitMessageFlagExportCompressGzip, WI_IsFlagSet(ExportFlags, LXSS_EXPORT_DISTRO_FLAGS_GZIP));
1254 WI_SetFlagIf(flags, LxMiniInitMessageFlagExportCompressXzip, WI_IsFlagSet(ExportFlags, LXSS_EXPORT_DISTRO_FLAGS_XZIP));
1255 WI_SetFlagIf(flags, LxMiniInitMessageFlagVerbose, WI_IsFlagSet(ExportFlags, LXSS_EXPORT_DISTRO_FLAGS_VERBOSE));
1256
1257 wsl::shared::MessageWriter<LX_MINI_INIT_MESSAGE> message(MessageType);
1258 message->MountDeviceType = LxMiniInitMountDeviceTypeLun;
1259 message->DeviceId = lun;
1260 message->Flags = flags;
1261 message.WriteString(message->FsTypeOffset, "ext4");
1262 message.WriteString(message->MountOptionsOffset, "discard,errors=remount-ro,data=ordered");
1263 message.WriteString(message->VmIdOffset, m_machineId);
1264 message.WriteString(message->DistributionNameOffset, Configuration.Name);
1265 message.WriteString(message->SharedMemoryRootOffset, sharedMemoryRoot);
1266 message.WriteString(message->InstallPathOffset, installPath);
1267 message.WriteString(message->UserProfileOffset, userProfile);
1268 auto transaction = m_miniInitChannel.StartTransaction();
1269 transaction.Send<LX_MINI_INIT_MESSAGE>(message.Span());
1270
1271 return CreateInstanceInternal(
1272 InstanceId, Configuration, ReceiveTimeout, DefaultUid, ClientLifetimeId, WI_IsFlagSet(flags, LxMiniInitMessageFlagLaunchSystemDistro), ConnectPort);
1273 }
1274
1275 std::shared_ptr<LxssRunningInstance> WslCoreVm::CreateInstanceInternal(
1276 _In_ const GUID& InstanceId,
1277 _In_ const LXSS_DISTRO_CONFIGURATION& Configuration,
1278 _In_ DWORD ReceiveTimeout,
1279 _In_ ULONG DefaultUid,
1280 _In_ ULONG64 ClientLifetimeId,
1281 _In_ bool LaunchSystemDistro,
1282 _Out_opt_ ULONG* ConnectPort)
1283 {
1284 // Clear the drive mounting flag if support is disabled at the VM level.
1285 //
1286 // N.B. If the system distro is enabled the share will still be created since
1287 // GUI apps require access to the Windows file system in order to launch mstsc.
1288 LXSS_DISTRO_CONFIGURATION localConfig = Configuration;
1289 WI_ClearFlagIf(localConfig.Flags, LXSS_DISTRO_FLAGS_ENABLE_DRIVE_MOUNTING, !m_vmConfig.EnableHostFileSystemAccess);
1290
1291 // Establish a communication channel with the init daemon.
1292 SlowOperationWatcher slowOperation{"WaitForInitDaemonConnect"};
1293 auto initSocket = AcceptConnection(ReceiveTimeout);
1294 slowOperation.Reset();
1295
1296 // If the system distro is enabled, establish a communication channel with its init daemon.
1297 wil::unique_socket systemDistroSocket;
1298 if (LaunchSystemDistro)
1299 {
1300 WI_ASSERT(m_vmConfig.EnableGuiApps);
1301 systemDistroSocket = AcceptConnection(ReceiveTimeout);
1302 }
1303
1304 // Set feature flags for the instance.
1305 ULONG featureFlags{};
1306 WI_SetFlagIf(featureFlags, LxInitFeatureVirtIo9p, m_vmConfig.EnableVirtio9p);
1307 WI_SetFlagIf(featureFlags, LxInitFeatureVirtIoFs, m_vmConfig.EnableVirtioFs);
1308 WI_SetFlagIf(featureFlags, LxInitFeatureDnsTunneling, m_vmConfig.EnableDnsTunneling);
1309
1310 // Create an instance, this takes ownership of the sockets.
1311 auto instance = std::make_shared<WslCoreInstance>(
1312 m_userToken.get(),
1313 initSocket,
1314 systemDistroSocket,
1315 InstanceId,
1316 m_runtimeId,
1317 localConfig,
1318 DefaultUid,
1319 ClientLifetimeId,
1320 m_initializeDrvFs,
1321 featureFlags,
1322 m_vmConfig.DistributionStartTimeout,
1323 m_vmConfig.InstanceIdleTimeout,
1324 ConnectPort,
1325 m_processJobObject.get());
1326
1327 WI_ASSERT(!initSocket && !systemDistroSocket);
1328
1329 return instance;
1330 }
1331
1332 wil::unique_socket WslCoreVm::CreateListeningSocket() const
1333 {
1334 return wsl::windows::common::hvsocket::Listen(m_runtimeId, 0);
1335 }
1336
1337 std::pair<int, LX_MINI_MOUNT_STEP> WslCoreVm::DetachDisk(_In_opt_ PCWSTR Disk)
1338 {
1339 bool deleted = !ARGUMENT_PRESENT(Disk);
1340 std::vector<AttachedDisk> selectedDisks;
1341
1342 auto diskMatches = [TargetPath = Disk](const AttachedDisk& disk) {
1343 if (!disk.User)
1344 {
1345 // Only user mounted disks can be detached
1346 return false;
1347 }
1348
1349 if (disk.Type == DiskType::VHD)
1350 {
1351 // N.B. std::filesystem::equivalent can throw if the path is malformed so use the noexcept variant.
1352 std::error_code error{};
1353 return TargetPath == nullptr || std::filesystem::equivalent(disk.Path, TargetPath, error);
1354 }
1355 else if (disk.Type == DiskType::PassThrough)
1356 {
1357 return TargetPath == nullptr || wsl::windows::common::string::IsPathComponentEqual(disk.Path, TargetPath);
1358 }
1359
1360 return false;
1361 };
1362
1363 auto lock = m_lock.lock_exclusive();
1364 for (auto it = m_attachedDisks.begin(); it != m_attachedDisks.end();)
1365 {
1366 if (diskMatches(it->first))
1367 {
1368 // Unmount any mounted volumes inside the utility VM.
1369 const auto result = UnmountDisk(it->first, it->second);
1370 if (result.first != 0)
1371 {
1372 return result;
1373 }
1374
1375 // Detach the disk from the VM.
1376 wsl::windows::common::hcs::RemoveScsiDisk(m_system.get(), it->second.Lun);
1377 if (WI_VERIFY(WI_IsFlagSet(it->second.Flags, DiskStateFlags::AccessGranted)))
1378 {
1379 wsl::windows::common::hcs::RevokeVmAccess(m_machineId.c_str(), it->first.Path.c_str());
1380 }
1381
1382 FreeLun(it->second.Lun);
1383
1384 // If the disk was online before being attached, revert to that state.
1385 if (WI_IsFlagSet(it->second.Flags, DiskStateFlags::Online))
1386 {
1387 RestorePassthroughDiskState(it->first.Path.c_str());
1388 }
1389
1390 deleted = true;
1391 it = m_attachedDisks.erase(it);
1392 }
1393 else
1394 {
1395 ++it;
1396 }
1397 }
1398
1399 THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), !deleted);
1400
1401 return std::make_pair(0, LxMiniInitMountStepNone);
1402 }
1403
1404 void WslCoreVm::EjectVhd(_In_ PCWSTR VhdPath)
1405 {
1406 auto lock = m_lock.lock_exclusive();
1407 return EjectVhdLockHeld(VhdPath);
1408 }
1409
1410 _Requires_lock_held_(m_lock)
1411 void WslCoreVm::EjectVhdLockHeld(_In_ PCWSTR VhdPath)
1412 {
1413 const auto search = m_attachedDisks.find({DiskType::VHD, VhdPath});
1414 if (search != m_attachedDisks.end())
1415 {
1416 EJECT_VHD_MESSAGE message;
1417 message.Header.MessageSize = sizeof(message);
1418 message.Header.MessageType = LxMiniInitMessageEjectVhd;
1419 message.Lun = search->second.Lun;
1420 const auto& result = m_miniInitChannel.Transaction(message);
1421 LOG_HR_IF_MSG(E_UNEXPECTED, result.Result != 0, "VHD eject failed: %u", result.Result);
1422
1423 // Impersonate the session manager and remove the vhd.
1424 {
1425 auto runAsSelf = wil::run_as_self();
1426 wsl::windows::common::hcs::RemoveScsiDisk(m_system.get(), search->second.Lun);
1427 if (WI_IsFlagSet(search->second.Flags, DiskStateFlags::AccessGranted))
1428 {
1429 wsl::windows::common::hcs::RevokeVmAccess(m_machineId.c_str(), VhdPath);
1430 }
1431 }
1432
1433 m_attachedDisks.erase(search);
1434 FreeLun(message.Lun);
1435 }
1436 }
1437
1438 _Requires_lock_held_(m_guestDeviceLock)
1439 std::optional<WslCoreVm::VirtioFsShare> WslCoreVm::FindVirtioFsShare(_In_ PCWSTR tag, _In_ std::optional<bool> Admin) const
1440 {
1441 for (const auto& share : m_virtioFsShares)
1442 {
1443 if ((share.second == tag) && (!Admin.has_value() || Admin.value() == share.first.Admin))
1444 {
1445 return share.first;
1446 }
1447 }
1448
1449 return {};
1450 }
1451
1452 void WslCoreVm::FreeLun(_In_ ULONG lun)
1453 {
1454 WI_ASSERT(m_lunBitmap[lun]);
1455 m_lunBitmap.set(lun, false);
1456 }
1457
1458 std::wstring WslCoreVm::GenerateConfigJson()
1459 {
1460 hcs::ComputeSystem systemSettings{};
1461 systemSettings.Owner = wsl::windows::common::wslutil::c_vmOwner;
1462 systemSettings.ShouldTerminateOnLastHandleClosed = true;
1463 systemSettings.SchemaVersion.Major = 2;
1464 systemSettings.SchemaVersion.Minor = 3;
1465 hcs::VirtualMachine vmSettings{};
1466 vmSettings.StopOnReset = true;
1467 vmSettings.Chipset.UseUtc = true;
1468
1469 // Ensure the 2MB granularity enforced by HCS.
1470 vmSettings.ComputeTopology.Memory.SizeInMB = ((m_vmConfig.MemorySizeBytes / _1MB) & ~0x1);
1471 vmSettings.ComputeTopology.Memory.AllowOvercommit = true;
1472 vmSettings.ComputeTopology.Memory.EnableDeferredCommit = true;
1473 vmSettings.ComputeTopology.Memory.EnableColdDiscardHint = true;
1474
1475 // Configure backing page size, fault cluster shift size, and page reporting order to favor density (lower vmmem usage).
1476 //
1477 // N.B. Page reporting order must be >= fault cluster size shift.
1478 //
1479 // N.B. This is only done on builds that have the fix for the VID deadlock on partition teardown.
1480 if ((m_windowsVersion.BuildNumber >= WindowsBuildNumbers::Germanium) ||
1481 (m_windowsVersion.BuildNumber >= WindowsBuildNumbers::Cobalt && m_windowsVersion.UpdateBuildRevision >= 2360) ||
1482 (m_windowsVersion.BuildNumber >= WindowsBuildNumbers::Iron && m_windowsVersion.UpdateBuildRevision >= 1970) ||
1483 (m_windowsVersion.BuildNumber >= WindowsBuildNumbers::Vibranium_22H2 && m_windowsVersion.UpdateBuildRevision >= 3393))
1484 {
1485 vmSettings.ComputeTopology.Memory.BackingPageSize = hcs::MemoryBackingPageSize::Small;
1486 vmSettings.ComputeTopology.Memory.FaultClusterSizeShift = 4; // 64k
1487 vmSettings.ComputeTopology.Memory.DirectMapFaultClusterSizeShift = 4; // 64k
1488 m_pageReportingOrder = 5; // 128k
1489 }
1490 else
1491 {
1492 m_pageReportingOrder = 9; // 2MB
1493 }
1494
1495 // May need more MMIO than the default 16GB. WSL uses a vpci device per Plan9 share, WSLg adds a GPU device,
1496 // and a pmem device, and each shared memory virtiofs device needs more than 8GB of MMIO.
1497 SafeInt<INT64> highMmioGapInMB = DEFAULT_HIGH_MMIO_GAP_IN_MB;
1498
1499 // Add additional MMIO space for the system distro and WSLg.
1500 bool privateSystemDistro = !m_vmConfig.SystemDistroPath.empty();
1501 if (!privateSystemDistro)
1502 {
1503 #ifdef WSL_SYSTEM_DISTRO_PATH
1504
1505 m_vmConfig.SystemDistroPath = TEXT(WSL_SYSTEM_DISTRO_PATH);
1506 privateSystemDistro = true;
1507
1508 #else
1509
1510 m_systemDistroDeviceType = LxMiniInitMountDeviceTypeLun;
1511 m_vmConfig.SystemDistroPath = (m_installPath / L"system.vhd").wstring();
1512 WI_ASSERT(wsl::windows::common::filesystem::FileExists(m_vmConfig.SystemDistroPath.c_str()));
1513
1514 #endif
1515 }
1516
1517 // Ensure the system distro exists and ends with a img or vhd file extension.
1518 if (privateSystemDistro)
1519 {
1520 if (wsl::windows::common::string::IsPathComponentEqual(m_vmConfig.SystemDistroPath.extension().native(), L".img"))
1521 {
1522 m_systemDistroDeviceType = LxMiniInitMountDeviceTypePmem;
1523 }
1524 else if (wsl::windows::common::string::IsPathComponentEqual(m_vmConfig.SystemDistroPath.extension().native(), L".vhd"))
1525 {
1526 m_systemDistroDeviceType = LxMiniInitMountDeviceTypeLun;
1527 }
1528
1529 THROW_HR_IF(
1530 WSL_E_CUSTOM_SYSTEM_DISTRO_ERROR,
1531 (m_systemDistroDeviceType == LxMiniInitMountDeviceTypeInvalid) ||
1532 (!wsl::windows::common::filesystem::FileExists(m_vmConfig.SystemDistroPath.c_str())));
1533 }
1534
1535 // Add MMIO space for the WSLg virtio shared memory device.
1536 if (m_vmConfig.EnableGuiApps && m_vmConfig.EnableVirtio)
1537 {
1538 highMmioGapInMB += WSLG_SHARED_MEMORY_SIZE_MB + EXTRA_MMIO_SIZE_PER_VIRTIOFS_DEVICE_IN_MB;
1539 }
1540
1541 // If using pmem for the system distro, add MMIO space for the device.
1542 if (m_systemDistroDeviceType == LxMiniInitMountDeviceTypePmem)
1543 {
1544 highMmioGapInMB += RequiredExtraMmioSpaceForPmemFileInMb(m_vmConfig.SystemDistroPath.c_str());
1545 }
1546
1547 // Log telemetry to measure system distro usage.
1548 WSL_LOG(
1549 "InitializeSystemDistro",
1550 TraceLoggingValue(static_cast<INT64>(highMmioGapInMB), "highMmioGapInMB"),
1551 TraceLoggingValue(privateSystemDistro, "privateSystemDistro"),
1552 TraceLoggingValue(static_cast<DWORD>(m_systemDistroDeviceType), "systemDistroDeviceType"),
1553 TraceLoggingLevel(WINEVENT_LEVEL_INFO));
1554
1555 vmSettings.ComputeTopology.Memory.HighMmioGapInMB = highMmioGapInMB;
1556
1557 // The guest may only be able to access 36-bits of address space (minimum supported), so shift the high MMIO base
1558 // down such that all addresses are accessible. The default starting point is 16G below the maximum 36-bit address,
1559 // so for guests that support larger address spaces, the default base should suffice.
1560 vmSettings.ComputeTopology.Memory.HighMmioBaseInMB = MAX_36_BIT_PAGE_IN_MB - highMmioGapInMB;
1561
1562 // Configure the number of processors.
1563 vmSettings.ComputeTopology.Processor.Count = m_vmConfig.ProcessorCount;
1564
1565 // Set the vmmem suffix which will change the process name in task manager.
1566 if (helpers::IsVmemmSuffixSupported())
1567 {
1568 vmSettings.ComputeTopology.Memory.HostingProcessNameSuffix = wsl::windows::common::wslutil::c_vmOwner;
1569 }
1570
1571 // If nested virtualization was requested, ensure the platform supports it.
1572 //
1573 // N.B. This is done because arm64 and some older amd64 processors do not support nested virtualization.
1574 // Nested virtualization not supported on Windows 10.
1575 if (m_vmConfig.EnableNestedVirtualization)
1576 {
1577 try
1578 {
1579 if (wsl::windows::common::helpers::IsWindows11OrAbove())
1580 {
1581 const auto& processorFeatures = wsl::windows::common::hcs::GetProcessorFeatures();
1582 auto feature = std::find(processorFeatures.begin(), processorFeatures.end(), "NestedVirt");
1583 m_vmConfig.EnableNestedVirtualization = (feature != processorFeatures.end());
1584 }
1585 else
1586 {
1587 m_vmConfig.EnableNestedVirtualization = false;
1588 }
1589
1590 vmSettings.ComputeTopology.Processor.ExposeVirtualizationExtensions = m_vmConfig.EnableNestedVirtualization;
1591 if (!m_vmConfig.EnableNestedVirtualization)
1592 {
1593 EMIT_USER_WARNING(wsl::shared::Localization::MessageNestedVirtualizationNotSupported());
1594 }
1595 }
1596 CATCH_LOG()
1597 }
1598
1599 #ifdef _AMD64_
1600
1601 // Enable hardware performance counters if they are supported.
1602 if (m_vmConfig.EnableHardwarePerformanceCounters)
1603 {
1604 HV_X64_HYPERVISOR_HARDWARE_FEATURES hardwareFeatures{};
1605 __cpuid(reinterpret_cast<int*>(&hardwareFeatures), HvCpuIdFunctionMsHvHardwareFeatures);
1606 vmSettings.ComputeTopology.Processor.EnablePerfmonPmu = hardwareFeatures.ChildPerfmonPmuSupported != 0;
1607 vmSettings.ComputeTopology.Processor.EnablePerfmonLbr = hardwareFeatures.ChildPerfmonLbrSupported != 0;
1608 }
1609
1610 #endif
1611
1612 // Initialize kernel command line.
1613 std::wstring kernelCmdLine = L"initrd=\\" LXSS_VM_MODE_INITRD_NAME L" " TEXT(WSL_ROOT_INIT_ENV) L"=1 panic=-1";
1614
1615 // Append common kernel parameters shared between WSL2 and WSLC.
1616 helpers::AppendCommonKernelCommandLine(kernelCmdLine, m_pageReportingOrder, m_vmConfig.SwiotlbSizeBytes, m_vmConfig.ProcessorCount);
1617
1618 if (m_vmConfig.EnableVirtio && helpers::IsVirtioSerialConsoleSupported())
1619 {
1620 vmSettings.Devices.VirtioSerial.emplace();
1621 }
1622
1623 if (m_dmesgCollector)
1624 {
1625 if (m_vmConfig.EnableEarlyBootLogging)
1626 {
1627 // Capture using the very slow legacy serial port up until the point that the virtio device is started.
1628 if constexpr (!wsl::shared::Arm64)
1629 {
1630 kernelCmdLine += L" earlycon=uart8250,io,0x3f8,115200";
1631 }
1632 else
1633 {
1634 kernelCmdLine += L" earlycon=pl011,0xeffec000,115200";
1635 }
1636
1637 vmSettings.Devices.ComPorts["0"] = hcs::ComPort{m_dmesgCollector->EarlyConsoleName()};
1638 }
1639
1640 // The primary "console" will be a virtio serial device.
1641 kernelCmdLine += L" console=hvc0 debug";
1642 hcs::VirtioSerialPort virtioPort{};
1643 virtioPort.Name = L"hvc0";
1644 virtioPort.NamedPipe = m_dmesgCollector->VirtioConsoleName();
1645 virtioPort.ConsoleSupport = true;
1646 vmSettings.Devices.VirtioSerial->Ports["0"] = std::move(virtioPort);
1647 }
1648 else if (m_vmConfig.EnableDebugConsole)
1649 {
1650 // If a debug console was requested, add required kernel command line options.
1651 if constexpr (!wsl::shared::Arm64)
1652 {
1653 kernelCmdLine += L" console=ttyS0,115200 debug";
1654 }
1655 else
1656 {
1657 kernelCmdLine += L" console=ttyAMA0 debug";
1658 }
1659 }
1660
1661 //
1662 // N.B. The ordering of these devices is important because it determines the order they show up as
1663 // /dev/hvc devices in the guest.
1664 //
1665
1666 if (m_gnsTelemetryLogger)
1667 {
1668 hcs::VirtioSerialPort virtioPort;
1669 virtioPort.Name = TEXT(LX_INIT_HVC_TELEMETRY);
1670 virtioPort.NamedPipe = m_gnsTelemetryLogger->GetPipeName();
1671 virtioPort.ConsoleSupport = true;
1672 vmSettings.Devices.VirtioSerial->Ports["1"] = std::move(virtioPort);
1673 }
1674
1675 if (!m_debugShellPipe.empty())
1676 {
1677 hcs::VirtioSerialPort virtioPort;
1678 virtioPort.Name = TEXT(LX_INIT_HVC_DEBUG_SHELL);
1679 virtioPort.NamedPipe = m_debugShellPipe;
1680 virtioPort.ConsoleSupport = true;
1681 vmSettings.Devices.VirtioSerial->Ports["2"] = std::move(virtioPort);
1682 }
1683
1684 // Ensure that virtio serial devices have unique names.
1685 if constexpr (wsl::shared::Debug)
1686 {
1687 if (vmSettings.Devices.VirtioSerial)
1688 {
1689 std::set<std::wstring_view> uniqueNames;
1690 for (const auto& device : vmSettings.Devices.VirtioSerial->Ports)
1691 {
1692 uniqueNames.emplace(device.second.Name);
1693 }
1694
1695 WI_ASSERT_MSG(
1696 uniqueNames.size() == vmSettings.Devices.VirtioSerial->Ports.size(), "Serial device names must be unique.");
1697 }
1698 }
1699
1700 // If a kernel debugger was requested, add required kernel command line options and
1701 // generate the name of the pipe.
1702 if (m_vmConfig.KernelDebugPort != 0)
1703 {
1704 PCWSTR debugDeviceName = nullptr;
1705 if constexpr (wsl::shared::Arm64)
1706 {
1707 debugDeviceName = L"ttyAMA1";
1708 }
1709 else
1710 {
1711 debugDeviceName = L"ttyS1";
1712 }
1713
1714 kernelCmdLine += std::format(L" pty.legacy_count=2 kgdboc={},115200", debugDeviceName);
1715
1716 m_comPipe1 = wsl::windows::common::helpers::GetUniquePipeName();
1717 wsl::windows::common::helpers::LaunchKdRelay(
1718 m_comPipe1.c_str(),
1719 m_restrictedToken.get(),
1720 m_vmConfig.KernelDebugPort,
1721 m_terminatingEvent.get(),
1722 !m_vmConfig.EnableTelemetry,
1723 m_processJobObject.get());
1724 }
1725 else
1726 {
1727 kernelCmdLine += L" pty.legacy_count=0";
1728 }
1729
1730 if (!m_comPipe0.empty() && (!m_dmesgCollector || !m_vmConfig.EnableEarlyBootLogging))
1731 {
1732 vmSettings.Devices.ComPorts["0"] = hcs::ComPort{m_comPipe0};
1733 }
1734
1735 if (!m_comPipe1.empty())
1736 {
1737 vmSettings.Devices.ComPorts["1"] = hcs::ComPort{m_comPipe1};
1738 }
1739
1740 if (m_vmConfig.MaxCrashDumpCount >= 0)
1741 {
1742 kernelCmdLine += L" " WSL_ENABLE_CRASH_DUMP_ENV L"=1";
1743 }
1744
1745 // Add user-specified kernel command line options at the end.
1746 if (!m_vmConfig.KernelCommandLine.empty())
1747 {
1748 kernelCmdLine += L" ";
1749 kernelCmdLine += m_vmConfig.KernelCommandLine;
1750 }
1751
1752 // Set up boot params.
1753 //
1754 // N.B. Linux kernel direct boot is not yet supported on ARM64.
1755 if constexpr (!wsl::shared::Arm64)
1756 {
1757 auto linuxKernelDirect = hcs::LinuxKernelDirect{};
1758 linuxKernelDirect.KernelFilePath = m_vmConfig.KernelPath.c_str();
1759 linuxKernelDirect.InitRdPath = (m_rootFsPath / LXSS_VM_MODE_INITRD_NAME).c_str();
1760 linuxKernelDirect.KernelCmdLine = kernelCmdLine;
1761 vmSettings.Chipset.LinuxKernelDirect = std::move(linuxKernelDirect);
1762 }
1763 else
1764 {
1765 auto bootThis = hcs::UefiBootEntry{};
1766 bootThis.DeviceType = hcs::UefiBootDevice::VmbFs;
1767 bootThis.VmbFsRootPath = m_rootFsPath.c_str();
1768 bootThis.DevicePath = L"\\" LXSS_VM_MODE_KERNEL_NAME;
1769 bootThis.OptionalData = kernelCmdLine;
1770 hcs::Uefi uefiSettings{};
1771 uefiSettings.BootThis = std::move(bootThis);
1772 vmSettings.Chipset.Uefi = std::move(uefiSettings);
1773 }
1774
1775 // Initialize SCSI devices.
1776 hcs::Scsi scsiController{};
1777
1778 // grantVmAccess should be true for user-supplied paths. Best-effort: failures (e.g. no
1779 // WRITE_DAC on a SYSTEM-owned VHD) are swallowed since VMWP may already have access via
1780 // inherited ACLs; otherwise StartComputeSystem will surface E_ACCESSDENIED.
1781 auto attachDisk = [&](PCWSTR path, bool grantVmAccess) {
1782 auto lun = ReserveLun();
1783 auto backingFile = OpenVhdBackingFile(path);
1784 hcs::Attachment disk{};
1785 disk.Type = hcs::AttachmentType::VirtualDisk;
1786 disk.Path = path;
1787 disk.ReadOnly = true;
1788 disk.SupportCompressedVolumes = true;
1789 disk.AlwaysAllowSparseFiles = true;
1790 disk.SupportEncryptedFiles = true;
1791 scsiController.Attachments[std::to_string(lun)] = std::move(disk);
1792
1793 DiskStateFlags diskFlags{};
1794 if (grantVmAccess)
1795 {
1796 try
1797 {
1798 auto runAsUser = wil::impersonate_token(m_userToken.get());
1799 wsl::windows::common::hcs::GrantVmAccess(m_machineId.c_str(), path);
1800 WI_SetFlag(diskFlags, DiskStateFlags::AccessGranted);
1801 }
1802 CATCH_LOG()
1803 }
1804
1805 m_attachedDisks.emplace(AttachedDisk{DiskType::VHD, path, false}, DiskState{lun, {}, diskFlags, std::move(backingFile)});
1806 return lun;
1807 };
1808
1809 if (m_systemDistroDeviceType == LxMiniInitMountDeviceTypeLun)
1810 {
1811 m_systemDistroDeviceId = attachDisk(m_vmConfig.SystemDistroPath.c_str(), privateSystemDistro);
1812 }
1813
1814 if (!m_vmConfig.KernelModulesPath.empty())
1815 {
1816 m_kernelModulesDeviceId = attachDisk(m_vmConfig.KernelModulesPath.c_str(), m_privateKernelModules);
1817 }
1818
1819 vmSettings.Devices.Scsi["0"] = std::move(scsiController);
1820
1821 // Construct a security descriptor that allows system and the current user.
1822 wil::unique_hlocal_string userSidString;
1823 THROW_LAST_ERROR_IF(!ConvertSidToStringSidW(&m_userSid.Sid, &userSidString));
1824
1825 std::wstring securityDescriptor{L"D:P(A;;FA;;;SY)(A;;FA;;;"};
1826 securityDescriptor += userSidString.get();
1827 securityDescriptor += L")";
1828 hcs::HvSocket hvSocketConfig{};
1829 hvSocketConfig.HvSocketConfig.DefaultBindSecurityDescriptor = securityDescriptor;
1830 hvSocketConfig.HvSocketConfig.DefaultConnectSecurityDescriptor = securityDescriptor;
1831 vmSettings.Devices.HvSocket = std::move(hvSocketConfig);
1832
1833 // N.B. Plan9 device is always added during serialization
1834
1835 systemSettings.VirtualMachine = std::move(vmSettings);
1836 return wsl::shared::ToJsonW(systemSettings);
1837 }
1838
1839 std::pair<int, LX_MINI_MOUNT_STEP> WslCoreVm::GetMountResult(_In_ wsl::shared::SocketChannel& Channel)
1840 {
1841 // Read the response from mini_init.
1842 const auto& Message = Channel.ReceiveMessage<LX_MINI_INIT_MOUNT_RESULT_MESSAGE>();
1843 return std::make_pair(Message.Result, Message.FailureStep);
1844 }
1845
1846 const wsl::core::Config& WslCoreVm::GetConfig() const noexcept
1847 {
1848 return m_vmConfig;
1849 }
1850
1851 GUID WslCoreVm::GetRuntimeId() const
1852 {
1853 return m_runtimeId;
1854 }
1855
1856 int WslCoreVm::GetVmIdleTimeout() const
1857 {
1858 return m_vmConfig.VmIdleTimeout;
1859 }
1860
1861 void WslCoreVm::GrantVmWorkerProcessAccessToDisk(_In_ PCWSTR Disk, _In_opt_ HANDLE UserToken) const
1862 {
1863 if (ARGUMENT_PRESENT(UserToken))
1864 {
1865 // Impersonating the user doesn't let us access a block device,
1866 // check for an elevated token instead.
1867 THROW_HR_IF(WSL_E_ELEVATION_NEEDED_TO_MOUNT_DISK, ((!wsl::windows::common::security::IsTokenElevated(UserToken))));
1868 }
1869
1870 wsl::windows::common::hcs::GrantVmAccess(m_machineId.c_str(), Disk);
1871 }
1872
1873 void WslCoreVm::InitializeGuest()
1874 {
1875 // If GUI apps are enabled, mount the shared memory device and write a registry key to suppress mstsc.exe security warnings.
1876 if (LXSS_ENABLE_GUI_APPS())
1877 {
1878 if (m_vmConfig.EnableVirtio)
1879 {
1880 try
1881 {
1882 m_guestDeviceManager->AddSharedMemoryDevice(L"wslg", L"wslg", WSLG_SHARED_MEMORY_SIZE_MB, m_userToken.get());
1883 m_sharedMemoryRoot = std::format(L"WSL\\{}\\wslg", m_machineId);
1884 }
1885 CATCH_LOG()
1886 }
1887
1888 try
1889 {
1890 auto runAsUser = wil::impersonate_token(m_userToken.get());
1891 const auto userKey = wsl::windows::common::registry::OpenCurrentUser();
1892 const auto devicesKey = wsl::windows::common::registry::CreateKey(userKey.get(), c_localDevicesKey, KEY_SET_VALUE);
1893 constexpr DWORD flags = 0xC4; // Allow clipboard, microphone, and printer access.
1894 wsl::windows::common::registry::WriteDword(devicesKey.get(), nullptr, m_machineId.c_str(), flags);
1895 m_localDevicesKeyCreated = true;
1896 }
1897 CATCH_LOG()
1898 }
1899
1900 // Calculate the size of the configuration message.
1901 wsl::shared::MessageWriter<LX_MINI_INIT_CONFIG_MESSAGE> message(LxMiniInitMessageInitialConfig);
1902 message->EntropySize = c_bootEntropy;
1903 message->EnableGuiApps = LXSS_ENABLE_GUI_APPS();
1904 message->MountGpuShares = m_vmConfig.EnableGpuSupport;
1905 message->EnableInboxGpuLibs = m_enableInboxGpuLibs;
1906 if (m_networkingEngine)
1907 {
1908 m_networkingEngine->FillInitialConfiguration(message->NetworkingConfiguration);
1909 }
1910
1911 WI_ASSERT(message->NetworkingConfiguration.NetworkingMode == static_cast<LX_MINI_INIT_NETWORKING_MODE>(m_vmConfig.NetworkingMode));
1912
1913 // Generate additional entropy to be injected.
1914 if (message->EntropySize > 0)
1915 {
1916 THROW_IF_NTSTATUS_FAILED(BCryptGenRandom(
1917 nullptr, (PUCHAR)message.InsertBuffer(message->EntropyOffset, message->EntropySize).data(), message->EntropySize, BCRYPT_USE_SYSTEM_PREFERRED_RNG));
1918 }
1919
1920 // Send the message.
1921 auto transaction = m_miniInitChannel.StartTransaction();
1922 transaction.Send<LX_MINI_INIT_CONFIG_MESSAGE>(message.Span());
1923
1924 // If port tracker or localhost relay are enabled, establish a connection with the guest and start processing messages.
1925 switch (message->NetworkingConfiguration.PortTrackerType)
1926 {
1927 case LxMiniInitPortTrackerTypeMirrored:
1928 {
1929 auto socket = AcceptConnection(m_vmConfig.KernelBootTimeout);
1930 m_networkingEngine->StartPortTracker(std::move(socket));
1931 break;
1932 }
1933 case LxMiniInitPortTrackerTypeRelay:
1934 {
1935 // If localhost relay is enabled, create a relay process.
1936 //
1937 // N.B. The relay process is launched at medium integrity level, and its lifetime is tied to the lifetime of the utility VM.
1938 const auto result = wil::ResultFromException(WI_DIAGNOSTICS_INFO, [&]() {
1939 const auto socket = AcceptConnection(m_vmConfig.KernelBootTimeout);
1940 wsl::windows::common::helpers::LaunchPortRelay(
1941 socket.get(), m_runtimeId, m_restrictedToken.get(), !m_vmConfig.EnableTelemetry, m_processJobObject.get());
1942 });
1943
1944 if (FAILED(result))
1945 {
1946 const auto errorString = wsl::windows::common::wslutil::GetSystemErrorString(result);
1947 EMIT_USER_WARNING(wsl::shared::Localization::MessageLocalhostRelayFailed(errorString));
1948 }
1949 break;
1950 }
1951
1952 default:
1953 break;
1954 }
1955 }
1956
1957 // Returns true if the admin drvfs share should be used,
1958 // false if the non-elevated share should be used
1959 bool WslCoreVm::InitializeDrvFs(_In_ HANDLE UserToken)
1960 {
1961 auto guestDeviceLock = m_guestDeviceLock.lock_exclusive();
1962 WI_ASSERT(m_vmConfig.EnableHostFileSystemAccess);
1963 if (m_drvfsInitialResult.valid())
1964 {
1965 // The drvfs drives might have been initialized with a different token.
1966 // Make sure the elevation status matches before returning the cached value.
1967 const auto elevated = wsl::windows::common::security::IsTokenElevated(UserToken);
1968 if (m_drvfsInitialResult.get() == elevated)
1969 {
1970 return elevated;
1971 }
1972 }
1973
1974 return InitializeDrvFsLockHeld(UserToken);
1975 }
1976
1977 // Returns true if the admin drvfs share should be used,
1978 // false if the non-elevated share should be used
1979 _Requires_lock_held_(m_guestDeviceLock)
1980 bool WslCoreVm::InitializeDrvFsLockHeld(_In_ HANDLE UserToken)
1981 {
1982 // Before checking whether DrvFs is already initialized, make sure any existing Plan 9 servers
1983 // are usable.
1984 VerifyPlan9Servers();
1985
1986 const auto elevated = wsl::windows::common::security::IsTokenElevated(UserToken);
1987 if (elevated)
1988 {
1989 if (!m_adminDrvfsToken)
1990 {
1991 AddDrvFsShare(true, UserToken);
1992 THROW_IF_WIN32_BOOL_FALSE(
1993 ::DuplicateTokenEx(UserToken, MAXIMUM_ALLOWED, nullptr, SecurityImpersonation, TokenImpersonation, &m_adminDrvfsToken));
1994 }
1995 }
1996 else
1997 {
1998 if (!m_drvfsToken)
1999 {
2000 AddDrvFsShare(false, UserToken);
2001 THROW_IF_WIN32_BOOL_FALSE(
2002 ::DuplicateTokenEx(UserToken, MAXIMUM_ALLOWED, nullptr, SecurityImpersonation, TokenImpersonation, &m_drvfsToken));
2003 }
2004 }
2005
2006 return elevated;
2007 }
2008
2009 bool WslCoreVm::IsDnsTunnelingSupported() const
2010 {
2011 WI_ASSERT(
2012 m_vmConfig.NetworkingMode == NetworkingMode::Nat || m_vmConfig.NetworkingMode == NetworkingMode::Mirrored ||
2013 m_vmConfig.NetworkingMode == NetworkingMode::Consomme);
2014
2015 return SUCCEEDED_LOG(wsl::core::networking::DnsResolver::LoadDnsResolverMethods());
2016 }
2017
2018 bool WslCoreVm::IsVhdAttached(_In_ PCWSTR VhdPath)
2019 {
2020 auto lock = m_lock.lock_exclusive();
2021 return m_attachedDisks.contains({DiskType::VHD, VhdPath});
2022 }
2023
2024 WslCoreVm::DiskMountResult WslCoreVm::MountDisk(
2025 _In_ PCWSTR Disk, _In_ DiskType MountDiskType, _In_ ULONG PartitionIndex, _In_opt_ PCWSTR Name, _In_opt_ PCWSTR Type, _In_opt_ PCWSTR Options)
2026 {
2027 auto lock = m_lock.lock_exclusive();
2028 return MountDiskLockHeld(Disk, MountDiskType, PartitionIndex, Name, Type, Options);
2029 }
2030
2031 WslCoreVm::DiskMountResult WslCoreVm::MountDiskLockHeld(
2032 _In_ PCWSTR Disk, _In_ DiskType MountDiskType, _In_ ULONG PartitionIndex, _In_opt_ PCWSTR Name, _In_opt_ PCWSTR Type, _In_opt_ PCWSTR Options)
2033 {
2034 const auto it = m_attachedDisks.find({MountDiskType, Disk});
2035 THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), (it == m_attachedDisks.end()));
2036 THROW_HR_IF(WSL_E_DISK_ALREADY_MOUNTED, it->second.Mounts.find(PartitionIndex) != it->second.Mounts.end());
2037
2038 // Get the name for the mountpoint
2039 auto targetName = s_GetMountTargetName(Disk, Name, PartitionIndex);
2040 auto targetNameWide = wsl::shared::string::MultiByteToWide(targetName);
2041 // For each attachedDisk pair
2042 const auto nameCollision = std::any_of(m_attachedDisks.begin(), m_attachedDisks.end(), [&](const auto& diskEntry) {
2043 // Check if the targetName matches the name of any Mount already present
2044 return (std::any_of(diskEntry.second.Mounts.begin(), diskEntry.second.Mounts.end(), [&](const auto& mountEntry) {
2045 return wsl::shared::string::IsEqual(mountEntry.second.Name, targetNameWide, false);
2046 }));
2047 });
2048
2049 // Throw error if the specified name was already used
2050 THROW_HR_IF(WSL_E_VM_MODE_MOUNT_NAME_ALREADY_EXISTS, nameCollision);
2051
2052 wsl::shared::MessageWriter<LX_MINI_INIT_MOUNT_MESSAGE> message(LxMiniInitMessageMount);
2053 message->PartitionIndex = PartitionIndex;
2054 message->ScsiLun = it->second.Lun;
2055 message.WriteString(message->TypeOffset, Type);
2056 message.WriteString(message->TargetNameOffset, targetName);
2057 message.WriteString(message->OptionsOffset, Options);
2058
2059 // Send the message.
2060 auto transaction = m_miniInitChannel.StartTransaction();
2061 transaction.Send<LX_MINI_INIT_MOUNT_MESSAGE>(message.Span());
2062
2063 // Accept a connection from mini_init
2064 wsl::shared::SocketChannel channel{AcceptConnection(m_vmConfig.KernelBootTimeout), "MountResult", {m_terminatingEvent.get()}};
2065
2066 // Get the mount result from mini_init
2067 auto [mountResult, step] = GetMountResult(channel);
2068 if (mountResult == 0)
2069 {
2070 Mount mount;
2071
2072 // Always set the Name attribute; use generated one as default
2073 mount.Name = std::move(targetNameWide);
2074
2075 if (Type != nullptr)
2076 {
2077 mount.Type = Type;
2078 }
2079
2080 if (Options != nullptr)
2081 {
2082 mount.Options = Options;
2083 }
2084
2085 it->second.Mounts.emplace(PartitionIndex, std::move(mount));
2086 }
2087
2088 return {std::move(targetName), mountResult, step};
2089 }
2090
2091 wil::unique_socket WslCoreVm::CreateRootNamespaceProcess(_In_ LPCSTR Path, _In_ LPCSTR* Arguments)
2092 {
2093 auto lock = m_lock.lock_exclusive();
2094
2095 return LxssCreateProcess::CreateLinuxProcess(
2096 Path, Arguments, m_runtimeId, m_miniInitChannel, m_terminatingEvent.get(), m_vmConfig.DistributionStartTimeout);
2097 }
2098
2099 void WslCoreVm::MountRootNamespaceFolder(_In_ LPCWSTR HostPath, _In_ LPCWSTR GuestPath, _In_ bool ReadOnly, _In_ LPCWSTR Name)
2100 {
2101 auto lock = m_lock.lock_exclusive();
2102
2103 const auto flags = (ReadOnly ? hcs::Plan9ShareFlags::ReadOnly : hcs::Plan9ShareFlags::None) | hcs::Plan9ShareFlags::AllowOptions;
2104 wsl::windows::common::hcs::AddPlan9Share(m_system.get(), Name, Name, HostPath, LX_INIT_UTILITY_VM_PLAN9_PORT, flags);
2105
2106 wsl::shared::MessageWriter<LX_MINI_INIT_MOUNT_FOLDER_MESSAGE> message(LxMiniInitMountFolder);
2107 message.WriteString(message->PathIndex, GuestPath);
2108 message.WriteString(message->NameIndex, Name);
2109 message->ReadOnly = ReadOnly;
2110
2111 const auto& ResultMessage = m_miniInitChannel.Transaction<LX_MINI_INIT_MOUNT_FOLDER_MESSAGE>(message.Span());
2112
2113 THROW_HR_IF_MSG(
2114 E_FAIL,
2115 ResultMessage.Result != 0,
2116 "Failed to mount folder. HostPath=%ls, GuestPath=%ls, Name=%ls, ReadOnly=%d, Result=%d",
2117 HostPath,
2118 GuestPath,
2119 Name,
2120 ReadOnly,
2121 ResultMessage.Result);
2122 }
2123
2124 ULONG
2125 WslCoreVm::MountFileAsPersistentMemory(_In_ PCWSTR FilePath, _In_ bool ReadOnly)
2126 {
2127 // Serialize calls to mount pmem devices to the VM. Some quick background on why we do this.
2128 // The problem stems from the fact that our caller needs to know the dev path where the pmem
2129 // device will be mounted (i.e. /dev/pmem0). We could dynamically discover the device path and
2130 // return that to our caller. However, some callers statically declare the dev paths in their
2131 // fstabs. Therefore, we must wait for each device to finish initializing before allowing the
2132 // next to proceed, so that they appear in the expected predefined order.
2133 //
2134 // Ideally callers wouldn't rely on the dev path, and would setup their fstabs using names. If
2135 // callers are ever updated, we could update this code to allow pmem devices to be added in
2136 // parallel and dynamically discover their dev path (which we would then use if the caller
2137 // asked us to mount the pmem device, instead of them doing it in their fstabs). To dynamically
2138 // discover the dev path, we'd have to poll /sys/class/block. Eventually a path such as
2139 // /sys/class/block/pmemX will appear. Once it appears, /sys/class/block/pmemX/device will be
2140 // a symlink that points to a path like:
2141 // /sys/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0004:00/VMBUS:00/<GUID>/pcicceb:00//cceb:00:00.0/virtio1/ndbus0/region0/namespace0.0/block/pmem0
2142 // Notice the GUID in the middle of that path. That GUID is the instance ID, which is randomly
2143 // generated by AddVirtioPmemDevice. So once we find a path with the instance ID, we know that
2144 // eventually /dev/pmemX will appear in the guest.
2145 auto persistentMemoryLock = m_persistentMemoryLock.lock_exclusive();
2146
2147 // Add the pmem device to the VM.
2148 // N.B. If this succeeds, technically we'd need to remove the device if we later encounter any
2149 // failures. Otherwise, we'd potentially leave the VM in a torn state. However, HCS
2150 // doesn't currently support this. For now, we rely on the fact that all pmem devices are
2151 // added as part of VM creation and therefore any failure will result in VM termination
2152 // (in which case there's no need to remove the device).
2153 (void)m_guestDeviceManager->AddVirtioPmemDevice(FilePath, ReadOnly, m_userToken.get());
2154
2155 // Wait for the pmem device to appear in the VM at /dev/pmemX. Guess the value of X given the
2156 // number of pmem devices that have been exposed to the VM. See above for more details why.
2157 // N.B. If hot remove of pmem devices is ever added, this logic will need to be updated.
2158 // Similarly, if nvdimm devices are ever passed through to the VM, this logic will need
2159 // to be updated.
2160 const ULONG persistentMemoryId = m_nextPersistentMemoryId;
2161 WaitForPmemDeviceInVm(persistentMemoryId);
2162
2163 // The pmem device was successfully found in the VM. Increment the next expected pmem device ID.
2164 m_nextPersistentMemoryId += 1;
2165
2166 return persistentMemoryId;
2167 }
2168
2169 void WslCoreVm::WaitForPmemDeviceInVm(_In_ ULONG PmemId)
2170 {
2171 // Construct the mini_init message.
2172 LX_MINI_INIT_WAIT_FOR_PMEM_DEVICE_MESSAGE message{};
2173 message.Header.MessageType = LxMiniInitMessageWaitForPmemDevice;
2174 message.Header.MessageSize = sizeof(message);
2175 message.PmemId = PmemId;
2176
2177 // Send the message to mini_init.
2178 wsl::shared::SocketChannel channel;
2179 {
2180 auto lock = m_lock.lock_exclusive();
2181
2182 auto transaction = m_miniInitChannel.StartTransaction();
2183 transaction.Send(message);
2184 channel = {
2185 AcceptConnection(m_vmConfig.KernelBootTimeout),
2186 "WaitForPmem",
2187 {m_terminatingEvent.get()},
2188 };
2189 }
2190
2191 // Wait for mini_init to respond.
2192
2193 const auto& resultMessage = channel.ReceiveMessage<LX_MINI_INIT_WAIT_FOR_PMEM_DEVICE_MESSAGE::TResponse>();
2194
2195 // Check if the device was found in the VM.
2196 if (resultMessage.Result != 0)
2197 {
2198 THROW_WIN32_MSG(ERROR_NOT_FOUND, "Failed to find /dev/pmem%u with result %d", PmemId, resultMessage.Result);
2199 }
2200 }
2201
2202 _Requires_lock_held_(m_guestDeviceLock)
2203 std::tuple<std::wstring, std::wstring, std::wstring> WslCoreVm::AddVirtioFsShare(_In_ bool Admin, _In_ PCWSTR Path, _In_ PCWSTR Options, _In_opt_ HANDLE UserToken)
2204 {
2205 WI_ASSERT(m_vmConfig.EnableVirtioFs);
2206
2207 if (!ARGUMENT_PRESENT(UserToken))
2208 {
2209 UserToken = Admin ? m_adminDrvfsToken.get() : m_drvfsToken.get();
2210 THROW_HR_IF_MSG(E_UNEXPECTED, !UserToken, "UserToken not set for supplied context (Admin = %d)", Admin);
2211 }
2212
2213 WI_ASSERT(Admin == wsl::windows::common::security::IsTokenElevated(UserToken));
2214
2215 // Ensure that the path has a trailing path separator.
2216 std::wstring sharePath(Path);
2217 if (!sharePath.ends_with(L'\\') && !sharePath.ends_with(L'/'))
2218 {
2219 sharePath.push_back(L'\\');
2220 }
2221
2222 sharePath = wsl::windows::common::filesystem::GetCanonicalPath(sharePath).wstring();
2223
2224 std::wstring effectiveOptions(Options);
2225
2226 // Check if a matching share already exists.
2227 bool created = false;
2228 std::wstring shareName;
2229 VirtioFsShare key(sharePath.c_str(), effectiveOptions.c_str(), Admin);
2230 if (!m_virtioFsShares.contains(key))
2231 {
2232 // Generate a new unique tag for the share.
2233 //
2234 // N.B. The tag can be maximum 36 characters long so a GUID without braces fits perfectly.
2235 GUID tagGuid{};
2236 THROW_IF_FAILED(CoCreateGuid(&tagGuid));
2237
2238 shareName = wsl::shared::string::GuidToString<wchar_t>(tagGuid, wsl::shared::string::None);
2239 WI_ASSERT(!FindVirtioFsShare(shareName.c_str(), Admin));
2240
2241 if (m_vmConfig.EnableVirtioFsAggregateShares)
2242 {
2243 auto& device = Admin ? m_adminVirtioFsDevice : m_virtioFsDevice;
2244 const PCWSTR deviceTag = Admin ? TEXT(LX_INIT_DRVFS_ADMIN_VIRTIO_TAG) : TEXT(LX_INIT_DRVFS_VIRTIO_TAG);
2245 if (!device.has_value())
2246 {
2247 VirtioFsShareOptions aggregateOptions{.Kind = VirtiofsShareKind_Aggregate};
2248 device = m_guestDeviceManager->AddVirtiofsDevice(deviceTag, L"", L"", UserToken, aggregateOptions);
2249 }
2250
2251 m_guestDeviceManager->AddVirtiofsChild(device.value(), shareName.c_str(), key.OptionsString().c_str(), sharePath.c_str());
2252 }
2253 else
2254 {
2255 (void)m_guestDeviceManager->AddVirtiofsDevice(shareName.c_str(), key.OptionsString().c_str(), sharePath.c_str(), UserToken);
2256 }
2257
2258 m_virtioFsShares.emplace(std::move(key), shareName);
2259 created = true;
2260 }
2261 else
2262 {
2263 shareName = m_virtioFsShares[key];
2264 }
2265
2266 const std::wstring deviceTag = m_vmConfig.EnableVirtioFsAggregateShares
2267 ? (Admin ? TEXT(LX_INIT_DRVFS_ADMIN_VIRTIO_TAG) : TEXT(LX_INIT_DRVFS_VIRTIO_TAG))
2268 : shareName;
2269 const std::wstring childName = m_vmConfig.EnableVirtioFsAggregateShares ? shareName : L"";
2270
2271 WSL_LOG(
2272 "WslCoreVmAddVirtioFsShare",
2273 TraceLoggingValue(Admin, "admin"),
2274 TraceLoggingValue(sharePath.c_str(), "path"),
2275 TraceLoggingValue(effectiveOptions.c_str(), "options"),
2276 TraceLoggingValue(deviceTag.c_str(), "tag"),
2277 TraceLoggingValue(childName.c_str(), "childName"),
2278 TraceLoggingValue(m_vmConfig.EnableVirtioFsAggregateShares, "aggregate"),
2279 TraceLoggingValue(created, "created"),
2280 TraceLoggingValue(m_virtioFsShares.size(), "shareCount"));
2281
2282 return {deviceTag, childName, sharePath};
2283 }
2284
2285 void WslCoreVm::OnCrash(_In_ LPCWSTR Details)
2286 {
2287 if (m_vmCrashEvent.is_signaled())
2288 {
2289 return; // Crash information has already been collected
2290 }
2291
2292 WSL_LOG("GuestCrash", TraceLoggingValue(Details, "Data"));
2293 const auto crashInformation = wsl::shared::FromJson<wsl::windows::common::hcs::CrashReport>(Details);
2294
2295 if (m_vmConfig.MaxCrashDumpCount >= 0)
2296 {
2297 constexpr auto c_extension = L".txt";
2298 constexpr auto c_prefix = L"kernel-panic-";
2299 const auto filename = std::format(L"{}{}-{}{}", c_prefix, std::time(nullptr), m_runtimeId, c_extension);
2300 auto tracePath = m_vmConfig.CrashDumpFolder / filename;
2301
2302 auto runAsUser = wil::impersonate_token(m_userToken.get());
2303
2304 std::error_code error;
2305 std::filesystem::create_directories(m_vmConfig.CrashDumpFolder, error);
2306 if (error.value())
2307 {
2308 THROW_WIN32_MSG(error.value(), "Failed to create folder: %ls", m_vmConfig.CrashDumpFolder.c_str());
2309 }
2310
2311 auto pred = [&c_extension, &c_prefix](const auto& e) {
2312 return WI_IsFlagSet(GetFileAttributes(e.path().c_str()), FILE_ATTRIBUTE_TEMPORARY) && e.path().has_extension() &&
2313 e.path().extension() == c_extension && e.path().has_filename() && e.path().filename().wstring().find(c_prefix) == 0;
2314 };
2315
2316 wsl::windows::common::wslutil::EnforceFileLimit(m_vmConfig.CrashDumpFolder.c_str(), m_vmConfig.MaxCrashDumpCount, pred);
2317
2318 {
2319 std::wofstream outputFile(tracePath.wstring());
2320 THROW_HR_IF(E_UNEXPECTED, !outputFile || !(outputFile << crashInformation.CrashLog));
2321 }
2322
2323 m_vmCrashLogFile = std::move(tracePath);
2324 }
2325
2326 m_vmCrashEvent.SetEvent();
2327 }
2328
2329 void WslCoreVm::OnExit(_In_opt_ PCWSTR ExitDetails)
2330 {
2331 // Indicate that the VM has exited, and wake any waiting threads. The instance may be in its destructor at this
2332 // point but closing m_system will wait for any outstanding callbacks, so this function will complete before the
2333 // destructor continues.
2334 std::function<void(GUID)> terminationCallback{};
2335 {
2336 auto exitLock = m_exitCallbackLock.lock_exclusive();
2337 if (ARGUMENT_PRESENT(ExitDetails))
2338 {
2339 m_exitDetails = ExitDetails;
2340 }
2341
2342 m_vmExitEvent.SetEvent();
2343
2344 // If we reach this block and 'm_terminatingEvent' is not signaled, then this is abnormal shutdown.
2345 // If that happens, set m_terminatingEvent so all pending socket operations can be properly cancelled.
2346 if (!m_terminatingEvent.is_signaled())
2347 {
2348 WSL_LOG("AbnormalVmExit", TraceLoggingValue(ExitDetails, "Details"));
2349 m_terminatingEvent.SetEvent();
2350 }
2351
2352 terminationCallback = std::move(m_onExit);
2353 }
2354
2355 if (terminationCallback)
2356 {
2357 terminationCallback(m_runtimeId);
2358 }
2359 }
2360
2361 void WslCoreVm::ReadGuestCapabilities()
2362 {
2363 const auto& info = m_miniInitChannel.ReceiveMessage<LX_INIT_GUEST_CAPABILITIES>();
2364
2365 m_kernelVersionString = wsl::shared::string::MultiByteToWide(info.Buffer);
2366
2367 // Parse the version string.
2368 const std::regex pattern("(\\d+)\\.(\\d+)\\.(\\d+).*");
2369 std::smatch match;
2370 const std::string input = info.Buffer;
2371 if (!std::regex_match(input, match, pattern) || match.size() != 4)
2372 {
2373 THROW_HR_MSG(E_UNEXPECTED, "Failed to parse kernel version: '%hs'", input.c_str());
2374 }
2375
2376 auto get = [&](int position) { return std::stoul(match.str(position)); };
2377
2378 try
2379 {
2380 m_kernelVersion = std::make_tuple(get(1), get(2), get(3));
2381 }
2382 catch (const std::exception& e)
2383 {
2384 THROW_HR_MSG(E_UNEXPECTED, "Failed to parse kernel version: '%hs', %hs", info.Buffer, e.what());
2385 }
2386
2387 m_seccompAvailable = info.SeccompAvailable;
2388 m_hvPciSwiotlbBase = info.HvPciSwiotlbBase;
2389 m_hvPciSwiotlbSize = info.HvPciSwiotlbSize;
2390 WSL_LOG(
2391 "GuestKernelInfo",
2392 TraceLoggingValue(m_seccompAvailable, "SeccompAvailable"),
2393 TraceLoggingValue(m_hvPciSwiotlbBase, "HvPciSwiotlbBase"),
2394 TraceLoggingValue(m_hvPciSwiotlbSize, "HvPciSwiotlbSize"),
2395 TraceLoggingValue(std::get<0>(m_kernelVersion), "Version"),
2396 TraceLoggingValue(std::get<1>(m_kernelVersion), "Revision"),
2397 TraceLoggingValue(std::get<2>(m_kernelVersion), "Minor"));
2398 }
2399
2400 ULONG WslCoreVm::ReserveLun(_In_ std::optional<ULONG> Lun)
2401 {
2402 if (Lun.has_value() && !m_lunBitmap[Lun.value()])
2403 {
2404 m_lunBitmap[Lun.value()] = true;
2405 return Lun.value();
2406 }
2407
2408 for (ULONG index = 0; index < gsl::narrow_cast<ULONG>(m_lunBitmap.size()); index += 1)
2409 {
2410 if (!m_lunBitmap[index])
2411 {
2412 m_lunBitmap[index] = true;
2413 return index;
2414 }
2415 }
2416
2417 THROW_HR(WSL_E_TOO_MANY_DISKS_ATTACHED);
2418 }
2419
2420 void WslCoreVm::RestorePassthroughDiskState(_In_ LPCWSTR Disk) const
2421 try
2422 {
2423 const auto diskHandle = wsl::windows::common::disk::OpenDevice(Disk, GENERIC_READ | GENERIC_WRITE, m_vmConfig.MountDeviceTimeout);
2424 wsl::windows::common::disk::SetOnline(diskHandle.get(), true, m_vmConfig.MountDeviceTimeout);
2425 return;
2426 }
2427 CATCH_LOG()
2428
2429 void WslCoreVm::RegisterCallbacks(_In_ const std::function<void(ULONG)>& DistroExitCallback, _In_ const std::function<void(GUID)>& TerminationCallback)
2430 {
2431 WSL_LOG(
2432 "WslCoreVm::RegisterCallbacks",
2433 TraceLoggingValue(static_cast<bool>(DistroExitCallback), "DistroExitCallback"),
2434 TraceLoggingValue(static_cast<bool>(TerminationCallback), "TerminationCallback"));
2435
2436 if (DistroExitCallback)
2437 {
2438 auto lock = m_lock.lock_exclusive();
2439 THROW_HR_IF(E_INVALIDARG, !m_notifyChannel);
2440 m_distroExitThread = std::thread([exitCallback = std::move(DistroExitCallback),
2441 notifyChannel = std::move(m_notifyChannel),
2442 terminationEvent = m_terminatingEvent.get()]() {
2443 try
2444 {
2445 wsl::windows::common::wslutil::SetThreadDescription(L"DistroExitCallback");
2446
2447 std::vector<gsl::byte> buffer;
2448 for (;;)
2449 {
2450 // Read the message.
2451 auto message = wsl::shared::socket::RecvMessage(notifyChannel.get(), buffer, terminationEvent);
2452 if (message.empty())
2453 {
2454 break;
2455 }
2456
2457 const auto* header = gslhelpers::get_struct<MESSAGE_HEADER>(message);
2458 if (header->MessageType == LxMiniInitMessageChildExit)
2459 {
2460 const auto* exitMessage = gslhelpers::try_get_struct<LX_MINI_INIT_CHILD_EXIT_MESSAGE>(message);
2461 if (exitMessage)
2462 {
2463 WSL_LOG("ProcessExited", TraceLoggingValue(exitMessage->ChildPid, "pid"));
2464 exitCallback(exitMessage->ChildPid);
2465 }
2466 }
2467 else
2468 {
2469 LOG_HR_MSG(E_UNEXPECTED, "Unexpected MessageType %d", header->MessageType);
2470 }
2471 }
2472 }
2473 CATCH_LOG()
2474 });
2475 }
2476
2477 if (TerminationCallback)
2478 {
2479 // Register the callback if the VM has not been terminated.
2480 auto exitLock = m_exitCallbackLock.lock_exclusive();
2481 THROW_HR_IF(E_INVALIDARG, m_onExit);
2482 if (!m_terminatingEvent.is_signaled())
2483 {
2484 m_onExit = std::move(TerminationCallback);
2485 }
2486 else
2487 {
2488 // The VM has already been terminated, invoke the callback on a separate thread.
2489 std::thread([terminationCallback = std::move(TerminationCallback), runtimeId = m_runtimeId]() {
2490 wsl::windows::common::wslutil::SetThreadDescription(L"TerminationCallback");
2491 terminationCallback(runtimeId);
2492 }).detach();
2493 }
2494 }
2495
2496 if (m_vmConfig.EnableHostFileSystemAccess && m_vmConfig.EnableVirtioFs)
2497 {
2498 // Create a thread listening for handling virtiofs requests.
2499 auto listenSocket = wsl::windows::common::hvsocket::Listen(m_runtimeId, LX_INIT_UTILITY_VM_VIRTIOFS_PORT);
2500 m_virtioFsThread = std::thread(&WslCoreVm::VirtioFsWorker, this, std::move(listenSocket));
2501 }
2502 }
2503
2504 void WslCoreVm::ResizeDistribution(_In_ ULONG Lun, _In_ HANDLE OutputHandle, _In_ ULONG64 NewSize)
2505 {
2506 auto lock = m_lock.lock_exclusive();
2507
2508 LX_MINI_INIT_RESIZE_DISTRIBUTION_MESSAGE message{};
2509 message.Header.MessageSize = sizeof(message);
2510 message.Header.MessageType = LxMiniInitMessageResizeDistribution;
2511 message.ScsiLun = Lun;
2512 message.NewSize = NewSize;
2513
2514 auto transaction = m_miniInitChannel.StartTransaction();
2515 transaction.Send(message);
2516
2517 wsl::shared::SocketChannel channel{AcceptConnection(m_vmConfig.KernelBootTimeout), "ResizeDistribution", {m_terminatingEvent.get()}};
2518 auto outputChannel = AcceptConnection(m_vmConfig.KernelBootTimeout);
2519
2520 wsl::windows::common::relay::ScopedRelay outputRelay(std::move(outputChannel), OutputHandle);
2521
2522 const auto& resultMessage = channel.ReceiveMessage<LX_MINI_INIT_RESIZE_DISTRIBUTION_RESPONSE>();
2523 if (resultMessage.ResponseCode != 0)
2524 {
2525 THROW_HR_WITH_USER_ERROR(E_FAIL, wsl::shared::Localization::MessageFailedToResizeDisk());
2526 }
2527 }
2528
2529 void WslCoreVm::TrimDistribution(_In_ ULONG Lun)
2530 {
2531 auto lock = m_lock.lock_exclusive();
2532
2533 LX_MINI_INIT_TRIM_DISTRIBUTION_MESSAGE message{};
2534 message.Header.MessageSize = sizeof(message);
2535 message.Header.MessageType = LxMiniInitMessageTrimDistribution;
2536 message.ScsiLun = Lun;
2537
2538 auto transaction = m_miniInitChannel.StartTransaction();
2539 transaction.Send(message);
2540
2541 wsl::shared::SocketChannel channel{AcceptConnection(m_vmConfig.KernelBootTimeout), "TrimDistribution", {m_terminatingEvent.get()}};
2542
2543 const auto& resultMessage = channel.ReceiveMessage<LX_MINI_INIT_TRIM_DISTRIBUTION_RESPONSE>();
2544 THROW_HR_IF(E_FAIL, resultMessage.ResponseCode != 0);
2545 }
2546
2547 void WslCoreVm::SaveAttachedDisksState()
2548 try
2549 {
2550 auto lock = m_lock.lock_exclusive();
2551 const auto key = wsl::windows::common::registry::OpenOrCreateLxssDiskMountsKey(&m_userSid.Sid);
2552 for (const auto& e : m_attachedDisks)
2553 {
2554 if (e.first.User)
2555 {
2556 SaveDiskState(key.get(), e.first, e.second, e.first.Type);
2557 }
2558 }
2559
2560 return;
2561 }
2562 CATCH_LOG()
2563
2564 void WslCoreVm::SaveDiskState(_In_ HKEY Key, _In_ const AttachedDisk& Disk, _In_ const DiskState& State, _In_ const DiskType& SaveDiskType)
2565 {
2566 const auto keyPath = std::to_wstring(State.Lun);
2567 const auto diskKey = wsl::windows::common::registry::CreateKey(Key, keyPath.c_str(), KEY_ALL_ACCESS, nullptr, REG_OPTION_VOLATILE);
2568
2569 wsl::windows::common::registry::WriteString(diskKey.get(), nullptr, c_diskValueName, Disk.Path.c_str());
2570
2571 wsl::windows::common::registry::WriteDword(diskKey.get(), nullptr, c_disktypeValueName, static_cast<DWORD>(SaveDiskType));
2572
2573 for (const auto& e : State.Mounts)
2574 {
2575 auto partition = std::to_wstring(e.first);
2576 auto mountKey = wsl::windows::common::registry::CreateKey(diskKey.get(), partition.c_str(), KEY_ALL_ACCESS, nullptr, REG_OPTION_VOLATILE);
2577
2578 wsl::windows::common::registry::WriteString(mountKey.get(), nullptr, c_mountNameValueName, e.second.Name.c_str());
2579
2580 if (e.second.Options.has_value())
2581 {
2582 wsl::windows::common::registry::WriteString(mountKey.get(), nullptr, c_optionsValueName, e.second.Options.value().c_str());
2583 }
2584
2585 if (e.second.Type.has_value())
2586 {
2587 wsl::windows::common::registry::WriteString(mountKey.get(), nullptr, c_typeValueName, e.second.Type.value().c_str());
2588 }
2589 }
2590 }
2591
2592 std::pair<int, LX_MINI_MOUNT_STEP> WslCoreVm::UnmountDisk(_In_ const AttachedDisk& Disk, _Inout_ DiskState& State)
2593 {
2594 // Iterate through the mountpoints to unmount and delete them
2595 for (auto it = State.Mounts.begin(); it != State.Mounts.end(); it = State.Mounts.erase(it))
2596 {
2597 const auto result = UnmountVolume(Disk, it->first, it->second.Name.c_str());
2598 if (result.first != 0)
2599 {
2600 return result;
2601 }
2602 }
2603
2604 // Tell the guest to flush its IO caches and stop using the disk.
2605 LX_MINI_INIT_DETACH_MESSAGE message{};
2606 message.Header.MessageType = LxMiniInitMessageDetach;
2607 message.Header.MessageSize = sizeof(message);
2608 message.ScsiLun = State.Lun;
2609
2610 auto transaction = m_miniInitChannel.StartTransaction();
2611 transaction.Send(message);
2612
2613 // Accept a connection from mini_init.
2614 wsl::shared::SocketChannel channel{AcceptConnection(m_vmConfig.KernelBootTimeout), "MountResult", {m_terminatingEvent.get()}};
2615
2616 // Get the unmount result from mini_init
2617 return GetMountResult(channel);
2618 }
2619
2620 std::pair<int, LX_MINI_MOUNT_STEP> WslCoreVm::UnmountVolume(_In_ const AttachedDisk& Disk, _In_ ULONG PartitionIndex, _In_ PCWSTR Name)
2621 {
2622 wsl::shared::MessageWriter<LX_MINI_INIT_UNMOUNT_MESSAGE> message(LxMiniInitMessageUnmount);
2623 message.WriteString(Name);
2624
2625 // Send the message.
2626 auto transaction = m_miniInitChannel.StartTransaction();
2627 transaction.Send<LX_MINI_INIT_UNMOUNT_MESSAGE>(message.Span());
2628
2629 // Accept a connection from mini_init.
2630 wsl::shared::SocketChannel channel{AcceptConnection(m_vmConfig.KernelBootTimeout), "MountResult", {m_terminatingEvent.get()}};
2631
2632 // Get the unmount result from mini_init.
2633 return GetMountResult(channel);
2634 }
2635
2636 _Requires_lock_held_(m_guestDeviceLock)
2637 void WslCoreVm::VerifyPlan9Servers()
2638 {
2639 for (auto it = m_plan9Servers.begin(); it != m_plan9Servers.end();)
2640 {
2641 const HRESULT result = it->second->IsRunning();
2642
2643 // If the server process was terminated (which can happen e.g. if the user logged out and
2644 // back in), attempting to make a COM call will return
2645 // HRESULT_FROM_WIN32(RPC_S_SERVER_UNAVAILABLE). For this and other errors, remove the
2646 // server from the list and mark DrvFs for that port uninitialized.
2647 // N.B. The call will return S_FALSE if the server is not running. That should never
2648 // happen since this service never calls Pause(), but in case it does that is also
2649 // treated as an error.
2650 if (result != S_OK)
2651 {
2652 if (it->first == LX_INIT_UTILITY_VM_PLAN9_DRVFS_ADMIN_PORT)
2653 {
2654 m_adminDrvfsToken.reset();
2655 }
2656 else
2657 {
2658 WI_ASSERT(it->first == LX_INIT_UTILITY_VM_PLAN9_DRVFS_PORT);
2659
2660 m_drvfsToken.reset();
2661 }
2662
2663 it = m_plan9Servers.erase(it);
2664 }
2665 else
2666 {
2667 ++it;
2668 }
2669 }
2670 }
2671
2672 void WslCoreVm::VirtioFsWorker(_In_ const wil::unique_socket& listenSocket)
2673 try
2674 {
2675 wsl::windows::common::wslutil::SetThreadDescription(L"VirtioFs - Worker");
2676
2677 io::MultiHandleWait io;
2678
2679 io.AddHandle(std::make_unique<io::AcceptHandle>(listenSocket.get(), false, [this, &io](wil::unique_socket&& socket) {
2680 auto channel = std::make_shared<wsl::shared::SocketChannel>(std::move(socket), "VirtioFs");
2681 auto buffer = std::make_shared<std::vector<gsl::byte>>();
2682 auto pendingBytes = std::make_shared<std::vector<gsl::byte>>();
2683
2684 io.AddHandle(
2685 std::make_unique<io::ReadSocketMessageHandle>(
2686 io::HandleWrapper(channel->Socket()),
2687 *buffer,
2688 *pendingBytes,
2689 [this, &io, channel, buffer, pendingBytes](const gsl::span<gsl::byte>& message) {
2690 if (message.empty())
2691 {
2692 return; // Channel closed, exit.
2693 }
2694
2695 THROW_HR_IF_MSG(
2696 E_UNEXPECTED, !pendingBytes->empty(), "Received message with additional bytes: %zu", pendingBytes->size());
2697
2698 try
2699 {
2700 auto response = ProcessVirtioFsRequest(message);
2701
2702 // Move the socket out of the channel into the WriteHandle so it is closed once the reply is sent.
2703 io.AddHandle(std::make_unique<io::WriteHandle>(channel->Release(), response), io::MultiHandleWait::IgnoreErrors);
2704 }
2705 CATCH_LOG();
2706 }),
2707 io::MultiHandleWait::IgnoreErrors);
2708 }));
2709
2710 io.AddHandle(std::make_unique<io::EventHandle>(m_terminatingEvent.get()), io::MultiHandleWait::CancelOnCompleted);
2711
2712 io.Run({});
2713 }
2714 CATCH_LOG()
2715
2716 std::vector<char> WslCoreVm::ProcessVirtioFsRequest(_In_ gsl::span<gsl::byte> Request)
2717 {
2718 const auto* header = gslhelpers::try_get_struct<MESSAGE_HEADER>(Request);
2719 THROW_HR_IF(E_UNEXPECTED, !header);
2720
2721 WSL_LOG("VirtiofsMessageRequest", TraceLoggingValue(header->PrettyPrint().c_str(), "Content"));
2722
2723 auto buildResponse = [header](const std::wstring& tag, const std::wstring& childName, const std::wstring& source, HRESULT result) {
2724 // Respond to the guest with the tag that should be used to mount the device.
2725 wsl::shared::MessageWriter<LX_INIT_ADD_VIRTIOFS_SHARE_RESPONSE_MESSAGE> response(LxInitMessageAddVirtioFsDeviceResponse);
2726 response->Result = SUCCEEDED(result) ? 0 : EINVAL; // TODO: Improved HRESULT -> errno mapping.
2727 response.WriteString(response->TagOffset, tag);
2728 response.WriteString(response->ChildNameOffset, childName);
2729 response.WriteString(response->SourceOffset, source);
2730
2731 // Echo the request's transaction id and mark the message as the first (and only) reply.
2732 response->Header.TransactionId = header->TransactionId;
2733 response->Header.TransactionStep = static_cast<unsigned int>(TRANSACTION_STEP::FIRST_REPLY);
2734
2735 WSL_LOG("VirtiofsMessageResponse", TraceLoggingValue(response->PrettyPrint().c_str(), "Content"));
2736
2737 const auto span = response.Span();
2738 return std::vector<char>(reinterpret_cast<const char*>(span.data()), reinterpret_cast<const char*>(span.data()) + span.size());
2739 };
2740
2741 if (header->MessageType == LxInitMessageAddVirtioFsDevice)
2742 {
2743 std::wstring tag;
2744 std::wstring childName;
2745 std::wstring source;
2746 const auto result = wil::ResultFromException([&]() {
2747 const auto* addShare = gslhelpers::try_get_struct<LX_INIT_ADD_VIRTIOFS_SHARE_MESSAGE>(Request);
2748 THROW_HR_IF(E_UNEXPECTED, !addShare);
2749
2750 const auto path = wsl::shared::string::FromSpan(Request, addShare->PathOffset);
2751 const auto pathWide = wsl::shared::string::MultiByteToWide(path);
2752 const auto options = wsl::shared::string::FromSpan(Request, addShare->OptionsOffset);
2753 const auto optionsWide = wsl::shared::string::MultiByteToWide(options);
2754
2755 // Acquire the lock and attempt to add the device.
2756 auto guestDeviceLock = m_guestDeviceLock.lock_exclusive();
2757 std::tie(tag, childName, source) = AddVirtioFsShare(addShare->Admin, pathWide.c_str(), optionsWide.c_str());
2758 });
2759
2760 return buildResponse(tag, childName, source, result);
2761 }
2762 else if (header->MessageType == LxInitMessageRemountVirtioFsDevice)
2763 {
2764 std::wstring newTag;
2765 std::wstring childName;
2766 std::wstring source;
2767 const auto result = wil::ResultFromException([&]() {
2768 const auto* remountShare = gslhelpers::try_get_struct<LX_INIT_REMOUNT_VIRTIOFS_SHARE_MESSAGE>(Request);
2769 THROW_HR_IF(E_UNEXPECTED, !remountShare);
2770
2771 const std::string tag = wsl::shared::string::FromSpan(Request, remountShare->TagOffset);
2772 const auto tagWide = wsl::shared::string::MultiByteToWide(tag);
2773 auto guestDeviceLock = m_guestDeviceLock.lock_exclusive();
2774 const auto foundShare = FindVirtioFsShare(tagWide.c_str(), !remountShare->Admin);
2775 THROW_HR_IF_MSG(E_UNEXPECTED, !foundShare.has_value(), "Unknown tag %ls", tagWide.c_str());
2776
2777 std::tie(newTag, childName, source) =
2778 AddVirtioFsShare(remountShare->Admin, foundShare->Path.c_str(), foundShare->OptionsString().c_str());
2779
2780 WI_ASSERT(source == foundShare->Path);
2781 });
2782
2783 return buildResponse(newTag, childName, source, result);
2784 }
2785 else
2786 {
2787 THROW_HR_MSG(E_UNEXPECTED, "Unexpected MessageType %d", header->MessageType);
2788 }
2789 }
2790
2791 std::string WslCoreVm::s_GetMountTargetName(_In_ PCWSTR Disk, _In_opt_ PCWSTR Name, _In_ int PartitionIndex)
2792 {
2793 // Derive the mount target from the disk and partition names.
2794 // The format is <Disk>p[partition]
2795 // For Example: PhysicalDisk1p2
2796 // If user has specified the name, ensure proper formatting and use it instead
2797 if (ARGUMENT_PRESENT(Name))
2798 {
2799 auto mountName = wsl::shared::string::WideToMultiByte(Name);
2800 THROW_HR_IF(
2801 WSL_E_VM_MODE_INVALID_MOUNT_NAME,
2802 mountName.empty() || mountName == "." || mountName == ".." || mountName.find('/') != std::string::npos);
2803 return mountName;
2804 }
2805
2806 std::string target{};
2807 auto mountName = wsl::shared::string::WideToMultiByte(Disk);
2808 std::copy_if(mountName.begin(), mountName.end(), std::back_inserter(target), &isalnum);
2809 if (PartitionIndex != 0)
2810 {
2811 target += std::format("p{}", PartitionIndex);
2812 }
2813
2814 return target;
2815 }
2816
2817 void CALLBACK WslCoreVm::s_OnExit(_In_ HCS_EVENT* Event, _In_opt_ void* Context)
2818 try
2819 {
2820 const auto utilityVm = static_cast<WslCoreVm*>(Context);
2821 if (Event->Type == HcsEventSystemCrashInitiated || Event->Type == HcsEventSystemCrashReport)
2822 {
2823 utilityVm->OnCrash(Event->EventData);
2824 }
2825 else if ((Event->Type == HcsEventSystemExited) || (Event->Type == HcsEventServiceDisconnect))
2826 {
2827 utilityVm->OnExit(Event->EventData);
2828 }
2829 }
2830 CATCH_LOG();
2831
2832 bool WslCoreVm::AttachedDisk::operator<(const AttachedDisk& other) const
2833 {
2834 if (Type < other.Type)
2835 {
2836 return true;
2837 }
2838
2839 if (Type == other.Type)
2840 {
2841 return _wcsicmp(Path.c_str(), other.Path.c_str()) < 0;
2842 }
2843
2844 return false;
2845 }
2846
2847 bool WslCoreVm::AttachedDisk::operator==(const AttachedDisk& other) const
2848 {
2849 return Type == other.Type && wsl::windows::common::string::IsPathComponentEqual(Path, other.Path);
2850 }
2851
2852 WslCoreVm::VirtioFsShare::VirtioFsShare(PCWSTR Path, PCWSTR Options, bool Admin) : Path(Path), Admin(Admin)
2853 {
2854 // Parse the options string into a map representing mount options to ensure that shares with functionally
2855 // identical options can share a single device.
2856 // For example: "uid=1000;gid=1000" and "gid=1000;uid=1000"
2857 auto optionsVector = wsl::shared::string::Split(std::wstring{Options}, L';');
2858 for (const auto& option : optionsVector)
2859 {
2860 std::wstring key;
2861 std::wstring value;
2862 const auto pos = option.find_first_of(L'=');
2863 if (pos == option.npos)
2864 {
2865 key = option;
2866 }
2867 else
2868 {
2869 key = option.substr(0, pos);
2870 value = option.substr(pos + 1);
2871 }
2872
2873 if (!key.empty())
2874 {
2875 this->Options.insert({std::move(key), std::move(value)});
2876 }
2877 }
2878
2879 if constexpr (wsl::shared::Debug)
2880 {
2881 const auto originalSet = std::set<std::wstring>(optionsVector.begin(), optionsVector.end());
2882 auto newVector = wsl::shared::string::Split(OptionsString(), L';');
2883 const auto newSet = std::set<std::wstring>(newVector.begin(), newVector.end());
2884 WI_ASSERT_MSG(originalSet == newSet, "mount options do not match");
2885 }
2886 }
2887
2888 std::wstring WslCoreVm::VirtioFsShare::OptionsString() const
2889 {
2890 std::wstring optionsString;
2891 for (const auto& option : Options)
2892 {
2893 if (!optionsString.empty())
2894 {
2895 optionsString += L';';
2896 }
2897
2898 optionsString += option.first;
2899 if (!option.second.empty())
2900 {
2901 optionsString += L'=';
2902 optionsString += option.second;
2903 }
2904 }
2905
2906 return optionsString;
2907 }
2908
2909 bool WslCoreVm::VirtioFsShare::operator<(const VirtioFsShare& other) const
2910 {
2911 return std::tie(Path, Options, Admin) < std::tie(other.Path, other.Options, other.Admin);
2912 }
2913
2914 bool WslCoreVm::VirtioFsShare::operator==(const VirtioFsShare& other) const
2915 {
2916 return Path == other.Path && Options == other.Options && Admin == other.Admin;
2917 }
2918
2919 void WslCoreVm::TraceLoggingRundown() const noexcept
2920 try
2921 {
2922 WSL_LOG(
2923 "WslCoreVm::Rundown",
2924 TraceLoggingValue("Machine Config"),
2925 TraceLoggingValue(m_machineId.c_str(), "machineId"),
2926 TraceLoggingValue(ToString(m_vmConfig.NetworkingMode), "networkingMode"));
2927
2928 if (m_networkingEngine)
2929 {
2930 m_networkingEngine->TraceLoggingRundown();
2931 }
2932 }
2933 CATCH_LOG()
2934
2935 void WslCoreVm::ValidateNetworkingMode()
2936 {
2937 using namespace wsl::core;
2938 using namespace wsl::windows::common;
2939
2940 ExecutionContext context(Context::ConfigureNetworking);
2941
2942 // Cache requested networking features to be logged via telemetry.
2943 const auto networkingModeRequested = m_vmConfig.NetworkingMode;
2944 auto firewallRequested = m_vmConfig.FirewallConfig.Enabled();
2945 auto dnsTunnelingRequested = m_vmConfig.EnableDnsTunneling;
2946
2947 // If Hyper-V firewall was requested, ensure it is supported by the OS.
2948 if (m_vmConfig.FirewallConfig.Enabled())
2949 {
2950 if (m_vmConfig.NetworkingMode == NetworkingMode::Mirrored || m_vmConfig.NetworkingMode == NetworkingMode::Nat)
2951 {
2952 if (!wsl::core::MirroredNetworking::IsHyperVFirewallSupported(m_vmConfig))
2953 {
2954 // Since hyper-V firewall is enabled by default, only show the warning if the user explicitly asked for it.
2955 if (m_vmConfig.FirewallConfigPresence == ConfigKeyPresence::Present)
2956 {
2957 EMIT_USER_WARNING(Localization::MessageHyperVFirewallNotSupported());
2958 }
2959
2960 m_vmConfig.FirewallConfig.reset();
2961 }
2962 }
2963 }
2964
2965 // If mirrored networking was requested, ensure IPv6 is not disabled on the host using registry,
2966 // as this is not supported by mirrored networking.
2967 // Note: Disabling IPv6 using Set-NetAdapterBinding is supported.
2968 if (m_vmConfig.NetworkingMode == NetworkingMode::Mirrored)
2969 {
2970 constexpr DWORD c_ipv6Disabled = 0xFF;
2971 DWORD disabledComponents = 0;
2972 wil::reg::get_value_dword_nothrow(
2973 HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters", L"DisabledComponents", &disabledComponents);
2974
2975 if (disabledComponents == c_ipv6Disabled)
2976 {
2977 m_vmConfig.NetworkingMode = NetworkingMode::Nat;
2978 EMIT_USER_WARNING(Localization::MessageMirroredNetworkingNotSupportedReason(
2979 Localization::MessageMirroredNetworkingNotSupportedIpv6Disabled()));
2980 }
2981 }
2982
2983 // If mirrored networking was requested, ensure it is supported by the OS and guest kernel.
2984 if (m_vmConfig.NetworkingMode == NetworkingMode::Mirrored)
2985 {
2986 if ((m_kernelVersion < std::make_tuple(5u, 10u, 0u)) || !m_seccompAvailable)
2987 {
2988 m_vmConfig.NetworkingMode = NetworkingMode::Nat;
2989 EMIT_USER_WARNING(Localization::MessageMirroredNetworkingNotSupportedReason(
2990 Localization::MessageMirroredNetworkingNotSupportedKernel()));
2991 }
2992 else if (!wsl::core::networking::IsFlowSteeringSupportedByHns() || !m_vmConfig.FirewallConfig.Enabled())
2993 {
2994 m_vmConfig.NetworkingMode = NetworkingMode::Nat;
2995 EMIT_USER_WARNING(Localization::MessageMirroredNetworkingNotSupportedReason(Localization::MessageMirroredNetworkingNotSupportedWindowsVersion(
2996 m_windowsVersion.BuildNumber, m_windowsVersion.UpdateBuildRevision)));
2997 }
2998 }
2999
3000 // Localhost relay is not supported in mirrored mode. Generate a warning if the user configures localhost relay
3001 // together with mirrored mode.
3002 // N.B. Mirrored mode already provides a way to communicate between Windows and Linux using localhost.
3003 if (m_vmConfig.NetworkingMode == NetworkingMode::Mirrored && m_vmConfig.LocalhostRelayConfigPresence == ConfigKeyPresence::Present)
3004 {
3005 EMIT_USER_WARNING(Localization::MessageLocalhostForwardingNotSupportedMirroredMode());
3006 }
3007
3008 // The DnsResolver support check still applies to Consomme because the host Consomme NAT uses the same Windows DNS APIs.
3009 if (m_vmConfig.EnableDnsTunneling && !IsDnsTunnelingSupported())
3010 {
3011 // Since DNS tunneling is enabled by default, only show the warning if the user explicitly asked for it.
3012 if (m_vmConfig.DnsTunnelingConfigPresence == ConfigKeyPresence::Present)
3013 {
3014 EMIT_USER_WARNING(Localization::MessageDnsTunnelingNotSupported());
3015 }
3016
3017 m_vmConfig.EnableDnsTunneling = false;
3018 }
3019
3020 // Gives information about the requested networking settings and whether they were enabled or not
3021 WSL_LOG_TELEMETRY(
3022 "WslCoreVmValidateNetworkingMode",
3023 PDT_ProductAndServicePerformance,
3024 TraceLoggingValue(m_runtimeId, "vmId"),
3025 TraceLoggingValue(ToString(networkingModeRequested), "networkingModeRequested"),
3026 TraceLoggingValue(ToString(m_vmConfig.NetworkingMode), "networkingMode"),
3027 TraceLoggingValue(m_vmConfig.NetworkingModePresence == ConfigKeyPresence::Present, "networkingModePresent"),
3028 TraceLoggingValue(firewallRequested, "firewallRequested"),
3029 TraceLoggingValue(m_vmConfig.FirewallConfig.Enabled(), "firewall"),
3030 TraceLoggingValue(dnsTunnelingRequested, "dnsTunnelingRequested"),
3031 TraceLoggingValue(m_vmConfig.DnsTunnelingConfigPresence == ConfigKeyPresence::Present, "dnsTunnelingConfigPresent"),
3032 TraceLoggingValue(m_vmConfig.EnableDnsTunneling, "dnsTunneling"));
3033 }