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

Commit 0bcae8a

Browse files
authored
Change display names/avatar URLs to None if they contain null bytes before storing in DB (#11230)
* change display names/avatar URLS to None if they contain null bytes * add changelog * add POC test, requested changes * add a saner test and remove old one * update test to verify that display name has been changed to None * make test less fragile
1 parent 9b90b94 commit 0bcae8a

File tree

3 files changed

+56
-4
lines changed

3 files changed

+56
-4
lines changed

changelog.d/11230.bugfix

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a long-standing bug wherein display names or avatar URLs containing null bytes cause an internal server error
2+
when stored in the DB.

synapse/storage/databases/main/events.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -1641,8 +1641,8 @@ def _store_event_reference_hashes_txn(self, txn, events):
16411641
def _store_room_members_txn(self, txn, events, backfilled):
16421642
"""Store a room member in the database."""
16431643

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

16471647
self.db_pool.simple_insert_many_txn(
16481648
txn,
@@ -1654,8 +1654,10 @@ def str_or_none(val: Any) -> Optional[str]:
16541654
"sender": event.user_id,
16551655
"room_id": event.room_id,
16561656
"membership": event.membership,
1657-
"display_name": str_or_none(event.content.get("displayname")),
1658-
"avatar_url": str_or_none(event.content.get("avatar_url")),
1657+
"display_name": non_null_str_or_none(
1658+
event.content.get("displayname")
1659+
),
1660+
"avatar_url": non_null_str_or_none(event.content.get("avatar_url")),
16591661
}
16601662
for event in events
16611663
],

tests/storage/test_roommember.py

+48
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,54 @@ def test_get_joined_users_from_context(self):
161161
)
162162
self.assertEqual(users.keys(), {self.u_alice, self.u_bob})
163163

164+
def test__null_byte_in_display_name_properly_handled(self):
165+
room = self.helper.create_room_as(self.u_alice, tok=self.t_alice)
166+
167+
res = self.get_success(
168+
self.store.db_pool.simple_select_list(
169+
"room_memberships",
170+
{"user_id": "@alice:test"},
171+
["display_name", "event_id"],
172+
)
173+
)
174+
# Check that we only got one result back
175+
self.assertEqual(len(res), 1)
176+
177+
# Check that alice's display name is "alice"
178+
self.assertEqual(res[0]["display_name"], "alice")
179+
180+
# Grab the event_id to use later
181+
event_id = res[0]["event_id"]
182+
183+
# Create a profile with the offending null byte in the display name
184+
new_profile = {"displayname": "ali\u0000ce"}
185+
186+
# Ensure that the change goes smoothly and does not fail due to the null byte
187+
self.helper.change_membership(
188+
room,
189+
self.u_alice,
190+
self.u_alice,
191+
"join",
192+
extra_data=new_profile,
193+
tok=self.t_alice,
194+
)
195+
196+
res2 = self.get_success(
197+
self.store.db_pool.simple_select_list(
198+
"room_memberships",
199+
{"user_id": "@alice:test"},
200+
["display_name", "event_id"],
201+
)
202+
)
203+
# Check that we only have two results
204+
self.assertEqual(len(res2), 2)
205+
206+
# Filter out the previous event using the event_id we grabbed above
207+
row = [row for row in res2 if row["event_id"] != event_id]
208+
209+
# Check that alice's display name is now None
210+
self.assertEqual(row[0]["display_name"], None)
211+
164212

165213
class CurrentStateMembershipUpdateTestCase(unittest.HomeserverTestCase):
166214
def prepare(self, reactor, clock, homeserver):

0 commit comments

Comments
 (0)