Skip to content

Conversation

@devonh
Copy link
Member

@devonh devonh commented Nov 21, 2025

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 handleContentChunk but 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:

  • multiple Content-Length headers
  • invalid Content-Length header value
  • request content size being larger than the Content-Length value

Pull Request Checklist

  • Pull request is based on the develop branch
  • Pull request includes a changelog file. The entry should:
    • Be a short description of your change which makes sense to users. "Fixed a bug that prevented receiving messages from other servers." instead of "Moved X method from EventStore to EventWorkerStore.".
    • Use markdown where necessary, mostly for code blocks.
    • End with either a period (.) or an exclamation mark (!).
    • Start with a capital letter.
    • Feel free to credit yourself, by adding a sentence "Contributed by @github_username." or "Contributed by [Your Name]." to the end of the entry.
  • Code style is correct (run the linters)

@devonh devonh requested a review from a team as a code owner November 21, 2025 17:14
@MadLittleMods MadLittleMods added A-Federation A-to-device-messages EDU messages sent exactly once to a specific set of devices. Related to E2EE labels Nov 24, 2025
@devonh devonh requested a review from MadLittleMods November 27, 2025 00:06
# we should get a 415
self.assertRegex(transport.value().decode(), r"^HTTP/1\.1 415 ")

def test_content_length_too_large(self) -> None:
Copy link
Contributor

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)

command.decode("ascii", errors="replace"),
self.get_redacted_uri(),
)
self.loseConnection()
Copy link
Contributor

@MadLittleMods MadLittleMods Dec 1, 2025

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

Copy link
Member Author

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.

Copy link
Member Author

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:
Copy link
Contributor

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 🤷

@devonh devonh changed the title Add HTTP 413 response when incoming request is too large Respond with useful error codes when Content-Length header/s are invalid Dec 4, 2025
@devonh devonh requested a review from MadLittleMods December 5, 2025 00:31
self.get_redacted_uri(),
)
error_response_json = {
"errcode": Codes.UNKNOWN,
Copy link
Contributor

@MadLittleMods MadLittleMods Dec 5, 2025

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

Copy link
Member Author

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

Copy link
Contributor

@MadLittleMods MadLittleMods Dec 5, 2025

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.

Copy link
Member Author

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.

return

if content_length is not None and self.content is not None:
if content_length < self.content.tell():
Copy link
Contributor

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()?

Copy link
Member Author

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.

Copy link
Member Author

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.

self.assertEqual(channel.code, 200)
self.assertNotIn("body", channel.result)

def test_content_too_large(self) -> None:
Copy link
Contributor

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?

Copy link
Member Author

Choose a reason for hiding this comment

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

Added.

@devonh devonh requested a review from MadLittleMods December 5, 2025 23:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Federation A-to-device-messages EDU messages sent exactly once to a specific set of devices. Related to E2EE A-Validation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants