Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit df3caf2

Browse files
author
Max Kerr
committed
Merged PR 158405: [2.1 Servicing] MSRC 47421: Disallow dangerous Unicode decompositions in System.Uri
This change blocks any Unicode decompositions that change the semantics of a URI. See the comments included in the change for more details. Unlike the netfx implementation of this change, there is no mechanism for users to disable the fix. I believe that this is the correct choice for two reasons: (1) Domain name registrars have disallowed these characters for almost as long as they have allowed punycode, so it's unlikely there are legitimate domains using them. (2) The consequences of disabling the fix are significant, and are likely non-obvious to most users.
1 parent fc9ba56 commit df3caf2

2 files changed

Lines changed: 52 additions & 2 deletions

File tree

src/System.Private.Uri/src/System/DomainNameHelper.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,12 @@ internal static unsafe string IdnEquivalent(char* hostname, int start, int end,
332332
bidiStrippedHost = UriHelper.StripBidiControlCharacter(hostname, start, end - start);
333333
try
334334
{
335-
return s_idnMapping.GetAscii(bidiStrippedHost);
335+
string asciiForm = s_idnMapping.GetAscii(bidiStrippedHost);
336+
if (ContainsCharactersUnsafeForNormalizedHost(asciiForm))
337+
{
338+
throw new UriFormatException(SR.net_uri_BadUnicodeHostForIdn);
339+
}
340+
return asciiForm;
336341
}
337342
catch (ArgumentException)
338343
{
@@ -536,5 +541,20 @@ private static bool IsValidDomainLabelCharacter(char character, ref bool notCano
536541

537542
return false;
538543
}
544+
545+
// The Unicode specification allows certain code points to be normalized not to
546+
// punycode, but to ASCII representations that retain the same meaning. For example,
547+
// the codepoint U+00BC "Vulgar Fraction One Quarter" is normalized to '1/4' rather
548+
// than being punycoded.
549+
//
550+
// This means that a host containing Unicode characters can be normalized to contain
551+
// URI reserved characters, changing the meaning of a URI only when certain properties
552+
// such as IdnHost are accessed. To be safe, disallow control characters in normalized hosts.
553+
private static readonly char[] s_UnsafeForNormalizedHost = { '\\', '/', '?', '@', '#', ':', '[', ']' };
554+
555+
internal static bool ContainsCharactersUnsafeForNormalizedHost(string host)
556+
{
557+
return host.IndexOfAny(s_UnsafeForNormalizedHost) != -1;
558+
}
539559
}
540560
}

src/System.Private.Uri/tests/FunctionalTests/IriTest.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
using System.Text;
5+
using System.Collections.Generic;
66
using System.Common.Tests;
7+
using System.Linq;
8+
using System.Text;
79

810
using Xunit;
911

@@ -567,5 +569,33 @@ public void Iri_RelativeUriCreation_ShouldNotNormalize(string uriString)
567569
Assert.True(Uri.TryCreate(baseIri, href, out hrefAbsolute));
568570
Assert.Equal("http://www.contoso.com/%C3%A8", hrefAbsolute.AbsoluteUri);
569571
}
572+
573+
public static IEnumerable<object[]> AllForbiddenDecompositions() =>
574+
from host in new[] { "canada.c\u2100.microsoft.com", // Unicode U+2100 'Account Of' decomposes to 'a/c'
575+
"canada.c\u2488.microsoft.com", // Unicode U+2488 'Digit One Full Stop" decomposes to '1.'
576+
"canada.c\u2048.microsoft.com", // Unicode U+2048 'Question Exclamation Mark" decomposes to '?!'
577+
"canada.c\uD83C\uDD00.microsoft.com" } // Unicode U+2488 'Digit Zero Full Stop" decomposes to '0.'
578+
from scheme in new[] { "http", // Known scheme.
579+
"test" } // Unknown scheme.
580+
select new object[] { scheme, host };
581+
582+
[Theory]
583+
[MemberData(nameof(AllForbiddenDecompositions))]
584+
public void Iri_AllForbiddenDecompositions_IdnHostThrows(string scheme, string host)
585+
{
586+
Uri uri = new Uri(scheme + "://" + host);
587+
Assert.Throws<UriFormatException>(() => uri.IdnHost);
588+
}
589+
590+
[Theory]
591+
[MemberData(nameof(AllForbiddenDecompositions))]
592+
public void Iri_AllForbiddenDecompositions_NonIdnPropertiesOk(string scheme, string host)
593+
{
594+
Uri uri = new Uri(scheme + "://" + host);
595+
Assert.Equal(host, uri.Host);
596+
Assert.Equal(host, uri.DnsSafeHost);
597+
Assert.Equal(host, uri.Authority);
598+
Assert.Equal(scheme + "://" + host + "/", uri.AbsoluteUri);
599+
}
570600
}
571601
}

0 commit comments

Comments
 (0)