Make GetFinalTransferCoding "safe" (#21202)

This commit is contained in:
Ben Adams 2020-04-28 21:01:22 +01:00 committed by GitHub
parent 8b2f8088e9
commit 5fc067266f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 105 additions and 36 deletions

View File

@ -2,10 +2,12 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Buffers.Binary;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.X86; using System.Runtime.Intrinsics.X86;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
@ -377,63 +379,104 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
return connectionOptions; return connectionOptions;
} }
public static unsafe TransferCoding GetFinalTransferCoding(StringValues transferEncoding) private static char ToLowerCase(char value) => (char)(value | (char)0x0020);
public static TransferCoding GetFinalTransferCoding(StringValues transferEncoding)
{ {
const ulong chunkedStart = 0x006b_006e_0075_0068; // 4 chars "hunk"
const uint chunkedEnd = 0x0064_0065; // 2 chars "ed"
var transferEncodingOptions = TransferCoding.None; var transferEncodingOptions = TransferCoding.None;
var transferEncodingCount = transferEncoding.Count; var transferEncodingCount = transferEncoding.Count;
for (var i = 0; i < transferEncodingCount; i++) for (var i = 0; i < transferEncodingCount; i++)
{ {
var value = transferEncoding[i]; var values = transferEncoding[i].AsSpan();
fixed (char* ptr = value)
while (values.Length > 0)
{ {
var ch = ptr; int offset;
var tokenEnd = ch; char c = '\0';
var end = ch + value.Length; // Skip any spaces and empty values.
for (offset = 0; offset < values.Length; offset++)
while (ch < end)
{ {
while (tokenEnd < end && *tokenEnd != ',') c = values[offset];
if (c != ' ' && c != ',')
{ {
tokenEnd++; break;
} }
}
while (ch < tokenEnd && *ch == ' ') // Skip last read char.
offset++;
if ((uint)offset > (uint)values.Length)
{
// Consumed entire string, move to next.
break;
}
// Remove leading spaces or empty values.
values = values.Slice(offset);
offset = 0;
var byteValue = MemoryMarshal.AsBytes(values);
if (ToLowerCase(c) == 'c' &&
TryReadLowerCaseUInt64(byteValue, out var result64) && result64 == chunkedStart)
{
offset += sizeof(ulong) / 2;
byteValue = byteValue.Slice(sizeof(ulong));
if (TryReadLowerCaseUInt32(byteValue, out var result32) && result32 == chunkedEnd)
{ {
ch++; offset += sizeof(uint) / 2;
transferEncodingOptions = TransferCoding.Chunked;
} }
}
var tokenLength = tokenEnd - ch; if ((uint)offset >= (uint)values.Length)
{
// Consumed entire string, move to next string.
break;
}
else
{
values = values.Slice(offset);
}
if (tokenLength >= 7 && (*ch | 0x20) == 'c') for (offset = 0; offset < values.Length; offset++)
{
c = values[offset];
if (c == ',')
{ {
if ((*++ch | 0x20) == 'h' && break;
(*++ch | 0x20) == 'u' &&
(*++ch | 0x20) == 'n' &&
(*++ch | 0x20) == 'k' &&
(*++ch | 0x20) == 'e' &&
(*++ch | 0x20) == 'd')
{
ch++;
while (ch < tokenEnd && *ch == ' ')
{
ch++;
}
if (ch == tokenEnd)
{
transferEncodingOptions = TransferCoding.Chunked;
}
}
} }
else if (c != ' ')
if (tokenLength > 0 && ch != tokenEnd)
{ {
// Value contains extra chars; Chunked is not the matched one.
transferEncodingOptions = TransferCoding.Other; transferEncodingOptions = TransferCoding.Other;
} }
}
tokenEnd++; if ((uint)offset >= (uint)values.Length)
ch = tokenEnd; {
// Consumed entire string, move to next string.
break;
}
else if (c == ',')
{
// Consumed value, move to next value.
offset++;
if ((uint)offset >= (uint)values.Length)
{
// Consumed entire string, move to next string.
break;
}
else
{
values = values.Slice(offset);
continue;
}
} }
} }
} }
@ -441,6 +484,32 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
return transferEncodingOptions; return transferEncodingOptions;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryReadLowerCaseUInt64(ReadOnlySpan<byte> byteValue, out ulong value)
{
if (BinaryPrimitives.TryReadUInt64LittleEndian(byteValue, out var result))
{
value = result | 0x0020_0020_0020_0020;
return true;
}
value = 0;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryReadLowerCaseUInt32(ReadOnlySpan<byte> byteValue, out uint value)
{
if (BinaryPrimitives.TryReadUInt32LittleEndian(byteValue, out var result))
{
value = result | 0x0020_0020;
return true;
}
value = 0;
return false;
}
private static void ThrowInvalidContentLengthException(long value) private static void ThrowInvalidContentLengthException(long value)
{ {
throw new ArgumentOutOfRangeException(CoreStrings.FormatInvalidContentLength_InvalidNumber(value)); throw new ArgumentOutOfRangeException(CoreStrings.FormatInvalidContentLength_InvalidNumber(value));