Reject client conn messages before key exchange

- IsMessageAllowedClient() gated the post-userauth rejection on
  connectState >= CONNECT_KEYED, so every state below it allowed
  msgid 80 and above.
- A client in the pre-key-exchange DoReceive() loops accepted an
  unencrypted CHANNEL_OPEN, created the channel and replied with
  CHANNEL_OPEN_CONF.
- Add a state sweep over the connection-protocol range and a full
  DoReceive() of a plaintext CHANNEL_OPEN from a pre-auth state.

Issue: F-7628
pull/1157/head
John Safranek 2026-08-05 14:29:16 -07:00 committed by Paul Adelsbach
parent ab8058d7cd
commit 4f827ca4a9
2 changed files with 77 additions and 2 deletions

View File

@ -852,8 +852,7 @@ INLINE static int IsMessageAllowedClient(WOLFSSH *ssh, byte msg)
}
/* Is client userauth complete? */
if (ssh->connectState >= CONNECT_KEYED
&& ssh->connectState < CONNECT_SERVER_USERAUTH_ACCEPT_DONE) {
if (ssh->connectState < CONNECT_SERVER_USERAUTH_ACCEPT_DONE) {
/* The endpoints should not allow message IDs greater than or
* equal to msgid 80 before user authentication is complete.
* Per RFC 4252 section 6. */

View File

@ -1685,6 +1685,38 @@ static void TestChannelBlockedBeforeAuth(WOLFSSH* ssh)
}
/* Reject connection-protocol messages in every state before user
* authentication completes, keying or not. */
static void TestChannelBlockedEveryPreAuthState(WOLFSSH* ssh)
{
static const byte connMsgs[] = {
MSGID_GLOBAL_REQUEST, MSGID_CHANNEL_OPEN, MSGID_CHANNEL_DATA,
MSGID_CHANNEL_REQUEST, MSGIDLIMIT_CONN_MAX
};
int state;
word32 i;
byte keying;
for (keying = 0; keying <= 1; keying++) {
for (state = CONNECT_BEGIN;
state < CONNECT_SERVER_USERAUTH_ACCEPT_DONE; state++) {
for (i = 0; i < sizeof(connMsgs)/sizeof(*connMsgs); i++) {
int allowed;
ResetSession(ssh);
ssh->isKeying = keying ? WOLFSSH_PEER_IS_KEYING : 0;
ssh->connectState = (byte)state;
allowed = wolfSSH_TestIsMessageAllowed(ssh, connMsgs[i],
WS_MSG_RECV);
AssertFalse(allowed);
AssertIntEQ(ssh->error, WS_MSGID_NOT_ALLOWED_E);
}
}
}
}
/* Allow channel messages after user authentication completes. */
static void TestChannelAllowedAfterAuth(WOLFSSH* ssh)
{
@ -1699,6 +1731,47 @@ static void TestChannelAllowedAfterAuth(WOLFSSH* ssh)
}
/* Drive the whole receive path with a CHANNEL_OPEN sent from a pre-auth
* connectState: no channel created, no reply emitted. The connectState
* gate does the rejecting; isKeying below is scene-setting only. */
static void TestChannelOpenRejectedBeforeKex(byte connectState)
{
WOLFSSH_CTX* ctx;
WOLFSSH* ssh;
MemIo io;
byte pkt[256];
byte out[256];
word32 pktSz;
pktSz = BuildChannelOpenPacket("session", 0, 131072, 16384, NULL, 0,
pkt, sizeof(pkt));
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
AssertNotNull(ctx);
wolfSSH_SetIORecv(ctx, MemRecv);
wolfSSH_SetIOSend(ctx, MemSend);
ssh = wolfSSH_new(ctx);
AssertNotNull(ssh);
MemIoInit(&io, pkt, pktSz, out, sizeof(out));
wolfSSH_SetIOReadCtx(ssh, &io);
wolfSSH_SetIOWriteCtx(ssh, &io);
ssh->connectState = connectState;
ssh->isKeying = WOLFSSH_PEER_IS_KEYING;
AssertIntEQ(wolfSSH_TestDoReceive(ssh), WS_FATAL_ERROR);
AssertIntEQ(ssh->error, WS_MSGID_NOT_ALLOWED_E);
AssertNull(ssh->channelList);
AssertIntEQ(ssh->channelListSz, 0);
AssertIntEQ(io.outSz, 0);
wolfSSH_free(ssh);
wolfSSH_CTX_free(ctx);
}
#ifndef NO_WOLFSSH_SERVER
/* The client gate tests above run against a client endpoint, so the server
* branch of IsMessageAllowed was never exercised. The tests below drive a
@ -5765,7 +5838,10 @@ int main(int argc, char** argv)
TestPublicKeyFailureBeforeRequest(ssh);
TestPublicKeyFailureAborts(ssh);
TestChannelBlockedBeforeAuth(ssh);
TestChannelBlockedEveryPreAuthState(ssh);
TestChannelAllowedAfterAuth(ssh);
TestChannelOpenRejectedBeforeKex(CONNECT_CLIENT_KEXINIT_SENT);
TestChannelOpenRejectedBeforeKex(CONNECT_CLIENT_KEXDH_INIT_SENT);
#ifndef NO_WOLFSSH_SERVER
TestServerChannelBlockedBeforeAuth(serverSsh);
TestServerChannelAllowedAfterAuth(serverSsh);