Skip to content

Commit 22d1441

Browse files
committed
Bounds-check the RecipientInfo SET length in wc_PKCS7_ParseToRecipientInfoSet()
1 parent 97b82b5 commit 22d1441

3 files changed

Lines changed: 106 additions & 1 deletion

File tree

tests/api/test_pkcs7.c

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2711,6 +2711,72 @@ int test_wc_PKCS7_DecodeEnvelopedData_stream(void)
27112711
} /* END test_wc_PKCS7_DecodeEnvelopedData_stream() */
27122712

27132713

2714+
/*
2715+
* Regression test: a PKCS#7 EnvelopedData with a forged RecipientInfo SET
2716+
* length (parsed via GetSet_ex with NO_USER_CHECK) must not drive an
2717+
* uncapped heap allocation through wc_PKCS7_GrowStream(). The decoder
2718+
* must reject the oversized allocation rather than attempting it.
2719+
*/
2720+
int test_wc_PKCS7_DecodeEnvelopedData_forgedRecipientSetLen(void)
2721+
{
2722+
EXPECT_DECLS;
2723+
#if defined(HAVE_PKCS7) && !defined(NO_RSA) && !defined(NO_PKCS7_STREAM)
2724+
/* Crafted ContentInfo/EnvelopedData header. All lengths use the
2725+
* 4-byte long form for clarity. The RecipientInfo SET length is
2726+
* forged to 0x01000001 (16 MB + 1), which exceeds the default
2727+
* WOLFSSL_PKCS7_MAX_STREAM_ALLOC cap and should be rejected before
2728+
* any allocation succeeds. The body after the SET header is never
2729+
* consumed because the decoder fails at the GrowStream() cap. */
2730+
static const byte forged[] = {
2731+
/* ContentInfo SEQUENCE, body length 0x01000021 */
2732+
0x30, 0x84, 0x01, 0x00, 0x00, 0x21,
2733+
/* OID 1.2.840.113549.1.7.3 (id-envelopedData) */
2734+
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D,
2735+
0x01, 0x07, 0x03,
2736+
/* [0] EXPLICIT content, body length 0x01000016 */
2737+
0xA0, 0x84, 0x01, 0x00, 0x00, 0x16,
2738+
/* EnvelopedData SEQUENCE, body length 0x01000010 */
2739+
0x30, 0x84, 0x01, 0x00, 0x00, 0x10,
2740+
/* version INTEGER 0 */
2741+
0x02, 0x01, 0x00,
2742+
/* Forged RecipientInfo SET header: length = 0x01000001 */
2743+
0x31, 0x84, 0x01, 0x00, 0x00, 0x01,
2744+
/* Padding so that header-parsing states can buffer their
2745+
* required lookahead without returning WC_PKCS7_WANT_READ_E.
2746+
* These bytes are never interpreted. */
2747+
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
2748+
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
2749+
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
2750+
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
2751+
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
2752+
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
2753+
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
2754+
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
2755+
};
2756+
PKCS7* pkcs7 = NULL;
2757+
byte out[32];
2758+
int ret;
2759+
2760+
ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId));
2761+
ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, (byte*)client_cert_der_2048,
2762+
sizeof_client_cert_der_2048), 0);
2763+
ExpectIntEQ(wc_PKCS7_SetKey(pkcs7, (byte*)client_key_der_2048,
2764+
sizeof_client_key_der_2048), 0);
2765+
2766+
ret = wc_PKCS7_DecodeEnvelopedData(pkcs7, (byte*)forged,
2767+
(word32)sizeof(forged), out, (word32)sizeof(out));
2768+
/* Must NOT return WC_PKCS7_WANT_READ_E (which would imply the
2769+
* oversized allocation succeeded and the decoder is waiting for
2770+
* the around 16 MB of SET body). Must NOT return 0 / positive length.
2771+
* Expected: BUFFER_E from the GrowStream cap. */
2772+
ExpectIntEQ(ret, WC_NO_ERR_TRACE(BUFFER_E));
2773+
2774+
wc_PKCS7_Free(pkcs7);
2775+
#endif
2776+
return EXPECT_RESULT();
2777+
} /* END test_wc_PKCS7_DecodeEnvelopedData_forgedRecipientSetLen() */
2778+
2779+
27142780
/*
27152781
* Testing wc_PKCS7_DecodeEnvelopedData with streaming
27162782
*/

tests/api/test_pkcs7.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ int test_wc_PKCS7_SetOriEncryptCtx(void);
6565
int test_wc_PKCS7_SetOriDecryptCtx(void);
6666
int test_wc_PKCS7_DecodeCompressedData(void);
6767
int test_wc_PKCS7_DecodeEnvelopedData_multiple_recipients(void);
68+
int test_wc_PKCS7_DecodeEnvelopedData_forgedRecipientSetLen(void);
6869
int test_wc_PKCS7_VerifySignedData_PKCS7ContentSeq(void);
6970
int test_wc_PKCS7_VerifySignedData_IndefLenOOB(void);
7071

@@ -129,7 +130,8 @@ int test_wc_PKCS7_VerifySignedData_IndefLenOOB(void);
129130
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_DecodeOneSymmetricKey), \
130131
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_SetOriEncryptCtx), \
131132
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_SetOriDecryptCtx), \
132-
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_DecodeEnvelopedData_multiple_recipients)
133+
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_DecodeEnvelopedData_multiple_recipients), \
134+
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_DecodeEnvelopedData_forgedRecipientSetLen)
133135

134136
#define TEST_PKCS7_SIGNED_ENCRYPTED_DATA_DECLS \
135137
TEST_DECL_GROUP("pkcs7_sed", test_wc_PKCS7_signed_enveloped)

wolfcrypt/src/pkcs7.c

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,17 @@ struct PKCS7SignerInfo {
113113

114114
#ifndef NO_PKCS7_STREAM
115115

116+
/* Hard upper bound on a single PKCS7 streaming buffer allocation. Guards
117+
* wc_PKCS7_GrowStream against attacker-controlled ASN.1 lengths that were
118+
* parsed with NO_USER_CHECK and would otherwise drive allocations up to
119+
* around 2GB (e.g. via a forged RecipientInfo SET length). 16 MB is well above
120+
* any legitimate RecipientInfo / encoded-attribute size but small enough
121+
* that a forged length fails allocation on constrained targets and is
122+
* rejected on larger ones. */
123+
#ifndef WOLFSSL_PKCS7_MAX_STREAM_ALLOC
124+
#define WOLFSSL_PKCS7_MAX_STREAM_ALLOC (16 * 1024 * 1024)
125+
#endif
126+
116127
#define MAX_PKCS7_STREAM_BUFFER 256
117128
struct PKCS7State {
118129
byte* tmpCert;
@@ -274,6 +285,16 @@ static void wc_PKCS7_FreeStream(wc_PKCS7* pkcs7)
274285
static int wc_PKCS7_GrowStream(wc_PKCS7* pkcs7, word32 newSz)
275286
{
276287
byte* pt;
288+
289+
/* Guard against attacker-controlled ASN.1 lengths reaching this
290+
* allocation. Several callers parse lengths with NO_USER_CHECK and
291+
* pass them here unvalidated (e.g. wc_PKCS7_ParseToRecipientInfoSet
292+
* on a forged RecipientInfo SET header). */
293+
if (newSz > WOLFSSL_PKCS7_MAX_STREAM_ALLOC) {
294+
WOLFSSL_MSG("PKCS7 streaming allocation exceeds maximum");
295+
return BUFFER_E;
296+
}
297+
277298
pt = (byte*)XMALLOC(newSz, pkcs7->heap, DYNAMIC_TYPE_PKCS7);
278299
if (pt == NULL) {
279300
return MEMORY_E;
@@ -13096,6 +13117,22 @@ static int wc_PKCS7_ParseToRecipientInfoSet(wc_PKCS7* pkcs7, byte* in,
1309613117
NO_USER_CHECK) < 0)
1309713118
ret = ASN_PARSE_E;
1309813119

13120+
/* GetSet_ex is called with NO_USER_CHECK, which skips the
13121+
* (idx + length > maxIdx) bounds check in GetLength_ex. In
13122+
* non-streaming mode, validate the SET length against the
13123+
* remaining input buffer; in streaming mode the length flows
13124+
* into pkcs7->stream->expected and then wc_PKCS7_GrowStream,
13125+
* where it is capped by WOLFSSL_PKCS7_MAX_STREAM_ALLOC. */
13126+
if (ret == 0 && length < 0)
13127+
ret = ASN_PARSE_E;
13128+
#ifdef NO_PKCS7_STREAM
13129+
if (ret == 0 &&
13130+
(*idx > pkiMsgSz ||
13131+
(word32)length > pkiMsgSz - *idx)) {
13132+
ret = ASN_PARSE_E;
13133+
}
13134+
#endif
13135+
1309913136
if (ret < 0)
1310013137
break;
1310113138

0 commit comments

Comments
 (0)