{"id":"CVE-2026-62949","aliases":["GHSA-rw4j-r22c-9gc3","PYSEC-2026-4026"],"url":"https://o3.security/vulnerability/CVE-2026-62949","summary":"AsyncSSH: asyncio event-loop freeze via SSH maximum packet size = 0 in SSH_MSG_CHANNEL_OPEN / OPEN_CONFIRMATION","details":"## Summary\n\nA malicious SSH server can wedge an AsyncSSH **client**, and an authenticated\nclient can wedge an AsyncSSH **server**, by sending a channel `maximum packet\nsize` of `0` in `SSH_MSG_CHANNEL_OPEN_CONFIRMATION` (server→client) or\n`SSH_MSG_CHANNEL_OPEN` (client→server). AsyncSSH stores the peer-supplied value\nverbatim with no lower-bound check; the first time channel data is written,\n`SSHChannel._flush_send_buf` enters a **synchronous infinite loop** that cannot\nbe interrupted by `asyncio.wait_for` or any timeout. The loop body has no\n`await`, so it blocks the entire asyncio event loop — for a server, one\nmalicious authenticated channel freezes **all** current and future connections.\n\nRFC 4254 §5.1 leaves receiver behavior for a peer-reported \"maximum packet\nsize = 0\" undefined, so the value must be rejected rather than stored.\n\n## Root cause\n\n`asyncssh/channel.py`:\n\n```python\n# process_open (server side)        -- line 465\nself._send_pktsize = send_pktsize    # peer value, no >= 1 check\n\n# process_open_confirmation (client) -- line 528\nself._send_pktsize = send_pktsize    # peer value, no >= 1 check\n\n# _flush_send_buf                    -- lines 305-320\nwhile self._send_buf and self._send_window:\n    pktsize = min(self._send_window, self._send_pktsize)  # 0 when peer sends 0\n    buf, datatype = self._send_buf[0]\n    if len(buf) > pktsize:          # True for any buffered data\n        data = buf[:pktsize]        # empty (b'')\n        del buf[:pktsize]           # no-op\n    ...\n    self._send_window -= len(data)  # -= 0, unchanged\n```\n\nWith `_send_pktsize == 0`, `pktsize` is `0`, so `buf[:0]` is empty,\n`del buf[:0]` is a no-op, and `_send_window` is never decremented — the\n`while` condition is permanently true, and with no `await` in the body the\nevent loop is blocked.\n\n## Impact\n\n- **Client vector (primary):** a malicious SSH server replies to the client's\n  channel open with `maximum packet size = 0`; the client wedges on its first\n  channel write. The attacker is the server, so it needs no valid credentials.\n- **Server vector:** an authenticated client opens a channel with\n  `maximum packet size = 0`; any server-side channel write wedges the AsyncSSH\n  server's event loop, freezing **every** current and future connection. A\n  single low-privilege account can take the whole server down.\n\nBoth vectors are a single SSH message, deterministic, and cause total\navailability loss for the affected process.\n\n## Affected versions\n\n<= 2.23.1 (latest release, 2026-06-06); also present on `master`\n(channel.py:465/528 unguarded). Verified end-to-end on 2.23.1.\n\n## Verification\n\nThe maintainer's proposed fix (reject `send_pktsize == 0` in `connection.py`\n`_process_channel_open` / `_process_channel_open_confirmation`) was applied to\n2.23.1 and re-tested end-to-end over TCP:\n\n- **Unpatched:** malicious server (paramiko forcing `max_packet_size=0` in\n  OPEN_CONFIRMATION) + real asyncssh client → client event loop wedges.\n- **Patched:** the guard fires inside `_process_channel_open_confirmation`, the\n  malicious value is rejected, the connection closes cleanly\n  (`ChannelOpenError: SSH connection closed`), and the client does **not** wedge.\n\nThe maintainer (Ron Frederick) independently confirmed the freeze and noted that\n**even shutting the server down does not break clients out of the loop**.\n\nReproducers available: a focused harness driving the real\n`SSHChannel._flush_send_buf` with `_send_pktsize=0`, and an end-to-end\n`malicious_server.py` (paramiko) + `client.py` (real asyncssh) pair. The\nend-to-end client repro uses `asyncio.new_event_loop()` (not `get_event_loop()`)\nfor Python 3.14 compatibility.\n\n## Suggested fix (maintainer's approach)\n\nIn `connection.py`, after each `send_pktsize = packet.get_uint32()` in\n`_process_channel_open` and `_process_channel_open_confirmation`:\n\n```python\nif send_pktsize == 0:\n    raise ProtocolError('Invalid maximum packet size')\n```\n\n## CVSS\n\n`CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H` (6.5 Medium). An earlier draft\nquoted 7.5 High (\"100% CPU\"); the synchronous loop burns ~100% of one core's\nworth of CPU but, being single-threaded, the OS scheduler spreads it across\ncores, so the real impact is event-loop / connection freeze, not machine-wide\nCPU exhaustion.\n\n## References\n\n- RFC 4254 §5.1 (channel \"maximum packet size\"; behavior for 0 is undefined).\n- The same `maximum packet size = 0` send-loop wedge was confirmed in several\n  other independent SSH implementations (different languages/runtimes) and\n  reported to each maintainer separately.\n\n## Credits\n\nReported by zhangph (afldl), 2026-06-20.\n```","published":"2026-09-16T20:00:46.657Z","modified":"2026-10-28T02:30:24.582339179Z","cvss":{"score":6.5,"severity":"MEDIUM","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H"},"epss":{"score":0.00388,"percentile":0.32661,"asOf":"2026-09-17"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"PyPI","name":"asyncssh","fixedVersion":"2.24.0"}],"fix":{"url":"https://github.com/ronf/asyncssh/commit/756cbae5350789ce9735f15f704bae9b5a3608b8","label":"ronf/asyncssh@756cbae"},"references":[{"type":"WEB","url":"https://github.com/ronf/asyncssh/releases/tag/v2.24.0"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/62xxx/CVE-2026-62949.json"},{"type":"ADVISORY","url":"https://github.com/ronf/asyncssh/security/advisories/GHSA-rw4j-r22c-9gc3"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-62949"},{"type":"FIX","url":"https://github.com/ronf/asyncssh/commit/756cbae5350789ce9735f15f704bae9b5a3608b8"},{"type":"FIX","url":"https://github.com/ronf/asyncssh/commit/9c354270c009285525e126721e8ed5fbed1f8a67"},{"type":"PACKAGE","url":"https://github.com/ronf/asyncssh"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-10-28T02:30:24.582339179Z"}}