Skip to content

Commit ec73081

Browse files
committed
Fix off-by-one OOB NUL write in GetCertName (classic ASN parser)
1 parent 5012d1d commit ec73081

3 files changed

Lines changed: 307 additions & 4 deletions

File tree

tests/api/test_asn.c

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1630,6 +1630,307 @@ int test_ParseCert_SM3wSM2_short_pubkey(void)
16301630
return EXPECT_RESULT();
16311631
}
16321632

1633+
#if !defined(NO_CERTS) && !defined(NO_ASN) && !defined(NO_RSA)
1634+
/* Number of bytes needed to DER-encode the definite length "len".
1635+
* Only handles len < 0x10000, which is all this test needs. */
1636+
static int dnb_lenSz(int len)
1637+
{
1638+
if (len < 0x80)
1639+
return 1;
1640+
if (len < 0x100)
1641+
return 2;
1642+
return 3;
1643+
}
1644+
1645+
/* Write the DER definite length "len" to out. Returns the bytes written. */
1646+
static int dnb_encodeLen(byte* out, int len)
1647+
{
1648+
int i = 0;
1649+
1650+
if (len < 0x80) {
1651+
out[i++] = (byte)len;
1652+
}
1653+
else if (len < 0x100) {
1654+
out[i++] = 0x81;
1655+
out[i++] = (byte)len;
1656+
}
1657+
else {
1658+
out[i++] = 0x82;
1659+
out[i++] = (byte)(len >> 8);
1660+
out[i++] = (byte)(len & 0xff);
1661+
}
1662+
return i;
1663+
}
1664+
1665+
/* Build one RDN: SET { SEQUENCE { <oidTlv>, <valTag> <val> } }, where oidTlv is
1666+
* the full DER attribute-type OID (tag, length and content). Returns the total
1667+
* number of bytes written to out. */
1668+
static int dnb_buildRdn(byte* out, const byte* oidTlv, int oidTlvLen,
1669+
byte valTag, const byte* val, int valLen)
1670+
{
1671+
int attrTlvLen;
1672+
int seqContentLen;
1673+
int setContentLen;
1674+
int idx = 0;
1675+
1676+
attrTlvLen = 1 + dnb_lenSz(valLen) + valLen;
1677+
seqContentLen = oidTlvLen + attrTlvLen;
1678+
setContentLen = 1 + dnb_lenSz(seqContentLen) + seqContentLen;
1679+
1680+
out[idx++] = 0x31; /* SET OF */
1681+
idx += dnb_encodeLen(&out[idx], setContentLen);
1682+
out[idx++] = 0x30; /* SEQUENCE */
1683+
idx += dnb_encodeLen(&out[idx], seqContentLen);
1684+
XMEMCPY(&out[idx], oidTlv, (size_t)oidTlvLen);
1685+
idx += oidTlvLen;
1686+
out[idx++] = valTag; /* string tag */
1687+
idx += dnb_encodeLen(&out[idx], valLen);
1688+
XMEMCPY(&out[idx], val, (size_t)valLen);
1689+
idx += valLen;
1690+
1691+
return idx;
1692+
}
1693+
#endif /* !NO_CERTS && !NO_ASN && !NO_RSA */
1694+
1695+
/* Regression test for a 1-byte out-of-bounds NUL write in GetCertName().
1696+
* When a Subject DN exactly fills the WC_ASN_NAME_MAX character buffer the
1697+
* classic (WOLFSSL_ASN_ORIGINAL) parser wrote the string terminator one byte
1698+
* past cert->subject. Each certificate below is built so its second (boundary)
1699+
* RDN would land the running index on exactly WC_ASN_NAME_MAX, which must now
1700+
* be rejected as too big, leaving only the first RDN. Several attribute types
1701+
* are exercised because the too-big guards differ per attribute: commonName
1702+
* uses the "/CN=" prefix, emailAddress takes the distinct email branch with the
1703+
* longer "/emailAddress=" prefix, and (under WOLFSSL_CERT_EXT) jurisdictionC
1704+
* takes the JOI branch. Each attribute is checked on both boundary sides: at
1705+
* the cap (dropped) and one byte under it (kept in full), so an over-tightening
1706+
* off-by-one on any single guard is caught too. Runs under both ASN parsers. */
1707+
int test_ParseCert_dnBufferBoundary(void)
1708+
{
1709+
EXPECT_DECLS;
1710+
1711+
#if !defined(NO_CERTS) && !defined(NO_ASN) && !defined(NO_RSA)
1712+
/* 2048-bit RSA SubjectPublicKeyInfo, extracted with OpenSSL from
1713+
* certs/client-cert.pem, so the key decodes and the parse succeeds. */
1714+
static const byte rsaSpki[] = {
1715+
0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
1716+
0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00,
1717+
0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xc3, 0x03, 0xd1,
1718+
0x2b, 0xfe, 0x39, 0xa4, 0x32, 0x45, 0x3b, 0x53, 0xc8, 0x84, 0x2b, 0x2a,
1719+
0x7c, 0x74, 0x9a, 0xbd, 0xaa, 0x2a, 0x52, 0x07, 0x47, 0xd6, 0xa6, 0x36,
1720+
0xb2, 0x07, 0x32, 0x8e, 0xd0, 0xba, 0x69, 0x7b, 0xc6, 0xc3, 0x44, 0x9e,
1721+
0xd4, 0x81, 0x48, 0xfd, 0x2d, 0x68, 0xa2, 0x8b, 0x67, 0xbb, 0xa1, 0x75,
1722+
0xc8, 0x36, 0x2c, 0x4a, 0xd2, 0x1b, 0xf7, 0x8b, 0xba, 0xcf, 0x0d, 0xf9,
1723+
0xef, 0xec, 0xf1, 0x81, 0x1e, 0x7b, 0x9b, 0x03, 0x47, 0x9a, 0xbf, 0x65,
1724+
0xcc, 0x7f, 0x65, 0x24, 0x69, 0xa6, 0xe8, 0x14, 0x89, 0x5b, 0xe4, 0x34,
1725+
0xf7, 0xc5, 0xb0, 0x14, 0x93, 0xf5, 0x67, 0x7b, 0x3a, 0x7a, 0x78, 0xe1,
1726+
0x01, 0x56, 0x56, 0x91, 0xa6, 0x13, 0x42, 0x8d, 0xd2, 0x3c, 0x40, 0x9c,
1727+
0x4c, 0xef, 0xd1, 0x86, 0xdf, 0x37, 0x51, 0x1b, 0x0c, 0xa1, 0x3b, 0xf5,
1728+
0xf1, 0xa3, 0x4a, 0x35, 0xe4, 0xe1, 0xce, 0x96, 0xdf, 0x1b, 0x7e, 0xbf,
1729+
0x4e, 0x97, 0xd0, 0x10, 0xe8, 0xa8, 0x08, 0x30, 0x81, 0xaf, 0x20, 0x0b,
1730+
0x43, 0x14, 0xc5, 0x74, 0x67, 0xb4, 0x32, 0x82, 0x6f, 0x8d, 0x86, 0xc2,
1731+
0x88, 0x40, 0x99, 0x36, 0x83, 0xba, 0x1e, 0x40, 0x72, 0x22, 0x17, 0xd7,
1732+
0x52, 0x65, 0x24, 0x73, 0xb0, 0xce, 0xef, 0x19, 0xcd, 0xae, 0xff, 0x78,
1733+
0x6c, 0x7b, 0xc0, 0x12, 0x03, 0xd4, 0x4e, 0x72, 0x0d, 0x50, 0x6d, 0x3b,
1734+
0xa3, 0x3b, 0xa3, 0x99, 0x5e, 0x9d, 0xc8, 0xd9, 0x0c, 0x85, 0xb3, 0xd9,
1735+
0x8a, 0xd9, 0x54, 0x26, 0xdb, 0x6d, 0xfa, 0xac, 0xbb, 0xff, 0x25, 0x4c,
1736+
0xc4, 0xd1, 0x79, 0xf4, 0x71, 0xd3, 0x86, 0x40, 0x18, 0x13, 0xb0, 0x63,
1737+
0xb5, 0x72, 0x4e, 0x30, 0xc4, 0x97, 0x84, 0x86, 0x2d, 0x56, 0x2f, 0xd7,
1738+
0x15, 0xf7, 0x7f, 0xc0, 0xae, 0xf5, 0xfc, 0x5b, 0xe5, 0xfb, 0xa1, 0xba,
1739+
0xd3, 0x02, 0x03, 0x01, 0x00, 0x01
1740+
};
1741+
/* Certificate fields up to (not including) the subject Name. The two 0x0000
1742+
* placeholders are the outer and tbsCertificate SEQUENCE lengths, patched
1743+
* once the subject size is known. */
1744+
static const byte certPrefix[] = {
1745+
/* Certificate SEQUENCE (length patched) */
1746+
0x30, 0x82, 0x00, 0x00,
1747+
/* tbsCertificate SEQUENCE (length patched) */
1748+
0x30, 0x82, 0x00, 0x00,
1749+
/* version [0] INTEGER 2 */
1750+
0xa0, 0x03, 0x02, 0x01, 0x02,
1751+
/* serialNumber INTEGER 1 */
1752+
0x02, 0x01, 0x01,
1753+
/* signature AlgorithmIdentifier: sha256WithRSAEncryption */
1754+
0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
1755+
0x0b, 0x05, 0x00,
1756+
/* issuer Name: /CN=Test */
1757+
0x30, 0x0f, 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13,
1758+
0x04, 0x54, 0x65, 0x73, 0x74,
1759+
/* validity: notBefore 20000101000000Z, notAfter 20491231235959Z */
1760+
0x30, 0x1e,
1761+
0x17, 0x0d, 0x30, 0x30, 0x30, 0x31, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30,
1762+
0x30, 0x30, 0x5a,
1763+
0x17, 0x0d, 0x34, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, 0x39,
1764+
0x35, 0x39, 0x5a
1765+
};
1766+
/* Outer signatureAlgorithm and a placeholder signatureValue. Not checked
1767+
* because NO_VERIFY is used, but the structure must be present. */
1768+
static const byte certSuffix[] = {
1769+
/* signatureAlgorithm: sha256WithRSAEncryption */
1770+
0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
1771+
0x0b, 0x05, 0x00,
1772+
/* signatureValue BIT STRING */
1773+
0x03, 0x05, 0x00, 0xde, 0xad, 0xbe, 0xef
1774+
};
1775+
/* commonName OID (2.5.4.3). */
1776+
static const byte cnOid[] = { 0x06, 0x03, 0x55, 0x04, 0x03 };
1777+
/* emailAddress OID (1.2.840.113549.1.9.1, PKCS#9). */
1778+
static const byte emailOid[] = {
1779+
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01
1780+
};
1781+
#ifdef WOLFSSL_CERT_EXT
1782+
/* jurisdictionCountryName OID (ASN_JOI_PREFIX + ASN_JOI_C). */
1783+
static const byte joiCOid[] = {
1784+
0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x3c, 0x02, 0x01,
1785+
0x03
1786+
};
1787+
#endif
1788+
/* One entry per boundary attribute. valLen is sized relative to the buffer
1789+
* cap: after the leading /CN=A (index 5) the parser adds copyLen + strLen,
1790+
* where commonName copyLen is 4 ("/CN="), emailAddress copyLen is
1791+
* sizeof(WOLFSSL_EMAIL_ADDR) - 1 ("/emailAddress="), and jurisdictionC
1792+
* copyLen is sizeof(WOLFSSL_JOI_C) - 1 ("/jurisdictionC="). The reject cases
1793+
* land on exactly WC_ASN_NAME_MAX (must be dropped); the accept case lands
1794+
* one byte under (largest name that still fits, must be kept in full). */
1795+
struct {
1796+
const byte* oid;
1797+
int oidLen;
1798+
byte valTag;
1799+
int valLen;
1800+
int expectFull; /* 1: second RDN kept in full; 0: dropped */
1801+
} cases[6];
1802+
DecodedCert cert;
1803+
byte* der = NULL;
1804+
byte* val2 = NULL;
1805+
byte rdn1[16];
1806+
int numCases = 4;
1807+
int valLen;
1808+
int rdn1Len;
1809+
int rdn2Len;
1810+
int nameContentLen;
1811+
int tbsContentLen;
1812+
int outerContentLen;
1813+
int pos;
1814+
int derSz;
1815+
int c;
1816+
1817+
/* Each attribute is tested on both boundary sides: valLen at the cap
1818+
* (copyLen + strLen == WC_ASN_NAME_MAX - idx) must be dropped, and one byte
1819+
* under the cap must be kept in full. Both sides are covered per attribute
1820+
* because the too-big guards are independent comparison sites. */
1821+
/* commonName (final catch-all guard). */
1822+
cases[0].oid = cnOid;
1823+
cases[0].oidLen = (int)sizeof(cnOid);
1824+
cases[0].valTag = 0x13; /* PrintableString */
1825+
cases[0].valLen = WC_ASN_NAME_MAX - 9;
1826+
cases[0].expectFull = 0;
1827+
cases[1].oid = cnOid;
1828+
cases[1].oidLen = (int)sizeof(cnOid);
1829+
cases[1].valTag = 0x13;
1830+
cases[1].valLen = WC_ASN_NAME_MAX - 10;
1831+
cases[1].expectFull = 1;
1832+
/* emailAddress (distinct email branch guard). */
1833+
cases[2].oid = emailOid;
1834+
cases[2].oidLen = (int)sizeof(emailOid);
1835+
cases[2].valTag = 0x16; /* IA5String */
1836+
cases[2].valLen = WC_ASN_NAME_MAX - 5 -
1837+
((int)sizeof(WOLFSSL_EMAIL_ADDR) - 1);
1838+
cases[2].expectFull = 0;
1839+
cases[3].oid = emailOid;
1840+
cases[3].oidLen = (int)sizeof(emailOid);
1841+
cases[3].valTag = 0x16;
1842+
cases[3].valLen = WC_ASN_NAME_MAX - 6 -
1843+
((int)sizeof(WOLFSSL_EMAIL_ADDR) - 1);
1844+
cases[3].expectFull = 1;
1845+
#ifdef WOLFSSL_CERT_EXT
1846+
/* jurisdictionCountryName (JOI branch guard). */
1847+
cases[4].oid = joiCOid;
1848+
cases[4].oidLen = (int)sizeof(joiCOid);
1849+
cases[4].valTag = 0x13; /* PrintableString */
1850+
cases[4].valLen = WC_ASN_NAME_MAX - 5 -
1851+
((int)sizeof(WOLFSSL_JOI_C) - 1);
1852+
cases[4].expectFull = 0;
1853+
cases[5].oid = joiCOid;
1854+
cases[5].oidLen = (int)sizeof(joiCOid);
1855+
cases[5].valTag = 0x13;
1856+
cases[5].valLen = WC_ASN_NAME_MAX - 6 -
1857+
((int)sizeof(WOLFSSL_JOI_C) - 1);
1858+
cases[5].expectFull = 1;
1859+
numCases = 6;
1860+
#endif
1861+
1862+
ExpectNotNull(val2 = (byte*)XMALLOC((size_t)WC_ASN_NAME_MAX, NULL,
1863+
DYNAMIC_TYPE_TMP_BUFFER));
1864+
ExpectNotNull(der = (byte*)XMALLOC(2048, NULL, DYNAMIC_TYPE_TMP_BUFFER));
1865+
1866+
for (c = 0; (der != NULL) && (val2 != NULL) && (c < numCases); c++) {
1867+
valLen = cases[c].valLen;
1868+
XMEMSET(val2, 'B', (size_t)valLen);
1869+
1870+
/* Fixed leading fields. */
1871+
XMEMCPY(der, certPrefix, sizeof(certPrefix));
1872+
pos = (int)sizeof(certPrefix);
1873+
1874+
/* First RDN: /CN=A (a short name that must survive). */
1875+
rdn1Len = dnb_buildRdn(rdn1, cnOid, (int)sizeof(cnOid), 0x13,
1876+
(const byte*)"A", 1);
1877+
1878+
/* Second RDN length, computed the same way dnb_buildRdn() lays it
1879+
* out. */
1880+
rdn2Len = 1 + dnb_lenSz(valLen) + valLen; /* value TLV */
1881+
rdn2Len = cases[c].oidLen + rdn2Len; /* SEQUENCE content */
1882+
rdn2Len = 1 + dnb_lenSz(rdn2Len) + rdn2Len; /* SET content */
1883+
rdn2Len = 1 + dnb_lenSz(rdn2Len) + rdn2Len; /* SET TLV */
1884+
nameContentLen = rdn1Len + rdn2Len;
1885+
1886+
/* Subject Name SEQUENCE header, then the two RDNs in order. */
1887+
der[pos++] = 0x30;
1888+
pos += dnb_encodeLen(&der[pos], nameContentLen);
1889+
XMEMCPY(&der[pos], rdn1, (size_t)rdn1Len);
1890+
pos += rdn1Len;
1891+
pos += dnb_buildRdn(&der[pos], cases[c].oid, cases[c].oidLen,
1892+
cases[c].valTag, val2, valLen);
1893+
1894+
/* SubjectPublicKeyInfo. */
1895+
XMEMCPY(&der[pos], rsaSpki, sizeof(rsaSpki));
1896+
pos += (int)sizeof(rsaSpki);
1897+
1898+
/* tbsCertificate content spans from offset 8 to here. */
1899+
tbsContentLen = pos - 8;
1900+
1901+
/* Outer signature algorithm and value. */
1902+
XMEMCPY(&der[pos], certSuffix, sizeof(certSuffix));
1903+
pos += (int)sizeof(certSuffix);
1904+
derSz = pos;
1905+
outerContentLen = derSz - 4;
1906+
1907+
/* Patch the two SEQUENCE lengths (both use the 0x82 long form). */
1908+
der[6] = (byte)(tbsContentLen >> 8);
1909+
der[7] = (byte)(tbsContentLen & 0xff);
1910+
der[2] = (byte)(outerContentLen >> 8);
1911+
der[3] = (byte)(outerContentLen & 0xff);
1912+
1913+
wc_InitDecodedCert(&cert, der, (word32)derSz, NULL);
1914+
ExpectIntEQ(wc_ParseCert(&cert, CERT_TYPE, NO_VERIFY, NULL), 0);
1915+
if (cases[c].expectFull) {
1916+
/* Largest name that still fits: the second RDN is kept and the
1917+
* subject fills the buffer up to the in-bounds terminator. */
1918+
ExpectIntEQ((int)XSTRLEN(cert.subject), WC_ASN_NAME_MAX - 1);
1919+
}
1920+
else {
1921+
/* The boundary attribute is dropped so the terminator stays in
1922+
* bounds; only the first RDN remains. */
1923+
ExpectStrEQ(cert.subject, "/CN=A");
1924+
}
1925+
wc_FreeDecodedCert(&cert);
1926+
}
1927+
1928+
XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER);
1929+
XFREE(val2, NULL, DYNAMIC_TYPE_TMP_BUFFER);
1930+
#endif /* !NO_CERTS && !NO_ASN && !NO_RSA */
1931+
return EXPECT_RESULT();
1932+
}
1933+
16331934
int test_SerialNumber0_RootCA(void)
16341935
{
16351936
EXPECT_DECLS;

tests/api/test_asn.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ int test_SerialNumber0_RootCA(void);
3737
int test_DecodeAltNames_length_underflow(void);
3838
int test_DecodeCertExtensions_dup_certpol(void);
3939
int test_ParseCert_SM3wSM2_short_pubkey(void);
40+
int test_ParseCert_dnBufferBoundary(void);
4041
int test_wc_DecodeObjectId(void);
4142
int test_ToTraditional_ex_handcrafted(void);
4243
int test_ToTraditional_ex_roundtrip(void);
@@ -59,6 +60,7 @@ int test_wc_AsnFeatureCoverage(void);
5960
TEST_DECL_GROUP("asn", test_DecodeAltNames_length_underflow), \
6061
TEST_DECL_GROUP("asn", test_DecodeCertExtensions_dup_certpol), \
6162
TEST_DECL_GROUP("asn", test_ParseCert_SM3wSM2_short_pubkey), \
63+
TEST_DECL_GROUP("asn", test_ParseCert_dnBufferBoundary), \
6264
TEST_DECL_GROUP("asn", test_wc_DecodeObjectId), \
6365
TEST_DECL_GROUP("asn", test_ToTraditional_ex_handcrafted), \
6466
TEST_DECL_GROUP("asn", test_ToTraditional_ex_roundtrip), \

wolfcrypt/src/asn_orig.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2340,7 +2340,7 @@ static int GetCertName(DecodedCert* cert, char* full, byte* hash, int nameType,
23402340
#endif /* OPENSSL_EXTRA */
23412341
}
23422342

2343-
if ((strLen + copyLen) > (int)(WC_ASN_NAME_MAX - idx)) {
2343+
if ((strLen + copyLen) >= (int)(WC_ASN_NAME_MAX - idx)) {
23442344
WOLFSSL_MSG("ASN Name too big, skipping");
23452345
tooBig = TRUE;
23462346
}
@@ -2374,14 +2374,14 @@ static int GetCertName(DecodedCert* cert, char* full, byte* hash, int nameType,
23742374
return ASN_PARSE_E;
23752375
}
23762376

2377-
if (strLen > (int)(WC_ASN_NAME_MAX - idx)) {
2377+
if (strLen >= (int)(WC_ASN_NAME_MAX - idx)) {
23782378
WOLFSSL_MSG("ASN name too big, skipping");
23792379
tooBig = TRUE;
23802380
}
23812381

23822382
if (email) {
23832383
copyLen = sizeof(WOLFSSL_EMAIL_ADDR) - 1;
2384-
if ((copyLen + strLen) > (int)(WC_ASN_NAME_MAX - idx)) {
2384+
if ((copyLen + strLen) >= (int)(WC_ASN_NAME_MAX - idx)) {
23852385
WOLFSSL_MSG("ASN name too big, skipping");
23862386
tooBig = TRUE;
23872387
}
@@ -2468,7 +2468,7 @@ static int GetCertName(DecodedCert* cert, char* full, byte* hash, int nameType,
24682468
}
24692469
}
24702470
}
2471-
if ((copyLen + strLen) > (int)(WC_ASN_NAME_MAX - idx))
2471+
if ((copyLen + strLen) >= (int)(WC_ASN_NAME_MAX - idx))
24722472
{
24732473
WOLFSSL_MSG("ASN Name too big, skipping");
24742474
tooBig = TRUE;

0 commit comments

Comments
 (0)