Fix ChannelAgentForwarding: move rbuf.reset() and fix skip() in unkno…#1059
Open
payne1982 wants to merge 1 commit into
Open
Fix ChannelAgentForwarding: move rbuf.reset() and fix skip() in unkno…#1059payne1982 wants to merge 1 commit into
payne1982 wants to merge 1 commit into
Conversation
…wn message handler
|
norrisjeremy
approved these changes
Jun 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



See #1025
Last post:
After a month of testing I found out the actual fix for the Agent Forwarding is not optimal in some cases.
There are now two bugs in the current write() method — one is the rbuf.skip() already described here, plus a second one introduced by the rbuf.reset() call at the top of the method (added in 2.27.3).
Bug 1 — rbuf.reset() at the top of write() breaks partial-message accumulation
The method is designed to handle SSH agent messages that arrive in multiple chunks: when the data received so far is shorter than the declared message length, it backs up the read pointer and returns:
On the next write() call the new chunk is supposed to be appended after the existing partial data. However, rbuf.reset() at the very top of the method resets both s and index to 0 on every call, silently discarding whatever was accumulated in the previous call. The result is an infinite partial-message loop that never resolves — especially visible on high-latency links (mobile networks, VPN tunnels with jump hosts) where fragmentation is common.
The fix is remove rbuf.reset(). The buffer-resize allocation must also be corrected accordingly (rbuf.s + l → rbuf.index + l):
Bug 2 — rbuf.skip() in the else branch advances the wrong pointer (already described in this issue)
Buffer.skip(n) increments index (the write pointer), not s (the read pointer). So after hitting the else branch for an unknown message type, s is left pointing one byte into the unread data. That orphaned byte is prepended to the next message's length field, corrupting all subsequent parsing.
The fix (as already proposed in this issue):
Combined effect
On a high-latency connection to a modern OpenSSH server (which sends SSH2_AGENTC_EXTENSION, type 27) through a jump host, both bugs fire together: Bug 2 leaves an orphaned byte which, combined with Bug 1's reset-on-every-call behaviour, causes the agent-forwarding channel to stall indefinitely. The SSH session appears to hang on the first connection attempt; a retry succeeds because the session state is clean.