master
cpp 3,166 lines 88.9 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 Common.cpp
8
9 Abstract:
10
11 This contains common used definitions used for testing.
12
13 --*/
14
15 // includes
16
17 #include "precomp.h"
18 #include "Common.h"
19 #include "LxssDynamicFunction.h"
20 #include "WslCoreNetworkEndpointSettings.h"
21 #include <tlhelp32.h>
22 #include <werapi.h>
23 #include <Dbghelp.h>
24 #include <winsafer.h>
25
26 using namespace WEX::Logging;
27 using namespace WEX::Common;
28 using namespace WEX::TestExecution;
29
30 MODULE_SETUP(ModuleSetup);
31 MODULE_CLEANUP(ModuleCleanup);
32
33 // Defines
34 #define LXSS_LOGS_DIRECTORY L"logs"
35 #define LXSS_TEST_DIRECTORY L"\\data\\test"
36 #define LXSS_TEST_LOG_SEPARATOR_CHAR L"&"
37 #define LXSS_DEFAULT_TIMEOUT (15 * 1000)
38
39 //
40 // The instance test timeout should roughly be the maximum time to start an
41 // instance.
42 //
43
44 #define LXSS_INSTANCE_TEST_TIMEOUT (3 * 1000)
45
46 //
47 // The watchdog timeout is set to 3 hours.
48 //
49
50 #define LXSS_WATCHDOG_TIMEOUT (3 * 60 * 60 * 1000)
51 #define LXSS_WATCHDOG_TIMEOUT_WINDOW 1000
52
53 //
54 // Global variables
55 //
56
57 static HANDLE g_OriginalStdout;
58 static HANDLE g_OriginalStderr;
59 static BOOL g_RelogEverything = TRUE;
60 static bool g_LogDmesgAfterEachTest = false;
61 static PTP_TIMER g_WatchdogTimer;
62
63 static BOOL g_VmMode;
64 static std::wstring g_originalConfig;
65 static std::wstring g_originalDefaultDistro;
66 std::wstring g_dumpFolder;
67 std::optional<std::wstring> g_dumpToolPath;
68 static bool g_enableWerReport = false;
69 std::wstring g_pipelineBuildId;
70 std::wstring g_testDistroPath;
71 std::wstring g_testDataPath;
72 bool g_fastTestRun = false; // True when test.bat was invoked with -f
73 static wil::unique_mta_usage_cookie g_mtaCookie;
74
75 std::pair<wil::unique_handle, wil::unique_handle> CreateSubprocessPipe(bool inheritRead, bool inheritWrite, DWORD bufferSize, _In_opt_ SECURITY_ATTRIBUTES* sa)
76 {
77 wil::unique_handle read;
78 wil::unique_handle write;
79 THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&read, &write, sa, bufferSize));
80
81 if (inheritWrite)
82 {
83 THROW_IF_WIN32_BOOL_FALSE(SetHandleInformation(write.get(), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT));
84 }
85
86 if (inheritRead)
87 {
88 THROW_IF_WIN32_BOOL_FALSE(SetHandleInformation(read.get(), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT));
89 }
90
91 return {std::move(read), std::move(write)};
92 }
93
94 // LxsstuLaunchWsl
95
96 DWORD
97 LxsstuLaunchWsl(_In_opt_ LPCWSTR Arguments, _In_opt_ HANDLE StandardInput, _In_opt_ HANDLE StandardOutput, _In_opt_ HANDLE StandardError, _In_opt_ HANDLE Token, _In_ DWORD Flags)
98 {
99 // Launch wsl.exe to handle the operation.
100 auto CommandLine = LxssGenerateWslCommandLine(Arguments);
101
102 return LxsstuRunCommand(CommandLine.data(), StandardInput, StandardOutput, StandardError, Token, Flags);
103 }
104
105 DWORD
106 LxsstuLaunchWsl(_In_opt_ const std::wstring& Arguments, _In_opt_ HANDLE StandardInput, _In_opt_ HANDLE StandardOutput, _In_opt_ HANDLE StandardError, _In_opt_ HANDLE Token)
107 {
108 return LxsstuLaunchWsl(Arguments.data(), StandardInput, StandardOutput, StandardError, Token);
109 }
110
111 // LxsstuLaunchWslAndCaptureOutput
112
113 std::pair<std::wstring, std::wstring> LxsstuLaunchWslAndCaptureOutput(
114 _In_ LPCWSTR Cmd, _In_ int ExpectedExitCode, _In_opt_ HANDLE StandardInput, _In_opt_ HANDLE Token, _In_ DWORD Flags, _In_ LPCWSTR EntryPoint)
115
116 /*++
117
118 Routine Description:
119
120 Run a WSL command and capture its output.
121
122 Arguments:
123
124 Cmd - The command line to run.
125
126 ExpectedExitCode - The expected exit code from the child process.
127
128 StandardInput - Handle to the process's standard input
129
130 Return Value:
131
132 A pair of strings with stdout and stderr output.
133
134 --*/
135
136 {
137
138 auto CommandLine = LxssGenerateWslCommandLine(Cmd, EntryPoint);
139 return LxsstuLaunchCommandAndCaptureOutput(CommandLine.data(), ExpectedExitCode, StandardInput, Token, Flags);
140 }
141
142 // LxssGenerateWslCommandLine
143
144 std::wstring LxssGenerateWslCommandLine(_In_opt_ LPCWSTR Arguments, _In_ LPCWSTR EntryPoint)
145 {
146 std::wstring CommandLine;
147 THROW_IF_FAILED(wil::GetSystemDirectoryW(CommandLine));
148
149 CommandLine += L"\\";
150 CommandLine += EntryPoint;
151 if (ARGUMENT_PRESENT(Arguments))
152 {
153 CommandLine += L" ";
154 CommandLine += Arguments;
155 }
156
157 return CommandLine;
158 }
159
160 // LxsstuLaunchWslAndCaptureOutput
161
162 std::pair<std::wstring, std::wstring> LxsstuLaunchWslAndCaptureOutput(
163 _In_ const std::wstring& Cmd, _In_ int ExpectedExitCode, _In_opt_ HANDLE StandardInput, _In_opt_ HANDLE Token, _In_ DWORD Flags, _In_ LPCWSTR EntryPoint)
164
165 /*++
166
167 Routine Description:
168
169 Run a wsl command and return its output.
170
171 Arguments:
172
173 Cmd - Supplies the wsl command to run.
174
175 ExpectedExitCode - The expected exit code from the child process.
176
177 StandardInput - Handle to the process's standard input
178
179 Return Value:
180
181 The command's stdout and stderr output.
182
183 --*/
184
185 {
186 return LxsstuLaunchWslAndCaptureOutput(Cmd.data(), ExpectedExitCode, StandardInput, Token, Flags, EntryPoint);
187 }
188
189 std::pair<std::wstring, std::wstring> LxsstuLaunchCommandAndCaptureOutput(_In_ LPWSTR Cmd, _In_ LPCSTR StandardInput, _In_opt_ HANDLE Token, _In_ DWORD Flags)
190 {
191 const auto inputSize = static_cast<DWORD>(strlen(StandardInput));
192 auto [read, write] = CreateSubprocessPipe(true, false, inputSize);
193 THROW_IF_WIN32_BOOL_FALSE(WriteFile(write.get(), StandardInput, inputSize, nullptr, nullptr));
194 write.reset();
195
196 return LxsstuLaunchCommandAndCaptureOutput(Cmd, 0, read.get(), Token, Flags);
197 }
198
199 // LxsstuLaunchCommandAndCaptureOutputWithResult
200
201 std::tuple<std::wstring, std::wstring, int> LxsstuLaunchCommandAndCaptureOutputWithResult(
202 _In_ LPWSTR Cmd, _In_opt_ HANDLE StandardInput, _In_opt_ HANDLE Token, _In_ DWORD Flags)
203
204 /*++
205
206 Routine Description:
207
208 Run a command and capture its output.
209
210 Arguments:
211
212 Cmd - The command line to run.
213
214 Return Value:
215
216 A pair of strings with stdout and stderr output.
217
218 --*/
219
220 {
221
222 wsl::windows::common::SubProcess process(nullptr, Cmd);
223 process.SetStdHandles(StandardInput, nullptr, nullptr);
224 process.SetToken(Token);
225 process.SetFlags(Flags);
226
227 auto result = process.RunAndCaptureOutput();
228
229 return {result.Stdout, result.Stderr, result.ExitCode};
230 }
231
232 // LxsstuLaunchCommandAndCaptureOutput
233
234 std::pair<std::wstring, std::wstring> LxsstuLaunchCommandAndCaptureOutput(
235 _In_ LPWSTR Cmd, _In_ int ExpectedExitCode, _In_opt_ HANDLE StandardInput, _In_opt_ HANDLE Token, _In_ DWORD Flags)
236
237 /*++
238
239 Routine Description:
240
241 Run a command and capture its output.
242
243 Arguments:
244
245 Cmd - The command line to run.
246
247 Return Value:
248
249 A pair of strings with stdout and stderr output.
250
251 --*/
252
253 {
254 auto [Out, Err, ExitCode] = LxsstuLaunchCommandAndCaptureOutputWithResult(Cmd, StandardInput, Token, Flags);
255 if (ExitCode != ExpectedExitCode)
256 {
257 THROW_HR_MSG(
258 E_UNEXPECTED,
259 "Command \"%ls\" "
260 "returned unexpected exit code (%lu != %i). "
261 "Stdout: '%ls' "
262 "Stderr: '%ls'",
263 Cmd,
264 ExitCode,
265 ExpectedExitCode,
266 Out.c_str(),
267 Err.c_str());
268 }
269
270 return std::make_pair(Out, Err);
271 }
272
273 // LxsstuRunCommand
274
275 DWORD
276 LxsstuRunCommand(_In_ LPWSTR Command, _In_opt_ HANDLE StandardInput, _In_opt_ HANDLE StandardOutput, _In_opt_ HANDLE StandardError, _In_opt_ HANDLE Token, _In_ DWORD Flags)
277 {
278 const auto Process = LxsstuStartProcess(Command, StandardInput, StandardOutput, StandardError, Token, Flags);
279 return wsl::windows::common::SubProcess::GetExitCode(Process.get());
280 }
281
282 // LxsstuStartProcess
283
284 wil::unique_handle LxsstuStartProcess(
285 _In_ LPWSTR Command, _In_opt_ HANDLE StandardInput, _In_opt_ HANDLE StandardOutput, _In_opt_ HANDLE StandardError, _In_opt_ HANDLE Token, _In_ DWORD Flags)
286 {
287 wsl::windows::common::SubProcess process(nullptr, Command);
288
289 process.SetStdHandles(
290 ARGUMENT_PRESENT(StandardInput) ? StandardInput : GetStdHandle(STD_INPUT_HANDLE),
291 ARGUMENT_PRESENT(StandardOutput) ? StandardOutput : GetStdHandle(STD_OUTPUT_HANDLE),
292 ARGUMENT_PRESENT(StandardError) ? StandardError : GetStdHandle(STD_ERROR_HANDLE));
293
294 process.SetToken(Token);
295 process.SetFlags(Flags);
296
297 return process.Start();
298 }
299
300 // FileFromHandle
301
302 wil::unique_file FileFromHandle(_Inout_ wil::unique_handle& Handle, _In_ const char* Mode)
303
304 /*++
305
306 Routine Description:
307
308 Create a FILE from a handle.
309
310 Arguments:
311 Handle - The handle to create the FILE from.
312
313 Mode - The mode to create the FILE with.
314
315 Return Value:
316
317 The created FILE.
318
319 --*/
320
321 {
322
323 using UniqueFd = wil::unique_any<int, decltype(_close), _close, wil::details::pointer_access_all, int, int, -1>;
324
325 UniqueFd Fd(_open_osfhandle(reinterpret_cast<intptr_t>(Handle.get()), 0));
326 if (Fd.get() < 0)
327 {
328 THROW_LAST_ERROR_MSG("_open_osfhandle failed");
329 }
330
331 Handle.release();
332
333 wil::unique_file File(_fdopen(Fd.get(), Mode));
334 VERIFY_IS_NOT_NULL(File.get());
335 Fd.release();
336
337 return File;
338 }
339
340 // LxsstuInitialize
341
342 BOOL LxsstuInitialize(__in BOOLEAN RunInstanceTests)
343 {
344 wil::unique_hkey Key;
345 LRESULT Result;
346 BOOL Success;
347 DWORD Value;
348
349 Success = FALSE;
350
351 THROW_IF_FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED));
352
353 //
354 // Don't fail if CoInitializeSecurity has already been called.
355 //
356
357 const auto Hr = CoInitializeSecurity(
358 nullptr, -1, nullptr, nullptr, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_STATIC_CLOAKING, 0);
359
360 THROW_HR_IF(Hr, FAILED(Hr) && Hr != RPC_E_TOO_LATE);
361
362 WSADATA Data;
363 THROW_IF_WIN32_ERROR(WSAStartup(MAKEWORD(2, 2), &Data));
364
365 VERIFY_IS_TRUE(SetEnvironmentVariableW(L"WSL_UTF8", L"1"));
366
367 if (LxsstuVmMode() == FALSE)
368 {
369 Result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, LXSS_REGISTRY_PATH, 0, KEY_ALL_ACCESS, &Key);
370
371 if (Result != ERROR_SUCCESS)
372 {
373 LogError("RegOpenKeyEx %s failed with %Id", LXSS_REGISTRY_PATH, Result);
374
375 goto InitializeEnd;
376 }
377
378 //
379 // Set the error level to critical so the driver will not break into kd
380 // while the test is running.
381 //
382
383 Value = LxErrorLevel_Critical;
384 Result = RegSetValueEx(Key.get(), LX_QUERY_REGISTRY_ERROR_LEVEL_SUBKEY, 0, REG_DWORD, (const PBYTE)&Value, sizeof(DWORD));
385
386 if (Result != ERROR_SUCCESS)
387 {
388 LogError("RegSetValueEx %s failed with %Id", LX_QUERY_REGISTRY_ERROR_LEVEL_SUBKEY, Result);
389
390 goto InitializeEnd;
391 }
392
393 //
394 // Disable breaking on syscall failures.
395 //
396
397 Value = FALSE;
398 Result = RegSetValueEx(Key.get(), LX_QUERY_REGISTRY_BREAK_ON_SYSCALL_FAILURE_SUBKEY, 0, REG_DWORD, (const PBYTE)&Value, sizeof(DWORD));
399
400 if (Result != ERROR_SUCCESS)
401 {
402 LogError("RegSetValueEx %s failed with %Id", LX_QUERY_REGISTRY_BREAK_ON_SYSCALL_FAILURE_SUBKEY, Result);
403
404 goto InitializeEnd;
405 }
406
407 //
408 // Enable lxbus root access.
409 //
410
411 Value = TRUE;
412 Result = RegSetValueEx(Key.get(), LX_QUERY_REGISTRY_ROOT_LXBUS_ACCESS, 0, REG_DWORD, (const PBYTE)&Value, sizeof(DWORD));
413
414 if (Result != ERROR_SUCCESS)
415 {
416 LogError("RegSetValueEx %s failed with %Id", LX_QUERY_REGISTRY_ROOT_LXBUS_ACCESS, Result);
417
418 goto InitializeEnd;
419 }
420
421 //
422 // Enable mounting DrvFs with case=force.
423 //
424
425 Value = TRUE;
426 Result = RegSetValueEx(Key.get(), LX_QUERY_REGISTRY_DRVFS_ALLOW_FORCE_CASE_SENSITIVITY, 0, REG_DWORD, (const PBYTE)&Value, sizeof(DWORD));
427
428 if (Result != ERROR_SUCCESS)
429 {
430 LogError("RegSetValueEx %s failed with %Id", LX_QUERY_REGISTRY_DRVFS_ALLOW_FORCE_CASE_SENSITIVITY, Result);
431
432 goto InitializeEnd;
433 }
434 }
435 else
436 {
437 const auto LogDirectory = LxsstuGetTestDirectory() + L"\\log";
438 wil::CreateDirectoryDeep(LogDirectory.c_str());
439 }
440
441 //
442 // Run the instance tests.
443 //
444
445 if (RunInstanceTests != FALSE)
446 {
447 VERIFY_NO_THROW(LxsstuInstanceTests());
448 }
449
450 Success = TRUE;
451
452 InitializeEnd:
453
454 return Success;
455 }
456
457 // LxxstuVmMode
458
459 BOOL LxsstuVmMode(VOID)
460
461 /*++
462
463 Routine Description:
464
465 Queries if the tests are being run in VM mode.
466
467 Arguments:
468
469 None.
470
471 Return Value:
472
473 TRUE if the tests are running in VM mode, FALSE otherwise.
474
475 --*/
476
477 {
478 return g_VmMode;
479 }
480
481 // LxsstuLaunchPowershellAndCaptureOutput
482
483 std::pair<std::wstring, std::wstring> LxsstuLaunchPowershellAndCaptureOutput(_In_ const std::wstring& Cmd, _In_ int ExpectedExitCode)
484
485 /*++
486
487 Routine Description:
488
489 Run a powershell command and return its output.
490
491 Arguments:
492
493 Cmd - Supplies the powershell command to run.
494
495 ExpectedExitCode - The expected exit code from the child process.
496
497 Return Value:
498 s
499 The command's stdout and stderr output.
500
501 --*/
502
503 {
504 auto CommandLine = L"Powershell -NoProfile -Command \"" + Cmd + L"\"";
505 LogInfo("Running the command: %ls\n", CommandLine.c_str());
506 return LxsstuLaunchCommandAndCaptureOutput(CommandLine.data(), ExpectedExitCode);
507 }
508
509 // LxsstuUninitialize
510
511 VOID LxsstuUninitialize(__in BOOLEAN RunInstanceTests)
512 {
513
514 wil::unique_hkey Key;
515 LRESULT Result;
516
517 //
518 // Run the instance tests again to make sure that the instance can be
519 // started and stopped (i.e. no leaked fs references).
520 //
521
522 if (RunInstanceTests != FALSE)
523 {
524 VERIFY_NO_THROW(LxsstuInstanceTests());
525 }
526
527 if (LxsstuVmMode() == FALSE)
528 {
529
530 //
531 // Delete registry subkeys that were set by the test framework.
532 //
533
534 Result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, LXSS_REGISTRY_PATH, 0, KEY_ALL_ACCESS, &Key);
535
536 if (Result != ERROR_SUCCESS)
537 {
538 LogInfo("RegOpenKeyEx failed with %Id", Result);
539 }
540 else
541 {
542 auto DeleteKey = [&](LPCWSTR KeyName) {
543 Result = RegDeleteKeyValue(Key.get(), nullptr, KeyName);
544 if (Result != ERROR_SUCCESS)
545 {
546 LogInfo("RegDeleteKeyValue %s failed with %Id", KeyName, Result);
547 }
548 };
549
550 DeleteKey(LX_QUERY_REGISTRY_ERROR_LEVEL_SUBKEY);
551 DeleteKey(LX_QUERY_REGISTRY_BREAK_ON_SYSCALL_FAILURE_SUBKEY);
552 DeleteKey(LX_QUERY_REGISTRY_ROOT_LXBUS_ACCESS);
553 DeleteKey(LX_QUERY_REGISTRY_DRVFS_ALLOW_FORCE_CASE_SENSITIVITY);
554 }
555 }
556
557 VERIFY_IS_TRUE(SetEnvironmentVariableW(L"WSL_UTF8", nullptr));
558
559 WSACleanup();
560
561 //
562 // Clear the winrt cache in case LookupLiftedPackage() is called again after another CoInitialize().
563 //
564
565 winrt::clear_factory_cache();
566
567 CoUninitialize();
568
569 return;
570 }
571
572 // LxssLogKernelOutput
573
574 void LxssLogKernelOutput(VOID)
575
576 /*++
577
578 Routine Description:
579
580 Write the kernel output in the test logs.
581
582 Arguments:
583 None.
584
585 Return Value:
586
587 None.
588
589 --*/
590
591 {
592 if (!g_LogDmesgAfterEachTest)
593 {
594 return;
595 }
596
597 //
598 // dmesg -c isn't implemented on WSL1
599 //
600
601 const auto cmd = LxsstuVmMode() ? L"dmesg -c" : L"dmesg";
602 const auto Output = LxsstuLaunchWslAndCaptureOutput(cmd);
603 LogInfo("Kernel logs: '%ls'", Output.first.c_str());
604 }
605
606 // LxsstuGetTestDirectory
607
608 std::wstring LxsstuGetTestDirectory(VOID)
609
610 /*++
611
612 Description:
613
614 This routine gets the test directory.
615
616 Parameters:
617
618 None.
619
620 Return:
621
622 The test directory.
623
624 --*/
625
626 {
627
628 std::wstring TestDirectory = LxsstuGetLxssDirectory();
629 TestDirectory += L"\\" LXSS_ROOTFS_DIRECTORY LXSS_TEST_DIRECTORY;
630 return TestDirectory;
631 }
632
633 // LxsstuGetLxssDirectory
634
635 std::wstring LxsstuGetLxssDirectory(VOID)
636
637 /*++
638
639 Description:
640
641 This routine gets the lxss directory.
642
643 Parameters:
644
645 None.
646
647 Return:
648
649 The lxss directory.
650
651 --*/
652
653 {
654
655 const wil::unique_hkey LxssKey = wsl::windows::common::registry::OpenLxssUserKey();
656 const std::wstring Default = wsl::windows::common::registry::ReadString(LxssKey.get(), nullptr, L"DefaultDistribution", nullptr);
657
658 std::wstring BasePath = wsl::windows::common::registry::ReadString(LxssKey.get(), Default.c_str(), L"BasePath", nullptr);
659
660 return BasePath;
661 }
662
663 void CaptureLiveDump()
664 {
665 auto PrivilegeState = wsl::windows::common::security::AcquirePrivilege(SE_DEBUG_NAME);
666
667 const std::wstring targetFile = g_dumpFolder + L"\\livedump.dmp";
668 LogInfo("Writing livedump in: %ls", targetFile.c_str());
669
670 wsl::windows::common::SubProcess dumpProcess{nullptr, std::format(L"{} \"{}\"", g_dumpToolPath->c_str(), targetFile.c_str()).c_str()};
671 const auto exitCode = dumpProcess.Run();
672 if (exitCode != 0)
673 {
674 LogError("Failed to capture livedump. ExitCode=%lu", exitCode);
675 return;
676 }
677
678 LogInfo("Dump size: %llu", std::filesystem::file_size(targetFile));
679
680 // Try to compress the dump.
681 std::wstring command = L"Powershell.exe -NoProfile -ExecutionPolicy Bypass -Command \"Compress-Archive -Force -Path '" +
682 targetFile + L"' -DestinationPath '" + targetFile + L".zip'\"";
683 if (LxsstuRunCommand(command.data()) != 0)
684 {
685 // Note: powershell will fail to create the .zip if the dump is bigger than 2GB with:
686 // Exception calling "Write" with "3" argument(s): "Stream was too long."
687 LogError("Failed to compress live dump");
688 }
689 else
690 {
691 THROW_IF_WIN32_BOOL_FALSE(DeleteFile(targetFile.c_str()));
692 }
693 }
694
695 DEFINE_ENUM_FLAG_OPERATORS(MINIDUMP_TYPE);
696
697 DWORD FindThreadInProcess(DWORD Pid)
698 {
699 const wil::unique_handle Threads{CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)};
700
701 THREADENTRY32 ThreadInfo{};
702 ThreadInfo.dwSize = sizeof(ThreadInfo);
703 for (auto result = Thread32First(Threads.get(), &ThreadInfo); result; result = Thread32Next(Threads.get(), &ThreadInfo))
704 {
705 if (ThreadInfo.th32OwnerProcessID == Pid)
706 {
707 return ThreadInfo.th32ThreadID;
708 }
709 }
710
711 THROW_HR(HRESULT_FROM_WIN32(STATUS_NOT_FOUND));
712 }
713
714 PVOID GetModuleAddressInProcess(HANDLE Process, const std::wstring& Module)
715 {
716 // From: https://learn.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumprocessmodulesex
717 // Do not call CloseHandle on any of the handles returned by this function. The information comes from a snapshot, so there are no resources to be freed.
718
719 std::vector<HMODULE> Modules;
720 DWORD RequiredSize{};
721 bool Result{};
722 do
723 {
724 Modules.resize(RequiredSize / sizeof(HMODULE));
725 Result = EnumProcessModulesEx(Process, Modules.data(), static_cast<DWORD>(Modules.size() * sizeof(HMODULE)), &RequiredSize, LIST_MODULES_ALL);
726 } while (Result && RequiredSize / sizeof(HMODULE) > Modules.size());
727
728 for (const auto& e : Modules)
729 {
730 std::filesystem::path modulePath = wil::GetModuleFileNameExW<std::wstring>(Process, e);
731
732 if (wsl::windows::common::string::IsPathComponentEqual(modulePath.filename().native(), Module))
733 {
734 MODULEINFO Info{};
735 THROW_IF_WIN32_BOOL_FALSE(GetModuleInformation(Process, e, &Info, sizeof(Info)));
736
737 return Info.lpBaseOfDll;
738 }
739 }
740
741 THROW_HR(HRESULT_FROM_WIN32(STATUS_NOT_FOUND));
742 }
743
744 void CreateCrashReport(HANDLE Process, LPCWSTR ProcessName, DWORD Pid, std::wstring const& EventName)
745 {
746 using unique_hreport = wil::unique_any<HREPORT, decltype(WerReportCloseHandle), WerReportCloseHandle>;
747
748 auto setProperty = [](LPWSTR Target, const std::wstring& Value, size_t BufferSize) {
749 wcsncpy(Target, Value.c_str(), std::min(BufferSize - 1, Value.size()));
750 };
751
752 WER_REPORT_INFORMATION Info{};
753 Info.dwSize = sizeof(Info);
754 Info.hProcess = Process;
755
756 setProperty(Info.wzDescription, EventName, ARRAYSIZE(Info.wzDescription));
757 setProperty(Info.wzApplicationName, ProcessName, ARRAYSIZE(Info.wzApplicationName));
758 setProperty(Info.wzApplicationPath, wil::GetModuleFileNameExW<std::wstring>(Process, nullptr), ARRAYSIZE(Info.wzApplicationPath));
759
760 unique_hreport Report;
761 THROW_IF_FAILED(WerReportCreate(EventName.c_str(), WerReportApplicationCrash, &Info, &Report));
762
763 const std::wstring DumpPath = g_dumpFolder + L"\\" + ProcessName + L"." + std::to_wstring(Pid) + L".hdmp";
764 wil::unique_hfile DumpFile{CreateFileW(DumpPath.c_str(), GENERIC_ALL, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)};
765 THROW_LAST_ERROR_IF(!DumpFile);
766
767 std::optional<MINIDUMP_EXCEPTION_INFORMATION> ExceptionInfo;
768 EXCEPTION_RECORD Record{};
769 EXCEPTION_POINTERS Pointers{};
770
771 // To get access to the dumps in AzureWatson, the exception address needs to point to a module
772 // that we own. To do that, load the main module and point the exception to its entrypoint.
773 try
774 {
775 Record.ExceptionAddress = GetModuleAddressInProcess(Process, ProcessName);
776 Record.ExceptionCode = EXCEPTION_BREAKPOINT;
777 Pointers.ExceptionRecord = &Record;
778
779 ExceptionInfo.emplace();
780 ExceptionInfo->ExceptionPointers = &Pointers;
781 ExceptionInfo->ThreadId = FindThreadInProcess(Pid);
782 }
783 catch (...)
784 {
785 LogError("Failed to find module address / thread id for %ls, 0x%x", ProcessName, wil::ResultFromCaughtException());
786 }
787
788 THROW_IF_WIN32_BOOL_FALSE(MiniDumpWriteDump(
789 Process,
790 Pid,
791 DumpFile.get(),
792 MiniDumpWithDataSegs | MiniDumpWithProcessThreadData | MiniDumpWithHandleData | MiniDumpWithPrivateReadWriteMemory |
793 MiniDumpWithUnloadedModules | MiniDumpWithFullMemoryInfo | MiniDumpWithThreadInfo | MiniDumpWithTokenInformation |
794 MiniDumpWithPrivateWriteCopyMemory | MiniDumpWithCodeSegs,
795 ExceptionInfo.has_value() ? &ExceptionInfo.value() : nullptr,
796 nullptr,
797 nullptr));
798
799 DumpFile.reset();
800
801 THROW_IF_FAILED(WerReportAddFile(Report.get(), DumpPath.c_str(), WerFileTypeHeapdump, 0));
802
803 WER_SUBMIT_RESULT SubmitResult{};
804 const auto Result = WerReportSubmit(
805 Report.get(),
806 WerConsentApproved,
807 WER_SUBMIT_ADD_REGISTERED_DATA | WER_SUBMIT_NO_CLOSE_UI | WER_SUBMIT_BYPASS_DATA_THROTTLING | WER_SUBMIT_REPORT_MACHINE_ID | WER_SUBMIT_QUEUE,
808 &SubmitResult);
809
810 LogInfo("WerReportSubmit() returned 0x%x, SubmitResult = %i, EventName = %ls", Result, SubmitResult, EventName.c_str());
811 }
812
813 void CreateProcessCrashReport(DWORD Pid, LPCWSTR ImageName, LPCWSTR EventName)
814 {
815 try
816 {
817 LogInfo("Opening process %ls, Pid %lu", ImageName, Pid);
818 const wil::unique_handle Process(OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid));
819 THROW_LAST_ERROR_IF_NULL(Process);
820
821 CreateCrashReport(Process.get(), ImageName, Pid, EventName);
822 }
823 catch (...)
824 {
825 LogError("Failed to create crash report for process %ls (%lu), %lu", ImageName, Pid, wil::ResultFromCaughtException());
826 }
827 }
828
829 void CreateWerReports()
830 {
831 static const std::set<std::wstring, wsl::shared::string::CaseInsensitiveCompare> WslProcesses{
832 L"wsl.exe",
833 L"wslhost.exe",
834 L"wslrelay.exe",
835 L"wslservice.exe",
836 L"wslg.exe",
837 L"vmcompute.exe",
838 L"vmwp.exe",
839 L"wslcsession.exe",
840 L"wslc.exe"};
841
842 auto PrivilegeState = wsl::windows::common::security::AcquirePrivilege(SE_DEBUG_NAME);
843 const std::wstring EventName = L"WslTestHang-" + g_pipelineBuildId;
844
845 LogInfo("Dumps here: https://azurewatson.microsoft.com/?EventType=%s", EventName.c_str());
846
847 // Start by capturing the test process, since collect dmesg changes the state of the UVM.
848 try
849 {
850 CreateProcessCrashReport(GetCurrentProcessId(), L"te.processhost.exe", EventName.c_str());
851 }
852 CATCH_LOG();
853
854 PROCESSENTRY32 PE32;
855 PE32.dwSize = sizeof(PE32);
856 const wil::unique_handle ProcessSnapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0));
857 THROW_LAST_ERROR_IF(ProcessSnapshot.get() == INVALID_HANDLE_VALUE);
858
859 try
860 {
861 if (Process32First(ProcessSnapshot.get(), &PE32))
862 {
863 do
864 {
865 if (WslProcesses.find(std::wstring(PE32.szExeFile)) == WslProcesses.end())
866 {
867 continue;
868 }
869
870 try
871 {
872 CreateProcessCrashReport(PE32.th32ProcessID, PE32.szExeFile, EventName.c_str());
873 }
874 CATCH_LOG();
875
876 } while (Process32Next(ProcessSnapshot.get(), &PE32));
877 }
878 THROW_LAST_ERROR_IF(GetLastError() != ERROR_NO_MORE_FILES);
879 }
880 CATCH_LOG();
881
882 // Also capture an HNS dump. Since the process name is svchost.exe, find its pid from its service.
883 const wil::unique_schandle manager{OpenSCManager(nullptr, nullptr, SC_MANAGER_CONNECT)};
884 THROW_LAST_ERROR_IF_NULL(manager);
885
886 const wil::unique_schandle service{OpenService(manager.get(), L"HNS", SERVICE_QUERY_STATUS)};
887 THROW_LAST_ERROR_IF_NULL(service);
888
889 auto [_, pid] = GetServiceState(service.get());
890 CreateProcessCrashReport(pid, L"svchost.exe", EventName.c_str());
891 }
892
893 void DumpGuestProcesses()
894 {
895 constexpr auto dumpScript =
896 R"(
897 set -ue
898
899 dmesg
900
901 # Try to install gdb
902 tdnf install -y gdb || true
903
904 declare -a pids_to_dump
905
906 for proc in /proc/[0-9]*; do
907 read -a stats < "$proc/stat" # Skip kernel threads to make the output easier to read
908 flags=${stats[8]}
909
910 if (( ("$flags" & 0x00200000) == 0x00200000 )); then
911 continue
912 fi
913
914 pid=$(basename "$proc")
915
916 pids_to_dump+=("$pid")
917 parent=$(ps -o ppid= -p "$pid")
918
919 echo -e "\nProcess: $pid (parent: $parent) "
920 echo -en "cmd: "
921 cat "/proc/$pid/cmdline" || true
922 echo -e "\nstat: "
923 cat "/proc/$pid/stat" || true
924
925 for tid in $(ls "/proc/$pid/task" || true); do
926 echo -n "tid: $tid - "
927 cat "/proc/$pid/task/$tid/comm" || true
928 cat "/proc/$pid/task/$tid/stack" || true
929 done
930
931 echo "fds: "
932 ls -la "/proc/$pid/fd" || true
933 done
934
935 for pid in "${pids_to_dump[@]}" ; do
936 name=$(ps -p "$pid" -o comm=)
937 if [[ "$name" =~ ^(bash|login)$ ]]; then
938 echo "Skipping dump for process: $name"
939 continue
940 fi
941
942 echo "Dumping process: $name ($pid) "
943 if gcore -a -o core "$pid" ; then
944 if ! /wsl-capture-crash 0 "$name" "$pid" 0 < "core.$pid" ; then
945 echo "Failed to dump process $pid"
946 fi
947
948 rm "core.$pid"
949 fi
950 done
951
952 echo "hvsockets: "
953 ss -lap --vsock
954
955 echo "meminfo: "
956 cat /proc/meminfo
957
958 poweroff -f
959 )";
960
961 const std::wstring filePath = g_dumpFolder + L"\\guest-state.txt";
962 LogInfo("Dumping guest processes in: %ls", filePath.c_str());
963
964 const wil::unique_hfile outputFile{CreateFileW(
965 filePath.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)};
966
967 THROW_LAST_ERROR_IF(!outputFile);
968 THROW_IF_WIN32_BOOL_FALSE(SetHandleInformation(outputFile.get(), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT));
969
970 auto [readPipe, writePipe] = CreateSubprocessPipe(true, false);
971
972 auto cmd = LxssGenerateWslCommandLine(L"--debug-shell");
973 const auto process = LxsstuStartProcess(cmd.data(), readPipe.get(), outputFile.get());
974
975 THROW_IF_WIN32_BOOL_FALSE(WriteFile(writePipe.get(), dumpScript, static_cast<DWORD>(strlen(dumpScript)), nullptr, nullptr));
976 writePipe.reset();
977
978 // Wait up to 5 minutes for that process
979 const auto result = WaitForSingleObject(process.get(), 60 * 1000 * 5);
980 if (result != WAIT_TIMEOUT)
981 {
982 LogError("Unexpected status waiting for the debug shell, %lu", result);
983 }
984 }
985
986 // LxsstuWatchdogTimer
987
988 VOID __stdcall LxsstuWatchdogTimer(_Inout_ PTP_CALLBACK_INSTANCE Instance, _Inout_opt_ PVOID ThreadpoolTimerContext, _Inout_ PTP_TIMER Timer)
989
990 /*++
991
992 Routine Description:
993
994 Runs when the watch dog timer has fired to crash the process.
995
996 Arguments:
997
998 Instance - Not used.
999
1000 ThreadpoolTimerContext - Not used.
1001
1002 Timer - Not used.
1003
1004 Return Value:
1005
1006 None.
1007
1008 --*/
1009
1010 {
1011
1012 UNREFERENCED_PARAMETER(Instance);
1013 UNREFERENCED_PARAMETER(ThreadpoolTimerContext);
1014 UNREFERENCED_PARAMETER(Timer);
1015
1016 try
1017 {
1018 if (g_enableWerReport)
1019 {
1020 CreateWerReports();
1021 }
1022 else
1023 {
1024 LogError("Wer reporting disabled, skipping");
1025 }
1026 }
1027 catch (...)
1028 {
1029 LogError("Failed to create WER report, 0x%x", wil::ResultFromCaughtException());
1030 }
1031
1032 if (LxsstuVmMode())
1033 {
1034 try
1035 {
1036 DumpGuestProcesses();
1037 }
1038 catch (...)
1039 {
1040 LogError("Failed to dump guest processes, 0x%x", wil::ResultFromCaughtException());
1041 }
1042 }
1043
1044 try
1045 {
1046 if (g_enableWerReport && g_dumpToolPath.has_value())
1047 {
1048 CaptureLiveDump();
1049 }
1050 }
1051 catch (...)
1052 {
1053 LogError("Failed to capture livedump, 0x%x", wil::ResultFromCaughtException());
1054 }
1055
1056 __fastfail(FAST_FAIL_FATAL_APP_EXIT);
1057 return;
1058 }
1059
1060 // LxsstuInstanceTests
1061
1062 VOID LxsstuInstanceTests(VOID)
1063
1064 /*++
1065
1066 Routine Description:
1067
1068 Runs the instance unit tests.
1069
1070 Arguments:
1071
1072 None.
1073
1074 Return Value:
1075
1076 None.
1077
1078 --*/
1079
1080 {
1081
1082 ULONG Iteration;
1083 ULONG NumberOfIterations;
1084 ULONG SleepDuration;
1085 unsigned int Seed;
1086
1087 //
1088 // Start and stop an instance multiple times, sleeping a random duration
1089 // between the start and stop.
1090 //
1091
1092 NumberOfIterations = 5;
1093 Seed = GetTickCount();
1094 srand(Seed);
1095 LogInfo("Starting instance tests, Seed = %u", Seed);
1096 for (Iteration = 0; Iteration < NumberOfIterations; Iteration++)
1097 {
1098 LogInfo("Create instance - Iteration %u of %u", (Iteration + 1), NumberOfIterations);
1099
1100 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"/bin/true"), 0u);
1101 SleepDuration = rand() % LXSS_INSTANCE_TEST_TIMEOUT;
1102 LogInfo("Sleeping %u milliseconds before destroying instance...", SleepDuration);
1103
1104 SleepEx(SleepDuration, FALSE);
1105 TerminateDistribution();
1106 }
1107
1108 LogPass("Instance tests passed");
1109
1110 return;
1111 }
1112
1113 // LxxsSplitString
1114
1115 std::vector<std::wstring> LxssSplitString(_In_ const std::wstring& String, _In_ const std::wstring& Delim)
1116
1117 /*++
1118
1119 Routine Description:
1120
1121 Split a string by a delimiter.
1122
1123 Arguments:
1124
1125 String - Supplies the string to split.
1126
1127 Delim - The delimiter to split the string on.
1128
1129 Return Value:
1130
1131 A vector of split string.
1132
1133 --*/
1134
1135 {
1136 std::vector<std::wstring> output;
1137
1138 std::wistringstream input(String);
1139 std::wstring entry;
1140
1141 std::string::size_type index = 0;
1142 std::string::size_type previous_index = 0;
1143
1144 while ((index = String.find(Delim, previous_index)) != std::string::npos)
1145 {
1146 output.emplace_back(String.substr(previous_index, index - previous_index));
1147 previous_index = index + Delim.size();
1148 }
1149
1150 auto remaining = String.substr(previous_index);
1151 if (remaining != Delim && !remaining.empty())
1152 {
1153 output.emplace_back(std::move(remaining));
1154 }
1155
1156 return output;
1157 }
1158
1159 // WslKeepAlive class definitions
1160
1161 WslKeepAlive::WslKeepAlive(HANDLE Token) : m_token(Token)
1162 {
1163 Set();
1164 }
1165
1166 WslKeepAlive::~WslKeepAlive()
1167 {
1168 Reset();
1169 }
1170
1171 void WslKeepAlive::Set()
1172 {
1173 std::tie(m_read, m_write) = CreateSubprocessPipe(true, false);
1174
1175 m_running.emplace();
1176 m_thread = std::thread(std::bind(&WslKeepAlive::Run, this));
1177 m_running->get_future().wait();
1178 }
1179
1180 void WslKeepAlive::Run()
1181 {
1182 try
1183 {
1184 // Create a pipe to read wsl's output
1185 wil::unique_handle read;
1186 wil::unique_handle write;
1187 SECURITY_ATTRIBUTES attributes = {0};
1188 attributes.nLength = sizeof(attributes);
1189 attributes.bInheritHandle = true;
1190 THROW_LAST_ERROR_IF(!CreatePipe(&read, &write, &attributes, sizeof(attributes)));
1191
1192 // Start a process that outputs 'running', then waits
1193 const std::wstring expectedOutput = L"running";
1194 std::wstring cmd = L"wsl.exe echo -n " + expectedOutput + L" && read -n 1 ";
1195 const auto process = LxsstuStartProcess(cmd.data(), m_read.get(), write.get(), nullptr, m_token);
1196 write.reset();
1197
1198 // Wait until we read 'running'
1199 std::string buffer(expectedOutput.size(), '\0');
1200 DWORD bytesRead = 0;
1201 VERIFY_IS_TRUE(ReadFile(read.get(), buffer.data(), static_cast<DWORD>(expectedOutput.size()), &bytesRead, nullptr));
1202
1203 VERIFY_ARE_EQUAL(buffer, wsl::shared::string::WideToMultiByte(expectedOutput));
1204
1205 m_running->set_value();
1206
1207 WaitForSingleObject(process.get(), INFINITE);
1208 }
1209 catch (...)
1210 {
1211 LogError("Caught exception in WslKeepAlive::Run");
1212 m_running->set_exception(std::current_exception());
1213 }
1214 }
1215
1216 void WslKeepAlive::Reset()
1217 {
1218 if (m_thread.joinable())
1219 {
1220 const char c = 'k';
1221 THROW_LAST_ERROR_IF(!WriteFile(m_write.get(), &c, sizeof(c), nullptr, nullptr));
1222 m_write.reset();
1223 m_thread.join();
1224 }
1225 }
1226
1227 std::pair<DWORD, DWORD> GetServiceState(SC_HANDLE service)
1228 {
1229 DWORD dwBytesNeeded{};
1230 SERVICE_STATUS_PROCESS status{};
1231 if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&status, sizeof(status), &dwBytesNeeded))
1232 {
1233 LogError("QueryServiceStatusEx() failed, %lu", GetLastError());
1234 VERIFY_FAIL();
1235 }
1236
1237 return std::make_pair(status.dwCurrentState, status.dwProcessId);
1238 }
1239
1240 void WaitForServiceState(SC_HANDLE service, DWORD state, DWORD previousPid)
1241 {
1242 DWORD currentState{};
1243 DWORD pid{};
1244 auto pred = [&]() {
1245 std::tie(currentState, pid) = GetServiceState(service);
1246 if (pid != previousPid && state == SERVICE_STOPPED)
1247 {
1248 return;
1249 }
1250
1251 THROW_HR_IF(E_ABORT, currentState != state && currentState != SERVICE_STOPPED);
1252 };
1253
1254 try
1255 {
1256 wsl::shared::retry::RetryWithTimeout<void>(pred, std::chrono::milliseconds(100), std::chrono::minutes(2), [&]() {
1257 return wil::ResultFromCaughtException() == E_ABORT;
1258 });
1259 }
1260 catch (...)
1261 {
1262 LogError("Timed waiting for service to reach state: %lu. Current state: %lu, error: 0x%x", state, currentState, wil::ResultFromCaughtException());
1263 }
1264 }
1265
1266 void StopService(SC_HANDLE service)
1267 {
1268 // Some services don't accept SERVICE_CONTROL_STOP when starting.
1269 // Wait for them to be running before stopping them
1270 auto [state, pid] = GetServiceState(service);
1271 if (state == SERVICE_START_PENDING)
1272 {
1273 WaitForServiceState(service, SERVICE_RUNNING, pid);
1274 }
1275
1276 SERVICE_STATUS status{};
1277 if (!ControlService(service, SERVICE_CONTROL_STOP, &status))
1278 {
1279 const auto error = GetLastError();
1280 if (error != ERROR_SERVICE_NOT_ACTIVE)
1281 {
1282 LogError("Unexpected error code: 0x%x", error);
1283 VERIFY_FAIL();
1284 }
1285 return; // Service is not running
1286 }
1287
1288 WaitForServiceState(service, SERVICE_STOPPED, pid);
1289 }
1290
1291 void RestartWslService()
1292 /*++
1293
1294 Routine Description:
1295
1296 Restart the WSL service.
1297
1298 Arguments:
1299
1300 None.
1301
1302 Return Value:
1303
1304 None.
1305
1306 --*/
1307 {
1308 LogInfo("Restarting WSLService");
1309 const wil::unique_schandle manager{OpenSCManager(nullptr, nullptr, SC_MANAGER_CONNECT)};
1310 VERIFY_IS_NOT_NULL(manager);
1311
1312 const wil::unique_schandle service{OpenService(manager.get(), L"wslservice", SERVICE_STOP | SERVICE_QUERY_STATUS | SERVICE_START)};
1313 VERIFY_IS_NOT_NULL(service);
1314
1315 StopService(service.get());
1316 if (!StartService(service.get(), 0, nullptr))
1317 {
1318 VERIFY_ARE_EQUAL(GetLastError(), ERROR_SERVICE_ALREADY_RUNNING);
1319 }
1320 }
1321
1322 void StopWslService()
1323 {
1324 LogInfo("Stopping WSLService");
1325 const wil::unique_schandle manager{OpenSCManager(nullptr, nullptr, SC_MANAGER_CONNECT)};
1326 VERIFY_IS_NOT_NULL(manager);
1327
1328 const wil::unique_schandle service{OpenService(manager.get(), L"wslservice", SERVICE_STOP | SERVICE_QUERY_STATUS)};
1329 VERIFY_IS_NOT_NULL(service);
1330 StopService(service.get());
1331 }
1332
1333 wil::unique_handle GetNonElevatedToken(TOKEN_TYPE Type)
1334 {
1335 auto token = wil::open_current_access_token(TOKEN_ALL_ACCESS);
1336
1337 if (Type != TokenPrimary)
1338 {
1339 // N.B. Using the Safer API to create a non-elevated primary token break drvfs, so skipping this for primary tokens.
1340 SAFER_LEVEL_HANDLE saferLevel = nullptr;
1341 auto closeSaferLevel = wil::scope_exit([&]() { SaferCloseLevel(saferLevel); });
1342
1343 THROW_IF_WIN32_BOOL_FALSE(SaferCreateLevel(SAFER_SCOPEID_MACHINE, SAFER_LEVELID_NORMALUSER, SAFER_LEVEL_OPEN, &saferLevel, nullptr));
1344
1345 wil::unique_handle restrictedToken;
1346 THROW_IF_WIN32_BOOL_FALSE(SaferComputeTokenFromLevel(saferLevel, token.get(), &restrictedToken, 0, nullptr));
1347
1348 token = std::move(restrictedToken);
1349 }
1350
1351 wil::unique_handle nonElevatedToken;
1352 THROW_IF_WIN32_BOOL_FALSE(DuplicateTokenEx(token.get(), TOKEN_ALL_ACCESS, nullptr, SecurityImpersonation, Type, &nonElevatedToken));
1353
1354 wil::unique_sid mediumIntegritySid;
1355 THROW_LAST_ERROR_IF(!ConvertStringSidToSidA("S-1-16-8192", &mediumIntegritySid));
1356
1357 TOKEN_MANDATORY_LABEL label = {0};
1358 label.Label.Attributes = SE_GROUP_INTEGRITY;
1359 label.Label.Sid = mediumIntegritySid.get();
1360 THROW_IF_WIN32_BOOL_FALSE(SetTokenInformation(nonElevatedToken.get(), TokenIntegrityLevel, &label, sizeof(label)));
1361
1362 return nonElevatedToken;
1363 }
1364
1365 WslConfigChange::WslConfigChange(const std::wstring& Content)
1366 {
1367 m_originalContent = Update(Content);
1368 }
1369
1370 WslConfigChange::WslConfigChange(WslConfigChange&& other) : m_originalContent(std::move(other.m_originalContent))
1371 {
1372 }
1373
1374 std::wstring WslConfigChange::Update(const std::wstring& Content)
1375 {
1376 auto previous = LxssWriteWslConfig(Content);
1377
1378 if (previous != Content)
1379 {
1380 RestartWslService();
1381 }
1382
1383 return previous;
1384 }
1385
1386 WslConfigChange::~WslConfigChange()
1387 {
1388 if (m_originalContent)
1389 {
1390 Update(m_originalContent.value());
1391 }
1392 }
1393
1394 HostFileChange::HostFileChange(const std::filesystem::path& Path, const std::string& NewContent) : m_path(Path)
1395 {
1396 if (std::filesystem::exists(m_path))
1397 {
1398 std::ifstream file(m_path, std::ios::binary);
1399 THROW_HR_IF(E_FAIL, !file.is_open());
1400 std::stringstream buffer;
1401 buffer << file.rdbuf();
1402 m_originalContent = buffer.str();
1403 }
1404
1405 Update(NewContent);
1406 }
1407
1408 HostFileChange::~HostFileChange()
1409 try
1410 {
1411 if (m_originalContent.has_value())
1412 {
1413 std::filesystem::create_directories(m_path.parent_path());
1414 std::ofstream file(m_path, std::ios::binary | std::ios::trunc);
1415 if (file.is_open())
1416 {
1417 file.write(m_originalContent->data(), static_cast<std::streamsize>(m_originalContent->size()));
1418 }
1419 }
1420 else
1421 {
1422 std::filesystem::remove(m_path);
1423 }
1424 }
1425 CATCH_LOG()
1426
1427 void HostFileChange::Update(const std::string& NewContent) const
1428 {
1429 std::filesystem::create_directories(m_path.parent_path());
1430 std::ofstream file(m_path, std::ios::binary | std::ios::trunc);
1431 THROW_HR_IF(E_FAIL, !file.is_open());
1432 file.write(NewContent.data(), static_cast<std::streamsize>(NewContent.size()));
1433 THROW_HR_IF(E_FAIL, !file.good());
1434 }
1435
1436 std::wstring ReadFileContent(const std::string& Path)
1437 {
1438 std::ifstream configRead(Path);
1439 return std::wstring{std::istreambuf_iterator<char>(configRead), {}};
1440 }
1441
1442 std::wstring ReadFileContent(const std::wstring& Path)
1443 {
1444 std::wifstream configRead(Path);
1445 return std::wstring{std::istreambuf_iterator<wchar_t>(configRead), {}};
1446 }
1447
1448 // writes global WSL 2 config settings at %userprofile%/.wslconfig
1449 std::wstring LxssWriteWslConfig(const std::wstring& Content)
1450 {
1451 auto path = getenv("userprofile") + std::string("\\.wslconfig");
1452
1453 auto previousContent = ReadFileContent(path);
1454
1455 std::wofstream config(path);
1456 VERIFY_IS_TRUE(config.good());
1457 config << Content;
1458
1459 return previousContent;
1460 }
1461
1462 // writes distro specific settings /etc/wsl.conf
1463 std::string LxssWriteWslDistroConfig(const std::string& Content, LPCWSTR DistributionName)
1464 {
1465 std::string path = std::format("\\\\wsl.localhost\\{}\\etc\\wsl.conf", DistributionName);
1466
1467 std::ifstream distroConfigRead(path);
1468 auto previousContent = std::string{std::istreambuf_iterator<char>(distroConfigRead), {}};
1469 distroConfigRead.close();
1470
1471 std::ofstream distroConfig(path, std::ios_base::binary);
1472 VERIFY_IS_TRUE(distroConfig.good());
1473 distroConfig.write(Content.c_str(), Content.size());
1474
1475 return previousContent;
1476 }
1477
1478 // generates a sample global WSL config for the tests
1479 std::wstring LxssGenerateTestConfig(TestConfigDefaults Default)
1480 {
1481 WEX::Common::String kernelLogsArg;
1482 WEX::TestExecution::RuntimeParameters::TryGetValue(L"KernelLogs", kernelLogsArg);
1483
1484 std::wstring kernelLogs;
1485 if (kernelLogsArg.IsEmpty())
1486 {
1487 kernelLogs = wil::GetCurrentDirectoryW().get() + std::wstring(L"\\kernelLogs.txt");
1488 }
1489 else
1490 {
1491 kernelLogs = kernelLogsArg;
1492 }
1493
1494 auto boolOptionToString = [](LPCWSTR optionName, std::optional<bool> condition, bool defaultValue) {
1495 std::wstring value{optionName};
1496 value += L"=";
1497 value += condition.value_or(defaultValue) ? L"true" : L"false";
1498 value += L"\n";
1499 return value;
1500 };
1501
1502 auto networkingModeToString = [](std::optional<wsl::core::NetworkingMode> mode) {
1503 if (mode.has_value())
1504 {
1505 std::wstring value = L"networkingMode=";
1506 value += wsl::shared::string::MultiByteToWide(wsl::core::ToString(mode.value()));
1507 value += L"\n";
1508 return value;
1509 }
1510
1511 return std::wstring{};
1512 };
1513
1514 auto drvFsModeToString = [](std::optional<DrvFsMode> mode) {
1515 std::wstring value;
1516 switch (mode.value_or(DrvFsMode::Plan9))
1517 {
1518 case DrvFsMode::Plan9:
1519 value = L"virtio9p=false";
1520 break;
1521 case DrvFsMode::Virtio9p:
1522 value = L"virtio9p=true";
1523 break;
1524 case DrvFsMode::VirtioFs:
1525 value = L"virtiofs=true";
1526 break;
1527 }
1528
1529 value += L"\n";
1530 return value;
1531 };
1532
1533 std::wstring newConfig =
1534 L"[wsl2]\n"
1535 L"crashDumpFolder=" +
1536 EscapePath(Default.CrashDumpFolder.value_or(g_dumpFolder + L"\\linux-crashes")) + L"\nmaxCrashDumpCount=" +
1537 std::to_wstring(Default.crashDumpCount) + L"\nvmIdleTimeout=" + std::to_wstring(Default.vmIdleTimeout.value_or(2000)) +
1538 L"\n"
1539 L"mountDeviceTimeout=120000\n"
1540 L"kernelBootTimeout=120000\n"
1541 L"debugConsoleLogFile=" +
1542 EscapePath(Default.debugConsoleLogFile.value_or(kernelLogs)) +
1543 L"\n"
1544 L"telemetry=false\n" +
1545 boolOptionToString(L"safeMode", Default.safeMode, false) + boolOptionToString(L"guiApplications", Default.guiApplications, true) +
1546 boolOptionToString(L"earlyBootLogging", Default.earlyBootLogging, false) +
1547 networkingModeToString(Default.networkingMode) + drvFsModeToString(Default.drvFsMode);
1548
1549 if (Default.kernel.has_value())
1550 {
1551 newConfig += L"kernel=" + EscapePath(Default.kernel.value()) + L"\n";
1552 }
1553
1554 if (Default.kernelCommandLine.has_value())
1555 {
1556 newConfig += L"kernelCommandLine=" + Default.kernelCommandLine.value() + L"\n";
1557 }
1558
1559 if (Default.kernelModules.has_value())
1560 {
1561 newConfig += L"kernelModules=" + EscapePath(Default.kernelModules.value()) + L"\n";
1562 }
1563
1564 if (Default.loadKernelModules.has_value())
1565 {
1566 newConfig += L"loadKernelModules=" + Default.loadKernelModules.value() + L"\n";
1567 }
1568
1569 if (Default.loadDefaultKernelModules.has_value())
1570 {
1571 newConfig +=
1572 L"loadDefaultKernelModules=" + std::wstring(Default.loadDefaultKernelModules.value() ? L"true" : L"false") + L"\n";
1573 }
1574
1575 if (Default.systemDistro.has_value())
1576 {
1577 newConfig += L"systemDistro=" + EscapePath(Default.systemDistro.value()) + L"\n";
1578 }
1579
1580 switch (Default.networkingMode.value_or(wsl::core::NetworkingMode::Nat))
1581 {
1582 case wsl::core::NetworkingMode::Nat:
1583 {
1584 if (Default.dnsProxy.has_value())
1585 {
1586 newConfig += boolOptionToString(L"dnsProxy", Default.dnsProxy, false);
1587 }
1588
1589 if (Default.firewall.has_value())
1590 {
1591 newConfig += L"[experimental]\nfirewall=";
1592 newConfig += *Default.firewall ? L"true" : L"false";
1593 newConfig += L"\n[wsl2]\n";
1594 }
1595
1596 break;
1597 }
1598 case wsl::core::NetworkingMode::Bridged:
1599 {
1600 VERIFY_IS_TRUE(Default.vmSwitch.has_value());
1601
1602 newConfig += L"vmSwitch=" + *Default.vmSwitch;
1603
1604 if (Default.macAddress.has_value())
1605 {
1606 newConfig += L"\nmacAddress=" + *Default.macAddress;
1607 }
1608
1609 newConfig += L"\nipv6=" + std::wstring(Default.ipv6 ? L"true" : L"false");
1610 newConfig += L"\n";
1611
1612 break;
1613 }
1614 }
1615
1616 if (Default.dnsTunneling.has_value())
1617 {
1618 newConfig += L"\n[experimental]\n";
1619 newConfig += boolOptionToString(L"dnsTunneling", Default.dnsTunneling, false);
1620 newConfig += L"[wsl2]\n";
1621 }
1622
1623 if (Default.dnsTunnelingIpAddress.has_value())
1624 {
1625 newConfig += L"\n[experimental]\n";
1626 newConfig += L"dnsTunnelingIpAddress=" + Default.dnsTunnelingIpAddress.value() + L"\n";
1627 newConfig += L"[wsl2]\n";
1628 }
1629
1630 // always add this regardless if it has value, want to have it disabled by default for tests
1631 newConfig += L"\n[experimental]\n";
1632 newConfig += boolOptionToString(L"autoProxy", Default.autoProxy, false);
1633 newConfig += L"[wsl2]\n";
1634
1635 if (Default.sparse.has_value())
1636 {
1637 std::wstring value = Default.sparse.value() ? L"true" : L"false";
1638 newConfig += L"[experimental]\nsparseVhd=" + value + L"\n[wsl2]";
1639 }
1640
1641 if (Default.hostAddressLoopback.has_value())
1642 {
1643 newConfig += L"\n[experimental]\n";
1644 newConfig += boolOptionToString(L"hostAddressLoopback", Default.hostAddressLoopback, false);
1645 newConfig += L"[wsl2]\n";
1646 }
1647
1648 if (Default.virtioFsAggregateShares.has_value())
1649 {
1650 newConfig += L"\n[experimental]\n";
1651 newConfig += boolOptionToString(L"virtioFsAggregateShares", Default.virtioFsAggregateShares, true);
1652 newConfig += L"[wsl2]\n";
1653 }
1654
1655 // TODO: Remove once SetVersion() truncated archive error is root caused.
1656 newConfig += L"\n[experimental]\nSetVersionDebug=true\n[wsl2]\n";
1657
1658 if (Default.isolateDistroCgroup.has_value())
1659 {
1660 newConfig += boolOptionToString(L"isolateDistroCgroup", Default.isolateDistroCgroup, true);
1661 }
1662
1663 return newConfig;
1664 }
1665
1666 std::wstring EscapePath(std::wstring_view Path)
1667 {
1668 std::wstring escaped;
1669 for (const auto e : Path)
1670 {
1671 escaped += e;
1672
1673 if (e == L'\\')
1674 {
1675 escaped += e;
1676 }
1677 }
1678
1679 return escaped;
1680 }
1681
1682 NTSTATUS
1683 LxsstuParseLinuxLogFiles(__in PCWSTR LogFileName, __out PBOOL TestPassed)
1684
1685 /*++
1686
1687 Routine Description:
1688
1689 Parses the output of the linux test and relogs the output.
1690
1691 Arguments:
1692
1693 LogFileName - Supplies a string containing the log files for the test
1694 separated by LXSS_TEST_LOG_SEPARATOR_CHAR.
1695
1696 TestPassed - Supplies a buffer to receive a boolean value specifying if the
1697 tests completed without errors.
1698
1699 Return Value:
1700
1701 NTSTATUS
1702
1703 --*/
1704
1705 {
1706
1707 HANDLE LinuxLogFile;
1708 WCHAR LinuxLogPath[MAX_PATH];
1709 WCHAR LocalLogFileBuffer[MAX_PATH];
1710 PWCHAR LogFileToken;
1711 DWORD PrintStatus;
1712 NTSTATUS Status;
1713 std::wstring TestDirectory;
1714 LXSS_TEST_LAUNCHER_TEST TestRecord;
1715 PWCHAR TokenState;
1716
1717 LinuxLogFile = INVALID_HANDLE_VALUE;
1718 Status = STATUS_UNSUCCESSFUL;
1719 *TestPassed = FALSE;
1720 RtlZeroMemory(&TestRecord, sizeof(TestRecord));
1721
1722 //
1723 // Make a copy of the log file name so wcstok can modify it.
1724 //
1725
1726 PrintStatus = swprintf_s(LocalLogFileBuffer, RTL_NUMBER_OF(LocalLogFileBuffer), L"%s", LogFileName);
1727
1728 if (PrintStatus == -1)
1729 {
1730 Status = STATUS_UNSUCCESSFUL;
1731 LogError("Increase LocalLogFileBuffer buffer");
1732 goto ErrorExit;
1733 }
1734
1735 //
1736 // Get the test directory.
1737 //
1738
1739 TestDirectory = LxsstuGetTestDirectory();
1740
1741 //
1742 // Parse the logs for the test and determine how many passes / errors there
1743 // were.
1744 //
1745
1746 LogFileToken = wcstok(LocalLogFileBuffer, LXSS_TEST_LOG_SEPARATOR_CHAR, &TokenState);
1747
1748 while (LogFileToken != NULL)
1749 {
1750 LogInfo("LOGFILE: %s", LogFileToken);
1751 PrintStatus = swprintf_s(LinuxLogPath, RTL_NUMBER_OF(LinuxLogPath), L"%s\\log\\%s", TestDirectory.c_str(), LogFileToken);
1752
1753 if (PrintStatus == -1)
1754 {
1755 Status = STATUS_UNSUCCESSFUL;
1756 LogError("Increase LinuxLogPath buffer");
1757 goto ErrorExit;
1758 }
1759
1760 //
1761 // For VM Mode, copy the output file out of the ext4 volume so it can
1762 // be read.
1763 //
1764
1765 if (LxsstuVmMode())
1766 {
1767 std::wstring Command = std::format(L"/bin/cp /data/test/log/{} $(wslpath '{}')", LogFileToken, LinuxLogPath);
1768 VERIFY_NO_THROW(LxsstuRunTest(Command.c_str()));
1769 }
1770
1771 LinuxLogFile =
1772 CreateFileW(LinuxLogPath, GENERIC_READ, (FILE_SHARE_READ | FILE_SHARE_WRITE), NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1773
1774 if (LinuxLogFile == INVALID_HANDLE_VALUE)
1775 {
1776 Status = STATUS_UNSUCCESSFUL;
1777 LogError("Could not open {:%s:} after running test, LastError %#x", LinuxLogPath, GetLastError());
1778
1779 goto ErrorExit;
1780 }
1781
1782 Status = LxsstuParseLogFile(LinuxLogFile, &TestRecord);
1783 if (!NT_SUCCESS(Status))
1784 {
1785 goto ErrorExit;
1786 }
1787
1788 if (TestRecord.NumberOfErrors > 0)
1789 {
1790 LogError("LOG FILE SUMMARY: %s - PASSED: %u ERRORS: %u", LogFileToken, TestRecord.NumberOfPasses, TestRecord.NumberOfErrors);
1791 }
1792 else if (TestRecord.NumberOfPasses > 0)
1793 {
1794 LogPass("LOG FILE SUMMARY: %s - PASSED: %u ERRORS: %u", LogFileToken, TestRecord.NumberOfPasses, TestRecord.NumberOfErrors);
1795 }
1796 else
1797 {
1798 LogError("LOG FILE SUMMARY: %s - log had no passes or errors, ensure test was actually run", LogFileToken);
1799 }
1800
1801 CloseHandle(LinuxLogFile);
1802 LinuxLogFile = INVALID_HANDLE_VALUE;
1803 LogFileToken = wcstok(NULL, LXSS_TEST_LOG_SEPARATOR_CHAR, &TokenState);
1804 }
1805
1806 Status = STATUS_SUCCESS;
1807
1808 ErrorExit:
1809 if (LinuxLogFile != INVALID_HANDLE_VALUE)
1810 {
1811 CloseHandle(LinuxLogFile);
1812 }
1813
1814 if ((TestRecord.NumberOfErrors == 0) && (TestRecord.NumberOfPasses > 0))
1815 {
1816 *TestPassed = TRUE;
1817 }
1818
1819 return Status;
1820 }
1821
1822 NTSTATUS
1823 LxsstuParseLogFile(__in HANDLE FileHandle, __in PLXSS_TEST_LAUNCHER_TEST TestRecord)
1824
1825 /*++
1826
1827 Routine Description:
1828
1829 Parses a single log file.
1830
1831 Arguments:
1832
1833 TestName - Name of the test.
1834
1835 LogFileName - string containing the log files for the test separated by
1836 LXSS_TEST_LOG_SEPARATOR_CHAR.
1837
1838 Return Value:
1839
1840 NTSTATUS
1841
1842 --*/
1843
1844 {
1845
1846 PBYTE Buffer;
1847 DWORD BytesRead;
1848 DWORD FileSize;
1849 DWORD FileSizeHigh;
1850 PCHAR Message;
1851 LXSS_TEST_LAUNCHER_MESSAGE_TYPE MessageType;
1852 NTSTATUS Status;
1853 PCHAR Token;
1854
1855 Buffer = NULL;
1856 Status = STATUS_UNSUCCESSFUL;
1857
1858 FileSize = GetFileSize(FileHandle, &FileSizeHigh);
1859 Buffer = (PBYTE)ALLOC(FileSize + 1);
1860 if (Buffer == NULL)
1861 {
1862 goto ErrorExit;
1863 }
1864
1865 Buffer[FileSize] = '\0';
1866
1867 do
1868 {
1869 RtlZeroMemory(Buffer, FileSize);
1870 if (ReadFile(FileHandle, Buffer, FileSize, &BytesRead, NULL) == FALSE)
1871 {
1872
1873 Status = STATUS_UNSUCCESSFUL;
1874 LogError("ReadFile failed, LastError %#x", GetLastError());
1875 goto ErrorExit;
1876 }
1877
1878 if (BytesRead == 0)
1879 {
1880 break;
1881 }
1882
1883 //
1884 // Parse the log line-by-line.
1885 //
1886
1887 Token = strtok((PCHAR)Buffer, "\n");
1888 while (Token != NULL)
1889 {
1890
1891 //
1892 // A well-formed message begins with a timestamp and then is either
1893 // a start, info, error, or pass message. For example:
1894 // [12:30:05.432] ERROR: Something went wrong!
1895 //
1896 // Anything that does not fit this format is re-logged an an "info"
1897 // message.
1898 //
1899
1900 MessageType = LogInfoMessage;
1901 if (Token[0] == '[')
1902 {
1903 Message = strchr(Token, ' ');
1904 if ((Message == NULL) || (strlen(Message) < 2))
1905 {
1906 break;
1907 }
1908
1909 switch (Message[1])
1910 {
1911 case 'E':
1912 case 'R':
1913 MessageType = LogErrorMessage;
1914 break;
1915
1916 case 'P':
1917 MessageType = LogPassMessage;
1918 break;
1919 }
1920 }
1921
1922 switch (MessageType)
1923 {
1924 case LogInfoMessage:
1925 if (g_RelogEverything != FALSE)
1926 {
1927 LogInfo("%S", Token);
1928 }
1929
1930 break;
1931
1932 case LogErrorMessage:
1933 TestRecord->NumberOfErrors += 1;
1934 if (g_RelogEverything != FALSE)
1935 {
1936 LogError("%S", Token);
1937 }
1938
1939 break;
1940
1941 case LogPassMessage:
1942 TestRecord->NumberOfPasses += 1;
1943 if (g_RelogEverything != FALSE)
1944 {
1945 LogPass("%S", Token);
1946 }
1947
1948 break;
1949
1950 DEFAULT_UNREACHABLE;
1951 }
1952
1953 Token = strtok(NULL, "\n");
1954 }
1955 } while (BytesRead > 0);
1956
1957 Status = STATUS_SUCCESS;
1958
1959 ErrorExit:
1960 if (Buffer != NULL)
1961 {
1962 FREE(Buffer);
1963 }
1964
1965 return Status;
1966 }
1967
1968 VOID LxsstuRunTest(_In_ PCWSTR CommandLine, _In_opt_ PCWSTR LogFileName, _In_opt_ PCWSTR Username) noexcept(false)
1969
1970 /*++
1971
1972 Routine Description:
1973
1974 Run an individual test.
1975
1976 Arguments:
1977
1978 CommandLine - Command line path and arguments to pass
1979 LogFileName - Name of the linux log file
1980 Username - User to run the test as, if one is not supplied the test
1981 will be run as root
1982
1983 Return Value:
1984
1985 None.
1986
1987 --*/
1988
1989 {
1990
1991 BOOL TestPassed;
1992 std::wstring LaunchArguments{};
1993
1994 if (ARGUMENT_PRESENT(Username))
1995 {
1996 LaunchArguments += WSL_USER_ARG L" ";
1997 LaunchArguments += Username;
1998 LaunchArguments += L" ";
1999 }
2000
2001 LaunchArguments += CommandLine;
2002 DWORD ExitCode = LxsstuLaunchWsl(LaunchArguments.c_str());
2003 LogInfo("Test process exited with: %lu", ExitCode);
2004
2005 //
2006 // Parse the contents of the linux log(s) files and relog.
2007 //
2008
2009 if (ARGUMENT_PRESENT(LogFileName))
2010 {
2011 THROW_IF_NTSTATUS_FAILED(LxsstuParseLinuxLogFiles(LogFileName, &TestPassed));
2012
2013 VERIFY_IS_TRUE(TestPassed);
2014 }
2015
2016 VERIFY_ARE_EQUAL(0, ExitCode);
2017
2018 return;
2019 }
2020
2021 bool ModuleSetup(VOID)
2022
2023 /*++
2024
2025 Routine Description:
2026
2027 Configures the machine to run tests
2028
2029 Arguments:
2030
2031 Return Value:
2032
2033 None.
2034
2035 --*/
2036
2037 {
2038 wsl::windows::common::wslutil::InitializeWil();
2039
2040 THROW_IF_FAILED(CoIncrementMTAUsage(&g_mtaCookie));
2041
2042 // Assign a job object to the current process to ensure that we don't leak processes on failure.
2043 // N.B. When the job object is closed, all processes associated with the job will be terminated.
2044 // Because of that, we're purposefully leaking this job object so we don't kill the test process on cleanup.
2045 auto job = CreateJobObjectW(nullptr, nullptr);
2046 THROW_LAST_ERROR_IF(!job);
2047
2048 JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo{};
2049 jobInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
2050 THROW_IF_WIN32_BOOL_FALSE(SetInformationJobObject(job, JobObjectExtendedLimitInformation, &jobInfo, sizeof(jobInfo)));
2051 THROW_IF_WIN32_BOOL_FALSE(AssignProcessToJobObject(job, GetCurrentProcess()));
2052
2053 // Don't crash for unknown exceptions (makes debugging testpasses harder)
2054 #ifndef _DEBUG
2055 wil::g_fResultFailFastUnknownExceptions = false;
2056 #endif
2057
2058 WslTraceLoggingInitialize(LxssTelemetryProvider, true);
2059 wsl::windows::common::EnableContextualizedErrors(false);
2060
2061 auto getOptionalTestParam = [](LPCWSTR Name) -> std::optional<std::wstring> {
2062 WEX::Common::String Value;
2063
2064 WEX::TestExecution::RuntimeParameters::TryGetValue(Name, Value);
2065
2066 return Value.IsEmpty() ? std::optional<std::wstring>() : static_cast<LPCWSTR>(Value);
2067 };
2068
2069 auto getTestParam = [&](LPCWSTR Name) -> std::wstring {
2070 auto value = getOptionalTestParam(Name);
2071 if (!value.has_value())
2072 {
2073 const std::wstring error = L"Missing TE argument: " + std::wstring(Name);
2074 VERIFY_FAIL(error.c_str());
2075 }
2076
2077 return value.value();
2078 };
2079
2080 try
2081 {
2082 const auto buildString = wsl::windows::common::registry::ReadString(
2083 HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", L"BuildLabEx");
2084
2085 LogInfo("OS build string: %ls", buildString.c_str());
2086 }
2087 CATCH_LOG();
2088
2089 try
2090 {
2091 const auto userKey = wsl::windows::common::registry::OpenLxssUserKey();
2092 g_originalDefaultDistro = wsl::windows::common::registry::ReadString(userKey.get(), nullptr, L"DefaultDistribution", L"");
2093 }
2094 CATCH_LOG();
2095
2096 g_originalConfig = LxssWriteWslConfig(LxssGenerateTestConfig());
2097
2098 const auto redirectStdout = getOptionalTestParam(L"RedirectStdout");
2099 const auto redirectStderr = getOptionalTestParam(L"RedirectStderr");
2100
2101 if (redirectStdout.has_value())
2102 {
2103 g_OriginalStdout = LxssRedirectOutput(STD_OUTPUT_HANDLE, redirectStdout.value());
2104 }
2105
2106 if (redirectStderr.has_value())
2107 {
2108 g_OriginalStderr = LxssRedirectOutput(STD_ERROR_HANDLE, redirectStderr.value());
2109 }
2110
2111 g_dumpFolder = getOptionalTestParam(L"DumpFolder").value_or(L".");
2112 g_dumpToolPath = getOptionalTestParam(L"DumpTool");
2113 g_pipelineBuildId = getOptionalTestParam(L"PipelineBuildId").value_or(L"");
2114
2115 if (!g_pipelineBuildId.empty())
2116 {
2117 LogInfo("Pipeline build id: %ls", g_pipelineBuildId.c_str());
2118 }
2119
2120 WEX::TestExecution::RuntimeParameters::TryGetValue(L"WerReport", g_enableWerReport);
2121 WEX::TestExecution::RuntimeParameters::TryGetValue(L"LogDmesg", g_LogDmesgAfterEachTest);
2122
2123 g_WatchdogTimer = CreateThreadpoolTimer(LxsstuWatchdogTimer, nullptr, nullptr);
2124 VERIFY_IS_NOT_NULL(g_WatchdogTimer);
2125
2126 ULARGE_INTEGER fileTimeConvert{};
2127 fileTimeConvert.QuadPart = LXSS_WATCHDOG_TIMEOUT;
2128 fileTimeConvert.QuadPart *= (-1 * 1000 * 10i64); // fileTime is unsigned- took out -1; check if this causes errors later
2129 FILETIME DueTime{};
2130 DueTime.dwLowDateTime = fileTimeConvert.LowPart;
2131 DueTime.dwHighDateTime = fileTimeConvert.HighPart;
2132 SetThreadpoolTimer(g_WatchdogTimer, &DueTime, 0, LXSS_WATCHDOG_TIMEOUT_WINDOW);
2133
2134 const auto version = getTestParam(L"Version");
2135 if (version == L"1")
2136 {
2137 g_VmMode = false;
2138 }
2139 else if (version == L"2")
2140 {
2141 g_VmMode = true;
2142 }
2143 else
2144 {
2145 LogError("Unexpected version: %ls", version.c_str());
2146 VERIFY_FAIL();
2147 }
2148
2149 g_testDistroPath = getTestParam(L"DistroPath");
2150
2151 g_testDataPath = getTestParam(L"TestDataPath");
2152
2153 const auto setupScript = getOptionalTestParam(L"SetupScript");
2154 if (!setupScript.has_value())
2155 {
2156 // If no setup script is present, mark test_distro as the default distro here for convenience.
2157 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"--set-default " LXSS_DISTRO_NAME_TEST_L), 0L);
2158 g_fastTestRun = true;
2159
2160 return true;
2161 }
2162
2163 std::wstring Cmd =
2164 L"Powershell \
2165 -NoProfile \
2166 -ExecutionPolicy Bypass \
2167 -Command \"" +
2168 setupScript.value() + L" -Version '" + getTestParam(L"Version") + L"'" + L" -DistroPath " + g_testDistroPath +
2169 L" -DistroName " + LXSS_DISTRO_NAME_TEST_L + L" -Package '" + getTestParam(L"Package") + L"'" + L" -UnitTestsPath " +
2170 getOptionalTestParam(L"UnitTestsPath").value_or(L"$null");
2171
2172 if (getOptionalTestParam(L"AllowUnsigned") == L"1")
2173 {
2174 Cmd += L" -AllowUnsigned";
2175 }
2176
2177 Cmd += +L"\"";
2178
2179 LogInfo("Running test setup command: %ls", Cmd.c_str());
2180
2181 const auto ExitCode = LxsstuRunCommand(Cmd.data());
2182 if (ExitCode != 0)
2183 {
2184 THROW_HR_MSG(E_FAIL, "Test setup returned non-zero exit code %lu", ExitCode);
2185 }
2186
2187 return true;
2188 }
2189
2190 bool ModuleCleanup(VOID)
2191
2192 /*++
2193
2194 Routine Description:
2195
2196 Called after the tests cases have been executed.
2197 Reverts WSL version upgrades, if any.
2198
2199 Arguments:
2200 None.
2201
2202 Return Value:
2203
2204 None.
2205
2206 --*/
2207
2208 {
2209 LogInfo("Exiting UnitTests module");
2210
2211 auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
2212 WslTraceLoggingUninitialize();
2213 g_mtaCookie.reset();
2214 });
2215
2216 //
2217 // Release the watchdog timer.
2218 //
2219
2220 if (g_WatchdogTimer != NULL)
2221 {
2222 SetThreadpoolTimer(g_WatchdogTimer, nullptr, 0, 0);
2223 WaitForThreadpoolTimerCallbacks(g_WatchdogTimer, true);
2224 CloseThreadpoolTimer(g_WatchdogTimer);
2225 }
2226
2227 // Save the Appx & defender logs in the dump folder
2228 if (!g_pipelineBuildId.empty())
2229 {
2230 auto commandLine = std::format(L"Get-AppPackageLog -All > \"{}\\appx-logs.txt\"", g_dumpFolder);
2231 LxsstuLaunchPowershellAndCaptureOutput(commandLine.data());
2232
2233 commandLine = std::format(L"Get-MpThreatDetection > \"{}\\Get-MpThreatDetection.txt\"", g_dumpFolder);
2234 LxsstuLaunchPowershellAndCaptureOutput(commandLine.data());
2235
2236 commandLine = std::format(L"Get-MpThreat > \"{}\\Get-MpThreat.txt\"", g_dumpFolder);
2237 LxsstuLaunchPowershellAndCaptureOutput(commandLine.data());
2238
2239 commandLine = std::format(L"Get-MpPreference > \"{}\\Get-MpPreference.txt\"", g_dumpFolder);
2240 LxsstuLaunchPowershellAndCaptureOutput(commandLine.data());
2241 }
2242
2243 if (!g_originalConfig.empty())
2244 {
2245 LogInfo("Restoring .wslconfig");
2246 LxssWriteWslConfig(g_originalConfig);
2247 }
2248
2249 if (!g_originalDefaultDistro.empty())
2250 {
2251 // Edge case: If the previous default distro was the test distro, it might have been deleted during the testpass.
2252 // Validate the distro exists before restoring.
2253
2254 const auto userKey = wsl::windows::common::registry::OpenLxssUserKey();
2255
2256 try
2257 {
2258 wsl::windows::common::registry::OpenKey(userKey.get(), g_originalDefaultDistro.c_str(), KEY_READ);
2259 }
2260 catch (...)
2261 {
2262 LogInfo("Previous default distro doesn't exist anymore: '%ls', skipping restore", g_originalDefaultDistro.c_str());
2263 return true;
2264 }
2265
2266 LogInfo("Restoring default distro: '%ls", g_originalDefaultDistro.c_str());
2267
2268 wsl::windows::common::registry::WriteString(userKey.get(), nullptr, L"DefaultDistribution", g_originalDefaultDistro.c_str());
2269 }
2270
2271 return true;
2272 }
2273
2274 HANDLE
2275 LxssRedirectOutput(_In_ DWORD Stream, _In_ const std::wstring& File)
2276
2277 /*++
2278
2279 Routine Description:
2280
2281 Redirect a standard stream to a file
2282
2283 Arguments:
2284 Stream - The stream to redirect
2285
2286 File - The file to redirect the output to
2287
2288 Return Value:
2289
2290 None.
2291
2292 --*/
2293
2294 {
2295 const HANDLE OriginalHandle = GetStdHandle(Stream);
2296
2297 SECURITY_ATTRIBUTES Attributes = {0};
2298 Attributes.nLength = sizeof(Attributes);
2299 Attributes.bInheritHandle = true;
2300
2301 const auto Handle =
2302 CreateFileW(File.c_str(), FILE_APPEND_DATA, FILE_SHARE_READ, &Attributes, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
2303
2304 VERIFY_IS_NOT_NULL(Handle);
2305
2306 VERIFY_IS_TRUE(SetStdHandle(Stream, Handle));
2307
2308 return OriginalHandle;
2309 }
2310
2311 void CreateUser(_In_ const std::wstring& Username, _Out_ PULONG Uid, _Out_ PULONG Gid)
2312 {
2313 //
2314 // Create the user account.
2315 //
2316 // N.B. The user may already exist if the test was run previously.
2317 //
2318
2319 std::wstring CreateUser{L"/usr/sbin/adduser --quiet --force-badname --disabled-password --gecos \"\" "};
2320 CreateUser += Username.c_str();
2321 LxsstuLaunchWsl(CreateUser.c_str());
2322
2323 //
2324 // Create an unnamed pipe to read the output of the launched commands.
2325 //
2326
2327 wil::unique_handle ReadPipe;
2328 wil::unique_handle WritePipe;
2329 THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&ReadPipe, &WritePipe, NULL, 0));
2330
2331 //
2332 // Mark the write end of the pipe as inheritable.
2333 //
2334
2335 THROW_IF_WIN32_BOOL_FALSE(SetHandleInformation(WritePipe.get(), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT));
2336
2337 //
2338 // Query the UID.
2339 //
2340
2341 std::wstring QueryUid{L"/usr/bin/id -u "};
2342 QueryUid += Username.c_str();
2343 THROW_HR_IF(E_UNEXPECTED, (LxsstuLaunchWsl(QueryUid.c_str(), nullptr, WritePipe.get()) != 0));
2344
2345 CHAR Buffer[64];
2346 DWORD BytesRead;
2347 THROW_IF_WIN32_BOOL_FALSE(ReadFile(ReadPipe.get(), Buffer, (sizeof(Buffer) - 1), &BytesRead, NULL));
2348 Buffer[BytesRead] = ANSI_NULL;
2349 const ULONG UidLocal = std::stoul(Buffer, nullptr, 10);
2350
2351 //
2352 // Query the GID.
2353 //
2354
2355 std::wstring QueryGid{L"/usr/bin/id -g "};
2356 QueryGid += Username.c_str();
2357 THROW_HR_IF(E_UNEXPECTED, (LxsstuLaunchWsl(QueryGid.c_str(), nullptr, WritePipe.get()) != 0));
2358
2359 THROW_IF_WIN32_BOOL_FALSE(ReadFile(ReadPipe.get(), Buffer, (sizeof(Buffer) - 1), &BytesRead, NULL));
2360 Buffer[BytesRead] = ANSI_NULL;
2361 const ULONG GidLocal = std::stoul(Buffer, nullptr, 10);
2362
2363 //
2364 // Return the queried values to the user.
2365 //
2366
2367 *Uid = UidLocal;
2368 *Gid = GidLocal;
2369 }
2370
2371 std::pair<HANDLE, HANDLE> UseOriginalStdHandles(VOID)
2372
2373 /*++
2374
2375 Routine Description:
2376
2377 Restores the original stdout & stderr handles, if any.
2378
2379 Arguments:
2380 None.
2381
2382 Return Value:
2383
2384 A pair of the previous stdout & stderr handles.
2385
2386 --*/
2387
2388 {
2389 HANDLE PreviousStdout = GetStdHandle(STD_OUTPUT_HANDLE);
2390 HANDLE PreviousStderr = GetStdHandle(STD_ERROR_HANDLE);
2391
2392 if (g_OriginalStdout != nullptr)
2393 {
2394 VERIFY_IS_TRUE(SetStdHandle(STD_OUTPUT_HANDLE, g_OriginalStdout));
2395 }
2396
2397 if (g_OriginalStderr != nullptr)
2398 {
2399 VERIFY_IS_TRUE(SetStdHandle(STD_ERROR_HANDLE, g_OriginalStderr));
2400 }
2401
2402 return {PreviousStdout, PreviousStderr};
2403 }
2404
2405 void RestoreTestStdHandles(_In_ const std::pair<HANDLE, HANDLE>& handles)
2406
2407 /*++
2408
2409 Routine Description:
2410
2411 Assign stdout & stderr handles.
2412
2413 Arguments:
2414 None.
2415
2416 Return Value:
2417
2418 None.
2419
2420 --*/
2421
2422 {
2423 VERIFY_IS_TRUE(SetStdHandle(STD_OUTPUT_HANDLE, handles.first));
2424 VERIFY_IS_TRUE(SetStdHandle(STD_ERROR_HANDLE, handles.second));
2425 }
2426
2427 bool TryLoadDnsResolverMethods() noexcept
2428 {
2429 constexpr auto c_dnsModuleName = L"dnsapi.dll";
2430 const wil::shared_hmodule dnsModule{LoadLibraryEx(c_dnsModuleName, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32)};
2431 if (!dnsModule)
2432 {
2433 return false;
2434 }
2435
2436 try
2437 {
2438 // attempt to find the functions for the DNS tunneling OS APIs.
2439 static LxssDynamicFunction<decltype(DnsQueryRaw)> dnsQueryRaw{dnsModule, "DnsQueryRaw"};
2440 static LxssDynamicFunction<decltype(DnsCancelQueryRaw)> dnsCancelQueryRaw{dnsModule, "DnsCancelQueryRaw"};
2441 static LxssDynamicFunction<decltype(DnsQueryRawResultFree)> dnsQueryRawResultFree{dnsModule, "DnsQueryRawResultFree"};
2442
2443 // Make a dummy call to the DNS APIs to verify if they are working. The APIs are going to be present
2444 // on older OS versions, where they can be turned on/off using a KIR. If the KIR is turned off, the APIs
2445 // will be unusable and will return ERROR_CALL_NOT_IMPLEMENTED.
2446 THROW_HR_IF(E_NOTIMPL, dnsQueryRaw(nullptr, nullptr) == ERROR_CALL_NOT_IMPLEMENTED);
2447 }
2448 catch (...)
2449 {
2450 return false;
2451 }
2452 return true;
2453 }
2454
2455 bool AreExperimentalNetworkingFeaturesSupported()
2456 {
2457 constexpr auto NETWORKING_EXPERIMENTAL_FLOOR_BUILD = 25885;
2458 constexpr auto GALLIUM_FLOOR_BUILD = 25846;
2459 const auto build = wsl::windows::common::helpers::GetWindowsVersion();
2460 return ((build.BuildNumber < GALLIUM_FLOOR_BUILD) || (build.BuildNumber >= GALLIUM_FLOOR_BUILD && build.BuildNumber >= NETWORKING_EXPERIMENTAL_FLOOR_BUILD));
2461 }
2462
2463 bool IsHyperVFirewallSupported() noexcept
2464 {
2465 try
2466 {
2467 // Query for the Hyper-V Firewall profile object. If this object is successfully queried, then
2468 // the OS has the necessary Hyper-V firewall support.
2469 LxsstuLaunchPowershellAndCaptureOutput(L"Get-NetFirewallHyperVProfile");
2470 }
2471 catch (...)
2472 {
2473 return false;
2474 }
2475 return true;
2476 }
2477
2478 std::optional<GUID> GetDistributionId(LPCWSTR Name)
2479 {
2480 // Get the GUID of the test distro
2481 wsl::windows::common::SvcComm service;
2482 for (const auto& e : service.EnumerateDistributions())
2483 {
2484 if (wsl::shared::string::IsEqual(e.DistroName, Name))
2485 {
2486 return e.DistroGuid;
2487 }
2488 }
2489
2490 return {};
2491 }
2492
2493 wil::unique_hkey OpenDistributionKey(LPCWSTR Name)
2494 {
2495 const auto id = GetDistributionId(Name);
2496 if (!id.has_value())
2497 {
2498 return {};
2499 }
2500
2501 const auto idString = wsl::shared::string::GuidToString<wchar_t>(id.value());
2502
2503 const auto userKey = wsl::windows::common::registry::OpenLxssUserKey();
2504 return wsl::windows::common::registry::OpenKey(userKey.get(), idString.c_str(), KEY_ALL_ACCESS);
2505 }
2506
2507 bool WslShutdown()
2508 {
2509 return VERIFY_ARE_EQUAL(0u, LxsstuLaunchWsl(WSL_SHUTDOWN_ARG));
2510 }
2511
2512 void TerminateDistribution(LPCWSTR DistributionName)
2513 {
2514 VERIFY_ARE_EQUAL(0u, LxsstuLaunchWsl(std::format(L"{} {}", WSL_TERMINATE_ARG, DistributionName)));
2515 }
2516
2517 void ValidateOutput(LPCWSTR CommandLine, const std::wstring& ExpectedOutput, const std::wstring& ExpectedWarnings, int ExitCode)
2518 {
2519 auto [output, warnings] = LxsstuLaunchWslAndCaptureOutput(CommandLine, ExitCode);
2520
2521 VERIFY_ARE_EQUAL(ExpectedOutput, output);
2522 VERIFY_ARE_EQUAL(ExpectedWarnings, warnings);
2523 }
2524
2525 // Trim helper method
2526
2527 void Trim(std::wstring& string)
2528 {
2529 // Remove any extra chars (lf, spaces, ...)
2530 std::erase_if(string, [](auto c) { return !isalnum(c); });
2531 }
2532
2533 static std::optional<std::wstring> CaptureEnvValue(const std::wstring& Name)
2534 {
2535 std::wstring value;
2536 HRESULT hr = wil::GetEnvironmentVariableW(Name.c_str(), value);
2537 if (hr == HRESULT_FROM_WIN32(ERROR_ENVVAR_NOT_FOUND))
2538 {
2539 return std::nullopt;
2540 }
2541 THROW_IF_FAILED(hr);
2542 return value;
2543 }
2544
2545 ScopedEnvVariable::ScopedEnvVariable(const std::wstring& Name) : m_name(Name), m_originalValue(CaptureEnvValue(Name))
2546 {
2547 VERIFY_IS_TRUE(SetEnvironmentVariableW(Name.c_str(), nullptr));
2548 }
2549
2550 ScopedEnvVariable::ScopedEnvVariable(const std::wstring& Name, const std::wstring& Value) :
2551 m_name(Name), m_originalValue(CaptureEnvValue(Name))
2552 {
2553 VERIFY_IS_TRUE(SetEnvironmentVariableW(Name.c_str(), Value.c_str()));
2554 }
2555
2556 ScopedEnvVariable::~ScopedEnvVariable()
2557 {
2558 VERIFY_IS_TRUE(SetEnvironmentVariableW(m_name.c_str(), m_originalValue.has_value() ? m_originalValue->c_str() : nullptr));
2559 }
2560
2561 void ScopedEnvVariable::Set(const std::wstring& Value)
2562 {
2563 VERIFY_IS_TRUE(SetEnvironmentVariableW(m_name.c_str(), Value.c_str()));
2564 }
2565
2566 void ScopedEnvVariable::Clear()
2567 {
2568 VERIFY_IS_TRUE(SetEnvironmentVariableW(m_name.c_str(), nullptr));
2569 }
2570
2571 UniqueWebServer::UniqueWebServer(LPCWSTR Endpoint, LPCWSTR Content, UINT StatusCode)
2572 {
2573 auto cmd = std::format(
2574 LR"(Powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "
2575 $ErrorActionPreference = 'Stop'
2576 $server = New-Object System.Net.HttpListener
2577 $server.Prefixes.Add('{}')
2578 $server.Start()
2579 while ($true)
2580 {{
2581 $context = $server.GetContext()
2582 $context.Response.StatusCode = {}
2583 $content = [Text.Encoding]::UTF8.GetBytes('{}')
2584 $context.Response.OutputStream.Write($content , 0, $content.length)
2585 $context.Response.close()
2586 }}")",
2587 Endpoint,
2588 StatusCode,
2589 Content);
2590
2591 m_process = LxsstuStartProcess(cmd.data());
2592 }
2593
2594 UniqueWebServer::UniqueWebServer(LPCWSTR Endpoint, const std::filesystem::path& File)
2595 {
2596 auto cmd = std::format(
2597 LR"(Powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "
2598 $ErrorActionPreference = 'Stop'
2599 $server = New-Object System.Net.HttpListener
2600 $server.Prefixes.Add('{}')
2601 $server.Start()
2602 while ($true)
2603 {{
2604 $context = $server.GetContext()
2605 $context.Response.StatusCode
2606 $content = [System.IO.File]::ReadAllBytes('{}')
2607 $context.Response.ContentLength64 = $content.length
2608 $context.Response.ContentType = 'application/octet-stream'
2609 $context.Response.OutputStream.Write($content, 0, $content.length)
2610 $context.Response.close()
2611 }}")",
2612 Endpoint,
2613 File.wstring());
2614
2615 m_process = LxsstuStartProcess(cmd.data());
2616 }
2617
2618 UniqueWebServer::~UniqueWebServer()
2619 {
2620 if (!TerminateProcess(m_process.get(), 0))
2621 {
2622 LogError("TerminateProcess failed, %lu", GetLastError());
2623 }
2624 }
2625
2626 DistroFileChange::DistroFileChange(LPCWSTR Path, bool exists) : m_path(Path)
2627 {
2628 if (exists)
2629 {
2630 m_originalContent = LxsstuLaunchWslAndCaptureOutput(std::format(L"cat '{}'", m_path)).first;
2631 }
2632 }
2633
2634 DistroFileChange::~DistroFileChange()
2635 {
2636 if (m_originalContent.has_value())
2637 {
2638 SetContent(m_originalContent->c_str());
2639 }
2640 else
2641 {
2642 Delete();
2643 }
2644 }
2645
2646 void DistroFileChange::SetContent(LPCWSTR Content)
2647 {
2648 const auto cmd = LxssGenerateWslCommandLine(std::format(L" -u root cat > '{}'", m_path).c_str());
2649 wsl::windows::common::SubProcess process(nullptr, cmd.c_str());
2650
2651 auto [read, write] = CreateSubprocessPipe(true, false);
2652
2653 process.SetStdHandles(read.get(), nullptr, nullptr);
2654 const auto processHandle = process.Start();
2655
2656 const auto utf8content = wsl::shared::string::WideToMultiByte(Content);
2657 auto index = 0;
2658
2659 while (index < utf8content.size())
2660 {
2661 DWORD written{};
2662
2663 VERIFY_IS_TRUE(WriteFile(write.get(), utf8content.data() + index, static_cast<DWORD>(utf8content.size() - index), &written, nullptr));
2664
2665 index += written;
2666 }
2667
2668 write.reset();
2669
2670 VERIFY_ARE_EQUAL(wsl::windows::common::SubProcess::GetExitCode(processHandle.get()), 0L);
2671 }
2672
2673 void DistroFileChange::Delete()
2674 {
2675 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"-u root rm -f '{}'", m_path).c_str()), 0L);
2676 }
2677
2678 std::string ReadToString(SOCKET Handle)
2679 {
2680 std::string output;
2681 DWORD offset = 0;
2682 while (true) // TODO: timeout
2683 {
2684 constexpr auto bufferSize = 512;
2685
2686 output.resize(output.size() + bufferSize);
2687 int bytesRead = 0;
2688
2689 if ((bytesRead = recv(Handle, &output[offset], bufferSize, 0)) < 0)
2690 {
2691 LogError("recv failed with %lu", GetLastError());
2692 VERIFY_FAIL();
2693 }
2694
2695 if (bytesRead == 0)
2696 {
2697 output.resize(offset);
2698 break;
2699 }
2700
2701 output.resize(offset + bytesRead);
2702 offset += bytesRead;
2703 }
2704
2705 return output;
2706 }
2707
2708 std::pair<wil::unique_socket, wil::unique_socket> MakeSocketPair()
2709 {
2710 wil::unique_socket listenSocket{WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, nullptr, 0, WSA_FLAG_OVERLAPPED)};
2711 THROW_LAST_ERROR_IF(!listenSocket);
2712
2713 sockaddr_in bindAddr{};
2714 bindAddr.sin_family = AF_INET;
2715 bindAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
2716 bindAddr.sin_port = 0;
2717 THROW_LAST_ERROR_IF(bind(listenSocket.get(), reinterpret_cast<sockaddr*>(&bindAddr), sizeof(bindAddr)) == SOCKET_ERROR);
2718 THROW_LAST_ERROR_IF(listen(listenSocket.get(), 1) == SOCKET_ERROR);
2719
2720 sockaddr_in boundAddr{};
2721 int boundAddrLen = sizeof(boundAddr);
2722 THROW_LAST_ERROR_IF(getsockname(listenSocket.get(), reinterpret_cast<sockaddr*>(&boundAddr), &boundAddrLen) == SOCKET_ERROR);
2723
2724 wil::unique_socket clientSocket{WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, nullptr, 0, WSA_FLAG_OVERLAPPED)};
2725 THROW_LAST_ERROR_IF(!clientSocket);
2726 THROW_LAST_ERROR_IF(connect(clientSocket.get(), reinterpret_cast<sockaddr*>(&boundAddr), sizeof(boundAddr)) == SOCKET_ERROR);
2727
2728 wil::unique_socket serverSocket{accept(listenSocket.get(), nullptr, nullptr)};
2729 THROW_LAST_ERROR_IF(!serverSocket);
2730
2731 return {std::move(clientSocket), std::move(serverSocket)};
2732 }
2733
2734 std::string ReadToString(HANDLE Handle)
2735 {
2736 std::string output;
2737 DWORD offset = 0;
2738 constexpr DWORD bufferSize = 4096;
2739
2740 while (true)
2741 {
2742 output.resize(offset + bufferSize);
2743 DWORD bytesRead = 0;
2744 if (!ReadFile(Handle, output.data() + offset, bufferSize, &bytesRead, nullptr))
2745 {
2746 VERIFY_ARE_EQUAL(GetLastError(), ERROR_BROKEN_PIPE);
2747 }
2748
2749 offset += bytesRead;
2750 output.resize(offset);
2751 if (bytesRead == 0)
2752 {
2753 break;
2754 }
2755 }
2756
2757 return output;
2758 }
2759
2760 void VerifyPatternMatch(const std::string& Content, const std::string& Pattern)
2761 {
2762 if (!PathMatchSpecA(Content.c_str(), Pattern.c_str()))
2763 {
2764 std::wstring message = std::format(L"Output: '{}' didn't match pattern: '{}'", Content, Pattern);
2765 VERIFY_FAIL(message.c_str());
2766 }
2767 }
2768
2769 std::string EscapeString(const std::string& Input)
2770 {
2771 std::string Output;
2772
2773 for (const auto& e : Input)
2774 {
2775 if (e == '\n')
2776 {
2777 Output += "\\n";
2778 }
2779 else if (e == '\r')
2780 {
2781 Output += "\\r";
2782 }
2783 else if (e == '\0')
2784 {
2785 Output += "\\0";
2786 }
2787 else if (e == '\t')
2788 {
2789 Output += "\\t";
2790 }
2791 else if (e == '\x1b') // ESC character - start of VT sequence
2792 {
2793 Output += "\\x1b";
2794 }
2795 else
2796 {
2797 Output += e;
2798 }
2799 }
2800
2801 return Output;
2802 }
2803
2804 PartialHandleRead::PartialHandleRead(HANDLE Handle) : m_handle(Handle)
2805 {
2806 m_thread = std::thread(std::bind(&PartialHandleRead::Run, this));
2807 }
2808
2809 PartialHandleRead::~PartialHandleRead()
2810 {
2811 Stop();
2812 }
2813
2814 void PartialHandleRead::Stop()
2815 {
2816 m_exitEvent.SetEvent();
2817 if (m_thread.joinable())
2818 {
2819 m_thread.join();
2820 }
2821 }
2822
2823 std::string PartialHandleRead::ReadBytes(size_t Length)
2824 {
2825 wsl::shared::retry::RetryWithTimeout<void>(
2826 [&]() {
2827 std::lock_guard lock{m_mutex};
2828
2829 THROW_HR_IF(E_ABORT, m_data.size() < Length);
2830 },
2831 std::chrono::milliseconds(100),
2832 std::chrono::seconds(60));
2833
2834 std::lock_guard lock{m_mutex};
2835
2836 return m_data.substr(0, Length);
2837 }
2838
2839 std::string PartialHandleRead::ConsumeBytes(size_t Length)
2840 {
2841 wsl::shared::retry::RetryWithTimeout<void>(
2842 [&]() {
2843 std::lock_guard lock{m_mutex};
2844
2845 THROW_HR_IF(E_ABORT, m_data.size() < Length);
2846 },
2847 std::chrono::milliseconds(100),
2848 std::chrono::seconds(60));
2849
2850 std::lock_guard lock{m_mutex};
2851 std::string result = m_data.substr(0, Length);
2852 m_data.erase(0, Length);
2853 return result;
2854 }
2855
2856 std::string PartialHandleRead::GetData() const
2857 {
2858 std::lock_guard lock{m_mutex};
2859 return m_data;
2860 }
2861
2862 void PartialHandleRead::Expect(const std::string& Expected)
2863 {
2864 auto content = ReadBytes(Expected.size());
2865
2866 VERIFY_ARE_EQUAL(content, Expected);
2867 }
2868
2869 void PartialHandleRead::ExpectConsume(const std::string& Expected)
2870 {
2871 auto content = ConsumeBytes(Expected.size());
2872
2873 if (content != Expected)
2874 {
2875 VERIFY_FAIL(std::format(
2876 L"Expected: '{}' but got: '{}'",
2877 wsl::shared::string::MultiByteToWide(EscapeString(Expected)),
2878 wsl::shared::string::MultiByteToWide(EscapeString(content)))
2879 .c_str());
2880 }
2881 }
2882
2883 void PartialHandleRead::ExpectClosed(DWORD Timeout)
2884 {
2885 VERIFY_ARE_EQUAL(WaitForSingleObject(m_thread.native_handle(), Timeout), WAIT_OBJECT_0);
2886 }
2887
2888 void PartialHandleRead::Run()
2889 try
2890 {
2891 std::vector<gsl::byte> buffer(4096);
2892
2893 while (!m_exitEvent.is_signaled())
2894 {
2895 auto bytesRead = wsl::windows::common::relay::InterruptableRead(m_handle, gsl::make_span(buffer), {m_exitEvent.get()});
2896 if (bytesRead == 0)
2897 {
2898 break;
2899 }
2900
2901 std::lock_guard lock{m_mutex};
2902 m_data.append(reinterpret_cast<char*>(buffer.data()), bytesRead);
2903 }
2904 }
2905 CATCH_LOG();
2906
2907 class ReadHandleWithTargetValue : public wsl::windows::common::io::ReadHandle
2908 {
2909 public:
2910 NON_COPYABLE(ReadHandleWithTargetValue);
2911 NON_MOVABLE(ReadHandleWithTargetValue);
2912
2913 ReadHandleWithTargetValue(wsl::windows::common::io::HandleWrapper&& MovedHandle, std::string_view targetValue) :
2914 ReadHandle(std::move(MovedHandle), [this](const auto& buffer) { m_readBuffer.append(buffer.data(), buffer.size()); }),
2915 m_targetValue(targetValue)
2916 {
2917 }
2918
2919 void Schedule() override
2920 {
2921 ReadHandle::Schedule();
2922 CheckIfTargetFound();
2923 }
2924
2925 void Collect() override
2926 {
2927 ReadHandle::Collect();
2928 CheckIfTargetFound();
2929 }
2930
2931 private:
2932 void CheckIfTargetFound()
2933 {
2934 using namespace wsl::windows::common::io;
2935
2936 if (State == IOHandleStatus::Standby || State == IOHandleStatus::Completed)
2937 {
2938 bool targetFound = (m_readBuffer.find(m_targetValue) != std::string::npos);
2939
2940 if (State == IOHandleStatus::Standby)
2941 {
2942 if (targetFound)
2943 {
2944 State = IOHandleStatus::Completed;
2945 }
2946 }
2947 else
2948 {
2949 THROW_WIN32_IF(ERROR_NOT_FOUND, !targetFound);
2950 }
2951 }
2952 }
2953
2954 std::string m_readBuffer;
2955 std::string m_targetValue;
2956 };
2957
2958 void WaitForOutput(wsl::windows::common::io::HandleWrapper handle, std::string_view targetValue, std::chrono::milliseconds timeout)
2959 {
2960 wsl::windows::common::io::MultiHandleWait io;
2961 io.AddHandle(std::make_unique<ReadHandleWithTargetValue>(std::move(handle), targetValue));
2962 io.Run(timeout);
2963 }
2964
2965 std::filesystem::path GetTestImagePath(std::string_view imageName)
2966 {
2967 std::filesystem::path result = std::filesystem::path{g_testDataPath};
2968
2969 if (imageName == "debian:latest")
2970 {
2971 result /= L"debian-latest.tar";
2972 }
2973 else if (imageName == "python:3.12-alpine")
2974 {
2975 result /= L"python-3_12-alpine.tar";
2976 }
2977 else if (imageName == "alpine:latest")
2978 {
2979 result /= L"alpine-latest.tar";
2980 }
2981 else if (imageName == "hello-world:latest")
2982 {
2983 result /= L"HelloWorldSaved.tar";
2984 }
2985 else if (imageName == "wslc-registry:latest")
2986 {
2987 result /= L"wslc-registry.tar";
2988 }
2989 else
2990 {
2991 THROW_HR_MSG(E_INVALIDARG, "Unknown test image: %hs", imageName.data());
2992 }
2993
2994 return result;
2995 }
2996
2997 void LoadTestImage(IWSLCSession& session, std::string_view imageName)
2998 {
2999 std::filesystem::path imagePath = GetTestImagePath(imageName);
3000 wil::unique_hfile imageFile{
3001 CreateFileW(imagePath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)};
3002 THROW_LAST_ERROR_IF(!imageFile);
3003
3004 LARGE_INTEGER fileSize{};
3005 THROW_LAST_ERROR_IF(!GetFileSizeEx(imageFile.get(), &fileSize));
3006
3007 THROW_IF_FAILED(session.LoadImage(wsl::windows::common::wslutil::ToCOMInputHandle(imageFile.get()), fileSize.QuadPart, nullptr, nullptr));
3008 }
3009
3010 void ExpectHttpResponse(LPCWSTR Url, std::optional<int> expectedCode, bool retry)
3011 {
3012 const winrt::Windows::Web::Http::Filters::HttpBaseProtocolFilter filter;
3013 filter.CacheControl().WriteBehavior(winrt::Windows::Web::Http::Filters::HttpCacheWriteBehavior::NoCache);
3014
3015 const winrt::Windows::Web::Http::HttpClient client(filter);
3016
3017 const auto sendRequest = [&]() {
3018 try
3019 {
3020 LogInfo("Sending request to: %ls", Url);
3021 auto response = client.GetAsync(winrt::Windows::Foundation::Uri(Url)).get();
3022 auto content = response.Content().ReadAsStringAsync().get();
3023
3024 if (expectedCode.has_value())
3025 {
3026 VERIFY_ARE_EQUAL(static_cast<int>(response.StatusCode()), expectedCode.value());
3027 }
3028 else
3029 {
3030 LogError("Unexpected reply for: %ls", Url);
3031 VERIFY_FAIL();
3032 }
3033 }
3034 catch (...)
3035 {
3036 auto result = wil::ResultFromCaughtException();
3037
3038 if (!expectedCode.has_value())
3039 {
3040 // We currently reset the connection if connect() fails inside
3041 // the VM. Consider failing the Windows connect() instead.
3042 VERIFY_ARE_EQUAL(result, HRESULT_FROM_WIN32(WININET_E_INVALID_SERVER_RESPONSE));
3043 return;
3044 }
3045
3046 // Throw so RetryWithTimeout can decide whether to retry.
3047 THROW_HR(result);
3048 }
3049 };
3050
3051 if (retry)
3052 {
3053 wsl::shared::retry::RetryWithTimeout<void>(sendRequest, std::chrono::milliseconds(500), std::chrono::seconds(30), [&]() {
3054 return wil::ResultFromCaughtException() == HRESULT_FROM_WIN32(WININET_E_INVALID_SERVER_RESPONSE);
3055 });
3056 }
3057 else
3058 {
3059 sendRequest();
3060 }
3061 }
3062
3063 std::optional<std::wstring> GetHostAdapterIpv4()
3064 {
3065 auto endpoint = wsl::core::networking::GetHostEndpointSettings();
3066 if (!endpoint || endpoint->PreferredIpAddress.AddressString.empty())
3067 {
3068 return {};
3069 }
3070
3071 return endpoint->PreferredIpAddress.AddressString;
3072 }
3073
3074 void SetPathAccess(const std::filesystem::path& path, DWORD Permissions, ACCESS_MODE Mode)
3075 {
3076 auto [everyoneSid, everyoneSidBuffer] = wsl::windows::common::security::CreateSid(SECURITY_WORLD_SID_AUTHORITY, SECURITY_WORLD_RID);
3077
3078 EXPLICIT_ACCESSW ea{};
3079 ea.grfAccessPermissions = Permissions;
3080 ea.grfAccessMode = Mode;
3081 ea.grfInheritance = NO_INHERITANCE;
3082 ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
3083 ea.Trustee.ptstrName = static_cast<LPWSTR>(everyoneSid);
3084
3085 PACL acl = nullptr;
3086 wil::unique_hlocal descriptor;
3087 THROW_IF_WIN32_ERROR(
3088 GetNamedSecurityInfoW(path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, &acl, nullptr, &descriptor));
3089
3090 wsl::windows::common::security::unique_acl newAcl;
3091 THROW_IF_WIN32_ERROR(SetEntriesInAclW(1, &ea, acl, &newAcl));
3092
3093 THROW_IF_WIN32_ERROR(SetNamedSecurityInfoW(
3094 const_cast<LPWSTR>(path.c_str()), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, newAcl.get(), nullptr));
3095 }
3096
3097 void WriteSocket(SOCKET Socket, const void* data, size_t size)
3098 {
3099 while (size > 0)
3100 {
3101 auto result = send(Socket, static_cast<const char*>(data), gsl::narrow_cast<int>(size), 0);
3102 VERIFY_IS_TRUE(result > 0);
3103
3104 size -= result;
3105 data = static_cast<const char*>(data) + result;
3106 }
3107 }
3108
3109 void ValidateCOMErrorMessage(const std::optional<std::wstring>& Expected, const std::source_location& Source)
3110 {
3111 auto comError = wsl::windows::common::wslutil::GetCOMErrorInfo();
3112
3113 if (comError.has_value())
3114 {
3115 if (!Expected.has_value())
3116 {
3117 LogError("Unexpected COM error: '%ls'. Source: %hs", comError->Message.get(), std::format("{}", Source).c_str());
3118 VERIFY_FAIL();
3119 }
3120
3121 VERIFY_ARE_EQUAL(Expected.value(), comError->Message.get());
3122 }
3123 else
3124 {
3125 if (Expected.has_value())
3126 {
3127 LogError("Expected COM error: '%ls' but none was set. Source: %hs", Expected->c_str(), std::format("{}", Source).c_str());
3128 VERIFY_FAIL();
3129 }
3130 }
3131 }
3132
3133 void ValidateCOMErrorMessageContains(const std::wstring& ExpectedSubstring)
3134 {
3135 auto comError = wsl::windows::common::wslutil::GetCOMErrorInfo();
3136
3137 if (comError.has_value())
3138 {
3139 if (!comError->Message)
3140 {
3141 LogError("Expected COM error containing: '%ls', but COM error message was null", ExpectedSubstring.c_str());
3142 VERIFY_FAIL();
3143 }
3144
3145 if (wcsstr(comError->Message.get(), ExpectedSubstring.c_str()) == nullptr)
3146 {
3147 LogError("Expected COM error containing: '%ls', but got: '%ls'", ExpectedSubstring.c_str(), comError->Message.get());
3148 VERIFY_FAIL();
3149 }
3150 }
3151 else
3152 {
3153 LogError("Expected COM error containing: '%ls' but none was set", ExpectedSubstring.c_str());
3154 VERIFY_FAIL();
3155 }
3156 }
3157
3158 std::wstring FormatErrorMessage(std::wstring_view message, std::wstring_view errorCode)
3159 {
3160 return std::format(
3161 L"{}\r\nError code: {}\r\n"
3162 L"If this error was unexpected, please consider searching for existing issues or filing a new issue at "
3163 L"https://github.com/microsoft/WSL/issues.\r\n",
3164 message,
3165 errorCode);
3166 }