Aborting request if a string can't be converted to ASCII
This commit is contained in:
parent
5d77ad24c2
commit
0753f06c28
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
Loading…
Reference in New Issue