Repository: microsoft/terminal (language: cpp)

Issue:
Potential crash when calling ITerminalConnection::WriteInput
### Windows Terminal version

Commit edfa3ea0f0080eadf7f01b463ed0a8638de6ce04

### Windows build number

10.0.19045.4651

### Other Software

_No response_

### Steps to reproduce

1. Patch the `DeviceAttributes` implementation in adaptDispatch.cpp to respond like this:
    ```cpp
    _api.ReturnResponse({L"ABCD", 3});
    ```
2. Open a WSL shell in OpenConsole and execute this:
    ```
    printf "\e[c"; read
    ```
3. Note that it successfully responds with `ABC`.
4. Now try the same thing in a WSL shell from within Windows Terminal.


### Expected Behavior

It should also respond successfully with `ABC`.

### Actual Behavior

The terminal crashes.

The `ReturnResponse` method takes a `std::wstring_view`, but the Windows Terminal implementation calls `ControlCore::_sendInputToConnection`, which calls `ITerminalConnection::WriteInput`, which expects an `hstring`. When constructing an `hstring` from a `std::wstring_view` it's required that the string be null-terminated, otherwise it aborts. And since `std::wstring_view` is obviously not guaranteed to be null-terminated, this is asking for trouble.

I know the example I've given above is contrived, but that's just because it was the easiest way to demonstrate the problem. There are a number of places in the `AdaptDispatch` class that are calling `ReturnResponse` with a `fmt::basic_memory_buffer`, and that's not guaranteed to be null-terminated. It's just a matter of luck whether that code crashes or not.


Discussion / hints:

Fix patch:
diff --git a/src/cascadia/TerminalApp/DebugTapConnection.cpp b/src/cascadia/TerminalApp/DebugTapConnection.cpp
index 25251bd9dfd..6d2ccc0b752 100644
--- a/src/cascadia/TerminalApp/DebugTapConnection.cpp
+++ b/src/cascadia/TerminalApp/DebugTapConnection.cpp
@@ -39,10 +39,10 @@ namespace winrt::Microsoft::TerminalApp::implementation
 
             _wrappedConnection.Start();
         }
-        void WriteInput(const hstring& data)
+        void WriteInput(const winrt::array_view<const char16_t> buffer)
         {
-            _pairedTap->_PrintInput(data);
-            _wrappedConnection.WriteInput(data);
+            _pairedTap->_PrintInput(winrt_array_to_wstring_view(buffer));
+            _wrappedConnection.WriteInput(buffer);
         }
         void Resize(uint32_t rows, uint32_t columns) { _wrappedConnection.Resize(rows, columns); }
         void Close() { _wrappedConnection.Close(); }
@@ -77,13 +77,13 @@ namespace winrt::Microsoft::TerminalApp::implementation
         _start.count_down();
     }
 
-    void DebugTapConnection::WriteInput(const hstring& data)
+    void DebugTapConnection::WriteInput(const winrt::array_view<const char16_t> buffer)
     {
         // If the user types into the tap side, forward it to the input side
         if (auto strongInput{ _inputSide.get() })
         {
             auto inputAsTap{ winrt::get_self<DebugInputTapConnection>(strongInput) };
-            inputAsTap->WriteInput(data);
+            inputAsTap->WriteInput(buffer);
         }
     }
 
@@ -117,7 +117,7 @@ namespace winrt::Microsoft::TerminalApp::implementation
         return ConnectionState::Failed;
     }
 
-    void DebugTapConnection::_OutputHandler(const hstring str)
+    void DebugTapConnection::_OutputHandler(const std::wstring_view str)
     {
         auto output = til::visualize_control_codes(str);
         // To make the output easier to read, we introduce a line break whenever
@@ -131,7 +131,7 @@ namespace winrt::Microsoft::TerminalApp::implementation
     }
 
     // Called by the DebugInputTapConnection to print user input
