Skip to content

Commit 8d8deb7

Browse files
authored
HTTP/3: Header limits and validation (#31928)
1 parent ea2f559 commit 8d8deb7

File tree

8 files changed

+351
-57
lines changed

8 files changed

+351
-57
lines changed

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -390,34 +390,34 @@
390390
<data name="Http2ErrorStreamIdle" xml:space="preserve">
391391
<value>The client sent a {frameType} frame to idle stream ID {streamId}.</value>
392392
</data>
393-
<data name="Http2ErrorTrailersContainPseudoHeaderField" xml:space="preserve">
393+
<data name="HttpErrorTrailersContainPseudoHeaderField" xml:space="preserve">
394394
<value>The client sent trailers containing one or more pseudo-header fields.</value>
395395
</data>
396-
<data name="Http2ErrorHeaderNameUppercase" xml:space="preserve">
396+
<data name="HttpErrorHeaderNameUppercase" xml:space="preserve">
397397
<value>The client sent a header with uppercase characters in its name.</value>
398398
</data>
399-
<data name="Http2ErrorTrailerNameUppercase" xml:space="preserve">
399+
<data name="HttpErrorTrailerNameUppercase" xml:space="preserve">
400400
<value>The client sent a trailer with uppercase characters in its name.</value>
401401
</data>
402402
<data name="Http2ErrorHeadersWithTrailersNoEndStream" xml:space="preserve">
403403
<value>The client sent a HEADERS frame containing trailers without setting the END_STREAM flag.</value>
404404
</data>
405-
<data name="Http2ErrorMissingMandatoryPseudoHeaderFields" xml:space="preserve">
405+
<data name="HttpErrorMissingMandatoryPseudoHeaderFields" xml:space="preserve">
406406
<value>Request headers missing one or more mandatory pseudo-header fields.</value>
407407
</data>
408-
<data name="Http2ErrorPseudoHeaderFieldAfterRegularHeaders" xml:space="preserve">
408+
<data name="HttpErrorPseudoHeaderFieldAfterRegularHeaders" xml:space="preserve">
409409
<value>Pseudo-header field found in request headers after regular header fields.</value>
410410
</data>
411-
<data name="Http2ErrorUnknownPseudoHeaderField" xml:space="preserve">
411+
<data name="HttpErrorUnknownPseudoHeaderField" xml:space="preserve">
412412
<value>Request headers contain unknown pseudo-header field.</value>
413413
</data>
414-
<data name="Http2ErrorResponsePseudoHeaderField" xml:space="preserve">
414+
<data name="HttpErrorResponsePseudoHeaderField" xml:space="preserve">
415415
<value>Request headers contain response-specific pseudo-header field.</value>
416416
</data>
417-
<data name="Http2ErrorDuplicatePseudoHeaderField" xml:space="preserve">
417+
<data name="HttpErrorDuplicatePseudoHeaderField" xml:space="preserve">
418418
<value>Request headers contain duplicate pseudo-header field.</value>
419419
</data>
420-
<data name="Http2ErrorConnectionSpecificHeaderField" xml:space="preserve">
420+
<data name="HttpErrorConnectionSpecificHeaderField" xml:space="preserve">
421421
<value>Request headers contain connection-specific header field.</value>
422422
</data>
423423
<data name="AuthenticationFailed" xml:space="preserve">
@@ -680,4 +680,4 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
680680
<data name="Http3ControlStreamErrorMultipleInboundStreams" xml:space="preserve">
681681
<value>The client created multiple inbound {streamType} streams for the connection.</value>
682682
</data>
683-
</root>
683+
</root>

src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,7 +1026,7 @@ private void StartStream()
10261026
// All HTTP/2 requests MUST include exactly one valid value for the :method, :scheme, and :path pseudo-header
10271027
// fields, unless it is a CONNECT request (Section 8.3). An HTTP request that omits mandatory pseudo-header
10281028
// fields is malformed (Section 8.1.2.6).
1029-
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorMissingMandatoryPseudoHeaderFields, Http2ErrorCode.PROTOCOL_ERROR);
1029+
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.HttpErrorMissingMandatoryPseudoHeaderFields, Http2ErrorCode.PROTOCOL_ERROR);
10301030
}
10311031

10321032
if (_clientActiveStreamCount == _serverSettings.MaxConcurrentStreams)
@@ -1327,7 +1327,7 @@ private void ValidateHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
13271327

