@samitouri / QOSAMI-WSL / commits / a03462f5

Fix issue with svccommio incorrectly resetting console state in the move operator (#14083)

The ConsoleInput and ConsoleOutput classes used = default move operations, which caused the moved-from temporary's destructor to restore the original console mode (re-enabling ENABLE_PROCESSED_INPUT) after the object was moved into the std::optional. The fix uses emplace() to construct objects directly in-place, avoiding moves entirely. Move operations are now deleted to prevent future misuse. Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com>

Ben Hillis committed Jan 20, 2026 at 13:27 UTC a03462f50b5c609c13e5293207abf41e560d2e96
2 files changed +30 -53
src/windows/common/svccommio.cpp
+19 -38
@@ -72,22 +72,9 @@ CATCH_LOG()
72
73 namespace wsl::windows::common {
74
75 -std::optional<ConsoleInput> ConsoleInput::Create(HANDLE Handle)
75 +ConsoleInput::ConsoleInput(HANDLE Handle, DWORD SavedMode) :
76 + m_Handle(Handle), m_SavedMode(SavedMode), m_SavedCodePage(GetConsoleCP())
77 {
77 - DWORD Mode;
78 - if (GetFileType(Handle) == FILE_TYPE_CHAR && GetConsoleMode(Handle, &Mode))
79 - {
80 - return ConsoleInput(Handle, Mode);
81 - }
82 -
83 - return std::nullopt;
84 -}
85 -
86 -ConsoleInput::ConsoleInput(HANDLE Handle, DWORD SavedMode) : m_Handle(Handle), m_SavedMode(SavedMode)
87 -{
88 - // Save code page.
89 - m_SavedCodePage = GetConsoleCP();
90 -
78 // Configure for raw input with VT support.
79 DWORD NewMode = m_SavedMode;
80 WI_SetAllFlags(NewMode, ENABLE_WINDOW_INPUT | ENABLE_VIRTUAL_TERMINAL_INPUT);
@@ -104,29 +91,9 @@ ConsoleInput::~ConsoleInput()
91 LOG_IF_WIN32_BOOL_FALSE(SetConsoleCP(m_SavedCodePage));
92 }
93
107 -std::optional<ConsoleOutput> ConsoleOutput::Create()
108 -{
109 - wil::unique_hfile ConsoleHandle(
110 - CreateFileW(L"CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr));
111 -
112 - if (ConsoleHandle)
113 - {
114 - DWORD Mode;
115 - if (GetConsoleMode(ConsoleHandle.get(), &Mode))
116 - {
117 - return ConsoleOutput(std::move(ConsoleHandle), Mode);
118 - }
119 - }
120 -
121 - return std::nullopt;
122 -}
123 -
94 ConsoleOutput::ConsoleOutput(wil::unique_hfile&& ConsoleHandle, DWORD SavedMode) :
125 - m_ConsoleHandle(std::move(ConsoleHandle)), m_SavedMode(SavedMode)
95 + m_ConsoleHandle(std::move(ConsoleHandle)), m_SavedMode(SavedMode), m_SavedCodePage(GetConsoleOutputCP())
96 {
127 - // Save code page.
128 - m_SavedCodePage = GetConsoleOutputCP();
129 -
97 // Configure for VT output.
98 DWORD NewMode = m_SavedMode;
99 WI_SetAllFlags(NewMode, ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN);
@@ -149,10 +116,24 @@ SvcCommIo::SvcCommIo()
116 const HANDLE ErrorHandle = GetStdHandle(STD_ERROR_HANDLE);
117
118 // Configure input console
152 - m_ConsoleInput = ConsoleInput::Create(InputHandle);
119 + DWORD InputMode;
120 + if (GetFileType(InputHandle) == FILE_TYPE_CHAR && GetConsoleMode(InputHandle, &InputMode))
121 + {
122 + m_ConsoleInput.emplace(InputHandle, InputMode);
123 + }
124
125 // Configure output console
155 - m_ConsoleOutput = ConsoleOutput::Create();
126 + wil::unique_hfile ConsoleHandle(
127 + CreateFileW(L"CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr));
128 +
129 + if (ConsoleHandle)
130 + {
131 + DWORD OutputMode;
132 + if (GetConsoleMode(ConsoleHandle.get(), &OutputMode))
133 + {
134 + m_ConsoleOutput.emplace(std::move(ConsoleHandle), OutputMode);
135 + }
136 + }
137
138 // Initialize the standard handles structure
139 const bool IsConsoleInput = m_ConsoleInput.has_value();
src/windows/common/svccommio.hpp
+11 -15
@@ -25,38 +25,34 @@ namespace wsl::windows::common {
25 class ConsoleInput
26 {
27 public:
28 - static std::optional<ConsoleInput> Create(HANDLE Handle);
28 + ConsoleInput(HANDLE Handle, DWORD SavedMode);
29 ~ConsoleInput();
30 ConsoleInput(const ConsoleInput&) = delete;
31 ConsoleInput& operator=(const ConsoleInput&) = delete;
32 - ConsoleInput(ConsoleInput&&) = default;
33 - ConsoleInput& operator=(ConsoleInput&&) = default;
32 + ConsoleInput(ConsoleInput&&) = delete;
33 + ConsoleInput& operator=(ConsoleInput&&) = delete;
34
35 private:
36 - ConsoleInput(HANDLE Handle, DWORD SavedMode);
37 -
38 - HANDLE m_Handle = nullptr;
39 - DWORD m_SavedMode = 0;
40 - UINT m_SavedCodePage = 0;
36 + HANDLE m_Handle{};
37 + DWORD m_SavedMode{};
38 + UINT m_SavedCodePage{};
39 };
40
41 // RAII wrapper for console output configuration and restoration
42 class ConsoleOutput
43 {
44 public:
47 - static std::optional<ConsoleOutput> Create();
45 + ConsoleOutput(wil::unique_hfile&& ConsoleHandle, DWORD SavedMode);
46 ~ConsoleOutput();
47 ConsoleOutput(const ConsoleOutput&) = delete;
48 ConsoleOutput& operator=(const ConsoleOutput&) = delete;
51 - ConsoleOutput(ConsoleOutput&&) = default;
52 - ConsoleOutput& operator=(ConsoleOutput&&) = default;
49 + ConsoleOutput(ConsoleOutput&&) = delete;
50 + ConsoleOutput& operator=(ConsoleOutput&&) = delete;
51
52 private:
55 - ConsoleOutput(wil::unique_hfile&& ConsoleHandle, DWORD SavedMode);
56 -
53 wil::unique_hfile m_ConsoleHandle;
58 - DWORD m_SavedMode = 0;
59 - UINT m_SavedCodePage = 0;
54 + DWORD m_SavedMode{};
55 + UINT m_SavedCodePage{};
56 };
57
58 class SvcCommIo