Skip to content
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

Fixes #5685 - AsyncProxyServlet calls onProxyResponseSuccess() when internally it throws "Response header too large" exception. #12351

Merged

Conversation

sbordet
Copy link
Contributor

@sbordet sbordet commented Oct 7, 2024

  • Introduced "maxResponseHeadersSize" parameter to AbstractProxyServlet.
  • Introduced HttpGenerator.maxResponseHeadersSize and added checks to not exceed it.
  • Fixed HttpParser to generate a 400 in case HttpParser.maxHeaderBytes are exceeded for a response.
  • HTTP2Flusher now resets streams in case of failures.
  • Removed cases in HTTP2Session where a GOAWAY frame was generated with lastStreamId=0. GOAWAY.lastStreamId=0 means that not a single request was processed by the server, which was obviously incorrect.
  • Written tests for both ProxyHandler and ProxyServlet about max response headers size exceeded.

…el clients.

Fixed initialization for HTTP/2 and HTTP/3.

Signed-off-by: Simone Bordet <[email protected]>
Signed-off-by: Simone Bordet <[email protected]>
…nternally it throws "Response header too large" exception.

* Introduced "maxResponseHeadersSize" parameter to AbstractProxyServlet.
* Introduced HttpGenerator.maxResponseHeadersSize and added checks to not exceed it.
* Fixed HttpParser to generate a 400 in case HttpParser.maxHeaderBytes are exceeded for a response.
* HTTP2Flusher now resets streams in case of failures.
* Removed cases in HTTP2Session where a GOAWAY frame was generated with lastStreamId=0.
  GOAWAY.lastStreamId=0 means that not a single request was processed by the server, which was obviously incorrect.
* Written tests for both ProxyHandler and ProxyServlet about max response headers size exceeded.

Signed-off-by: Simone Bordet <[email protected]>
private boolean _needCRLF = false;
private int _maxHeaderBytes;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if 0 is a sensible default, even if we always call the setter before using the generator.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We did not check for response headers max size before, so 0 keeps the original behavior.

if (_requestParser)
throw new BadMessageException(header ? HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE_431 : HttpStatus.PAYLOAD_TOO_LARGE_413);
// There is no equivalent of 431 for response headers.
throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Response Header Fields Too Large");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a 400 error either as 4xx codes indicate client errors but here it really is a server error. 500 looks like a more correct response code IMHO.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately BadMessageException only allows 4xx error codes.

I'll check if we can throw a different exception.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible to throw a different exception, HttpException.RuntimeException, but this is a client parsing a response: should it really generate a 500 Internal Server Error (emphasis on "Server")?

The server could generate a large response; it's the client that cannot parse it, so it's not technically a server error in HTTP/1.1 where the client cannot inform the server about how much it can parse.

@gregw thoughts?

@@ -776,7 +778,7 @@ public Action process() throws Exception
case HEADER_OVERFLOW:
{
if (_header.capacity() >= getHttpConfiguration().getResponseHeaderSize())
throw new HttpException.RuntimeException(INTERNAL_SERVER_ERROR_500, "Response header too large");
throw new HttpException.RuntimeException(INTERNAL_SERVER_ERROR_500, "Response Header Fields Too Large");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, the status is (IMHO correctly) set to 500 when headers are too large, unlike H2.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what you mean here? H2 does not generate an HTTP response (it just fails the connection).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wrote H2, I meant the parser. Silly me.

.timeout(5, TimeUnit.SECONDS)
.send();

// assertTrue(serverToProxyFailureLatch.await(5, TimeUnit.SECONDS));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete this comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be restored!

Signed-off-by: Simone Bordet <[email protected]>
Signed-off-by: Simone Bordet <[email protected]>
@sbordet sbordet requested a review from lorban October 8, 2024 17:13
Base automatically changed from fix/jetty-12.0.x/12348/httpclienttransportdynamic-config to jetty-12.0.x October 10, 2024 15:03
Copy link
Contributor

@gregw gregw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not allocate a headers buffer of the appropriate size instead of adding a new counter?

Comment on lines +100 to +108
public int getMaxHeaderBytes()
{
return _maxHeaderBytes;
}

public void setMaxHeaderBytes(int maxHeaderBytes)
{
_maxHeaderBytes = maxHeaderBytes;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kind of a duplicate in the server as we already have HttpConfiguration.getResponseHeaderSize(), and we have use overflowing of that buffer to enforce a header size.

So why are we duplicating this mechanism? Can't the client just allocate a buffer of a specific size when it is asked for a HEADERs buffer?

Copy link
Contributor

@lorban lorban left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides wiring HttpConfiguration.getResponseHeaderSize() like Greg suggested, LGTM.

@sbordet
Copy link
Contributor Author

sbordet commented Oct 29, 2024

Why not allocate a headers buffer of the appropriate size instead of adding a new counter?

Because we cannot rely on buffer capacity to enforce max header size.
When a buffer is acquired from the pool, its capacity is rounded up to the next bucket.

So If I configure maxHeaderBytes=1200, I'm actually able to stuff up to 2048 bytes, which breaks HTTP/2 and HTTP/3 clients. This is because it is the client telling the server how much the client can receive via a SETTINGS frame.

So we need a mechanism that is independent of the buffer capacity.

@sbordet sbordet requested review from lorban and gregw October 29, 2024 09:04
@gregw
Copy link
Contributor

gregw commented Oct 29, 2024

So we need a mechanism that is independent of the buffer capacity.

I agree that h2/h3 need greater accuracy than can be achieved with a buffer from a pool. I also see you have wired up the HttpConfiguration to the generator, so I think that is OK.... although if the buffer size is exact, it will probably overflow before the limit check is applied.

@sbordet sbordet merged commit 85ce152 into jetty-12.0.x Oct 30, 2024
2 of 4 checks passed
@sbordet sbordet deleted the fix/jetty-12.0.x/5685/proxy-response-headers-too-large branch October 30, 2024 17:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: ✅ Done
3 participants