13281328
if (IsConnectionSpecificHeaderField(name, value))
13291329
{
1330-
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorConnectionSpecificHeaderField, Http2ErrorCode.PROTOCOL_ERROR);
1330+
throw new Http2ConnectionErrorException(CoreStrings.HttpErrorConnectionSpecificHeaderField, Http2ErrorCode.PROTOCOL_ERROR);
13311331
}
13321332

13331333
// http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2
@@ -1338,11 +1338,11 @@ private void ValidateHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
13381338
{
13391339
if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers)
13401340
{
1341-
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorTrailerNameUppercase, Http2ErrorCode.PROTOCOL_ERROR);
1341+
throw new Http2ConnectionErrorException(CoreStrings.HttpErrorTrailerNameUppercase, Http2ErrorCode.PROTOCOL_ERROR);
13421342
}
13431343
else
13441344
{
1345-
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorHeaderNameUppercase, Http2ErrorCode.PROTOCOL_ERROR);
1345+
throw new Http2ConnectionErrorException(CoreStrings.HttpErrorHeaderNameUppercase, Http2ErrorCode.PROTOCOL_ERROR);
13461346
}
13471347
}
13481348
}
@@ -1398,13 +1398,13 @@ implementations to these vulnerabilities.*/
13981398
// All pseudo-header fields MUST appear in the header block before regular header fields.
13991399
// Any request or response that contains a pseudo-header field that appears in a header
14001400
// block after a regular header field MUST be treated as malformed (Section 8.1.2.6).
1401-
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorPseudoHeaderFieldAfterRegularHeaders, Http2ErrorCode.PROTOCOL_ERROR);
1401+
throw new Http2ConnectionErrorException(CoreStrings.HttpErrorPseudoHeaderFieldAfterRegularHeaders, Http2ErrorCode.PROTOCOL_ERROR);
14021402
}
14031403

14041404
if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers)
14051405
{
14061406
// Pseudo-header fields MUST NOT appear in trailers.
1407-
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorTrailersContainPseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR);
1407+
throw new Http2ConnectionErrorException(CoreStrings.HttpErrorTrailersContainPseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR);
14081408
}
14091409

14101410
_requestHeaderParsingState = RequestHeaderParsingState.PseudoHeaderFields;
@@ -1413,21 +1413,21 @@ implementations to these vulnerabilities.*/
14131413
{
14141414
// Endpoints MUST treat a request or response that contains undefined or invalid pseudo-header
14151415
// fields as malformed (Section 8.1.2.6).
1416-
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorUnknownPseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR);
1416+
throw new Http2ConnectionErrorException(CoreStrings.HttpErrorUnknownPseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR);
14171417
}
14181418

14191419
if (headerField == PseudoHeaderFields.Status)
14201420
{
14211421
// Pseudo-header fields defined for requests MUST NOT appear in responses; pseudo-header fields
14221422
// defined for responses MUST NOT appear in requests.
1423-
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorResponsePseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR);
1423+
throw new Http2ConnectionErrorException(CoreStrings.HttpErrorResponsePseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR);
14241424
}
14251425

14261426
if ((_parsedPseudoHeaderFields & headerField) == headerField)
14271427
{
14281428
// http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.3
14291429
// All HTTP/2 requests MUST include exactly one valid value for the :method, :scheme, and :path pseudo-header fields
1430-
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorDuplicatePseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR);
1430+
throw new Http2ConnectionErrorException(CoreStrings.HttpErrorDuplicatePseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR);
14311431
}
14321432

14331433
if (headerField == PseudoHeaderFields.Method)

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

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttpHeadersHandler,
3232
private static ReadOnlySpan<byte> TrailersBytes => new byte[8] { (byte)'t', (byte)'r', (byte)'a', (byte)'i', (byte)'l', (byte)'e', (byte)'r', (byte)'s' };
3333
private static ReadOnlySpan<byte> ConnectBytes => new byte[7] { (byte)'C', (byte)'O', (byte)'N', (byte)'N', (byte)'E', (byte)'C', (byte)'T' };
3434

35+
private static readonly PseudoHeaderFields _mandatoryRequestPseudoHeaderFields =
36+
PseudoHeaderFields.Method | PseudoHeaderFields.Path | PseudoHeaderFields.Scheme;
37+
3538
private readonly Http3FrameWriter _frameWriter;
3639
private readonly Http3OutputProducer _http3Output;
3740
private int _isClosed;
@@ -42,6 +45,7 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttpHeadersHandler,
4245
private readonly Http3RawFrame _incomingFrame = new Http3RawFrame();
4346
protected RequestHeaderParsingState _requestHeaderParsingState;
4447
private PseudoHeaderFields _parsedPseudoHeaderFields;
48+
private int _totalParsedHeaderSize;
4549
private bool _isMethodConnect;
4650

