Skip to content

Commit 41e8e94

Browse files
internal: reject inbound packets that are not block aligned
- DoReceive() validates the peeked packet_length in PROCESS_PACKET_LENGTH: UINT32_SZ plus curSz for non-AEAD, curSz alone for AEAD, against peerBlockSz floored at MIN_BLOCK_SZ. A non-zero remainder sets ssh->error to WS_BUFFER_E and returns WS_FATAL_ERROR. - BuildPacket() in regress.c and BuildMacTestPacketPrefix() in unit.c pad to a block-aligned total, the latter taking padLen from the caller; test_DoReceive_VerifyMacFailure, test_DoReceive_AeadTagFailure, and test_DoReceive_RejectsShortPadding follow. - test_DoReceive_RejectsMisalignedPacket and test_DoReceive_RejectsMisalignedAead cover the cleartext and AES-GCM paths. Issue: F-8834
1 parent 581053b commit 41e8e94

3 files changed

Lines changed: 226 additions & 20 deletions

File tree

src/internal.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12813,10 +12813,12 @@ int DoReceive(WOLFSSH* ssh)
1281312813
int ret = WS_SUCCESS;
1281412814
int verifyResult;
1281512815
word32 readSz;
12816+
word32 alignSz;
1281612817
byte peerBlockSz = ssh->peerBlockSz;
1281712818
byte peerMacSz = ssh->peerMacSz;
1281812819
byte aeadMode = ssh->peerAeadMode;
1281912820
byte bufferConsumed = 0;
12821+
byte alignBlockSz;
1282012822

1282112823
switch (ssh->processReplyState) {
1282212824
case PROCESS_INIT:
@@ -12855,6 +12857,19 @@ int DoReceive(WOLFSSH* ssh)
1285512857
ssh->error = WS_OVERFLOW_E;
1285612858
return WS_FATAL_ERROR;
1285712859
}
12860+
12861+
/* RFC 4253 section 6: the packet is a multiple of the cipher
12862+
* block size, or 8, whichever is larger. */
12863+
alignBlockSz = peerBlockSz < MIN_BLOCK_SZ ?
12864+
MIN_BLOCK_SZ : peerBlockSz;
12865+
alignSz = aeadMode ? ssh->curSz : UINT32_SZ + ssh->curSz;
12866+
if (alignSz % alignBlockSz != 0) {
12867+
WLOG(WS_LOG_DEBUG,
12868+
"Packet not block aligned: size = %u, block = %u",
12869+
alignSz, (word32)alignBlockSz);
12870+
ssh->error = WS_BUFFER_E;
12871+
return WS_FATAL_ERROR;
12872+
}
1285812873
ssh->processReplyState = PROCESS_PACKET_FINISH;
1285912874
FALL_THROUGH;
1286012875

tests/regress.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,10 @@ static HandshakeInfo* AllocHandshake(WOLFSSH* ssh)
107107
}
108108

109109
/* Build a minimal SSH binary packet carrying only a message ID.
110-
* Layout: uint32 packetLen, byte padLen, payload[msgId], pad[padLen].
111-
* Choose padLen so total is 8-byte aligned for the clear transport case. */
110+
* Layout: uint32 packetLen, byte padLen, payload[msgId], pad[padLen]. */
112111
static word32 BuildPacket(byte msgId, byte* out, word32 outSz)
113112
{
114-
byte padLen = 6; /* 1 (msgId) +1 (padLen) +6 = 8 */
113+
byte padLen = 10; /* 4 (len) +1 (padLen) +1 (msgId) +10 = 16 */
115114
word32 packetLen = 1 + 1 + padLen; /* payload + padLen field + pad */
116115
word32 need = 4 + packetLen;
117116

tests/unit.c

Lines changed: 209 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,10 +1246,10 @@ static int test_OpenSshFormatNonCompositeRejected(void)
12461246
!defined(WOLFSSH_NO_AES_GCM))
12471247

