Skip to content

Commit 89b8055

Browse files
ejohnstownphilljj
authored andcommitted
fix: reject service messages while keying
RFC 4253 section 7.1 bars SERVICE_REQUEST and SERVICE_ACCEPT between a KEXINIT and the matching NEWKEYS. The admission check now consults ssh->isKeying, which acceptState and connectState do not track. It tests WOLFSSH_PEER_IS_KEYING, as the KEXINIT gate below it does: a peer that has not seen our KEXINIT yet may still legally send. - gate MSGID_SERVICE_REQUEST on isKeying in IsMessageAllowedServer() - gate MSGID_SERVICE_ACCEPT on isKeying in IsMessageAllowedClient() - record the refusals as WS_MSGID_NOT_ALLOWED_E, the code DoReceive() stores anyway, rather than WS_REKEYING, a soft retry-later status - add unit and receive-path coverage in tests/regress.c Issue: F-10569
1 parent 5fb7be4 commit 89b8055

2 files changed

Lines changed: 115 additions & 4 deletions

File tree

src/internal.c

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,15 @@ INLINE static int IsMessageAllowedServer(WOLFSSH *ssh, byte msg)
710710
}
711711

712712
if (msg == MSGID_SERVICE_REQUEST) {
713+
/* RFC 4253 section 7.1: no service request once the peer's KEXINIT
714+
* lands. acceptState doesn't track a rekey. */
715+
if (ssh->isKeying & WOLFSSH_PEER_IS_KEYING) {
716+
WLOG(WS_LOG_DEBUG, "Message ID %u not allowed by %s %s",
717+
msg, "server", "when keying");
718+
ssh->error = WS_MSGID_NOT_ALLOWED_E;
719+
return 0;
720+
}
721+
713722
if (ssh->acceptState == ACCEPT_KEYED) {
714723
return 1;
715724
}
@@ -732,7 +741,7 @@ INLINE static int IsMessageAllowedServer(WOLFSSH *ssh, byte msg)
732741
if (msg == MSGID_KEXINIT) {
733742
WLOG(WS_LOG_DEBUG, "Message ID %u not allowed by %s %s",
734743
msg, "server", "when keying");
735-
ssh->error = WS_REKEYING;
744+
ssh->error = WS_MSGID_NOT_ALLOWED_E;
736745
return 0;
737746
}
738747

@@ -744,7 +753,7 @@ INLINE static int IsMessageAllowedServer(WOLFSSH *ssh, byte msg)
744753
WLOG(WS_LOG_DEBUG,
745754
"Message ID %u not the expected message %u",
746755
msg, ssh->handshake->expectMsgId);
747-
ssh->error = WS_REKEYING;
756+
ssh->error = WS_MSGID_NOT_ALLOWED_E;
748757
return 0;
749758
}
750759
else {
@@ -823,6 +832,15 @@ INLINE static int IsMessageAllowedClient(WOLFSSH *ssh, byte msg)
823832
}
824833

825834
if (msg == MSGID_SERVICE_ACCEPT) {
835+
/* RFC 4253 section 7.1: no service accept once the peer's KEXINIT
836+
* lands. connectState doesn't track a rekey. */
837+
if (ssh->isKeying & WOLFSSH_PEER_IS_KEYING) {
838+
WLOG(WS_LOG_DEBUG, "Message ID %u not allowed by %s %s",
839+
msg, "client", "when keying");
840+
ssh->error = WS_MSGID_NOT_ALLOWED_E;
841+
return 0;
842+
}
843+
826844
if (ssh->connectState == CONNECT_CLIENT_USERAUTH_REQUEST_SENT) {
827845
return 1;
828846
}
@@ -845,7 +863,7 @@ INLINE static int IsMessageAllowedClient(WOLFSSH *ssh, byte msg)
845863
if (msg == MSGID_KEXINIT) {
846864
WLOG(WS_LOG_DEBUG, "Message ID %u not allowed by %s %s",
847865
msg, "client", "when keying");
848-
ssh->error = WS_REKEYING;
866+
ssh->error = WS_MSGID_NOT_ALLOWED_E;
849867
return 0;
850868
}
851869

@@ -857,7 +875,7 @@ INLINE static int IsMessageAllowedClient(WOLFSSH *ssh, byte msg)
857875
WLOG(WS_LOG_DEBUG,
858876
"Message ID %u not the expected message %u",
859877
msg, ssh->handshake->expectMsgId);
860-
ssh->error = WS_REKEYING;
878+
ssh->error = WS_MSGID_NOT_ALLOWED_E;
861879
return 0;
862880
}
863881
else {

tests/regress.c

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2167,6 +2167,39 @@ static void TestChannelAllowedAfterAuth(WOLFSSH* ssh)
21672167
}
21682168

21692169

2170+
/* A service accept is allowed only when no key exchange is in flight.
2171+
* connectState does not see a rekey, so isKeying is checked too. */
2172+
static void TestClientServiceAcceptBlockedDuringKeying(WOLFSSH* ssh)
2173+
{
2174+
int allowed;
2175+
2176+
ResetSession(ssh);
2177+
ssh->connectState = CONNECT_CLIENT_USERAUTH_REQUEST_SENT;
2178+
2179+
allowed = wolfSSH_TestIsMessageAllowed(ssh, MSGID_SERVICE_ACCEPT,
2180+
WS_MSG_RECV);
2181+
AssertTrue(allowed);
2182+
2183+
/* Same connectState, peer's rekey KEXINIT arrived first. */
2184+
ssh->isKeying = WOLFSSH_PEER_IS_KEYING;
2185+
2186+
allowed = wolfSSH_TestIsMessageAllowed(ssh, MSGID_SERVICE_ACCEPT,
2187+
WS_MSG_RECV);
2188+
AssertFalse(allowed);
2189+
AssertIntEQ(ssh->error, WS_MSGID_NOT_ALLOWED_E);
2190+
2191+
/* Allowed when only this side has started a rekey: RFC 4253 section 7.1
2192+
* requires tolerating what the peer sent before it saw our KEXINIT. */
2193+
ResetSession(ssh);
2194+
ssh->connectState = CONNECT_CLIENT_USERAUTH_REQUEST_SENT;
2195+
ssh->isKeying = WOLFSSH_SELF_IS_KEYING;
2196+
2197+
allowed = wolfSSH_TestIsMessageAllowed(ssh, MSGID_SERVICE_ACCEPT,
2198+
WS_MSG_RECV);
2199+
AssertTrue(allowed);
2200+
}
2201+
2202+
21702203
/* Drive the whole receive path with a CHANNEL_OPEN sent from a pre-auth
21712204
* connectState: no channel created, no reply emitted. The connectState
21722205
* gate does the rejecting; isKeying below is scene-setting only. */
@@ -2325,6 +2358,64 @@ static void TestServerServiceRequestStateGated(WOLFSSH* ssh)
23252358
}
23262359

23272360

2361+
/* Drive the receive path with a SERVICE_REQUEST arriving mid-rekey.
2362+
* acceptState sits at ACCEPT_KEYED throughout, so only isKeying catches
2363+
* it. */
2364+
static void TestServerServiceRequestRejectedDuringKeying(void)
2365+
{
2366+
WOLFSSH_CTX* ctx;
2367+
WOLFSSH* ssh;
2368+
MemIo io;
2369+
byte payload[64];
2370+
byte pkt[128];
2371+
byte out[128];
2372+
word32 pktSz;
2373+
word32 idx = 0;
2374+
2375+
idx = AppendString(payload, sizeof(payload), idx, "ssh-userauth");
2376+
pktSz = WrapPacket(MSGID_SERVICE_REQUEST, payload, idx, pkt, sizeof(pkt));
2377+
2378+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
2379+
AssertNotNull(ctx);
2380+
wolfSSH_SetIORecv(ctx, MemRecv);
2381+
wolfSSH_SetIOSend(ctx, MemSend);
2382+
2383+
ssh = wolfSSH_new(ctx);
2384+
AssertNotNull(ssh);
2385+
2386+
MemIoInit(&io, pkt, pktSz, out, sizeof(out));
2387+
wolfSSH_SetIOReadCtx(ssh, &io);
2388+
wolfSSH_SetIOWriteCtx(ssh, &io);
2389+
2390+
ssh->acceptState = ACCEPT_KEYED;
2391+
/* The peer's rekey KEXINIT arrived, its NEWKEYS has not. */
2392+
ssh->isKeying = WOLFSSH_PEER_IS_KEYING;
2393+
2394+
AssertIntEQ(wolfSSH_TestDoReceive(ssh), WS_FATAL_ERROR);
2395+
AssertIntEQ(ssh->error, WS_MSGID_NOT_ALLOWED_E);
2396+
/* Not dispatched, so acceptState did not advance. */
2397+
AssertIntEQ(ssh->clientState, CLIENT_BEGIN);
2398+
AssertIntEQ(ssh->acceptState, ACCEPT_KEYED);
2399+
/* Nothing emitted in reply. */
2400+
AssertIntEQ(io.outSz, 0);
2401+
2402+
/* Allowed when only this side has started a rekey. */
2403+
ssh->error = 0;
2404+
ssh->isKeying = WOLFSSH_SELF_IS_KEYING;
2405+
AssertTrue(wolfSSH_TestIsMessageAllowed(ssh, MSGID_SERVICE_REQUEST,
2406+
WS_MSG_RECV));
2407+
2408+
/* Allowed once the key exchange is done. */
2409+
ssh->error = 0;
2410+
ssh->isKeying = 0;
2411+
AssertTrue(wolfSSH_TestIsMessageAllowed(ssh, MSGID_SERVICE_REQUEST,
2412+
WS_MSG_RECV));
2413+
2414+
wolfSSH_free(ssh);
2415+
wolfSSH_CTX_free(ctx);
2416+
}
2417+
2418+
23282419
static void TestChannelOpenCallbackRejectSendsOpenFail(void)
23292420
{
23302421
ChannelOpenHarness harness;
@@ -8501,6 +8592,7 @@ int main(int argc, char** argv)
85018592
TestChannelBlockedBeforeAuth(ssh);
85028593
TestChannelBlockedEveryPreAuthState(ssh);
85038594
TestChannelAllowedAfterAuth(ssh);
8595+
TestClientServiceAcceptBlockedDuringKeying(ssh);
85048596
TestChannelOpenRejectedBeforeKex(CONNECT_CLIENT_KEXINIT_SENT);
85058597
TestChannelOpenRejectedBeforeKex(CONNECT_CLIENT_KEXDH_INIT_SENT);
85068598
#ifndef NO_WOLFSSH_SERVER
@@ -8509,6 +8601,7 @@ int main(int argc, char** argv)
85098601
TestServerUserauthBlockedBeforeKeyed(serverSsh);
85108602
TestServerOnlyUserauthMsgsBlocked(serverSsh);
85118603
TestServerServiceRequestStateGated(serverSsh);
8604+
TestServerServiceRequestRejectedDuringKeying();
85128605
TestChannelOpenCallbackRejectSendsOpenFail();
85138606
TestSecondSessionChannelRejected();
85148607
TestUsernameChangeDisconnects();

0 commit comments

Comments
 (0)