-    void DebugTapConnection::_PrintInput(const hstring& str)
+    void DebugTapConnection::_PrintInput(const std::wstring_view str)
     {
         auto clean{ til::visualize_control_codes(str) };
         auto formatted{ wil::str_printf<std::wstring>(L"\x1b[91m%ls\x1b[m", clean.data()) };
diff --git a/src/cascadia/TerminalApp/DebugTapConnection.h b/src/cascadia/TerminalApp/DebugTapConnection.h
index 933270df139..70b607e82ff 100644
--- a/src/cascadia/TerminalApp/DebugTapConnection.h
+++ b/src/cascadia/TerminalApp/DebugTapConnection.h
@@ -16,7 +16,7 @@ namespace winrt::Microsoft::TerminalApp::implementation
         void Initialize(const Windows::Foundation::Collections::ValueSet& /*settings*/){};
         ~DebugTapConnection();
         void Start();
-        void WriteInput(const hstring& data);
+        void WriteInput(const winrt::array_view<const char16_t> data);
         void Resize(uint32_t rows, uint32_t columns);
         void Close();
 
@@ -30,8 +30,8 @@ namespace winrt::Microsoft::TerminalApp::implementation
         til::typed_event<winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection, winrt::Windows::Foundation::IInspectable> StateChanged;
 
     private:
-        void _PrintInput(const hstring& data);
-        void _OutputHandler(const hstring str);
+        void _PrintInput(const std::wstring_view data);
+        void _OutputHandler(const std::wstring_view str);
 
         winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection::TerminalOutput_revoker _outputRevoker;
         winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection::StateChanged_revoker _stateChangedRevoker;
diff --git a/src/cascadia/TerminalAzBridge/main.cpp b/src/cascadia/TerminalAzBridge/main.cpp
index c5515f7ff57..05e3e99b23a 100644
--- a/src/cascadia/TerminalAzBridge/main.cpp
+++ b/src/cascadia/TerminalAzBridge/main.cpp
@@ -42,7 +42,7 @@ static ConnectionState RunConnectionToCompletion(const ITerminalConnection& conn
             auto input = reader.Read();
             if (input)
             {
-                connection.WriteInput(*input);
+                connection.WriteInput(winrt_wstring_to_array_view(*input));
             }
         }
     }).detach();
diff --git a/src/cascadia/TerminalConnection/AzureConnection.cpp b/src/cascadia/TerminalConnection/AzureConnection.cpp
index 210f7cc06c1..a0f4e0f926b 100644
--- a/src/cascadia/TerminalConnection/AzureConnection.cpp
+++ b/src/cascadia/TerminalConnection/AzureConnection.cpp
@@ -186,7 +186,12 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
     // - handles the different possible inputs in the different states
     // Arguments:
     // the user's input
-    void AzureConnection::WriteInput(const hstring& data)
+    void AzureConnection::WriteInput(const winrt::array_view<const char16_t> buffer)
+    {
+        _writeInput(winrt_array_to_wstring_view(buffer));
+    }
+
+    void AzureConnection::_writeInput(const std::wstring_view data)
     {
         // We read input while connected AND connecting.
         if (!_isStateOneOf(ConnectionState::Connected, ConnectionState::Connecting))
@@ -804,7 +809,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
         std::swap(_userInput, queuedUserInput);
         if (queuedUserInput.size() > 0)
         {
-            WriteInput(static_cast<winrt::hstring>(queuedUserInput)); // send the user's queued up input back through
+            _writeInput(queuedUserInput); // send the user's queued up input back through
         }
     }
 
diff --git a/src/cascadia/TerminalConnection/AzureConnection.h b/src/cascadia/TerminalConnection/AzureConnection.h
index afb5d6e8b9c..3b8a092692f 100644
--- a/src/cascadia/TerminalConnection/AzureConnection.h
+++ b/src/cascadia/TerminalConnection/AzureConnection.h
@@ -22,7 +22,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
         void Initialize(const Windows::Foundation::Collections::ValueSet& settings);
 
         void Start();
-        void WriteInput(const hstring& data);
+        void WriteInput(const winrt::array_view<const char16_t> buffer);
         void Resize(uint32_t rows, uint32_t columns);
         void Close();
 
@@ -66,6 +66,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
         std::vector<::Microsoft::Terminal::Azure::Tenant> _tenantList;
         std::optional<::Microsoft::Terminal::Azure::Tenant> _currentTenant;
 
+        void _writeInput(const std::wstring_view str);
         void _WriteStringWithNewline(const std::wstring_view str);
         void _WriteCaughtExceptionRecord();
         winrt::Windows::Data::Json::JsonObject _SendRequestReturningJson(std::wstring_view uri, const winrt::Windows::Web::Http::IHttpContent& content = nullptr, winrt::Windows::Web::Http::HttpMethod method = nullptr, const winrt::Windows::Foundation::Uri referer = nullptr);
diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp
index 23a7e263b84..1cb7689c4c1 100644
--- a/src/cascadia/TerminalConnection/ConptyConnection.cpp
+++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp
@@ -473,8 +473,10 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
     }
     CATCH_LOG()
 
-    void ConptyConnection::WriteInput(const hstring& data)
+    void ConptyConnection::WriteInput(const winrt::array_view<const char16_t> buffer)
     {
+        const auto data = winrt_array_to_wstring_view(buffer);
+
         if (!_isConnected())
         {
             return;
diff --git a/src/cascadia/TerminalConnection/ConptyConnection.h b/src/cascadia/TerminalConnection/ConptyConnection.h
index 85f292baad7..083005e70ea 100644
--- a/src/cascadia/TerminalConnection/ConptyConnection.h
+++ b/src/cascadia/TerminalConnection/ConptyConnection.h
@@ -21,7 +21,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
         static winrt::fire_and_forget final_release(std::unique_ptr<ConptyConnection> connection);
 
         void Start();
-        void WriteInput(const hstring& data);
+        void WriteInput(const winrt::array_view<const char16_t> buffer);
         void Resize(uint32_t rows, uint32_t columns);
         void Close() noexcept;
         void ClearBuffer();
diff --git a/src/cascadia/TerminalConnection/EchoConnection.cpp b/src/cascadia/TerminalConnection/EchoConnection.cpp
index 5ac052ad6a5..ca28af28e54 100644
--- a/src/cascadia/TerminalConnection/EchoConnection.cpp
+++ b/src/cascadia/TerminalConnection/EchoConnection.cpp
@@ -15,8 +15,9 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
     {
     }
 
-    void EchoConnection::WriteInput(const hstring& data)
+    void EchoConnection::WriteInput(const winrt::array_view<const char16_t> buffer)
     {
+        const auto data = winrt_array_to_wstring_view(buffer);
         std::wstringstream prettyPrint;
         for (const auto& wch : data)
         {
diff --git a/src/cascadia/TerminalConnection/EchoConnection.h b/src/cascadia/TerminalConnection/EchoConnection.h
index cfdf4189db4..92146a87718 100644
--- a/src/cascadia/TerminalConnection/EchoConnection.h
+++ b/src/cascadia/TerminalConnection/EchoConnection.h
@@ -12,7 +12,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
         EchoConnection() noexcept;
 
         void Start() noexcept;
-        void WriteInput(const hstring& data);
+        void WriteInput(const winrt::array_view<const char16_t> buffer);
         void Resize(uint32_t rows, uint32_t columns) noexcept;
         void Close() noexcept;
 
diff --git a/src/cascadia/TerminalConnection/ITerminalConnection.idl b/src/cascadia/TerminalConnection/ITerminalConnection.idl
index bae69c765fd..275b57b2ac9 100644
--- a/src/cascadia/TerminalConnection/ITerminalConnection.idl
+++ b/src/cascadia/TerminalConnection/ITerminalConnection.idl
@@ -20,7 +20,7 @@ namespace Microsoft.Terminal.TerminalConnection
         void Initialize(Windows.Foundation.Collections.ValueSet settings);
 
         void Start();
-        void WriteInput(String data);
+        void WriteInput(Char[] data);
         void Resize(UInt32 rows, UInt32 columns);
         void Close();
 
diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp
index 47ed10b0507..0ea14696a62 100644
--- a/src/cascadia/TerminalControl/ControlCore.cpp
+++ b/src/cascadia/TerminalControl/ControlCore.cpp
@@ -438,7 +438,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         }
         else
         {
-            _connection.WriteInput(wstr);
+            _connection.WriteInput(winrt_wstring_to_array_view(wstr));
         }
     }
 
@@ -2505,7 +2505,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         {
             // _sendInputToConnection() asserts that we aren't in focus mode,
             // but window focus events are always fine to send.
-            _connection.WriteInput(*out);
+            _connection.WriteInput(winrt_wstring_to_array_view(*out));
         }
     }
 
diff --git a/src/cascadia/TerminalSettingsEditor/PreviewConnection.cpp b/src/cascadia/TerminalSettingsEditor/PreviewConnection.cpp
index 6be3a1ddb15..aabfb6579c1 100644
--- a/src/cascadia/TerminalSettingsEditor/PreviewConnection.cpp
+++ b/src/cascadia/TerminalSettingsEditor/PreviewConnection.cpp
@@ -39,7 +39,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
     {
     }
 
-    void PreviewConnection::WriteInput(const hstring& /*data*/)
+    void PreviewConnection::WriteInput(const winrt::array_view<const char16_t> /*data*/)
     {
     }
 
diff --git a/src/cascadia/TerminalSettingsEditor/PreviewConnection.h b/src/cascadia/TerminalSettingsEditor/PreviewConnection.h
index 9c953771349..c988ff5156c 100644
--- a/src/cascadia/TerminalSettingsEditor/PreviewConnection.h
+++ b/src/cascadia/TerminalSettingsEditor/PreviewConnection.h
@@ -23,7 +23,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
 
         void Initialize(const Windows::Foundation::Collections::ValueSet& settings) noexcept;
         void Start() noexcept;
-        void WriteInput(const hstring& data);
+        void WriteInput(const winrt::array_view<const char16_t> buffer);
         void Resize(uint32_t rows, uint32_t columns) noexcept;
         void Close() noexcept;
 
diff --git a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp
index 2aaaed243f3..8c76135d4e9 100644
--- a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp
+++ b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp
@@ -249,9 +249,9 @@ namespace ControlUnitTests
                      L"(leaving the cursor afer 'Bar')");
         for (auto i = 0; i < 40; ++i)
         {
-            conn->WriteInput(L"Foo\r\n");
+            conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
         }
-        conn->WriteInput(L"Bar");
+        conn->WriteInput(winrt_wstring_to_array_view(L"Bar"));
 
         // We printed that 40 times, but the final \r\n bumped the view down one MORE row.
         Log::Comment(L"Check the buffer viewport before the clear");
@@ -286,9 +286,9 @@ namespace ControlUnitTests
                      L"(leaving the cursor afer 'Bar')");
         for (auto i = 0; i < 40; ++i)
         {
-            conn->WriteInput(L"Foo\r\n");
+            conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
         }
-        conn->WriteInput(L"Bar");
+        conn->WriteInput(winrt_wstring_to_array_view(L"Bar"));
 
         // We printed that 40 times, but the final \r\n bumped the view down one MORE row.
         Log::Comment(L"Check the buffer viewport before the clear");
@@ -323,9 +323,9 @@ namespace ControlUnitTests
                      L"(leaving the cursor afer 'Bar')");
         for (auto i = 0; i < 40; ++i)
         {
-            conn->WriteInput(L"Foo\r\n");
+            conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
         }
-        conn->WriteInput(L"Bar");
+        conn->WriteInput(winrt_wstring_to_array_view(L"Bar"));
 
         // We printed that 40 times, but the final \r\n bumped the view down one MORE row.
         Log::Comment(L"Check the buffer viewport before the clear");
@@ -358,25 +358,26 @@ namespace ControlUnitTests
         _standardInit(core);
 
         Log::Comment(L"Print some text");
-        conn->WriteInput(L"This is some text     \r\n");
-        conn->WriteInput(L"with varying amounts  \r\n");
-        conn->WriteInput(L"of whitespace         \r\n");
+        conn->WriteInput(winrt_wstring_to_array_view(L"This is some text     \r\n"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"with varying amounts  \r\n"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"of whitespace         \r\n"));
 
         Log::Comment(L"Check the buffer contents");
         VERIFY_ARE_EQUAL(L"This is some text\r\nwith varying amounts\r\nof whitespace\r\n",
                          core->ReadEntireBuffer());
     }
