BN_CTX pool doesn't scrub a BIGNUM's backing memory on release (BN_CTX_end/BN_POOL_release)
Nobody has claimed this yet.
- Dominant language
- C
- Stars
- 30.8k
- Forks
- 11.5k
- Avg merge
- 10m
- Merged PRs (30d)
- 1
Description
Summary
BN_CTX_get() / BN_CTX_end() / BN_POOL_release() in crypto/bn/bn_ctx.c
never scrub a BIGNUM's backing d[] array when a pool slot is released
or recycled. Only BN_CTX_free() (via BN_POOL_finish() -> BN_clear_free())
actually zeroes this memory - and only when the entire BN_CTX is destroyed.
- Every RSA/DH/ECDSA private-key operation uses BN_CTX pool slots to hold
intermediate, secret-dependent values during modular exponentiation /
Montgomery multiplication.
While an individual BN_CTX in the current
RSA/ECDSA code paths (crypto/rsa/rsa_ossl.c, crypto/ec/ecdsa_ossl.c)
is created and freed per-operation (so BN_CTX_free() does eventually
scrub it), any code that follows OpenSSL's own documented best practice
of reusing one BN_CTX across many operations (recommended for
performance) will have this residue persist for the BN_CTX's entire
lifetime.
Root cause
BN_CTX_get()(bn_ctx.c:220-243) callsBN_zero(ret)before handing
back a pool slot.BN_zero()only setsbn->top = 0- it does not
touchbn->d.BN_POOL_release()(bn_ctx.c:360-373), called fromBN_CTX_end(),
only adjusts the pool's internalusedbookkeeping and a linked-list
pointer, never zeroes or frees anything.- Only place actually scrubs bignums in the pool is
BN_POOL_finish()(bn_ctx.c:304-317), called fromBN_CTX_free(),
which invokesBN_clear_free()on each bignum with a non-NULLd.
Sample test program
Attached: bn_pool_leak_poc.c
Output:
=== Step 1: secret value stored in a BN_CTX pool slot ===
pool slot address: 0x55b9c2529520, backing size: 32 bytes
raw bytes while in use: babbb8b9bebfbcbdb2b3b0b1b6b7b4b5aaaba8a9aeafacada2a3a0a1a6a7a4a5
=== Step 2: BN_CTX_end() releases it back to the pool ===
raw bytes immediately after release: babbb8b9bebfbcbdb2b3b0b1b6b7b4b5aaaba8a9aeafacada2a3a0a1a6a7a4a5
unchanged from before release: YES
=== Step 3: a later, unrelated computation reuses the same slot ===
fresh->d == old slot address: YES (same slot)
raw bytes of the 'fresh' bignum from BN_CTX_get(): babbb8b9bebfbcbdb2b3b0b1b6b7b4b5aaaba8a9aeafacada2a3a0a1a6a7a4a5
still equals the OLD secret: YES - stale secret handed back as 'fresh' memory
=== Step 4: new computation writes only a 4-byte value into the reused slot ===
raw backing bytes after writing the small value: 0403020100000000b2b3b0b1b6b7b4b5aaaba8a9aeafacada2a3a0a1a6a7a4a5
bytes beyond the touched word still match the OLD secret: YES - old secret bytes still present past 'top'
BN_CTX_get() labeled step 3's bignum as "fresh" (it called
BN_zero() internally), but its actual backing memory still held the
previous, unrelated secret byte-for-byte.
Related prior issues/PRs
- #29591 ("Sensitive key material not consistently zeroized on free")
raised a similar general concern in Jan 2026 but was closed in March
2026 for lacking specifics - this issue provides the specific,
reproducible instance for the BN_CTX pool mechanism. - #20626 ("EVP_PKEY_get_bn_param() does not zeroize temporary buffer")
is a different, already-fixed instance of the same underlying bug
class (temporary buffer not cleansed).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in crypto/bn/bn_ctx.c by tracing BN_CTX_get(), BN_CTX_end(), BN_POOL_release(), and BN_POOL_finish(); use the attached bn_pool_leak_poc.c to reproduce the retained bytes. Compare the pool lifecycle with the per-operation paths in crypto/rsa/rsa_ossl.c and crypto/ec/ecdsa_ossl.c. Done means released or recycled pool slots no longer retain the prior backing data.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c
- Domain
- cryptography, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100