element-hq / synapse

Synapse: Matrix homeserver written in Python/Twisted.
https://element-hq.github.io/synapse
GNU Affero General Public License v3.0
1.48k stars 182 forks source link

/_matrix/federation/v1/media/thumbnail/ responds with wrong Content-Length #17518

Closed Xiretza closed 2 months ago

Xiretza commented 2 months ago

Description

When requesting a thumbnail via the new Authenticated Media federation endpoint /_matrix/federation/v1/media/thumbnail/{mediaId}, synapse v1.111.0 sends a Content-Length header that's larger than the actual response body. This makes the request fail on the client with e.g. "unexpected EOF"/"connection reset".

Steps to reproduce

Redacted curl command line:

$ curl -v -o /tmp/response \
      -H 'Authorization: X-Matrix destination=xiretza.xyz,key="ed25519:HgIdvBXv",origin=blep.space,sig="[REDACTED request signature]"' \
      'https://matrix.xiretza.xyz:8448/_matrix/federation/v1/media/thumbnail/[REDACTED media ID]?method=crop&width=14&height=14&animated=false'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0* Host matrix.xiretza.xyz:8448 was resolved.
* IPv6: 2a01:4f8:c0c:8e8a::1
* IPv4: 116.203.191.189
*   Trying 116.203.191.189:8448...
* Connected to matrix.xiretza.xyz (116.203.191.189) port 8448
* ALPN: curl offers h2,http/1.1
} [5 bytes data]
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
} [512 bytes data]
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: none
{ [5 bytes data]
* TLSv1.3 (IN), TLS handshake, Server hello (2):
{ [122 bytes data]
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
{ [25 bytes data]
* TLSv1.3 (IN), TLS handshake, Certificate (11):
{ [2037 bytes data]
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
{ [79 bytes data]
* TLSv1.3 (IN), TLS handshake, Finished (20):
{ [52 bytes data]
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
} [1 bytes data]
* TLSv1.3 (OUT), TLS handshake, Finished (20):
} [52 bytes data]
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / x25519 / id-ecPublicKey
* ALPN: server accepted http/1.1
* Server certificate:
*  subject: CN=matrix.xiretza.xyz
*  start date: Jul 26 04:58:30 2024 GMT
*  expire date: Oct 24 04:58:29 2024 GMT
*  subjectAltName: host "matrix.xiretza.xyz" matched cert's "matrix.xiretza.xyz"
*  issuer: C=US; O=Let's Encrypt; CN=E5
*  SSL certificate verify ok.
*   Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA384
*   Certificate level 1: Public key type EC/secp384r1 (384/192 Bits/secBits), signed using sha256WithRSAEncryption
*   Certificate level 2: Public key type RSA (4096/152 Bits/secBits), signed using sha256WithRSAEncryption
* using HTTP/1.x
} [5 bytes data]
> GET /_matrix/federation/v1/media/thumbnail/[REDACTED media ID]?method=crop&width=14&height=14&animated=false HTTP/1.1
> Host: matrix.xiretza.xyz:8448
> User-Agent: curl/8.9.1
> Accept: */*
> Authorization: X-Matrix destination=xiretza.xyz,key="ed25519:HgIdvBXv",origin=blep.space,sig="[REDACTED request signature]"
>
* Request completely sent off
{ [5 bytes data]
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
{ [57 bytes data]
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
{ [57 bytes data]
< HTTP/1.1 200 OK
< Server: nginx/1.26.1
< Date: Sun, 04 Aug 2024 18:25:18 GMT
< Content-Type: multipart/mixed; boundary=2b22fe34bba54787b55d5234ba335893
< Content-Length: 3961
< Connection: keep-alive
< Strict-Transport-Security: max-age=31536000; includeSubDomains
< X-Frame-Options: SAMEORIGIN
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Content-Security-Policy: frame-ancestors 'none'
<
{ [1189 bytes data]
* TLSv1.3 (IN), TLS alert, close notify (256):
{ [2 bytes data]
* end of response with 2772 bytes missing
 30  3961   30  1189    0     0  16270      0 --:--:-- --:--:-- --:--:-- 16287
* closing connection #0
curl: (18) end of response with 2772 bytes missing

The response in /tmp/response is a complete and correct response containing two parts, the second of which is the correct thumbnail. It is 1189 bytes in size in total.

As can be seen, the Content-Length header is set to 3961, which is way longer than the actual response.

The log (see below) also shows the correct length of 1189.

Homeserver

xiretza.xyz

Synapse Version

v1.111.0

Installation Method

Other (please mention below)

Database

postgresql

Workers

Single process

Platform

Arch Linux, x86_64

Configuration

Nothing special

Relevant log output

Aug 04 18:24:52 matrix synapse[488]: synapse.access.http.8008: [GET-64230] [REDACTED client IP] - 8008 - {blep.space} Processed request: 0.063sec/-0.000sec (0.002sec, 0.000sec) (0.002sec/0.014sec/2) 1189B 200 "GET /_matrix/federation/v1/media/thumbnail/[REDACTED media ID]?method=crop&width=14&height=14&animated=false HTTP/1.0" "curl/8.9.1" [0 dbevts]

Anything else that would be useful to know?

No response

H-Shay commented 2 months ago

the request fail on the client

When you say client, what are you referring to? Are you seeing this between synapse <-> synapse or synapse <-> another homeserver implementation?

The response in /tmp/response is a complete and correct response containing two parts, the second of which is the correct thumbnail. It is 1189 bytes in size in total.

Just to clarify, when you say a complete and correct response, you mean the response contains the json object + field, ie:

Content-Type: multipart/mixed; boundary=gc0p4Jq0M2Yt08jU534c0p

--gc0p4Jq0M2Yt08jU534c0p
Content-Type: application/json

{}

plus the second field which is the thumbnail bytes, and both of these fields add up to 1189 bytes?

Xiretza commented 2 months ago

When you say client, what are you referring to? Are you seeing this between synapse <-> synapse or synapse <-> another homeserver implementation?

Another federating homeserver, in this case grapevine, where I'm trying to implement Authenticated Media support. Synapse would likely have the same problem as a client, but it doesn't request /_matrix/federation/v1/media/thumbnail/ over federation and instead always fetches the complete media and does the thumbnailing itself; this is probably why the issue hasn't been spotted so far.

Just to clarify, when you say a complete and correct response, you mean the response contains the json object + field, ie:

Content-Type: multipart/mixed; boundary=gc0p4Jq0M2Yt08jU534c0p

--gc0p4Jq0M2Yt08jU534c0p
Content-Type: application/json

{}

plus the second field which is the thumbnail bytes, and both of these fields add up to 1189 bytes?

That's correct.

H-Shay commented 2 months ago

Thanks for reporting this - I think what happened is that the content-length was being calculated based off of the length of the original media file, rather than the thumbnail media file. Hopefully this is fixed in #17532, and since Synapse doesn't make requests to this endpoint (which, as you correctly noted, is why this was missed so far) I added a complement test to ensure the basic functionality of the endpoint.