-    void _writePrompt(const winrt::com_ptr<MockConnection>& conn, const auto& path)
+
+    static void _writePrompt(const winrt::com_ptr<MockConnection>& conn, const std::wstring_view& path)
     {
-        conn->WriteInput(L"\x1b]133;D\x7");
-        conn->WriteInput(L"\x1b]133;A\x7");
-        conn->WriteInput(L"\x1b]9;9;");
-        conn->WriteInput(path);
-        conn->WriteInput(L"\x7");
-        conn->WriteInput(L"PWSH ");
-        conn->WriteInput(path);
-        conn->WriteInput(L"> ");
-        conn->WriteInput(L"\x1b]133;B\x7");
+        conn->WriteInput(winrt_wstring_to_array_view(L"\x1b]133;D\x7"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"\x1b]133;A\x7"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"\x1b]9;9;"));
+        conn->WriteInput(winrt_wstring_to_array_view(path));
+        conn->WriteInput(winrt_wstring_to_array_view(L"\x7"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"PWSH "));
+        conn->WriteInput(winrt_wstring_to_array_view(path));
+        conn->WriteInput(winrt_wstring_to_array_view(L"> "));
+        conn->WriteInput(winrt_wstring_to_array_view(L"\x1b]133;B\x7"));
     }
 
     void ControlCoreTests::TestSelectCommandSimple()
@@ -390,13 +391,13 @@ namespace ControlUnitTests
         Log::Comment(L"Print some text");
 
         _writePrompt(conn, L"C:\\Windows");
-        conn->WriteInput(L"Foo-bar");
-        conn->WriteInput(L"\x1b]133;C\x7");
+        conn->WriteInput(winrt_wstring_to_array_view(L"Foo-bar"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"\x1b]133;C\x7"));
 
-        conn->WriteInput(L"\r\n");
-        conn->WriteInput(L"This is some text     \r\n");
-        conn->WriteInput(L"with varying amounts  \r\n");
-        conn->WriteInput(L"of whitespace         \r\n");
+        conn->WriteInput(winrt_wstring_to_array_view(L"\r\n"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"This is some text     \r\n"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"with varying amounts  \r\n"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"of whitespace         \r\n"));
 
         _writePrompt(conn, L"C:\\Windows");
 
@@ -422,8 +423,8 @@ namespace ControlUnitTests
         }
 
         core->_terminal->ClearSelection();
-        conn->WriteInput(L"Boo-far");
-        conn->WriteInput(L"\x1b]133;C\x7");
+        conn->WriteInput(winrt_wstring_to_array_view(L"Boo-far"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"\x1b]133;C\x7"));
 
         VERIFY_IS_FALSE(core->HasSelection());
         {
@@ -473,13 +474,13 @@ namespace ControlUnitTests
         Log::Comment(L"Print some text");
 
         _writePrompt(conn, L"C:\\Windows");
-        conn->WriteInput(L"Foo-bar");
-        conn->WriteInput(L"\x1b]133;C\x7");
+        conn->WriteInput(winrt_wstring_to_array_view(L"Foo-bar"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"\x1b]133;C\x7"));
 
-        conn->WriteInput(L"\r\n");
-        conn->WriteInput(L"This is some text     \r\n");
-        conn->WriteInput(L"with varying amounts  \r\n");
-        conn->WriteInput(L"of whitespace         \r\n");
+        conn->WriteInput(winrt_wstring_to_array_view(L"\r\n"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"This is some text     \r\n"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"with varying amounts  \r\n"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"of whitespace         \r\n"));
 
         _writePrompt(conn, L"C:\\Windows");
 
@@ -515,13 +516,13 @@ namespace ControlUnitTests
         Log::Comment(L"Print some text");
 
         _writePrompt(conn, L"C:\\Windows");
-        conn->WriteInput(L"Foo-bar");
-        conn->WriteInput(L"\x1b]133;C\x7");
+        conn->WriteInput(winrt_wstring_to_array_view(L"Foo-bar"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"\x1b]133;C\x7"));
 
-        conn->WriteInput(L"\r\n");
-        conn->WriteInput(L"This is some text     \r\n");
-        conn->WriteInput(L"with varying amounts  \r\n");
-        conn->WriteInput(L"of whitespace         \r\n");
+        conn->WriteInput(winrt_wstring_to_array_view(L"\r\n"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"This is some text     \r\n"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"with varying amounts  \r\n"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"of whitespace         \r\n"));
 
         _writePrompt(conn, L"C:\\Windows");
 
@@ -535,7 +536,7 @@ namespace ControlUnitTests
         }
 
         Log::Comment(L"Write 'Bar' to the command...");