4751
private readonly Http3Connection _http3Connection;
@@ -148,7 +152,14 @@ public void OnStaticIndexedHeader(int index, ReadOnlySpan<byte> value)
148152

149153
public override void OnHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
150154
{
151-
// TODO MaxRequestHeadersTotalSize?
155+
// https://tools.ietf.org/html/rfc7540#section-6.5.2
156+
// "The value is based on the uncompressed size of header fields, including the length of the name and value in octets plus an overhead of 32 octets for each header field.";
157+
_totalParsedHeaderSize += HeaderField.RfcOverhead + name.Length + value.Length;
158+
if (_totalParsedHeaderSize > _context.ServiceContext.ServerOptions.Limits.MaxRequestHeadersTotalSize)
159+
{
160+
throw new Http3StreamErrorException(CoreStrings.BadRequest_HeadersExceedMaxTotalSize, Http3ErrorCode.RequestRejected);
161+
}
162+
152163
ValidateHeader(name, value);
153164
try
154165
{
@@ -165,23 +176,22 @@ public override void OnHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
165176
}
166177
catch (Microsoft.AspNetCore.Http.BadHttpRequestException bre)
167178
{
168-
throw new Http3StreamErrorException(bre.Message, Http3ErrorCode.ProtocolError);
179+
throw new Http3StreamErrorException(bre.Message, Http3ErrorCode.MessageError);
169180
}
170181
catch (InvalidOperationException)
171182
{
172-
throw new Http3StreamErrorException(CoreStrings.BadRequest_MalformedRequestInvalidHeaders, Http3ErrorCode.ProtocolError);
183+
throw new Http3StreamErrorException(CoreStrings.BadRequest_MalformedRequestInvalidHeaders, Http3ErrorCode.MessageError);
173184
}
174185
}
175186

