Merge pull request #10441 from padelsbach/pkcs7-verify-fix

Fix OOB possibility in PKCS7_VerifySignedData
This commit is contained in:
Sean Parkinson
2026-05-13 16:19:15 +10:00
committed by GitHub
3 changed files with 143 additions and 6 deletions
+108
View File
@@ -5320,3 +5320,111 @@ int test_wc_PKCS7_VerifySignedData_IndefLenOOB(void)
#endif /* HAVE_PKCS7 && !NO_PKCS7_STREAM */
return EXPECT_RESULT();
}
/*
* SignedData bundle truncated at the eContent [0] EXPLICIT tag in
* encapContentInfo. Verifies that the parser rejects the malformed
* input rather than dereferencing past the end of the buffer.
*/
int test_wc_PKCS7_VerifySignedData_TruncEContentTag(void)
{
EXPECT_DECLS;
#if defined(HAVE_PKCS7)
PKCS7* pkcs7 = NULL;
WOLFSSL_SMALL_STACK_STATIC byte der[] = {
/* outer ContentInfo SEQUENCE (75 bytes content) */
0x30, 0x4B,
/* contentType OID 1.2.840.113549.1.7.2 (signedData) */
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x02,
/* [0] EXPLICIT (62 bytes content) */
0xA0, 0x3E,
/* SignedData SEQUENCE (60 bytes content) */
0x30, 0x3C,
/* version INTEGER 1 */
0x02, 0x01, 0x01,
/* digestAlgorithms SET (empty - degenerate) */
0x31, 0x00,
/* encapContentInfo SEQUENCE (53 bytes content) */
0x30, 0x35,
/* eContentType OID with 50 bytes of arbitrary payload */
0x06, 0x32,
0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* eContent [0] EXPLICIT - buffer ends here, no length, no content */
0xA0
};
word32 derSz = (word32)sizeof(der);
ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId));
ExpectIntEQ(wc_PKCS7_Init(pkcs7, HEAP_HINT, INVALID_DEVID), 0);
ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0);
ExpectIntNE(wc_PKCS7_VerifySignedData(pkcs7, der, derSz), 0);
wc_PKCS7_Free(pkcs7);
#endif /* HAVE_PKCS7 */
return EXPECT_RESULT();
}
/*
* SignedData bundle truncated at the certificates [0] IMPLICIT tag.
* Verifies that the parser rejects the malformed input rather than
* dereferencing past the end of the buffer.
*
* TODO: limited to NO_PKCS7_STREAM because the streaming parser's stage 3
* early-exit check (pkcs7.c near line 6594) accepts any bundle
* whose remaining footer is < 6 bytes as a successful degenerate end,
* so the bounds check at line 6765 is unreachable in streaming mode.
* Drop the NO_PKCS7_STREAM gate if/when the early-exit check becomes
* more accurate.
*/
int test_wc_PKCS7_VerifySignedData_TruncCertSetTag(void)
{
EXPECT_DECLS;
#if defined(HAVE_PKCS7) && defined(NO_PKCS7_STREAM)
PKCS7* pkcs7 = NULL;
WOLFSSL_SMALL_STACK_STATIC byte der[] = {
/* outer ContentInfo SEQUENCE (78 bytes content) */
0x30, 0x4E,
/* contentType OID signedData */
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x02,
/* [0] EXPLICIT (65 bytes content) */
0xA0, 0x41,
/* SignedData SEQUENCE (63 bytes content) */
0x30, 0x3F,
/* version INTEGER 1 */
0x02, 0x01, 0x01,
/* digestAlgorithms SET (empty) */
0x31, 0x00,
/* encapContentInfo SEQUENCE (55 bytes content) */
0x30, 0x37,
/* eContentType OID 1.2.840.113549.1.7.1 (data) */
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x01,
/* eContent [0] EXPLICIT (42 bytes content) */
0xA0, 0x2A,
/* OCTET STRING (40 bytes content) */
0x04, 0x28,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* certificates [0] IMPLICIT - buffer ends here, no length */
0xA0
};
word32 derSz = (word32)sizeof(der);
ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId));
ExpectIntEQ(wc_PKCS7_Init(pkcs7, HEAP_HINT, INVALID_DEVID), 0);
ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0);
ExpectIntNE(wc_PKCS7_VerifySignedData(pkcs7, der, derSz), 0);
wc_PKCS7_Free(pkcs7);
#endif /* HAVE_PKCS7 && NO_PKCS7_STREAM */
return EXPECT_RESULT();
}
+5 -1
View File
@@ -68,6 +68,8 @@ int test_wc_PKCS7_DecodeEnvelopedData_multiple_recipients(void);
int test_wc_PKCS7_DecodeEnvelopedData_forgedRecipientSetLen(void);
int test_wc_PKCS7_VerifySignedData_PKCS7ContentSeq(void);
int test_wc_PKCS7_VerifySignedData_IndefLenOOB(void);
int test_wc_PKCS7_VerifySignedData_TruncEContentTag(void);
int test_wc_PKCS7_VerifySignedData_TruncCertSetTag(void);
#define TEST_PKCS7_DECLS \
@@ -115,7 +117,9 @@ int test_wc_PKCS7_VerifySignedData_IndefLenOOB(void);
TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_BER), \
TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_NoDefaultSignedAttribs), \
TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_VerifySignedData_PKCS7ContentSeq), \
TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_VerifySignedData_IndefLenOOB)
TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_VerifySignedData_IndefLenOOB), \
TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_VerifySignedData_TruncEContentTag), \
TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_VerifySignedData_TruncCertSetTag)
#define TEST_PKCS7_ENCRYPTED_DATA_DECLS \
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_DecodeEnvelopedData_stream), \
+30 -5
View File
@@ -6333,6 +6333,14 @@ static int PKCS7_VerifySignedData(wc_PKCS7* pkcs7, const byte* hashBuf,
* OCTET_STRING will be next. If so, we use the length retrieved
* there. PKCS#7 spec defines ANY as eContent type. In this case
* we fall back and save this content length for use later */
if (ret == 0 && localIdx >= pkiMsgSz) {
/* Truncated input: don't dereference past the buffer.
* Break out of the switch directly so the degenerate-
* recovery path below cannot mask this error. */
ret = BUFFER_E;
break;
}
if (ret == 0 && pkiMsg[localIdx] != ASN_INDEF_LENGTH) {
if (GetLength_ex(pkiMsg, &localIdx, &length, pkiMsgSz,
NO_USER_CHECK) <= 0) {
@@ -6590,6 +6598,14 @@ static int PKCS7_VerifySignedData(wc_PKCS7* pkcs7, const byte* hashBuf,
/* check if bundle has more elements or footer, if not, set content
* to pkcs7->content and hash to pkcs7->hash.
*
* NOTE: this check returns success whenever fewer than 6 bytes
* follow the content within the outer ContentInfo, which also
* accepts truncated bundles whose footer was cut short (e.g. a
* lone certificates [0] tag with no length). Distinguishing a
* legitimate degenerate end (such as an empty signerInfos SET
* "31 00") from truncated junk would require peeking at the
* remaining bytes or making stage 4's `expected` window smaller.
*/
if (ret == 0 && pkcs7->stream->maxLen > 0 &&
(pkcs7->stream->maxLen - pkcs7->stream->totalRd)
@@ -6759,6 +6775,10 @@ static int PKCS7_VerifySignedData(wc_PKCS7* pkcs7, const byte* hashBuf,
&& tag == (ASN_CONSTRUCTED | ASN_CONTEXT_SPECIFIC | 0)) {
idx++;
if (localIdx >= pkiMsg2Sz) {
ret = BUFFER_E;
}
/* if certificates set has indefinite length, try to get
* the first certificate length of the set.
*/
@@ -7269,11 +7289,16 @@ static int PKCS7_VerifySignedData(wc_PKCS7* pkcs7, const byte* hashBuf,
/* make sure that terminating zero's follow */
if ((ret == WC_NO_ERR_TRACE(PKCS7_SIGNEEDS_CHECK) || ret >= 0) &&
pkcs7->stream->indefLen == 1) {
word32 i;
for (i = 0; i < 3 * ASN_INDEF_END_SZ; i++) {
if (pkiMsg2[idx + i] != 0) {
ret = ASN_PARSE_E;
break;
if (idx + (3 * ASN_INDEF_END_SZ) > pkiMsg2Sz) {
ret = ASN_PARSE_E;
}
else {
word32 i;
for (i = 0; i < 3 * ASN_INDEF_END_SZ; i++) {
if (pkiMsg2[idx + i] != 0) {
ret = ASN_PARSE_E;
break;
}
}
}
}