diff --git a/src/Kestrel.Core/Internal/Http2/Http2Connection.cs b/src/Kestrel.Core/Internal/Http2/Http2Connection.cs index dcf3aed4ad..70ca7a28c4 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2Connection.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2Connection.cs @@ -196,6 +196,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 private Task ProcessFrameAsync(IHttpApplication application) { + // http://httpwg.org/specs/rfc7540.html#rfc.section.5.1.1 + // Streams initiated by a client MUST use odd-numbered stream identifiers; ... + // An endpoint that receives an unexpected stream identifier MUST respond with + // a connection error (Section 5.4.1) of type PROTOCOL_ERROR. + if (_incomingFrame.StreamId != 0 && (_incomingFrame.StreamId & 1) == 0) + { + throw new Http2ConnectionErrorException(Http2ErrorCode.PROTOCOL_ERROR); + } + switch (_incomingFrame.Type) { case Http2FrameType.DATA: diff --git a/test/Kestrel.Core.Tests/Http2ConnectionTests.cs b/test/Kestrel.Core.Tests/Http2ConnectionTests.cs index 5cc64cec8c..93515b1267 100644 --- a/test/Kestrel.Core.Tests/Http2ConnectionTests.cs +++ b/test/Kestrel.Core.Tests/Http2ConnectionTests.cs @@ -412,6 +412,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); } + [Fact] + public async Task DATA_Received_StreamIdEven_ConnectionError() + { + await InitializeConnectionAsync(_noopApplication); + + await SendDataAsync(2, _noData, endStream: false); + + await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + } + [Fact] public async Task DATA_Received_PaddingEqualToFramePayloadLength_ConnectionError() { @@ -635,6 +645,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); } + [Fact] + public async Task HEADERS_Received_StreamIdEven_ConnectionError() + { + await InitializeConnectionAsync(_noopApplication); + + await StartStreamAsync(2, _browserRequestHeaders, endStream: true); + + await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + } + [Theory] [InlineData(0)] [InlineData(1)] @@ -682,6 +702,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); } + [Fact] + public async Task PRIORITY_Received_StreamIdEven_ConnectionError() + { + await InitializeConnectionAsync(_noopApplication); + + await SendPriorityAsync(2); + + await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + } + [Theory] [InlineData(4)] [InlineData(6)] @@ -751,6 +781,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); } + [Fact] + public async Task RST_STREAM_Received_StreamIdEven_ConnectionError() + { + await InitializeConnectionAsync(_noopApplication); + + await SendRstStreamAsync(2); + + await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + } + [Theory] [InlineData(3)] [InlineData(5)] @@ -786,7 +826,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } [Fact] - public async Task SETTINGS_Received_StreamIdZero_ConnectionError() + public async Task SETTINGS_Received_StreamIdNonZero_ConnectionError() { await InitializeConnectionAsync(_noopApplication); @@ -962,6 +1002,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); } + [Fact] + public async Task WINDOW_UPDATE_Received_StreamIdEven_ConnectionError() + { + await InitializeConnectionAsync(_noopApplication); + + await SendWindowUpdateAsync(2, sizeIncrement: 42); + + await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR, ignoreNonGoAwayFrames: false); + } + [Fact] public async Task WINDOW_UPDATE_Received_InterleavedWithHeaders_ConnectionError() {