Skip to content

Commit ee7fa04

Browse files
ejohnstownphilljj
authored andcommitted
ssh: flush any queued output at teardown
wolfSSH_shutdown() flushes whatever a short send left in the output buffer, not just a queued disconnect. A rejected auth's USERAUTH_FAILURE has no channel, and DoChannelClose() retires the channel as soon as the close is bundled, so neither had anything left to carry the retry. - The gate still refuses a flush once the peer has disconnected, unless our own disconnect is the thing queued. - A flush that finishes on a live session clears the WS_WANT_WRITE the short send latched, as the disconnected path already did. - ssh.h documents the widened flush and the WS_WANT_WRITE a short one leaves wolfSSH_shutdown() returning. - Tests cover the rejected auth with no channel and the close whose channel DoChannelClose() already retired. - A flush that short-sends on a live channel leaves the teardown queued behind it. The retry adds no second EOF or close, and skips the close wait while its own flush is still short. - A teardown send that drained the leftovers settles the flush; one that failed outranks it, since a reset leaves the buffer intact.
1 parent 54610fd commit ee7fa04

3 files changed

Lines changed: 515 additions & 29 deletions

File tree

src/ssh.c

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,17 +1149,20 @@ static int SendAfterDisconnect(WOLFSSH* ssh)
11491149
}
11501150

11511151

1152-
/* A disconnect of ours left queued by a short send still has to reach the
1152+
/* Whatever a short send left in the output buffer still has to reach the
11531153
* peer, and flushing bytes that are already bundled is not the new traffic
1154-
* RFC 4253 section 11.1 forbids. Only our own disconnect qualifies: a
1155-
* disconnect from the peer leaves nothing queued but unrelated traffic,
1156-
* which the session is over for. Call only after a NULL check of ssh. */
1157-
static int FlushQueuedDisconnect(WOLFSSH* ssh)
1154+
* RFC 4253 section 11.1 forbids. Once the peer has disconnected, though,
1155+
* only our own queued disconnect still qualifies: anything else in there
1156+
* belongs to a session that is over. Call only after a NULL check of ssh. */
1157+
static int FlushQueuedOutput(WOLFSSH* ssh)
11581158
{
1159-
if (!ssh->disconnectTxd || !wolfSSH_OutputPending(ssh))
1159+
if (!wolfSSH_OutputPending(ssh))
11601160
return 0;
11611161

1162-
WLOG(WS_LOG_DEBUG, "Flushing a disconnect left queued by a short send");
1162+
if (ssh->disconnected && !ssh->disconnectTxd)
1163+
return 0;
1164+
1165+
WLOG(WS_LOG_DEBUG, "Flushing output left queued by a short send");
11631166
return 1;
11641167
}
11651168

@@ -1168,17 +1171,22 @@ int wolfSSH_shutdown(WOLFSSH* ssh)
11681171
{
11691172
int ret = WS_SUCCESS;
11701173
int flushRet = WS_SUCCESS;
1174+
int flushed = 0;
11711175
WOLFSSH_CHANNEL* channel = NULL;
11721176

11731177
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_shutdown()");
11741178

11751179
if (ssh == NULL)
11761180
ret = WS_BAD_ARGUMENT;
11771181

1178-
/* This is a teardown call, so a disconnect of ours left queued by a
1179-
* short send goes out here, with or without a channel to tear down. */
1180-
if (ret == WS_SUCCESS && FlushQueuedDisconnect(ssh))
1182+
/* This is a teardown call, so anything a short send left queued goes out
1183+
* here, with or without a channel to tear down. A rejected auth's
1184+
* USERAUTH_FAILURE has no channel, and a channel close is retired off
1185+
* the channel the moment it is bundled. */
1186+
if (ret == WS_SUCCESS && FlushQueuedOutput(ssh)) {
11811187
flushRet = wolfSSH_SendPacket(ssh);
1188+
flushed = flushRet == WS_SUCCESS;
1189+
}
11821190

11831191
if (ret == WS_SUCCESS && ssh->channelList == NULL)
11841192
ret = WS_BAD_ARGUMENT;
@@ -1202,6 +1210,13 @@ int wolfSSH_shutdown(WOLFSSH* ssh)
12021210
if (ssh != NULL && ssh->disconnected && flushRet == WS_SUCCESS)
12031211
ssh->error = WS_DISCONNECT;
12041212

1213+
/* A live session has no WS_DISCONNECT to displace that stale error with,
1214+
* and the widened flush reaches sessions that are still up. The write the
1215+
* short send latched WS_WANT_WRITE for is the one that just finished, so
1216+
* it is not owed twice. */
1217+
else if (flushed && ssh->error == WS_WANT_WRITE)
1218+
ssh->error = WS_SUCCESS;
1219+
12051220
/* if channel close was not already sent then send it */
12061221
if (channel != NULL && !channel->closeTxd) {
12071222
if (ret == WS_SUCCESS) {
@@ -1227,8 +1242,11 @@ int wolfSSH_shutdown(WOLFSSH* ssh)
12271242

12281243

12291244
/* if the channel was not yet removed then read to get
1230-
* response to SendChannelClose */
1231-
if (channel != NULL && ret == WS_SUCCESS) {
1245+
* response to SendChannelClose. Not while the flush left output queued:
1246+
* the peer cannot answer a close it has not finished receiving, and the
1247+
* worker has nothing to read, so a want-read from it would send the
1248+
* caller to wait on the wrong side of the socket. */
1249+
if (channel != NULL && ret == WS_SUCCESS && !wolfSSH_OutputPending(ssh)) {
12321250
ret = wolfSSH_worker(ssh, NULL);
12331251
if (ret == WS_CHAN_RXD || ret == WS_EOF) {
12341252
/* received response */
@@ -1242,8 +1260,16 @@ int wolfSSH_shutdown(WOLFSSH* ssh)
12421260
}
12431261

12441262
/* An unfinished flush outranks the channel status: the caller has to
1245-
* come back for the rest of the disconnect. */
1246-
if (flushRet != WS_SUCCESS)
1263+
* come back for the rest of the disconnect. Not so once a teardown send
1264+
* has carried the leftovers out with it: that write is settled, and the
1265+
* teardown result stands. Nor does it outrank a teardown send that
1266+
* failed: a reset leaves the buffer intact, so the flush still reads as
1267+
* owed while the send holds the real reason. A flush that failed
1268+
* outright reports whatever else happened. */
1269+
if (flushRet != WS_SUCCESS &&
1270+
(flushRet != WS_WANT_WRITE ||
1271+
(wolfSSH_OutputPending(ssh) &&
1272+
(ret == WS_SUCCESS || ret == WS_CHANNEL_CLOSED))))
12471273
ret = flushRet;
12481274

12491275
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_shutdown(), ret = %d", ret);
@@ -1760,7 +1786,7 @@ int wolfSSH_SendDisconnect(WOLFSSH *ssh, word32 reason)
17601786
* connection that is already over. A short send leaves the first one
17611787
* queued, though, so that retry goes through. */
17621788
if (SendAfterDisconnect(ssh)) {
1763-
if (FlushQueuedDisconnect(ssh))
1789+
if (FlushQueuedOutput(ssh))
17641790
return wolfSSH_SendPacket(ssh);
17651791
return WS_FATAL_ERROR;
17661792
}

0 commit comments

Comments
 (0)