From 178c9f8fa8a39805643e40b2864e471bf27b64ec Mon Sep 17 00:00:00 2001 From: Reda Chouk Date: Wed, 2 Sep 2026 10:42:39 +0200 Subject: [PATCH] Reject the KEX replies a server never receives --- src/internal.c | 4 +++- tests/regress.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/internal.c b/src/internal.c index 4c61cd6a9..21d667507 100644 --- a/src/internal.c +++ b/src/internal.c @@ -702,7 +702,9 @@ static void HandshakeInfoFree(HandshakeInfo* hs, void* heap) INLINE static int IsMessageAllowedServer(WOLFSSH *ssh, byte msg) { /* Only the server should send these messages, never receive. */ - if (msg == MSGID_SERVICE_ACCEPT) { + if (msg == MSGID_SERVICE_ACCEPT || + msg == MSGID_KEXDH_REPLY || /* aliases MSGID_KEXDH_GEX_GROUP */ + msg == MSGID_KEXDH_GEX_REPLY) { WLOG(WS_LOG_DEBUG, "Message ID %u not allowed by %s %s", msg, "server", "ever"); ssh->error = WS_MSGID_NOT_ALLOWED_E; diff --git a/tests/regress.c b/tests/regress.c index 37f5e7b99..f5e6b3f00 100644 --- a/tests/regress.c +++ b/tests/regress.c @@ -2528,6 +2528,58 @@ static void TestServerOnlyUserauthMsgsBlocked(WOLFSSH* ssh) } +/* Reject the key exchange messages that only the server sends. */ +static void TestServerOnlyKexMsgsBlocked(WOLFSSH* ssh) +{ + int allowed; + + ResetSession(ssh); + /* The peer's KEXINIT has landed, its GEX request has not. */ + ssh->acceptState = ACCEPT_SERVER_KEXINIT_SENT; + ssh->isKeying = WOLFSSH_PEER_IS_KEYING; + ssh->handshake = AllocHandshake(ssh); + ssh->handshake->kexId = ID_DH_GEX_SHA256; + /* The server expects no particular message yet, so the expectMsgId + * check cannot catch these, the role check has to. */ + AssertIntEQ(ssh->handshake->expectMsgId, MSGID_NONE); + + allowed = wolfSSH_TestIsMessageAllowed(ssh, MSGID_KEXDH_GEX_GROUP, + WS_MSG_RECV); + AssertFalse(allowed); + AssertIntEQ(ssh->error, WS_MSGID_NOT_ALLOWED_E); + + ssh->error = 0; + allowed = wolfSSH_TestIsMessageAllowed(ssh, MSGID_KEXDH_GEX_REPLY, + WS_MSG_RECV); + AssertFalse(allowed); + AssertIntEQ(ssh->error, WS_MSGID_NOT_ALLOWED_E); + + /* The message a conformant client sends in this state is unaffected. */ + ssh->error = 0; + allowed = wolfSSH_TestIsMessageAllowed(ssh, MSGID_KEXDH_GEX_REQUEST, + WS_MSG_RECV); + AssertTrue(allowed); + AssertIntEQ(ssh->error, WS_SUCCESS); + + /* 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. */ + ssh->error = 0; + ssh->acceptState = ACCEPT_CLIENT_SESSION_ESTABLISHED; + + allowed = wolfSSH_TestIsMessageAllowed(ssh, MSGID_KEXDH_GEX_GROUP, + WS_MSG_RECV); + AssertFalse(allowed); + AssertIntEQ(ssh->error, WS_MSGID_NOT_ALLOWED_E); + + ssh->error = 0; + allowed = wolfSSH_TestIsMessageAllowed(ssh, MSGID_KEXDH_GEX_REPLY, + WS_MSG_RECV); + AssertFalse(allowed); + AssertIntEQ(ssh->error, WS_MSGID_NOT_ALLOWED_E); +} + + /* The server accepts a service request only once keyed, and never accepts * the service accept that only it sends. */ static void TestServerServiceRequestStateGated(WOLFSSH* ssh) @@ -11024,6 +11076,7 @@ int main(int argc, char** argv) TestServerKnownAuthMsgIdBeforeAuthDisconnects(); TestServerUnknownHighMsgIdBeforeAuthDisconnects(); TestServerOnlyUserauthMsgsBlocked(serverSsh); + TestServerOnlyKexMsgsBlocked(serverSsh); TestServerServiceRequestStateGated(serverSsh); TestServerServiceRequestRejectedDuringKeying(); TestFailedSendClearsPendingPlaintext();