Sanitize and centralize exception throws (#2293)

* Sanitize and centralize exception throws
This commit is contained in:
Ben Adams 2018-02-23 17:24:20 +00:00 committed by David Fowler
parent 6252ffd86a
commit 6728e756b7
9 changed files with 101 additions and 54 deletions

View File

@ -1,7 +1,9 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// 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.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
@ -30,6 +32,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
internal StringValues AllowedHeader { get; } internal StringValues AllowedHeader { get; }
[StackTraceHidden]
internal static void Throw(RequestRejectionReason reason)
{
throw GetException(reason);
}
[MethodImpl(MethodImplOptions.NoInlining)]
internal static BadHttpRequestException GetException(RequestRejectionReason reason) internal static BadHttpRequestException GetException(RequestRejectionReason reason)
{ {
BadHttpRequestException ex; BadHttpRequestException ex;
@ -102,6 +111,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
return ex; return ex;
} }
[StackTraceHidden]
internal static void Throw(RequestRejectionReason reason, string detail)
{
throw GetException(reason, detail);
}
[StackTraceHidden]
internal static void Throw(RequestRejectionReason reason, in StringValues detail)
{
throw GetException(reason, detail.ToString());
}
[MethodImpl(MethodImplOptions.NoInlining)]
internal static BadHttpRequestException GetException(RequestRejectionReason reason, string detail) internal static BadHttpRequestException GetException(RequestRejectionReason reason, string detail)
{ {
BadHttpRequestException ex; BadHttpRequestException ex;
@ -141,4 +163,4 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
return ex; return ex;
} }
} }
} }

View File

