Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package matrix-synapse for openSUSE:Factory checked in at 2025-10-07 18:29:41 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/matrix-synapse (Old) and /work/SRC/openSUSE:Factory/.matrix-synapse.new.11973 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "matrix-synapse" Tue Oct 7 18:29:41 2025 rev:142 rq:1309635 version:1.139.1 Changes: -------- --- /work/SRC/openSUSE:Factory/matrix-synapse/matrix-synapse.changes 2025-10-01 18:59:30.260805384 +0200 +++ /work/SRC/openSUSE:Factory/.matrix-synapse.new.11973/matrix-synapse.changes 2025-10-07 18:32:04.962871439 +0200 @@ -1,0 +2,15 @@ +Tue Oct 7 13:01:53 UTC 2025 - Marcus Rueckert <[email protected]> + +- Update to 1.139.1 (boo#1251231) + - Security Fixes + - Fix CVE-2025-61672 / GHSA-fh66-fcv5-jjfr. Lack of validation + for device keys in Synapse before 1.139.1 allows an attacker + registered on the victim homeserver to degrade federation + functionality, unpredictably breaking outbound federation to + other homeservers. (#17097) + - Deprecations and Removals + - Drop support for unstable field names from the long-accepted + MSC2732 (Olm fallback keys) proposal. This change allows unit + tests to pass following the security patch above. (#18996) + +------------------------------------------------------------------- Old: ---- matrix-synapse-1.139.0.obscpio New: ---- matrix-synapse-1.139.1.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ matrix-synapse-test.spec ++++++ --- /var/tmp/diff_new_pack.GWsTua/_old 2025-10-07 18:32:06.274926722 +0200 +++ /var/tmp/diff_new_pack.GWsTua/_new 2025-10-07 18:32:06.274926722 +0200 @@ -27,7 +27,7 @@ %define pkgname matrix-synapse Name: %{pkgname}-test -Version: 1.139.0 +Version: 1.139.1 Release: 0 Summary: Test package for %{pkgname} License: AGPL-3.0-or-later ++++++ matrix-synapse.spec ++++++ --- /var/tmp/diff_new_pack.GWsTua/_old 2025-10-07 18:32:06.318928577 +0200 +++ /var/tmp/diff_new_pack.GWsTua/_new 2025-10-07 18:32:06.318928577 +0200 @@ -158,7 +158,7 @@ %define pkgname matrix-synapse %define eggname matrix_synapse Name: %{pkgname} -Version: 1.139.0 +Version: 1.139.1 Release: 0 Summary: Matrix protocol reference homeserver License: AGPL-3.0-or-later ++++++ _service ++++++ --- /var/tmp/diff_new_pack.GWsTua/_old 2025-10-07 18:32:06.390931610 +0200 +++ /var/tmp/diff_new_pack.GWsTua/_new 2025-10-07 18:32:06.394931779 +0200 @@ -4,7 +4,7 @@ <param name="versionformat">@PARENT_TAG@</param> <param name="url">https://github.com/element-hq/synapse.git</param> <param name="scm">git</param> - <param name="revision">v1.139.0</param> + <param name="revision">v1.139.1</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="versionrewrite-replacement">\1</param> <!-- ++++++ matrix-synapse-1.139.0.obscpio -> matrix-synapse-1.139.1.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/matrix-synapse-1.139.0/CHANGES.md new/matrix-synapse-1.139.1/CHANGES.md --- old/matrix-synapse-1.139.0/CHANGES.md 2025-09-30 12:58:59.000000000 +0200 +++ new/matrix-synapse-1.139.1/CHANGES.md 2025-10-07 12:58:08.000000000 +0200 @@ -1,5 +1,27 @@ +# Synapse 1.139.1 (2025-10-07) + +## Security Fixes + +- Fix [CVE-2025-61672](https://www.cve.org/CVERecord?id=CVE-2025-61672) / [GHSA-fh66-fcv5-jjfr](https://github.com/element-hq/synapse/security/advisories/GHSA-fh66-fcv5-jjfr). Lack of validation for device keys in Synapse before 1.139.1 allows an attacker registered on the victim homeserver to degrade federation functionality, unpredictably breaking outbound federation to other homeservers. ([\#17097](https://github.com/element-hq/synapse/issues/17097)) + +## Deprecations and Removals + +- Drop support for unstable field names from the long-accepted [MSC2732](https://github.com/matrix-org/matrix-spec-proposals/pull/2732) (Olm fallback keys) proposal. This change allows unit tests to pass following the security patch above. ([\#18996](https://github.com/element-hq/synapse/issues/18996)) + + + # Synapse 1.139.0 (2025-09-30) +### `/register` requests from old application service implementations may break when using MAS + +If you are using Matrix Authentication Service (MAS), as of this release any +Application Services that do not set `inhibit_login=true` when calling `POST +/_matrix/client/v3/register` will receive the error +`IO.ELEMENT.MSC4190.M_APPSERVICE_LOGIN_UNSUPPORTED` in response. Please see [the +upgrade +notes](https://element-hq.github.io/synapse/develop/upgrade.html#register-requests-from-old-application-service-implementations-may-break-when-using-mas) +for more information. + No significant changes since 1.139.0rc3. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/matrix-synapse-1.139.0/debian/changelog new/matrix-synapse-1.139.1/debian/changelog --- old/matrix-synapse-1.139.0/debian/changelog 2025-09-30 12:58:59.000000000 +0200 +++ new/matrix-synapse-1.139.1/debian/changelog 2025-10-07 12:58:08.000000000 +0200 @@ -1,3 +1,9 @@ +matrix-synapse-py3 (1.139.1) stable; urgency=medium + + * New Synapse release 1.139.1. + + -- Synapse Packaging team <[email protected]> Tue, 07 Oct 2025 11:46:51 +0100 + matrix-synapse-py3 (1.139.0) stable; urgency=medium * New Synapse release 1.139.0. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/matrix-synapse-1.139.0/pyproject.toml new/matrix-synapse-1.139.1/pyproject.toml --- old/matrix-synapse-1.139.0/pyproject.toml 2025-09-30 12:58:59.000000000 +0200 +++ new/matrix-synapse-1.139.1/pyproject.toml 2025-10-07 12:58:08.000000000 +0200 @@ -101,7 +101,7 @@ [tool.poetry] name = "matrix-synapse" -version = "1.139.0" +version = "1.139.1" description = "Homeserver for the Matrix decentralised comms protocol" authors = ["Matrix.org Team and Contributors <[email protected]>"] license = "AGPL-3.0-or-later" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/matrix-synapse-1.139.0/synapse/handlers/e2e_keys.py new/matrix-synapse-1.139.1/synapse/handlers/e2e_keys.py --- old/matrix-synapse-1.139.0/synapse/handlers/e2e_keys.py 2025-09-30 12:58:59.000000000 +0200 +++ new/matrix-synapse-1.139.1/synapse/handlers/e2e_keys.py 2025-10-07 12:58:08.000000000 +0200 @@ -57,7 +57,6 @@ logger = logging.getLogger(__name__) - ONE_TIME_KEY_UPLOAD = "one_time_key_upload_lock" @@ -848,14 +847,22 @@ """ time_now = self.clock.time_msec() - # TODO: Validate the JSON to make sure it has the right keys. device_keys = keys.get("device_keys", None) if device_keys: + log_kv( + { + "message": "Updating device_keys for user.", + "user_id": user_id, + "device_id": device_id, + } + ) await self.upload_device_keys_for_user( user_id=user_id, device_id=device_id, keys={"device_keys": device_keys}, ) + else: + log_kv({"message": "Did not update device_keys", "reason": "not a dict"}) one_time_keys = keys.get("one_time_keys", None) if one_time_keys: @@ -873,10 +880,9 @@ log_kv( {"message": "Did not update one_time_keys", "reason": "no keys given"} ) - fallback_keys = keys.get("fallback_keys") or keys.get( - "org.matrix.msc2732.fallback_keys" - ) - if fallback_keys and isinstance(fallback_keys, dict): + + fallback_keys = keys.get("fallback_keys") + if fallback_keys: log_kv( { "message": "Updating fallback_keys for device.", @@ -885,8 +891,6 @@ } ) await self.store.set_e2e_fallback_keys(user_id, device_id, fallback_keys) - elif fallback_keys: - log_kv({"message": "Did not update fallback_keys", "reason": "not a dict"}) else: log_kv( {"message": "Did not update fallback_keys", "reason": "no keys given"} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/matrix-synapse-1.139.0/synapse/rest/client/keys.py new/matrix-synapse-1.139.1/synapse/rest/client/keys.py --- old/matrix-synapse-1.139.0/synapse/rest/client/keys.py 2025-09-30 12:58:59.000000000 +0200 +++ new/matrix-synapse-1.139.1/synapse/rest/client/keys.py 2025-10-07 12:58:08.000000000 +0200 @@ -23,10 +23,19 @@ import logging import re from collections import Counter -from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple +from http import HTTPStatus +from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Tuple, Union +from typing_extensions import Self + +from synapse._pydantic_compat import ( + StrictBool, + StrictStr, + validator, +) from synapse.api.auth.mas import MasDelegatedAuth from synapse.api.errors import ( + Codes, InteractiveAuthIncompleteError, InvalidAPICallError, SynapseError, @@ -37,11 +46,13 @@ parse_integer, parse_json_object_from_request, parse_string, + validate_json_object, ) from synapse.http.site import SynapseRequest from synapse.logging.opentracing import log_kv, set_tag from synapse.rest.client._base import client_patterns, interactive_auth_handler from synapse.types import JsonDict, StreamToken +from synapse.types.rest import RequestBodyModel from synapse.util.cancellation import cancellable if TYPE_CHECKING: @@ -59,7 +70,6 @@ "device_keys": { "user_id": "<user_id>", "device_id": "<device_id>", - "valid_until_ts": <millisecond_timestamp>, "algorithms": [ "m.olm.curve25519-aes-sha2", ] @@ -111,12 +121,123 @@ self._clock = hs.get_clock() self._store = hs.get_datastores().main + class KeyUploadRequestBody(RequestBodyModel): + """ + The body of a `POST /_matrix/client/v3/keys/upload` request. + + Based on https://spec.matrix.org/v1.16/client-server-api/#post_matrixclientv3keysupload. + """ + + class DeviceKeys(RequestBodyModel): + algorithms: List[StrictStr] + """The encryption algorithms supported by this device.""" + + device_id: StrictStr + """The ID of the device these keys belong to. Must match the device ID used when logging in.""" + + keys: Mapping[StrictStr, StrictStr] + """ + Public identity keys. The names of the properties should be in the + format `<algorithm>:<device_id>`. The keys themselves should be encoded as + specified by the key algorithm. + """ + + signatures: Mapping[StrictStr, Mapping[StrictStr, StrictStr]] + """Signatures for the device key object. A map from user ID, to a map from "<algorithm>:<device_id>" to the signature.""" + + user_id: StrictStr + """The ID of the user the device belongs to. Must match the user ID used when logging in.""" + + class KeyObject(RequestBodyModel): + key: StrictStr + """The key, encoded using unpadded base64.""" + + fallback: Optional[StrictBool] = False + """Whether this is a fallback key. Only used when handling fallback keys.""" + + signatures: Mapping[StrictStr, Mapping[StrictStr, StrictStr]] + """Signature for the device. Mapped from user ID to another map of key signing identifier to the signature itself. + + See the following for more detail: https://spec.matrix.org/v1.16/appendices/#signing-details + """ + + device_keys: Optional[DeviceKeys] = None + """Identity keys for the device. May be absent if no new identity keys are required.""" + + fallback_keys: Optional[Mapping[StrictStr, Union[StrictStr, KeyObject]]] + """ + The public key which should be used if the device's one-time keys are + exhausted. The fallback key is not deleted once used, but should be + replaced when additional one-time keys are being uploaded. The server + will notify the client of the fallback key being used through `/sync`. + + There can only be at most one key per algorithm uploaded, and the server + will only persist one key per algorithm. + + When uploading a signed key, an additional fallback: true key should be + included to denote that the key is a fallback key. + + May be absent if a new fallback key is not required. + """ + + @validator("fallback_keys", pre=True) + def validate_fallback_keys(cls: Self, v: Any) -> Any: + if v is None: + return v + if not isinstance(v, dict): + raise TypeError("fallback_keys must be a mapping") + + for k in v.keys(): + if not len(k.split(":")) == 2: + raise SynapseError( + code=HTTPStatus.BAD_REQUEST, + errcode=Codes.BAD_JSON, + msg=f"Invalid fallback_keys key {k!r}. " + 'Expected "<algorithm>:<device_id>".', + ) + return v + + one_time_keys: Optional[Mapping[StrictStr, Union[StrictStr, KeyObject]]] = None + """ + One-time public keys for "pre-key" messages. The names of the properties + should be in the format `<algorithm>:<key_id>`. + + The format of the key is determined by the key algorithm, see: + https://spec.matrix.org/v1.16/client-server-api/#key-algorithms. + """ + + @validator("one_time_keys", pre=True) + def validate_one_time_keys(cls: Self, v: Any) -> Any: + if v is None: + return v + if not isinstance(v, dict): + raise TypeError("one_time_keys must be a mapping") + + for k, _ in v.items(): + if not len(k.split(":")) == 2: + raise SynapseError( + code=HTTPStatus.BAD_REQUEST, + errcode=Codes.BAD_JSON, + msg=f"Invalid one_time_keys key {k!r}. " + 'Expected "<algorithm>:<device_id>".', + ) + return v + async def on_POST( self, request: SynapseRequest, device_id: Optional[str] ) -> Tuple[int, JsonDict]: requester = await self.auth.get_user_by_req(request, allow_guest=True) user_id = requester.user.to_string() + + # Parse the request body. Validate separately, as the handler expects a + # plain dict, rather than any parsed object. + # + # Note: It would be nice to work with a parsed object, but the handler + # needs to encode portions of the request body as canonical JSON before + # storing the result in the DB. There's little point in converted to a + # parsed object and then back to a dict. body = parse_json_object_from_request(request) + validate_json_object(body, self.KeyUploadRequestBody) if device_id is not None: # Providing the device_id should only be done for setting keys @@ -149,8 +270,31 @@ 400, "To upload keys, you must pass device_id when authenticating" ) + if "device_keys" in body: + # Validate the provided `user_id` and `device_id` fields in + # `device_keys` match that of the requesting user. We can't do + # this directly in the pydantic model as we don't have access + # to the requester yet. + # + # TODO: We could use ValidationInfo when we switch to Pydantic v2. + # https://docs.pydantic.dev/latest/concepts/validators/#validation-info + if body["device_keys"]["user_id"] != user_id: + raise SynapseError( + code=HTTPStatus.BAD_REQUEST, + errcode=Codes.BAD_JSON, + msg="Provided `user_id` in `device_keys` does not match that of the authenticated user", + ) + if body["device_keys"]["device_id"] != device_id: + raise SynapseError( + code=HTTPStatus.BAD_REQUEST, + errcode=Codes.BAD_JSON, + msg="Provided `device_id` in `device_keys` does not match that of the authenticated user device", + ) + result = await self.e2e_keys_handler.upload_keys_for_user( - user_id=user_id, device_id=device_id, keys=body + user_id=user_id, + device_id=device_id, + keys=body, ) return 200, result diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/matrix-synapse-1.139.0/synapse/rest/client/sync.py new/matrix-synapse-1.139.1/synapse/rest/client/sync.py --- old/matrix-synapse-1.139.0/synapse/rest/client/sync.py 2025-09-30 12:58:59.000000000 +0200 +++ new/matrix-synapse-1.139.1/synapse/rest/client/sync.py 2025-10-07 12:58:08.000000000 +0200 @@ -363,9 +363,6 @@ # https://github.com/matrix-org/matrix-doc/blob/54255851f642f84a4f1aaf7bc063eebe3d76752b/proposals/2732-olm-fallback-keys.md # states that this field should always be included, as long as the server supports the feature. - response["org.matrix.msc2732.device_unused_fallback_key_types"] = ( - sync_result.device_unused_fallback_key_types - ) response["device_unused_fallback_key_types"] = ( sync_result.device_unused_fallback_key_types ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/matrix-synapse-1.139.0/tests/handlers/test_e2e_keys.py new/matrix-synapse-1.139.1/tests/handlers/test_e2e_keys.py --- old/matrix-synapse-1.139.0/tests/handlers/test_e2e_keys.py 2025-09-30 12:58:59.000000000 +0200 +++ new/matrix-synapse-1.139.1/tests/handlers/test_e2e_keys.py 2025-10-07 12:58:08.000000000 +0200 @@ -410,7 +410,6 @@ device_id = "xyz" fallback_key = {"alg1:k1": "fallback_key1"} fallback_key2 = {"alg1:k2": "fallback_key2"} - fallback_key3 = {"alg1:k2": "fallback_key3"} otk = {"alg1:k2": "key2"} # we shouldn't have any unused fallback keys yet @@ -531,28 +530,6 @@ {"failures": {}, "one_time_keys": {local_user: {device_id: fallback_key2}}}, ) - # using the unstable prefix should also set the fallback key - self.get_success( - self.handler.upload_keys_for_user( - local_user, - device_id, - {"org.matrix.msc2732.fallback_keys": fallback_key3}, - ) - ) - - claim_res = self.get_success( - self.handler.claim_one_time_keys( - {local_user: {device_id: {"alg1": 1}}}, - self.requester, - timeout=None, - always_include_fallback_keys=False, - ) - ) - self.assertEqual( - claim_res, - {"failures": {}, "one_time_keys": {local_user: {device_id: fallback_key3}}}, - ) - def test_fallback_key_bulk(self) -> None: """Like test_fallback_key, but claims multiple keys in one handler call.""" alice = f"@alice:{self.hs.hostname}" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/matrix-synapse-1.139.0/tests/rest/client/test_keys.py new/matrix-synapse-1.139.1/tests/rest/client/test_keys.py --- old/matrix-synapse-1.139.0/tests/rest/client/test_keys.py 2025-09-30 12:58:59.000000000 +0200 +++ new/matrix-synapse-1.139.1/tests/rest/client/test_keys.py 2025-10-07 12:58:08.000000000 +0200 @@ -40,6 +40,127 @@ from tests.utils import HAS_AUTHLIB +class KeyUploadTestCase(unittest.HomeserverTestCase): + servlets = [ + keys.register_servlets, + admin.register_servlets_for_client_rest_resource, + login.register_servlets, + ] + + def test_upload_keys_fails_on_invalid_structure(self) -> None: + """Check that we validate the structure of keys upon upload. + + Regression test for https://github.com/element-hq/synapse/pull/17097 + """ + self.register_user("alice", "wonderland") + alice_token = self.login("alice", "wonderland") + + channel = self.make_request( + "POST", + "/_matrix/client/v3/keys/upload", + { + # Error: device_keys must be a dict + "device_keys": ["some", "stuff", "weewoo"] + }, + alice_token, + ) + self.assertEqual(channel.code, HTTPStatus.BAD_REQUEST, channel.result) + self.assertEqual( + channel.json_body["errcode"], + Codes.BAD_JSON, + channel.result, + ) + + channel = self.make_request( + "POST", + "/_matrix/client/v3/keys/upload", + { + # Error: properties of fallback_keys must be in the form `<algorithm>:<device_id>` + "fallback_keys": {"invalid_key": "signature_base64"} + }, + alice_token, + ) + self.assertEqual(channel.code, HTTPStatus.BAD_REQUEST, channel.result) + self.assertEqual( + channel.json_body["errcode"], + Codes.BAD_JSON, + channel.result, + ) + + channel = self.make_request( + "POST", + "/_matrix/client/v3/keys/upload", + { + # Same as above, but for one_time_keys + "one_time_keys": {"invalid_key": "signature_base64"} + }, + alice_token, + ) + self.assertEqual(channel.code, HTTPStatus.BAD_REQUEST, channel.result) + self.assertEqual( + channel.json_body["errcode"], + Codes.BAD_JSON, + channel.result, + ) + + def test_upload_keys_fails_on_invalid_user_id_or_device_id(self) -> None: + """ + Validate that the requesting user is uploading their own keys and nobody + else's. + """ + device_id = "DEVICE_ID" + alice_user_id = self.register_user("alice", "wonderland") + alice_token = self.login("alice", "wonderland", device_id=device_id) + + channel = self.make_request( + "POST", + "/_matrix/client/v3/keys/upload", + { + "device_keys": { + # Included `user_id` does not match requesting user. + "user_id": "@unknown_user:test", + "device_id": device_id, + "algorithms": ["m.olm.curve25519-aes-sha2"], + "keys": { + f"ed25519:{device_id}": "publickey", + }, + "signatures": {}, + } + }, + alice_token, + ) + self.assertEqual(channel.code, HTTPStatus.BAD_REQUEST, channel.result) + self.assertEqual( + channel.json_body["errcode"], + Codes.BAD_JSON, + channel.result, + ) + + channel = self.make_request( + "POST", + "/_matrix/client/v3/keys/upload", + { + "device_keys": { + "user_id": alice_user_id, + # Included `device_id` does not match requesting user's. + "device_id": "UNKNOWN_DEVICE_ID", + "algorithms": ["m.olm.curve25519-aes-sha2"], + "keys": { + f"ed25519:{device_id}": "publickey", + }, + "signatures": {}, + } + }, + alice_token, + ) + self.assertEqual(channel.code, HTTPStatus.BAD_REQUEST, channel.result) + self.assertEqual( + channel.json_body["errcode"], + Codes.BAD_JSON, + channel.result, + ) + + class KeyQueryTestCase(unittest.HomeserverTestCase): servlets = [ keys.register_servlets, ++++++ matrix-synapse.obsinfo ++++++ --- /var/tmp/diff_new_pack.GWsTua/_old 2025-10-07 18:32:08.931038639 +0200 +++ /var/tmp/diff_new_pack.GWsTua/_new 2025-10-07 18:32:08.935038807 +0200 @@ -1,5 +1,5 @@ name: matrix-synapse -version: 1.139.0 -mtime: 1759229939 -commit: 72020f3f2c1890e6b262001bcdd6f642b729b9a9 +version: 1.139.1 +mtime: 1759834688 +commit: 76b012c3f5a1d51294dabcabde31e5dce94dddf8 ++++++ vendor.tar.zst ++++++ /work/SRC/openSUSE:Factory/matrix-synapse/vendor.tar.zst /work/SRC/openSUSE:Factory/.matrix-synapse.new.11973/vendor.tar.zst differ: char 7, line 1
