From 0b889c8e40dd78b339ea82645aa895c5ad0e6a1e Mon Sep 17 00:00:00 2001 From: Reda Chouk Date: Wed, 2 Sep 2026 18:29:16 +0200 Subject: [PATCH] Reject the KEX requests a client never receives This is just a follow-up PR to (#1221), that mirrors pretty much the same changes for the client side too. --- src/internal.c | 7 ++++-- tests/regress.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/internal.c b/src/internal.c index 21d667507..dd44b307c 100644 --- a/src/internal.c +++ b/src/internal.c @@ -703,7 +703,7 @@ INLINE static int IsMessageAllowedServer(WOLFSSH *ssh, byte msg) { /* Only the server should send these messages, never receive. */ if (msg == MSGID_SERVICE_ACCEPT || - msg == MSGID_KEXDH_REPLY || /* aliases MSGID_KEXDH_GEX_GROUP */ + msg == MSGID_KEXDH_REPLY || /* 31: also ECDH, KEM, GEX_GROUP */ msg == MSGID_KEXDH_GEX_REPLY) { WLOG(WS_LOG_DEBUG, "Message ID %u not allowed by %s %s", msg, "server", "ever"); @@ -826,7 +826,10 @@ INLINE static int IsMessageAllowedServer(WOLFSSH *ssh, byte msg) INLINE static int IsMessageAllowedClient(WOLFSSH *ssh, byte msg) { /* Only the client should send these messages, never receive. */ - if (msg == MSGID_SERVICE_REQUEST || msg == MSGID_USERAUTH_REQUEST) { + if (msg == MSGID_SERVICE_REQUEST || msg == MSGID_USERAUTH_REQUEST || + msg == MSGID_KEXDH_INIT || /* 30: also ECDH, KEM, GEX_REQ_OLD */ + msg == MSGID_KEXDH_GEX_INIT || + msg == MSGID_KEXDH_GEX_REQUEST) { WLOG(WS_LOG_DEBUG, "Message ID %u not allowed by %s %s", msg, "client", "ever"); ssh->error = WS_MSGID_NOT_ALLOWED_E; diff --git a/tests/regress.c b/tests/regress.c index 3f09b397c..a0290b737 100644 --- a/tests/regress.c +++ b/tests/regress.c @@ -2263,6 +2263,55 @@ static void TestChannelAllowedAfterAuth(WOLFSSH* ssh) } +/* Reject the key exchange messages that only the client sends. */ +static void TestClientOnlyKexMsgsBlocked(WOLFSSH* ssh) +{ + int allowed; + + ResetSession(ssh); + /* Our KEXINIT is out, the server's group has not arrived. */ + ssh->connectState = CONNECT_CLIENT_KEXINIT_SENT; + ssh->isKeying = WOLFSSH_PEER_IS_KEYING; + ssh->handshake = AllocHandshake(ssh); + ssh->handshake->kexId = ID_DH_GEX_SHA256; + + allowed = wolfSSH_TestIsMessageAllowed(ssh, MSGID_KEXDH_INIT, + WS_MSG_RECV); + AssertFalse(allowed); + AssertIntEQ(ssh->error, WS_MSGID_NOT_ALLOWED_E); + + ssh->error = 0; + allowed = wolfSSH_TestIsMessageAllowed(ssh, MSGID_KEXDH_GEX_INIT, + WS_MSG_RECV); + AssertFalse(allowed); + AssertIntEQ(ssh->error, WS_MSGID_NOT_ALLOWED_E); + + ssh->error = 0; + allowed = wolfSSH_TestIsMessageAllowed(ssh, MSGID_KEXDH_GEX_REQUEST, + WS_MSG_RECV); + AssertFalse(allowed); + AssertIntEQ(ssh->error, WS_MSGID_NOT_ALLOWED_E); + + /* 31 is the server's answer to a group request, so it stays allowed + * where the client expects it. */ + ssh->error = 0; + ssh->handshake->expectMsgId = MSGID_KEXDH_GEX_GROUP; + allowed = wolfSSH_TestIsMessageAllowed(ssh, MSGID_KEXDH_GEX_GROUP, + WS_MSG_RECV); + AssertTrue(allowed); + AssertIntEQ(ssh->handshake->expectMsgId, MSGID_NONE); + + /* Same answer during a rekey on an established session. */ + ssh->error = 0; + ssh->connectState = CONNECT_DONE; + + allowed = wolfSSH_TestIsMessageAllowed(ssh, MSGID_KEXDH_GEX_REQUEST, + WS_MSG_RECV); + AssertFalse(allowed); + AssertIntEQ(ssh->error, WS_MSGID_NOT_ALLOWED_E); +} + + /* A service accept is allowed only when no key exchange is in flight. * connectState does not see a rekey, so isKeying is checked too. */ static void TestClientServiceAcceptBlockedDuringKeying(WOLFSSH* ssh) @@ -2561,6 +2610,16 @@ static void TestServerOnlyKexMsgsBlocked(WOLFSSH* ssh) AssertTrue(allowed); AssertIntEQ(ssh->error, WS_SUCCESS); + /* 32 sits between the two blocked ids and has to stay allowed. Assert + * it where the server actually expects it, once it has sent the + * group. */ + ssh->error = 0; + ssh->handshake->expectMsgId = MSGID_KEXDH_GEX_INIT; + allowed = wolfSSH_TestIsMessageAllowed(ssh, MSGID_KEXDH_GEX_INIT, + WS_MSG_RECV); + AssertTrue(allowed); + AssertIntEQ(ssh->handshake->expectMsgId, MSGID_NONE); + /* Same answer during a rekey on an established session. The pre-keyed * range check does not run this far along, so a check placed there * would leave this window open. */ @@ -11332,6 +11391,7 @@ int main(int argc, char** argv) TestChannelBlockedBeforeAuth(ssh); TestChannelBlockedEveryPreAuthState(ssh); TestChannelAllowedAfterAuth(ssh); + TestClientOnlyKexMsgsBlocked(ssh); TestClientServiceAcceptBlockedDuringKeying(ssh); TestChannelOpenRejectedBeforeKex(CONNECT_CLIENT_KEXINIT_SENT); TestChannelOpenRejectedBeforeKex(CONNECT_CLIENT_KEXDH_INIT_SENT);