@ -119,7 +119,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
var result = _parser.ParseRequestLine(new Http1ParsingHandler(this), buffer, out consumed, out examined); var result = _parser.ParseRequestLine(new Http1ParsingHandler(this), buffer, out consumed, out examined);
if (!result && overLength) if (!result && overLength)
{ {
ThrowRequestRejected(RequestRejectionReason.RequestLineTooLong); BadHttpRequestException.Throw(RequestRejectionReason.RequestLineTooLong);
} }
return result; return result;
@ -143,7 +143,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (!result && overLength) if (!result && overLength)
{ {
ThrowRequestRejected(RequestRejectionReason.HeadersExceedMaxTotalSize); BadHttpRequestException.Throw(RequestRejectionReason.HeadersExceedMaxTotalSize);
} }
if (result) if (result)
{ {
@ -278,7 +278,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
// requests (https://tools.ietf.org/html/rfc7231#section-4.3.6). // requests (https://tools.ietf.org/html/rfc7231#section-4.3.6).
if (method != HttpMethod.Connect) if (method != HttpMethod.Connect)
{ {
ThrowRequestRejected(RequestRejectionReason.ConnectMethodRequired); BadHttpRequestException.Throw(RequestRejectionReason.ConnectMethodRequired);
} }
// When making a CONNECT request to establish a tunnel through one or // When making a CONNECT request to establish a tunnel through one or
@ -303,7 +303,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
// OPTIONS request (https://tools.ietf.org/html/rfc7231#section-4.3.7). // OPTIONS request (https://tools.ietf.org/html/rfc7231#section-4.3.7).
if (method != HttpMethod.Options) if (method != HttpMethod.Options)
{ {
ThrowRequestRejected(RequestRejectionReason.OptionsMethodRequired); BadHttpRequestException.Throw(RequestRejectionReason.OptionsMethodRequired);
} }
RawTarget = Asterisk; RawTarget = Asterisk;
@ -367,17 +367,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
var host = HttpRequestHeaders.HeaderHost; var host = HttpRequestHeaders.HeaderHost;
if (host.Count <= 0) if (host.Count <= 0)
{ {
ThrowRequestRejected(RequestRejectionReason.MissingHostHeader); BadHttpRequestException.Throw(RequestRejectionReason.MissingHostHeader);
} }
else if (host.Count > 1) else if (host.Count > 1)
{ {
ThrowRequestRejected(RequestRejectionReason.MultipleHostHeaders); BadHttpRequestException.Throw(RequestRejectionReason.MultipleHostHeaders);
} }
else if (_requestTargetForm == HttpRequestTarget.AuthorityForm) else if (_requestTargetForm == HttpRequestTarget.AuthorityForm)
{ {
if (!host.Equals(RawTarget)) if (!host.Equals(RawTarget))
{ {
ThrowRequestRejected(RequestRejectionReason.InvalidHostHeader, host.ToString()); BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, in host);
} }
} }
else if (_requestTargetForm == HttpRequestTarget.AbsoluteForm) else if (_requestTargetForm == HttpRequestTarget.AbsoluteForm)
@ -393,7 +393,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if ((host != _absoluteRequestTarget.Authority || !_absoluteRequestTarget.IsDefaultPort) if ((host != _absoluteRequestTarget.Authority || !_absoluteRequestTarget.IsDefaultPort)
&& host != authorityAndPort) && host != authorityAndPort)
{ {
ThrowRequestRejected(RequestRejectionReason.InvalidHostHeader, host.ToString()); BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, in host);
} }
} }
} }
@ -446,7 +446,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
if (_requestProcessingStatus == RequestProcessingStatus.ParsingHeaders) if (_requestProcessingStatus == RequestProcessingStatus.ParsingHeaders)
{ {
throw BadHttpRequestException.GetException(RequestRejectionReason BadHttpRequestException.Throw(RequestRejectionReason
.MalformedRequestInvalidHeaders); .MalformedRequestInvalidHeaders);
} }
throw; throw;
@ -464,11 +464,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
endConnection = true; endConnection = true;
return true; return true;
case RequestProcessingStatus.ParsingRequestLine: case RequestProcessingStatus.ParsingRequestLine:
throw BadHttpRequestException.GetException( BadHttpRequestException.Throw(RequestRejectionReason.InvalidRequestLine);
RequestRejectionReason.InvalidRequestLine); break;
case RequestProcessingStatus.ParsingHeaders: case RequestProcessingStatus.ParsingHeaders:
throw BadHttpRequestException.GetException( BadHttpRequestException.Throw(RequestRejectionReason.MalformedRequestInvalidHeaders);
RequestRejectionReason.MalformedRequestInvalidHeaders); break;
} }
} }
else if (!_keepAlive && _requestProcessingStatus == RequestProcessingStatus.RequestPending) else if (!_keepAlive && _requestProcessingStatus == RequestProcessingStatus.RequestPending)
@ -482,7 +482,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
// In this case, there is an ongoing request but the start line/header parsing has timed out, so send // In this case, there is an ongoing request but the start line/header parsing has timed out, so send
// a 408 response. // a 408 response.
throw BadHttpRequestException.GetException(RequestRejectionReason.RequestTimeout); BadHttpRequestException.Throw(RequestRejectionReason.RequestTimeout);
} }
endConnection = false; endConnection = false;

View File