176187
private void ValidateHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value)
177188
{
178189
// http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.1
179190
/*
180-
Intermediaries that process HTTP requests or responses (i.e., any
181-
intermediary not acting as a tunnel) MUST NOT forward a malformed
182-
request or response. Malformed requests or responses that are
183-
detected MUST be treated as a stream error (Section 5.4.2) of type
184-
PROTOCOL_ERROR.
191+
Intermediaries that process HTTP requests or responses
192+
(i.e., any intermediary not acting as a tunnel) MUST NOT forward a
193+
malformed request or response. Malformed requests or responses that
194+
are detected MUST be treated as a stream error of type H3_MESSAGE_ERROR.
185195
186196
For malformed requests, a server MAY send an HTTP response prior to
187197
closing or resetting the stream. Clients MUST NOT accept a malformed
@@ -193,39 +203,43 @@ implementations to these vulnerabilities.*/
193203
{
194204
if (_requestHeaderParsingState == RequestHeaderParsingState.Headers)
195205
{
206+
// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1.1.1-4
196207
// All pseudo-header fields MUST appear in the header block before regular header fields.
197208
// Any request or response that contains a pseudo-header field that appears in a header
198-
// block after a regular header field MUST be treated as malformed (Section 8.1.2.6).
199-
throw new Http3StreamErrorException(CoreStrings.Http2ErrorPseudoHeaderFieldAfterRegularHeaders, Http3ErrorCode.ProtocolError);
209+
// block after a regular header field MUST be treated as malformed.
210+
throw new Http3StreamErrorException(CoreStrings.HttpErrorPseudoHeaderFieldAfterRegularHeaders, Http3ErrorCode.MessageError);
200211
}
201212

202213
if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers)
203214
{
215+
// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1.1.1-3
204216
// Pseudo-header fields MUST NOT appear in trailers.
205-
throw new Http3StreamErrorException(CoreStrings.Http2ErrorTrailersContainPseudoHeaderField, Http3ErrorCode.ProtocolError);
217+
throw new Http3StreamErrorException(CoreStrings.HttpErrorTrailersContainPseudoHeaderField, Http3ErrorCode.MessageError);
206218
}
207219

208220
_requestHeaderParsingState = RequestHeaderParsingState.PseudoHeaderFields;
209221

210222
if (headerField == PseudoHeaderFields.Unknown)
211223
{
224+
// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1.1.1-3
212225
// Endpoints MUST treat a request or response that contains undefined or invalid pseudo-header
213-
// fields as malformed (Section 8.1.2.6).
214-
throw new Http3StreamErrorException(CoreStrings.Http2ErrorUnknownPseudoHeaderField, Http3ErrorCode.ProtocolError);
226+
// fields as malformed.
227+
throw new Http3StreamErrorException(CoreStrings.HttpErrorUnknownPseudoHeaderField, Http3ErrorCode.MessageError);
215228
}
216229

217230
if (headerField == PseudoHeaderFields.Status)
218231
{
232+
// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1.1.1-3
219233
// Pseudo-header fields defined for requests MUST NOT appear in responses; pseudo-header fields
220234
// defined for responses MUST NOT appear in requests.
221-
throw new Http3StreamErrorException(CoreStrings.Http2ErrorResponsePseudoHeaderField, Http3ErrorCode.ProtocolError);
235+
throw new Http3StreamErrorException(CoreStrings.HttpErrorResponsePseudoHeaderField, Http3ErrorCode.MessageError);
222236
}
223237

224238
if ((_parsedPseudoHeaderFields & headerField) == headerField)
225239
{
226-
// http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.3
227-
// All HTTP/2 requests MUST include exactly one valid value for the :method, :scheme, and :path pseudo-header fields
228-
throw new Http3StreamErrorException(CoreStrings.Http2ErrorDuplicatePseudoHeaderField, Http3ErrorCode.ProtocolError);
240+
// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1.1.1-7
241+
// All HTTP/3 requests MUST include exactly one valid value for the :method, :scheme, and :path pseudo-header fields
242+
throw new Http3StreamErrorException(CoreStrings.HttpErrorDuplicatePseudoHeaderField, Http3ErrorCode.MessageError);
229243
}
230244

231245
if (headerField == PseudoHeaderFields.Method)
@@ -242,22 +256,22 @@ implementations to these vulnerabilities.*/
242256

243257
if (IsConnectionSpecificHeaderField(name, value))
244258
{
245-
throw new Http3StreamErrorException(CoreStrings.Http2ErrorConnectionSpecificHeaderField, Http3ErrorCode.ProtocolError);
259+
throw new Http3StreamErrorException(CoreStrings.HttpErrorConnectionSpecificHeaderField, Http3ErrorCode.MessageError);
246260
}
247261

248-
// http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2
249-
// A request or response containing uppercase header field names MUST be treated as malformed (Section 8.1.2.6).
262+
// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1.1-3
263+
// A request or response containing uppercase header field names MUST be treated as malformed.
250264
for (var i = 0; i < name.Length; i++)
251265
{
252266
if (name[i] >= 65 && name[i] <= 90)
253267
{
254268
if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers)
255269
{
256-
throw new Http3StreamErrorException(CoreStrings.Http2ErrorTrailerNameUppercase, Http3ErrorCode.ProtocolError);
270+
throw new Http3StreamErrorException(CoreStrings.HttpErrorTrailerNameUppercase, Http3ErrorCode.MessageError);
257271
}
258272
else
259273
{
260-
throw new Http3StreamErrorException(CoreStrings.Http2ErrorHeaderNameUppercase, Http3ErrorCode.ProtocolError);
274+
throw new Http3StreamErrorException(CoreStrings.HttpErrorHeaderNameUppercase, Http3ErrorCode.MessageError);
261275
}
262276
}
263277
}
@@ -521,6 +535,15 @@ private async Task ProcessHeadersFrameAsync<TContext>(IHttpApplication<TContext>
521535
await OnEndStreamReceived();
522536
}
523537

538+
if (!_isMethodConnect && (_parsedPseudoHeaderFields & _mandatoryRequestPseudoHeaderFields) != _mandatoryRequestPseudoHeaderFields)
539+
{
540+
// All HTTP/3 requests MUST include exactly one valid value for the :method, :scheme, and :path pseudo-header
541+
// fields, unless it is a CONNECT request. An HTTP request that omits mandatory pseudo-header
542+
// fields is malformed.
543+
// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1.1.1
544+
throw new Http3StreamErrorException(CoreStrings.HttpErrorMissingMandatoryPseudoHeaderFields, Http3ErrorCode.MessageError);
545+
}
546+
524547
_appCompleted = new TaskCompletionSource();
525548

526549
ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false);

0 commit comments

Comments
 (0)