-
Notifications
You must be signed in to change notification settings - Fork 119
Shutdown channel lookup and disconnect state #1190
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
d4bf5e0
76a7291
12b8f53
ad66ac0
e2e820a
87327f7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1103,6 +1103,21 @@ int wolfSSH_connect(WOLFSSH* ssh) | |
| #endif /* NO_WOLFSSH_CLIENT */ | ||
|
|
||
|
|
||
| /* A disconnect, sent or received, ends the session, so nothing further may | ||
| * go out. RFC 4253 section 11.1. Reads are deliberately not gated on this: | ||
| * channel data that arrived before the disconnect is still the caller's. | ||
| * Call only after ssh has been checked for NULL. */ | ||
| static int SendAfterDisconnect(WOLFSSH* ssh) | ||
| { | ||
| if (ssh->disconnected) { | ||
| WLOG(WS_LOG_DEBUG, "Send attempted after a disconnect"); | ||
| ssh->error = WS_DISCONNECT; | ||
| return 1; | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
|
|
||
| int wolfSSH_shutdown(WOLFSSH* ssh) | ||
| { | ||
| int ret = WS_SUCCESS; | ||
|
|
@@ -1113,9 +1128,17 @@ int wolfSSH_shutdown(WOLFSSH* ssh) | |
| if (ssh == NULL || ssh->channelList == NULL) | ||
| ret = WS_BAD_ARGUMENT; | ||
|
|
||
| /* look up the channel if it still exists */ | ||
| /* The session channel is the head of the list. */ | ||
| if (ret == WS_SUCCESS) { | ||
| channel = ChannelFind(ssh, ssh->channelList->peerChannel, WS_CHANNEL_ID_SELF); | ||
| channel = ssh->channelList; | ||
| } | ||
|
|
||
| /* Session already over. Drop the channel to skip the teardown sends | ||
| * and the wait for a close that will not come. RFC 4253 section 11.1. */ | ||
| if (channel != NULL && ssh->disconnected) { | ||
| WLOG(WS_LOG_DEBUG, "Session already disconnected, nothing to send"); | ||
| ssh->error = WS_DISCONNECT; | ||
| channel = NULL; | ||
| } | ||
|
|
||
| /* if channel close was not already sent then send it */ | ||
|
|
@@ -1170,6 +1193,9 @@ int wolfSSH_TriggerKeyExchange(WOLFSSH* ssh) | |
| if (ssh == NULL) | ||
| ret = WS_BAD_ARGUMENT; | ||
|
|
||
| if (ret == WS_SUCCESS && SendAfterDisconnect(ssh)) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gating wolfSSH_TriggerKeyExchange() makes the default highwater callback fail every send after a disconnect · Incorrect error handling
Related known finding #8817 (similar but distinct): Both can cause wolfSSH_shutdown() to skip EOF/exit/close traffic, but this finding faults wolfSSH_TriggerKeyExchange() returning WS_FATAL_ERROR after disconnect through the highwater callback; #8817 faults wolfSSH_shutdown() using a peer ID with SELF lookup. Their root causes and required patches are separate. Fix: Return WS_SUCCESS (skip the rekey) instead of WS_FATAL_ERROR when |
||
| ret = WS_FATAL_ERROR; | ||
|
|
||
| if (ret == WS_SUCCESS) | ||
| ret = ssh->error = SendKexInit(ssh); | ||
|
|
||
|
|
@@ -1183,6 +1209,7 @@ int wolfSSH_TriggerKeyExchange(WOLFSSH* ssh) | |
| int wolfSSH_stream_peek(WOLFSSH* ssh, byte* buf, word32 bufSz) | ||
| { | ||
| WOLFSSH_BUFFER* inputBuffer; | ||
| word32 avail; | ||
|
|
||
| WLOG(WS_LOG_DEBUG, "Entering wolfSSH_stream_peek()"); | ||
|
|
||
|
|
@@ -1199,11 +1226,22 @@ int wolfSSH_stream_peek(WOLFSSH* ssh, byte* buf, word32 bufSz) | |
| } | ||
|
|
||
| inputBuffer = &ssh->channelList->inputBuffer; | ||
| bufSz = min(bufSz, inputBuffer->length - inputBuffer->idx); | ||
| avail = inputBuffer->length - inputBuffer->idx; | ||
|
|
||
| /* Report the disconnect only once the buffered data is drained, the | ||
| * same way wolfSSH_stream_read() does. Callers use this to tell a | ||
| * drained channel from one with more to come, and a dead session is | ||
| * neither. */ | ||
| if (avail == 0 && ssh->disconnected) { | ||
| ssh->error = WS_DISCONNECT; | ||
| return WS_FATAL_ERROR; | ||
| } | ||
|
|
||
| bufSz = min(bufSz, avail); | ||
| if (buf != NULL) { | ||
| WMEMCPY(buf, inputBuffer->buffer + inputBuffer->idx, bufSz); | ||
| } | ||
| return bufSz; | ||
| return (int)bufSz; | ||
| } | ||
|
|
||
|
|
||
|
|
@@ -1228,8 +1266,17 @@ int wolfSSH_stream_read(WOLFSSH* ssh, byte* buf, word32 bufSz) | |
|
|
||
| WLOG(WS_LOG_DEBUG, "Entering wolfSSH_stream_read()"); | ||
|
|
||
| if (ssh == NULL || buf == NULL || bufSz == 0 || ssh->channelList == NULL) | ||
| if (ssh == NULL || buf == NULL || bufSz == 0) | ||
| return WS_BAD_ARGUMENT; | ||
|
|
||
| if (ssh->channelList == NULL) { | ||
| /* No channel left to drain, so the disconnect is all there is. */ | ||
| if (ssh->disconnected) { | ||
| ssh->error = WS_DISCONNECT; | ||
| return WS_FATAL_ERROR; | ||
| } | ||
| return WS_BAD_ARGUMENT; | ||
| } | ||
|
|
||
| if (ssh->channelList->eofRxd) { | ||
| ssh->error = WS_EOF; | ||
|
|
@@ -1244,6 +1291,13 @@ int wolfSSH_stream_read(WOLFSSH* ssh, byte* buf, word32 bufSz) | |
| inputBuffer = &ssh->channelList->inputBuffer; | ||
| ssh->error = WS_SUCCESS; | ||
|
|
||
| /* Hand back whatever arrived before the disconnect, then report it once | ||
| * the buffer runs dry rather than going back to a dead transport. */ | ||
| if (ssh->disconnected && inputBuffer->length - inputBuffer->idx == 0) { | ||
| ssh->error = WS_DISCONNECT; | ||
| return WS_FATAL_ERROR; | ||
| } | ||
|
|
||
| if (ret == WS_SUCCESS) { | ||
| WLOG(WS_LOG_DEBUG, " Stream read index of %u", inputBuffer->idx); | ||
| WLOG(WS_LOG_DEBUG, " Stream read ava data %u", inputBuffer->length); | ||
|
|
@@ -1307,7 +1361,13 @@ int wolfSSH_stream_send(WOLFSSH* ssh, byte* buf, word32 bufSz) | |
|
|
||
| WLOG(WS_LOG_DEBUG, "Entering wolfSSH_stream_send()"); | ||
|
|
||
| if (ssh == NULL || buf == NULL || ssh->channelList == NULL) | ||
| if (ssh == NULL || buf == NULL) | ||
| return WS_BAD_ARGUMENT; | ||
|
|
||
| if (SendAfterDisconnect(ssh)) | ||
| return WS_FATAL_ERROR; | ||
|
|
||
| if (ssh->channelList == NULL) | ||
| return WS_BAD_ARGUMENT; | ||
|
|
||
| if (ssh->isKeying) { | ||
|
|
@@ -1334,6 +1394,9 @@ int wolfSSH_ChannelIdSend(WOLFSSH* ssh, word32 channelId, | |
| if (ssh == NULL || buf == NULL) | ||
| ret = WS_BAD_ARGUMENT; | ||
|
|
||
| if (ret == WS_SUCCESS && SendAfterDisconnect(ssh)) | ||
| ret = WS_FATAL_ERROR; | ||
|
|
||
| if (ret == WS_SUCCESS) { | ||
| channel = ChannelFind(ssh, channelId, WS_CHANNEL_ID_SELF); | ||
| if (channel == NULL) { | ||
|
|
@@ -1370,6 +1433,9 @@ int wolfSSH_ChannelIdSendExt(WOLFSSH* ssh, word32 channelId, | |
| if (ssh == NULL || buf == NULL) | ||
| ret = WS_BAD_ARGUMENT; | ||
|
|
||
| if (ret == WS_SUCCESS && SendAfterDisconnect(ssh)) | ||
| ret = WS_FATAL_ERROR; | ||
|
|
||
| if (ret == WS_SUCCESS) { | ||
| channel = ChannelFind(ssh, channelId, WS_CHANNEL_ID_SELF); | ||
| if (channel == NULL) { | ||
|
|
@@ -1403,6 +1469,9 @@ int wolfSSH_stream_exit(WOLFSSH* ssh, int status) | |
| if (ssh == NULL || ssh->channelList == NULL) | ||
| ret = WS_BAD_ARGUMENT; | ||
|
|
||
| if (ret == WS_SUCCESS && SendAfterDisconnect(ssh)) | ||
| ret = WS_FATAL_ERROR; | ||
|
|
||
| if (ret == WS_SUCCESS) | ||
| ret = SendChannelExit(ssh, ssh->channelList->peerChannel, status); | ||
|
|
||
|
|
@@ -1426,6 +1495,8 @@ int wolfSSH_global_request(WOLFSSH *ssh, const unsigned char* data, word32 dataS | |
| return WS_BAD_ARGUMENT; | ||
| if (reply != 0 && reply != 1) | ||
| return WS_BAD_ARGUMENT; | ||
| if (SendAfterDisconnect(ssh)) | ||
| return WS_FATAL_ERROR; | ||
| return SendGlobalRequest(ssh, data, dataSz, reply); | ||
| } | ||
|
|
||
|
|
@@ -1436,7 +1507,13 @@ int wolfSSH_extended_data_send(WOLFSSH* ssh, byte* buf, word32 bufSz) | |
|
|
||
| WLOG(WS_LOG_DEBUG, "Entering wolfSSH_extended_data_send()"); | ||
|
|
||
| if (ssh == NULL || buf == NULL || ssh->channelList == NULL) | ||
| if (ssh == NULL || buf == NULL) | ||
| return WS_BAD_ARGUMENT; | ||
|
|
||
| if (SendAfterDisconnect(ssh)) | ||
| return WS_FATAL_ERROR; | ||
|
|
||
| if (ssh->channelList == NULL) | ||
| return WS_BAD_ARGUMENT; | ||
|
|
||
| if (ssh->isKeying) { | ||
|
|
@@ -1476,6 +1553,13 @@ int wolfSSH_SendIgnore(WOLFSSH* ssh, const byte* buf, word32 bufSz) | |
|
|
||
| WOLFSSH_UNUSED(buf); | ||
| WOLFSSH_UNUSED(bufSz); | ||
|
|
||
| if (ssh == NULL) | ||
| return WS_BAD_ARGUMENT; | ||
|
|
||
| if (SendAfterDisconnect(ssh)) | ||
| return WS_FATAL_ERROR; | ||
|
|
||
| WMEMSET(scratch, 0, sizeof(scratch)); | ||
|
|
||
| return SendIgnore(ssh, scratch, sizeof(scratch)); | ||
|
|
@@ -1485,6 +1569,15 @@ int wolfSSH_SendIgnore(WOLFSSH* ssh, const byte* buf, word32 bufSz) | |
| int wolfSSH_SendDisconnect(WOLFSSH *ssh, word32 reason) | ||
| { | ||
| WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SendDisconnect"); | ||
|
|
||
| if (ssh == NULL) | ||
| return WS_BAD_ARGUMENT; | ||
|
|
||
| /* One disconnect ends the session; a second is more traffic on a | ||
| * connection that is already over. */ | ||
| if (SendAfterDisconnect(ssh)) | ||
| return WS_FATAL_ERROR; | ||
|
|
||
| return SendDisconnect(ssh, reason); | ||
| } | ||
|
|
||
|
|
@@ -1586,6 +1679,9 @@ int wolfSSH_ChangeTerminalSize(WOLFSSH* ssh, word32 columns, word32 rows, | |
| if (ssh == NULL) | ||
| ret = WS_BAD_ARGUMENT; | ||
|
|
||
| if (ret == WS_SUCCESS && SendAfterDisconnect(ssh)) | ||
| ret = WS_FATAL_ERROR; | ||
|
|
||
| if (ret == WS_SUCCESS) { | ||
| ret = SendChannelTerminalResize(ssh, columns, rows, widthPixels, | ||
| heightPixels); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wolfSSH_shutdown() transmits and drains after a disconnect · SSH protocol violations
Taking the head channel directly makes the send block at
src/ssh.c:1137always execute, butwolfSSH_shutdown()is the one send path this PR leaves ungated byssh->disconnected. AfterDoDisconnect()it emits CHANNEL_EOF, exit-status and CHANNEL_CLOSE (RFC 4253 §11.1 forbids sending after SSH_MSG_DISCONNECT), then waits inwolfSSH_worker()atsrc/ssh.c:1163for a close the peer will not send. Reached in-tree fromexamples/echoserver/echoserver.c:1640, whereerror == WS_DISCONNECTpasses the guard at line 1639.Related known finding #8817 (similar but distinct): Both affect wolfSSH_shutdown() channel teardown sends and drain logic, but #8817 faults in ChannelFind using peerChannel with WS_CHANNEL_ID_SELF, while this candidate faults by allowing sends/draining after ssh->disconnected. Their root causes and required patches differ.
Fix: Apply the
SendAfterDisconnect()gate inwolfSSH_shutdown()so the channel teardown sends and the drain are skipped oncessh->disconnectedis set.