Repository: matrix-org/synapse

Issue:
500 error on /sendToDevice if destination user is ill-formed (SYN-771)
Submitted by @&#8203;richvdh:sw1v.org

```
$ curl 'https://matrix.org/_matrix/client/unstable/sendToDevice/m.new_device/m1474132576890.1?access_token=<...>' -X PUT --data-binary '{"messages":{"@&#8203;userId":{}}}' 
{"errcode":"M_UNKNOWN","error":"Internal server error"}
```

stacktrace is:

```
2016-09-17 18:16:25,433 - synapse.http.server - 139 - ERROR - PUT-2695429 - Failed handle request synapse.http.server._async_render on <synapse.rest.ClientRestResource object at 0x5dd1310>: <XForwardedForRequest at 0x7fbb9dca8560 method=PUT uri=/_matrix/client/unstable/sendToDevice/m.new_device/m1474132576890.0?access_token=<redacted> clientproto=HTTP/1.1 site=8080>
Traceback (most recent call last):
  File "synapse/http/server.py", line 116, in wrapped_request_handler
    yield request_handler(self, request, request_metrics)
IndexError: list index out of range
```

(Imported from https://matrix.org/jira/browse/SYN-771)


Discussion:

Resolving patch:
diff --git a/synapse/handlers/devicemessage.py b/synapse/handlers/devicemessage.py
--- a/synapse/handlers/devicemessage.py
+++ b/synapse/handlers/devicemessage.py
@@ -17,7 +17,8 @@
 
 from twisted.internet import defer
 
-from synapse.types import get_domain_from_id
+from synapse.api.errors import SynapseError
+from synapse.types import get_domain_from_id, UserID
 from synapse.util.stringutils import random_string
 
 
@@ -33,7 +34,7 @@ def __init__(self, hs):
         """
         self.store = hs.get_datastore()
         self.notifier = hs.get_notifier()
-        self.is_mine_id = hs.is_mine_id
+        self.is_mine = hs.is_mine
         self.federation = hs.get_federation_sender()
 
         hs.get_replication_layer().register_edu_handler(
@@ -52,6 +53,12 @@ def on_direct_to_device_edu(self, origin, content):
         message_type = content["type"]
         message_id = content["message_id"]
         for user_id, by_device in content["messages"].items():
+            # we use UserID.from_string to catch invalid user ids
+            if not self.is_mine(UserID.from_string(user_id)):
+                logger.warning("Request for keys for non-local user %s",
+                               user_id)
+                raise SynapseError(400, "Not a user here")
+
             messages_by_device = {
                 device_id: {
                     "content": message_content,
@@ -77,7 +84,8 @@ def send_device_message(self, sender_user_id, message_type, messages):
         local_messages = {}
         remote_messages = {}
         for user_id, by_device in messages.items():
-            if self.is_mine_id(user_id):
+            # we use UserID.from_string to catch invalid user ids
+            if self.is_mine(UserID.from_string(user_id)):
                 messages_by_device = {
                     device_id: {
                         "content": message_content,
diff --git a/synapse/handlers/e2e_keys.py b/synapse/handlers/e2e_keys.py
--- a/synapse/handlers/e2e_keys.py
+++ b/synapse/handlers/e2e_keys.py
@@ -20,7 +20,7 @@
 from twisted.internet import defer
 
 from synapse.api.errors import SynapseError, CodeMessageException
-from synapse.types import get_domain_from_id
+from synapse.types import get_domain_from_id, UserID
 from synapse.util.logcontext import preserve_fn, make_deferred_yieldable
 from synapse.util.retryutils import NotRetryingDestination
 
@@ -32,7 +32,7 @@ def __init__(self, hs):
         self.store = hs.get_datastore()
         self.federation = hs.get_replication_layer()
         self.device_handler = hs.get_device_handler()
-        self.is_mine_id = hs.is_mine_id
+        self.is_mine = hs.is_mine
         self.clock = hs.get_clock()
 
         # doesn't really work as part of the generic query API, because the
@@ -70,7 +70,8 @@ def query_devices(self, query_body, timeout):
         remote_queries = {}
 
         for user_id, device_ids in device_keys_query.items():
-            if self.is_mine_id(user_id):
+            # we use UserID.from_string to catch invalid user ids
+            if self.is_mine(UserID.from_string(user_id)):
                 local_query[user_id] = device_ids
             else:
                 remote_queries[user_id] = device_ids
@@ -170,7 +171,8 @@ def query_local_devices(self, query):
 
         result_dict = {}
         for user_id, device_ids in query.items():
-            if not self.is_mine_id(user_id):
+            # we use UserID.from_string to catch invalid user ids
+            if not self.is_mine(UserID.from_string(user_id)):
                 logger.warning("Request for keys for non-local user %s",
                                user_id)
                 raise SynapseError(400, "Not a user here")
@@ -213,7 +215,8 @@ def claim_one_time_keys(self, query, timeout):
         remote_queries = {}
 
         for user_id, device_keys in query.get("one_time_keys", {}).items():
-            if self.is_mine_id(user_id):
+            # we use UserID.from_string to catch invalid user ids
+            if self.is_mine(UserID.from_string(user_id)):
                 for device_id, algorithm in device_keys.items():
                     local_query.append((user_id, device_id, algorithm))
             else:


Test patch: