From 52bd594a8a190f4ebd2bff6413d4b33406351eab Mon Sep 17 00:00:00 2001 From: Yosuke Shimizu Date: Thu, 17 Sep 2026 16:55:28 +0900 Subject: [PATCH] internal: set the keying flag once the KEX init reaches the transport - SendKexInit() takes ssh->txFlushCount before wolfSSH_SendPacket() and sets WOLFSSH_SELF_IS_KEYING when SendPacketDelivered() reports the packet away, in place of setting it before the packet is built. - PurgePacket() runs on the same decision, so a packet the transport took is not purged behind an error the highwater callback raised. - internal.h describes the flag as set once the KEX init is sent or queued. - tests/unit.c covers a KEX init whose send fails outright, one that short-writes, and one the transport takes whole behind a highwater callback that fails. - FailHighwater() moves beside the other shared send callbacks and gains WS_MAYBE_UNUSED, so the client test uses it too. --- src/internal.c | 13 +++++-- tests/unit.c | 94 ++++++++++++++++++++++++++++++++++++++++++---- wolfssh/internal.h | 2 +- 3 files changed, 98 insertions(+), 11 deletions(-) diff --git a/src/internal.c b/src/internal.c index 77382f50..90eb3800 100644 --- a/src/internal.c +++ b/src/internal.c @@ -15194,6 +15194,7 @@ int SendKexInit(WOLFSSH* ssh) macAlgoNamesSz = 0, noneNamesSz = 0; int ret = WS_SUCCESS; + int delivered = 0; WLOG(WS_LOG_DEBUG, "Entering SendKexInit()"); @@ -15220,8 +15221,6 @@ int SendKexInit(WOLFSSH* ssh) } if (ret == WS_SUCCESS) { - /* Set self is keying flag since we started sending the KEX init msg */ - ssh->isKeying |= WOLFSSH_SELF_IS_KEYING; if (ssh->handshake == NULL) { ssh->handshake = HandshakeInfoNew(ssh->ctx->heap); if (ssh->handshake == NULL) { @@ -15349,11 +15348,19 @@ int SendKexInit(WOLFSSH* ssh) } if (ret == WS_SUCCESS) { + word32 flushes = ssh->txFlushCount; + ret = wolfSSH_SendPacket(ssh); + delivered = SendPacketDelivered(ssh, flushes, ret); } - if (ret != WS_WANT_WRITE && ret != WS_SUCCESS) + if (delivered) { + /* Set self is keying flag now the KEX init msg is away */ + ssh->isKeying |= WOLFSSH_SELF_IS_KEYING; + } + else { PurgePacket(ssh); + } WLOG(WS_LOG_DEBUG, "Leaving SendKexInit(), ret = %d", ret); return ret; diff --git a/tests/unit.c b/tests/unit.c index f7ddcd61..b421068f 100644 --- a/tests/unit.c +++ b/tests/unit.c @@ -4565,6 +4565,13 @@ static WS_MAYBE_UNUSED int OobIoSend(WOLFSSH* ssh, void* buf, word32 sz, return (int)sz + 1; } +/* Fires once the message highwater mark is crossed and reports an error. */ +static WS_MAYBE_UNUSED int FailHighwater(byte side, void* ctx) +{ + (void)side; (void)ctx; + return WS_FATAL_ERROR; +} + static int test_DoChannelExtendedData_overflow(void) { WOLFSSH_CTX* ctx = NULL; @@ -5705,13 +5712,6 @@ done: #ifndef NO_WOLFSSH_SERVER -/* Fires once the message highwater mark is crossed and reports an error. */ -static int FailHighwater(byte side, void* ctx) -{ - (void)side; (void)ctx; - return WS_FATAL_ERROR; -} - /* wolfSSH_SendPacket() runs the highwater check after the packet is on the wire * and returns the highwater callback's status, so a failing callback makes a * delivered WINDOW_ADJUST look like a failed send. Credit re-parked then is @@ -8758,6 +8758,81 @@ done: wolfSSH_CTX_free(ctx); return result; } + + +/* Covers a KEX init whose send fails outright and one that short-writes. */ +static int test_KexInitSendAwayGatesKeying(void) +{ + WOLFSSH_CTX* ctx = NULL; + WOLFSSH* ssh = NULL; + int result = 0; + int ret; + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); + if (ctx == NULL) + return -1897; + /* No refusals, so the first write resets the socket. */ + s_sendRefusals = 0; + wolfSSH_SetIOSend(ctx, RefuseThenResetIoSend); + wolfSSH_SetIORecv(ctx, PacketIoRecv); + + ssh = wolfSSH_new(ctx); + if (ssh == NULL) { result = -1898; goto done; } + + ret = wolfSSH_TriggerKeyExchange(ssh); + if (ret == WS_SUCCESS || ret == WS_WANT_WRITE) { + result = -1899; + goto done; + } + if (wolfSSH_RekeyPending(ssh)) { result = -1900; goto done; } + + wolfSSH_free(ssh); + + /* One refusal short-writes instead. */ + s_sendRefusals = 1; + ssh = wolfSSH_new(ctx); + if (ssh == NULL) { result = -1903; goto done; } + + ret = wolfSSH_TriggerKeyExchange(ssh); + if (ret != WS_SUCCESS && ret != WS_WANT_WRITE) { + result = -1904; + goto done; + } + if (!wolfSSH_RekeyPending(ssh)) { result = -1905; goto done; } + + wolfSSH_free(ssh); + ssh = NULL; + wolfSSH_CTX_free(ctx); + ctx = NULL; + + /* The transport takes the whole packet and the highwater callback then + * fails, so the error arrives with the KEX init already sent. */ + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); + if (ctx == NULL) { result = -1906; goto done; } + wolfSSH_SetIOSend(ctx, DiscardIoSend); + wolfSSH_SetIORecv(ctx, PacketIoRecv); + wolfSSH_SetHighwaterCb(ctx, 1, FailHighwater); + + ssh = wolfSSH_new(ctx); + if (ssh == NULL) { result = -1907; goto done; } + if (wolfSSH_SetHighwater(ssh, 1) != WS_SUCCESS) { + result = -1908; + goto done; + } + + ret = wolfSSH_TriggerKeyExchange(ssh); + if (ret == WS_SUCCESS) { result = -1909; goto done; } + if (!wolfSSH_RekeyPending(ssh)) { result = -1910; goto done; } + +done: + s_sendRefusals = 0; + s_recvPkt = NULL; + s_recvPktSz = 0; + s_recvPktOff = 0; + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); + return result; +} #endif /* NO_WOLFSSH_CLIENT */ @@ -22798,6 +22873,11 @@ int wolfSSH_UnitTest(int argc, char** argv) printf("TriggerKeyExchangeKeepsError: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; + + unitResult = test_KexInitSendAwayGatesKeying(); + printf("KexInitSendAwayGatesKeying: %s\n", + (unitResult == 0 ? "SUCCESS" : "FAILED")); + testResult = testResult || unitResult; #endif diff --git a/wolfssh/internal.h b/wolfssh/internal.h index d41e9330..6682661b 100644 --- a/wolfssh/internal.h +++ b/wolfssh/internal.h @@ -736,7 +736,7 @@ enum NameIdType { #define WOLFSSH_PROTOID_LIMIT 255 /* Keep track of keying state for both sides of the connection. - * WOLFSSH_SELF_IS_KEYING gets set on sending KEX init and + * WOLFSSH_SELF_IS_KEYING gets set once the KEX init is sent or queued and * WOLFSSH_PEER_IS_KEYING gets set on receiving KEX init */ #define WOLFSSH_PEER_IS_KEYING 0x01 #define WOLFSSH_SELF_IS_KEYING 0x02