Skip to content

Commit 47d5b2c

Browse files
committed
pkcs7: address second-round review (no-malloc, test guards, hardening)
- Replace the mandatory heap allocation of the signer attribute array with a hybrid: an inline array (sized MAX_SIGNED_ATTRIBS_SZ, the historical footprint) is used for the common case with no heap use, and a heap buffer is allocated only when the attribute count exceeds it. Restores WOLFSSL_NO_MALLOC support (no allocation in the common path; >inline returns BUFFER_E without a heap, matching the historical cap) and keeps the per-ESD footprint unchanged. - wc_PKCS7_GetSignedAttribValue(): reject a value that still carries an outer SET wrapper (defends against an unexpected decoded shape) and guard the returned length against INT overflow. - Multi-certificate decode bound: clamp certSetEnd to pkiMsg2Sz to guard against overflow/over-long length, and correct the comment about the streaming idx. - Tests: guard the 3-certificate certs-only and multi-cert tests with MAX_PKCS7_CERTS >= 3, and the 9-attribute test with (!WOLFSSL_NO_MALLOC || MAX_SIGNED_ATTRIBS_SZ >= 9), so constrained builds do not fail spuriously. - ChangeLog: document the non-streaming multi-certificate decode fix. Verified across default (streaming), NO_PKCS7_STREAM, and WOLFSSL_SMALL_STACK builds; pkcs7.c also compiles cleanly with -DWOLFSSL_NO_MALLOC. wolfcrypt and unit tests pass and valgrind is clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EprPgb9PzJkPp8LXuFk1yR
1 parent 546a635 commit 47d5b2c

3 files changed

Lines changed: 99 additions & 33 deletions

File tree

ChangeLog.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@
1919
decoded-attribute value shape is documentation of existing behavior and does
2020
not change it.
2121

22+
* **PKCS#7 multi-certificate decode fix (non-streaming)**: Fixed the additional
23+
-certificate loop in `wc_PKCS7_VerifySignedData` to bound against the absolute
24+
end of the certificate set rather than its relative length. In `NO_PKCS7_STREAM`
25+
builds this previously stopped short and dropped trailing certificates (and,
26+
with a large eContent preceding the certificate set, could drop all but the
27+
first certificate, causing signature verification to fail when the signer
28+
certificate was among those dropped). Streaming builds were unaffected.
29+
2230
* **Behavioral change (RSA-PSS trailerField enforcement)**: `DecodeRsaPssParams`
2331
(and its public wrapper `wc_DecodeRsaPssParams`) now enforces RFC 8017 A.2.3,
2432
which mandates `trailerField == trailerFieldBC(1)`. In the default build

wolfcrypt/src/pkcs7.c

Lines changed: 70 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1571,8 +1571,15 @@ int wc_PKCS7_GetSignedAttribValue(wc_PKCS7* pkcs7, const byte* oid,
15711571
return ASN_PARSE_E;
15721572
}
15731573

