-
Notifications
You must be signed in to change notification settings - Fork 424
Respond with useful error codes when Content-Length header/s are invalid
#19212
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: develop
Are you sure you want to change the base?
Conversation
| # we should get a 415 | ||
| self.assertRegex(transport.value().decode(), r"^HTTP/1\.1 415 ") | ||
|
|
||
| def test_content_length_too_large(self) -> None: |
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.
(test hasn't been added yet)
synapse/http/site.py
Outdated
| command.decode("ascii", errors="replace"), | ||
| self.get_redacted_uri(), | ||
| ) | ||
| self.loseConnection() |
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 it would be nice to respond with an actual HTTP status code and a good error explaining why.
In the case of multiple Content-Length headers -> 400 Bad Request
In the case of no Content-Length header -> 411 Length Required
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.
Yeah, in the case of multiple headers we can do that.
In the case of no header, we can't jump to that conclusion so easily. It depends on the HTTP version. Required in HTTP 1, not required if there is a Transfer-Encoding: chunked header in the HTTP 1.1, and not required in HTTP 2.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Length
We could bake in all these additional checks if you think it doesn't blow this PR up. I'm not sure if twisted already handles these cases or not.
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 have added functionality for ensuring the Content-Length header is valid when present.
I don't think it is worth adding in further validation to enforce HTTP spec compliance. Doing so adds complexity without addressing any specific issues, and becomes a maintenance burden trying to account for all the HTTP variations and future spec changes.
Also, not all HTTP clients strictly follow the HTTP spec/s so adding in strict spec compliance checks could break existing deployments for no real reason.
| # we should get a 415 | ||
| self.assertRegex(transport.value().decode(), r"^HTTP/1\.1 415 ") | ||
|
|
||
| def test_content_length_too_large(self) -> None: |
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 did a bunch of testing and Synapse (Twisted) handles this by truncating the HTTP request at the specified size of the
Content-Length. So the request will go through to the server correctly but the body will be silently truncated. The rest of the bytes are dropped.
Perhaps some request with a basic JSON body that will be cut-off and we expect M_NOT_JSON 🤷
Content-Length header/s are invalid
synapse/http/site.py
Outdated
| self.get_redacted_uri(), | ||
| ) | ||
| error_response_json = { | ||
| "errcode": Codes.UNKNOWN, |
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.
Optional: We could have our own errcode for the known scenarios.
IO.ELEMENT.INVALID_CONTENT_LENGTH, etc
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 using M_UNKNOWN here is more in the spirit of the spec https://spec.matrix.org/v1.16/client-server-api/#standard-error-response
We are trying to provide an HTTP error here since this is an HTTP issue, not a matrix endpoint issue. Assigning it a custom matrix errcode carries with it the implication that this is a matrix error.
From the spec:
When encountering the error code M_UNKNOWN, clients should prefer the HTTP status code as a more reliable reference for what the issue was.
if the client were to receive an error code of M_UNKNOWN with a 400 Bad Request, the client should assume that the request being made was invalid
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 that works. But I also think we could do better ⏩
By the same logic, M_TOO_LARGE would be inappropriate here then. Bottom-line is that we just need better error codes in the spec and that could start with our own namespaced versions which can be parsed by downstream code.
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.
M_TOO_LARGE happens due to an internal Synapse limit based on the size of transactions from the matrix spec (or config setting). So it's easy to argue that it's a matrix based error here, not a generic HTTP error.
synapse/http/site.py
Outdated
| return | ||
|
|
||
| if content_length is not None and self.content is not None: | ||
| if content_length < self.content.tell(): |
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.
What about the opposite case: content_length > self.content.tell()?
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.
It wasn't clear to me from the spec that this is always an error.
But we can treat it that way and be spec compliant. So I have added it for completeness.
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 also refactored things now that we have so many comparisons to reduce duplication.
tests/test_server.py
Outdated
| self.assertEqual(channel.code, 200) | ||
| self.assertNotIn("body", channel.result) | ||
|
|
||
| def test_content_too_large(self) -> None: |
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.
Test for the opposite case?
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.
Added.
Co-authored-by: Eric Eastwood <[email protected]>
Co-authored-by: Eric Eastwood <[email protected]>
Related to #17035, when Synapse receives a request that is larger than the maximum size allowed, it aborts the connection without ever sending back a HTTP response.
I dug into our usage of twisted and how best to try and report such an error and this is what I came up with.
It would be ideal to be able to report the status from within
handleContentChunkbut that is called too early on in the twisted http handling code, before things have been setup enough to be able to properly write a response.I tested this change out locally (both with C-S and S-S apis) and they do receive a 413 response now in addition to the connection being closed.
Hopefully this will aid in being able to quickly detect when #17035 is occurring as the current situation makes it very hard to narrow things down to that specific issue without making a lot of assumptions.
This PR also responds with more meaningful error codes now in the case of:
Content-LengthheadersContent-Lengthheader valueContent-LengthvaluePull Request Checklist
EventStoretoEventWorkerStore.".code blocks.