Add configurability for max header field size in HPACK

This commit is contained in:
John Luo 2018-09-21 15:59:29 -07:00
parent a142c01a1c
commit ceaa3c86fc
6 changed files with 86 additions and 27 deletions

View File

@ -14,11 +14,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
private int _maxStreamsPerConnection = 100;
private int _headerTableSize = (int)Http2PeerSettings.DefaultHeaderTableSize;
private int _maxFrameSize = (int)Http2PeerSettings.DefaultMaxFrameSize;
private int _maxRequestHeaderFieldSize = 8192;
/// <summary>
/// Limits the number of concurrent request streams per HTTP/2 connection. Excess streams will be refused.
/// <para>
/// Defaults to 100
/// Value must be greater than 0, defaults to 100
/// </para>
/// </summary>
public int MaxStreamsPerConnection
@ -74,5 +75,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
_maxFrameSize = value;
}
}
/// <summary>
/// Indicates the size of the maximum allowed size of a request header field sequence. This limit applies to both name and value sequences in their compressed and uncompressed representations.
/// <para>
/// Value must be greater than 0, defaults to 8192
/// </para>
/// </summary>
public int MaxRequestHeaderFieldSize
{
get => _maxRequestHeaderFieldSize;
set
{
if (value <= 0)
{
throw new ArgumentOutOfRangeException(nameof(value), value, CoreStrings.GreaterThanZeroRequired);
}
_maxRequestHeaderFieldSize = value;
}
}
}
}

View File

@ -23,9 +23,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
DynamicTableSizeUpdate
}
// TODO: add new configurable limit
public const int MaxStringOctets = 4096;
// http://httpwg.org/specs/rfc7541.html#rfc.section.6.1
// 0 1 2 3 4 5 6 7
// +---+---+---+---+---+---+---+---+
@ -83,9 +80,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
private readonly int _maxDynamicTableSize;
private readonly DynamicTable _dynamicTable;
private readonly IntegerDecoder _integerDecoder = new IntegerDecoder();
private readonly byte[] _stringOctets = new byte[MaxStringOctets];
private readonly byte[] _headerNameOctets = new byte[MaxStringOctets];
private readonly byte[] _headerValueOctets = new byte[MaxStringOctets];
private readonly byte[] _stringOctets;
private readonly byte[] _headerNameOctets;
private readonly byte[] _headerValueOctets;
private State _state = State.Ready;
private byte[] _headerName;
@ -97,17 +94,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
private bool _huffman;
private bool _headersObserved;
public HPackDecoder(int maxDynamicTableSize)
: this(maxDynamicTableSize, new DynamicTable(maxDynamicTableSize))
{
_maxDynamicTableSize = maxDynamicTableSize;
}
public HPackDecoder(int maxDynamicTableSize, int maxRequestHeaderFieldSize)
: this(maxDynamicTableSize, maxRequestHeaderFieldSize, new DynamicTable(maxDynamicTableSize)) { }
// For testing.
internal HPackDecoder(int maxDynamicTableSize, DynamicTable dynamicTable)
internal HPackDecoder(int maxDynamicTableSize, int maxRequestHeaderFieldSize, DynamicTable dynamicTable)
{
_maxDynamicTableSize = maxDynamicTableSize;
_dynamicTable = dynamicTable;
_stringOctets = new byte[maxRequestHeaderFieldSize];
_headerNameOctets = new byte[maxRequestHeaderFieldSize];
_headerValueOctets = new byte[maxRequestHeaderFieldSize];
}
public void Decode(ReadOnlySequence<byte> data, bool endHeaders, IHttpHeadersHandler handler)

View File

@ -86,13 +86,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
public Http2Connection(Http2ConnectionContext context)
{
var httpLimits = context.ServiceContext.ServerOptions.Limits;
var http2Limits = httpLimits.Http2;
_context = context;
_frameWriter = new Http2FrameWriter(context.Transport.Output, context.ConnectionContext, _outputFlowControl, this, context.ConnectionId, context.ServiceContext.Log);
_serverSettings.MaxConcurrentStreams = (uint)context.ServiceContext.ServerOptions.Limits.Http2.MaxStreamsPerConnection;
_serverSettings.MaxFrameSize = (uint)context.ServiceContext.ServerOptions.Limits.Http2.MaxFrameSize;
_serverSettings.HeaderTableSize = (uint)context.ServiceContext.ServerOptions.Limits.Http2.HeaderTableSize;
_hpackDecoder = new HPackDecoder((int)_serverSettings.HeaderTableSize);
_serverSettings.MaxHeaderListSize = (uint)context.ServiceContext.ServerOptions.Limits.MaxRequestHeadersTotalSize;
_serverSettings.MaxConcurrentStreams = (uint)http2Limits.MaxStreamsPerConnection;
_serverSettings.MaxFrameSize = (uint)http2Limits.MaxFrameSize;
_serverSettings.HeaderTableSize = (uint)http2Limits.HeaderTableSize;
_hpackDecoder = new HPackDecoder(http2Limits.HeaderTableSize, http2Limits.MaxRequestHeaderFieldSize);
_serverSettings.MaxHeaderListSize = (uint)httpLimits.MaxRequestHeadersTotalSize;
}
public string ConnectionId => _context.ConnectionId;

View File

@ -17,6 +17,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
public class HPackDecoderTests : IHttpHeadersHandler
{
private const int DynamicTableInitialMaxSize = 4096;
private const int MaxRequestHeaderFieldSize = 8192;
// Indexed Header Field Representation - Static Table - Index 2 (:method: GET)
private static readonly byte[] _indexedHeaderStatic = new byte[] { 0x82 };
@ -94,7 +95,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
public HPackDecoderTests()
{
_dynamicTable = new DynamicTable(DynamicTableInitialMaxSize);
_decoder = new HPackDecoder(DynamicTableInitialMaxSize, _dynamicTable);
_decoder = new HPackDecoder(DynamicTableInitialMaxSize, MaxRequestHeaderFieldSize, _dynamicTable);
}
void IHttpHeadersHandler.OnHeader(Span<byte> name, Span<byte> value)
@ -438,14 +439,32 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
public void DecodesStringLength_GreaterThanLimit_Error()
{
var encoded = _literalHeaderFieldWithoutIndexingNewName
.Concat(new byte[] { 0xff, 0x82, 0x1f }) // 4097 encoded with 7-bit prefix
.Concat(new byte[] { 0xff, 0x82, 0x3f }) // 8193 encoded with 7-bit prefix
.ToArray();
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(new ReadOnlySequence<byte>(encoded), endHeaders: true, handler: this));
Assert.Equal(CoreStrings.FormatHPackStringLengthTooLarge(4097, HPackDecoder.MaxStringOctets), exception.Message);
Assert.Equal(CoreStrings.FormatHPackStringLengthTooLarge(MaxRequestHeaderFieldSize + 1, MaxRequestHeaderFieldSize), exception.Message);
Assert.Empty(_decodedHeaders);
}
[Fact]
public void DecodesStringLength_LimitConfigurable()
{
var decoder = new HPackDecoder(DynamicTableInitialMaxSize, MaxRequestHeaderFieldSize + 1);
var string8193 = new string('a', MaxRequestHeaderFieldSize + 1);
var encoded = _literalHeaderFieldWithoutIndexingNewName
.Concat(new byte[] { 0x7f, 0x82, 0x3f }) // 8193 encoded with 7-bit prefix, no Huffman encoding
.Concat(Encoding.ASCII.GetBytes(string8193))
.Concat(new byte[] { 0x7f, 0x82, 0x3f }) // 8193 encoded with 7-bit prefix, no Huffman encoding
.Concat(Encoding.ASCII.GetBytes(string8193))
.ToArray();
decoder.Decode(new ReadOnlySequence<byte>(encoded), endHeaders: true, handler: this);
Assert.Equal(string8193, _decodedHeaders[string8193]);
}
public static readonly TheoryData<byte[]> _incompleteHeaderBlockData = new TheoryData<byte[]>
{
// Indexed Header Field Representation - incomplete index encoding

View File

@ -315,7 +315,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
}
[Theory]
[InlineData(1 << 14 - 1)]
[InlineData((1 << 14) - 1)]
[InlineData(1 << 24)]
[InlineData(-1)]
public void Http2MaxFrameSizeInvalid(int value)
@ -331,12 +331,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
}
[Theory]
[InlineData(4097)]
[InlineData(int.MinValue)]
[InlineData(-1)]
[InlineData(0)]
public void Http2HeaderTableSizeInvalid(int value)
{
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new KestrelServerLimits().Http2.MaxFrameSize = value);
Assert.Contains("A value between", ex.Message);
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new KestrelServerLimits().Http2.HeaderTableSize = value);
Assert.StartsWith(CoreStrings.GreaterThanZeroRequired, ex.Message);
}
[Fact]
public void Http2MaxRequestHeaderFieldSizeDefault()
{
Assert.Equal(8192, new KestrelServerLimits().Http2.MaxRequestHeaderFieldSize);
}
[Theory]
[InlineData(int.MinValue)]
[InlineData(-1)]
[InlineData(0)]
public void Http2MaxRequestHeaderFieldSizeInvalid(int value)
{
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new KestrelServerLimits().Http2.MaxRequestHeaderFieldSize = value);
Assert.StartsWith(CoreStrings.GreaterThanZeroRequired, ex.Message);
}
public static TheoryData<TimeSpan> TimeoutValidData => new TheoryData<TimeSpan>

View File

@ -31,7 +31,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
public class Http2TestBase : TestApplicationErrorLoggerLoggedTest, IDisposable, IHttpHeadersHandler
{
protected static readonly string _4kHeaderValue = new string('a', HPackDecoder.MaxStringOctets);
protected static readonly int MaxRequestHeaderFieldSize = 8192;
protected static readonly string _4kHeaderValue = new string('a', 4096);
protected static readonly IEnumerable<KeyValuePair<string, string>> _browserRequestHeaders = new[]
{
@ -99,7 +100,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
);
_pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions);
_hpackDecoder = new HPackDecoder((int)_clientSettings.HeaderTableSize);
_hpackDecoder = new HPackDecoder((int)_clientSettings.HeaderTableSize, MaxRequestHeaderFieldSize);
_noopApplication = context => Task.CompletedTask;