Fix malformed UNC pipe path for IPv6 literals in managed SNI - #4558
Fix malformed UNC pipe path for IPv6 literals in managed SNI#4558cheenamalhotra wants to merge 5 commits into
Conversation
A UNC path host component may never contain a colon, so an IPv6 literal server name composes a malformed pipe path such as \\::1\pipe\sql\query. Handing that to the OS sends the SMB redirector into an SMB session setup whose SPNEGO/NegoEx target name embeds the IPv6 literal, which can fault LSASS on Windows and force a reboot. Managed SNI defaults to TCP when no protocol prefix is given, so this is reachable only when Named Pipes is selected explicitly (np:::1) or via a UNC pipe path (\\::1\pipe\sql\query). Validate the host component in both DataSource.InferNamedPipesInformation branches, plus a final safeguard in SniProxy.CreateNpHandle, mirroring the native SNI fix. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Ports a Managed SNI hardening fix to prevent composing/handing malformed UNC Named Pipes paths when the data source host component is an IPv6 literal (contains :), avoiding a Windows LSASS crash/reboot scenario when Named Pipes is explicitly selected.
Changes:
- Add pipe-host validation (
IsValidPipeHostName) and enforce it during Named Pipes parsing (bothnp:hostand\\host\pipe\...forms). - Add a final defensive validation in
SniProxy.CreateNpHandlebefore constructing the Named Pipes handle. - Add new unit tests covering IPv6-literal rejection on the Named Pipes path and ensuring valid NP inputs still parse correctly.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniProxy.netcore.cs | Adds validation to reject pipe hostnames containing : during NP parsing and before NP handle creation. |
| src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ManagedSni/DataSourceNamedPipesTests.cs | Adds regression tests to ensure IPv6 literals are rejected for Named Pipes while valid NP data sources continue to parse. |
Suppressed comments (4)
src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ManagedSni/DataSourceNamedPipesTests.cs:47
- This test method is missing an XML
<summary>comment. Please add a brief summary describing what valid inputs are being verified and why (to match the documentation style used by other ManagedSni unit tests in this folder).
[Theory]
[InlineData(@"np:127.0.0.1", "127.0.0.1")]
[InlineData(@"np:localhost", "localhost")]
[InlineData(@"np:.", ".")]
[InlineData(@"np:server\instance", "server")]
[InlineData(@"\\127.0.0.1\pipe\sql\query", "127.0.0.1")]
[InlineData(@"\\.\pipe\MSSQL$MYINSTANCE\sql\query", ".")]
[InlineData(@"\\my-server\pipe\sql\query", "my-server")]
public void ParseServerName_NamedPipesWithValidHost_IsAccepted(
string dataSource, string expectedPipeHostName)
{
src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ManagedSni/DataSourceNamedPipesTests.cs:62
- This test method is missing an XML
<summary>comment. Please add a brief summary describing the default pipe-name composition being asserted (to match the documentation style used by other ManagedSni unit tests in this folder).
[Theory]
[InlineData(@"np:127.0.0.1", @"sql\query")]
[InlineData(@"np:localhost", @"sql\query")]
[InlineData(@"np:server\instance", @"MSSQL$instance\sql\query")]
public void ParseServerName_NamedPipesWithoutUncPath_ComposesDefaultPipeName(
string dataSource, string expectedPipeName)
{
src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ManagedSni/DataSourceNamedPipesTests.cs:93
- This test method is missing an XML
<summary>comment. Please add a brief summary describing the inputs/outputs for IsValidPipeHostName (to match the documentation style used by other ManagedSni unit tests in this folder).
[Theory]
[InlineData("::1", false)]
[InlineData("[::1]", false)]
[InlineData("", false)]
[InlineData(".", true)]
[InlineData("localhost", true)]
[InlineData("127.0.0.1", true)]
[InlineData("my-server.contoso.com", true)]
public void IsValidPipeHostName_ReturnsExpected(string hostName, bool expected)
src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ManagedSni/DataSourceNamedPipesTests.cs:102
- This test method is missing an XML
<summary>comment. Please add a brief summary describing the null-handling behavior being asserted (to match the documentation style used by other ManagedSni unit tests in this folder).
[Fact]
public void IsValidPipeHostName_Null_ReturnsFalse()
{
Assert.False(DataSource.IsValidPipeHostName(null));
}
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- IsValidPipeHostName now compares against the literal ':' instead of the misleadingly named SemiColon constant. - Add XML summaries to every test method in DataSourceNamedPipesTests, matching the convention used by the other ManagedSni unit tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Review feedback: rejecting IPv6 literals on the Named Pipes path blocked addresses that would otherwise work. Windows defines a UNC transcription for exactly this case (MS-DTYP 2.2.57): replace ':' with '-' and '%' with 's', then append '.ipv6-literal.net', so 2001:db8::1 becomes 2001-db8--1.ipv6-literal.net. DataSource.GetUncCompatibleHostName replaces IsValidPipeHostName and applies that transcription at both Named Pipes host-assignment sites. Colon-free hosts (host names, IPv4 literals, already-transcribed names) pass through untouched, and a colon-bearing host with no IPv6 interpretation still fails cleanly. ServerName keeps the original literal because it feeds SPN creation. CreateNpHandle keeps a colon check as a final safeguard. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (3)
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniProxy.netcore.cs:739
- In the UNC Named Pipes parsing branch, bracketed IPv6 hosts (e.g. "\[2001:db8::1]\pipe\...") are accepted/transcribed for PipeHostName but ServerName is still set to the original bracketed token. Since ServerName feeds DNS/SPN generation, it should be normalized to an unbracketed IPv6 literal when the host token is a bracketed IPv6 address.
ServerName = IsLocalHost(host) ? Environment.MachineName : host;
// Pipe hostname is the hostname after leading \\ which should be passed down as is to open Named Pipe.
// For Named Pipes the ServerName makes sense for SPN creation only.
PipeHostName = uncHost;
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniProxy.netcore.cs:770
- The PR metadata/title describe "rejecting" IPv6 literals on the Named Pipes path (and mention an IsValidPipeHostName helper), but the current implementation instead transcribes parseable IPv6 literals into the Windows ".ipv6-literal.net" form via GetUncCompatibleHostName and only rejects colon-bearing hosts that are not valid IPv6 literals. Please update the PR title/description to match the implemented behavior to avoid confusion during release notes/backports.
/// <summary>
/// Converts a host name into a form that can legally appear as the host component of a UNC
/// pipe path (<c>\\host\pipe\sql\query</c>), returning <see langword="null"/> if no such
/// form exists.
/// </summary>
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniProxy.netcore.cs:671
- In the np:host parsing path, bracketed IPv6 literals (e.g. "np:[::1]") get transcribed for PipeHostName but ServerName remains bracketed. ServerName is later used for DNS/SPN generation (SniProxy.GetSqlServerSPNs), and the bracketed form is not a valid host/address there, which can lead to an invalid SPN being constructed. Consider normalizing ServerName to the unbracketed IPv6 literal when the original input is a bracketed IPv6 address.
This issue also appears on line 736 of the same file.
// An IPv6 literal must be transcribed before it can appear in a UNC pipe path.
// ServerName keeps the original form because it is only used for SPN creation.
// See GetUncCompatibleHostName for details.
PipeHostName = GetUncCompatibleHostName(PipeHostName);
if (PipeHostName is null)
Copilot review: for a bracketed IPv6 host such as np:[2001:db8::1] or \\[2001:db8::1]\pipe\sql\query, PipeHostName was transcribed but ServerName kept the brackets. ServerName feeds Dns.GetHostEntry and SPN construction in SniProxy.GetSqlServerSPNs, neither of which accepts the bracketed spelling, so lookup would fail and a malformed SPN such as MSSQLSvc/[2001:db8::1] could be produced. Factor the literal parsing into TryParseIPv6Literal, shared by the new NormalizeHostName (unwraps brackets to the canonical unbracketed form) and GetUncCompatibleHostName. Both Named Pipes host-assignment sites now normalize ServerName alongside transcribing PipeHostName. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Follow-up on the latest Copilot review pass. It generated no new inline threads, but three suppressed comments were worth acting on, and two of them flagged a real bug I had introduced. Bracketed IPv6 left The literal parsing is now factored into
Both Named Pipes host-assignment sites now apply both. Added a theory covering all four bracketed/unbracketed x Title/description drift (third suppressed comment). Both said "reject", which stopped being accurate once I switched to transcription. The description was updated earlier; I have now retitled the PR to |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4558 +/- ##
==========================================
- Coverage 65.23% 63.61% -1.62%
==========================================
Files 288 283 -5
Lines 44587 67691 +23104
==========================================
+ Hits 29087 43064 +13977
- Misses 15500 24627 +9127
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1790b392-a88c-4aa2-a50b-90046a378db5
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ManagedSni/DataSourceNamedPipesTests.cs:162
- This test asserts that for a bracketed IPv6 literal (e.g. "[::1]") without a protocol prefix, DataSource.ServerName remains bracketed. However, the TCP path passes DataSource.ServerName directly into DNS resolution (SniProxy.netcore.cs:224 -> SniTcpHandle.netcore.cs:330 uses Dns.GetHostAddresses(serverName)), and "[::1]" is not a valid host string for Dns.GetHostAddresses. This means the behavior the test is locking in is likely non-functional for TCP connections. Consider normalizing ServerName for non-NP parsing as well (e.g., apply DataSource.NormalizeHostName in InferConnectionDetails / ParseServerName for TCP) and update this assertion to expect the unbracketed form instead.
Assert.NotNull(details);
Assert.NotEqual(DataSource.Protocol.NP, details.ResolvedProtocol);
Assert.Equal(dataSource, details.ServerName);
}
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniProxy.netcore.cs:803
- The remarks on NormalizeHostName currently state that ServerName feeds DNS resolution and SPN construction (which is true), and that bracketed IPv6 must be dropped before it’s used there. However, the non-NP (TCP) parsing path in this class does not call NormalizeHostName, so this remark reads broader than what the implementation currently guarantees. Either apply NormalizeHostName to the TCP parsing path as well, or narrow this remark to reflect that the normalization is only performed by Named Pipes parsing today.
/// <remarks>
/// <see cref="ServerName"/> feeds DNS resolution and SPN construction, neither of which
/// accepts the bracketed spelling, so the brackets must be dropped before it is used there.
/// </remarks>
internal static string NormalizeHostName(string hostName) =>
Description
Ports the native SNI fix (ADO PR 8120) to the Managed SNI code base, with an IPv6 improvement over the native version.
A UNC path host component may never contain a colon, so an IPv6 literal server name composes a malformed pipe path such as
\\::1\pipe\sql\query. Handing that path to the OS sends the SMB redirector into an SMB session setup whose SPNEGO/NegoEx target name embeds the IPv6 literal, hitting an access violation inside LSASS on Windows. LSASS is a critical process, so Windows forces a reboot.Managed SNI does not walk the
sm->tcp->npdefault protocol list the way native SNI does: with no protocol prefix it resolves to TCP, soServer=::1is already safe there. The malformed path is still reachable when Named Pipes is selected explicitly, for exampleServer=np:::1,Server=np:[::1], orServer=\\::1\pipe\sql\query.Rather than rejecting these outright (which would block IPv6 addresses that can work), the parser applies the UNC transcription Windows defines for exactly this case (MS-DTYP 2.2.57): replace each
:with-and each%(zone index) withs, then append.ipv6-literal.net.PipeHostNamenp:::1--1.ipv6-literal.netnp:[::1]--1.ipv6-literal.netnp:2001:db8::12001-db8--1.ipv6-literal.netnp:fe80::1%3fe80--1s3.ipv6-literal.netImplementation, all in
ManagedSni/SniProxy.netcore.cs:DataSource.GetUncCompatibleHostNamehelper performs the transcription, accepting the bracketed[::1]spelling users often carry over from URL syntax.InferNamedPipesInformation(thenp:hostform and the\\host\pipe\...UNC form).SniProxy.CreateNpHandleretains a colon check as a final safeguard, mirroring theNp::OpenPipecheck in native SNI.Edge behavior worth noting:
.ipv6-literal.netnames, pass through untouched, so nothing that worked before changes. LocalDB,localhostand.are unaffected.ServerNamedeliberately keeps the original literal because it only feeds SPN creation; onlyPipeHostName, which is what reaches the OS, is transcribed.not:a:host) fails cleanly with the standard invalid-connection-string SNI error instead of composing a malformed path.No public API changes.
Issues
Fixes #4523 in the Managed SNI code path. Native SNI counterpart:
Microsoft.Data.SqlClient.sniPR 8120.Testing
New unit tests in
src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ManagedSni/DataSourceNamedPipesTests.cs:.ipv6-literal.netform, covering thenp:hostand UNC forms, bracketed and unbracketed spellings, and a zone index.np:127.0.0.1,np:localhost,np:.,np:server\instance,\\127.0.0.1\pipe\sql\query,\\.\pipe\MSSQL$MYINSTANCE\sql\query,\\my-server\pipe\sql\query.ServerNamekeeps the original IPv6 literal for SPN purposes whilePipeHostNameis transcribed.GetUncCompatibleHostNamehelper, including the null and empty cases.48 ManagedSni unit tests pass locally (
dotnet test -f net9.0 --filter FullyQualifiedName~ManagedSni), and the driver builds clean across all TFMs. Pipe-name assertions deliberately avoid the UNC forms because that code path builds the name withPath.DirectorySeparatorChar, which is platform dependent.Note that no automated test can cover the LSASS crash itself, since reproducing it reboots the machine. The native SNI PR was verified manually on Windows; this change removes the malformed path at the parser level, before it can reach the OS.