Skip to content

Commit 48ca213

Browse files
yosuke-wolfsslejohnstown
authored andcommitted
internal, tests: start no highwater rekey after an inbound packet fails
- DoReceive() sets highwaterFlag and msgHighwaterFlag when an inbound packet fails decryption, its MAC, or its length or alignment check. - DoPacket() sets both flags when padding_length is below MIN_PAD_LENGTH or leaves no room for the message id. - The highwaterFlag and msgHighwaterFlag comments name that writer. - The wolfSSH_SendPacket() comment limits its ssh->error claim to the flush and says HighwaterCheck() writes nothing there but its callback may. - unit.c adds test_HighwaterQuietAfterBadPacket(): an idle worker pass fires the highwater callback, and a bad-MAC worker pass and the send after it do not. - unit.c adds test_DoReceive_RejectsPaddingUnderflow(), and the VerifyMacFailure, AeadTagFailure, RejectsShortPadding, RejectsMisalignedPacket and WorkerHardRecvErrorOutranksFlush tests check both flags are set. VerifyMacFailure clears them per case.
1 parent ec4b478 commit 48ca213

3 files changed

Lines changed: 223 additions & 5 deletions

File tree

‎src/internal.c‎

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5599,9 +5599,9 @@ static int SendPacketFlush(WOLFSSH* ssh)
55995599
}
56005600

56015601

5602-
/* returns WS_SUCCESS on success. Transport failures record their code in
5603-
* ssh->error, so a later write to that field on the same pass has to be
5604-
* conditional on this having succeeded, or it hides the dead transport. */
5602+
/* returns WS_SUCCESS on success. Flush failures record their code in
5603+
* ssh->error, so a later write there on the same pass must wait for this to
5604+
* succeed. HighwaterCheck() writes nothing there, but its callback may. */
56055605
int wolfSSH_SendPacket(WOLFSSH* ssh)
56065606
{
56075607
int ret;
@@ -13997,11 +13997,16 @@ static int DoPacket(WOLFSSH* ssh, byte* bufferConsumed)
1399713997
if (padSz < MIN_PAD_LENGTH) {
1399813998
WLOG(WS_LOG_DEBUG, "Packet padding length %u below minimum %u",
1399913999
(word32)padSz, (word32)MIN_PAD_LENGTH);
14000+
/* Retries fail on this unconsumed packet, so no rekey may start. */
14001+
ssh->highwaterFlag = 1;
14002+
ssh->msgHighwaterFlag = 1;
1400014003
return WS_BUFFER_E;
1400114004
}
1400214005

1400314006
/* check for underflow */
1400414007
if ((word32)(PAD_LENGTH_SZ + padSz + MSG_ID_SZ) > pktSz) {
14008+
ssh->highwaterFlag = 1;
14009+
ssh->msgHighwaterFlag = 1;
1400514010
return WS_OVERFLOW_E;
1400614011
}
1400714012

@@ -14772,6 +14777,10 @@ int DoReceive(WOLFSSH* ssh)
1477214777
if (ret != WS_SUCCESS) {
1477314778
WLOG(WS_LOG_DEBUG, "PR: First decrypt fail");
1477414779
ssh->error = ret;
14780+
/* The input cannot be resynced, so no highwater rekey
14781+
* may start. */
14782+
ssh->highwaterFlag = 1;
14783+
ssh->msgHighwaterFlag = 1;
1477514784
return WS_FATAL_ERROR;
1477614785
}
1477714786
}
@@ -14789,6 +14798,8 @@ int DoReceive(WOLFSSH* ssh)
1478914798
WLOG(WS_LOG_DEBUG, "Packet length overflow: size = %u",
1479014799
ssh->curSz);
1479114800
ssh->error = WS_OVERFLOW_E;
14801+
ssh->highwaterFlag = 1;
14802+
ssh->msgHighwaterFlag = 1;
1479214803
return WS_FATAL_ERROR;
1479314804
}
1479414805

@@ -14804,6 +14815,8 @@ int DoReceive(WOLFSSH* ssh)
1480414815
"block = %u, aead = %u",
1480514816
alignSz, (word32)alignBlockSz, (word32)aeadMode);
1480614817
ssh->error = WS_BUFFER_E;
14818+
ssh->highwaterFlag = 1;
14819+
ssh->msgHighwaterFlag = 1;
1480714820
return WS_FATAL_ERROR;
1480814821
}
1480914822
ssh->processReplyState = PROCESS_PACKET_FINISH;
@@ -14843,11 +14856,15 @@ int DoReceive(WOLFSSH* ssh)
1484314856
if (ret != WS_SUCCESS) {
1484414857
WLOG(WS_LOG_DEBUG, "PR: Decrypt fail");
1484514858
ssh->error = ret;
14859+
ssh->highwaterFlag = 1;
14860+
ssh->msgHighwaterFlag = 1;
1484614861
return WS_FATAL_ERROR;
1484714862
}
1484814863
if (verifyResult != WS_SUCCESS) {
1484914864
WLOG(WS_LOG_DEBUG, "PR: VerifyMac fail");
1485014865
ssh->error = verifyResult;
14866+
ssh->highwaterFlag = 1;
14867+
ssh->msgHighwaterFlag = 1;
1485114868
return WS_FATAL_ERROR;
1485214869
}
1485314870
}
@@ -14867,6 +14884,8 @@ int DoReceive(WOLFSSH* ssh)
1486714884
if (ret != WS_SUCCESS) {
1486814885
WLOG(WS_LOG_DEBUG, "PR: DecryptAead fail");
1486914886
ssh->error = ret;
14887+
ssh->highwaterFlag = 1;
14888+
ssh->msgHighwaterFlag = 1;
1487014889
return WS_FATAL_ERROR;
1487114890
}
1487214891
#endif

‎tests/unit.c‎

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1731,6 +1731,8 @@ static int test_DoReceive_VerifyMacFailure(void)
17311731
ssh->curSz = 0;
17321732
ssh->processReplyState = PROCESS_INIT;
17331733
ssh->error = 0;
1734+
ssh->highwaterFlag = 0;
1735+
ssh->msgHighwaterFlag = 0;
17341736

17351737
flatSeq[0] = (byte)(ssh->peerSeq >> 24);
17361738
flatSeq[1] = (byte)(ssh->peerSeq >> 16);
@@ -1781,6 +1783,10 @@ static int test_DoReceive_VerifyMacFailure(void)
17811783
result = -207;
17821784
goto done;
17831785
}
1786+
if (!ssh->highwaterFlag || !ssh->msgHighwaterFlag) {
1787+
result = -209;
1788+
goto done;
1789+
}
17841790
}
17851791

17861792
done:
@@ -1886,6 +1892,10 @@ static int test_DoReceive_AeadTagFailure(void)
18861892
result = -229;
18871893
goto done;
18881894
}
1895+
if (!ssh->highwaterFlag || !ssh->msgHighwaterFlag) {
1896+
result = -230;
1897+
goto done;
1898+
}
18891899

18901900
done:
18911901
if (aesInited)
@@ -2130,6 +2140,10 @@ static int test_DoReceive_RejectsShortPadding(void)
21302140
result = -764;
21312141
goto done2;
21322142
}
2143+
if (!ssh->highwaterFlag || !ssh->msgHighwaterFlag) {
2144+
result = -765;
2145+
goto done2;
2146+
}
21332147

21342148
done2:
21352149
wolfSSH_free(ssh);
@@ -2138,6 +2152,58 @@ static int test_DoReceive_RejectsShortPadding(void)
21382152
}
21392153

21402154

2155+
/* Verify DoReceive rejects a cleartext packet whose padding_length leaves no
2156+
* room for the message id, returning WS_OVERFLOW_E, and stops the highwater
2157+
* callback. Layout: packet_length=4, padding_length=4 => 8 bytes total. */
2158+
static int test_DoReceive_RejectsPaddingUnderflow(void)
2159+
{
2160+
WOLFSSH_CTX* ctx = NULL;
2161+
WOLFSSH* ssh = NULL;
2162+
int ret;
2163+
int result = 0;
2164+
byte pkt[8];
2165+
word32 totalLen = (word32)sizeof(pkt);
2166+
2167+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
2168+
if (ctx == NULL)
2169+
return -766;
2170+
ssh = wolfSSH_new(ctx);
2171+
if (ssh == NULL) {
2172+
wolfSSH_CTX_free(ctx);
2173+
return -767;
2174+
}
2175+
2176+
WMEMSET(pkt, 0, sizeof(pkt));
2177+
pkt[3] = 4; /* packet_length */
2178+
pkt[4] = MIN_PAD_LENGTH;
2179+
2180+
ShrinkBuffer(&ssh->inputBuffer, 1);
2181+
ret = GrowBuffer(&ssh->inputBuffer, totalLen);
2182+
if (ret != WS_SUCCESS) {
2183+
result = -768;
2184+
goto done;
2185+
}
2186+
WMEMCPY(ssh->inputBuffer.buffer, pkt, totalLen);
2187+
ssh->inputBuffer.length = totalLen;
2188+
ssh->inputBuffer.idx = 0;
2189+
2190+
ret = wolfSSH_TestDoReceive(ssh);
2191+
if (ret != WS_FATAL_ERROR || ssh->error != WS_OVERFLOW_E) {
2192+
result = -769;
2193+
goto done;
2194+
}
2195+
if (!ssh->highwaterFlag || !ssh->msgHighwaterFlag) {
2196+
result = -793;
2197+
goto done;
2198+
}
2199+
2200+
done:
2201+
wolfSSH_free(ssh);
2202+
wolfSSH_CTX_free(ctx);
2203+
return result;
2204+
}
2205+
2206+
21412207
/* Verify DoReceive rejects a cleartext binary packet whose length field is
21422208
* not block aligned. The packet is valid in every other respect, so the
21432209
* RFC 4253 section 6 alignment check is the only thing that can reject it. */
@@ -2197,6 +2263,10 @@ static int test_DoReceive_RejectsMisalignedPacket(void)
21972263
result = -774;
21982264
goto done3;
21992265
}
2266+
if (!ssh->highwaterFlag || !ssh->msgHighwaterFlag) {
2267+
result = -794;
2268+
goto done3;
2269+
}
22002270

22012271
done3:
22022272
wolfSSH_free(ssh);
@@ -6606,6 +6676,117 @@ static int test_WorkerReportsExtDataChannelKeying(void)
66066676
return result;
66076677
}
66086678

6679+
#if defined(WOLFSSH_TEST_INTERNAL) && !defined(WOLFSSH_NO_HMAC_SHA2_256)
6680+
/* An inbound packet that fails its checks starts no highwater rekey, neither
6681+
* from the worker's flush on that pass nor from a later send. */
6682+
static int test_HighwaterQuietAfterBadPacket(void)
6683+
{
6684+
WOLFSSH_CTX* ctx = NULL;
6685+
WOLFSSH* ssh = NULL;
6686+
HwTestCtx hc;
6687+
Hmac hmac;
6688+
int result = 0;
6689+
int ret;
6690+
word32 prefixLen;
6691+
byte seq[LENGTH_SZ];
6692+
byte macKey[WC_SHA256_DIGEST_SIZE];
6693+
byte digest[WC_SHA256_DIGEST_SIZE];
6694+
byte pkt[UINT32_SZ + 12 + WC_SHA256_DIGEST_SIZE];
6695+
6696+
WMEMSET(&hc, 0, sizeof(hc));
6697+
s_extSendCount = 0;
6698+
6699+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
6700+
if (ctx == NULL)
6701+
return -1950;
6702+
wolfSSH_SetIOSend(ctx, CountIoSend);
6703+
wolfSSH_SetIORecv(ctx, PacketIoRecv);
6704+
wolfSSH_SetHighwaterCb(ctx, 1, HwTestCb);
6705+
6706+
ssh = wolfSSH_new(ctx);
6707+
if (ssh == NULL) { result = -1951; goto done; }
6708+
hc.ssh = ssh;
6709+
wolfSSH_SetHighwaterCtx(ssh, &hc);
6710+
wolfSSH_SetMsgHighwater(ssh, 1);
6711+
ssh->txCount = 1;
6712+
ssh->txMsgCount = 1;
6713+
6714+
/* Control: the flush after an idle receive fires the callback. */
6715+
ssh->outputBuffer.length = 1;
6716+
ssh->outputBuffer.idx = 0;
6717+
ssh->outputBuffer.buffer[0] = 0;
6718+
ret = wolfSSH_worker(ssh, NULL);
6719+
if (ret != WS_FATAL_ERROR || wolfSSH_get_error(ssh) != WS_WANT_READ) {
6720+
result = -1952;
6721+
goto done;
6722+
}
6723+
if (hc.count != 1) { result = -1953; goto done; }
6724+
6725+
/* A new key epoch clears both flags. */
6726+
ssh->highwaterFlag = 0;
6727+
ssh->msgHighwaterFlag = 0;
6728+
hc.count = 0;
6729+
6730+
/* An IGNORE packet under HMAC-SHA2-256 at peerSeq 0, one MAC bit off. */
6731+
prefixLen = BuildMacTestPacketPrefix(MSGID_IGNORE, 10, pkt, sizeof(pkt));
6732+
if (prefixLen == 0) { result = -1954; goto done; }
6733+
WMEMSET(macKey, 0xA5, sizeof(macKey));
6734+
WMEMSET(seq, 0, sizeof(seq));
6735+
ret = wc_HmacInit(&hmac, ssh->ctx->heap, INVALID_DEVID);
6736+
if (ret == 0) {
6737+
ret = wc_HmacSetKey(&hmac, WC_SHA256, macKey, sizeof(macKey));
6738+
if (ret == 0)
6739+
ret = wc_HmacUpdate(&hmac, seq, sizeof(seq));
6740+
if (ret == 0)
6741+
ret = wc_HmacUpdate(&hmac, pkt, prefixLen);
6742+
if (ret == 0)
6743+
ret = wc_HmacFinal(&hmac, digest);
6744+
wc_HmacFree(&hmac);
6745+
}
6746+
if (ret != 0) { result = -1955; goto done; }
6747+
WMEMCPY(pkt + prefixLen, digest, sizeof(digest));
6748+
pkt[prefixLen] ^= 0x01;
6749+
6750+
ssh->peerMacId = ID_HMAC_SHA2_256;
6751+
ssh->peerMacSz = WC_SHA256_DIGEST_SIZE;
6752+
WMEMCPY(ssh->peerKeys.macKey, macKey, sizeof(macKey));
6753+
ssh->peerKeys.macKeySz = sizeof(macKey);
6754+
s_recvPkt = pkt;
6755+
s_recvPktSz = prefixLen + WC_SHA256_DIGEST_SIZE;
6756+
s_recvPktOff = 0;
6757+
6758+
/* The worker still flushes what was queued, but fires nothing. */
6759+
ssh->outputBuffer.length = 1;
6760+
ssh->outputBuffer.idx = 0;
6761+
ssh->outputBuffer.buffer[0] = 0;
6762+
s_extSendCount = 0;
6763+
ret = wolfSSH_worker(ssh, NULL);
6764+
if (ret != WS_FATAL_ERROR || wolfSSH_get_error(ssh) != WS_VERIFY_MAC_E) {
6765+
result = -1957;
6766+
goto done;
6767+
}
6768+
if (wolfSSH_OutputPending(ssh) || s_extSendCount == 0) {
6769+
result = -1958;
6770+
goto done;
6771+
}
6772+
if (hc.count != 0) { result = -1959; goto done; }
6773+
6774+
/* Nor does the next send, as a teardown would make. */
6775+
ret = wolfSSH_SendIgnore(ssh, NULL, 0);
6776+
if (ret != WS_SUCCESS) { result = -1960; goto done; }
6777+
if (hc.count != 0) { result = -1961; goto done; }
6778+
6779+
done:
6780+
s_recvPkt = NULL;
6781+
s_recvPktSz = 0;
6782+
s_recvPktOff = 0;
6783+
s_extSendCount = 0;
6784+
wolfSSH_free(ssh);
6785+
wolfSSH_CTX_free(ctx);
6786+
return result;
6787+
}
6788+
#endif /* WOLFSSH_TEST_INTERNAL && !WOLFSSH_NO_HMAC_SHA2_256 */
6789+
66096790
/* channelId=0, type=1 (stderr), dataSz=10, payload all 0x44. */
66106791
static const byte s_workerExtBlob[] = {
66116792
0x00, 0x00, 0x00, 0x00,
@@ -6811,6 +6992,10 @@ static int test_WorkerHardRecvErrorOutranksFlush(void)
68116992
ret = wolfSSH_worker(ssh, NULL);
68126993
if (ret != WS_FATAL_ERROR) { result = -1735; goto done; }
68136994
if (wolfSSH_get_error(ssh) != WS_OVERFLOW_E) { result = -1736; goto done; }
6995+
if (!ssh->highwaterFlag || !ssh->msgHighwaterFlag) {
6996+
result = -1784;
6997+
goto done;
6998+
}
68146999

68157000
done:
68167001
s_recvPkt = NULL;
@@ -22806,6 +22991,11 @@ int wolfSSH_UnitTest(int argc, char** argv)
2280622991
printf("DoReceiveRejectsShortPadding: %s\n",
2280722992
(unitResult == 0 ? "SUCCESS" : "FAILED"));
2280822993
testResult = testResult || unitResult;
22994+
22995+
unitResult = test_DoReceive_RejectsPaddingUnderflow();
22996+
printf("DoReceiveRejectsPaddingUnderflow: %s\n",
22997+
(unitResult == 0 ? "SUCCESS" : "FAILED"));
22998+
testResult = testResult || unitResult;
2280922999
#endif
2281023000

2281123001
#ifdef WOLFSSH_TEST_INTERNAL
@@ -23034,6 +23224,13 @@ int wolfSSH_UnitTest(int argc, char** argv)
2303423224
(unitResult == 0 ? "SUCCESS" : "FAILED"));
2303523225
testResult = testResult || unitResult;
2303623226

23227+
#if defined(WOLFSSH_TEST_INTERNAL) && !defined(WOLFSSH_NO_HMAC_SHA2_256)
23228+
unitResult = test_HighwaterQuietAfterBadPacket();
23229+
printf("HighwaterQuietAfterBadPacket: %s\n",
23230+
(unitResult == 0 ? "SUCCESS" : "FAILED"));
23231+
testResult = testResult || unitResult;
23232+
#endif
23233+
2303723234
unitResult = test_WorkerFlushesOnIdleReceive();
2303823235
printf("WorkerFlushesOnIdleReceive: %s\n",
2303923236
(unitResult == 0 ? "SUCCESS" : "FAILED"));

‎wolfssh/internal.h‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,8 +1203,10 @@ struct WOLFSSH {
12031203
word32 txFlushCount; /* Output buffer drained, whatever came after */
12041204
word32 highwaterMark;
12051205
word32 msgHighwaterMark; /* Per-key packet limit (RFC 4344 Sec 3.1) */
1206-
byte highwaterFlag; /* Set when highwater CB called */
1207-
byte msgHighwaterFlag; /* Set when msg-count highwater CB called */
1206+
byte highwaterFlag; /* Set when highwater CB called, or when an
1207+
* inbound packet fails a framing or integrity
1208+
* check */
1209+
byte msgHighwaterFlag; /* Same, for the msg-count highwater CB */
12081210
void* highwaterCtx; /* Highwater CB context */
12091211
void* globalReqCtx; /* Global Request CB context */
12101212
void* reqSuccessCtx; /* Global Request Success CB context */

0 commit comments

Comments
 (0)