1574-
/* attrib->value holds the SET contents; parse the first AttributeValue
1575-
* TLV (tag + length + content) and return a pointer spanning it */
1574+
/* attrib->value must be the SET contents with the outer SET tag stripped
1575+
* (the documented shape). Reject a value that still carries a SET wrapper,
1576+
* which would indicate the value is not in the expected form. */
1577+
if (attrib->value[0] == (ASN_SET | ASN_CONSTRUCTED)) {
1578+
return ASN_PARSE_E;
1579+
}
1580+
1581+
/* parse the first AttributeValue TLV (tag + length + content) and return a
1582+
* pointer spanning it */
15761583
idx = 1; /* skip the value tag */
15771584
if (GetLength(attrib->value, &idx, &length, attrib->valueSz) < 0) {
15781585
return ASN_PARSE_E;
@@ -1584,6 +1591,9 @@ int wc_PKCS7_GetSignedAttribValue(wc_PKCS7* pkcs7, const byte* oid,
15841591

15851592
*out = attrib->value;
15861593
*outSz = idx + (word32)length;
1594+
if (*outSz > (word32)WC_MAX_SINT_OF(int)) {
1595+
return BUFFER_E;
1596+
}
15871597
return (int)*outSz;
15881598
}
15891599

@@ -1671,11 +1681,13 @@ typedef struct ESD {
16711681
byte digEncAlgoId[MAX_ALGO_SZ];
16721682
#endif
16731683
byte signedAttribSet[MAX_SET_SZ];
1674-
/* signedAttribs is allocated at encode time, sized
1675-
* to the actual attribute count, so it is not
1676-
* bounded by MAX_SIGNED_ATTRIBS_SZ and adds no
1677-
* fixed per-ESD footprint. signedAttribsCap holds
1678-
* its allocated entry count. */
1684+
/* Working attribute array. signedAttribs points at
1685+
* the inline buffer for the common case (no heap
1686+
* use, important for no-malloc/static-memory builds)
1687+
* and is redirected to a heap allocation only when
1688+
* the attribute count exceeds MAX_SIGNED_ATTRIBS_SZ.
1689+
* signedAttribsCap holds the usable entry count. */
1690+
EncodedAttrib signedAttribsInline[MAX_SIGNED_ATTRIBS_SZ];
16791691
EncodedAttrib* signedAttribs;
16801692
byte signerDigest[MAX_OCTET_STR_SZ];
16811693
word32 signedAttribsCap;
@@ -3326,30 +3338,50 @@ static int PKCS7_EncodeSigned(wc_PKCS7* pkcs7,
33263338
}
33273339
signerInfoSz += esd->digEncAlgoIdSz;
33283340

3329-
/* Allocate the working attribute array, sized to the actual number of
3330-
* attributes: user-supplied plus up to the CMS auto-defaults
3341+
/* Point the working attribute array at the inline buffer, sized for the
3342+
* actual attribute count: user-supplied plus the CMS auto-defaults
33313343
* (contentType, messageDigest, and, unless NO_ASN_TIME, signingTime).
3332-
* This avoids a fixed per-ESD array and any MAX_SIGNED_ATTRIBS_SZ cap. */
3344+
* Only fall back to a heap allocation when more attributes are needed
3345+
* than fit inline, so the common case stays allocation-free. */
33333346
{
33343347
#ifdef NO_ASN_TIME
33353348
word32 defaultAttribCap = 2;
33363349
#else
33373350
word32 defaultAttribCap = 3;
33383351
#endif
3339-
esd->signedAttribsCap = defaultAttribCap + pkcs7->signedAttribsSz;
3340-
/* detect addition and multiplication overflow */
3341-
if (esd->signedAttribsCap < pkcs7->signedAttribsSz ||
3342-
esd->signedAttribsCap >
3343-
((word32)WC_MAX_SINT_OF(int) / (word32)sizeof(EncodedAttrib))) {
3352+
word32 neededCap = defaultAttribCap + pkcs7->signedAttribsSz;
3353+
3354+
/* detect addition overflow */
3355+
if (neededCap < pkcs7->signedAttribsSz) {
33443356
idx = BUFFER_E;
33453357
goto out;
33463358
}
3347-
esd->signedAttribs = (EncodedAttrib*)XMALLOC(
3348-
esd->signedAttribsCap * (word32)sizeof(EncodedAttrib),
3349-
pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER);
3350-
if (esd->signedAttribs == NULL) {
3351-
idx = MEMORY_E;
3359+
3360+
if (neededCap <= MAX_SIGNED_ATTRIBS_SZ) {
3361+
esd->signedAttribs = esd->signedAttribsInline;
3362+
esd->signedAttribsCap = MAX_SIGNED_ATTRIBS_SZ;
3363+
}
3364+
else {
3365+
#ifdef WOLFSSL_NO_MALLOC
3366+
/* cannot grow beyond the inline array without a heap */
3367+
idx = BUFFER_E;
33523368
goto out;
3369+
#else
3370+
/* detect multiplication overflow */
3371+
if (neededCap > ((word32)WC_MAX_SINT_OF(int) /
3372+
(word32)sizeof(EncodedAttrib))) {
3373+
idx = BUFFER_E;
3374+
goto out;
3375+
}
3376+
esd->signedAttribs = (EncodedAttrib*)XMALLOC(
3377+
neededCap * (word32)sizeof(EncodedAttrib),
3378+
pkcs7->heap, DYNAMIC_TYPE_PKCS7);
3379+
if (esd->signedAttribs == NULL) {
3380+
idx = MEMORY_E;
3381+
goto out;
3382+
}
3383+
esd->signedAttribsCap = neededCap;
3384+
#endif
33533385
}
33543386
XMEMSET(esd->signedAttribs, 0,
33553387
esd->signedAttribsCap * (word32)sizeof(EncodedAttrib));
@@ -3783,13 +3815,17 @@ static int PKCS7_EncodeSigned(wc_PKCS7* pkcs7,
37833815

37843816
XFREE(flatSignedAttribs, pkcs7->heap, DYNAMIC_TYPE_PKCS7);
37853817

3786-
/* free working attribute array (allocated above) before freeing esd. In
3787-
* small-stack builds esd is heap-allocated and may be NULL here. */
3818+
/* free the working attribute array only if it was heap-allocated (i.e. it
3819+
* is not the inline buffer) before freeing esd. In small-stack builds esd
3820+
* is heap-allocated and may be NULL here. */
37883821
#ifdef WOLFSSL_SMALL_STACK
37893822
if (esd != NULL)
37903823
#endif
37913824
{
3792-
XFREE(esd->signedAttribs, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER);
3825+
if (esd->signedAttribs != NULL &&
3826+
esd->signedAttribs != esd->signedAttribsInline) {
3827+
XFREE(esd->signedAttribs, pkcs7->heap, DYNAMIC_TYPE_PKCS7);
3828+
}
37933829
esd->signedAttribs = NULL;
37943830
}
37953831

@@ -7219,14 +7255,18 @@ static int PKCS7_VerifySignedData(wc_PKCS7* pkcs7, const byte* hashBuf,
72197255
int sz = 0;
72207256
int i;
72217257
/* Absolute end of the certificate set within pkiMsg2.
7222-
* idx is the start of the set: 0 in streaming mode
7223-
* (the set was copied to a standalone buffer), or the
7224-
* absolute offset into the message in non-streaming
7225-
* mode. The set spans [idx, idx + length). Bounding the
7226-
* loop with the relative length alone stops short by
7227-
* idx bytes in non-streaming mode and can drop the last
7228-
* certificate. */
7258+
* idx is the start of the set, so the set spans
7259+
* [idx, idx + length). In non-streaming mode idx is the
7260+
* absolute offset into the message; in streaming mode it
7261+
* is typically 0 (the set was copied to a standalone
7262+
* buffer). Bounding the loop with the relative length
7263+
* alone stops short by idx bytes in non-streaming mode
7264+
* and can drop the last certificate. Clamp to pkiMsg2Sz
7265+
* to guard against overflow/over-long length (reads stay
7266+
* bounded by the certIdx + 1 < pkiMsg2Sz check below). */
72297267
word32 certSetEnd = idx + (word32)length;
7268+
if (certSetEnd < idx || certSetEnd > pkiMsg2Sz)
7269+
certSetEnd = pkiMsg2Sz;
72307270

72317271
pkcs7->cert[0] = cert;
72327272
pkcs7->certSz[0] = (word32)certSz;

wolfcrypt/test/test.c

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67507,6 +67507,7 @@ static wc_test_ret_t pkcs7_certsonly_test(byte* cert1, word32 cert1Sz,
6750767507
XFREE(out, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
6750867508
out = NULL;
6750967509

67510+
#if MAX_PKCS7_CERTS >= 3
6751067511
/* --- three certificates, in input order --- */
6751167512
pkcs7 = wc_PKCS7_New(HEAP_HINT, devId);
6751267513
if (pkcs7 == NULL)
@@ -67559,6 +67560,9 @@ static wc_test_ret_t pkcs7_certsonly_test(byte* cert1, word32 cert1Sz,
6755967560
if (!found1 || !found2 || !found3)
6756067561
ERROR_OUT(WC_TEST_RET_ENC_NC, out);
6756167562
}
67563+
#else
67564+
(void)cert2; (void)cert2Sz; (void)cert3; (void)cert3Sz;
67565+
#endif /* MAX_PKCS7_CERTS >= 3 */
6756267566

6756367567
ret = 0;
6756467568

@@ -67572,9 +67576,13 @@ static wc_test_ret_t pkcs7_certsonly_test(byte* cert1, word32 cert1Sz,
6757267576
#endif /* !NO_RSA */
6757367577

6757467578
#if !defined(NO_RSA) && !defined(NO_SHA256)
67579+
/* The nine-attribute case below needs more slots than the inline array holds
67580+
* (MAX_SIGNED_ATTRIBS_SZ), which requires a heap allocation; skip it on
67581+
* no-malloc builds unless the inline array was enlarged at build time. */
67582+
#if !defined(WOLFSSL_NO_MALLOC) || (MAX_SIGNED_ATTRIBS_SZ >= 9)
6757567583
/* Test that a SignedData carrying six user signed attributes plus the three
67576-
* CMS auto-defaults (nine total) encodes and verifies. This exceeds the old
67577-
* MAX_SIGNED_ATTRIBS_SZ default of 7. */
67584+
* CMS auto-defaults (nine total) encodes and verifies. This exceeds the
67585+
* default inline capacity (MAX_SIGNED_ATTRIBS_SZ == 7). */
6757867586
static wc_test_ret_t pkcs7_signed_attribs_count_test(byte* cert, word32 certSz,
6757967587
byte* key, word32 keySz)
6758067588
{
@@ -67676,6 +67684,7 @@ static wc_test_ret_t pkcs7_signed_attribs_count_test(byte* cert, word32 certSz,
6767667684

6767767685
return ret;
6767867686
}
67687+
#endif /* !WOLFSSL_NO_MALLOC || MAX_SIGNED_ATTRIBS_SZ >= 9 */
6767967688

6768067689
/* Test that decoded signed-attribute values follow the documented, stable
6768167690
* shape (the contents of the SET OF AttributeValue, outer SET tag stripped)
@@ -67818,6 +67827,12 @@ static wc_test_ret_t pkcs7_signed_multi_cert_test(
6781867827
byte* cert1, word32 cert1Sz, byte* key, word32 keySz,
6781967828
byte* cert2, word32 cert2Sz, byte* cert3, word32 cert3Sz)
6782067829
{
67830+
#if MAX_PKCS7_CERTS < 3
67831+
/* needs at least three certificate slots to exercise the bound */
67832+
(void)cert1; (void)cert1Sz; (void)key; (void)keySz;
67833+
(void)cert2; (void)cert2Sz; (void)cert3; (void)cert3Sz;
67834+
return 0;
67835+
#else
6782167836
wc_test_ret_t ret = 0;
6782267837
wc_PKCS7* pkcs7 = NULL;
6782367838
WC_RNG rng;
@@ -67900,6 +67915,7 @@ static wc_test_ret_t pkcs7_signed_multi_cert_test(
6790067915
XFREE(content, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
6790167916

6790267917
return ret;
67918+
#endif /* MAX_PKCS7_CERTS < 3 */
6790367919
}
6790467920
#endif /* !NO_RSA && !NO_SHA256 */
6790567921

@@ -68044,11 +68060,13 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t pkcs7signed_test(void)
6804468060
#endif
6804568061

6804668062
#if !defined(NO_RSA) && !defined(NO_SHA256)
68047-
/* signed-attribute count beyond the old MAX_SIGNED_ATTRIBS_SZ default */
68063+
#if !defined(WOLFSSL_NO_MALLOC) || (MAX_SIGNED_ATTRIBS_SZ >= 9)
68064+
/* signed-attribute count beyond the inline capacity */
6804868065
if (ret >= 0)
6804968066
ret = pkcs7_signed_attribs_count_test(
6805068067
rsaClientCertBuf, (word32)rsaClientCertBufSz,
6805168068
rsaClientPrivKeyBuf, (word32)rsaClientPrivKeyBufSz);
68069+
#endif
6805268070

6805368071
/* decoded signed-attribute value shape */
6805468072
if (ret >= 0)

0 commit comments

Comments
 (0)