mirror of https://github.com/wolfSSL/wolfssh.git
Carry the EOF status through SFTP and SCP
wolfSSH_SFTP_buffer_send() and ScpStreamSend() keep driving the worker when the peer half-closes. Both return any negative status, so a WS_EOF would have aborted a transfer that is still perfectly able to finish: the peer closed its sending direction, not ours. - wolfSSH_SFTP_buffer_read() reports every negative peek but a rekey instead of spending a receive on it. A drained channel at EOF, a dead session and a channel that is gone all mean no more data can arrive, and the poll only overwrites the latched cause with WS_WANT_READ or blocks on a peer that has hung up. - A rekey is not a drained channel: peek reports it before it looks at the buffer at all, so that one still needs the poll. - DoScpRequest() reads its own EOF case the same way as the rest.pull/1218/head
parent
74098863ad
commit
2bfdbac2b3
|
|
@ -151,9 +151,10 @@ static int ScpStreamSend(WOLFSSH* ssh, byte* data, word32 sz)
|
|||
if (err == WS_WANT_READ || err == WS_WANT_WRITE)
|
||||
return err;
|
||||
/* Only a rekey/window/channel-data status means "keep driving".
|
||||
* Any other negative status is fatal and returned. */
|
||||
* Any other negative status is fatal and returned. A peer EOF
|
||||
* closes their direction only, and is raised once. */
|
||||
if (ret < 0 && ret != WS_REKEYING && ret != WS_WINDOW_FULL
|
||||
&& ret != WS_CHAN_RXD)
|
||||
&& ret != WS_CHAN_RXD && ret != WS_EOF)
|
||||
return ret;
|
||||
/* otherwise loop and retry the send, which clears the status */
|
||||
}
|
||||
|
|
@ -944,15 +945,13 @@ int DoScpRequest(WOLFSSH* ssh)
|
|||
/* Peer MUST send back a SSH_MSG_CHANNEL_CLOSE unless already
|
||||
sent*/
|
||||
ret = ScpStreamRead(ssh, buf, 1);
|
||||
if (ret == WS_SOCKET_ERROR_E || ret == WS_CHANNEL_CLOSED) {
|
||||
if (ret == WS_SOCKET_ERROR_E || ret == WS_CHANNEL_CLOSED
|
||||
|| ret == WS_EOF) {
|
||||
WLOG(WS_LOG_DEBUG, scpState, "Peer hung up, but SCP is done");
|
||||
ret = WS_SUCCESS;
|
||||
}
|
||||
else if (ret != WS_EOF) {
|
||||
WLOG(WS_LOG_DEBUG, scpState, "Did not receive EOF packet");
|
||||
}
|
||||
else {
|
||||
ret = WS_SUCCESS;
|
||||
WLOG(WS_LOG_DEBUG, scpState, "Did not receive EOF packet");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1784,7 +1783,7 @@ int ReceiveScpMessage(WOLFSSH* ssh)
|
|||
}
|
||||
}
|
||||
|
||||
/* check if wolfSSH_worker returns 0 from handling a channel eof */
|
||||
/* Already at EOF, and the worker had nothing else to report. */
|
||||
if (err == 0) {
|
||||
WOLFSSH_CHANNEL* channel;
|
||||
channel = wolfSSH_ChannelFind(ssh, lastChannel, WS_CHANNEL_ID_SELF);
|
||||
|
|
|
|||
|
|
@ -593,9 +593,9 @@ static int wolfSSH_SFTP_buffer_send(WOLFSSH* ssh, WS_SFTP_BUFFER* buffer)
|
|||
/* Only a rekey/window/channel-data status means "keep driving". Any
|
||||
* other negative status (fatal error, or WS_WANT_READ/WS_WANT_WRITE on
|
||||
* a non-blocking socket) is returned so a stalled or dead rekey cannot
|
||||
* spin forever. */
|
||||
* spin forever. A peer EOF ends their direction only, not ours. */
|
||||
if (ret < 0 && ret != WS_REKEYING && ret != WS_WINDOW_FULL
|
||||
&& ret != WS_CHAN_RXD) {
|
||||
&& ret != WS_CHAN_RXD && ret != WS_EOF) {
|
||||
return ret;
|
||||
}
|
||||
err = wolfSSH_get_error(ssh);
|
||||
|
|
@ -720,6 +720,7 @@ static int wolfSSH_SFTP_buffer_read(WOLFSSH* ssh, WS_SFTP_BUFFER* buffer,
|
|||
{
|
||||
int ret;
|
||||
int polled;
|
||||
int peekRet;
|
||||
byte peekBuf[1];
|
||||
|
||||
if (buffer == NULL || ssh == NULL) {
|
||||
|
|
@ -759,7 +760,18 @@ static int wolfSSH_SFTP_buffer_read(WOLFSSH* ssh, WS_SFTP_BUFFER* buffer,
|
|||
}
|
||||
}
|
||||
|
||||
if (!wolfSSH_stream_peek(ssh, peekBuf, 1)) {
|
||||
peekRet = wolfSSH_stream_peek(ssh, peekBuf, 1);
|
||||
|
||||
/* Every negative peek but a rekey means no more data can arrive:
|
||||
* drained and at EOF, session gone, or channel gone. Polling would
|
||||
* only overwrite the cause with WS_WANT_READ or block on a dead
|
||||
* peer. A rekey is not a drained channel, so it still polls. */
|
||||
if (peekRet < 0 && peekRet != WS_REKEYING) {
|
||||
return WS_FATAL_ERROR;
|
||||
}
|
||||
|
||||
/* Nothing buffered. Poll for the real status. */
|
||||
if (peekRet <= 0) {
|
||||
/* poll more data off the wire */
|
||||
ret = wolfSSH_worker(ssh, NULL);
|
||||
polled = 1;
|
||||
|
|
|
|||
73
tests/unit.c
73
tests/unit.c
|
|
@ -6956,6 +6956,72 @@ done:
|
|||
}
|
||||
|
||||
|
||||
#ifdef WOLFSSH_SFTP
|
||||
static int s_recvCalls = 0;
|
||||
|
||||
/* Counts the polls, so a test can prove the wire was never touched. */
|
||||
static int CountingIoRecv(WOLFSSH* ssh, void* buf, word32 sz, void* ctx)
|
||||
{
|
||||
WOLFSSH_UNUSED(ssh);
|
||||
WOLFSSH_UNUSED(buf);
|
||||
WOLFSSH_UNUSED(sz);
|
||||
WOLFSSH_UNUSED(ctx);
|
||||
|
||||
s_recvCalls++;
|
||||
return WS_CBIO_ERR_WANT_READ;
|
||||
}
|
||||
|
||||
/* The SFTP read must not poll a channel that has latched its EOF and been
|
||||
* drained. No further data can arrive, so the poll cannot finish the message
|
||||
* and only overwrites the latched WS_EOF with WS_WANT_READ, which the SFTP
|
||||
* loops read as "come back later" -- or blocks outright on a blocking
|
||||
* socket. */
|
||||
static int test_SftpReadEofNoPoll(void)
|
||||
{
|
||||
WOLFSSH_CTX* ctx = NULL;
|
||||
WOLFSSH* ssh = NULL;
|
||||
WOLFSSH_CHANNEL* ch = NULL;
|
||||
int result = 0;
|
||||
int ret;
|
||||
|
||||
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
|
||||
if (ctx == NULL)
|
||||
return -1620;
|
||||
wolfSSH_SetIOSend(ctx, DiscardIoSend);
|
||||
wolfSSH_SetIORecv(ctx, CountingIoRecv);
|
||||
|
||||
ssh = wolfSSH_new(ctx);
|
||||
if (ssh == NULL) { result = -1621; goto done; }
|
||||
ssh->acceptState = ACCEPT_SERVER_USERAUTH_SENT;
|
||||
|
||||
ch = ChannelNew(ssh, ID_CHANTYPE_SESSION, 1024, 1024);
|
||||
if (ch == NULL) { result = -1622; goto done; }
|
||||
if (ChannelAppend(ssh, ch) != WS_SUCCESS) {
|
||||
ChannelDelete(ch, ssh->ctx->heap);
|
||||
result = -1623;
|
||||
goto done;
|
||||
}
|
||||
ch->openConfirmed = 1;
|
||||
ch->peerWindowSz = 1024;
|
||||
ch->peerMaxPacketSz = 1024;
|
||||
|
||||
/* The peer half-closed and everything it sent has been read. */
|
||||
ch->eofRxd = 1;
|
||||
s_recvCalls = 0;
|
||||
|
||||
ret = wolfSSH_SFTP_read(ssh);
|
||||
if (ret != WS_FATAL_ERROR) { result = -1624; goto done; }
|
||||
if (wolfSSH_get_error(ssh) != WS_EOF) { result = -1625; goto done; }
|
||||
if (s_recvCalls != 0) { result = -1626; goto done; }
|
||||
|
||||
done:
|
||||
s_recvCalls = 0;
|
||||
wolfSSH_free(ssh);
|
||||
wolfSSH_CTX_free(ctx);
|
||||
return result;
|
||||
}
|
||||
#endif /* WOLFSSH_SFTP */
|
||||
|
||||
|
||||
/* DoReceive() can retire the head channel mid-read: DoChannelClose() frees it
|
||||
* while wolfSSH_stream_read() still holds its inputBuffer. If the next head
|
||||
|
|
@ -20002,6 +20068,13 @@ int wolfSSH_UnitTest(int argc, char** argv)
|
|||
(unitResult == 0 ? "SUCCESS" : "FAILED"));
|
||||
testResult = testResult || unitResult;
|
||||
|
||||
#ifdef WOLFSSH_SFTP
|
||||
unitResult = test_SftpReadEofNoPoll();
|
||||
printf("SftpReadEofNoPoll: %s\n",
|
||||
(unitResult == 0 ? "SUCCESS" : "FAILED"));
|
||||
testResult = testResult || unitResult;
|
||||
#endif
|
||||
|
||||
unitResult = test_AcceptSurvivesChannelEof();
|
||||
printf("AcceptSurvivesChannelEof: %s\n",
|
||||
(unitResult == 0 ? "SUCCESS" : "FAILED"));
|
||||
|
|
|
|||
Loading…
Reference in New Issue