Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit 70ce9ae

Browse files
authored
Strip unauthorized fields from unsigned object in events received over federation (#11530)
* add some tests to verify we are stripping unauthorized fields out of unsigned * add function to strip unauthorized fields from the unsigned object of event * newsfragment * update newsfragment number * add check to on_send_membership_event * refactor tests * fix lint error * slightly refactor tests and add some comments * slight refactor * refactor tests * fix import error * slight refactor * remove unsigned filtration code from synapse/handlers/federation_event.py * lint * move unsigned filtering code to event base * refactor tests * update newsfragment * requested changes * remove unused retun values
1 parent 2ef1fea commit 70ce9ae

File tree

3 files changed

+99
-0
lines changed

3 files changed

+99
-0
lines changed

changelog.d/11530.bugfix

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a long-standing issue which could cause Synapse to incorrectly accept data in the unsigned field of events
2+
received over federation.

synapse/federation/federation_base.py

+25
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,10 @@ def event_from_pdu_json(pdu_json: JsonDict, room_version: RoomVersion) -> EventB
230230
# origin, etc etc)
231231
assert_params_in_dict(pdu_json, ("type", "depth"))
232232

233+
# Strip any unauthorized values from "unsigned" if they exist
234+
if "unsigned" in pdu_json:
235+
_strip_unsigned_values(pdu_json)
236+
233237
depth = pdu_json["depth"]
234238
if not isinstance(depth, int):
235239
raise SynapseError(400, "Depth %r not an intger" % (depth,), Codes.BAD_JSON)
@@ -245,3 +249,24 @@ def event_from_pdu_json(pdu_json: JsonDict, room_version: RoomVersion) -> EventB
245249

246250
event = make_event_from_dict(pdu_json, room_version)
247251
return event
252+
253+
254+
def _strip_unsigned_values(pdu_dict: JsonDict) -> None:
255+
"""
256+
Strip any unsigned values unless specifically allowed, as defined by the whitelist.
257+
258+
pdu: the json dict to strip values from. Note that the dict is mutated by this
259+
function
260+
"""
261+
unsigned = pdu_dict["unsigned"]
262+
263+
if not isinstance(unsigned, dict):
264+
pdu_dict["unsigned"] = {}
265+
266+
if pdu_dict["type"] == "m.room.member":
267+
whitelist = ["knock_room_state", "invite_room_state", "age"]
268+
else:
269+
whitelist = ["age"]
270+
271+
filtered_unsigned = {k: v for k, v in unsigned.items() if k in whitelist}
272+
pdu_dict["unsigned"] = filtered_unsigned

tests/test_federation.py

+72
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
from twisted.internet.defer import succeed
1818

1919
from synapse.api.errors import FederationError
20+
from synapse.api.room_versions import RoomVersions
2021
from synapse.events import make_event_from_dict
22+
from synapse.federation.federation_base import event_from_pdu_json
2123
from synapse.logging.context import LoggingContext
2224
from synapse.types import UserID, create_requester
2325
from synapse.util import Clock
@@ -276,3 +278,73 @@ def test_cross_signing_keys_retry(self):
276278
"ed25519:" + remote_self_signing_key in self_signing_key["keys"].keys(),
277279
)
278280
self.assertTrue(remote_self_signing_key in self_signing_key["keys"].values())
281+
282+
283+
class StripUnsignedFromEventsTestCase(unittest.TestCase):
284+
def test_strip_unauthorized_unsigned_values(self):
285+
event1 = {
286+
"sender": "@baduser:test.serv",
287+
"state_key": "@baduser:test.serv",
288+
"event_id": "$event1:test.serv",
289+
"depth": 1000,
290+
"origin_server_ts": 1,
291+
"type": "m.room.member",
292+
"origin": "test.servx",
293+
"content": {"membership": "join"},
294+
"auth_events": [],
295+
"unsigned": {"malicious garbage": "hackz", "more warez": "more hackz"},
296+
}
297+
filtered_event = event_from_pdu_json(event1, RoomVersions.V1)
298+
# Make sure unauthorized fields are stripped from unsigned
299+
self.assertNotIn("more warez", filtered_event.unsigned)
300+
301+
def test_strip_event_maintains_allowed_fields(self):
302+
event2 = {
303+
"sender": "@baduser:test.serv",
304+
"state_key": "@baduser:test.serv",
305+
"event_id": "$event2:test.serv",
306+
"depth": 1000,
307+
"origin_server_ts": 1,
308+
"type": "m.room.member",
309+
"origin": "test.servx",
310+
"auth_events": [],
311+
"content": {"membership": "join"},
312+
"unsigned": {
313+
"malicious garbage": "hackz",
314+
"more warez": "more hackz",
315+
"age": 14,
316+
"invite_room_state": [],
317+
},
318+
}
319+
320+
filtered_event2 = event_from_pdu_json(event2, RoomVersions.V1)
321+
self.assertIn("age", filtered_event2.unsigned)
322+
self.assertEqual(14, filtered_event2.unsigned["age"])
323+
self.assertNotIn("more warez", filtered_event2.unsigned)
324+
# Invite_room_state is allowed in events of type m.room.member
325+
self.assertIn("invite_room_state", filtered_event2.unsigned)
326+
self.assertEqual([], filtered_event2.unsigned["invite_room_state"])
327+
328+
def test_strip_event_removes_fields_based_on_event_type(self):
329+
event3 = {
330+
"sender": "@baduser:test.serv",
331+
"state_key": "@baduser:test.serv",
332+
"event_id": "$event3:test.serv",
333+
"depth": 1000,
334+
"origin_server_ts": 1,
335+
"type": "m.room.power_levels",
336+
"origin": "test.servx",
337+
"content": {},
338+
"auth_events": [],
339+
"unsigned": {
340+
"malicious garbage": "hackz",
341+
"more warez": "more hackz",
342+
"age": 14,
343+
"invite_room_state": [],
344+
},
345+
}
346+
filtered_event3 = event_from_pdu_json(event3, RoomVersions.V1)
347+
self.assertIn("age", filtered_event3.unsigned)
348+
# Invite_room_state field is only permitted in event type m.room.member
349+
self.assertNotIn("invite_room_state", filtered_event3.unsigned)
350+
self.assertNotIn("more warez", filtered_event3.unsigned)

0 commit comments

Comments
 (0)