Look the SSLContext up from the SSL_CTX in the ALPN/NPN callbacks - #1089
Open
jeremy wants to merge 2 commits into
Open
Look the SSLContext up from the SSL_CTX in the ALPN/NPN callbacks#1089jeremy wants to merge 2 commits into
jeremy wants to merge 2 commits into
Conversation
ossl_sslctx_mark uses rb_gc_mark_movable, so the SSLContext relocates. Its VALUE is stored in four places: the SSL_CTX's ex_data, and the callback argument of the NPN advertise, NPN select and ALPN select callbacks. ossl_sslctx_compact updates the first. Nothing updates the other three, so after a compaction they hold the pre-move address. The three callbacks all receive the SSL, and the SSL_CTX's ex_data copy is already kept current -- so they can look the object up instead of carrying their own copy, which leaves exactly one stored copy and one place to maintain. Registration is one-shot (ossl_sslctx_setup returns early when self is frozen), so the stale address is captured at the first handshake and never refreshed. Fixes ruby#1088.
rhenium
reviewed
Aug 5, 2026
rhenium
reviewed
Aug 6, 2026
It sits above the three ALPN/NPN callbacks that use it, and outside the OSSL_USE_NEXTPROTONEG guard so ssl_alpn_select_cb still sees it.
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.
Fixes #1088.
ossl_sslctx_markusesrb_gc_mark_movable, so the SSLContext relocates. ItsVALUEis stored in four places:SSL_CTXex_dataossl_sslctx_compactSSL_CTX_set_next_protos_advertised_cbarg (:810)SSL_CTX_set_next_proto_select_cbarg (:814)SSL_CTX_set_alpn_select_cbarg (:830)After a compaction the three callback arguments hold the pre-move address, and each callback does
rb_attr_geton it. Registration is one-shot —ossl_sslctx_setupreturns early whenselfis frozen — so the address is captured at the first handshake and never refreshed.This is a regression from the switch to
rb_gc_mark_movable; every release pins withrb_gc_markand is unaffected.Approach
Rather than adding three fix-ups to
ossl_sslctx_compact, this removes the three extra copies. All three callbacks already receive theSSL, andSSL_get_SSL_CTX(ssl)'s ex_data is the copy that is already maintained — so they can look the object up and passNULLas the callback argument.That leaves exactly one stored copy and one place to keep current, which seemed worth more than the alternative:
ossl_sslctx_compactwould otherwise have to re-register all three callbacks from inside a GC compaction callback, since OpenSSL exposes no getter for these arguments.The helper sits next to the other ex_data accessors and mirrors the idiom already used in the session-remove callback.
Verification
Built HEAD (
9796ee8) and the patched tree in the same step that ran the tests, so the artifact can't drift from the source. Ruby 4.0.6, OpenSSL 3.5.6, in a container.The two NPN sites are isolated by putting the server and client contexts in different processes (fork + pipes), so exactly one of them is subject to the compaction — otherwise the server fails first and masks the client site entirely.
Both ends pin
max_versionto TLS 1.2. That detail is why #1088 recorded the NPN sites as "code reading only": NPN isn't sent at TLS 1.3, so an unconstrained test negotiates 1.3, neither callback fires, and the run looks clean. The scripts now assert the callbacks actually ran before reporting anything.Failure modes observed on master, both from the same defect depending on what lands in the vacated slot:
:810— SEGV inssl_npn_advertise_cb, faulting at0x4, i.e.RSTRING_PTR(Qnil):814—NoMethodError: undefined method 'call' for nilout ofSSLSocket#connect, with the peer loggingsslv3 alert handshake failureReachability
npn_protocols=,npn_select_cb=andalpn_select_cb=are public documented accessors. The trigger is the application's ownGC.compact, not anything an attacker supplies, which is why this is a public issue rather than a private report.One honest caveat: confirmed under
GC.verify_compaction_references. I could not get a plainGC.compactto relocate the SSLContext in a small process, so I'm not claiming a spontaneous production rate — every run where the context did relocate failed (30/30), and every run where it didn't survived (60/60).