Skip to content

Commit 0500a6c

Browse files
committed
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, and the retry adds no second EOF or close. - A teardown send that drained the leftovers settles the flush; one that failed outranks it, since a reset leaves the buffer intact.
1 parent 4ee445d commit 0500a6c

3 files changed

Lines changed: 448 additions & 13 deletions

File tree

src/ssh.c

Lines changed: 36 additions & 13 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) {
@@ -1242,8 +1257,16 @@ int wolfSSH_shutdown(WOLFSSH* ssh)
12421257
}
12431258

12441259
/* 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)
1260+
* come back for the rest of the disconnect. Not so once a teardown send
1261+
* has carried the leftovers out with it: that write is settled, and the
1262+
* teardown result stands. Nor does it outrank a teardown send that
1263+
* failed: a reset leaves the buffer intact, so the flush still reads as
1264+
* owed while the send holds the real reason. A flush that failed
1265+
* outright reports whatever else happened. */
1266+
if (flushRet != WS_SUCCESS &&
1267+
(flushRet != WS_WANT_WRITE ||
1268+
(wolfSSH_OutputPending(ssh) &&
1269+
(ret == WS_SUCCESS || ret == WS_CHANNEL_CLOSED))))
12471270
ret = flushRet;
12481271

12491272
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_shutdown(), ret = %d", ret);
@@ -1760,7 +1783,7 @@ int wolfSSH_SendDisconnect(WOLFSSH *ssh, word32 reason)
17601783
* connection that is already over. A short send leaves the first one
17611784
* queued, though, so that retry goes through. */
17621785
if (SendAfterDisconnect(ssh)) {
1763-
if (FlushQueuedDisconnect(ssh))
1786+
if (FlushQueuedOutput(ssh))
17641787
return wolfSSH_SendPacket(ssh);
17651788
return WS_FATAL_ERROR;
17661789
}

0 commit comments

Comments
 (0)