-        conn->WriteInput(L"Bar");
+        conn->WriteInput(winrt_wstring_to_array_view(L"Bar"));
         {
             auto historyContext{ core->CommandHistory() };
             // Bar shouldn't be in the history, it should be the current command
@@ -544,9 +545,9 @@ namespace ControlUnitTests
         }
 
         Log::Comment(L"then delete it");
-        conn->WriteInput(L"\b \b");
-        conn->WriteInput(L"\b \b");
-        conn->WriteInput(L"\b \b");
+        conn->WriteInput(winrt_wstring_to_array_view(L"\b \b"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"\b \b"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"\b \b"));
         {
             auto historyContext{ core->CommandHistory() };
             VERIFY_ARE_EQUAL(1u, historyContext.History().Size());
@@ -566,23 +567,23 @@ namespace ControlUnitTests
         Log::Comment(L"Print some text");
 
         _writePrompt(conn, L"C:\\Windows"); // row 0
-        conn->WriteInput(L"Foo-bar"); // row 0
-        conn->WriteInput(L"\x1b]133;C\x7");
+        conn->WriteInput(winrt_wstring_to_array_view(L"Foo-bar")); // row 0
+        conn->WriteInput(winrt_wstring_to_array_view(L"\x1b]133;C\x7"));
 
-        conn->WriteInput(L"\r\n");
-        conn->WriteInput(L"This is some text     \r\n"); // row 1
-        conn->WriteInput(L"with varying amounts  \r\n"); // row 2
-        conn->WriteInput(L"of whitespace         \r\n"); // row 3
+        conn->WriteInput(winrt_wstring_to_array_view(L"\r\n"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"This is some text     \r\n")); // row 1
+        conn->WriteInput(winrt_wstring_to_array_view(L"with varying amounts  \r\n")); // row 2
+        conn->WriteInput(winrt_wstring_to_array_view(L"of whitespace         \r\n")); // row 3
 
         _writePrompt(conn, L"C:\\Windows"); // row 4
-        conn->WriteInput(L"gci");
-        conn->WriteInput(L"\x1b]133;C\x7");
-        conn->WriteInput(L"\r\n");
+        conn->WriteInput(winrt_wstring_to_array_view(L"gci"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"\x1b]133;C\x7"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"\r\n"));
 
         // enough to scroll
         for (auto i = 0; i < 30; i++) // row 5-34
         {
-            conn->WriteInput(L"-a--- 2/8/2024  9:47 README\r\n");
+            conn->WriteInput(winrt_wstring_to_array_view(L"-a--- 2/8/2024  9:47 README\r\n"));
         }
 
         _writePrompt(conn, L"C:\\Windows");
@@ -635,23 +636,23 @@ namespace ControlUnitTests
         Log::Comment(L"Print some text");
 
         _writePrompt(conn, L"C:\\Windows"); // row 0
-        conn->WriteInput(L"Foo-bar"); // row 0
-        conn->WriteInput(L"\x1b]133;C\x7");
+        conn->WriteInput(winrt_wstring_to_array_view(L"Foo-bar")); // row 0
+        conn->WriteInput(winrt_wstring_to_array_view(L"\x1b]133;C\x7"));
 
-        conn->WriteInput(L"\r\n");
-        conn->WriteInput(L"This is some text     \r\n"); // row 1
-        conn->WriteInput(L"with varying amounts  \r\n"); // row 2
-        conn->WriteInput(L"of whitespace         \r\n"); // row 3
+        conn->WriteInput(winrt_wstring_to_array_view(L"\r\n"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"This is some text     \r\n")); // row 1
+        conn->WriteInput(winrt_wstring_to_array_view(L"with varying amounts  \r\n")); // row 2
+        conn->WriteInput(winrt_wstring_to_array_view(L"of whitespace         \r\n")); // row 3
 
         _writePrompt(conn, L"C:\\Windows"); // row 4
-        conn->WriteInput(L"gci");
-        conn->WriteInput(L"\x1b]133;C\x7");
-        conn->WriteInput(L"\r\n");
+        conn->WriteInput(winrt_wstring_to_array_view(L"gci"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"\x1b]133;C\x7"));
+        conn->WriteInput(winrt_wstring_to_array_view(L"\r\n"));
 
         // enough to scroll
         for (auto i = 0; i < 30; i++) // row 5-35
         {
-            conn->WriteInput(L"-a--- 2/8/2024  9:47 README.md\r\n");
+            conn->WriteInput(winrt_wstring_to_array_view(L"-a--- 2/8/2024  9:47 README.md\r\n"));
         }
 
         _writePrompt(conn, L"C:\\Windows");
diff --git a/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp b/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp
index f95d9f0f16d..9ef3edc4fef 100644
--- a/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp
+++ b/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp
@@ -230,7 +230,7 @@ namespace ControlUnitTests
                 expectedBufferHeight++;
             }
 
