Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

event.freeze() causes subtle downstream bugs that makes type comparisons brittle #18117

Open
MadLittleMods opened this issue Jan 30, 2025 · 1 comment

Comments

@MadLittleMods
Copy link
Contributor

MadLittleMods commented Jan 30, 2025

Spawning from reviewing #18066 by @blackmad which has the original investigation on this,

Normally, when usingEventBase, you would expect to be able to do naive comparisons like this isinstance(event.content.get("foo"), list) or isinstance(event.content.get("foo"), dict). But if event.freeze() was run beforehand, we actually need to take into account (list, tuple) and (dict, immutabledict).

What makes this even more subtle is that event.freeze() is only run when Synapse modules are configured.

It would be nice to for the type system, linting or anything to be able to catch this mistake. Or refactor things so that event.freeze() doesn't have such big downstream effects (perhaps we just make a copy that the Synapse module can do whatever it wants with).

Related issues:

Here is a list of places in the codebase where this matters (compiled from searching isinstance\(.+list and isinstance\(.+dict and manually inspecting if they're interacting with EventBase):

Places we already take this into account ✅

It looks like we already take this into account in a few places:

isinstance\(.+list:

isinstance\(.+dict):

  • None

Places we still need to update ❌

But there are also other spots we also need to update:

isinstance\(.+list:

isinstance\(.+dict:

@MadLittleMods
Copy link
Contributor Author

I've just had a thought on how we could solve all of the cases in #18117 pretty elegantly without having to muck about in the downstream code,

Instead of calling unfreeze at the spot we're trying to use it here, we could unfreeze after we're done with the frozen event in the third party module callbacks (where we called freeze in the first place).

(unfreeze after we run all of the callbacks)

# Ensure that the event is frozen, to make sure that the module is not tempted
# to try to modify it. Any attempt to modify it at this point will invalidate
# the hashes and signatures.
event.freeze()
for callback in self._check_event_allowed_callbacks:
try:
res, replacement_data = await delay_cancellation(
callback(event, state_events)
)
except CancelledError:
raise
except SynapseError as e:
# FIXME: Being able to throw SynapseErrors is relied upon by
# some modules. PR https://github.com/matrix-org/synapse/pull/10386
# accidentally broke this ability.
# That said, we aren't keen on exposing this implementation detail
# to modules and we should one day have a proper way to do what
# is wanted.
# This module callback needs a rework so that hacks such as
# this one are not necessary.
raise e
except Exception:
raise ModuleFailedException(
"Failed to run `check_event_allowed` module API callback"
)

-- @MadLittleMods, #18103 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant