Aborting request if a string can't be converted to ASCII

This commit is contained in:
moozzyk 2016-05-27 10:32:51 -07:00
parent 5d77ad24c2
commit 0753f06c28
5 changed files with 79 additions and 17 deletions

View File

@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
private readonly object _onStartingSync = new Object(); private readonly object _onStartingSync = new Object();
private readonly object _onCompletedSync = new Object(); private readonly object _onCompletedSync = new Object();
private bool _requestRejected; protected bool _requestRejected;
private Streams _frameStreams; private Streams _frameStreams;
protected List<KeyValuePair<Func<object, Task>, object>> _onStarting; protected List<KeyValuePair<Func<object, Task>, object>> _onStarting;
@ -1175,12 +1175,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
} }
public void RejectRequest(string message) public void RejectRequest(string message)
{
var ex = new BadHttpRequestException(message);
SetBadRequestState(ex);
throw ex;
}
public void SetBadRequestState(BadHttpRequestException ex)
{ {
_requestProcessingStopping = true; _requestProcessingStopping = true;
_requestRejected = true; _requestRejected = true;
var ex = new BadHttpRequestException(message);
Log.ConnectionBadRequest(ConnectionId, ex); Log.ConnectionBadRequest(ConnectionId, ex);
throw ex;
} }
protected void ReportApplicationError(Exception ex) protected void ReportApplicationError(Exception ex)

View File

