-
Notifications
You must be signed in to change notification settings - Fork 333
Fix malformed UNC pipe path for IPv6 literals in managed SNI #4558
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
cheenamalhotra
wants to merge
5
commits into
main
Choose a base branch
from
dev/cheena/animated-garbanzo
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+346
−4
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
9e638d5
Reject IPv6 literal host names on the Named Pipes path in managed SNI
cheenamalhotra 07e8b7b
Address review feedback: use literal ':' and document test methods
cheenamalhotra e9285c9
Transcribe IPv6 literals to UNC form instead of rejecting them
cheenamalhotra 615bd11
Normalize ServerName for bracketed IPv6 literals
cheenamalhotra 45478c2
Address IPv6 named pipe review feedback
cheenamalhotra File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
212 changes: 212 additions & 0 deletions
212
...qlClient/tests/UnitTests/Microsoft/Data/SqlClient/ManagedSni/DataSourceNamedPipesTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,212 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
| // See the LICENSE file in the project root for more information. | ||
|
|
||
| #if NET | ||
|
|
||
| using Microsoft.Data.SqlClient.ManagedSni; | ||
| using Xunit; | ||
|
|
||
| namespace Microsoft.Data.SqlClient.UnitTests.ManagedSni | ||
| { | ||
| /// <summary> | ||
| /// Regression tests for Named Pipes data source parsing in <see cref="DataSource"/>. | ||
| /// | ||
| /// A UNC path host component may never contain a colon, so an IPv6 literal server name cannot | ||
| /// be used directly. Passing one through anyway composes a malformed pipe path such as | ||
| /// <c>\\::1\pipe\sql\query</c>, which sends the SMB redirector into an SMB session setup that | ||
| /// can fault LSASS on Windows and force a reboot. Windows instead defines a transcription for | ||
| /// this case (<c>2001:db8::1</c> becomes <c>2001-db8--1.ipv6-literal.net</c>), which the parser | ||
| /// now applies so IPv6 Named Pipes connections keep working. | ||
| /// | ||
| /// See: https://github.com/dotnet/SqlClient/issues/4523 | ||
| /// and https://learn.microsoft.com/openspecs/windows_protocols/ms-dtyp/62e862f4-2a51-452e-8eeb-dc4ff5ee33cc | ||
| /// </summary> | ||
| public class DataSourceNamedPipesTests | ||
| { | ||
| /// <summary> | ||
| /// Verifies that an IPv6 literal host is transcribed to its <c>.ipv6-literal.net</c> UNC | ||
| /// form, covering both the <c>np:host</c> form and the <c>\\host\pipe\...</c> UNC form, | ||
| /// with and without brackets and with a zone index. | ||
| /// </summary> | ||
| [Theory] | ||
| [InlineData(@"np:::1", "--1.ipv6-literal.net")] | ||
| [InlineData(@"np:[::1]", "--1.ipv6-literal.net")] | ||
| [InlineData(@"np:2001:db8::1", "2001-db8--1.ipv6-literal.net")] | ||
| [InlineData(@"np:fe80::1%3", "fe80--1s3.ipv6-literal.net")] | ||
| [InlineData(@"\\::1\pipe\sql\query", "--1.ipv6-literal.net")] | ||
| [InlineData(@"np:\\::1\pipe\sql\query", "--1.ipv6-literal.net")] | ||
| [InlineData(@"np:\\[2001:db8::1]\pipe\MSSQL$MYINSTANCE\sql\query", "2001-db8--1.ipv6-literal.net")] | ||
| public void ParseServerName_NamedPipesWithIPv6Literal_IsTranscribedToUncForm( | ||
| string dataSource, string expectedPipeHostName) | ||
| { | ||
| DataSource details = DataSource.ParseServerName(dataSource); | ||
|
|
||
| Assert.NotNull(details); | ||
| Assert.Equal(DataSource.Protocol.NP, details.ResolvedProtocol); | ||
| Assert.Equal(expectedPipeHostName, details.PipeHostName); | ||
| // The pipe host name is what reaches the OS, so it must never carry a colon. | ||
| Assert.DoesNotContain(":", details.PipeHostName); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Verifies that a colon-bearing host that is not a parseable IPv6 literal has no UNC form | ||
| /// and is therefore rejected, rather than composing a malformed pipe path. | ||
| /// </summary> | ||
| [Theory] | ||
| [InlineData(@"np:not:a:host")] | ||
| [InlineData(@"np:2001:db8:::::1")] | ||
| [InlineData(@"\\not:a:host\pipe\sql\query")] | ||
| public void ParseServerName_NamedPipesWithUnparseableColonHost_IsRejected(string dataSource) | ||
| { | ||
| Assert.Null(DataSource.ParseServerName(dataSource)); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Verifies that IPv6 transcription does not regress legitimate Named Pipes data sources: | ||
| /// IPv4 literals, <c>localhost</c>, <c>.</c>, named instances, and explicit UNC pipe paths | ||
| /// must still parse and yield an unchanged pipe host name. | ||
| /// </summary> | ||
| [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) | ||
| { | ||
| DataSource details = DataSource.ParseServerName(dataSource); | ||
|
|
||
| Assert.NotNull(details); | ||
| Assert.Equal(DataSource.Protocol.NP, details.ResolvedProtocol); | ||
| Assert.Equal(expectedPipeHostName, details.PipeHostName); | ||
| Assert.False(string.IsNullOrEmpty(details.PipeName)); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Verifies that a Named Pipes data source given without a UNC path still composes the | ||
| /// default pipe name, including the <c>MSSQL$<instance></c> prefix for named instances. | ||
| /// These forms are asserted separately from the UNC forms because the UNC path builds its | ||
| /// pipe name with <see cref="System.IO.Path.DirectorySeparatorChar"/>, which is platform dependent. | ||
| /// </summary> | ||
| [Theory] | ||
| [InlineData(@"np:127.0.0.1", @"sql\query")] | ||
| [InlineData(@"np:localhost", @"sql\query")] | ||
| [InlineData(@"np:::1", @"sql\query")] | ||
| [InlineData(@"np:server\instance", @"MSSQL$instance\sql\query")] | ||
| public void ParseServerName_NamedPipesWithoutUncPath_ComposesDefaultPipeName( | ||
| string dataSource, string expectedPipeName) | ||
| { | ||
| DataSource details = DataSource.ParseServerName(dataSource); | ||
|
|
||
| Assert.NotNull(details); | ||
| Assert.Equal(expectedPipeName, details.PipeName); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Verifies that an IPv6 literal is preserved (unbracketed) in <see cref="DataSource.ServerName"/>, | ||
| /// which feeds DNS resolution and SPN construction, while the pipe host name is transcribed. | ||
| /// The bracketed spelling must not survive into <see cref="DataSource.ServerName"/> because | ||
| /// neither DNS nor SPN construction accepts it. | ||
| /// </summary> | ||
| [Theory] | ||
| [InlineData(@"np:2001:db8::1")] | ||
| [InlineData(@"np:[2001:db8::1]")] | ||
| [InlineData(@"np:\\2001:db8::1\pipe\sql\query")] | ||
| [InlineData(@"np:\\[2001:db8::1]\pipe\sql\query")] | ||
| public void ParseServerName_NamedPipesWithIPv6Literal_PreservesUnbracketedServerNameForSpn(string dataSource) | ||
| { | ||
| DataSource details = DataSource.ParseServerName(dataSource); | ||
|
|
||
| Assert.NotNull(details); | ||
| Assert.Equal("2001:db8::1", details.ServerName); | ||
| Assert.Equal("2001-db8--1.ipv6-literal.net", details.PipeHostName); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Verifies <see cref="DataSource.NormalizeHostName"/> unwraps bracketed IPv6 literals and | ||
| /// leaves every other host name untouched. | ||
| /// </summary> | ||
| [Theory] | ||
| [InlineData("[::1]", "::1")] | ||
| [InlineData("::1", "::1")] | ||
| [InlineData("[2001:db8::1]", "2001:db8::1")] | ||
| [InlineData("[fe80::1%3]", "fe80::1%3")] | ||
| [InlineData("localhost", "localhost")] | ||
| [InlineData("127.0.0.1", "127.0.0.1")] | ||
| [InlineData("not:a:host", "not:a:host")] | ||
| [InlineData("", "")] | ||
| public void NormalizeHostName_ReturnsExpected(string hostName, string expected) | ||
| { | ||
| Assert.Equal(expected, DataSource.NormalizeHostName(hostName)); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Without an explicit protocol prefix, managed SNI defaults to TCP, so an IPv6 literal | ||
| /// server name must continue to parse successfully and never reach the Named Pipes path. | ||
| /// </summary> | ||
| [Theory] | ||
| [InlineData("::1")] | ||
| [InlineData("[::1]")] | ||
| [InlineData("fe80::1")] | ||
| public void ParseServerName_IPv6LiteralWithoutProtocol_ResolvesToNonNamedPipes(string dataSource) | ||
| { | ||
| DataSource details = DataSource.ParseServerName(dataSource); | ||
|
|
||
| Assert.NotNull(details); | ||
| Assert.NotEqual(DataSource.Protocol.NP, details.ResolvedProtocol); | ||
|
cheenamalhotra marked this conversation as resolved.
|
||
| Assert.Equal(dataSource, details.ServerName); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Verifies <see cref="DataSource.GetUncCompatibleHostName"/> directly: colon-free host names | ||
| /// pass through untouched, IPv6 literals are transcribed per MS-DTYP, and colon-bearing host | ||
| /// names with no IPv6 interpretation return <see langword="null"/>. | ||
| /// </summary> | ||
| [Theory] | ||
| [InlineData(".", ".")] | ||
| [InlineData("localhost", "localhost")] | ||
| [InlineData("127.0.0.1", "127.0.0.1")] | ||
| [InlineData("my-server.contoso.com", "my-server.contoso.com")] | ||
| [InlineData("--1.ipv6-literal.net", "--1.ipv6-literal.net")] | ||
| [InlineData("::1", "--1.ipv6-literal.net")] | ||
| [InlineData("[::1]", "--1.ipv6-literal.net")] | ||
| [InlineData("2001:db8::1", "2001-db8--1.ipv6-literal.net")] | ||
| [InlineData("::ffff:1.2.3.4", "--ffff-1.2.3.4.ipv6-literal.net")] | ||
| [InlineData("fe80::1%3", "fe80--1s3.ipv6-literal.net")] | ||
|
cheenamalhotra marked this conversation as resolved.
|
||
| public void GetUncCompatibleHostName_ReturnsExpected(string hostName, string expected) | ||
| { | ||
| Assert.Equal(expected, DataSource.GetUncCompatibleHostName(hostName)); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Verifies <see cref="DataSource.GetUncCompatibleHostName"/> returns <see langword="null"/> | ||
| /// for host names that contain a colon but have no IPv6 interpretation, and for empty input. | ||
| /// </summary> | ||
| [Theory] | ||
| [InlineData("not:a:host")] | ||
| [InlineData("2001:db8:::::1")] | ||
| [InlineData("[:]")] | ||
| [InlineData("")] | ||
| public void GetUncCompatibleHostName_UnconvertibleHost_ReturnsNull(string hostName) | ||
| { | ||
| Assert.Null(DataSource.GetUncCompatibleHostName(hostName)); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Verifies <see cref="DataSource.GetUncCompatibleHostName"/> returns <see langword="null"/> | ||
| /// for a null host name. Covered separately from the theory above because xUnit disallows | ||
| /// null theory data for a non-nullable string parameter. | ||
| /// </summary> | ||
| [Fact] | ||
| public void GetUncCompatibleHostName_Null_ReturnsNull() | ||
| { | ||
| Assert.Null(DataSource.GetUncCompatibleHostName(null)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #endif | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.