Repository: matrix-org/synapse

Issue:
synapse checks signature on `join_authorised_via_users_server` in `m.room.membership` events for room versions which do not specify it
As of room v8 and later, an extra auth rule is added which says that the event must be signed by the server given in `join_authorised_via_users_server`.  However, synapse also applies that to check to old room versions, and to membership events other than joins. Since spec-compliant implementations will *not* perform the check in those instances, this could lead to spilt-brained rooms.


Discussion:
cc @clokep

Synapse also does the signature check if `do_sig_check` is true. I don't really understand why that parameter is ever set to false, but the docstring is "True if it should be verified that the **sending** server signed the event." This is nothing to do with the sending server.
There's a few spots I think you might be referring to:

* [`_check_sigs_on_pdu`](https://github.com/matrix-org/synapse/blob/228decfce1a71651d64c359d1cf28e10d0a69fc8/synapse/federation/federation_base.py#L181-L207) which checks signatures on events to ensure they're signed by the sending server (according to `_check_sigs_and_hash`). This seems to properly check the room version; it does *not* check whether the join rules are set to `restricted` though since that would require knowing other events which isn't really part of checking signatures (but it does mean that you could fail this check if you just throw an unused `join_authorised_via_users_server` into the event).
* [`event_auth.check` in the `do_sig_check` block](https://github.com/matrix-org/synapse/blob/51e2db35983953b13e536331ec2f6ad4cae7e0f1/synapse/event_auth.py#L115-L125) which ensures that the event has a signature, but does not assert the signature is valid.
* [`event_auth.check` in the `restricted` join rules processing](https://github.com/matrix-org/synapse/blob/51e2db35983953b13e536331ec2f6ad4cae7e0f1/synapse/event_auth.py#L377-L398), this processes the join event to ensure it meets the requirements of the join rule. I don't see this doing anything with signature checking?

I don't see any of these meeting the requirements in the description, could you point to which of these (or maybe I missed a spot!) that you were taking a look at?

> Synapse also does the signature check if `do_sig_check` is true. I don't really understand why that parameter is ever set to false, but the docstring is "True if it should be verified that the **sending** server signed the event." This is nothing to do with the sending server.

`do_sig_check` is `False` during a `make_{join,leave,knock}` since the partial event cannot have valid signatures yet. There might be other situations too. Note that it doesn't seem to check the validity of the signature here, just that one exists. (The assumption being that the signature was already checked to be valid in `_check_sigs_on_pdu`, I think.)

The comment was added in #10393, I might have a flawed understanding of what this code is doing (or might have over-simplified for the comment).
Sorry! This one:

>  * [`event_auth.check` in the `do_sig_check` block](https://github.com/matrix-org/synapse/blob/51e2db35983953b13e536331ec2f6ad4cae7e0f1/synapse/event_auth.py#L115-L125) which ensures that the event has a signature, but does not assert the signature is valid.

right, but it still checks it is there, which is a new requirement which existing servers would not apply - hence the danger of split-braining where some servers accept, and some reject, the event.

But now that you point out the other places, I have another opinion :/

> * [`_check_sigs_on_pdu`](https://github.com/matrix-org/synapse/blob/228decfce1a71651d64c359d1cf28e10d0a69fc8/synapse/federation/federation_base.py#L181-L207) which checks signatures on events to ensure they're signed by the sending server (according to `_check_sigs_and_hash`).  This seems to properly check the room version; it does not check whether the join rules are set to restricted though since that would require knowing other events which isn't really part of checking signatures (but it does mean that you could fail this check if you just throw an unused join_authorised_via_users_server into the event).

The implementation here looks sound, though:
 * Again, this is not necessarily the sending server - we should make sure that comments and docstrings do not imply that these signature checks are limited to the sending server.
 * Not a new problem, but the fact that `event_auth.check` doesn't check the validity of any signatures (merely assuming that any signatures that are present have already been validated) is confusing and feels a bit brittle. In particular, it feels like it would be very easy for us to end up relying on a signature whose validity we haven't already checked. We should at the very least have some comments explaining what's going on.
 * We'll need to make sure that whatever ends up getting specced in https://github.com/matrix-org/matrix-doc/pull/3387 matches this. I don't think it does, currently.

> There might be other situations too.

well yes, therein lies the rub. I was mostly worrying about the calls from `synapse.state.{v1,v2}`. However, on staring at them a bit more closely, I think it's probably correct (at that point the events have already been accepted or rejected, and we are now authing them against different sets of state - so we wouldn't expect things like "is a signature present" to change).

I would say: we should update the docstring on `event_auth.check` to make it clear that `do_sig_check` does not just apply to the sending server; however it's hard not to feel like it just needs splitting up into smaller functions. Leave that one with me, and I'll do it as part of my work on #9595.
> Sorry! This one:
> 
> > * [`event_auth.check` in the `do_sig_check` block](https://github.com/matrix-org/synapse/blob/51e2db35983953b13e536331ec2f6ad4cae7e0f1/synapse/event_auth.py#L115-L125) which ensures that the event has a signature, but does not assert the signature is valid.
> 
> right, but it still checks it is there, which is a new requirement which existing servers would not apply - hence the danger of split-braining where some servers accept, and some reject, the event.

This should be checking the room versions, I think this is what you're filing this issue about?

> But now that you point out the other places, I have another opinion :/
> 
> > * [`_check_sigs_on_pdu`](https://github.com/matrix-org/synapse/blob/228decfce1a71651d64c359d1cf28e10d0a69fc8/synapse/federation/federation_base.py#L181-L207) which checks signatures on events to ensure they're signed by the sending server (according to `_check_sigs_and_hash`).  This seems to properly check the room version; it does not check whether the join rules are set to restricted though since that would require knowing other events which isn't really part of checking signatures (but it does mean that you could fail this check if you just throw an unused join_authorised_via_users_server into the event).
> 
> The implementation here looks sound, though:
> 
>     * Again, this is not necessarily the sending server - we should make sure that comments and docstrings do not imply that these signature checks are limited to the sending server.

If you have thoughts on an updated comment I'd really appreciate it! I added it while trying to figure out how all this works and I probably don't have a full underestanding!

>     * Not a new problem, but the fact that `event_auth.check` doesn't check the validity of any signatures (merely assuming that any signatures that are present have already been validated) is confusing and feels a bit brittle. In particular, it feels like it would be very easy for us to end up relying on a signature whose validity we haven't already checked. We should at the very least have some comments explaining what's going on.

I *think* @erikjohnston said that this was for (a) speed, and (b) because a signature could become invalid after it was accepted due to a server changing their keys, so you don't want to constantly check them. They should be accepted or not once.

>     * We'll need to make sure that whatever ends up getting specced in [Room versions 8 and 9: Restricted rooms matrix-doc#3387](https://github.com/matrix-org/matrix-doc/pull/3387) matches this. I don't think it does, currently.

It sounds like this is a minor implementation bug (which I'll fix shortly!), but I don't think the proper implementation is different than the spec?

> > There might be other situations too.
> 
> well yes, therein lies the rub. I was mostly worrying about the calls from `synapse.state.{v1,v2}`. However, on staring at them a bit more closely, I think it's probably correct (at that point the events have already been accepted or rejected, and we are now authing them against different sets of state - so we wouldn't expect things like "is a signature present" to change).
> 
> I would say: we should update the docstring on `event_auth.check` to make it clear that `do_sig_check` does not just apply to the sending server; however it's hard not to feel like it just needs splitting up into smaller functions. Leave that one with me, and I'll do it as part of my work on #9595.

This sounds like my understanding of why this flag exists (b) from above.


Resolving patch:
diff --git a/synapse/event_auth.py b/synapse/event_auth.py
--- a/synapse/event_auth.py
+++ b/synapse/event_auth.py
@@ -113,7 +113,8 @@ def check(
                 raise AuthError(403, "Event not signed by sending server")
 
         is_invite_via_allow_rule = (
-            event.type == EventTypes.Member
+            room_version_obj.msc3083_join_rules
+            and event.type == EventTypes.Member
             and event.membership == Membership.JOIN
             and "join_authorised_via_users_server" in event.content
         )


Test patch: