From 25fe6df85d5c2edfcbc0b10b74a738a6d196c8ff Mon Sep 17 00:00:00 2001 From: John Safranek Date: Mon, 18 May 2026 16:59:32 -0700 Subject: [PATCH 1/6] wolfsshd: retry select() on EINTR in relay loop SIGCHLD from the exec child interrupts select(), returning -1 and breaking the loop. Any windowFull data awaiting a peer window adjust was then abandoned, causing intermittent 32 KB truncations on large exec transfers. Continue on EINTR. --- apps/wolfsshd/wolfsshd.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/wolfsshd/wolfsshd.c b/apps/wolfsshd/wolfsshd.c index fe0f7419a..e08cb42d5 100644 --- a/apps/wolfsshd/wolfsshd.c +++ b/apps/wolfsshd/wolfsshd.c @@ -1543,8 +1543,14 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, } rc = select((int)maxFd + 1, &readFds, &writeFds, NULL, NULL); - if (rc == -1) + if (rc == -1) { + /* Signal (e.g. SIGCHLD from child exit) interrupted select. + * Re-evaluate the loop condition so any pending windowFull + * data and remaining pipe contents still get drained. */ + if (errno == EINTR) + continue; break; + } } else { pending = 1; /* found some pending SSH data */ From 62b87e94c5205b1c6bf35496685914fec7e60510 Mon Sep 17 00:00:00 2001 From: John Safranek Date: Mon, 18 May 2026 17:00:55 -0700 Subject: [PATCH 2/6] wolfsshd: track stream for windowFull retry Stderr and stdout shared shellBuffer/windowFull, so a stderr hold retried on stdout, and a stderr partial send let the stdout read clobber the remainder. Tag the held bytes with windowFullExt; retry via extended_data_send and skip the stdout read when set. --- apps/wolfsshd/wolfsshd.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/apps/wolfsshd/wolfsshd.c b/apps/wolfsshd/wolfsshd.c index e08cb42d5..ccdadebf8 100644 --- a/apps/wolfsshd/wolfsshd.c +++ b/apps/wolfsshd/wolfsshd.c @@ -1234,6 +1234,10 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, int windowFull = 0; /* Contains size of bytes from shellBuffer that did * not get passed on to wolfSSH yet. This happens * with window full errors or when rekeying. */ + int windowFullExt = 0; /* Nonzero when the bytes held in shellBuffer + * belong to the extended (stderr) data stream + * and must be resent with + * wolfSSH_extended_data_send(). */ int wantWrite = 0; int peerConnected = 1; int stdoutEmpty = 0; @@ -1629,8 +1633,14 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, /* if the window was previously full, try resending the data */ if (windowFull) { - cnt_w = wolfSSH_ChannelIdSend(ssh, shellChannelId, - shellBuffer, windowFull); + if (windowFullExt) { + cnt_w = wolfSSH_extended_data_send(ssh, shellBuffer, + windowFull); + } + else { + cnt_w = wolfSSH_ChannelIdSend(ssh, shellChannelId, + shellBuffer, windowFull); + } if (cnt_w == WS_WINDOW_FULL || cnt_w == WS_REKEYING) { continue; } @@ -1646,6 +1656,7 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, } if (windowFull < 0) windowFull = 0; + windowFullExt = 0; } } @@ -1666,12 +1677,17 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, cnt_r); if (cnt_w > 0 && cnt_w < cnt_r) { /* partial send */ windowFull = cnt_r - cnt_w; + windowFullExt = 1; WMEMMOVE(shellBuffer, shellBuffer + cnt_w, windowFull); + /* don't let the stdout read below trample the + * buffered stderr remainder */ + continue; } else if (cnt_w == WS_WINDOW_FULL || cnt_w == WS_REKEYING) { windowFull = cnt_r; /* save amount to be sent */ + windowFullExt = 1; continue; } else if (cnt_w == WS_WANT_WRITE) { From 795b85e9eb04bf75e3875b5c43503055a1cc8737 Mon Sep 17 00:00:00 2001 From: John Safranek Date: Mon, 1 Jun 2026 10:30:14 -0700 Subject: [PATCH 3/6] wolfsshd: save shellBuffer on WS_WANT_WRITE ChannelIdSend / extended_data_send returning WS_WANT_WRITE set wantWrite but didn't save cnt_r, so the next read overwrote the held bytes. Store cnt_r in windowFull at all three read sites (stderr, stdout, pty childFd); flag windowFullExt for stderr so the retry routes through extended_data_send. --- apps/wolfsshd/wolfsshd.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/wolfsshd/wolfsshd.c b/apps/wolfsshd/wolfsshd.c index ccdadebf8..ccc668115 100644 --- a/apps/wolfsshd/wolfsshd.c +++ b/apps/wolfsshd/wolfsshd.c @@ -1691,6 +1691,8 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, continue; } else if (cnt_w == WS_WANT_WRITE) { + windowFull = cnt_r; + windowFullExt = 1; wantWrite = 1; continue; } @@ -1729,6 +1731,7 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, continue; } else if (cnt_w == WS_WANT_WRITE) { + windowFull = cnt_r; wantWrite = 1; continue; } @@ -1765,6 +1768,7 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, continue; } else if (cnt_w == WS_WANT_WRITE) { + windowFull = cnt_r; wantWrite = 1; continue; } From cfa504d349fbc65fac52581292b8b1af5f67fba4 Mon Sep 17 00:00:00 2001 From: John Safranek Date: Mon, 1 Jun 2026 16:11:40 -0700 Subject: [PATCH 4/6] wolfsshd: harden windowFull retry - Guard the retry against a negative cnt_w so a non-sentinel send error cannot become a negative memmove offset. - Clear windowFullExt at the stdout and childFd save sites so stream ownership is set unconditionally. --- apps/wolfsshd/wolfsshd.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/wolfsshd/wolfsshd.c b/apps/wolfsshd/wolfsshd.c index ccc668115..8bdd06d4b 100644 --- a/apps/wolfsshd/wolfsshd.c +++ b/apps/wolfsshd/wolfsshd.c @@ -1648,6 +1648,10 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, wantWrite = 1; continue; } + else if (cnt_w < 0) { + kill(childPid, SIGINT); + break; + } else { windowFull -= cnt_w; if (windowFull > 0) { @@ -1722,16 +1726,19 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, shellBuffer, cnt_r); if (cnt_w > 0 && cnt_w < cnt_r) { /* partial send */ windowFull = cnt_r - cnt_w; + windowFullExt = 0; WMEMMOVE(shellBuffer, shellBuffer + cnt_w, windowFull); } else if (cnt_w == WS_WINDOW_FULL || cnt_w == WS_REKEYING) { windowFull = cnt_r; /* save amount to be sent */ + windowFullExt = 0; continue; } else if (cnt_w == WS_WANT_WRITE) { windowFull = cnt_r; + windowFullExt = 0; wantWrite = 1; continue; } @@ -1759,16 +1766,19 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, shellBuffer, cnt_r); if (cnt_w > 0 && cnt_w < cnt_r) { /* partial send */ windowFull = cnt_r - cnt_w; + windowFullExt = 0; WMEMMOVE(shellBuffer, shellBuffer + cnt_w, windowFull); } else if (cnt_w == WS_WINDOW_FULL || cnt_w == WS_REKEYING) { windowFull = cnt_r; + windowFullExt = 0; continue; } else if (cnt_w == WS_WANT_WRITE) { windowFull = cnt_r; + windowFullExt = 0; wantWrite = 1; continue; } From 0602a7185dc61adbdf017652e2e18f05e2fa1fe7 Mon Sep 17 00:00:00 2001 From: John Safranek Date: Tue, 2 Jun 2026 10:18:25 -0700 Subject: [PATCH 5/6] wolfsshd: set stdoutEmpty on PTY EOF Lets the relay loop reach its shutdown condition after SIGCHLD instead of relying on a -1/EIO break. --- apps/wolfsshd/wolfsshd.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/wolfsshd/wolfsshd.c b/apps/wolfsshd/wolfsshd.c index 8bdd06d4b..5da795219 100644 --- a/apps/wolfsshd/wolfsshd.c +++ b/apps/wolfsshd/wolfsshd.c @@ -1753,13 +1753,16 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, else { if (FD_ISSET(childFd, &readFds)) { cnt_r = (int)read(childFd, shellBuffer, sizeof shellBuffer); - /* This read will return 0 on EOF */ - if (cnt_r <= 0) { + /* Treat a 0 return as EOF so the loop can shut down. */ + if (cnt_r < 0) { int err = errno; if (err != EAGAIN && err != 0) { break; } } + else if (cnt_r == 0) { + stdoutEmpty = 1; + } else { if (cnt_r > 0) { cnt_w = wolfSSH_ChannelIdSend(ssh, shellChannelId, From 2550828e195741073434b574067b50d31a3e37c0 Mon Sep 17 00:00:00 2001 From: John Safranek Date: Tue, 2 Jun 2026 10:36:43 -0700 Subject: [PATCH 6/6] wolfsshd: fix data loss in Windows shell relay - buffer unsent bytes on backpressure/partial send and resend - hold loop open until buffered data flushes after child exit --- apps/wolfsshd/wolfsshd.c | 47 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/apps/wolfsshd/wolfsshd.c b/apps/wolfsshd/wolfsshd.c index 5da795219..0d1785d08 100644 --- a/apps/wolfsshd/wolfsshd.c +++ b/apps/wolfsshd/wolfsshd.c @@ -831,6 +831,10 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, #endif byte shellBuffer[WOLFSSHD_SHELL_BUFFER_SZ]; int cnt_r, cnt_w; + int windowFull = 0; /* bytes left in shellBuffer that a prior send could + * not pass on to wolfSSH yet. This happens with window + * full, rekey, or want-write; resent before reading + * more so the buffered data is not overwritten. */ HANDLE ptyIn = NULL, ptyOut = NULL; HANDLE cnslIn = NULL, cnslOut = NULL; STARTUPINFOEX ext; @@ -1063,7 +1067,10 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, continue; } } - break; + /* keep looping while buffered data still needs to be + * flushed to the SSH channel */ + if (!windowFull) + break; } } if (wolfSSH_stream_peek(ssh, tmp, 1) <= 0) { @@ -1123,7 +1130,30 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, } } - if (readPending) { + /* if a previous send could not complete, resend the buffered data + * before reading more so shellBuffer is not overwritten */ + if (windowFull) { + cnt_w = wolfSSH_ChannelIdSend(ssh, shellChannelId, + shellBuffer, windowFull); + if (cnt_w == WS_WINDOW_FULL || cnt_w == WS_REKEYING || + cnt_w == WS_WANT_WRITE) { + continue; + } + else if (cnt_w < 0) { + break; + } + else { + windowFull -= cnt_w; + if (windowFull > 0) { + WMEMMOVE(shellBuffer, shellBuffer + cnt_w, windowFull); + continue; + } + if (windowFull < 0) + windowFull = 0; + } + } + + if (readPending && !windowFull) { WMEMSET(shellBuffer, 0, WOLFSSHD_SHELL_BUFFER_SZ); if (ReadFile(ptyOut, shellBuffer, WOLFSSHD_SHELL_BUFFER_SZ, &cnt_r, @@ -1137,8 +1167,19 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, if (cnt_r > 0) { cnt_w = wolfSSH_ChannelIdSend(ssh, shellChannelId, shellBuffer, cnt_r); - if (cnt_w < 0) + if (cnt_w > 0 && cnt_w < cnt_r) { /* partial send */ + windowFull = cnt_r - cnt_w; + WMEMMOVE(shellBuffer, shellBuffer + cnt_w, + windowFull); + } + else if (cnt_w == WS_WINDOW_FULL || + cnt_w == WS_REKEYING || + cnt_w == WS_WANT_WRITE) { + windowFull = cnt_r; /* save amount to be sent */ + } + else if (cnt_w < 0) { break; + } } } }