Repository: matrix-org/synapse

Issue:
ValueError: Stop argument for islice() must be None or an integer: 0 <= x <= sys.maxsize.
See https://sentry.matrix.org/sentry/synapse-matrixorg/issues/222815/?query=is%3Aunresolved

Happening at `synapse.handlers.space_summary` in `_summarize_local_room`. Seems to be due to `max_rooms_per_space` being passed a `-1` via federation. The stack is:

```
ValueError: Stop argument for islice() must be None or an integer: 0 <= x <= sys.maxsize.
  File "synapse/http/server.py", line 258, in _async_render_wrapper
    callback_return = await self._async_render(request)
  File "synapse/http/server.py", line 446, in _async_render
    callback_return = await raw_callback_return
  File "synapse/federation/transport/server.py", line 369, in new_func
    origin, content, request.args, *args, **kwargs
  File "synapse/federation/transport/server.py", line 1944, in on_POST
    origin, room_id, suggested_only, max_rooms_per_space, exclude_rooms
  File "synapse/handlers/space_summary.py", line 289, in federation_space_summary
    None, origin, room_id, suggested_only, max_rooms_per_space
  File "synapse/handlers/space_summary.py", line 357, in _summarize_local_room
    for edge_event in itertools.islice(child_events, max_children):
```


Discussion:

Resolving patch:
diff --git a/synapse/federation/transport/server/federation.py b/synapse/federation/transport/server/federation.py
--- a/synapse/federation/transport/server/federation.py
+++ b/synapse/federation/transport/server/federation.py
@@ -557,7 +557,14 @@ async def on_GET(
         room_id: str,
     ) -> Tuple[int, JsonDict]:
         suggested_only = parse_boolean_from_args(query, "suggested_only", default=False)
+
         max_rooms_per_space = parse_integer_from_args(query, "max_rooms_per_space")
+        if max_rooms_per_space is not None and max_rooms_per_space < 0:
+            raise SynapseError(
+                400,
+                "Value for 'max_rooms_per_space' must be a non-negative integer",
+                Codes.BAD_JSON,
+            )
 
         exclude_rooms = parse_strings_from_args(query, "exclude_rooms", default=[])
 
@@ -586,10 +593,17 @@ async def on_POST(
             raise SynapseError(400, "bad value for 'exclude_rooms'", Codes.BAD_JSON)
 
         max_rooms_per_space = content.get("max_rooms_per_space")
-        if max_rooms_per_space is not None and not isinstance(max_rooms_per_space, int):
-            raise SynapseError(
-                400, "bad value for 'max_rooms_per_space'", Codes.BAD_JSON
-            )
+        if max_rooms_per_space is not None:
+            if not isinstance(max_rooms_per_space, int):
+                raise SynapseError(
+                    400, "bad value for 'max_rooms_per_space'", Codes.BAD_JSON
+                )
+            if max_rooms_per_space < 0:
+                raise SynapseError(
+                    400,
+                    "Value for 'max_rooms_per_space' must be a non-negative integer",
+                    Codes.BAD_JSON,
+                )
 
         return 200, await self.handler.federation_space_summary(
             origin, room_id, suggested_only, max_rooms_per_space, exclude_rooms
diff --git a/synapse/rest/client/v1/room.py b/synapse/rest/client/v1/room.py
--- a/synapse/rest/client/v1/room.py
+++ b/synapse/rest/client/v1/room.py
@@ -993,11 +993,19 @@ async def on_GET(
     ) -> Tuple[int, JsonDict]:
         requester = await self._auth.get_user_by_req(request, allow_guest=True)
 
+        max_rooms_per_space = parse_integer(request, "max_rooms_per_space")
+        if max_rooms_per_space is not None and max_rooms_per_space < 0:
+            raise SynapseError(
+                400,
+                "Value for 'max_rooms_per_space' must be a non-negative integer",
+                Codes.BAD_JSON,
+            )
+
         return 200, await self._room_summary_handler.get_space_summary(
             requester.user.to_string(),
             room_id,
             suggested_only=parse_boolean(request, "suggested_only", default=False),
-            max_rooms_per_space=parse_integer(request, "max_rooms_per_space"),
+            max_rooms_per_space=max_rooms_per_space,
         )
 
     # TODO When switching to the stable endpoint, remove the POST handler.
@@ -1014,10 +1022,17 @@ async def on_POST(
             )
 
         max_rooms_per_space = content.get("max_rooms_per_space")
-        if max_rooms_per_space is not None and not isinstance(max_rooms_per_space, int):
-            raise SynapseError(
-                400, "'max_rooms_per_space' must be an integer", Codes.BAD_JSON
-            )
+        if max_rooms_per_space is not None:
+            if not isinstance(max_rooms_per_space, int):
+                raise SynapseError(
+                    400, "'max_rooms_per_space' must be an integer", Codes.BAD_JSON
+                )
+            if max_rooms_per_space < 0:
+                raise SynapseError(
+                    400,
+                    "Value for 'max_rooms_per_space' must be a non-negative integer",
+                    Codes.BAD_JSON,
+                )
 
         return 200, await self._room_summary_handler.get_space_summary(
             requester.user.to_string(),


Test patch: