Skip to content

Commit 76a7291

Browse files
committed
Make a disconnect end the session
SSH_MSG_DISCONNECT left nothing behind but ssh->error, which wolfSSH_stream_read() clears on entry. An application looping on the stream calls lost the code and went back to a connection already over. - Add WOLFSSH.disconnected, set by DoDisconnect() and SendDisconnect(). - DoDisconnect() sets it before decoding the payload, so a malformed message still ends the session. RFC 4253 section 11.1. - wolfSSH_stream_read() and wolfSSH_stream_send() report WS_DISCONNECT from the flag instead of reaching for the transport again. - Both guards run ahead of the channelList NULL test, so a torn-down session reports the disconnect rather than WS_BAD_ARGUMENT. - ssh.h states that undrained channel data goes with the session; internal.h states which calls the flag gates and which it does not. - regress.c: the receive side, the send side, and both of those again on a session with an open channel. Issue: F-8837
1 parent d4bf5e0 commit 76a7291

5 files changed

Lines changed: 161 additions & 2 deletions

File tree

src/internal.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8166,6 +8166,10 @@ static int DoDisconnect(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
81668166

81678167
WOLFSSH_UNUSED(reasonStr);
81688168

8169+
/* RFC 4253 section 11.1, the peer is gone whether or not the rest of
8170+
* the message decodes. */
8171+
ssh->disconnected = 1;
8172+
81698173
ret = GetUint32(&reason, buf, len, &begin);
81708174
if (ret == WS_SUCCESS) {
81718175
/* Skip the description text. */
@@ -16731,6 +16735,11 @@ int SendDisconnect(WOLFSSH* ssh, word32 reason)
1673116735
if (ssh == NULL)
1673216736
ret = WS_BAD_ARGUMENT;
1673316737

16738+
/* Mark the session over before the send. A partial or failed send
16739+
* still ends it. */
16740+
if (ret == WS_SUCCESS)
16741+
ssh->disconnected = 1;
16742+
1673416743
if (ret == WS_SUCCESS)
1673516744
ret = PreparePacket(ssh, MSG_ID_SZ + UINT32_SZ + (LENGTH_SZ * 2));
1673616745

src/ssh.c

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,7 +1228,15 @@ int wolfSSH_stream_read(WOLFSSH* ssh, byte* buf, word32 bufSz)
12281228

12291229
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_stream_read()");
12301230

1231-
if (ssh == NULL || buf == NULL || bufSz == 0 || ssh->channelList == NULL)
1231+
if (ssh == NULL || buf == NULL || bufSz == 0)
1232+
return WS_BAD_ARGUMENT;
1233+
1234+
if (ssh->disconnected) {
1235+
ssh->error = WS_DISCONNECT;
1236+
return WS_FATAL_ERROR;
1237+
}
1238+
1239+
if (ssh->channelList == NULL)
12321240
return WS_BAD_ARGUMENT;
12331241

12341242
if (ssh->channelList->eofRxd) {
@@ -1307,7 +1315,15 @@ int wolfSSH_stream_send(WOLFSSH* ssh, byte* buf, word32 bufSz)
13071315

13081316
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_stream_send()");
13091317

1310-
if (ssh == NULL || buf == NULL || ssh->channelList == NULL)
1318+
if (ssh == NULL || buf == NULL)
1319+
return WS_BAD_ARGUMENT;
1320+
1321+
if (ssh->disconnected) {
1322+
ssh->error = WS_DISCONNECT;
1323+
return WS_FATAL_ERROR;
1324+
}
1325+
1326+
if (ssh->channelList == NULL)
13111327
return WS_BAD_ARGUMENT;
13121328

13131329
if (ssh->isKeying) {

tests/regress.c

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ static void ResetSession(WOLFSSH* ssh)
8888
ssh->connectState = CONNECT_BEGIN;
8989
ssh->acceptState = ACCEPT_BEGIN;
9090
ssh->error = 0;
91+
ssh->disconnected = 0;
9192
}
9293

9394

@@ -2571,6 +2572,7 @@ static void TestDisconnectSetsDisconnectError(void)
25712572
MemIo io;
25722573
byte in[128];
25732574
byte out[32];
2575+
byte data[8];
25742576
word32 inSz;
25752577
int ret;
25762578

@@ -2594,6 +2596,129 @@ static void TestDisconnectSetsDisconnectError(void)
25942596
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
25952597
AssertIntEQ(io.inOff, io.inSz);
25962598

2599+
/* The disconnect is terminal, not just this call's error. Later stream
2600+
* calls must report it rather than clearing the error and reading or
2601+
* writing more. */
2602+
AssertTrue(ssh->disconnected);
2603+
2604+
WMEMSET(data, 0, sizeof(data));
2605+
ret = wolfSSH_stream_read(ssh, data, sizeof(data));
2606+
AssertIntEQ(ret, WS_FATAL_ERROR);
2607+
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
2608+
2609+
ret = wolfSSH_stream_send(ssh, data, sizeof(data));
2610+
AssertIntEQ(ret, WS_FATAL_ERROR);
2611+
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
2612+
2613+
wolfSSH_free(ssh);
2614+
wolfSSH_CTX_free(ctx);
2615+
}
2616+
2617+
2618+
/* Append a bare session channel so the stream calls have a channel to work
2619+
* on, the state a disconnect actually arrives in. */
2620+
static void AddSessionChannel(WOLFSSH* ssh)
2621+
{
2622+
WOLFSSH_CHANNEL* ch;
2623+
2624+
ch = ChannelNew(ssh, ID_CHANTYPE_SESSION, 1024, 1024);
2625+
AssertNotNull(ch);
2626+
AssertIntEQ(ChannelAppend(ssh, ch), WS_SUCCESS);
2627+
ch->openConfirmed = 1;
2628+
}
2629+
2630+
2631+
/* The same received disconnect on an established session. Without a channel
2632+
* the stream calls bail out on the NULL channel list before they reach
2633+
* anything, so this is the case that shows the gate doing work. */
2634+
static void TestDisconnectTerminalWithChannel(void)
2635+
{
2636+
WOLFSSH_CTX* ctx;
2637+
WOLFSSH* ssh;
2638+
MemIo io;
2639+
byte in[128];
2640+
byte out[128];
2641+
byte data[8];
2642+
word32 inSz;
2643+
int ret;
2644+
2645+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
2646+
AssertNotNull(ctx);
2647+
2648+
wolfSSH_SetIORecv(ctx, MemRecv);
2649+
wolfSSH_SetIOSend(ctx, MemSend);
2650+
2651+
ssh = wolfSSH_new(ctx);
2652+
AssertNotNull(ssh);
2653+
AddSessionChannel(ssh);
2654+
2655+
inSz = BuildDisconnectPacket(WOLFSSH_DISCONNECT_BY_APPLICATION,
2656+
in, sizeof(in));
2657+
MemIoInit(&io, in, inSz, out, sizeof(out));
2658+
wolfSSH_SetIOReadCtx(ssh, &io);
2659+
wolfSSH_SetIOWriteCtx(ssh, &io);
2660+
2661+
ret = DoReceive(ssh);
2662+
AssertIntEQ(ret, WS_FATAL_ERROR);
2663+
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
2664+
AssertTrue(ssh->disconnected);
2665+
2666+
WMEMSET(data, 0, sizeof(data));
2667+
ret = wolfSSH_stream_read(ssh, data, sizeof(data));
2668+
AssertIntEQ(ret, WS_FATAL_ERROR);
2669+
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
2670+
2671+
/* Nothing may go out on the channel either. */
2672+
ret = wolfSSH_stream_send(ssh, data, sizeof(data));
2673+
AssertIntEQ(ret, WS_FATAL_ERROR);
2674+
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
2675+
AssertIntEQ(io.outSz, 0);
2676+
2677+
wolfSSH_free(ssh);
2678+
wolfSSH_CTX_free(ctx);
2679+
}
2680+
2681+
2682+
/* Sending SSH_MSG_DISCONNECT ends the session the same way receiving one
2683+
* does: RFC 4253 section 11.1 says the connection is over once the message
2684+
* goes out, so the stream calls must refuse afterwards. */
2685+
static void TestSendDisconnectIsTerminal(void)
2686+
{
2687+
WOLFSSH_CTX* ctx;
2688+
WOLFSSH* ssh;
2689+
MemIo io;
2690+
byte out[128];
2691+
byte data[8];
2692+
int ret;
2693+
2694+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
2695+
AssertNotNull(ctx);
2696+
2697+
wolfSSH_SetIORecv(ctx, MemRecv);
2698+
wolfSSH_SetIOSend(ctx, MemSend);
2699+
2700+
ssh = wolfSSH_new(ctx);
2701+
AssertNotNull(ssh);
2702+
AddSessionChannel(ssh);
2703+
2704+
MemIoInit(&io, NULL, 0, out, sizeof(out));
2705+
wolfSSH_SetIOReadCtx(ssh, &io);
2706+
wolfSSH_SetIOWriteCtx(ssh, &io);
2707+
2708+
ret = wolfSSH_SendDisconnect(ssh, WOLFSSH_DISCONNECT_BY_APPLICATION);
2709+
AssertIntEQ(ret, WS_SUCCESS);
2710+
AssertTrue(ssh->disconnected);
2711+
AssertTrue(io.outSz > 0);
2712+
2713+
WMEMSET(data, 0, sizeof(data));
2714+
ret = wolfSSH_stream_send(ssh, data, sizeof(data));
2715+
AssertIntEQ(ret, WS_FATAL_ERROR);
2716+
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
2717+
2718+
ret = wolfSSH_stream_read(ssh, data, sizeof(data));
2719+
AssertIntEQ(ret, WS_FATAL_ERROR);
2720+
AssertIntEQ(wolfSSH_get_error(ssh), WS_DISCONNECT);
2721+
25972722
wolfSSH_free(ssh);
25982723
wolfSSH_CTX_free(ctx);
25992724
}
@@ -6328,6 +6453,8 @@ int main(int argc, char** argv)
63286453
TestDoNewKeys();
63296454
#endif
63306455
TestDisconnectSetsDisconnectError();
6456+
TestDisconnectTerminalWithChannel();
6457+
TestSendDisconnectIsTerminal();
63316458
#if !(defined(WOLFSSH_NO_RSA) && defined(WOLFSSH_NO_ECDSA_SHA2_NISTP256))
63326459
TestClientBuffersIdempotent();
63336460
#endif

wolfssh/internal.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,6 +1073,10 @@ struct WOLFSSH {
10731073
#endif
10741074
byte connReset;
10751075
byte isClosed;
1076+
/* Set when a DISCONNECT is sent or received. Only wolfSSH_stream_read()
1077+
* and wolfSSH_stream_send() are gated on it; the channel-id calls and
1078+
* wolfSSH_worker() are not, since the shutdown paths still pump them. */
1079+
byte disconnected;
10761080
byte clientOpenSSH;
10771081

10781082
byte kexId;

wolfssh/ssh.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,9 @@ WOLFSSH_API int wolfSSH_CTX_SetWindowPacketSize(WOLFSSH_CTX* ctx,
558558
WOLFSSH_API int wolfSSH_accept(WOLFSSH* ssh);
559559
WOLFSSH_API int wolfSSH_connect(WOLFSSH* ssh);
560560
WOLFSSH_API int wolfSSH_shutdown(WOLFSSH* ssh);
561+
/* A disconnect, sent or received, ends the session: wolfSSH_stream_read()
562+
* and wolfSSH_stream_send() report WS_DISCONNECT from then on, and channel
563+
* data that arrived before it but was never drained is dropped. */
561564
WOLFSSH_API int wolfSSH_stream_peek(WOLFSSH* ssh, byte* buf, word32 bufSz);
562565
WOLFSSH_API int wolfSSH_stream_read(WOLFSSH* ssh, byte* buf, word32 bufSz);
563566
WOLFSSH_API int wolfSSH_stream_send(WOLFSSH* ssh, byte* buf, word32 bufSz);

0 commit comments

Comments
 (0)