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

Fix inconsistencies in event validation #13088

Merged
merged 4 commits into from
Jun 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/13088.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix some inconsistencies in the event authentication code.
23 changes: 21 additions & 2 deletions synapse/event_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,15 @@ async def check_state_independent_auth_rules(
# 1.5 Otherwise, allow
return

# Check the auth events.
# 2. Reject if event has auth_events that: ...
auth_events = await store.get_events(
event.auth_event_ids(),
redact_behaviour=EventRedactBehaviour.as_is,
allow_rejected=True,
)
room_id = event.room_id
auth_dict: MutableStateMap[str] = {}
expected_auth_types = auth_types_for_event(event.room_version, event)
for auth_event_id in event.auth_event_ids():
auth_event = auth_events.get(auth_event_id)

Expand All @@ -179,6 +180,24 @@ async def check_state_independent_auth_rules(
% (event.event_id, room_id, auth_event_id, auth_event.room_id),
)

k = (auth_event.type, auth_event.state_key)

# 2.1 ... have duplicate entries for a given type and state_key pair
if k in auth_dict:
raise AuthError(
403,
f"Event {event.event_id} has duplicate auth_events for {k}: {auth_dict[k]} and {auth_event_id}",
)

# 2.2 ... have entries whose type and state_key don’t match those specified by
# the auth events selection algorithm described in the server
# specification.
if k not in expected_auth_types:
raise AuthError(
403,
f"Event {event.event_id} has unexpected auth_event for {k}: {auth_event_id}",
)

# We also need to check that the auth event itself is not rejected.
if auth_event.rejected_reason:
raise AuthError(
Expand All @@ -187,7 +206,7 @@ async def check_state_independent_auth_rules(
% (event.event_id, auth_event.event_id),
)

auth_dict[(auth_event.type, auth_event.state_key)] = auth_event_id
auth_dict[k] = auth_event_id

# 3. If event does not have a m.room.create in its auth_events, reject.
creation_event = auth_dict.get((EventTypes.Create, ""), None)
Expand Down
14 changes: 10 additions & 4 deletions tests/handlers/test_federation.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,10 @@ def test_backfill_with_many_backward_extremities(self) -> None:

# we need a user on the remote server to be a member, so that we can send
# extremity-causing events.
remote_server_user_id = f"@user:{self.OTHER_SERVER_NAME}"
self.get_success(
event_injection.inject_member_event(
self.hs, room_id, f"@user:{self.OTHER_SERVER_NAME}", "join"
self.hs, room_id, remote_server_user_id, "join"
)
)

Expand All @@ -247,6 +248,12 @@ def test_backfill_with_many_backward_extremities(self) -> None:
# create more than is 5 which corresponds to the number of backward
# extremities we slice off in `_maybe_backfill_inner`
federation_event_handler = self.hs.get_federation_event_handler()
auth_events = [
ev
for ev in current_state
if (ev.type, ev.state_key)
in {("m.room.create", ""), ("m.room.member", remote_server_user_id)}
]
for _ in range(0, 8):
event = make_event_from_dict(
self.add_hashes_and_signatures(
Expand All @@ -258,15 +265,14 @@ def test_backfill_with_many_backward_extremities(self) -> None:
"body": "message connected to fake event",
},
"room_id": room_id,
"sender": f"@user:{self.OTHER_SERVER_NAME}",
"sender": remote_server_user_id,
"prev_events": [
ev1.event_id,
# We're creating an backward extremity each time thanks
# to this fake event
generate_fake_event_id(),
],
# lazy: *everything* is an auth event
"auth_events": [ev.event_id for ev in current_state],
"auth_events": [ev.event_id for ev in auth_events],
"depth": ev1.depth + 1,
},
room_version,
Expand Down
1 change: 0 additions & 1 deletion tests/handlers/test_federation_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ def _test_process_pulled_event_with_missing_state(
auth_event_ids = [
initial_state_map[("m.room.create", "")],
initial_state_map[("m.room.power_levels", "")],
initial_state_map[("m.room.join_rules", "")],
member_event.event_id,
]

Expand Down
86 changes: 86 additions & 0 deletions tests/test_event_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,92 @@ def test_create_event_with_prev_events(self):
event_auth.check_state_independent_auth_rules(event_store, bad_event)
)

def test_duplicate_auth_events(self):
"""Events with duplicate auth_events should be rejected

https://spec.matrix.org/v1.3/rooms/v9/#authorization-rules
2. Reject if event has auth_events that:
1. have duplicate entries for a given type and state_key pair
"""
creator = "@creator:example.com"

create_event = _create_event(RoomVersions.V9, creator)
join_event1 = _join_event(RoomVersions.V9, creator)
pl_event = _power_levels_event(
RoomVersions.V9,
creator,
{"state_default": 30, "users": {"creator": 100}},
)

# create a second join event, so that we can make a duplicate
join_event2 = _join_event(RoomVersions.V9, creator)

event_store = _StubEventSourceStore()
event_store.add_events([create_event, join_event1, join_event2, pl_event])

good_event = _random_state_event(
RoomVersions.V9, creator, [create_event, join_event2, pl_event]
)
bad_event = _random_state_event(
RoomVersions.V9, creator, [create_event, join_event1, join_event2, pl_event]
)
# a variation: two instances of the *same* event
bad_event2 = _random_state_event(
RoomVersions.V9, creator, [create_event, join_event2, join_event2, pl_event]
)

get_awaitable_result(
event_auth.check_state_independent_auth_rules(event_store, good_event)
)
with self.assertRaises(AuthError):
get_awaitable_result(
event_auth.check_state_independent_auth_rules(event_store, bad_event)
)
with self.assertRaises(AuthError):
get_awaitable_result(
event_auth.check_state_independent_auth_rules(event_store, bad_event2)
)

def test_unexpected_auth_events(self):
"""Events with excess auth_events should be rejected

https://spec.matrix.org/v1.3/rooms/v9/#authorization-rules
2. Reject if event has auth_events that:
2. have entries whose type and state_key don’t match those specified by the
auth events selection algorithm described in the server specification.
"""
creator = "@creator:example.com"

create_event = _create_event(RoomVersions.V9, creator)
join_event = _join_event(RoomVersions.V9, creator)
pl_event = _power_levels_event(
RoomVersions.V9,
creator,
{"state_default": 30, "users": {"creator": 100}},
)
join_rules_event = _join_rules_event(RoomVersions.V9, creator, "public")

event_store = _StubEventSourceStore()
event_store.add_events([create_event, join_event, pl_event, join_rules_event])

good_event = _random_state_event(
RoomVersions.V9, creator, [create_event, join_event, pl_event]
)
# join rules should *not* be included in the auth events.
bad_event = _random_state_event(
RoomVersions.V9,
creator,
[create_event, join_event, pl_event, join_rules_event],
)

get_awaitable_result(
event_auth.check_state_independent_auth_rules(event_store, good_event)
)
with self.assertRaises(AuthError):
get_awaitable_result(
event_auth.check_state_independent_auth_rules(event_store, bad_event)
)

def test_random_users_cannot_send_state_before_first_pl(self):
"""
Check that, before the first PL lands, the creator is the only user
Expand Down