-            conn->WriteInput(L"Foo\r\n");
+            conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
         }
         // We printed that 40 times, but the final \r\n bumped the view down one MORE row.
         VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
@@ -398,7 +398,7 @@ namespace ControlUnitTests
         Log::Comment(L"Add some test to the terminal so we can scroll");
         for (auto i = 0; i < 40; ++i)
         {
-            conn->WriteInput(L"Foo\r\n");
+            conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
         }
         // We printed that 40 times, but the final \r\n bumped the view down one MORE row.
         VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
@@ -475,7 +475,7 @@ namespace ControlUnitTests
 
         for (auto i = 0; i < 40; ++i)
         {
-            conn->WriteInput(L"Foo\r\n");
+            conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
         }
         // We printed that 40 times, but the final \r\n bumped the view down one MORE row.
         VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
@@ -534,7 +534,7 @@ namespace ControlUnitTests
         interactivity->MouseWheel(modifiers, delta, mousePos, state); // 2/5
         VERIFY_ARE_EQUAL(21, core->ScrollOffset());
 
-        conn->WriteInput(L"Foo\r\n");
+        conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
         VERIFY_ARE_EQUAL(22, core->ScrollOffset());
         interactivity->MouseWheel(modifiers, delta, mousePos, state); // 1/5
         VERIFY_ARE_EQUAL(22, core->ScrollOffset());
@@ -699,7 +699,7 @@ namespace ControlUnitTests
                 expectedBufferHeight++;
             }
 
-            conn->WriteInput(L"Foo\r\n");
+            conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
         }
         // We printed that 40 times, but the final \r\n bumped the view down one MORE row.
         VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
@@ -721,7 +721,7 @@ namespace ControlUnitTests
         }
 
         // Enable VT mouse event tracking
-        conn->WriteInput(L"\x1b[?1003;1006h");
+        conn->WriteInput(winrt_wstring_to_array_view(L"\x1b[?1003;1006h"));
 
         // Mouse clicks in the inactive region (i.e. the top 10 rows in this case) should not register
         Log::Comment(L"Click on the terminal");
@@ -765,7 +765,7 @@ namespace ControlUnitTests
         // at the point where outputting more lines causes circular incrementing
         for (auto i = 0; i < settings->HistorySize() + core->ViewHeight(); ++i)
         {
-            conn->WriteInput(L"Foo\r\n");
+            conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
         }
         VERIFY_ARE_EQUAL(scrollbackLength, core->_terminal->GetScrollOffset());
 
@@ -811,7 +811,7 @@ namespace ControlUnitTests
         VERIFY_ARE_EQUAL(expectedAnchor, core->_terminal->GetSelectionEnd());
 
         Log::Comment(L"Output a line of text");
-        conn->WriteInput(L"Foo\r\n");
+        conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
 
         Log::Comment(L"Verify the location of the selection");
         // The selection should now be 1 row lower
