Conversation
The TLS 1.2 master secret / TLS 1.3 resumption PSK in |ssl_session_st|, the TLS 1.3 traffic secrets in |SSL_HANDSHAKE| and |SSL3_STATE|, and the key material in |SSL_HANDSHAKE_HINTS| were all left intact in freed heap memory. Cleanse them in the corresponding destructors so they do not outlive the objects that own them. This is defense in depth, not a fix for an exploitable bug: it only matters against an attacker who can already read freed heap memory, via a core dump, swap, a hypervisor snapshot, or a separate use-after-free. Member destructors run after the enclosing destructor body, so cleansing |Array<uint8_t>| contents there happens before the allocation is released. Addresses aws#3494 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
unlong
requested a deployment
to
manual-approval
September 18, 2026 02:29 — with
GitHub Actions
Waiting
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related issues
Addresses #3494
Context and motivation
ssl_session_st::secret— the TLS 1.2 master secret, and the TLS 1.3 resumption PSK — is never zeroized, so it stays intact in the heap allocation after the session is released.SSL_SESSION_dupcopies it into fresh allocations that go through the same non-cleansing destructor, so a single handshake can leave several independent copies behind.Per @torben-hansen's note on the issue, the same applies to other objects holding key material:
SSL_HANDSHAKE,SSL_HANDSHAKE_HINTS, andSSL3_STATE. Today there is not a singleOPENSSL_cleansecall anywhere underssl/.This is defense in depth, not a fix for an exploitable bug. There is no memory-disclosure primitive here; it only matters against an attacker who can already read freed heap memory via a core dump, swap, a hypervisor snapshot, or a separate use-after-free.
Description of changes
Cleanse key material in the destructor of each object that owns it:
ssl_session_st—secret. This also covers everySSL_SESSION_dupcopy, since they all release through this path.SSL_HANDSHAKE— the sevenSSL_MAX_MD_SIZEsecret arrays (secret_,early_traffic_secret_, both handshake secrets, both traffic secrets,expected_client_finished_) and the TLS 1.2key_block.SSL_HANDSHAKE_HINTS—key_share_secret,ecdhe_private_key,decrypted_psk,decrypted_ticket. This struct had no destructor at all; one is added.SSL3_STATE—write_traffic_secret,read_traffic_secret,exporter_secret.Array::Resetreleases through a plainOPENSSL_free, so aCleanseArray(Array<uint8_t> *)helper is added next toArrayfor the heap-backed fields. Member destructors run after the enclosing destructor body, so cleansingArraycontents from the destructor happens before the allocation is released.Scoped to the objects named in the issue.
SSLKeyShareprivate keys and the stack-localkey_blockinssl/handoff.ccare left alone as follow-up work.Testing
Two regression tests in
ssl/ssl_misc_test.cccoverSSL_HANDSHAKEandSSL3_STATE. Each placement-news the object into caller-owned storage, fills every secret field with a known pattern, records the field offsets, runs the destructor explicitly, and asserts the bytes are zero. Because the storage outlives the object, reading it after destruction is well defined. Reverting either destructor change fails the corresponding test.ssl_session_stis not covered. Its destructor is private toRefCounted, andRefCounted::DecRefInternalcallsOPENSSL_freeimmediately after it, so the same technique would mean reading freed memory. Adding afrienddeclaration tossl/internal.hpurely for the test seemed like the worse tradeoff, but happy to do it if you would rather have the coverage.I was not able to build locally (no CMake on hand), so the new tests have not been executed — I have only confirmed that every non-test file under
ssl/and the test file itself passclang++ -fsyntax-only -std=c++17. Relying on CI here, and I will follow up on any failures.Review considerations
No public API or ABI change; all four types are internal to libssl. No FIPS boundary impact.
SSL_HANDSHAKE_HINTSgains a user-declared destructor, which makes it non-trivially-destructible. It is only ever created viaMakeUniqueand is never moved or aggregate-initialized, so this should be inert, but it is the one change worth a second look.🤖 Generated with Claude Code