mirror of https://github.com/wolfSSL/wolfssh.git
Block every send after a disconnect
The disconnect flag gated wolfSSH_stream_read() and wolfSSH_stream_send(), which is the client-side API. wolfsshd and echoserver drive their channels through the channel-id calls, so the daemon was never gated at all. - New SendAfterDisconnect() helper, used by the six send entry points: stream_send, stream_exit, ChannelIdSend, ChannelIdSendExt, extended_data_send and global_request. - Reads stay open, since data that arrived before the disconnect is still the caller's. wolfSSH_stream_read() drains its buffer and reports WS_DISCONNECT only once it runs dry. - wolfSSH_worker() stays ungated; the shutdown paths still pump it. - ssh.h and internal.h describe the split. - regress.c: buffered data survives the disconnect, and every send call refuses without a byte leaving the session. Issue: F-8837 Every public send call means every one: the channel-pointer sends (wolfSSH_ChannelSend, wolfSSH_ChannelSendExt, wolfSSH_ChannelExit), the forwarding requests and both wolfSSH_ChannelFwdNew* opens carry the gate too, and none of them had a message-filter backstop. ChannelCreditWindow() parks its credit rather than sending. The reads that drain what arrived before the disconnect credit the window for the bytes taken, and that credit went straight to the transport: each drain put a CHANNEL_WINDOW_ADJUST on the wire after the session was over, and a failing send replaced the byte count already copied for the caller.pull/1192/head
parent
e6b324d14f
commit
285e0409cf
|
|
@ -7635,8 +7635,9 @@ static int DoKexDhReply(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
|
|||
/* Returns amount bytes of receive-window credit to the peer, folding in credit
|
||||
* already parked on the channel. Credit that cannot reach the transport is
|
||||
* parked, not dropped: no WINDOW_ADJUST may be sent mid-rekey (RFC 4253 section
|
||||
* 7.1), an unbundled packet queued nothing, and a socket error can discard what
|
||||
* was bundled. Credit that reached the output buffer counts as delivered. */
|
||||
* 7.1) or after a disconnect (section 11.1), an unbundled packet queued
|
||||
* nothing, and a socket error can discard what was bundled. Credit that reached
|
||||
* the output buffer counts as delivered. */
|
||||
int ChannelCreditWindow(WOLFSSH* ssh, WOLFSSH_CHANNEL* channel, word32 amount)
|
||||
{
|
||||
word32 total;
|
||||
|
|
@ -7655,7 +7656,10 @@ int ChannelCreditWindow(WOLFSSH* ssh, WOLFSSH_CHANNEL* channel, word32 amount)
|
|||
if (total == 0)
|
||||
return WS_SUCCESS;
|
||||
|
||||
if (ssh->isKeying) {
|
||||
/* The reads that drain what arrived before a disconnect still credit the
|
||||
* window locally, but the session is over and nothing more may go out.
|
||||
* Park the credit so the read reports its bytes, not a send failure. */
|
||||
if (ssh->isKeying || ssh->disconnected) {
|
||||
channel->pendingWindowAdjust = total;
|
||||
return WS_SUCCESS;
|
||||
}
|
||||
|
|
|
|||
87
src/ssh.c
87
src/ssh.c
|
|
@ -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;
|
||||
|
|
@ -1231,13 +1246,14 @@ int wolfSSH_stream_read(WOLFSSH* ssh, byte* buf, word32 bufSz)
|
|||
if (ssh == NULL || buf == NULL || bufSz == 0)
|
||||
return WS_BAD_ARGUMENT;
|
||||
|
||||
if (ssh->disconnected) {
|
||||
ssh->error = WS_DISCONNECT;
|
||||
return WS_FATAL_ERROR;
|
||||
}
|
||||
|
||||
if (ssh->channelList == NULL)
|
||||
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;
|
||||
|
|
@ -1252,6 +1268,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);
|
||||
|
|
@ -1318,10 +1341,8 @@ int wolfSSH_stream_send(WOLFSSH* ssh, byte* buf, word32 bufSz)
|
|||
if (ssh == NULL || buf == NULL)
|
||||
return WS_BAD_ARGUMENT;
|
||||
|
||||
if (ssh->disconnected) {
|
||||
ssh->error = WS_DISCONNECT;
|
||||
if (SendAfterDisconnect(ssh))
|
||||
return WS_FATAL_ERROR;
|
||||
}
|
||||
|
||||
if (ssh->channelList == NULL)
|
||||
return WS_BAD_ARGUMENT;
|
||||
|
|
@ -1350,6 +1371,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) {
|
||||
|
|
@ -1386,6 +1410,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) {
|
||||
|
|
@ -1416,7 +1443,15 @@ int wolfSSH_stream_exit(WOLFSSH* ssh, int status)
|
|||
|
||||
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_stream_exit(), status = %d", status);
|
||||
|
||||
if (ssh == NULL || ssh->channelList == NULL)
|
||||
if (ssh == NULL)
|
||||
ret = WS_BAD_ARGUMENT;
|
||||
|
||||
/* Ahead of the channel-list test, like the other stream calls, so a
|
||||
* torn-down session reports the disconnect and not a bad argument. */
|
||||
if (ret == WS_SUCCESS && SendAfterDisconnect(ssh))
|
||||
ret = WS_FATAL_ERROR;
|
||||
|
||||
if (ret == WS_SUCCESS && ssh->channelList == NULL)
|
||||
ret = WS_BAD_ARGUMENT;
|
||||
|
||||
if (ret == WS_SUCCESS)
|
||||
|
|
@ -1442,6 +1477,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);
|
||||
}
|
||||
|
||||
|
|
@ -1452,7 +1489,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) {
|
||||
|
|
@ -3570,6 +3613,9 @@ WOLFSSH_CHANNEL* wolfSSH_ChannelFwdNewLocal(WOLFSSH* ssh,
|
|||
if (ssh == NULL || ssh->ctx == NULL || host == NULL || origin == NULL)
|
||||
ret = WS_BAD_ARGUMENT;
|
||||
|
||||
if (ret == WS_SUCCESS && SendAfterDisconnect(ssh))
|
||||
ret = WS_FATAL_ERROR;
|
||||
|
||||
if (ret == WS_SUCCESS) {
|
||||
newChannel = ChannelNew(ssh, ID_CHANTYPE_TCPIP_DIRECT,
|
||||
ssh->ctx->windowSz, ssh->ctx->maxPacketSz);
|
||||
|
|
@ -3609,6 +3655,9 @@ WOLFSSH_CHANNEL* wolfSSH_ChannelFwdNewRemote(WOLFSSH* ssh,
|
|||
if (ssh == NULL || ssh->ctx == NULL || host == NULL || origin == NULL)
|
||||
ret = WS_BAD_ARGUMENT;
|
||||
|
||||
if (ret == WS_SUCCESS && SendAfterDisconnect(ssh))
|
||||
ret = WS_FATAL_ERROR;
|
||||
|
||||
if (ret == WS_SUCCESS) {
|
||||
newChannel = ChannelNew(ssh, ID_CHANTYPE_TCPIP_FORWARD,
|
||||
ssh->ctx->windowSz, ssh->ctx->maxPacketSz);
|
||||
|
|
@ -3677,6 +3726,9 @@ int wolfSSH_FwdRemoteSetup(WOLFSSH* ssh, const char* bindAddr,
|
|||
if (ret == WS_SUCCESS && ssh->ctx->side != WOLFSSH_ENDPOINT_CLIENT)
|
||||
ret = WS_BAD_ARGUMENT;
|
||||
|
||||
if (ret == WS_SUCCESS && SendAfterDisconnect(ssh))
|
||||
ret = WS_FATAL_ERROR;
|
||||
|
||||
/* A global request must not go out mid-rekey; only KEX traffic may. */
|
||||
if (ret == WS_SUCCESS && ssh->isKeying)
|
||||
ret = WS_REKEYING;
|
||||
|
|
@ -3712,6 +3764,9 @@ int wolfSSH_FwdRemoteCancel(WOLFSSH* ssh, const char* bindAddr,
|
|||
if (ret == WS_SUCCESS && ssh->ctx->side != WOLFSSH_ENDPOINT_CLIENT)
|
||||
ret = WS_BAD_ARGUMENT;
|
||||
|
||||
if (ret == WS_SUCCESS && SendAfterDisconnect(ssh))
|
||||
ret = WS_FATAL_ERROR;
|
||||
|
||||
/* A global request must not go out mid-rekey; only KEX traffic may. */
|
||||
if (ret == WS_SUCCESS && ssh->isKeying)
|
||||
ret = WS_REKEYING;
|
||||
|
|
@ -4029,6 +4084,9 @@ int wolfSSH_ChannelSend(WOLFSSH_CHANNEL* channel,
|
|||
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_ChannelSend(), ID = %d, peerID = %d",
|
||||
channel->channel, channel->peerChannel);
|
||||
|
||||
if (channel->ssh != NULL && SendAfterDisconnect(channel->ssh))
|
||||
return WS_FATAL_ERROR;
|
||||
|
||||
#ifdef DEBUG_WOLFSSH
|
||||
DumpOctetString(buf, bufSz);
|
||||
#endif
|
||||
|
|
@ -4064,6 +4122,9 @@ int wolfSSH_ChannelSendExt(WOLFSSH_CHANNEL* channel,
|
|||
"Entering wolfSSH_ChannelSendExt(), ID = %d, peerID = %d",
|
||||
channel->channel, channel->peerChannel);
|
||||
|
||||
if (channel->ssh != NULL && SendAfterDisconnect(channel->ssh))
|
||||
return WS_FATAL_ERROR;
|
||||
|
||||
#ifdef DEBUG_WOLFSSH
|
||||
DumpOctetString(buf, bufSz);
|
||||
#endif
|
||||
|
|
@ -4093,6 +4154,10 @@ int wolfSSH_ChannelExit(WOLFSSH_CHANNEL* channel)
|
|||
if (channel == NULL)
|
||||
ret = WS_BAD_ARGUMENT;
|
||||
|
||||
if (ret == WS_SUCCESS && channel->ssh != NULL &&
|
||||
SendAfterDisconnect(channel->ssh))
|
||||
ret = WS_FATAL_ERROR;
|
||||
|
||||
if (ret == WS_SUCCESS)
|
||||
ret = SendChannelEof(channel->ssh, channel->peerChannel);
|
||||
|
||||
|
|
|
|||
327
tests/regress.c
327
tests/regress.c
|
|
@ -2965,6 +2965,128 @@ static void TestDisconnectTerminalWithChannel(void)
|
|||
}
|
||||
|
||||
|
||||
/* The disconnect stops sends, not reads. Channel data that arrived before
|
||||
* it is still the caller's, and only once that runs dry does the read
|
||||
* report the disconnect. */
|
||||
static void TestDisconnectDrainsBufferedData(void)
|
||||
{
|
||||
WOLFSSH_CTX* ctx;
|
||||
WOLFSSH* ssh;
|
||||
MemIo io;
|
||||
byte in[128];
|
||||
byte out[128];
|
||||
byte data[16];
|
||||
byte payload[] = { 'h', 'e', 'l', 'l', 'o' };
|
||||
word32 inSz;
|
||||
int ret;
|
||||
|
||||
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
|
||||
AssertNotNull(ctx);
|
||||
|
||||
wolfSSH_SetIORecv(ctx, MemRecv);
|
||||
wolfSSH_SetIOSend(ctx, MemSend);
|
||||
|
||||
ssh = wolfSSH_new(ctx);
|
||||
AssertNotNull(ssh);
|
||||
AddSessionChannel(ssh);
|
||||
|
||||
AssertIntEQ(ChannelPutData(ssh->channelList, payload, sizeof(payload)),
|
||||
WS_SUCCESS);
|
||||
|
||||
inSz = BuildDisconnectPacket(WOLFSSH_DISCONNECT_BY_APPLICATION,
|
||||
in, sizeof(in));
|
||||
MemIoInit(&io, in, inSz, out, sizeof(out));
|
||||
wolfSSH_SetIOReadCtx(ssh, &io);
|
||||
wolfSSH_SetIOWriteCtx(ssh, &io);
|
||||
|
||||
ret = DoReceive(ssh);
|
||||
AssertIntEQ(ret, WS_FATAL_ERROR);
|
||||
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
|
||||
AssertTrue(ssh->disconnected);
|
||||
|
||||
WMEMSET(data, 0, sizeof(data));
|
||||
ret = wolfSSH_stream_read(ssh, data, sizeof(data));
|
||||
AssertIntEQ(ret, (int)sizeof(payload));
|
||||
AssertIntEQ(WMEMCMP(data, payload, sizeof(payload)), 0);
|
||||
|
||||
/* Buffer is dry now, so the disconnect is what is left to report. */
|
||||
ret = wolfSSH_stream_read(ssh, data, sizeof(data));
|
||||
AssertIntEQ(ret, WS_FATAL_ERROR);
|
||||
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
|
||||
|
||||
wolfSSH_free(ssh);
|
||||
wolfSSH_CTX_free(ctx);
|
||||
}
|
||||
|
||||
|
||||
/* Every send entry point refuses after a disconnect, not just the stream
|
||||
* calls. wolfsshd and echoserver drive their channels through the
|
||||
* channel-id and extended-data calls and never touch wolfSSH_stream_send(). */
|
||||
static void TestDisconnectBlocksEverySend(void)
|
||||
{
|
||||
WOLFSSH_CTX* ctx;
|
||||
WOLFSSH* ssh;
|
||||
MemIo io;
|
||||
byte out[256];
|
||||
byte data[8];
|
||||
word32 quietSz;
|
||||
word32 channelId;
|
||||
int ret;
|
||||
|
||||
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
|
||||
AssertNotNull(ctx);
|
||||
|
||||
wolfSSH_SetIORecv(ctx, MemRecv);
|
||||
wolfSSH_SetIOSend(ctx, MemSend);
|
||||
|
||||
ssh = wolfSSH_new(ctx);
|
||||
AssertNotNull(ssh);
|
||||
AddSessionChannel(ssh);
|
||||
channelId = ssh->channelList->channel;
|
||||
|
||||
MemIoInit(&io, NULL, 0, out, sizeof(out));
|
||||
wolfSSH_SetIOReadCtx(ssh, &io);
|
||||
wolfSSH_SetIOWriteCtx(ssh, &io);
|
||||
|
||||
AssertIntEQ(wolfSSH_SendDisconnect(ssh, WOLFSSH_DISCONNECT_BY_APPLICATION),
|
||||
WS_SUCCESS);
|
||||
AssertTrue(ssh->disconnected);
|
||||
quietSz = io.outSz;
|
||||
|
||||
WMEMSET(data, 0, sizeof(data));
|
||||
|
||||
ret = wolfSSH_stream_send(ssh, data, sizeof(data));
|
||||
AssertIntEQ(ret, WS_FATAL_ERROR);
|
||||
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
|
||||
|
||||
ret = wolfSSH_ChannelIdSend(ssh, channelId, data, sizeof(data));
|
||||
AssertIntEQ(ret, WS_FATAL_ERROR);
|
||||
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
|
||||
|
||||
ret = wolfSSH_ChannelIdSendExt(ssh, channelId, data, sizeof(data));
|
||||
AssertIntEQ(ret, WS_FATAL_ERROR);
|
||||
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
|
||||
|
||||
ret = wolfSSH_extended_data_send(ssh, data, sizeof(data));
|
||||
AssertIntEQ(ret, WS_FATAL_ERROR);
|
||||
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
|
||||
|
||||
ret = wolfSSH_global_request(ssh, data, sizeof(data), 0);
|
||||
AssertIntEQ(ret, WS_FATAL_ERROR);
|
||||
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
|
||||
|
||||
ret = wolfSSH_stream_exit(ssh, 0);
|
||||
AssertIntEQ(ret, WS_FATAL_ERROR);
|
||||
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
|
||||
|
||||
/* Not one byte left the session after the disconnect. */
|
||||
AssertIntEQ(io.outSz, quietSz);
|
||||
|
||||
wolfSSH_free(ssh);
|
||||
wolfSSH_CTX_free(ctx);
|
||||
}
|
||||
|
||||
|
||||
/* Sending SSH_MSG_DISCONNECT ends the session the same way receiving one
|
||||
* does: RFC 4253 section 11.1 says the connection is over once the message
|
||||
* goes out, so the stream calls must refuse afterwards. */
|
||||
|
|
@ -3011,6 +3133,206 @@ static void TestSendDisconnectIsTerminal(void)
|
|||
wolfSSH_CTX_free(ctx);
|
||||
}
|
||||
|
||||
|
||||
/* The reads that drain what arrived before a disconnect must not put a
|
||||
* window adjust on the wire. The credit is parked on the channel instead,
|
||||
* so the read still reports its bytes rather than a send failure. */
|
||||
static void TestDisconnectQuietWindowAdjust(void)
|
||||
{
|
||||
WOLFSSH_CTX* ctx;
|
||||
WOLFSSH* ssh;
|
||||
WOLFSSH_CHANNEL* channel;
|
||||
MemIo io;
|
||||
byte in[128];
|
||||
byte out[256];
|
||||
byte payload[600];
|
||||
byte extPayload[64];
|
||||
byte data[600];
|
||||
word32 inSz;
|
||||
word32 windowSz;
|
||||
word32 pendingSz;
|
||||
int ret;
|
||||
|
||||
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
|
||||
AssertNotNull(ctx);
|
||||
|
||||
wolfSSH_SetIORecv(ctx, MemRecv);
|
||||
wolfSSH_SetIOSend(ctx, MemSend);
|
||||
|
||||
ssh = wolfSSH_new(ctx);
|
||||
AssertNotNull(ssh);
|
||||
AddSessionChannel(ssh);
|
||||
channel = ssh->channelList;
|
||||
/* Past userauth, or the message filter blocks the adjust on its own and
|
||||
* the wire check below proves nothing. */
|
||||
ssh->connectState = CONNECT_SERVER_USERAUTH_ACCEPT_DONE;
|
||||
|
||||
/* More than half the channel buffer, so draining it trips the window
|
||||
* update in _UpdateChannelWindow(). */
|
||||
WMEMSET(payload, 'a', sizeof(payload));
|
||||
AssertIntEQ(ChannelPutData(channel, payload, sizeof(payload)), WS_SUCCESS);
|
||||
|
||||
/* Buffered stderr for the extended-data drain. */
|
||||
WMEMSET(extPayload, 'e', sizeof(extPayload));
|
||||
AssertIntEQ(GrowBuffer(&channel->extDataBuffer, sizeof(extPayload)),
|
||||
WS_SUCCESS);
|
||||
WMEMCPY(channel->extDataBuffer.buffer, extPayload, sizeof(extPayload));
|
||||
channel->extDataBuffer.length = sizeof(extPayload);
|
||||
channel->extDataBuffer.idx = 0;
|
||||
|
||||
inSz = BuildDisconnectPacket(WOLFSSH_DISCONNECT_BY_APPLICATION,
|
||||
in, sizeof(in));
|
||||
MemIoInit(&io, in, inSz, out, sizeof(out));
|
||||
wolfSSH_SetIOReadCtx(ssh, &io);
|
||||
wolfSSH_SetIOWriteCtx(ssh, &io);
|
||||
|
||||
ret = DoReceive(ssh);
|
||||
AssertIntEQ(ret, WS_FATAL_ERROR);
|
||||
AssertTrue(ssh->disconnected);
|
||||
io.outSz = 0;
|
||||
|
||||
/* Two reads: the first leaves the index non-zero, the second is the one
|
||||
* with credit to return. */
|
||||
ret = wolfSSH_stream_read(ssh, data, 300);
|
||||
AssertIntEQ(ret, 300);
|
||||
ret = wolfSSH_stream_read(ssh, data, 300);
|
||||
AssertIntEQ(ret, 300);
|
||||
AssertIntEQ(io.outSz, 0);
|
||||
|
||||
/* The credit is owed, not lost. The window update runs before the read
|
||||
* advances the index, so the second read is the one that credits the
|
||||
* first read's 300 bytes. */
|
||||
AssertIntEQ(channel->pendingWindowAdjust, 300);
|
||||
pendingSz = channel->pendingWindowAdjust;
|
||||
windowSz = channel->windowSz;
|
||||
|
||||
ret = wolfSSH_extended_data_read(ssh, data, sizeof(extPayload));
|
||||
AssertIntEQ(ret, (int)sizeof(extPayload));
|
||||
AssertIntEQ(io.outSz, 0);
|
||||
AssertIntEQ(channel->pendingWindowAdjust,
|
||||
pendingSz + (word32)sizeof(extPayload));
|
||||
AssertIntEQ(channel->windowSz, windowSz + (word32)sizeof(extPayload));
|
||||
|
||||
/* wolfsshd and echoserver read by channel ID, so cover that drain too. */
|
||||
AssertIntEQ(ChannelPutData(channel, payload, sizeof(payload)), WS_SUCCESS);
|
||||
ret = wolfSSH_ChannelIdRead(ssh, channel->channel, data, sizeof(payload));
|
||||
AssertIntEQ(ret, (int)sizeof(payload));
|
||||
AssertIntEQ(io.outSz, 0);
|
||||
|
||||
wolfSSH_free(ssh);
|
||||
wolfSSH_CTX_free(ctx);
|
||||
}
|
||||
|
||||
|
||||
/* The channel-pointer and forwarding APIs are send calls too. */
|
||||
static void TestDisconnectBlocksChannelAndFwdSends(void)
|
||||
{
|
||||
WOLFSSH_CTX* ctx;
|
||||
WOLFSSH* ssh;
|
||||
WOLFSSH_CHANNEL* channel;
|
||||
MemIo io;
|
||||
byte out[256];
|
||||
byte data[8];
|
||||
word32 quietSz;
|
||||
|
||||
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
|
||||
AssertNotNull(ctx);
|
||||
|
||||
wolfSSH_SetIORecv(ctx, MemRecv);
|
||||
wolfSSH_SetIOSend(ctx, MemSend);
|
||||
|
||||
ssh = wolfSSH_new(ctx);
|
||||
AssertNotNull(ssh);
|
||||
AddSessionChannel(ssh);
|
||||
channel = ssh->channelList;
|
||||
ssh->connectState = CONNECT_SERVER_USERAUTH_ACCEPT_DONE;
|
||||
|
||||
MemIoInit(&io, NULL, 0, out, sizeof(out));
|
||||
wolfSSH_SetIOReadCtx(ssh, &io);
|
||||
wolfSSH_SetIOWriteCtx(ssh, &io);
|
||||
|
||||
AssertIntEQ(wolfSSH_SendDisconnect(ssh, WOLFSSH_DISCONNECT_BY_APPLICATION),
|
||||
WS_SUCCESS);
|
||||
quietSz = io.outSz;
|
||||
|
||||
WMEMSET(data, 0, sizeof(data));
|
||||
|
||||
AssertIntEQ(wolfSSH_ChannelSend(channel, data, sizeof(data)),
|
||||
WS_FATAL_ERROR);
|
||||
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
|
||||
|
||||
AssertIntEQ(wolfSSH_ChannelSendExt(channel, data, sizeof(data)),
|
||||
WS_FATAL_ERROR);
|
||||
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
|
||||
|
||||
AssertIntEQ(wolfSSH_ChannelExit(channel), WS_FATAL_ERROR);
|
||||
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
|
||||
|
||||
#ifdef WOLFSSH_FWD
|
||||
AssertIntEQ(wolfSSH_FwdRemoteSetup(ssh, "127.0.0.1", 22, 0),
|
||||
WS_FATAL_ERROR);
|
||||
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
|
||||
|
||||
AssertIntEQ(wolfSSH_FwdRemoteCancel(ssh, "127.0.0.1", 22, 0),
|
||||
WS_FATAL_ERROR);
|
||||
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
|
||||
|
||||
AssertNull(wolfSSH_ChannelFwdNewLocal(ssh, "127.0.0.1", 22,
|
||||
"127.0.0.1", 22));
|
||||
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
|
||||
|
||||
AssertNull(wolfSSH_ChannelFwdNewRemote(ssh, "127.0.0.1", 22,
|
||||
"127.0.0.1", 22));
|
||||
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
|
||||
#endif
|
||||
|
||||
/* The channel is still whole: nothing was torn down either. */
|
||||
AssertIntEQ(channel->eofTxd, 0);
|
||||
AssertIntEQ(channel->closeTxd, 0);
|
||||
AssertIntEQ(io.outSz, quietSz);
|
||||
|
||||
wolfSSH_free(ssh);
|
||||
wolfSSH_CTX_free(ctx);
|
||||
}
|
||||
|
||||
|
||||
/* wolfSSH_stream_exit() answers like the rest of its family on a session
|
||||
* whose channel is already gone: the disconnect, not a bad argument. */
|
||||
static void TestStreamExitReportsDisconnect(void)
|
||||
{
|
||||
WOLFSSH_CTX* ctx;
|
||||
WOLFSSH* ssh;
|
||||
MemIo io;
|
||||
byte out[256];
|
||||
|
||||
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
|
||||
AssertNotNull(ctx);
|
||||
|
||||
wolfSSH_SetIORecv(ctx, MemRecv);
|
||||
wolfSSH_SetIOSend(ctx, MemSend);
|
||||
|
||||
ssh = wolfSSH_new(ctx);
|
||||
AssertNotNull(ssh);
|
||||
AddSessionChannel(ssh);
|
||||
ssh->connectState = CONNECT_SERVER_USERAUTH_ACCEPT_DONE;
|
||||
|
||||
MemIoInit(&io, NULL, 0, out, sizeof(out));
|
||||
wolfSSH_SetIOReadCtx(ssh, &io);
|
||||
wolfSSH_SetIOWriteCtx(ssh, &io);
|
||||
|
||||
AssertIntEQ(wolfSSH_SendDisconnect(ssh, WOLFSSH_DISCONNECT_BY_APPLICATION),
|
||||
WS_SUCCESS);
|
||||
AssertIntEQ(ChannelRemove(ssh, ssh->channelList->channel,
|
||||
WS_CHANNEL_ID_SELF), WS_SUCCESS);
|
||||
AssertNull(ssh->channelList);
|
||||
|
||||
AssertIntEQ(wolfSSH_stream_exit(ssh, 0), WS_FATAL_ERROR);
|
||||
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
|
||||
|
||||
wolfSSH_free(ssh);
|
||||
wolfSSH_CTX_free(ctx);
|
||||
}
|
||||
|
||||
#ifdef WOLFSSH_SFTP
|
||||
static void TestOct2DecRejectsInvalidNonLeadingDigit(void)
|
||||
{
|
||||
|
|
@ -6738,7 +7060,12 @@ int main(int argc, char** argv)
|
|||
#endif
|
||||
TestDisconnectSetsDisconnectError();
|
||||
TestDisconnectTerminalWithChannel();
|
||||
TestDisconnectDrainsBufferedData();
|
||||
TestDisconnectBlocksEverySend();
|
||||
TestSendDisconnectIsTerminal();
|
||||
TestDisconnectQuietWindowAdjust();
|
||||
TestDisconnectBlocksChannelAndFwdSends();
|
||||
TestStreamExitReportsDisconnect();
|
||||
#if !(defined(WOLFSSH_NO_RSA) && defined(WOLFSSH_NO_ECDSA_SHA2_NISTP256))
|
||||
TestClientBuffersIdempotent();
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1102,9 +1102,11 @@ struct WOLFSSH {
|
|||
#endif
|
||||
byte connReset;
|
||||
byte isClosed;
|
||||
/* Set when a DISCONNECT is sent or received. Only wolfSSH_stream_read()
|
||||
* and wolfSSH_stream_send() are gated on it; the channel-id calls and
|
||||
* wolfSSH_worker() are not, since the shutdown paths still pump them. */
|
||||
/* Set when a DISCONNECT is sent or received. Gates every public send
|
||||
* call, so
|
||||
* nothing more goes out. The read calls are not gated: data that
|
||||
* arrived before the disconnect can still be drained. wolfSSH_worker()
|
||||
* is not gated either, since the shutdown paths still pump it. */
|
||||
byte disconnected;
|
||||
byte clientOpenSSH;
|
||||
|
||||
|
|
|
|||
|
|
@ -558,9 +558,12 @@ WOLFSSH_API int wolfSSH_CTX_SetWindowPacketSize(WOLFSSH_CTX* ctx,
|
|||
WOLFSSH_API int wolfSSH_accept(WOLFSSH* ssh);
|
||||
WOLFSSH_API int wolfSSH_connect(WOLFSSH* ssh);
|
||||
WOLFSSH_API int wolfSSH_shutdown(WOLFSSH* ssh);
|
||||
/* A disconnect, sent or received, ends the session: wolfSSH_stream_read()
|
||||
* and wolfSSH_stream_send() report WS_DISCONNECT from then on, and channel
|
||||
* data that arrived before it but was never drained is dropped. */
|
||||
/* A disconnect, sent or received, ends the session. Nothing more goes out:
|
||||
* every send call in this header, above this comment and below it,
|
||||
* reports WS_DISCONNECT from then on. Reads are not
|
||||
* gated, so channel data that arrived before the disconnect can still be
|
||||
* drained; wolfSSH_stream_read() reports WS_DISCONNECT once its buffer
|
||||
* runs dry. RFC 4253 section 11.1. */
|
||||
WOLFSSH_API int wolfSSH_stream_peek(WOLFSSH* ssh, byte* buf, word32 bufSz);
|
||||
WOLFSSH_API int wolfSSH_stream_read(WOLFSSH* ssh, byte* buf, word32 bufSz);
|
||||
WOLFSSH_API int wolfSSH_stream_send(WOLFSSH* ssh, byte* buf, word32 bufSz);
|
||||
|
|
@ -588,7 +591,8 @@ WOLFSSH_API int wolfSSH_extended_data_send(WOLFSSH* ssh, byte* buf, word32 bufSz
|
|||
* can be short: the byte count is still returned, but wolfSSH_get_error() is
|
||||
* left at WS_WANT_WRITE to show a flush is owed. An app that only reads must
|
||||
* then flush, with wolfSSH_worker(), or the peer's window is never replenished
|
||||
* and the channel stalls.
|
||||
* and the channel stalls. After a disconnect there is nothing to flush: the
|
||||
* credit is parked on the channel rather than sent.
|
||||
*
|
||||
* The buffer lives on the channel: anything unread when the channel is removed
|
||||
* (the peer's CHANNEL_CLOSE) is discarded with it. */
|
||||
|
|
|
|||
Loading…
Reference in New Issue