wolfsshd: wait on the write side in the shell loop - #1253
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Two moderate issues remain unresolved regarding initial pending-output tracking and blocked-send retries.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
This pull request updates SHELL_Subsystem() to improve queued-output and back-pressure handling on Windows and POSIX.
Changes:
- Tracks pending output after worker, read, and send operations.
- Rebuilds Windows descriptor sets with state-dependent waits.
- Separates channel-window handling from POSIX write readiness.
Review findings:
- Moderate (2 votes): seed
wantWritefromwolfSSH_OutputPending(ssh)after the preflight worker call. - Moderate (1 vote): retry queued output only when the socket is reported writable.
File summaries
| File | Summary |
|---|---|
apps/wolfsshd/wolfsshd.c |
Updates Windows and POSIX shell-loop polling and output handling. |
Review details
Suppressed comments (1)
apps/wolfsshd/wolfsshd.c:2300
wantWritemeans output is still owed, not thatselect()reported the socket writable. When the socket remains blocked and there is no inbound data, the 800-us timeout expires, but this condition still callswolfSSH_worker()solely becausewantWriteis carried from the previous pass; the worker retries the same blocked send about 1,250 times per second instead of waiting for writability. Gate this term onFD_ISSET(sshFd, &writeFds)while retainingwindowFull/read handling for the other wake conditions.
if (wantWrite || windowFull || pending
|| FD_ISSET(sshFd, &readFds)) {
- Files reviewed: 1/1 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
9bf5274 to
a0c5c4f
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1253
Scan targets checked: wolfssh-src, wolfssh-bugs
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.
a0c5c4f to
ff29227
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1253
Scan targets checked: wolfssh-src, wolfssh-bugs
Findings: 1
1 finding(s) posted as inline comments (see file-level comments below)
💬 1 finding(s) from an earlier review are still open and were not re-posted:
- POSIX window-full path still busy-spins on child readiness —
apps/wolfsshd/wolfsshd.c:2951
This review was generated automatically by Fenrir. Reported findings require changes before merge.
ff29227 to
8560de6
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1253
Scan targets checked: wolfssh-src, wolfssh-bugs
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.
8560de6 to
8466497
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1253
Scan targets checked: wolfssh-src, wolfssh-bugs
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.
8466497 to
bea2922
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1253
Scan targets checked: wolfssh-src, wolfssh-bugs
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.
23320b3 to
a564d6b
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1253
Scan targets checked: wolfssh-src, wolfssh-bugs
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.
- SHELL_BACKLOG holds the bytes a send did not take, the stream they came from, and a SHELL_SEND_STATE of READY, BLOCKED or NEVER that SHELL_BacklogState() derives from the send result, the output queue and the channel's window and packet size. It replaces windowFull in both SHELL_Subsystem() copies, and windowFullExt in the POSIX one. - Both copies arm their select() write set from wolfSSH_OutputPending() and branch their timeouts on the state; a NEVER backlog ends the session. - While a backlog is held neither copy reads more child output, and the POSIX copy leaves the child's descriptors out of the read set. - The WIN32 copy rebuilds its fd sets each pass, runs select() every pass, resets pending, and passes an empty write set as NULL. - It also takes WS_REKEYING in the WS_CHAN_RXD arm, survives a zero-length channel read, and reads inbound data into its own channelBuffer, zeroized with shellBuffer at cleanup.
- processState starts at 0 in SHELL_Subsystem()'s WIN32 half, where it is read by wolfSSH_SetExitStatus() on paths that leave the loop without GetExitCodeProcess() having written it.
a564d6b to
5f06b27
Compare
Problem
The WIN32
SHELL_Subsystem()loop inapps/wolfsshd/wolfsshd.cwaited on the wrongside of the socket:
select()was passedNULLforwritefds; the wait was skippedentirely on passes holding child output; and
rcwas loop-carried, so a timed-out passcould stop calling
wolfSSH_worker()altogether, latching the backlog and hanging thesession.
Underneath both copies sat the same defect.
windowFullrecorded how many bytes asend left behind but erased why, and the causes need different waits:
WS_WANT_WRITE— queued in wolfSSH, socket refusedWS_WINDOW_FULL/WS_REKEYING— peer credit spentpeerMaxPacketSzSendChannelData()clamps tomin(peerWindowSz, peerMaxPacketSz, maxPacketSz)andreturns only the count, so the last two are indistinguishable to the caller. Every
consumer of
windowFullhad to guess, and each guess was right for some causes andwrong for others — a spin in one, a stall in another.
Closes
8cc2fa42e040bd60.Fix (
apps/wolfsshd/wolfsshd.c)Record the state instead of inferring it:
SHELL_BACKLOGbinds that state to the byte count and the stream, replacingwindowFullandwindowFullExtin both copies;SHELL_BacklogState()derives it fromthe send result,
wolfSSH_OutputPending(), and the channel's window and packet size,so the loop waits, retries immediately, or gives up on evidence rather than a guess.
SHELL_SEND_NEVERmatters becauseWS_WINDOW_FULLdoes not only mean "credit spent":SendChannelData()also returns it whenpeerMaxPacketSzis zero, which is takenverbatim from
CHANNEL_OPENand which no window adjust can ever clear. The packet sizeis therefore tested before the window, which would otherwise mask it whenever both are
zero. A backlog on a retired channel is
NEVERfor the same reason — no adjust canreach a channel that is gone.
Two invariants hold while a backlog is held: the loop never reads more child output
(both copies reuse
shellBuffer, so reaching a read would overwrite unsent bytes), andthe child's descriptors stay out of the read set (an exited child's pipe is always
ready, so watching it would spin).
WIN32 additionally:
select()runs every pass so nocontinueskips the wait, fd setsare rebuilt per pass,
pendingno longer latches, the write set is passed asNULLwhile empty since Winsock rejects an empty non-
NULLset withWSAEINVAL, and inbounddata is read into its own
channelBufferrather than sharingshellBufferwith thebacklog, as the POSIX copy has always done.
WS_REKEYINGis handled in theWS_CHAN_RXDarm rather than breaking the loop — the worker returns it in place ofWS_CHAN_RXD, so a separate arm would strand the keystroke it masked — and azero-length channel read no longer ends the session.
processStateis initialised, so abreak before
GetExitCodeProcess()no longer reads an indeterminate value.Verification
Preflight clean (lint plus six
gcc-13 -Werrorconfigs), each commit standalone so theseries bisects. MSVC Debug x64
wolfsshd.exe: 0 warnings, 0 errors, with the WIN32 pathconfirmed present in the binary.
unit,regress,testsuiteand the four scripttests green; merges cleanly with #1251.
skoll multi review,bugs,audit: 0 critical,0 high. The POSIX sshd suite cannot run on macOS —
setgroups()fails againstNGROUPS_MAX— sosshd-test.ymlgates that half.Not in this PR
back-pressure, so the new branches have no functional coverage.
(
ee2918352885c95f). Pre-existing on the other break paths and wire-visible.SHELL_BacklogState()readspeerWindowSz, which isinternal. An accessor for a channel's send room would let a consumer read only what
it can send, so no backlog would exist to classify — and the same bookkeeping could
go from five other consumers.
wolfSSH_ChannelGetSessionGranted()on master is thesame pattern.