| 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 | } |