Skip to content

Commit eee57aa

Browse files
committed
Connection error for invalid HTTP/3 frame on request stream
1 parent e61245a commit eee57aa

File tree

6 files changed

+61
-15
lines changed

6 files changed

+61
-15
lines changed

src/Servers/Kestrel/Core/src/CoreStrings.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,4 +659,10 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
659659
<data name="Http3StreamErrorFrameReceivedAfterTrailers" xml:space="preserve">
660660
<value>The client sent a {frameType} frame after trailing HEADERS.</value>
661661
</data>
662+
<data name="Http3ErrorUnsupportedFrameOnRequestStream" xml:space="preserve">
663+
<value>The client sent a {frameType} frame to a request stream which isn't supported.</value>
664+
</data>
665+
<data name="Http3ErrorUnsupportedFrameOnServer" xml:space="preserve">
666+
<value>The client sent a {frameType} frame to a the server which isn't supported.</value>
667+
</data>
662668
</root>

src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.Data.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,17 @@ public void PrepareData()
1010
Length = 0;
1111
Type = Http3FrameType.Data;
1212
}
13+
14+
public string FormattedType => Type switch
15+
{
16+
Http3FrameType.Data => "DATA",
17+
Http3FrameType.Headers => "HEADERS",
18+
Http3FrameType.CancelPush => "CANCEL_PUSH",
19+
Http3FrameType.Settings => "SETTINGS",
20+
Http3FrameType.PushPromise => "PUSH_PROMISE",
21+
Http3FrameType.GoAway => "GO_AWAY",
22+
Http3FrameType.MaxPushId => "MAX_PUSH_ID",
23+
_ => Type.ToString()
24+
};
1325
}
1426
}

src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,6 @@ private ValueTask ProcessHttp3ControlStream(in ReadOnlySequence<byte> payload)
245245
{
246246
case Http3FrameType.Data:
247247
case Http3FrameType.Headers:
248-
case Http3FrameType.DuplicatePush:
249248
case Http3FrameType.PushPromise:
250249
throw new Http3ConnectionException("HTTP_FRAME_UNEXPECTED");
251250
case Http3FrameType.Settings:

src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -446,14 +446,15 @@ private Task ProcessHttp3Stream<TContext>(IHttpApplication<TContext> application
446446
return ProcessDataFrameAsync(payload);
447447
case Http3FrameType.Headers:
448448
return ProcessHeadersFrameAsync(application, payload);
449-
// need to be on control stream
450-
case Http3FrameType.DuplicatePush:
451-
case Http3FrameType.PushPromise:
452449
case Http3FrameType.Settings:
453-
case Http3FrameType.GoAway:
454450
case Http3FrameType.CancelPush:
451+
case Http3FrameType.GoAway:
455452
case Http3FrameType.MaxPushId:
456-
throw new Http3ConnectionException("HTTP_FRAME_UNEXPECTED");
453+
// These frames need to be on a control stream
454+
throw new Http3StreamErrorException(CoreStrings.FormatHttp3ErrorUnsupportedFrameOnRequestStream(_incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame);
455+
case Http3FrameType.PushPromise:
456+
// The server should never receive push promise
457+
throw new Http3StreamErrorException(CoreStrings.FormatHttp3ErrorUnsupportedFrameOnServer(_incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame);
457458
default:
458459
return ProcessUnknownFrameAsync();
459460
}

src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1726,5 +1726,34 @@ public async Task TrailersWithoutEndingStream_ErrorAccessingTrailers()
17261726
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => readTrailersTcs.Task).DefaultTimeout();
17271727
Assert.Equal("The request trailers are not available yet. They may not be available until the full request body is read.", ex.Message);
17281728
}
1729+
1730+
[Theory]
1731+
[InlineData(nameof(Http3FrameType.MaxPushId))]
1732+
[InlineData(nameof(Http3FrameType.Settings))]
1733+
[InlineData(nameof(Http3FrameType.CancelPush))]
1734+
[InlineData(nameof(Http3FrameType.GoAway))]
1735+
public async Task UnexpectedRequestFrame(string frameType)
1736+
{
1737+
var requestStream = await InitializeConnectionAndStreamsAsync(_echoApplication);
1738+
1739+
var frame = new Http3RawFrame();
1740+
frame.Type = Enum.Parse<Http3FrameType>(frameType);
1741+
await requestStream.SendFrameAsync(frame, Memory<byte>.Empty);
1742+
1743+
await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.UnexpectedFrame, expectedErrorMessage: CoreStrings.FormatHttp3ErrorUnsupportedFrameOnRequestStream(frame.FormattedType));
1744+
}
1745+
1746+
[Theory]
1747+
[InlineData(nameof(Http3FrameType.PushPromise))]
1748+
public async Task UnexpectedServerFrame(string frameType)
1749+
{
1750+
var requestStream = await InitializeConnectionAndStreamsAsync(_echoApplication);
1751+
1752+
var frame = new Http3RawFrame();
1753+
frame.Type = Enum.Parse<Http3FrameType>(frameType);
1754+
await requestStream.SendFrameAsync(frame, Memory<byte>.Empty);
1755+
1756+
await requestStream.WaitForStreamErrorAsync(Http3ErrorCode.UnexpectedFrame, expectedErrorMessage: CoreStrings.FormatHttp3ErrorUnsupportedFrameOnServer(frame.FormattedType));
1757+
}
17291758
}
17301759
}

src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -369,24 +369,23 @@ public async Task<bool> SendHeadersAsync(IEnumerable<KeyValuePair<string, string
369369
frame.PrepareHeaders();
370370
var buffer = _headerEncodingBuffer.AsMemory();
371371
var done = _qpackEncoder.BeginEncode(headers, buffer.Span, out var length);
372-
frame.Length = length;
373-
// TODO may want to modify behavior of input frames to mock different client behavior (client can send anything).
374-
Http3FrameWriter.WriteHeader(frame, outputWriter);
375-
await SendAsync(buffer.Span.Slice(0, length));
376372

377-
if (endStream)
378-
{
379-
await _pair.Application.Output.CompleteAsync();
380-
}
373+
// TODO may want to modify behavior of input frames to mock different client behavior (client can send anything).
374+
await SendFrameAsync(frame, buffer.Slice(0, length), endStream);
381375

382376
return done;
383377
}
384378

385379
internal async Task SendDataAsync(Memory<byte> data, bool endStream = false)
386380
{
387-
var outputWriter = _pair.Application.Output;
388381
var frame = new Http3RawFrame();
389382
frame.PrepareData();
383+
await SendFrameAsync(frame, data, endStream);
384+
}
385+
386+
internal async Task SendFrameAsync(Http3RawFrame frame, Memory<byte> data, bool endStream = false)
387+
{
388+
var outputWriter = _pair.Application.Output;
390389
frame.Length = data.Length;
391390
Http3FrameWriter.WriteHeader(frame, outputWriter);
392391
await SendAsync(data.Span);

0 commit comments

Comments
 (0)