Heap buffer overflow read in [@ sec_pkcs7_decoder_work_data] via PORT_ArenaGrow when decoding multi-chunk PKCS#7 EncryptedData with no content callback
Categories
(NSS :: Libraries, defect, P2)
Tracking
(nss 3.123, firefox-esr115 wontfix, firefox-esr140 wontfix, firefox149 wontfix, firefox150 wontfix, firefox151 fixed)
People
(Reporter: bugmon, Assigned: jschanck)
Details
(5 keywords, Whiteboard: [nss-nofx][adv-main151-])
Attachments
(5 files)
When SEC_PKCS7DecoderStart() is called with cb == NULL but with a decrypt_key_cb and decrypt_allowed_cb (so that decryption is performed and the plaintext is accumulated into encContentInfo.plainContent rather than streamed to a callback), sec_pkcs7_decoder_work_data() mishandles the running plaintext buffer. After growing the arena buffer it advances the returned pointer by oldlen (line 138) and stores that mid-buffer pointer into plain->data (line 140), while plain->len continues to accumulate the total decrypted length (line 154).
On the third and subsequent ciphertext chunks, PORT_ArenaGrow(pool, plain->data, plain->len, plain->len + buflen) is therefore invoked with a pointer that points oldlen bytes into the previous allocation but with an oldsize equal to the full accumulated length. PL_ArenaGrow then does memcpy(newp, p, size), reading thousands of bytes past the end of the previous arena chunk and past the end of the underlying malloc'd block (ASAN shadow shows the read crossing both the f7 arena poison and the fa heap right-redzone).
An attacker can trigger this with a single well-formed BER PKCS#7 EncryptedData (or EnvelopedData) message whose [0] encryptedContent is a constructed indefinite-length OCTET STRING containing three or more large primitive sub-strings; the streaming ASN.1 decoder delivers each sub-string as a separate filter call. The out-of-bounds heap bytes are copied into the new plainContent buffer and are subsequently retrievable via SEC_PKCS7GetContent(), so beyond the memory-safety violation this is also an information leak of adjacent heap memory. No in-tree Firefox/Thunderbird caller currently uses cb == NULL together with decryption callbacks, so the practical impact is on third-party NSS consumers that use this (valid, exported) API pattern.
Build Info
- Branch: main
- Revision: 6164ea4bacaeaed1f617c11911df7fc32f2e6ec2
- Timestamp: 2026-04-02T19:36:24+00:00
Affected Code
File: security/nss/lib/pkcs7/p7decode.c, line 119-155
} else {
unsigned long oldlen;
plain = &(p7dcx->cinfo->content.envelopedData->encContentInfo.plainContent);
oldlen = plain->len;
if (oldlen == 0) {
buf = (unsigned char *)PORT_ArenaAlloc(p7dcx->cinfo->poolp,
buflen);
} else {
buf = (unsigned char *)PORT_ArenaGrow(p7dcx->cinfo->poolp,
plain->data, /* BUG: mid-buffer ptr on 3rd+ call */
oldlen, oldlen + buflen);/* BUG: oldlen is TOTAL length */
if (buf != NULL)
buf += oldlen; /* advance into buffer ... */
}
plain->data = buf; /* ... and store mid-buffer pointer */
}
...
if (plain != NULL) {
PORT_Assert(final || outlen == buflen);
plain->len += outlen; /* but len keeps TOTAL length */
}
File: nsprpub/lib/ds/plarena.c, line 159-171
PR_IMPLEMENT(void*)
PL_ArenaGrow(PLArenaPool* pool, void* p, PRUint32 size, PRUint32 incr) {
void* newp;
if (PR_UINT32_MAX - size < incr) {
return NULL;
}
PL_ARENA_ALLOCATE(newp, pool, size + incr);
if (newp) {
memcpy(newp, p, size); /* OOB read: p is mid-buffer, size is total length */
}
return newp;
}
plain->data is set to buf + oldlen (a pointer into the middle of the grown buffer) while plain->len accumulates the total decrypted length. On the next chunk these mismatched values are passed straight to PORT_ArenaGrow -> PL_ArenaGrow, whose memcpy(newp, p, size) reads plain->len bytes starting at the mid-buffer pointer, overrunning the previous arena chunk and the underlying malloc allocation.
Exploit Chain
- Attacker crafts a PKCS#7 EncryptedData (or EnvelopedData) BER blob whose [0] encryptedContent is a constructed indefinite-length OCTET STRING containing >=3 large (multi-KB, block-aligned) primitive OCTET STRING segments.
- Victim application calls SEC_PKCS7DecoderStart(cb=NULL, ..., decrypt_key_cb, ..., decrypt_allowed_cb) and feeds the blob through SEC_PKCS7DecoderUpdate (or equivalently SEC_PKCS7DecodeItem with cb=NULL).
- The ASN.1 streaming decoder delivers each primitive segment separately to sec_pkcs7_decoder_filter -> sec_pkcs7_decoder_work_data.
- Chunk 1: PORT_ArenaAlloc; plain->data = buf (start of buffer), plain->len = outlen1.
- Chunk 2: PORT_ArenaGrow succeeds; buf += oldlen; plain->data = buf (now mid-buffer); plain->len = outlen1 + outlen2.
- Chunk 3: PORT_ArenaGrow(pool, mid-buffer-ptr, outlen1+outlen2, ...) -> PL_ArenaGrow memcpy reads outlen1+outlen2 bytes from mid-buffer-ptr, overrunning the previous arena chunk and the underlying heap allocation.
- The OOB-read bytes are copied into the new plainContent buffer and exposed to the caller via SEC_PKCS7GetContent(), leaking adjacent heap contents; under ASAN the process aborts.
Steps to Reproduce
- Apply the test additions in security/nss/gtests/smime_gtest/smime_unittest.cc (test SMimeTest.PKCS7DecodeEncryptedDataPlaintextOverflow) and add '<(DEPTH)/lib/smime/smime.gyp:smime3' to the dependencies in security/nss/gtests/smime_gtest/smime_gtest.gyp.
- Build NSS with ASAN: cd security/nss && ./build.sh --asan (gyp + ninja required).
- Run: GTESTFILTER=SMimeTest.PKCS7DecodeEncryptedDataPlaintextOverflow security/nss/tests/gtests/gtests.sh (or run smime_gtest directly with --gtest_filter=SMimeTest.PKCS7DecodeEncryptedDataPlaintextOverflow).
- Observe AddressSanitizer: use-after-poison / heap-buffer-overflow READ of ~16 KB in memcpy <- PL_ArenaGrow <- PORT_ArenaGrow_Util <- sec_pkcs7_decoder_work_data (p7decode.c:134).
Security Impact
- Severity: Moderate
- Attacker capability: Heap out-of-bounds read of attacker-influenced size (proportional to ciphertext length) whose contents are copied into the decoded plainContent SECItem and returned to the caller via SEC_PKCS7GetContent(); enables disclosure of adjacent heap memory and corrupts the decrypted output. Read-only; no OOB write.
- Preconditions: An NSS consumer must decode an attacker-supplied PKCS#7 EncryptedData/EnvelopedData using SEC_PKCS7DecoderStart/SEC_PKCS7DecodeItem with content callback cb == NULL while supplying decrypt_key_cb and decrypt_allowed_cb (so that decryption runs and plaintext is accumulated in the arena). No in-tree Mozilla caller currently uses this exact combination, but it is a valid, exported, documented API pattern available to any third-party NSS consumer.
ASAN Report
==23460==ERROR: AddressSanitizer: use-after-poison on address 0x528000003f90 at pc 0x7ebf23fe542e bp 0x7fff969252f0 sp 0x7fff96924a98
READ of size 15984 at 0x528000003f90 thread T0
#0 0x7ebf23fe542d in memcpy ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors_memintrinsics.inc:115
#1 0x7ebf239d6e6f in PL_ArenaGrow ../../../lib/ds/plarena.c:168
#2 0x7ebf23c7da8f in PORT_ArenaGrow_Util ../../lib/util/secport.c:454
#3 0x7ebf23bf78bf in sec_pkcs7_decoder_work_data ../../lib/pkcs7/p7decode.c:134
#4 0x7ebf23bf7efc in sec_pkcs7_decoder_filter ../../lib/pkcs7/p7decode.c:219
#5 0x7ebf23c70b12 in SEC_ASN1DecoderUpdate_Util ../../lib/util/secasn1d.c:2939
#6 0x7ebf23bfad01 in SEC_PKCS7DecoderUpdate ../../lib/pkcs7/p7decode.c:1070
#7 0x594fff5f5082 in nss_test::SMimeTest_PKCS7DecodeEncryptedDataPlaintextOverflow_Test::TestBody()
0x528000003f97 is located 0 bytes after 16023-byte region [0x528000000100,0x528000003f97)
allocated by thread T0 here:
#0 0x7ebf23fe79c7 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
#1 0x7ebf2393c6e7 in PR_Malloc ../../../../pr/src/malloc/prmem.c:425
#2 0x7ebf239d69cc in PL_ArenaAllocate ../../../lib/ds/plarena.c:132
#3 0x7ebf239d6de9 in PL_ArenaGrow ../../../lib/ds/plarena.c:166
#4 0x7ebf23c7da8f in PORT_ArenaGrow_Util ../../lib/util/secport.c:454
#5 0x7ebf23bf78bf in sec_pkcs7_decoder_work_data ../../lib/pkcs7/p7decode.c:134
#6 0x7ebf23bf7efc in sec_pkcs7_decoder_filter ../../lib/pkcs7/p7decode.c:219
#7 0x7ebf23c70b12 in SEC_ASN1DecoderUpdate_Util ../../lib/util/secasn1d.c:2939
#8 0x7ebf23bfad01 in SEC_PKCS7DecoderUpdate ../../lib/pkcs7/p7decode.c:1070
#9 0x594fff5f5082 in nss_test::SMimeTest_PKCS7DecodeEncryptedDataPlaintextOverflow_Test::TestBody()
SUMMARY: AddressSanitizer: use-after-poison ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors_memintrinsics.inc:115 in memcpy
Shadow bytes around the buggy address:
0x528000003f00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x528000003f80: 00 00[f7]fa fa fa fa fa fa fa fa fa fa fa fa fa
0x528000004000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
==23460==ABORTING
Updated•5 months ago
|
| Reporter | ||
Comment 1•5 months ago
|
||
| Reporter | ||
Comment 2•5 months ago
|
||
| Reporter | ||
Comment 3•5 months ago
|
||
| Reporter | ||
Comment 4•5 months ago
|
||
| Assignee | ||
Updated•5 months ago
|
| Assignee | ||
Comment 6•5 months ago
|
||
Pushed by jschanck@mozilla.com:
https://hg.mozilla.org/projects/nss/rev/12ff3372c5b1
fix use of PORT_ArenaGrow when decoding multi-chunk PKCS#7 EncryptedData with no content callback. r=nss-reviewers,rrelyea,keeler
Updated•5 months ago
|
Updated•5 months ago
|
| Assignee | ||
Updated•5 months ago
|
| Assignee | ||
Updated•5 months ago
|
Updated•5 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Updated•1 month ago
|
Description
•