@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (_context.RequestTimedOut) if (_context.RequestTimedOut)
{ {
_context.ThrowRequestRejected(RequestRejectionReason.RequestTimeout); BadHttpRequestException.Throw(RequestRejectionReason.RequestTimeout);
} }
var readableBuffer = result.Buffer; var readableBuffer = result.Buffer;
@ -97,7 +97,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
} }
else if (result.IsCompleted) else if (result.IsCompleted)
{ {
_context.ThrowRequestRejected(RequestRejectionReason.UnexpectedEndOfRequestContent); BadHttpRequestException.Throw(RequestRejectionReason.UnexpectedEndOfRequestContent);
} }
awaitable = _context.Input.ReadAsync(); awaitable = _context.Input.ReadAsync();
@ -233,7 +233,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
if (headers.HeaderTransferEncoding.Count > 0 || (headers.ContentLength.HasValue && headers.ContentLength.Value != 0)) if (headers.HeaderTransferEncoding.Count > 0 || (headers.ContentLength.HasValue && headers.ContentLength.Value != 0))
{ {
context.ThrowRequestRejected(RequestRejectionReason.UpgradeRequestCannotHavePayload); BadHttpRequestException.Throw(RequestRejectionReason.UpgradeRequestCannotHavePayload);
} }
return new ForUpgrade(context); return new ForUpgrade(context);
@ -252,7 +252,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
// status code and then close the connection. // status code and then close the connection.
if (transferCoding != TransferCoding.Chunked) if (transferCoding != TransferCoding.Chunked)
{ {
context.ThrowRequestRejected(RequestRejectionReason.FinalTransferCodingNotChunked, transferEncoding.ToString()); BadHttpRequestException.Throw(RequestRejectionReason.FinalTransferCodingNotChunked, in transferEncoding);
} }
return new ForChunkedEncoding(keepAlive, context); return new ForChunkedEncoding(keepAlive, context);
@ -278,7 +278,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (HttpMethods.IsPost(context.Method) || HttpMethods.IsPut(context.Method)) if (HttpMethods.IsPost(context.Method) || HttpMethods.IsPut(context.Method))
{ {
var requestRejectionReason = httpVersion == HttpVersion.Http11 ? RequestRejectionReason.LengthRequired : RequestRejectionReason.LengthRequiredHttp10; var requestRejectionReason = httpVersion == HttpVersion.Http11 ? RequestRejectionReason.LengthRequired : RequestRejectionReason.LengthRequiredHttp10;
context.ThrowRequestRejected(requestRejectionReason, context.Method); BadHttpRequestException.Throw(requestRejectionReason, context.Method);
} }
} }
@ -339,7 +339,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
if (_contentLength > _context.MaxRequestBodySize) if (_contentLength > _context.MaxRequestBodySize)
{ {
_context.ThrowRequestRejected(RequestRejectionReason.RequestBodyTooLarge); BadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTooLarge);
} }
} }
} }
@ -452,7 +452,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (_consumedBytes > _context.MaxRequestBodySize) if (_consumedBytes > _context.MaxRequestBodySize)
{ {
_context.ThrowRequestRejected(RequestRejectionReason.RequestBodyTooLarge); BadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTooLarge);
} }
} }
@ -509,7 +509,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
} }
// At this point, 10 bytes have been consumed which is enough to parse the max value "7FFFFFFF\r\n". // At this point, 10 bytes have been consumed which is enough to parse the max value "7FFFFFFF\r\n".
_context.ThrowRequestRejected(RequestRejectionReason.BadChunkSizeData); BadHttpRequestException.Throw(RequestRejectionReason.BadChunkSizeData);
} }
private void ParseExtension(ReadOnlyBuffer<byte> buffer, out SequencePosition consumed, out SequencePosition examined) private void ParseExtension(ReadOnlyBuffer<byte> buffer, out SequencePosition consumed, out SequencePosition examined)
@ -604,7 +604,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
} }
else else
{ {
_context.ThrowRequestRejected(RequestRejectionReason.BadChunkSuffix); BadHttpRequestException.Throw(RequestRejectionReason.BadChunkSuffix);
} }
} }
@ -660,7 +660,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
throw new IOException(CoreStrings.BadRequest_BadChunkSizeData, ex); throw new IOException(CoreStrings.BadRequest_BadChunkSizeData, ex);
} }
_context.ThrowRequestRejected(RequestRejectionReason.BadChunkSizeData); BadHttpRequestException.Throw(RequestRejectionReason.BadChunkSizeData);
return -1; // can't happen, but compiler complains return -1; // can't happen, but compiler complains
} }

View File