@@ -829,7 +829,7 @@ namespace ControlUnitTests
         VERIFY_ARE_EQUAL(scrollbackLength - 1, core->_terminal->GetScrollOffset());
 
         Log::Comment(L"Output a line of text");
-        conn->WriteInput(L"Foo\r\n");
+        conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
 
         Log::Comment(L"Verify the location of the selection");
         // The selection should now be 1 row lower
@@ -875,7 +875,7 @@ namespace ControlUnitTests
         Log::Comment(L"Output a line ant move the mouse a little to update the selection, all at once");
         // Same as above. The viewport has moved, so the mouse is still over the
         // same character, even though it's at a new offset.
-        conn->WriteInput(L"Foo\r\n");
+        conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
         expectedAnchor.y -= 1;
         VERIFY_ARE_EQUAL(scrollbackLength - 3, core->_terminal->GetScrollOffset());
         interactivity->PointerMoved(leftMouseDown,
@@ -900,7 +900,7 @@ namespace ControlUnitTests
         // Output enough text for the selection to get pushed off the buffer
         for (auto i = 0; i < settings->HistorySize() + core->ViewHeight(); ++i)
         {
-            conn->WriteInput(L"Foo\r\n");
+            conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
         }
         // Verify that the selection got reset
         VERIFY_IS_FALSE(core->HasSelection());
@@ -954,7 +954,7 @@ namespace ControlUnitTests
         // Output enough text for view to start scrolling
         for (auto i = 0; i < core->ViewHeight() * 2; ++i)
         {
-            conn->WriteInput(L"Foo\r\n");
+            conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
         }
 
         // Start checking output
@@ -1018,7 +1018,7 @@ namespace ControlUnitTests
                                       modifiers,
                                       cursorPosition0.to_core_point());
         // Flush it out.
-        conn->WriteInput(L"sentinel");
+        conn->WriteInput(winrt_wstring_to_array_view(L"sentinel"));
         VERIFY_ARE_EQUAL(0u, expectedOutput.size(), L"Validate we drained all the expected output");
 
         // This is the part as mentioned in GH#12719
diff --git a/src/cascadia/UnitTests_Control/MockConnection.h b/src/cascadia/UnitTests_Control/MockConnection.h
index abbed690952..90758a4ce31 100644
--- a/src/cascadia/UnitTests_Control/MockConnection.h
+++ b/src/cascadia/UnitTests_Control/MockConnection.h
@@ -16,9 +16,9 @@ namespace ControlUnitTests
 
         void Initialize(const winrt::Windows::Foundation::Collections::ValueSet& /*settings*/){};
         void Start() noexcept {};
-        void WriteInput(const winrt::hstring& data)
+        void WriteInput(const winrt::array_view<const char16_t> data)
         {
-            TerminalOutput.raise(data);
+            TerminalOutput.raise(winrt_array_to_wstring_view(data));
         }
         void Resize(uint32_t /*rows*/, uint32_t /*columns*/) noexcept {}
         void Close() noexcept {}
diff --git a/src/cascadia/inc/cppwinrt_utils.h b/src/cascadia/inc/cppwinrt_utils.h
index 0f35d6eefc4..851156ef481 100644
--- a/src/cascadia/inc/cppwinrt_utils.h
+++ b/src/cascadia/inc/cppwinrt_utils.h
@@ -176,6 +176,18 @@ protected:
     {                                                                 \
     };
 
+inline winrt::array_view<const char16_t> winrt_wstring_to_array_view(const std::wstring_view& str)
+{
+#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1).
+    return winrt::array_view<const char16_t>(reinterpret_cast<const char16_t*>(str.data()), gsl::narrow<uint32_t>(str.size()));
+}
+
+inline std::wstring_view winrt_array_to_wstring_view(const winrt::array_view<const char16_t>& str) noexcept
+{
+#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1).
+    return { reinterpret_cast<const wchar_t*>(str.data()), str.size() };
+}
+
 // This is a helper method for deserializing a SAFEARRAY of
 // COM objects and converting it to a vector that
 // owns the extracted COM objects


Test patch: