ssh, internal: flush the worker's queued output on every call - #1217
ssh, internal: flush the worker's queued output on every call#1217yosuke-wolfssl wants to merge 1 commit into
Conversation
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1217
Scan targets checked: wolfssh-bugs, wolfssh-src
Findings: 1
1 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Reported findings require changes before merge.
b9d4cc3 to
a650544
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1217
Scan targets checked: wolfssh-bugs, wolfssh-src
Findings: 2
2 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Reported findings require changes before merge.
a650544 to
7b1ec65
Compare
7b1ec65 to
d95204d
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1217
Scan targets checked: wolfssh-bugs, wolfssh-src
Fenrir result: Approved ✅
No new issues found in the changed files.
Advisory only — this automated result does not count as a GitHub approval.
Fenrir's latest completed scan found no issues; clearing the prior automated change request.
- wolfSSH_worker() calls wolfSSH_SendPacket() whenever ssh->outputBuffer holds bytes and the session is not disconnected, in place of doing so only for WS_SUCCESS, WS_WANT_READ, WS_CHAN_RXD or WS_EOF. A failed flush reaches the return only when the receive reported nothing, and ssh->error keeps the receive's code only when the receive failed. Drops the second DoReceive(), its WS_WINDOW_FULL case, the WOLFSSH_TEST_BLOCK fork, and the separate WS_CHANNEL_CLOSED flush. - SendPacketFlush() records its code in ssh->error on every transport failure path, and wolfSSH_SendPacket() says so. The comments on _ChannelRead(), _ChannelReadExt() and test_ChannelReadExtHardFailureReported() drop the former contract. - The echoserver shell loop and the Windows wolfsshd shell loop treat a WS_WANT_WRITE from wolfSSH_worker() as non-fatal. - tests cover the flush on an idle receive, the owed flush across calls, what ssh->error holds after a receive failure, a hard send failure, channel data alongside a failed send, a discarded buffer and an out-of-bounds send, and queued output staying unsent on the disconnect pass. ConnResetIoSend moves to the shared doubles beside a new OobIoSend.
d95204d to
b598ecc
Compare
Problem
A read-only application on a non-blocking socket stalls permanently.
A channel read credits the window,
ChannelCreditWindow()bundles aCHANNEL_WINDOW_ADJUSTintossh->outputBuffer, and the socket write blocks. The credit is not re-parked — the packet is already encrypted and sequenced — sowolfSSH_SendPacket()is the only thing that can discharge it. The peer has spent its window and goes silent waiting for that adjust.The application calls
wolfSSH_worker(), aswolfssh/ssh.hdirects. The worker gated its flush onDoReceive()'s return, and an idle socket makesDoReceive()returnWS_FATAL_ERROR— not one of the gated values. No write is attempted, on that call or any later one. The gate also listedWS_WANT_READ, whichDoReceive()never returns; that dead arm is the bug.Fix (
src/ssh.c)wolfSSH_worker()flushes whenever output is queued and the session is live:!ssh->disconnectedkeeps bytes from going out after a DISCONNECT (RFC 4253 §11.1), which the old gate excluded only by accident.ssh->errorkeeps the receive's code only when the receive itself failed.WS_CHAN_RXD,WS_EOFandWS_EXTDATAkeep the flush's code, asssh.hdocuments forWS_EOF.DoReceive(), itsWS_WINDOW_FULLcase, theWOLFSSH_TEST_BLOCKordering fork, and the separateWS_CHANNEL_CLOSEDflush — that block existed only because the gate skipped that status.DoChannelCloseWantWriteandDoChannelCloseFlushesReplypass unchanged without it.Fix (
src/internal.c)SendPacketFlush()records its code inssh->erroron every transport failure path, not onlyWS_WANT_WRITE. Otherwise a hard send failure during the flush left the idle receive'sWS_WANT_READin place, and callers routing onwolfSSH_get_error()would select for read on a dead socket.Fix (consumers)
The worker now reports
WS_WANT_WRITEwhere the removed secondDoReceive()leftWS_WANT_READ. Two shell loops treated anything else as fatal and would end a live session on a briefly unwritable socket:examples/echoserver/echoserver.c:1208andapps/wolfsshd/wolfsshd.c:1364(WIN32). Every otherwolfSSH_worker()call site already handles it.No public API or header change.
Tests
Seven
wolfSSH_worker()tests cover the flush on an idle receive, the owed flush across calls, and whatssh->errorholds after a receive failure, a hard send failure, channel data alongside a failed send, a discarded buffer, and an out-of-bounds send.TestWorkerReportsDisconnectcovers queued output staying unsent on the disconnect pass.Verification
unit.test149 passed / 0 failed;regress.testpassed.-Werroracross 6 configurations, plus lint.-DWOLFSSH_TEST_BLOCK,scripts/sftp.test), the harness whose ordering fork this removes:WOLFSSH_BLOCK_PROBKnown limitation, not addressed here
echoserver'sssh_worker()waits inselect()on read fds with a NULL timeout and callswolfSSH_worker()only when the socket is readable, so an owed flush is retried only if the peer sends. That stall predates this PR — on master the same path leftWS_WANT_READand also never flushed. Closing it needs a write fd_set in the example's event loop.