Mplex.read_message reads the frame body via read_varint_prefixed_bytes (mplex.py:273 → varint.py), which takes the length straight from the wire and calls read_exactly with no cap. Every muxer besides mplex bounds this: yamux rejects DATA frames > MAX_WINDOW_SIZE (16 MB) and read_length_prefixed_protobuf caps at 1 MB. go-mplex enforces MaxMessageSize = 1 MiB. So a peer can make the victim buffer an arbitrarily large frame (bandwidth-bound; amplified across many streams/connections).
Why it's not a one-line cap. read_exactly doesn't pre-allocate (no amplification per frame), and mplex write() sends each app write as a single unchunked frame — so a read cap alone breaks legitimate py↔py writes larger than the cap. Pairing it with write-chunking (like yamux) then runs into mplex's flow control: _handle_message pushes each frame into a per-stream channel of size MPLEX_MESSAGE_CHANNEL_SIZE = 8 and resets the stream on overflow — so chunking a large write into many frames can spuriously reset it.
Proposed direction:
- Add an optional
max_length to read_varint_prefixed_bytes and enforce a message-size limit in mplex (spec-aligned, e.g. 1 MiB → MplexUnavailable).
- Chunk writes at that limit, and move per-stream flow control from message-count to a byte-based buffer so chunking doesn't trip the 8-frame reset.
Flagging for maintainer input on the flow-control policy before a PR, since (2) changes behavior. Related: #572 (resource exhaustion), #534 (mplex→yamux).
Mplex.read_messagereads the frame body viaread_varint_prefixed_bytes(mplex.py:273 → varint.py), which takes the length straight from the wire and callsread_exactlywith no cap. Every muxer besides mplex bounds this: yamux rejects DATA frames >MAX_WINDOW_SIZE(16 MB) andread_length_prefixed_protobufcaps at 1 MB. go-mplex enforcesMaxMessageSize = 1 MiB. So a peer can make the victim buffer an arbitrarily large frame (bandwidth-bound; amplified across many streams/connections).Why it's not a one-line cap.
read_exactlydoesn't pre-allocate (no amplification per frame), and mplexwrite()sends each app write as a single unchunked frame — so a read cap alone breaks legitimate py↔py writes larger than the cap. Pairing it with write-chunking (like yamux) then runs into mplex's flow control:_handle_messagepushes each frame into a per-stream channel of sizeMPLEX_MESSAGE_CHANNEL_SIZE = 8and resets the stream on overflow — so chunking a large write into many frames can spuriously reset it.Proposed direction:
max_lengthtoread_varint_prefixed_bytesand enforce a message-size limit in mplex (spec-aligned, e.g. 1 MiB →MplexUnavailable).Flagging for maintainer input on the flow-control policy before a PR, since (2) changes behavior. Related: #572 (resource exhaustion), #534 (mplex→yamux).