#2659·paramiko

[BUG] - O(n²) behavior in Channel.sendall() with tiny remote flow-control windows

Author: pontusj101Created Jul 21, 2026Updated Jul 21, 2026
LabelsBug

Are you using paramiko as a client or server?

Client

What feature(s) aren't working right?

SSH

What version(s) of paramiko are you using?

5.0.0

What version(s) of Python are you using?

3.9.6

What operating system and version are you using?

macOS 26.5.2

If you're connecting as a client, which SSH server are you connecting to?

Not applicable. The reproduction is an in-process simulation and does not connect to an SSH server.

If you're using paramiko as part of another tool, which tool/version?

No. This is a direct Paramiko reproduction.

Expected/desired behavior

Send a large data buffer over an SSH channel using Channel.sendall().

Channel.sendall() and Channel.sendall_stderr() should process large buffers with approximately linear CPU cost, even when the remote channel flow-control window is small. The implementation should avoid repeatedly copying the remaining unsent suffix of the buffer.

Actual behavior

In Paramiko 5.0.0, sendall() and sendall_stderr() repeatedly slice the remaining buffer after each partial send. When the remote peer grants only one byte, or a similarly small amount, per iteration, the total amount of data copied becomes O(n²).

This causes very high CPU usage and makes large writes take substantially longer than expected. In the reproduction, a 1,650,000-byte buffer with a one-byte window grant took 119.334 seconds.

The behavior is scoped to the thread handling the affected channel. No memory exhaustion, code execution, or confidentiality/integrity impact was observed.

How to reproduce

  1. Install Paramiko 5.0.0.
  2. Run the attached two_min_dos.py reproduction.
  3. The script creates an in-process Paramiko Channel and simulates a peer that grants one byte per send.
  4. Observe that Channel.sendall() repeatedly re-slices the remaining buffer and takes approximately two minutes for a 1,650,000-byte input.
  5. The reproduction does not open a network connection or target a remote host.

Observed result:

  • Input size: 1,650,000 bytes
  • Simulated window grant: 1 byte
  • Runtime: 119.334 seconds

Anything else?

This appears to be the same slice-based buffering pattern previously reported in Paramiko issue #892: https://github.com/paramiko/paramiko/issues/892

That issue was addressed in the SFTP/file path using bytearray/memoryview, but the analogous pattern remains in channel.py. A similar bytearray/memoryview approach may be appropriate for Channel.sendall() and Channel.sendall_stderr().

This report is submitted as a performance/hardening issue rather than a CVE claim.