@ -5,6 +5,7 @@ using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Server.Kestrel.Exceptions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Http namespace Microsoft.AspNetCore.Server.Kestrel.Http
@ -145,6 +146,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
Reset(); Reset();
} }
} }
catch (BadHttpRequestException ex)
{
if (!_requestRejected)
{
SetBadRequestState(ex);
Log.LogWarning(0, ex, "Connection processing ended abnormally");
}
}
catch (Exception ex) catch (Exception ex)
{ {
Log.LogWarning(0, ex, "Connection processing ended abnormally"); Log.LogWarning(0, ex, "Connection processing ended abnormally");

View File

@ -5,11 +5,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
{ {
internal class AsciiUtilities internal class AsciiUtilities
{ {
public static unsafe void GetAsciiString(byte* input, char* output, int count) public static unsafe bool TryGetAsciiString(byte* input, char* output, int count)
{ {
var i = 0; var i = 0;
int orValue = 0;
bool hasZero = false;
while (i < count - 11) while (i < count - 11)
{ {
orValue |= *input | *(input + 1) | *(input + 2) | *(input + 3) | *(input + 4) | *(input + 5) |
*(input + 6) | *(input + 7) | *(input + 8) | *(input + 9) | *(input + 10) | *(input + 11);
hasZero = hasZero || *input == 0 || *(input + 1) == 0 || *(input + 2) == 0 || *(input + 3) == 0 ||
*(input + 4) == 0 || *(input + 5) == 0 || *(input + 6) == 0 || *(input + 7) == 0 ||
*(input + 8) == 0 || *(input + 9) == 0 || *(input + 10) == 0 || *(input + 11) == 0;
i += 12; i += 12;
*(output) = (char)*(input); *(output) = (char)*(input);
*(output + 1) = (char)*(input + 1); *(output + 1) = (char)*(input + 1);
@ -28,6 +36,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
} }
if (i < count - 5) if (i < count - 5)
{ {
orValue |= *input | *(input + 1) | *(input + 2) | *(input + 3) | *(input + 4) | *(input + 5);
hasZero = hasZero || *input == 0 || *(input + 1) == 0 || *(input + 2) == 0 || *(input + 3) == 0 ||
*(input + 4) == 0 || *(input + 5) == 0;
i += 6; i += 6;
*(output) = (char)*(input); *(output) = (char)*(input);
*(output + 1) = (char)*(input + 1); *(output + 1) = (char)*(input + 1);
@ -40,6 +52,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
} }
if (i < count - 3) if (i < count - 3)
{ {
orValue |= *input | *(input + 1) | *(input + 2) | *(input + 3);
hasZero = hasZero || *input == 0 || *(input + 1) == 0 || *(input + 2) == 0 || *(input + 3) == 0;
i += 4; i += 4;
*(output) = (char)*(input); *(output) = (char)*(input);
*(output + 1) = (char)*(input + 1); *(output + 1) = (char)*(input + 1);
@ -48,13 +63,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
output += 4; output += 4;
input += 4; input += 4;
} }
while (i < count) while (i < count)
{ {
orValue |= *input;
hasZero = hasZero || *input == 0;
i++; i++;
*output = (char)*input; *output = (char)*input;
output++; output++;
input++; input++;
} }
return (orValue & 0x80) == 0 && !hasZero;
} }
} }
} }

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Text; using System.Text;
using Microsoft.AspNetCore.Server.Kestrel.Exceptions;
namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
{ {
@ -93,10 +94,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
return null; return null;
} }
// Bytes out of the range of ascii are treated as "opaque data"
// and kept in string as a char value that casts to same input byte value
// https://tools.ietf.org/html/rfc7230#section-3.2.4
var inputOffset = start.Index; var inputOffset = start.Index;
var block = start.Block; var block = start.Block;
@ -117,7 +114,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure
if (following > 0) if (following > 0)
{ {
AsciiUtilities.GetAsciiString(block.DataFixedPtr + inputOffset, output + outputOffset, following); if (!AsciiUtilities.TryGetAsciiString(block.DataFixedPtr + inputOffset, output + outputOffset, following))
{
throw new BadHttpRequestException("The input string contains non-ASCII or null characters.");
}
outputOffset += following; outputOffset += following;
remaining -= following; remaining -= following;
} }

View File

@ -3,17 +3,18 @@
using System; using System;
using System.Linq; using System.Linq;
using Microsoft.AspNetCore.Server.Kestrel.Exceptions;
using Microsoft.AspNetCore.Server.Kestrel.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Infrastructure;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Server.KestrelTests namespace Microsoft.AspNetCore.Server.KestrelTests
{ {
public class AsciiDecoderTests public class AsciiDecodingTests
{ {
[Fact] [Fact]
private void FullByteRangeSupported() private void FullAsciiRangeSupported()
{ {
var byteRange = Enumerable.Range(0, 256).Select(x => (byte)x).ToArray(); var byteRange = Enumerable.Range(1, 127).Select(x => (byte)x).ToArray();
using (var pool = new MemoryPool()) using (var pool = new MemoryPool())
{ {
var mem = pool.Lease(); var mem = pool.Lease();
@ -26,7 +27,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
Assert.Equal(s.Length, byteRange.Length); Assert.Equal(s.Length, byteRange.Length);
for (var i = 0; i < byteRange.Length; i++) for (var i = 1; i < byteRange.Length; i++)
{ {
var sb = (byte)s[i]; var sb = (byte)s[i];
var b = byteRange[i]; var b = byteRange[i];
@ -38,10 +39,36 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
} }
} }
[Theory]
[InlineData(0x00)]
[InlineData(0x80)]
private void ExceptionThrownForZeroOrNonAscii(byte b)
{
for (var length = 1; length < 16; length++)
{
for (var position = 0; position < length; position++)
{
var byteRange = Enumerable.Range(1, length).Select(x => (byte)x).ToArray();
byteRange[position] = b;
using (var pool = new MemoryPool())
{
var mem = pool.Lease();
mem.GetIterator().CopyFrom(byteRange);
var begin = mem.GetIterator();
var end = GetIterator(begin, byteRange.Length);
Assert.Throws<BadHttpRequestException>(() => begin.GetAsciiString(end));
}
}
}
}
[Fact] [Fact]
private void MultiBlockProducesCorrectResults() private void MultiBlockProducesCorrectResults()
{ {
var byteRange = Enumerable.Range(0, 512 + 64).Select(x => (byte)x).ToArray(); var byteRange = Enumerable.Range(0, 512 + 64).Select(x => (byte)((x & 0x7f) | 0x01)).ToArray();
var expectedByteRange = byteRange var expectedByteRange = byteRange
.Concat(byteRange) .Concat(byteRange)
.Concat(byteRange) .Concat(byteRange)
@ -72,7 +99,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
for (var i = 0; i < expectedByteRange.Length; i++) for (var i = 0; i < expectedByteRange.Length; i++)
{ {
var sb = (byte)s[i]; var sb = (byte)((s[i] & 0x7f) | 0x01);
var b = expectedByteRange[i]; var b = expectedByteRange[i];
Assert.Equal(sb, b); Assert.Equal(sb, b);
@ -88,7 +115,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
[Fact] [Fact]
private void LargeAllocationProducesCorrectResults() private void LargeAllocationProducesCorrectResults()
{ {
var byteRange = Enumerable.Range(0, 16384 + 64).Select(x => (byte)x).ToArray(); var byteRange = Enumerable.Range(0, 16384 + 64).Select(x => (byte)((x & 0x7f) | 0x01)).ToArray();
var expectedByteRange = byteRange.Concat(byteRange).ToArray(); var expectedByteRange = byteRange.Concat(byteRange).ToArray();
using (var pool = new MemoryPool()) using (var pool = new MemoryPool())
{ {
@ -113,7 +140,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
for (var i = 0; i < expectedByteRange.Length; i++) for (var i = 0; i < expectedByteRange.Length; i++)
{ {
var sb = (byte)s[i]; var sb = (byte)((s[i] & 0x7f) | 0x01);
var b = expectedByteRange[i]; var b = expectedByteRange[i];
Assert.Equal(sb, b); Assert.Equal(sb, b);