Skip to content

Commit 0eb579f

Browse files
ejohnstownpadelsbach
authored andcommitted
internal: skip guessed KEX packets on server only
Every supported KEX method opens with a client message, so only the client can send a guessed packet (RFC 4253 section 7.1). DoKexInit() sets ignoreNextKexMsg only on the server and logs a server's flag on the client; DoKexDhReply() and DoKexDhGexGroup() no longer skip. - tests: client KEXINIT with a wrong-guess follows flag does not skip - tests: client KEXDH_REPLY and GEX_GROUP parse even with the flag set - tests: the skip case helper drives only the server Issue: F-10585
1 parent 41c13f6 commit 0eb579f

2 files changed

Lines changed: 72 additions & 40 deletions

File tree

‎src/internal.c‎

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6950,7 +6950,13 @@ static int DoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
69506950
if (ret == WS_SUCCESS) {
69516951
WLOG(WS_LOG_DEBUG, " packet follows: %s",
69526952
kexPacketFollows ? "yes" : "no");
6953-
if (kexPacketFollows
6953+
/* Every supported KEX starts with a client message, so only a
6954+
* client can send a guessed packet. Ignore the flag from a
6955+
* server. */
6956+
if (kexPacketFollows && side != WOLFSSH_ENDPOINT_SERVER) {
6957+
WLOG(WS_LOG_DEBUG, " ignoring server's packet follows flag");
6958+
}
6959+
else if (kexPacketFollows
69546960
&& (kexIdGuess != ssh->handshake->kexId
69556961
|| pubKeyIdGuess != ssh->handshake->pubKeyId)) {
69566962
ssh->handshake->ignoreNextKexMsg = 1;
@@ -8675,12 +8681,6 @@ static int DoKexDhReply(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
86758681
return ret;
86768682
}
86778683

8678-
if (ret == WS_SUCCESS) {
8679-
if (SkipGuessedKexMsg(ssh, "server's KEXDH_REPLY message",
8680-
len, idx))
8681-
return WS_SUCCESS;
8682-
}
8683-
86848684
if (ret == WS_SUCCESS && len < LENGTH_SZ*2 + *idx) {
86858685
ret = WS_BUFFER_E;
86868686
}
@@ -9402,14 +9402,6 @@ static int DoKexDhGexGroup(WOLFSSH* ssh,
94029402
ret = WS_BAD_ARGUMENT;
94039403

94049404
if (ret == WS_SUCCESS) {
9405-
/* A conformant server sends GROUP only in response to the client's
9406-
* REQUEST, so it should never set first_packet_follows here. Discard
9407-
* the message defensively if a peer sets it anyway, mirroring the other
9408-
* Do* handlers. */
9409-
if (SkipGuessedKexMsg(ssh, "server's KEXDH_GEX_GROUP message",
9410-
len, idx))
9411-
return WS_SUCCESS;
9412-
94139405
begin = *idx;
94149406
ret = GetMpint(&primeGroupSz, &primeGroup, buf, len, &begin);
94159407
if (ret == WS_SUCCESS && primeGroupSz > (MAX_KEX_KEY_SZ + 1)) {

‎tests/regress.c‎

Lines changed: 65 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16493,19 +16493,28 @@ typedef struct {
1649316493
const char* keyList;
1649416494
byte firstPacketFollows;
1649516495
byte expectIgnore;
16496+
byte endpointType;
1649616497
} FirstPacketFollowsCase;
1649716498

1649816499
static const FirstPacketFollowsCase firstPacketFollowsCases[] = {
1649916500
{ "follows=0, guesses irrelevant: flag stays off",
16500-
FPF_KEX_BAD "," FPF_KEX_GOOD, FPF_KEY_BAD "," FPF_KEY_GOOD, 0, 0 },
16501+
FPF_KEX_BAD "," FPF_KEX_GOOD, FPF_KEY_BAD "," FPF_KEY_GOOD, 0, 0,
16502+
WOLFSSH_ENDPOINT_SERVER },
1650116503
{ "follows=1, both guesses match: do not skip",
16502-
FPF_KEX_GOOD, FPF_KEY_GOOD, 1, 0 },
16504+
FPF_KEX_GOOD, FPF_KEY_GOOD, 1, 0, WOLFSSH_ENDPOINT_SERVER },
1650316505
{ "follows=1, KEX guess wrong: skip",
16504-
FPF_KEX_BAD "," FPF_KEX_GOOD, FPF_KEY_GOOD, 1, 1 },
16506+
FPF_KEX_BAD "," FPF_KEX_GOOD, FPF_KEY_GOOD, 1, 1,
16507+
WOLFSSH_ENDPOINT_SERVER },
1650516508
{ "follows=1, host-key guess wrong: skip", /* regression case */
16506-
FPF_KEX_GOOD, FPF_KEY_BAD "," FPF_KEY_GOOD, 1, 1 },
16509+
FPF_KEX_GOOD, FPF_KEY_BAD "," FPF_KEY_GOOD, 1, 1,
16510+
WOLFSSH_ENDPOINT_SERVER },
1650716511
{ "follows=1, both guesses wrong: skip",
16508-
FPF_KEX_BAD "," FPF_KEX_GOOD, FPF_KEY_BAD "," FPF_KEY_GOOD, 1, 1 },
16512+
FPF_KEX_BAD "," FPF_KEX_GOOD, FPF_KEY_BAD "," FPF_KEY_GOOD, 1, 1,
16513+
WOLFSSH_ENDPOINT_SERVER },
16514+
/* A server never sends a guessed packet; the client ignores the flag. */
16515+
{ "client, server follows=1, both guesses wrong: do not skip",
16516+
FPF_KEX_BAD "," FPF_KEX_GOOD, FPF_KEY_BAD "," FPF_KEY_GOOD, 1, 0,
16517+
WOLFSSH_ENDPOINT_CLIENT },
1650916518
};
1651016519

1651116520
static void RunFirstPacketFollowsCase(const FirstPacketFollowsCase* tc)
@@ -16516,7 +16525,7 @@ static void RunFirstPacketFollowsCase(const FirstPacketFollowsCase* tc)
1651616525
word32 payloadSz;
1651716526
word32 idx = 0;
1651816527

16519-
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
16528+
ctx = wolfSSH_CTX_new(tc->endpointType, NULL);
1652016529
AssertNotNull(ctx);
1652116530

1652216531
ssh = wolfSSH_new(ctx);
@@ -16535,6 +16544,11 @@ static void RunFirstPacketFollowsCase(const FirstPacketFollowsCase* tc)
1653516544
(void)wolfSSH_TestDoKexInit(ssh, payload, payloadSz, &idx);
1653616545

1653716546
AssertNotNull(ssh->handshake);
16547+
/* The algorithm ids are set only after first_packet_follows is read. */
16548+
if (ssh->handshake->peerEncryptId == ID_NONE) {
16549+
Fail(("DoKexInit parses past first_packet_follows (%s)",
16550+
tc->description), ("peerEncryptId == ID_NONE"));
16551+
}
1653816552
if (ssh->handshake->ignoreNextKexMsg != tc->expectIgnore) {
1653916553
Fail(("ignoreNextKexMsg == %u (%s)",
1654016554
tc->expectIgnore, tc->description),
@@ -16548,29 +16562,26 @@ static void RunFirstPacketFollowsCase(const FirstPacketFollowsCase* tc)
1654816562
typedef int (*FirstPacketFollowsSkipFn)(WOLFSSH* ssh, byte* buf, word32 len,
1654916563
word32* idx);
1655016564

16551-
/* With ignoreNextKexMsg set, the target Do* handler must consume the packet,
16552-
* clear the flag, and not advance the peer's state past KEXINIT_DONE. */
16565+
/* With ignoreNextKexMsg set, the server's Do* handler must consume the packet,
16566+
* clear the flag, and not advance the client's state past KEXINIT_DONE. */
1655316567
static void RunFirstPacketFollowsSkipCase(FirstPacketFollowsSkipFn fn,
16554-
const char* label, byte endpointType, byte initState)
16568+
const char* label)
1655516569
{
1655616570
WOLFSSH_CTX* ctx;
1655716571
WOLFSSH* ssh;
1655816572
byte payload[8];
1655916573
word32 idx = 0;
1656016574
int ret;
1656116575

16562-
ctx = wolfSSH_CTX_new(endpointType, NULL);
16576+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
1656316577
AssertNotNull(ctx);
1656416578

1656516579
ssh = wolfSSH_new(ctx);
1656616580
AssertNotNull(ssh);
1656716581
AssertNotNull(ssh->handshake);
1656816582

1656916583
ssh->handshake->ignoreNextKexMsg = 1;
16570-
if (endpointType == WOLFSSH_ENDPOINT_SERVER)
16571-
ssh->clientState = initState;
16572-
else
16573-
ssh->serverState = initState;
16584+
ssh->clientState = CLIENT_KEXINIT_DONE;
1657416585

1657516586
/* Garbage payload that must never be parsed when skipped. */
1657616587
WMEMSET(payload, 0xAB, sizeof(payload));
@@ -16581,10 +16592,38 @@ static void RunFirstPacketFollowsSkipCase(FirstPacketFollowsSkipFn fn,
1658116592
}
1658216593
AssertIntEQ(idx, sizeof(payload));
1658316594
AssertIntEQ(ssh->handshake->ignoreNextKexMsg, 0);
16584-
if (endpointType == WOLFSSH_ENDPOINT_SERVER)
16585-
AssertIntEQ(ssh->clientState, initState);
16586-
else
16587-
AssertIntEQ(ssh->serverState, initState);
16595+
AssertIntEQ(ssh->clientState, CLIENT_KEXINIT_DONE);
16596+
16597+
wolfSSH_free(ssh);
16598+
wolfSSH_CTX_free(ctx);
16599+
}
16600+
16601+
/* The client never discards a server KEX message as a guess; even with the
16602+
* flag forced on, the handler must parse the packet and reject the garbage. */
16603+
static void RunFirstPacketFollowsClientNoSkipCase(FirstPacketFollowsSkipFn fn,
16604+
const char* label)
16605+
{
16606+
WOLFSSH_CTX* ctx;
16607+
WOLFSSH* ssh;
16608+
byte payload[8];
16609+
word32 idx = 0;
16610+
int ret;
16611+
16612+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
16613+
AssertNotNull(ctx);
16614+
ssh = wolfSSH_new(ctx);
16615+
AssertNotNull(ssh);
16616+
AssertNotNull(ssh->handshake);
16617+
16618+
ssh->handshake->ignoreNextKexMsg = 1;
16619+
ssh->serverState = SERVER_KEXINIT_DONE;
16620+
WMEMSET(payload, 0xAB, sizeof(payload));
16621+
16622+
ret = fn(ssh, payload, sizeof(payload), &idx);
16623+
if (ret == WS_SUCCESS)
16624+
Fail(("%s rejects the packet on a client", label), ("%d", ret));
16625+
AssertIntEQ(idx, 0);
16626+
AssertIntEQ(ssh->handshake->ignoreNextKexMsg, 1);
1658816627

1658916628
wolfSSH_free(ssh);
1659016629
wolfSSH_CTX_free(ctx);
@@ -16637,13 +16676,10 @@ static void RunFirstPacketFollowsCrossBoundaryCase(FirstPacketFollowsSkipFn fn,
1663716676

1663816677
static void TestFirstPacketFollowsSkipped(void)
1663916678
{
16640-
RunFirstPacketFollowsSkipCase(wolfSSH_TestDoKexDhInit,
16641-
"DoKexDhInit", WOLFSSH_ENDPOINT_SERVER, CLIENT_KEXINIT_DONE);
16679+
RunFirstPacketFollowsSkipCase(wolfSSH_TestDoKexDhInit, "DoKexDhInit");
1664216680
#ifndef WOLFSSH_NO_DH_GEX_SHA256
1664316681
RunFirstPacketFollowsSkipCase(wolfSSH_TestDoKexDhGexRequest,
16644-
"DoKexDhGexRequest", WOLFSSH_ENDPOINT_SERVER, CLIENT_KEXINIT_DONE);
16645-
RunFirstPacketFollowsSkipCase(wolfSSH_TestDoKexDhGexGroup,
16646-
"DoKexDhGexGroup", WOLFSSH_ENDPOINT_CLIENT, SERVER_KEXINIT_DONE);
16682+
"DoKexDhGexRequest");
1664716683
/* Guess/negotiation straddling the GEX boundary, both directions. */
1664816684
#ifndef NO_WOLFSSH_SERVER
1664916685
RunFirstPacketFollowsCrossBoundaryCase(wolfSSH_TestDoKexDhInit,
@@ -16652,8 +16688,12 @@ static void TestFirstPacketFollowsSkipped(void)
1665216688
"DoKexDhGexRequest->KEXDH_INIT", MSGID_KEXDH_INIT);
1665316689
#endif /* NO_WOLFSSH_SERVER */
1665416690
#endif /* WOLFSSH_NO_DH_GEX_SHA256 */
16655-
RunFirstPacketFollowsSkipCase(wolfSSH_TestDoKexDhReply,
16656-
"DoKexDhReply", WOLFSSH_ENDPOINT_CLIENT, SERVER_KEXINIT_DONE);
16691+
RunFirstPacketFollowsClientNoSkipCase(wolfSSH_TestDoKexDhReply,
16692+
"DoKexDhReply");
16693+
#ifndef WOLFSSH_NO_DH_GEX_SHA256
16694+
RunFirstPacketFollowsClientNoSkipCase(wolfSSH_TestDoKexDhGexGroup,
16695+
"DoKexDhGexGroup");
16696+
#endif
1665716697
}
1665816698

1665916699
static void TestFirstPacketFollows(void)

0 commit comments

Comments
 (0)