-
Notifications
You must be signed in to change notification settings - Fork 5.1k
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
Refuse adding invalid HTTP 2.0 headers #277
base: main
Are you sure you want to change the base?
Conversation
Can you show your sample curl input/output? |
@michael-o curl example:
Safari will display the following error in the javascript console:
|
Of course, you will need to actually add the faulty header to the response to see this error. |
I'm curious why applications think they need to set the connection header. I'd expect the container to handle this. Further, applications that want to set this header can/should use If applied (and I'm not convinced it should be) the formatting of the PR needs fixing to be consistent with the Tomcat code. I'd also consider using UserDataHelper for the logging even though this isn't strictly a user data issue. |
-1 |
I fail to see this issue: Pure:
with explicit headers:
Running Tomcat 9.0.34 and
Even with
Please ugrade your curl first! |
Since it's now 2020, shouldn't it be doable to block any attempt to set the connection header by the application ? |
That would also allow some clean up in the current code that sets the header and has to take account of any value that may have been set by the application. |
@markt-asf This happens for instance in SSE components (tomcat does not provide such components), or more generally in any J2EE filters or servlets, coded before HTTP/2.0, which want to ensure that the connection is kept alive. @rmaucher It means that you choose tomcat to let buggy server-side components break HTTP/2.0 streams. Other user agents do always ignore this faulty header, so what's the point in sending it in the first place? Helping to find bad client code by breaking more often? Of course, it's a strategy, but considering the vast amount of HTTP/1.1-specific code in the nature that is supposed to migrate to HTTP/2.0, it's better to just log an informative warning and get things straight. Or directly throw. @michael-o I'm using a recent curl. There is a misunderstanding, here, I'm talking about a header sent by the server in the HTTP response.
@rmaucher That would mean to throw the exception right away, it's enough to remove the try catch in this patch. Yes, certainly simpler.
@markt-asf Can you give me some pointers on the needed fixes? |
Why can't I reproduce it although I have an h2c response?! |
We are talking about this code block: tomcat/java/org/apache/coyote/http11/Http11Processor.java Lines 932 to 956 in 65bc61a
|
I see. But at that point, you do not know who set up the header, so the stack trace won't be informative. |
Can you retry with a vanilla Tomcat and the most recent curl? |
I've spent enough time digging for this bug (to end up finding it in a SSE dependency), including step by step debugging in 9.0.33 sources (then checking it is still present with 9.0.34) and in the specs and the browser sources to not really need to retry anything to persuade myself of the pertinenece of this patch. |
@arkanovicz More generally... It would be worth reviewing the HTTP/2 spec to check if there are any other headers that are invalid for HTTP/2. The global blocking off applications setting Connection headers seems reasonable at first consideration but needs more thought/review in case there are use cases where it is arguably valid / necessary to do so. |
java/org/apache/coyote/Response.java
Outdated
/** | ||
* Helper to log the invalid HTTP/2.0 header error only once per instance | ||
*/ | ||
private static AtomicBoolean invalidHeaderWarningEmitted = new AtomicBoolean(false); |
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.
per instance
of what ? Because you use static
here, so it is on class loader level.
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.
Ok, noted.
To my knowledge, only the Connection headers.
At best, if the faulty header doesn't provoke an error (required by the specs), it will be ignored. Here's what the spec says:
|
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.
The exception message does not correspond to the behavior. The header isn't ignored, but an exception is signaled. Isn't that a contradiction?
I will maintain my -1 |
The initial post says SHOULD, but after actually checking the spec it is a MUST. It is really odd the specification made such a choice given how many applications toy with that header in an attempt to do something useful, and given they have no idea if they're using HTTP/1.1 or HTTP/2. So this looks to me like a spec mistake [for compatibility, they should have said it has to be ignored and can be freely removed, whatever], and it is not very surprising most HTTP/2 clients would not check this requirement. |
@rmaucher The spec authors justify themselves of this strict policy by saying that otherwise it's a vector of attack. I don't know the whereabouts, here. About where to put the filtering, in Response.addHeader() or StreamProcessor.prepareHeaders(), I understand your point of view (which is, I suppose, to gather all protocol stuff together) but targeting StreamProcessor doesn't respect the Fail Fast paradigm, which prevails IMHO. |
Still, -1, again for your patch. In addition to being ugly, there's no provision in the Servlet spec to throw an exception on random header names, especially common ones, so failing, fast or slow, is wrong. |
I've been reading the HTTP/2 RFC and there is more to this than simply blocking the
We need to figure out what we actually want to do first. I'm currently leaning towards introducing logging of attempts to set connection level headers with a warning that a future version will block the attempt. Probably with |
Yes, it is accurate if there's a "connection: foobar" header, then there could be a "foobar" header and in that case it's tied to the connection header. Note about my earlier "proposal" for HTTP/1.1, the connection header is used by the websocket.server.UpgradeUtil helper class to allow upgrade through the API. So it's not possible to filter it and be done, this would have to be fixed as well [not sure how]. Servlet apps can upgrade through the proper API and would not be affected. |
Please note that there may be client components which may explicitly close connections with |
Indeed, there are two very legacy looking workarounds for brokeness in spnego. One of them can be removed since using Java 8u40 is not reasonable anymore. What about that connection close one ? IMO it's useless since there is no default, so an admin would have to figure out whatever broken clients are out there, then populate it. Not reasonable. If the feature is useful, then it should have a default with all the known broken clients. |
@rmaucher The question is when is it justified for component or webapp code to close the connection? Should a container solely decide to close the connection? |
In an ideal world, anything in the Connection header should only be the concern of the container. Historically that hasn't been possible due to broken clients. The question is, are we at the point where it would be possible? Logging a warning when an application attempts this is one way to help us find out. Users will complain about the log message. Whether they are able to fix the app and/or client in question will tell us how safe it would be for us to move to a ban. |
@@ -435,6 +435,20 @@ private boolean checkSpecialHeader( String name, String value) { | |||
return false; | |||
} | |||
} | |||
|
|||
if (outputBuffer instanceof Http2OutputBuffer && name.equalsIgnoreCase("Connection") ) { |
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.
if (outputBuffer instanceof Http2OutputBuffer && "Connection".equalsIgnoreCase(name) ) {
Connection headers like
Connection: keep-alive
are invalid in HTTP/2, and some clients (like Safari or curl) are very touchy about it.When an application component adds the typical HTTP/1.x "Connection: keep-alive" header to the response, despite the component's good intention, the header is faulty in HTTP/2.0 and SHOULD always be filtered.
The current implementation emits a warning in the logs only once per instance.