Skip to content

Commit 4135f55

Browse files
mattcaswelljogme
authored andcommitted
Fix out-of-bounds valid_flags access after SSL_set_SSL_CTX()
SSL_new() sizes the connection-local signature algorithm state from the SSL_CTX the connection was created from: sc->ssl_pkey_num counts the built-in certificate slots plus one for each of that context's provider TLS-SIGALG entries, and s3.tmp.valid_flags is later allocated to match. SSL_set_SSL_CTX() installs a duplicate of the replacement context's CERT but leaves both of those describing the original context. Peer signature algorithm codepoints are subsequently resolved against the replacement context, where a provider sigalg's sig_idx is simply its position in that context's list. If the replacement context advertises more provider sigalgs than the original, a codepoint occupying one of the excess slots yields an index past the end of valid_flags. The usual route is a switch from the servername callback, which runs before tls1_set_server_sigalgs() allocates the buffer: the stale ssl_pkey_num undersizes the allocation, and tls1_process_sigalgs() then reads one 4-byte word past the end for each such codepoint the peer offered, and writes CERT_PKEY_EXPLICIT_SIGN|CERT_PKEY_SIGN where that word already reads zero. The peer chooses how many of these accesses occur, and at which offsets, by selecting which codepoints to send. Refresh ssl_pkey_num from the newly installed CERT and, where a valid_flags buffer already exists, replace it with one sized for that context. A count which is too large is wrong in the same way as one that is too small: loops bounded by ssl_pkey_num index cert->pkeys, which the replacement context sizes. The replacement buffer is allocated before the point at which the switch is committed, so that a failure can still be reported rather than leaving the connection half switched. The built-in slots are copied across rather than zeroed. They may already hold peer signature algorithm state which is not derived from the SSL_CTX, and nothing recomputes it after a context switch: at TLS 1.2 and above tls1_check_chain() only ORs CERT_PKEY_SIGN and CERT_PKEY_EXPLICIT_SIGN in from the existing value, and ssl_set_masks() needs them to enable ECDSA, Ed25519 and Ed448. Zeroing them makes a TLSv1.2 handshake with an ECDSA certificate fail with "no shared cipher". The provider slots are positional and context specific, so they are reset. Fixes CVE-2026-72897 Assisted-by: Claude Code:claude-opus-5[1m] Reviewed-by: Saša Nedvědický <sashan@openssl.org> Reviewed-by: Neil Horman <nhorman@openssl.org> Merge-date: Tue Sep 29 11:22:11 2026
1 parent 906cf0e commit 4135f55

1 file changed

Lines changed: 35 additions & 0 deletions

File tree

‎ssl/ssl_lib.c‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5485,6 +5485,7 @@ SSL_CTX *SSL_get_SSL_CTX(const SSL *ssl)
54855485
SSL_CTX *SSL_set_SSL_CTX(SSL *ssl, SSL_CTX *ctx)
54865486
{
54875487
CERT *new_cert;
5488+
uint32_t *new_valid_flags = NULL;
54885489
SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL_ONLY(ssl);
54895490

54905491
/* TODO(QUIC FUTURE): Add support for QUIC */
@@ -5509,6 +5510,34 @@ SSL_CTX *SSL_set_SSL_CTX(SSL *ssl, SSL_CTX *ctx)
55095510
*/
55105511
if (!ossl_assert(sc->sid_ctx_length <= sizeof(sc->sid_ctx)))
55115512
goto err;
5513+
5514+
/*
5515+
* |valid_flags| is sized from the number of signature algorithm slots of
5516+
* the SSL_CTX the connection was created from, so it must be resized for
5517+
* the replacement context.
5518+
*
5519+
* The built-in slots are indexed by the fixed SSL_PKEY_* constants and so
5520+
* mean the same thing in either context. They are preserved because they
5521+
* may already hold peer signature algorithm state which does not depend
5522+
* on the SSL_CTX. A provider slot index is instead a position in one
5523+
* context's provider list, so the same index denotes a different
5524+
* algorithm here and the old value cannot be carried over. They are reset
5525+
* rather than recomputed: recomputing them means recomputing the shared
5526+
* signature algorithms against the replacement context, which would let
5527+
* its preferences take effect on an established connection.
5528+
*/
5529+
if (sc->s3.tmp.valid_flags != NULL) {
5530+
/* Should never happen: ssl_cert_new() enforces this */
5531+
if (!ossl_assert(new_cert->ssl_pkey_num >= SSL_PKEY_NUM))
5532+
goto err;
5533+
new_valid_flags = OPENSSL_zalloc(new_cert->ssl_pkey_num
5534+
* sizeof(*new_valid_flags));
5535+
if (new_valid_flags == NULL)
5536+
goto err;
5537+
memcpy(new_valid_flags, sc->s3.tmp.valid_flags,
5538+
SSL_PKEY_NUM * sizeof(*new_valid_flags));
5539+
}
5540+
55125541
if (!SSL_CTX_up_ref(ctx))
55135542
goto err;
55145543

@@ -5525,12 +5554,18 @@ SSL_CTX *SSL_set_SSL_CTX(SSL *ssl, SSL_CTX *ctx)
55255554

55265555
ssl_cert_free(sc->cert);
55275556
sc->cert = new_cert;
5557+
sc->ssl_pkey_num = new_cert->ssl_pkey_num;
5558+
if (new_valid_flags != NULL) {
5559+
OPENSSL_free(sc->s3.tmp.valid_flags);
5560+
sc->s3.tmp.valid_flags = new_valid_flags;
5561+
}
55285562
SSL_CTX_free(ssl->ctx); /* decrement reference count */
55295563
ssl->ctx = ctx;
55305564

55315565
return ssl->ctx;
55325566

55335567
err:
5568+
OPENSSL_free(new_valid_flags);
55345569
ssl_cert_free(new_cert);
55355570
return NULL;
55365571
}

0 commit comments

Comments
 (0)