From 0b28019f17fb6d52243a16c4417cb57cd2bca348 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Wed, 5 Aug 2026 08:20:52 -0700 Subject: [PATCH 1/2] Look the SSLContext up from the SSL_CTX in the ALPN/NPN callbacks 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 #1088. --- ext/openssl/ossl_ssl.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index fcbbec0b3..1b98a11ca 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -59,6 +59,19 @@ ossl_sslctx_mark(void *ptr) rb_gc_mark_movable((VALUE)SSL_CTX_get_ex_data(ctx, ossl_sslctx_ex_ptr_idx)); } +/* + * The SSLContext's VALUE is stored in exactly one place -- the SSL_CTX's ex_data, + * which ossl_sslctx_compact keeps up to date. Callbacks must go through here rather + * than capture their own copy, which nothing would relocate. + */ +static VALUE +ossl_sslctx_obj_from_ssl(const SSL *ssl) +{ + SSL_CTX *ctx = SSL_get_SSL_CTX(ssl); + + return (VALUE)SSL_CTX_get_ex_data(ctx, ossl_sslctx_ex_ptr_idx); +} + static void ossl_sslctx_free(void *ptr) { @@ -584,7 +597,8 @@ static int ssl_npn_advertise_cb(SSL *ssl, const unsigned char **out, unsigned int *outlen, void *arg) { - VALUE protocols = rb_attr_get((VALUE)arg, id_npn_protocols_encoded); + VALUE protocols = rb_attr_get(ossl_sslctx_obj_from_ssl(ssl), + id_npn_protocols_encoded); *out = (const unsigned char *) RSTRING_PTR(protocols); *outlen = RSTRING_LENINT(protocols); @@ -598,7 +612,7 @@ ssl_npn_select_cb(SSL *ssl, unsigned char **out, unsigned char *outlen, { VALUE sslctx_obj, cb; - sslctx_obj = (VALUE) arg; + sslctx_obj = ossl_sslctx_obj_from_ssl(ssl); cb = rb_attr_get(sslctx_obj, id_i_npn_select_cb); return ssl_npn_select_cb_common(ssl, cb, (const unsigned char **)out, @@ -612,7 +626,7 @@ ssl_alpn_select_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen, { VALUE sslctx_obj, cb; - sslctx_obj = (VALUE) arg; + sslctx_obj = ossl_sslctx_obj_from_ssl(ssl); cb = rb_attr_get(sslctx_obj, id_i_alpn_select_cb); return ssl_npn_select_cb_common(ssl, cb, out, outlen, in, inlen); @@ -807,11 +821,11 @@ ossl_sslctx_setup(VALUE self) if (!NIL_P(val)) { VALUE encoded = ssl_encode_npn_protocols(val); rb_ivar_set(self, id_npn_protocols_encoded, encoded); - SSL_CTX_set_next_protos_advertised_cb(ctx, ssl_npn_advertise_cb, (void *)self); + SSL_CTX_set_next_protos_advertised_cb(ctx, ssl_npn_advertise_cb, NULL); OSSL_Debug("SSL NPN advertise callback added"); } if (RTEST(rb_attr_get(self, id_i_npn_select_cb))) { - SSL_CTX_set_next_proto_select_cb(ctx, ssl_npn_select_cb, (void *) self); + SSL_CTX_set_next_proto_select_cb(ctx, ssl_npn_select_cb, NULL); OSSL_Debug("SSL NPN select callback added"); } #endif @@ -827,7 +841,7 @@ ossl_sslctx_setup(VALUE self) OSSL_Debug("SSL ALPN values added"); } if (RTEST(rb_attr_get(self, id_i_alpn_select_cb))) { - SSL_CTX_set_alpn_select_cb(ctx, ssl_alpn_select_cb, (void *) self); + SSL_CTX_set_alpn_select_cb(ctx, ssl_alpn_select_cb, NULL); OSSL_Debug("SSL ALPN select callback added"); } From ba7a363731a1fbb659f23bf4704097c78484406d Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Fri, 7 Aug 2026 01:05:45 -0700 Subject: [PATCH 2/2] Move ossl_sslctx_obj_from_ssl next to its callers 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. --- ext/openssl/ossl_ssl.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index 1b98a11ca..0e03f5678 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -59,19 +59,6 @@ ossl_sslctx_mark(void *ptr) rb_gc_mark_movable((VALUE)SSL_CTX_get_ex_data(ctx, ossl_sslctx_ex_ptr_idx)); } -/* - * The SSLContext's VALUE is stored in exactly one place -- the SSL_CTX's ex_data, - * which ossl_sslctx_compact keeps up to date. Callbacks must go through here rather - * than capture their own copy, which nothing would relocate. - */ -static VALUE -ossl_sslctx_obj_from_ssl(const SSL *ssl) -{ - SSL_CTX *ctx = SSL_get_SSL_CTX(ssl); - - return (VALUE)SSL_CTX_get_ex_data(ctx, ossl_sslctx_ex_ptr_idx); -} - static void ossl_sslctx_free(void *ptr) { @@ -592,6 +579,19 @@ ssl_npn_select_cb_common(SSL *ssl, VALUE cb, const unsigned char **out, return SSL_TLSEXT_ERR_OK; } +/* + * The SSLContext's VALUE is stored in exactly one place -- the SSL_CTX's ex_data, + * which ossl_sslctx_compact keeps up to date. Callbacks must go through here rather + * than capture their own copy, which nothing would relocate. + */ +static VALUE +ossl_sslctx_obj_from_ssl(const SSL *ssl) +{ + SSL_CTX *ctx = SSL_get_SSL_CTX(ssl); + + return (VALUE)SSL_CTX_get_ex_data(ctx, ossl_sslctx_ex_ptr_idx); +} + #ifdef OSSL_USE_NEXTPROTONEG static int ssl_npn_advertise_cb(SSL *ssl, const unsigned char **out, unsigned int *outlen,