@@ -3734,6 +3734,80 @@ static void TestShutdownFlushesQueuedDisconnect(void)
37343734}
37353735
37363736
3737+ /* A rekey that the peer abandons with a DISCONNECT never completes: only
3738+ * NEWKEYS clears isKeying, and nothing more arrives. The read calls test
3739+ * isKeying first, so they report WS_REKEYING forever and the caller's
3740+ * "keep turning the crank" branch spins for the life of the connection.
3741+ * A dead session outranks a rekey that can no longer finish. */
3742+ static void TestDisconnectOutranksRekey (void )
3743+ {
3744+ WOLFSSH_CTX * ctx ;
3745+ WOLFSSH * ssh ;
3746+ WOLFSSH_CHANNEL * channel ;
3747+ MemIo io ;
3748+ byte in [128 ];
3749+ byte out [256 ];
3750+ byte payload [32 ];
3751+ byte data [64 ];
3752+ word32 inSz ;
3753+ int ret ;
3754+
3755+ ctx = wolfSSH_CTX_new (WOLFSSH_ENDPOINT_CLIENT , NULL );
3756+ AssertNotNull (ctx );
3757+
3758+ wolfSSH_SetIORecv (ctx , MemRecv );
3759+ wolfSSH_SetIOSend (ctx , MemSend );
3760+
3761+ ssh = wolfSSH_new (ctx );
3762+ AssertNotNull (ssh );
3763+ AddSessionChannel (ssh );
3764+ channel = ssh -> channelList ;
3765+ ssh -> connectState = CONNECT_SERVER_USERAUTH_ACCEPT_DONE ;
3766+
3767+ /* Channel data that arrived before the rekey started. */
3768+ WMEMSET (payload , 'a' , sizeof (payload ));
3769+ AssertIntEQ (ChannelPutData (channel , payload , sizeof (payload )), WS_SUCCESS );
3770+
3771+ /* The peer's KEXINIT, the way DoKexInit records it. */
3772+ ssh -> isKeying |= WOLFSSH_PEER_IS_KEYING ;
3773+
3774+ /* DISCONNECT is a transport-generic message, so the rekey filter in
3775+ * IsMessageAllowed() lets it through. */
3776+ inSz = BuildDisconnectPacket (WOLFSSH_DISCONNECT_BY_APPLICATION ,
3777+ in , sizeof (in ));
3778+ MemIoInit (& io , in , inSz , out , sizeof (out ));
3779+ wolfSSH_SetIOReadCtx (ssh , & io );
3780+ wolfSSH_SetIOWriteCtx (ssh , & io );
3781+
3782+ AssertIntEQ (DoReceive (ssh ), WS_FATAL_ERROR );
3783+ AssertTrue (ssh -> disconnected );
3784+ /* The rekey is stuck: no NEWKEYS is ever coming. */
3785+ AssertTrue (ssh -> isKeying != 0 );
3786+ io .outSz = 0 ;
3787+
3788+ /* What arrived before the disconnect is still the caller's. */
3789+ ret = wolfSSH_stream_peek (ssh , data , sizeof (data ));
3790+ AssertIntEQ (ret , (int )sizeof (payload ));
3791+ ret = wolfSSH_stream_read (ssh , data , sizeof (data ));
3792+ AssertIntEQ (ret , (int )sizeof (payload ));
3793+
3794+ /* Drained, so both report the disconnect instead of a rekey that will
3795+ * never finish. */
3796+ ret = wolfSSH_stream_peek (ssh , data , sizeof (data ));
3797+ AssertIntEQ (ret , WS_FATAL_ERROR );
3798+ AssertIntEQ (wolfSSH_get_error (ssh ), WS_DISCONNECT );
3799+ ret = wolfSSH_stream_read (ssh , data , sizeof (data ));
3800+ AssertIntEQ (ret , WS_FATAL_ERROR );
3801+ AssertIntEQ (wolfSSH_get_error (ssh ), WS_DISCONNECT );
3802+
3803+ /* The drain stayed quiet, as it does outside a rekey. */
3804+ AssertIntEQ (io .outSz , 0 );
3805+
3806+ wolfSSH_free (ssh );
3807+ wolfSSH_CTX_free (ctx );
3808+ }
3809+
3810+
37373811/* A flush that is itself short owns ssh->error. Callers gate their retry on
37383812 * WS_WANT_WRITE, so the disconnect gate must not overwrite it. */
37393813static void TestShutdownKeepsFlushWantWrite (void )
@@ -7567,6 +7641,7 @@ int main(int argc, char** argv)
75677641 TestQueuedDisconnectFlushes ();
75687642 TestShutdownFlushesQueuedDisconnect ();
75697643 TestShutdownKeepsFlushWantWrite ();
7644+ TestDisconnectOutranksRekey ();
75707645#if defined(WOLFSSH_TERM ) && !defined(NO_FILESYSTEM )
75717646 TestTerminalResizeBlockedAfterDisconnect ();
75727647#endif
0 commit comments