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

Change display names/avatar URLs to None if they contain null bytes before storing in DB #11230

Merged
merged 7 commits into from
Nov 12, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelog.d/11230.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix a long-standing bug wherein display names or avatar URLs containing null bytes cause an internal server error
when stored in the DB.
10 changes: 6 additions & 4 deletions synapse/storage/databases/main/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -1641,8 +1641,8 @@ def _store_event_reference_hashes_txn(self, txn, events):
def _store_room_members_txn(self, txn, events, backfilled):
"""Store a room member in the database."""

def str_or_none(val: Any) -> Optional[str]:
return val if isinstance(val, str) else None
def non_null_str_or_none(val: Any) -> Optional[str]:
return val if isinstance(val, str) and "\u0000" not in val else None

self.db_pool.simple_insert_many_txn(
txn,
Expand All @@ -1654,8 +1654,10 @@ def str_or_none(val: Any) -> Optional[str]:
"sender": event.user_id,
"room_id": event.room_id,
"membership": event.membership,
"display_name": str_or_none(event.content.get("displayname")),
"avatar_url": str_or_none(event.content.get("avatar_url")),
"display_name": non_null_str_or_none(
event.content.get("displayname")
),
"avatar_url": non_null_str_or_none(event.content.get("avatar_url")),
}
for event in events
],
Expand Down
48 changes: 48 additions & 0 deletions tests/storage/test_roommember.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,54 @@ def test_get_joined_users_from_context(self):
)
self.assertEqual(users.keys(), {self.u_alice, self.u_bob})

def test__null_byte_in_display_name_properly_handled(self):
room = self.helper.create_room_as(self.u_alice, tok=self.t_alice)

res = self.get_success(
self.store.db_pool.simple_select_list(
"room_memberships",
{"user_id": "@alice:test"},
["display_name", "event_id"],
)
)
# Check that we only got one result back
self.assertEqual(len(res), 1)

# Check that alice's display name is "alice"
self.assertEqual(res[0]["display_name"], "alice")

# Grab the event_id to use later
event_id = res[0]["event_id"]

# Create a profile with the offending null byte in the display name
new_profile = {"displayname": "ali\u0000ce"}

# Ensure that the change goes smoothly and does not fail due to the null byte
self.helper.change_membership(
room,
self.u_alice,
self.u_alice,
"join",
extra_data=new_profile,
tok=self.t_alice,
)

res2 = self.get_success(
self.store.db_pool.simple_select_list(
"room_memberships",
{"user_id": "@alice:test"},
["display_name", "event_id"],
)
)
# Check that we only have two results
self.assertEqual(len(res2), 2)

# Filter out the previous event using the event_id we grabbed above
row = [row for row in res2 if row["event_id"] != event_id]

# Check that alice's display name is now None
self.assertEqual(row[0]["display_name"], None)


class CurrentStateMembershipUpdateTestCase(unittest.HomeserverTestCase):
def prepare(self, reactor, clock, homeserver):
Expand Down