Skip to content
This repository has been archived by the owner on Dec 18, 2018. It is now read-only.

Vectorize ValidateHeaderCharacters #1301

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -7683,7 +7683,7 @@ protected override bool TryGetValueFast(string key, out StringValues value)
}
protected override void SetValueFast(string key, StringValues value)
{
ValidateHeaderCharacters(value);
ValidateHeaderCharacters(ref value);
switch (key.Length)
{
case 13:
Expand Down Expand Up @@ -8025,7 +8025,7 @@ protected override void SetValueFast(string key, StringValues value)
}
protected override void AddValueFast(string key, StringValues value)
{
ValidateHeaderCharacters(value);
ValidateHeaderCharacters(ref value);
switch (key.Length)
{
case 13:
Expand Down
Expand Up @@ -5,6 +5,8 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
Expand Down Expand Up @@ -211,25 +213,80 @@ IEnumerator IEnumerable.GetEnumerator()
return TryGetValueFast(key, out value);
}

public static void ValidateHeaderCharacters(StringValues headerValues)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ValidateHeaderCharacters(ref StringValues headerValues)
{
var count = headerValues.Count;
for (var i = 0; i < count; i++)

{
ValidateHeaderCharacters(headerValues[i]);
}
}

public static void ValidateHeaderCharacters(string headerCharacters)
public static unsafe void ValidateHeaderCharacters(string headerCharacters)
{
if (headerCharacters != null)
{
foreach (var ch in headerCharacters)
fixed (char* header = headerCharacters)
{
if (ch < 0x20 || ch > 0x7E)
// offsets and lengths are handled in byte* sizes
var pHeader = (byte*)header;
var offset = 0;
var length = headerCharacters.Length * 2;

if (Vector.IsHardwareAccelerated && Vector<byte>.Count <= length)
{
var vSub = Vector.AsVectorUInt16(new Vector<uint>(0x00200020u));
// 0x7e as highest ascii (don't include DEL)
// 0x20 as lowest ascii (space)
var vTest = Vector.AsVectorUInt16(new Vector<uint>(0x007e007eu - 0x00200020u));

do
{
var stringVector = Unsafe.Read<Vector<ushort>>(pHeader + offset);
offset += Vector<byte>.Count;
if (Vector.GreaterThanAny(stringVector - vSub, vTest))
{
ThrowInvalidHeaderCharacter(pHeader + offset, Vector<byte>.Count);
}
} while (offset <= length - Vector<byte>.Count);
}

// Non-vector testing:
// Flag 0x7f => Add 0x0001 to each char so DEL (0x7f) will set a high bit
// Flag < 0x20 => Sub 0x0020 from each char so high bit will be set in previous char bit
// Flag > 0x007f => All but highest bit picked up by 0x7f flagging, highest bit picked up by < 0x20 flagging
// Bitwise | or the above three together
// Bitwise & and each char with 0xff80; result should be 0 if all tests pass
if (offset <= length - sizeof(ulong))
{
do
{
var stringUlong = (ulong*)(pHeader + offset);
offset += sizeof(ulong);
if ((((*stringUlong + 0x0001000100010001UL) | (*stringUlong - 0x0020002000200020UL)) & 0xff80ff80ff80ff80UL) != 0)
{
ThrowInvalidHeaderCharacter(pHeader + offset, sizeof(ulong));
}
} while (offset <= length - sizeof(ulong));
}
if (offset <= length - sizeof(uint))
{
ThrowInvalidHeaderCharacter(ch);
var stringUint = (uint*)(pHeader + offset);
offset += sizeof(uint);
if ((((*stringUint + 0x00010001u) | (*stringUint - 0x00200020u)) & 0xff80ff80u) != 0)
{
ThrowInvalidHeaderCharacter(pHeader + offset, sizeof(uint));
}
}
if (offset <= length - sizeof(ushort))
{
var stringUshort = (ushort*)(pHeader + offset);
offset += sizeof(ushort);
if ((((*stringUshort + 0x0001u) | (*stringUshort - 0x0020u)) & 0xff80u) != 0)
{
ThrowInvalidHeaderCharacter(pHeader + offset, sizeof(ushort));
}
}
}
}
Expand Down Expand Up @@ -417,9 +474,26 @@ private static void ThrowInvalidContentLengthException(string value)
throw new InvalidOperationException($"Invalid Content-Length: \"{value}\". Value must be a positive integral number.");
}

private static void ThrowInvalidHeaderCharacter(char ch)
private unsafe static Exception GetInvalidHeaderCharacterException(byte* end, int byteCount)
{
var start = end - byteCount;
while (start < end)
{
var ch = *(char*)start;
if (ch < 0x20 || ch >= 0x7f)
{
return new InvalidOperationException(string.Format("Invalid non-ASCII or control character in header: 0x{0:X4}", (ushort)ch));
}
start += sizeof(char);
}

// Should never be reached, use different exception type so unit tests can pick it up
return new ArgumentException("Invalid non-ASCII or control character in header");
}

private unsafe static void ThrowInvalidHeaderCharacter(byte* end, int byteCount)
{
throw new InvalidOperationException(string.Format("Invalid non-ASCII or control character in header: 0x{0:X4}", (ushort)ch));
throw GetInvalidHeaderCharacterException(end, byteCount);
}
}
}
Expand Up @@ -77,6 +77,16 @@ public void InitialDictionaryIsEmpty()
[InlineData("Server", "Da�ta")]
[InlineData("Unknown�-Header", "Data")]
[InlineData("Ser�ver", "Data")]
[InlineData("Cookie", "ZGVmYXVsdC1zcmM\0gJ25vbmUnOyBiYXNlLXVyaSAnc2VsZic7IGJsb2NrLWFsbC1taXhlZC1jb250ZW50OyBjaGlsZC1zcmMgcmVuZGVyLmdpdGh1YnVzZXJjb250ZW50LmNvbTsgY29ubmVjdC1zcmMgJ3NlbGYnIHVwbG9hZHMuZ2l0aHViLmNvbSBzdGF0dXMuZ2l0aHViLmNvbSBhcGkuZ2l0aHViLmNvbSB3d3cuZ29vZ2xlLWFuYWx5dGljcy5jb20gZ2l0aHViLWNsb3VkLnMzLmFtYXpvbmF3cy5jb20gYXBpLmJyYWludHJlZWdhdGV3YXkuY29tIGNsaWVudC1hbmFseXRpY3MuYnJhaW50cmVlZ2F0ZXdheS5jb20gd3NzOi8vbGl2ZS5naXRodWIuY29tOyBmb250LXNyYyBhc3NldHMtY2RuLmdpdGh1Yi5jb207IGZvcm0tYWN0aW9uICdzZWxmJyBnaXRodWIuY29tIGdpc3QuZ2l0aHViLmNvbTsgZnJhbWUtYW5jZXN0b3JzICdub25lJzsgZnJhbWUtc3JjIHJlbmRlci5naXRodWJ1c2VyY29udGVudC5jb207IGltZy1zcmMgJ3NlbGYnIGRhdGE6IGFzc2V0cy1jZG4uZ2l0aHViLmNvbSBpZGVudGljb25zLmdpdGh1Yi5jb20gd3d3Lmdvb2dsZS1hbmFseXRpY3MuY29tIGNvbGxlY3Rvci5naXRodWJhcHAuY29tICouZ3JhdmF0YXIuY29tICoud3AuY29tIGNoZWNrb3V0LnBheXBhbC5jb20gKi5naXRodWJ1c2VyY29udGVudC5jb207IG1lZGlhLXNyYyAnbm9uZSc7IG9iamVjdC1zcmMgYXNzZXRzLWNkbi5naXRodWIuY29tOyBwbHVnaW4tdHlwZXMgYXBwbGljYXRpb24veC1zaG9ja3dhdmUtZmxhc2g7IHNjcmlwdC1zcmMgYXNzZXRzLWNkbi5naXRodWIuY29tOyBzdHlsZS1zcmMgJ3Vuc2FmZS1pbmxpbmUnIGFzc2V0cy1jZG4uZ2l0aHViLmNvbTsgcmVwb3J0LXVyaSBodHRwczovL2FwaS5naXRodWIuY29tL19wcml2YXRlL2Jyb3dzZXIvZXJyb3Jz")]
[InlineData("Cookie", "ZGVmYXVsdC1zcmMg\0J25vbmUnOyBiYXNlLXVyaSAnc2VsZic7IGJsb2NrLWFsbC1taXhlZC1jb250ZW50OyBjaGlsZC1zcmMgcmVuZGVyLmdpdGh1YnVzZXJjb250ZW50LmNvbTsgY29ubmVjdC1zcmMgJ3NlbGYnIHVwbG9hZHMuZ2l0aHViLmNvbSBzdGF0dXMuZ2l0aHViLmNvbSBhcGkuZ2l0aHViLmNvbSB3d3cuZ29vZ2xlLWFuYWx5dGljcy5jb20gZ2l0aHViLWNsb3VkLnMzLmFtYXpvbmF3cy5jb20gYXBpLmJyYWludHJlZWdhdGV3YXkuY29tIGNsaWVudC1hbmFseXRpY3MuYnJhaW50cmVlZ2F0ZXdheS5jb20gd3NzOi8vbGl2ZS5naXRodWIuY29tOyBmb250LXNyYyBhc3NldHMtY2RuLmdpdGh1Yi5jb207IGZvcm0tYWN0aW9uICdzZWxmJyBnaXRodWIuY29tIGdpc3QuZ2l0aHViLmNvbTsgZnJhbWUtYW5jZXN0b3JzICdub25lJzsgZnJhbWUtc3JjIHJlbmRlci5naXRodWJ1c2VyY29udGVudC5jb207IGltZy1zcmMgJ3NlbGYnIGRhdGE6IGFzc2V0cy1jZG4uZ2l0aHViLmNvbSBpZGVudGljb25zLmdpdGh1Yi5jb20gd3d3Lmdvb2dsZS1hbmFseXRpY3MuY29tIGNvbGxlY3Rvci5naXRodWJhcHAuY29tICouZ3JhdmF0YXIuY29tICoud3AuY29tIGNoZWNrb3V0LnBheXBhbC5jb20gKi5naXRodWJ1c2VyY29udGVudC5jb207IG1lZGlhLXNyYyAnbm9uZSc7IG9iamVjdC1zcmMgYXNzZXRzLWNkbi5naXRodWIuY29tOyBwbHVnaW4tdHlwZXMgYXBwbGljYXRpb24veC1zaG9ja3dhdmUtZmxhc2g7IHNjcmlwdC1zcmMgYXNzZXRzLWNkbi5naXRodWIuY29tOyBzdHlsZS1zcmMgJ3Vuc2FmZS1pbmxpbmUnIGFzc2V0cy1jZG4uZ2l0aHViLmNvbTsgcmVwb3J0LXVyaSBodHRwczovL2FwaS5naXRodWIuY29tL19wcml2YXRlL2Jyb3dzZXIvZXJyb3Jz")]
[InlineData("Cookie", "ZGVmYXVsdC1zcmMgJ\025vbmUnOyBiYXNlLXVyaSAnc2VsZic7IGJsb2NrLWFsbC1taXhlZC1jb250ZW50OyBjaGlsZC1zcmMgcmVuZGVyLmdpdGh1YnVzZXJjb250ZW50LmNvbTsgY29ubmVjdC1zcmMgJ3NlbGYnIHVwbG9hZHMuZ2l0aHViLmNvbSBzdGF0dXMuZ2l0aHViLmNvbSBhcGkuZ2l0aHViLmNvbSB3d3cuZ29vZ2xlLWFuYWx5dGljcy5jb20gZ2l0aHViLWNsb3VkLnMzLmFtYXpvbmF3cy5jb20gYXBpLmJyYWludHJlZWdhdGV3YXkuY29tIGNsaWVudC1hbmFseXRpY3MuYnJhaW50cmVlZ2F0ZXdheS5jb20gd3NzOi8vbGl2ZS5naXRodWIuY29tOyBmb250LXNyYyBhc3NldHMtY2RuLmdpdGh1Yi5jb207IGZvcm0tYWN0aW9uICdzZWxmJyBnaXRodWIuY29tIGdpc3QuZ2l0aHViLmNvbTsgZnJhbWUtYW5jZXN0b3JzICdub25lJzsgZnJhbWUtc3JjIHJlbmRlci5naXRodWJ1c2VyY29udGVudC5jb207IGltZy1zcmMgJ3NlbGYnIGRhdGE6IGFzc2V0cy1jZG4uZ2l0aHViLmNvbSBpZGVudGljb25zLmdpdGh1Yi5jb20gd3d3Lmdvb2dsZS1hbmFseXRpY3MuY29tIGNvbGxlY3Rvci5naXRodWJhcHAuY29tICouZ3JhdmF0YXIuY29tICoud3AuY29tIGNoZWNrb3V0LnBheXBhbC5jb20gKi5naXRodWJ1c2VyY29udGVudC5jb207IG1lZGlhLXNyYyAnbm9uZSc7IG9iamVjdC1zcmMgYXNzZXRzLWNkbi5naXRodWIuY29tOyBwbHVnaW4tdHlwZXMgYXBwbGljYXRpb24veC1zaG9ja3dhdmUtZmxhc2g7IHNjcmlwdC1zcmMgYXNzZXRzLWNkbi5naXRodWIuY29tOyBzdHlsZS1zcmMgJ3Vuc2FmZS1pbmxpbmUnIGFzc2V0cy1jZG4uZ2l0aHViLmNvbTsgcmVwb3J0LXVyaSBodHRwczovL2FwaS5naXRodWIuY29tL19wcml2YXRlL2Jyb3dzZXIvZXJyb3Jz")]
[InlineData("Cookie", "ZGVmYXVsdC1zcmMgJ25vbmUnOyBiYXNlLXVyaSAnc2VsZic7IGJsb2NrLWFsbC1taXhlZC1jb250ZW50OyBjaGlsZC1zcmMgcmVuZGVyLmdpdGh1YnVzZXJjb250ZW50LmNvbTsgY29ubmVjdC1zcmMgJ3NlbGYnIHVwbG9hZHMuZ2l0aHViLmNvbSBzdGF0dXMuZ2l0aHViLmNvbSBhcGkuZ2l0aHViLmNvbSB3d3cuZ29vZ2xlLWFuYWx5dGljcy5jb20gZ2l0aHViLWNsb3VkLnMzLmFtYXpvbmF3cy5jb20gYXBpLmJyYWludHJlZWdhdGV3YXkuY29tIGNsaWVudC1hbmFseXRpY3MuYnJhaW50cmVlZ2F0ZXdheS5jb20gd3NzOi8vbGl2ZS5naXRodWIuY29tOyBmb250LXNyYyBhc3NldHMtY2RuLmdpdGh1Yi5jb207IGZvcm0tYWN0aW9uICdzZWxmJyBnaXRodWIuY29tIGdpc3QuZ2l0aHViLmNvbTsgZnJhbWUtYW5jZXN0b3JzICdub25lJzsgZnJhbWUtc3JjIHJlbmRlci5naXRodWJ1c2VyY29udGVudC5jb207IGltZy1zcmMgJ3NlbGYnIGRhdGE6IGFzc2V0cy1jZG4uZ2l0aHViLmNvbSBpZGVudGljb25zLmdpdGh1Yi5jb20gd3d3Lmdvb2dsZS1hbmFseXRpY3MuY29tIGNvbGxlY3Rvci5naXRodWJhcHAuY29tICouZ3JhdmF0YXIuY29tICoud3AuY29tIGNoZWNrb3V0LnBheXBhbC5jb20gKi5naXRodWJ1c2VyY29udGVudC5jb207IG1lZGlhLXNyYyAnbm9uZSc7IG9iamVjdC1zcmMgYXNzZXRzLWNkbi5naXRodWIuY29tOyBwbHVnaW4tdHlwZXMgYXBwbGljYXRpb24veC1zaG9ja3dhdmUtZmxhc2g7IHNjcmlwdC1zcmMgYXNzZXRzLWNkbi5naXRodWIuY29tOyBzdHlsZS1zcmMgJ3Vuc2FmZS1pbmxpbmUnIGFzc2V0cy1jZG4uZ2l0aHViLmNvbTsgcmVwb3J0LXVyaSBodHRwczovL2FwaS5naXRodWIuY29tL19wcml2YXRlL2Jyb3dzZXIvZXJyb3Jz\0")]
[InlineData("Content-Security-Polic\0y", "default-src 'none'; base-uri 'self'; block-all-mixed-content; child-src render.githubusercontent.com; connect-src 'self' uploads.github.com status.github.com api.github.com www.google-analytics.com github-cloud.s3.amazonaws.com api.braintreegateway.com client-analytics.braintreegateway.com wss://live.github.com; font-src assets-cdn.github.com; form-action 'self' github.com gist.github.com; frame-ancestors 'none'; frame-src render.githubusercontent.com; img-src 'self' data: assets-cdn.github.com identicons.github.com www.google-analytics.com collector.githubapp.com *.gravatar.com *.wp.com checkout.paypal.com *.githubusercontent.com; media-src 'none'; object-src assets-cdn.github.com; plugin-types application/x-shockwave-flash; script-src assets-cdn.github.com; style-src 'unsafe-inline' assets-cdn.github.com; report-uri https://api.github.com/_private/browser/errors")]
[InlineData("Content-Securit\0y-Policy", "default-src 'none'; base-uri 'self'; block-all-mixed-content; child-src render.githubusercontent.com; connect-src 'self' uploads.github.com status.github.com api.github.com www.google-analytics.com github-cloud.s3.amazonaws.com api.braintreegateway.com client-analytics.braintreegateway.com wss://live.github.com; font-src assets-cdn.github.com; form-action 'self' github.com gist.github.com; frame-ancestors 'none'; frame-src render.githubusercontent.com; img-src 'self' data: assets-cdn.github.com identicons.github.com www.google-analytics.com collector.githubapp.com *.gravatar.com *.wp.com checkout.paypal.com *.githubusercontent.com; media-src 'none'; object-src assets-cdn.github.com; plugin-types application/x-shockwave-flash; script-src assets-cdn.github.com; style-src 'unsafe-inline' assets-cdn.github.com; report-uri https://api.github.com/_private/browser/errors")]
[InlineData("Content-Security-Policy", "default-src 'none'; base-uri 'self'; \0block-all-mixed-content; child-src render.githubusercontent.com; connect-src 'self' uploads.github.com status.github.com api.github.com www.google-analytics.com github-cloud.s3.amazonaws.com api.braintreegateway.com client-analytics.braintreegateway.com wss://live.github.com; font-src assets-cdn.github.com; form-action 'self' github.com gist.github.com; frame-ancestors 'none'; frame-src render.githubusercontent.com; img-src 'self' data: assets-cdn.github.com identicons.github.com www.google-analytics.com collector.githubapp.com *.gravatar.com *.wp.com checkout.paypal.com *.githubusercontent.com; media-src 'none'; object-src assets-cdn.github.com; plugin-types application/x-shockwave-flash; script-src assets-cdn.github.com; style-src 'unsafe-inline' assets-cdn.github.com; report-uri https://api.github.com/_private/browser/errors")]
[InlineData("Content-Security-Policy", "default-src 'none'; base-uri 'self'; �block-all-mixed-content; child-src render.githubusercontent.com; connect-src 'self' uploads.github.com status.github.com api.github.com www.google-analytics.com github-cloud.s3.amazonaws.com api.braintreegateway.com client-analytics.braintreegateway.com wss://live.github.com; font-src assets-cdn.github.com; form-action 'self' github.com gist.github.com; frame-ancestors 'none'; frame-src render.githubusercontent.com; img-src 'self' data: assets-cdn.github.com identicons.github.com www.google-analytics.com collector.githubapp.com *.gravatar.com *.wp.com checkout.paypal.com *.githubusercontent.com; media-src 'none'; object-src assets-cdn.github.com; plugin-types application/x-shockwave-flash; script-src assets-cdn.github.com; style-src 'unsafe-inline' assets-cdn.github.com; report-uri https://api.github.com/_private/browser/errors")]
[InlineData("Content-Security-Policy", "default-src 'none'; base-uri 'self'; \u0080block-all-mixed-content; child-src render.githubusercontent.com; connect-src 'self' uploads.github.com status.github.com api.github.com www.google-analytics.com github-cloud.s3.amazonaws.com api.braintreegateway.com client-analytics.braintreegateway.com wss://live.github.com; font-src assets-cdn.github.com; form-action 'self' github.com gist.github.com; frame-ancestors 'none'; frame-src render.githubusercontent.com; img-src 'self' data: assets-cdn.github.com identicons.github.com www.google-analytics.com collector.githubapp.com *.gravatar.com *.wp.com checkout.paypal.com *.githubusercontent.com; media-src 'none'; object-src assets-cdn.github.com; plugin-types application/x-shockwave-flash; script-src assets-cdn.github.com; style-src 'unsafe-inline' assets-cdn.github.com; report-uri https://api.github.com/_private/browser/errors")]
[InlineData("Content-Security-Policy", "default-src 'none'; base-uri 'self'; block-all-mixed-content; child-src render.githubusercontent.com; connect-src 'self' uploads.github.com status.github.com api.github.com www.google-analytics.com github-cloud.s3.amazonaws.com api.braintreegateway.com client-analytics.braintreegateway.com wss://live.github.com; font-src assets-cdn.github.com; form-action 'self' github.com gist.github.com; frame-ancestors 'none'; frame-src render.githubusercontent.com; img-src 'self' data: assets-cdn.github.com identicons.github.com www.google-analytics.com collector.githubapp.com *.gravatar.com *.wp.com checkout.paypal.com *.githubusercontent.com; media-src 'none'; object-src assets-cdn.github.com; plugin-types application/x-shockwave-flash; script-src assets-cdn.github.com; style-src 'unsafe-inline' assets-cdn.github.com; report-uri https://api.github.com/_private/browser/errors�")]
public void AddingControlOrNonAsciiCharactersToHeadersThrows(string key, string value)
{
var responseHeaders = new FrameResponseHeaders();
Expand Down
Expand Up @@ -368,7 +368,7 @@ protected override bool TryGetValueFast(string key, out StringValues value)
}}
protected override void SetValueFast(string key, StringValues value)
{{
{(loop.ClassName == "FrameResponseHeaders" ? "ValidateHeaderCharacters(value);" : "")}
{(loop.ClassName == "FrameResponseHeaders" ? "ValidateHeaderCharacters(ref value);" : "")}
switch (key.Length)
{{{Each(loop.HeadersByLength, byLength => $@"
case {byLength.Key}:
Expand Down