-
-
Notifications
You must be signed in to change notification settings - Fork 87
http2/stream: fix trailers validation #237
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Signed-off-by: phantomas <[email protected]>
problem: h2_stream validate_header reject valid response trailers, while accepting invalid ones. cause: validate_header checks that all headers contains the ':status' pseudo-header, which is disallowed by the RFC. fix: add an 'expect_trailers' parameter to validate_header. This value is set to true after a final (i.e. non 1XX) header has validated. When this flag is set, pseudo-headers are disallowed, and ended_stream must be set on the next header validation. Signed-off-by: phantomas <[email protected]>
6d333e1 to
e879651
Compare
|
@daurnimator this should address your comment in #236 |
| end | ||
| if stream.type == "client" and not stream.recv_final_header then | ||
| local status = headers:get(":status") | ||
| stream.recv_final_header = string.match(status, "1%d%d") == nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is the correct way to check this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I disagree, but let me explain why I've implemented the fix like this (quote are from RFC 9113 8.1, unless otherwise specified). If you don´t agree after that, I´ll go with the fix in the next change request.
The standard describe a message as:
An HTTP message (request or response) consists of:
- one HEADERS frame (followed by zero or more CONTINUATION frames) containing the header section (see Section 6.3 of [HTTP]),
- zero or more DATA frames containing the message content (see Section 6.4 of [HTTP]), and
- optionally, one HEADERS frame (followed by zero or more CONTINUATION frames) containing the trailer section, if present (see Section 6.5 of [HTTP]).
Since 2. is optional, we can't rely on the presence of data to know the next header frame will be a trailer (the message could be bunch of interim response, followed by a single final header, followed by a trailer).
We can't rely on the number of headers either because of interim responses.
An interim response consists of a HEADERS frame (which might be followed by zero or more CONTINUATION frames) containing the control data and header section of an interim (1xx) HTTP response
From RFC 9110:
A 1xx response is terminated by the end of the header section; it cannot contain content or trailers.
This means that regardless of the amount of interim responses, there will be at most one trailer, which will be held in a header field with the END_STREAM flag set.
For a response only, a server MAY send any number of interim responses before the HEADERS frame containing a final response.
[...]
An endpoint that receives a HEADERS frame without the END_STREAM flag set after receiving the HEADERS frame that opens a request or after receiving a final (non-informational) status code MUST treat the corresponding request or response as malformed (Section 8.1.1).
Since only interim responses are allowed before the "final" response, once we've seen the final response (i.e. a non 1xx status code), we know that the next header frame (if any) must be a trailer.
From all of this, my understanding is that only trailers can appear after a non-informational status code was receive. Since all responses (interim or final) must include a ":status" pseudo header, and everything else is optional, this code is the only thing that can reliably identify the next HEADER as a trailer on a response.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An endpoint that receives a HEADERS frame without the END_STREAM flag set after receiving the HEADERS frame that opens a request or after receiving a final (non-informational) status code MUST treat the corresponding request or response as malformed (Section 8.1.1).
Ah, that's a better rule to quote+live by!
So not string.find(status, "^1%d%d$") is indeed a reasonable check.
However that's only a stream level error.
vs the nth_header == 1 change I suggested should still be a protocol error I think? Or at least I vaguely remember clients/a test suite expecting it to be a protocol error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However that's only a stream level error.
vs the nth_header == 1 change I suggested should still be a protocol error I think?
I'm not sure I follow. This just mark the stream so we know we're in the last message, but doesn't trigger any error by itself. The mark is then forwarded to validate_headers as its expect_trailer parameter.
A protocol error is warranted (and returned) we then see a header:
- with a status pseudo-header (disallowed in the context of trailers)
Lines 395 to 397 in e879651
if (is_request and nth_header ~= 1) or valid_pseudo_headers[name] ~= is_request or expect_trailer then return nil, h2_errors.PROTOCOL_ERROR:new_traceback("Pseudo-header fields are only valid in the context in which they are defined", true), ce.EILSEQ end - without the END_STREAM flag (since trailer MUST end the stream)
Lines 455 to 457 in e879651
if not ended_stream then return nil, h2_errors.PROTOCOL_ERROR:new_traceback("Trailers MUST be at end of stream", true), ce.EILSEQ end
| return nil, h2_errors.PROTOCOL_ERROR:new_traceback("An HTTP request consists of maximum 2 HEADER blocks", true), ce.EILSEQ | ||
| end | ||
| else | ||
| elseif not expect_trailer then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a better valid fix to this whole issue might be just changing this line to:
| elseif not expect_trailer then | |
| elseif nth_header == 1 then |
and fixing up the comment to say that not all header blocks (such as trailers) will have a status field
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doing this will let invalid header go through if at least one interim response was received. If anything, the status check should only be removed when ended_stream is true, as this will limit the amount of false positive.
problem: h2_stream validate_header reject valid trailers
cause: for response headers, validate_header ignores the
nth_headerparameter, and checked the pseudo-header:status, even for response trailers.fix: only check for
:statuson the first header.