@ -4049,7 +4049,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
if (_contentLength.HasValue) if (_contentLength.HasValue)
{ {
ThrowMultipleContentLengthsException(); BadHttpRequestException.Throw(RequestRejectionReason.MultipleContentLengths);
} }
else else
{ {

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Collections; using System.Collections;
using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
@ -254,7 +255,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
} }
// Headers don't end in CRLF line. // Headers don't end in CRLF line.
RejectRequest(RequestRejectionReason.InvalidRequestHeadersNoCRLF); BadHttpRequestException.Throw(RequestRejectionReason.InvalidRequestHeadersNoCRLF);
} }
// We moved the reader so look ahead 2 bytes so reset both the reader // We moved the reader so look ahead 2 bytes so reset both the reader
@ -483,18 +484,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
c == '~'; c == '~';
} }
private void RejectRequest(RequestRejectionReason reason) [StackTraceHidden]
=> throw BadHttpRequestException.GetException(reason);
private unsafe void RejectRequestLine(byte* requestLine, int length) private unsafe void RejectRequestLine(byte* requestLine, int length)
=> throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestLine, requestLine, length); => throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestLine, requestLine, length);
[StackTraceHidden]
private unsafe void RejectRequestHeader(byte* headerLine, int length) private unsafe void RejectRequestHeader(byte* headerLine, int length)
=> throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestHeader, headerLine, length); => throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestHeader, headerLine, length);
[StackTraceHidden]
private unsafe void RejectUnknownVersion(byte* version, int length) private unsafe void RejectUnknownVersion(byte* version, int length)
=> throw GetInvalidRequestException(RequestRejectionReason.UnrecognizedHTTPVersion, version, length); => throw GetInvalidRequestException(RequestRejectionReason.UnrecognizedHTTPVersion, version, length);
[MethodImpl(MethodImplOptions.NoInlining)]
private unsafe BadHttpRequestException GetInvalidRequestException(RequestRejectionReason reason, byte* detail, int length) private unsafe BadHttpRequestException GetInvalidRequestException(RequestRejectionReason reason, byte* detail, int length)
=> BadHttpRequestException.GetException( => BadHttpRequestException.GetException(
reason, reason,

View File

@ -429,7 +429,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_requestHeadersParsed++; _requestHeadersParsed++;
if (_requestHeadersParsed > ServerOptions.Limits.MaxRequestHeaderCount) if (_requestHeadersParsed > ServerOptions.Limits.MaxRequestHeaderCount)
{ {
ThrowRequestRejected(RequestRejectionReason.TooManyHeaders); BadHttpRequestException.Throw(RequestRejectionReason.TooManyHeaders);
} }
var valueString = value.GetAsciiStringNonNullCharacters(); var valueString = value.GetAsciiStringNonNullCharacters();
@ -863,13 +863,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_responseBytesWritten + count > responseHeaders.ContentLength.Value) _responseBytesWritten + count > responseHeaders.ContentLength.Value)
{ {
_keepAlive = false; _keepAlive = false;
throw new InvalidOperationException( ThrowTooManyBytesWritten(count);
CoreStrings.FormatTooManyBytesWritten(_responseBytesWritten + count, responseHeaders.ContentLength.Value));
} }
_responseBytesWritten += count; _responseBytesWritten += count;
} }
[StackTraceHidden]
private void ThrowTooManyBytesWritten(int count)
{
throw GetTooManyBytesWrittenException(count);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private InvalidOperationException GetTooManyBytesWrittenException(int count)
{
var responseHeaders = HttpResponseHeaders;
return new InvalidOperationException(
CoreStrings.FormatTooManyBytesWritten(_responseBytesWritten + count, responseHeaders.ContentLength.Value));
}
private void CheckLastWrite() private void CheckLastWrite()
{ {
var responseHeaders = HttpResponseHeaders; var responseHeaders = HttpResponseHeaders;
@ -1250,25 +1263,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
// Writes to HEAD response are ignored and logged at the end of the request // Writes to HEAD response are ignored and logged at the end of the request
if (Method != "HEAD") if (Method != "HEAD")
{ {
// Throw Exception for 204, 205, 304 responses. ThrowWritingToResponseBodyNotSupported();
throw new InvalidOperationException(CoreStrings.FormatWritingToResponseBodyNotSupported(StatusCode));
} }
} }
[StackTraceHidden]
private void ThrowWritingToResponseBodyNotSupported()
{
// Throw Exception for 204, 205, 304 responses.
throw new InvalidOperationException(CoreStrings.FormatWritingToResponseBodyNotSupported(StatusCode));
}
[StackTraceHidden]
private void ThrowResponseAbortedException() private void ThrowResponseAbortedException()
{ {
throw new ObjectDisposedException(CoreStrings.UnhandledApplicationException, _applicationException); throw new ObjectDisposedException(CoreStrings.UnhandledApplicationException, _applicationException);
} }
public void ThrowRequestRejected(RequestRejectionReason reason) [StackTraceHidden]
=> throw BadHttpRequestException.GetException(reason);
public void ThrowRequestRejected(RequestRejectionReason reason, string detail)
=> throw BadHttpRequestException.GetException(reason, detail);
public void ThrowRequestTargetRejected(Span<byte> target) public void ThrowRequestTargetRejected(Span<byte> target)
=> throw GetInvalidRequestTargetException(target); => throw GetInvalidRequestTargetException(target);
[MethodImpl(MethodImplOptions.NoInlining)]
private BadHttpRequestException GetInvalidRequestTargetException(Span<byte> target) private BadHttpRequestException GetInvalidRequestTargetException(Span<byte> target)
=> BadHttpRequestException.GetException( => BadHttpRequestException.GetException(
RequestRejectionReason.InvalidRequestTarget, RequestRejectionReason.InvalidRequestTarget,

View File

@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
long parsed; long parsed;
if (!HeaderUtilities.TryParseNonNegativeInt64(value, out parsed)) if (!HeaderUtilities.TryParseNonNegativeInt64(value, out parsed))
{ {
ThrowInvalidContentLengthException(value); BadHttpRequestException.Throw(RequestRejectionReason.InvalidContentLength, value);
} }
return parsed; return parsed;
@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
if (!StringUtilities.TryGetAsciiString(pKeyBytes, keyBuffer, keyLength)) if (!StringUtilities.TryGetAsciiString(pKeyBytes, keyBuffer, keyLength))
{ {
throw BadHttpRequestException.GetException(RequestRejectionReason.InvalidCharactersInHeaderName); BadHttpRequestException.Throw(RequestRejectionReason.InvalidCharactersInHeaderName);
} }
} }
@ -56,16 +56,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
Unknown[key] = AppendValue(existing, value); Unknown[key] = AppendValue(existing, value);
} }
private static void ThrowInvalidContentLengthException(string value)
{
throw BadHttpRequestException.GetException(RequestRejectionReason.InvalidContentLength, value);
}
private static void ThrowMultipleContentLengthsException()
{
throw BadHttpRequestException.GetException(RequestRejectionReason.MultipleContentLengths);
}
public Enumerator GetEnumerator() public Enumerator GetEnumerator()
{ {
return new Enumerator(this); return new Enumerator(this);

View File

@ -0,0 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace System.Diagnostics
{
/// <summary>
/// Attribute to add to non-returning throw only methods,
/// to restore the stack trace back to what it would be if the throw was in-place
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Struct, Inherited = false)]
internal sealed class StackTraceHiddenAttribute : Attribute
{
// https://github.com/dotnet/coreclr/blob/eb54e48b13fdfb7233b7bcd32b93792ba3e89f0c/src/mscorlib/shared/System/Diagnostics/StackTraceHiddenAttribute.cs
public StackTraceHiddenAttribute() { }
}
}

View File

@ -36,7 +36,7 @@ namespace CodeGenerator
{{{(header.Identifier == "ContentLength" ? $@" {{{(header.Identifier == "ContentLength" ? $@"
if (_contentLength.HasValue) if (_contentLength.HasValue)
{{ {{
ThrowMultipleContentLengthsException(); BadHttpRequestException.Throw(RequestRejectionReason.MultipleContentLengths);
}} }}
else else
{{ {{