Skip to content

Commit 46c782d

Browse files
authored
Fix 2252 (#2263)
* start new dev branch; add audit file * Add failing tests: RawSocket max_message_size must be validated over [512, 16MB] (#2252) check_rawsocket_options() reuses the WebSocket-shaped check_transport_max_message_size(), which accepts [1, 64MB]. But the RawSocket transport hands the value to autobahn's WampRawSocketFactory.setProtocolOptions, which asserts [512, 2**24] — the range the WAMP RawSocket handshake can encode (length exponent 2**(9+n), n in [0,15]). So a config like max_message_size=100 or max_message_size=32000000 passes 'crossbar check' and then AssertionErrors at transport start: a false green. Add CheckRawsocketTests asserting RawSocket rejects < 512 and > 16 MB (incl. the 511 and 2**24+1 boundaries), accepts the 512 and 2**24 boundaries, and that the split leaves WebSocket's own [1, 64MB] range unaffected. RED as expected: the four out-of-range assertions fail (InvalidConfigException not raised); the two boundary-accept cases and the WebSocket guard already pass. Note: This work was completed with AI assistance (Claude Code). * Validate RawSocket max_message_size over [512, 16MB] (#2252) Give RawSocket its own max_message_size validator instead of reusing the WebSocket-shaped check_transport_max_message_size() ([1, 64MB]). The root cause was one shared validator applied to two transports with genuinely different constraints: WAMP RawSocket encodes the receive limit as a length exponent 2**(9+n), n in [0,15], so autobahn's WampRawSocketFactory.setProtocolOptions asserts [512, 2**24]. A config in the gap passed 'crossbar check' and then AssertionError-ed at transport start (a false green). - checkconfig.py: add check_transport_rawsocket_max_message_size() over [RAWSOCKET_MAX_MESSAGE_SIZE_MIN=2**9, RAWSOCKET_MAX_MESSAGE_SIZE_MAX=2**24]; point check_rawsocket_options() at it; clarify that check_transport_max_message_size() is the WebSocket ([1, 64MB]) validator. The bounds are fixed by the RawSocket handshake (not autobahn being fussy), so they cannot drift; the comment ties them to autobahn's assert. - docs/RawSocket-Transport.rst: correct the range (was 1-64MB) and the default (was 128kB; the real default is 16MB) and note the value is the uncompressed message size. - changelog: 26.7.1 entry. Turns CheckRawsocketTests green (7/7); WebSocket's own range is unaffected. Full local gate: ruff + ty clean, test_checkconfig 40 passed, Sphinx dummy build clean on the changed pages. Note: This work was completed with AI assistance (Claude Code).
1 parent d73ba82 commit 46c782d

5 files changed

Lines changed: 86 additions & 3 deletions

File tree

.audit/oberstet_fix_2252.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
- [ ] I did **not** use any AI-assistance tools to help create this pull request.
2+
- [x] I **did** use AI-assistance tools to *help* create this pull request.
3+
- [x] I have read, understood and followed the projects' [AI Policy](https://github.com/crossbario/autobahn-python/blob/main/AI_POLICY.md) when creating code, documentation etc. for this pull request.
4+
5+
Submitted by: @oberstet
6+
Date: 2026-07-17
7+
Related issue(s): #2252
8+
Branch: oberstet:fix_2252

docs/RawSocket-Transport.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ for RawSocket listening transports are:
6363
+--------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
6464
| serializers | List of serializers to use from "json" or "msgpack" (default: all available) |
6565
+--------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
66-
| max_message_size | Maximum size in bytes of incoming RawSocket messages accepted. Must be between 1 and 64MB (default: 128kB) |
66+
| max_message_size | Maximum size in bytes of incoming (uncompressed) RawSocket messages accepted. Must be between 512 bytes and 16 MB (default: 16777216 = 16 MB) |
6767
+--------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
6868
| auth | Authentication to be used for this Endpoint - see :doc:`Authentication<Authentication>` |
6969
+--------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+

docs/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Changelog
1010

1111
* new: coordinated WAMP **26.7.1 release train** — pin ``autobahn>=26.7.1`` for the WebSocket permessage-deflate decompression-bomb fix (advisory ``GHSA-hxp9-w8x3-p566``) and relock ``uv.lock`` to the coordinated set (autobahn 26.7.1, zlmdb 26.7.1); ``cfxdb`` and ``txaio`` remain at 26.6.1 (`#2250 <https://github.com/crossbario/crossbar/issues/2250>`_)
1212
* **⚠ behaviour change** — ``max_message_size`` now **defaults to 16 MB** (``2**24``) on **both** the WebSocket and RawSocket transports (secure by default). Previously the WebSocket default was ``0`` (unlimited) while RawSocket already defaulted to 16 MB, so a WebSocket transport that did not set the option accepted messages of any size — leaving the router unbounded by default and the ``GHSA-hxp9-w8x3-p566`` protection effectively opt-in. Messages larger than 16 MB on a WebSocket transport with no explicit ``max_message_size`` are now rejected (close code 1009, ``MESSAGE_TOO_BIG``). Set ``max_message_size`` explicitly if you need more — ``0`` allows any size, and WebSocket permits up to 64 MB (RawSocket cannot exceed 16 MB by protocol) (`#2251 <https://github.com/crossbario/crossbar/issues/2251>`_)
13+
* fix: validate RawSocket ``max_message_size`` over its actual protocol range ``[512, 16 MB]`` with a dedicated validator. It previously reused the WebSocket-shaped ``[1, 64 MB]`` check, so a RawSocket config with ``max_message_size`` below 512 or above 16 MB passed ``crossbar check`` but then raised ``AssertionError`` at transport start (autobahn's factory asserts the WAMP RawSocket handshake range ``2**(9+n)``, i.e. ``[512, 16 MB]``). Also corrects ``docs/RawSocket-Transport.rst``: the documented range (``1–64 MB``) and default (``128 kB``) were both wrong — the real default is 16 MB (`#2252 <https://github.com/crossbario/crossbar/issues/2252>`_)
1314
* fix: validate WebSocket ``compression`` transport configuration. ``check_websocket_compression()`` was an empty stub yet wired into ``check_websocket_options()``, so unknown codecs, unknown or mis-typed deflate attributes and out-of-range ``request_max_window_bits`` / ``max_window_bits`` / ``memory_level`` were all silently accepted and only surfaced (if at all) as a run-time error deep inside the compression backend (`#2250 <https://github.com/crossbario/crossbar/issues/2250>`_)
1415
* new: functional test asserting the router rejects a decompression ("zip") bomb — a payload tiny on the wire but inflating far past the transport's ``max_message_size`` — on its *uncompressed* reassembled size (close code 1009). Verified a genuine regression test: it fails against autobahn < 26.7.1 and passes against 26.7.1 (`#2250 <https://github.com/crossbario/crossbar/issues/2250>`_)
1516
* docs: clarify the ``max_frame_size`` (on-the-wire, per-frame) versus ``max_message_size`` (reassembled and decompressed, per-message) semantics on the WebSocket and RawSocket transports, and document ``max_message_size`` as the decompression-bomb guard in the WebSocket Compression docs (`#2250 <https://github.com/crossbario/crossbar/issues/2250>`_)

src/crossbar/common/checkconfig.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,7 +1011,10 @@ def check_endpoint_timeout(timeout):
10111011

10121012
def check_transport_max_message_size(max_message_size):
10131013
"""
1014-
Check maximum message size parameter in RawSocket and WebSocket transports.
1014+
Check the ``max_message_size`` parameter for a WebSocket transport ([1, 64MB]).
1015+
1016+
RawSocket has a narrower, protocol-imposed range and its own validator, see
1017+
:func:`check_transport_rawsocket_max_message_size`.
10151018
10161019
:param max_message_size: The maximum message size parameter to check.
10171020
:type max_message_size: int
@@ -1028,6 +1031,38 @@ def check_transport_max_message_size(max_message_size):
10281031
)
10291032

10301033

1034+
# WAMP RawSocket negotiates its receive limit as a length exponent 2**(9+n),
1035+
# n in [0, 15], so max_message_size must lie in [2**9, 2**24] = [512, 16 MB].
1036+
# autobahn's WampRawSocketFactory.setProtocolOptions asserts exactly this, so a
1037+
# value outside the range passes config validation but raises AssertionError at
1038+
# transport start. WebSocket has no such protocol cap. (crossbar #2252)
1039+
RAWSOCKET_MAX_MESSAGE_SIZE_MIN = 2**9 # 512 octets
1040+
RAWSOCKET_MAX_MESSAGE_SIZE_MAX = 2**24 # 16 MB
1041+
1042+
1043+
def check_transport_rawsocket_max_message_size(max_message_size):
1044+
"""
1045+
Check the ``max_message_size`` parameter for a RawSocket transport.
1046+
1047+
Unlike WebSocket, WAMP RawSocket bounds this to [512, 16MB] by protocol.
1048+
1049+
:param max_message_size: The maximum message size parameter to check.
1050+
:type max_message_size: int
1051+
"""
1052+
if not isinstance(max_message_size, int):
1053+
raise InvalidConfigException(
1054+
"'max_message_size' attribute in RawSocket transport must be int ({} encountered)".format(
1055+
type(max_message_size)
1056+
)
1057+
)
1058+
if max_message_size < RAWSOCKET_MAX_MESSAGE_SIZE_MIN or max_message_size > RAWSOCKET_MAX_MESSAGE_SIZE_MAX:
1059+
raise InvalidConfigException(
1060+
"invalid value {} for 'max_message_size' attribute in RawSocket transport (must be from [512, 16MB])".format(
1061+
max_message_size
1062+
)
1063+
)
1064+
1065+
10311066
def check_listening_endpoint_tls(tls):
10321067
"""
10331068
Check a listening endpoint TLS configuration.
@@ -1486,7 +1521,7 @@ def check_rawsocket_options(options):
14861521
)
14871522

14881523
if "max_message_size" in options:
1489-
check_transport_max_message_size(options["max_message_size"])
1524+
check_transport_rawsocket_max_message_size(options["max_message_size"])
14901525

14911526

14921527
def check_websocket_options(options):

src/crossbar/test/test_checkconfig.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,3 +456,42 @@ def test_port_wrong_type(self):
456456
)
457457
self.assertIn("invalid type", str(ctx.exception))
458458
self.assertIn("encountered for attribute 'port'", str(ctx.exception))
459+
460+
461+
class CheckRawsocketTests(TestCase):
462+
"""
463+
RawSocket ``max_message_size`` must be validated over the range the WAMP
464+
RawSocket protocol can actually negotiate — [512, 16 MB] — not the wider
465+
[1, 64 MB] shared with WebSocket. A value in the gap passes ``crossbar
466+
check`` but then makes autobahn's factory ``AssertionError`` at transport
467+
start (crossbar #2252).
468+
"""
469+
470+
def test_rejects_below_512(self):
471+
with self.assertRaises(checkconfig.InvalidConfigException):
472+
checkconfig.check_rawsocket_options({"max_message_size": 100})
473+
474+
def test_rejects_just_below_512(self):
475+
with self.assertRaises(checkconfig.InvalidConfigException):
476+
checkconfig.check_rawsocket_options({"max_message_size": 511})
477+
478+
def test_rejects_above_16mb(self):
479+
with self.assertRaises(checkconfig.InvalidConfigException):
480+
checkconfig.check_rawsocket_options({"max_message_size": 32000000})
481+
482+
def test_rejects_just_above_16mb(self):
483+
with self.assertRaises(checkconfig.InvalidConfigException):
484+
checkconfig.check_rawsocket_options({"max_message_size": 2**24 + 1})
485+
486+
def test_accepts_min_512(self):
487+
# boundary: exactly the RawSocket minimum (2**9) is valid
488+
checkconfig.check_rawsocket_options({"max_message_size": 512})
489+
490+
def test_accepts_max_16mb(self):
491+
# boundary: exactly the RawSocket maximum (2**24) is valid
492+
checkconfig.check_rawsocket_options({"max_message_size": 2**24})
493+
494+
def test_websocket_range_unaffected(self):
495+
# the RawSocket split must not narrow WebSocket, which still allows up to
496+
# 64 MB (a value invalid for RawSocket but valid for WebSocket).
497+
checkconfig.check_websocket_options({"max_message_size": 32 * 1024 * 1024})

0 commit comments

Comments
 (0)