12481248
/* Minimal SSH binary packet: uint32 length, padding_length, msgId, padding.
1249-
* Same layout as tests/regress.c BuildPacket (8-byte aligned body). */
1250-
static word32 BuildMacTestPacketPrefix(byte msgId, byte* out, word32 outSz)
1249+
* The caller picks padLen to get the alignment it wants. */
1250+
static word32 BuildMacTestPacketPrefix(byte msgId, byte padLen,
1251+
byte* out, word32 outSz)
12511252
{
1252-
byte padLen = 6;
12531253
word32 packetLen = (word32)(1 + 1 + padLen);
12541254
word32 need = UINT32_SZ + packetLen;
12551255

@@ -1284,7 +1284,7 @@ static int test_DoReceive_VerifyMacFailure(void)
12841284
Hmac hmac;
12851285
word32 prefixLen;
12861286
word32 totalLen;
1287-
byte pkt[UINT32_SZ + 8 + MAX_HMAC_SZ];
1287+
byte pkt[UINT32_SZ + 12 + MAX_HMAC_SZ];
12881288
int i;
12891289
struct {
12901290
byte macId;
@@ -1320,7 +1320,8 @@ static int test_DoReceive_VerifyMacFailure(void)
13201320
WMEMSET(macKey, 0xA5, sizeof(macKey));
13211321

13221322
for (i = 0; i < (int)(sizeof(cases) / sizeof(cases[0])); i++) {
1323-
prefixLen = BuildMacTestPacketPrefix(MSGID_IGNORE, pkt, sizeof(pkt));
1323+
prefixLen = BuildMacTestPacketPrefix(MSGID_IGNORE, 10,
1324+
pkt, sizeof(pkt));
13241325
if (prefixLen == 0) {
13251326
result = -202;
13261327
goto done;
@@ -1412,8 +1413,8 @@ static int test_DoReceive_AeadTagFailure(void)
14121413
int aesInited = 0;
14131414
byte key[AES_256_KEY_SIZE];
14141415
byte iv[GCM_NONCE_MID_SZ];
1415-
byte pkt[UINT32_SZ + 8];
1416-
byte record[UINT32_SZ + 8 + AES_BLOCK_SIZE];
1416+
byte pkt[UINT32_SZ + 16];
1417+
byte record[UINT32_SZ + 16 + AES_BLOCK_SIZE];
14171418
word32 prefixLen, payloadSz, totalLen;
14181419

14191420
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
@@ -1428,7 +1429,8 @@ static int test_DoReceive_AeadTagFailure(void)
14281429
WMEMSET(key, 0x5A, sizeof(key));
14291430
WMEMSET(iv, 0x31, sizeof(iv));
14301431

1431-
prefixLen = BuildMacTestPacketPrefix(MSGID_IGNORE, pkt, sizeof(pkt));
1432+
prefixLen = BuildMacTestPacketPrefix(MSGID_IGNORE, 14,
1433+
pkt, sizeof(pkt));
14321434
if (prefixLen == 0) {
14331435
result = -222;
14341436
goto done;
@@ -1466,7 +1468,7 @@ static int test_DoReceive_AeadTagFailure(void)
14661468
ssh->decryptCipher.cipherType = ID_AES256_GCM;
14671469
ssh->peerEncryptId = ID_AES256_GCM;
14681470
ssh->peerAeadMode = 1;
1469-
ssh->peerBlockSz = UINT32_SZ;
1471+
ssh->peerBlockSz = AES_BLOCK_SIZE;
14701472
ssh->peerMacSz = AES_BLOCK_SIZE;
14711473
WMEMCPY(ssh->peerKeys.iv, iv, sizeof(iv));
14721474
ssh->peerKeys.ivSz = sizeof(iv);
@@ -1685,11 +1687,11 @@ static int test_DoReceive_RejectsShortPadding(void)
16851687
int ret;
16861688
int result = 0;
16871689
/* A well-formed MSGID_IGNORE packet carrying an empty string, but with
1688-
* padding_length = 1 (below MIN_PAD_LENGTH). Aside from the short padding
1689-
* the packet parses cleanly, so the padding check is the only thing that
1690-
* can reject it. Layout: uint32 packet_length=7, padding_length=1,
1691-
* msgId, uint32 string_len=0, 1 pad byte => 11 bytes total. */
1692-
byte pkt[11];
1690+
* padding_length = 1 (below MIN_PAD_LENGTH). The 16-byte total keeps the
1691+
* packet block aligned so the padding check is the only thing that can
1692+
* reject it. Layout: uint32 packet_length=12, padding_length=1, msgId,
1693+
* uint32 string_len=0, 6 pad bytes => 16 bytes total. */
1694+
byte pkt[16];
16931695
word32 totalLen = (word32)sizeof(pkt);
16941696

16951697
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
@@ -1701,11 +1703,11 @@ static int test_DoReceive_RejectsShortPadding(void)
17011703
return -761;
17021704
}
17031705

1704-
pkt[0] = 0; pkt[1] = 0; pkt[2] = 0; pkt[3] = 7; /* packet_length */
1706+
WMEMSET(pkt, 0, sizeof(pkt));
1707+
pkt[3] = 12; /* packet_length */
17051708
pkt[4] = 1; /* padding_length, below MIN_PAD_LENGTH (4) */
17061709
pkt[5] = MSGID_IGNORE;
1707-
pkt[6] = 0; pkt[7] = 0; pkt[8] = 0; pkt[9] = 0; /* string_len = 0 */
1708-
pkt[10] = 0; /* padding */
1710+
/* string_len = 0, then padding, both zero */
17091711

17101712
ssh->peerEncryptId = ID_NONE;
17111713
ssh->peerAeadMode = 0;
@@ -1743,6 +1745,182 @@ static int test_DoReceive_RejectsShortPadding(void)
17431745
return result;
17441746
}
17451747

1748+
1749+
/* Verify DoReceive rejects a cleartext binary packet whose length field is
1750+
* not block aligned. The packet is valid in every other respect, so the
1751+
* RFC 4253 section 6 alignment check is the only thing that can reject it. */
1752+
static int test_DoReceive_RejectsMisalignedPacket(void)
1753+
{
1754+
WOLFSSH_CTX* ctx = NULL;
1755+
WOLFSSH* ssh = NULL;
1756+
int ret;
1757+
int result = 0;
1758+
/* Layout: uint32 packet_length=10, padding_length=4, msgId,
1759+
* uint32 string_len=0, 4 pad bytes => 14 bytes total. 14 is not a
1760+
* multiple of the 8-byte minimum block size. */
1761+
byte pkt[14];
1762+
word32 totalLen = (word32)sizeof(pkt);
1763+
1764+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
1765+
if (ctx == NULL)
1766+
return -770;
1767+
ssh = wolfSSH_new(ctx);
1768+
if (ssh == NULL) {
1769+
wolfSSH_CTX_free(ctx);
1770+
return -771;
1771+
}
1772+
1773+
WMEMSET(pkt, 0, sizeof(pkt));
1774+
pkt[3] = 10; /* packet_length */
1775+
pkt[4] = MIN_PAD_LENGTH;
1776+
pkt[5] = MSGID_IGNORE;
1777+
/* string_len = 0, then padding, both zero */
1778+
1779+
ssh->peerEncryptId = ID_NONE;
1780+
ssh->peerAeadMode = 0;
1781+
ssh->peerBlockSz = MIN_BLOCK_SZ;
1782+
ssh->peerMacId = ID_NONE;
1783+
ssh->peerMacSz = 0;
1784+
ssh->peerSeq = 0;
1785+
ssh->curSz = 0;
1786+
ssh->processReplyState = PROCESS_INIT;
1787+
ssh->error = 0;
1788+
1789+
ShrinkBuffer(&ssh->inputBuffer, 1);
1790+
ret = GrowBuffer(&ssh->inputBuffer, totalLen);
1791+
if (ret != WS_SUCCESS) {
1792+
result = -772;
1793+
goto done3;
1794+
}
1795+
WMEMCPY(ssh->inputBuffer.buffer, pkt, totalLen);
1796+
ssh->inputBuffer.length = totalLen;
1797+
ssh->inputBuffer.idx = 0;
1798+
1799+
ret = wolfSSH_TestDoReceive(ssh);
1800+
if (ret != WS_FATAL_ERROR) {
1801+
result = -773;
1802+
goto done3;
1803+
}
1804+
if (ssh->error != WS_BUFFER_E) {
1805+
result = -774;
1806+
goto done3;
1807+
}
1808+
1809+
done3:
1810+
wolfSSH_free(ssh);
1811+
wolfSSH_CTX_free(ctx);
1812+
return result;
1813+
}
1814+
#endif /* WOLFSSH_TEST_INTERNAL */
1815+
1816+
1817+
#if defined(WOLFSSH_TEST_INTERNAL) && !defined(WOLFSSH_NO_AES_GCM)
1818+
/* Verify DoReceive rejects an AES-GCM record whose length field is not a
1819+
* multiple of the cipher block size. RFC 5647 aligns the body alone, so a
1820+
* 12-byte body is misaligned even though the tag over it is valid. */
1821+
static int test_DoReceive_RejectsMisalignedAead(void)
1822+
{
1823+
WOLFSSH_CTX* ctx = NULL;
1824+
WOLFSSH* ssh = NULL;
1825+
Aes encAes;
1826+
int ret;
1827+
int result = 0;
1828+
int aesInited = 0;
1829+
byte key[AES_256_KEY_SIZE];
1830+
byte iv[GCM_NONCE_MID_SZ];
1831+
/* Layout: uint32 packet_length=12, padding_length=6, msgId,
1832+
* uint32 string_len=0, 6 pad bytes => a 12-byte body, which is not a
1833+
* multiple of AES_BLOCK_SIZE. Valid in every other respect. */
1834+
byte pkt[UINT32_SZ + 12];
1835+
byte record[UINT32_SZ + 12 + AES_BLOCK_SIZE];
1836+
word32 prefixLen, payloadSz, totalLen;
1837+
1838+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
1839+
if (ctx == NULL)
1840+
return -780;
1841+
ssh = wolfSSH_new(ctx);
1842+
if (ssh == NULL) {
1843+
wolfSSH_CTX_free(ctx);
1844+
return -781;
1845+
}
1846+
1847+
WMEMSET(key, 0x5A, sizeof(key));
1848+
WMEMSET(iv, 0x31, sizeof(iv));
1849+
1850+
WMEMSET(pkt, 0, sizeof(pkt));
1851+
pkt[3] = 12; /* packet_length */
1852+
pkt[4] = 6; /* padding_length */
1853+
pkt[5] = MSGID_IGNORE;
1854+
/* string_len = 0, then padding, both zero */
1855+
prefixLen = (word32)sizeof(pkt);
1856+
payloadSz = prefixLen - UINT32_SZ;
1857+
1858+
ret = wc_AesInit(&encAes, NULL, INVALID_DEVID);
1859+
if (ret != 0) {
1860+
result = -783;
1861+
goto done4;
1862+
}
1863+
aesInited = 1;
1864+
if (wc_AesGcmSetKey(&encAes, key, sizeof(key)) != 0) {
1865+
result = -784;
1866+
goto done4;
1867+
}
1868+
WMEMCPY(record, pkt, UINT32_SZ);
1869+
if (wc_AesGcmEncrypt(&encAes, record + UINT32_SZ, pkt + UINT32_SZ,
1870+
payloadSz, iv, sizeof(iv), record + UINT32_SZ + payloadSz,
1871+
AES_BLOCK_SIZE, record, UINT32_SZ) != 0) {
1872+
result = -785;
1873+
goto done4;
1874+
}
1875+
totalLen = UINT32_SZ + payloadSz + AES_BLOCK_SIZE;
1876+
1877+
if (wc_AesInit(&ssh->decryptCipher.aes, ssh->ctx->heap, INVALID_DEVID) != 0
1878+
|| wc_AesGcmSetKey(&ssh->decryptCipher.aes, key, sizeof(key)) != 0) {
1879+
result = -786;
1880+
goto done4;
1881+
}
1882+
ssh->decryptCipher.isInit = 1;
1883+
ssh->decryptCipher.cipherType = ID_AES256_GCM;
1884+
ssh->peerEncryptId = ID_AES256_GCM;
1885+
ssh->peerAeadMode = 1;
1886+
ssh->peerBlockSz = AES_BLOCK_SIZE;
1887+
ssh->peerMacSz = AES_BLOCK_SIZE;
1888+
WMEMCPY(ssh->peerKeys.iv, iv, sizeof(iv));
1889+
ssh->peerKeys.ivSz = sizeof(iv);
1890+
ssh->curSz = 0;
1891+
ssh->processReplyState = PROCESS_INIT;
1892+
ssh->error = 0;
1893+
1894+
ShrinkBuffer(&ssh->inputBuffer, 1);
1895+
if (GrowBuffer(&ssh->inputBuffer, totalLen) != WS_SUCCESS) {
1896+
result = -787;
1897+
goto done4;
1898+
}
1899+
WMEMCPY(ssh->inputBuffer.buffer, record, totalLen);
1900+
ssh->inputBuffer.length = totalLen;
1901+
ssh->inputBuffer.idx = 0;
1902+
1903+
ret = wolfSSH_TestDoReceive(ssh);
1904+
if (ret != WS_FATAL_ERROR) {
1905+
result = -788;
1906+
goto done4;
1907+
}
1908+
if (ssh->error != WS_BUFFER_E) {
1909+
result = -789;
1910+
goto done4;
1911+
}
1912+
1913+
done4:
1914+
if (aesInited)
1915+
wc_AesFree(&encAes);
1916+
wolfSSH_free(ssh);
1917+
wolfSSH_CTX_free(ctx);
1918+
return result;
1919+
}
1920+
#endif /* WOLFSSH_TEST_INTERNAL && !WOLFSSH_NO_AES_GCM */
1921+
1922+
1923+
#ifdef WOLFSSH_TEST_INTERNAL
17461924
/* Send sink, so a test can run a real send path (build the packet, drain it)
17471925
* without a live transport. */
17481926
static int UnitIoSendSink(WOLFSSH* ssh, void* buf, word32 sz, void* ctx)
@@ -16164,6 +16342,20 @@ int wolfSSH_UnitTest(int argc, char** argv)
1616416342
testResult = testResult || unitResult;
1616516343
#endif
1616616344

16345+
#ifdef WOLFSSH_TEST_INTERNAL
16346+
unitResult = test_DoReceive_RejectsMisalignedPacket();
16347+
printf("DoReceiveRejectsMisalignedPacket: %s\n",
16348+
(unitResult == 0 ? "SUCCESS" : "FAILED"));
16349+
testResult = testResult || unitResult;
16350+
#endif
16351+
16352+
#if defined(WOLFSSH_TEST_INTERNAL) && !defined(WOLFSSH_NO_AES_GCM)
16353+
unitResult = test_DoReceive_RejectsMisalignedAead();
16354+
printf("DoReceiveRejectsMisalignedAead: %s\n",
16355+
(unitResult == 0 ? "SUCCESS" : "FAILED"));
16356+
testResult = testResult || unitResult;
16357+
#endif
16358+
1616716359
#if defined(WOLFSSH_TEST_INTERNAL) && !defined(WOLFSSH_NO_DH_GEX_SHA256)
1616816360
unitResult = test_DhGexGroupValidate();
1616916361
printf("DhGexGroupValidate: %s\n",

0 commit comments

Comments
 (0)