Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package openssl-3 for openSUSE:Factory checked in at 2026-08-27 18:48:43 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/openssl-3 (Old) and /work/SRC/openSUSE:Factory/.openssl-3.new.1265 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "openssl-3" Thu Aug 27 18:48:43 2026 rev:53 rq:1373819 version:3.5.3 Changes: -------- --- /work/SRC/openSUSE:Factory/openssl-3/openssl-3.changes 2026-07-24 22:05:02.545080055 +0200 +++ /work/SRC/openSUSE:Factory/.openssl-3.new.1265/openssl-3.changes 2026-08-27 18:48:44.417069434 +0200 @@ -1,0 +2,36 @@ +Mon Aug 24 10:15:53 UTC 2026 - Pedro Monreal <[email protected]> + +- Security fix: + * CVE-2026-75803: openssl: AEAD Forgeries with Empty Ciphertext + When Using EVP_Cipher() (bsc#1275837) + * Add openssl-CVE-2026-75803.patch + +------------------------------------------------------------------- +Mon Aug 17 10:22:20 UTC 2026 - Pedro Monreal <[email protected]> + +- Security fixes in August 2026 release: (bsc#1274774) + * CVE-2026-14456: Unbounded Memory Growth in QUIC Server Incoming + Channel Queue (bsc#1274791) + * CVE-2026-14457: RPK Server Signature Algorithm Selection Can + Dereference a Missing Certificate (bsc#1274792) + * CVE-2026-18798: QUIC Server May Trigger Double Free When Processing + INITIAL Packet (bsc#1274777) + * CVE-2026-34181: PKCS#12 Files with PBMAC1 Are Accepted with Short + HMAC Keys (bsc#1266343) + * CVE-2026-54874: Excessive Memory Use Buffering DTLS Records for + a Future Epoch (bsc#1274795) + * CVE-2026-63072: Heap Buffer Overflow in CMS Key Unwrapping (bsc#1274788) + * CVE-2026-63073: Untrusted Sender DN Used as Format String in CMP + Response Validation (bsc#1274796) + * CVE-2026-63074: CMP Indefinite Cache Growth of ExtraCerts (bsc#1274797) + * CVE-2026-63075: QUIC ACK-only Packet Retention Can Cause Memory + Exhaustion (bsc#1274798) + * CVE-2026-63076: Invalid Pointer Dereference in CMP Server via Crafted + protectionAlg (bsc#1274790) + * Add patches: openssl-CVE-2026-14456.patch openssl-CVE-2026-14457.patch + openssl-CVE-2026-18798.patch openssl-CVE-2026-34181.patch + openssl-CVE-2026-54874.patch openssl-CVE-2026-63072.patch + openssl-CVE-2026-63073.patch openssl-CVE-2026-63074.patch + openssl-CVE-2026-63075.patch openssl-CVE-2026-63076.patch + +------------------------------------------------------------------- New: ---- openssl-CVE-2026-14456.patch openssl-CVE-2026-14457.patch openssl-CVE-2026-18798.patch openssl-CVE-2026-34181.patch openssl-CVE-2026-54874.patch openssl-CVE-2026-63072.patch openssl-CVE-2026-63073.patch openssl-CVE-2026-63074.patch openssl-CVE-2026-63075.patch openssl-CVE-2026-63076.patch openssl-CVE-2026-75803.patch ----------(New B)---------- New: protectionAlg (bsc#1274790) * Add patches: openssl-CVE-2026-14456.patch openssl-CVE-2026-14457.patch openssl-CVE-2026-18798.patch openssl-CVE-2026-34181.patch New: protectionAlg (bsc#1274790) * Add patches: openssl-CVE-2026-14456.patch openssl-CVE-2026-14457.patch openssl-CVE-2026-18798.patch openssl-CVE-2026-34181.patch New: * Add patches: openssl-CVE-2026-14456.patch openssl-CVE-2026-14457.patch openssl-CVE-2026-18798.patch openssl-CVE-2026-34181.patch openssl-CVE-2026-54874.patch openssl-CVE-2026-63072.patch New: * Add patches: openssl-CVE-2026-14456.patch openssl-CVE-2026-14457.patch openssl-CVE-2026-18798.patch openssl-CVE-2026-34181.patch openssl-CVE-2026-54874.patch openssl-CVE-2026-63072.patch New: openssl-CVE-2026-18798.patch openssl-CVE-2026-34181.patch openssl-CVE-2026-54874.patch openssl-CVE-2026-63072.patch openssl-CVE-2026-63073.patch openssl-CVE-2026-63074.patch New: openssl-CVE-2026-18798.patch openssl-CVE-2026-34181.patch openssl-CVE-2026-54874.patch openssl-CVE-2026-63072.patch openssl-CVE-2026-63073.patch openssl-CVE-2026-63074.patch New: openssl-CVE-2026-54874.patch openssl-CVE-2026-63072.patch openssl-CVE-2026-63073.patch openssl-CVE-2026-63074.patch openssl-CVE-2026-63075.patch openssl-CVE-2026-63076.patch New: openssl-CVE-2026-54874.patch openssl-CVE-2026-63072.patch openssl-CVE-2026-63073.patch openssl-CVE-2026-63074.patch openssl-CVE-2026-63075.patch openssl-CVE-2026-63076.patch New: openssl-CVE-2026-63073.patch openssl-CVE-2026-63074.patch openssl-CVE-2026-63075.patch openssl-CVE-2026-63076.patch New: openssl-CVE-2026-63073.patch openssl-CVE-2026-63074.patch openssl-CVE-2026-63075.patch openssl-CVE-2026-63076.patch New: When Using EVP_Cipher() (bsc#1275837) * Add openssl-CVE-2026-75803.patch ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ openssl-3.spec ++++++ --- /var/tmp/diff_new_pack.keqMPQ/_old 2026-08-27 18:48:47.333171371 +0200 +++ /var/tmp/diff_new_pack.keqMPQ/_new 2026-08-27 18:48:47.335171441 +0200 @@ -208,6 +208,28 @@ Patch90: openssl-CVE-2026-34180.patch # PATCH-FIX-UPSTREAM: Grow the init_buf incrementally as we receive data (bsc#1271712) Patch91: openssl-HollowByte.patch +# PATCH-FIX-UPSTREAM: CVE-2026-14456: Unbounded Memory Growth in QUIC Server Incoming Channel Queue (bsc#1274791) +Patch92: openssl-CVE-2026-14456.patch +# PATCH-FIX-UPSTREAM: CVE-2026-14457: RPK Server Signature Algorithm Selection Can Dereference a Missing Certificate (bsc#1274792) +Patch93: openssl-CVE-2026-14457.patch +# PATCH-FIX-UPSTREAM: CVE-2026-18798: QUIC Server May Trigger Double Free When Processing INITIAL Packet (bsc#1274777) +Patch94: openssl-CVE-2026-18798.patch +# PATCH-FIX-UPSTREAM: CVE-2026-34181: PKCS#12 Files with PBMAC1 Are Accepted with Short HMAC Keys (bsc#1266343) +Patch95: openssl-CVE-2026-34181.patch +# PATCH-FIX-UPSTREAM: CVE-2026-54874: Excessive Memory Use Buffering DTLS Records for a Future Epoch (bsc#1274795) +Patch96: openssl-CVE-2026-54874.patch +# PATCH-FIX-UPSTREAM: CVE-2026-63072: Heap Buffer Overflow in CMS Key Unwrapping (bsc#1274788) +Patch97: openssl-CVE-2026-63072.patch +# PATCH-FIX-UPSTREAM: CVE-2026-63073: Untrusted Sender DN Used as Format String in CMP Response Validation (bsc#1274796) +Patch98: openssl-CVE-2026-63073.patch +# PATCH-FIX-UPSTREAM: CVE-2026-63074: CMP Indefinite Cache Growth of ExtraCerts (bsc#1274797) +Patch99: openssl-CVE-2026-63074.patch +# PATCH-FIX-UPSTREAM: CVE-2026-63075: QUIC ACK-only Packet Retention Can Cause Memory Exhaustion (bsc#1274798) +Patch100: openssl-CVE-2026-63075.patch +# PATCH-FIX-UPSTREAM: CVE-2026-63076: Invalid Pointer Dereference in CMP Server via Crafted protectionAlg (bsc#1274790) +Patch101: openssl-CVE-2026-63076.patch +# PATCH-FIX-UPSTREAM: CVE-2026-75803: AEAD Forgeries with Empty Ciphertext When Using EVP_Cipher() (bsc#1275837) +Patch102: openssl-CVE-2026-75803.patch # ulp-macros is available according to SUSE version. %if 0%{?sle_version} >= 150400 || 0%{?suse_version} >= 1540 ++++++ openssl-CVE-2026-14456.patch ++++++ >From 08e7756c3900bcfd77a720e7b74e27d6e4ed01a9 Mon Sep 17 00:00:00 2001 From: Alexandr Nedvedicky <[email protected]> Date: Thu, 23 Jul 2026 09:38:02 +0200 Subject: [PATCH] QUIC server: limit number of pending QUIC channels/connections Currently, there is no limit for pending QUIC connections. The port default packet handler creates channel for every valid initial packet which does belong to existing channel (a.k.a. connection). The newly created channel is inserted to list of pending channels where it waits to be accepted by local application by call to SSL_accept_connection(3ossl). This change introduces a limit for pending connection. The pending queue is limited to 256 pending connections. Applications may change the limit by calling SSL_set_feature_request_uint(3ossl) on SSL server listener object with configurable value SSL_VALUE_QUIC_MAX_PENDING_CONNS. Fixes: CVE-2026-14456 Reviewed-by: Eugene Syromiatnikov <[email protected]> Reviewed-by: Andrew Dinh <[email protected]> Reviewed-by: Neil Horman <[email protected]> MergeDate: Wed Aug 12 15:00:25 2026 (Merged from https://github.com/openssl/openssl/pull/32052) (cherry picked from commit 9416706d408bb84deb7cee4647bff3045d2dc7ba) (cherry picked from commit 4084152e040329ca0194c4c1750b9b46d00a5b6b) --- doc/man3/SSL_get_value_uint.pod | 20 +++++++++++++++-- include/internal/quic_port.h | 4 ++++ include/openssl/ssl.h.in | 1 + ssl/quic/quic_impl.c | 38 +++++++++++++++++++++++++++++++++ ssl/quic/quic_port.c | 16 ++++++++++++++ ssl/quic/quic_port_local.h | 1 + util/other.syms | 1 + 7 files changed, 79 insertions(+), 2 deletions(-) Index: openssl-3.5.0/doc/man3/SSL_get_value_uint.pod =================================================================== --- openssl-3.5.0.orig/doc/man3/SSL_get_value_uint.pod +++ openssl-3.5.0/doc/man3/SSL_get_value_uint.pod @@ -12,6 +12,7 @@ SSL_VALUE_CLASS_FEATURE_REQUEST, SSL_VAL SSL_VALUE_CLASS_FEATURE_NEGOTIATED, SSL_VALUE_QUIC_STREAM_BIDI_LOCAL_AVAIL, SSL_VALUE_QUIC_STREAM_BIDI_REMOTE_AVAIL, SSL_VALUE_QUIC_STREAM_UNI_LOCAL_AVAIL, SSL_VALUE_QUIC_STREAM_UNI_REMOTE_AVAIL, SSL_VALUE_QUIC_IDLE_TIMEOUT, +SSL_VALUE_QUIC_MAX_PENDING_CONNS, SSL_VALUE_EVENT_HANDLING_MODE, SSL_VALUE_EVENT_HANDLING_MODE_INHERIT, SSL_VALUE_EVENT_HANDLING_MODE_EXPLICIT, @@ -45,6 +46,7 @@ manage negotiable features and configura #define SSL_VALUE_QUIC_STREAM_UNI_LOCAL_AVAIL #define SSL_VALUE_QUIC_STREAM_UNI_REMOTE_AVAIL #define SSL_VALUE_QUIC_IDLE_TIMEOUT + #define SSL_VALUE_QUIC_MAX_PENDING_CONNS #define SSL_VALUE_EVENT_HANDLING_MODE #define SSL_VALUE_EVENT_HANDLING_MODE_INHERIT @@ -168,6 +170,16 @@ changed. This release of OpenSSL uses a default value of 30 seconds. This default value may change between releases of OpenSSL. +=item B<SSL_VALUE_QUIC_MAX_PENDING_CONNS> (listener object) + +Generic value, sets the limit on channels (connection objects) which a QUIC server can +insert into the list of pending connections. A pending connection is a connection +which the local application needs to accept (L<SSL_accept_connection(3)>) in order to retrieve +an SSL connection object. The connection is removed from the pending queue by a call +to L<SSL_accept_connection(3)>. The default limit for pending connections is 256. An INITIAL +QUIC packet, which is received by a QUIC server with a full pending connections +queue, is silently discarded. Setting the value to zero disables the limit. + =item B<SSL_VALUE_QUIC_STREAM_BIDI_LOCAL_AVAIL> (connection object) Generic read-only statistical value. The number of bidirectional, @@ -335,11 +347,15 @@ time. L<SSL_ctrl(3)>, L<SSL_get_accept_stream_queue_len(3)>, L<SSL_get_stream_read_state(3)>, L<SSL_get_stream_write_state(3)>, L<SSL_get_stream_read_error_code(3)>, L<SSL_get_stream_write_error_code(3)>, -L<SSL_set_default_stream_mode(3)>, L<SSL_set_incoming_stream_policy(3)> +L<SSL_set_default_stream_mode(3)>, L<SSL_set_incoming_stream_policy(3)>, +L<SSL_accept_connection(3)> =head1 HISTORY -These functions were added in OpenSSL 3.3. +The value SSL_VALUE_QUIC_MAX_PENDING_CONNS has been added in OpenSSL 4.1 +and ported to older releases 4.0.2, 3.6.4 and 3.5.8. + +The remaining functions and values described here were all added in OpenSSL 3.3. =head1 COPYRIGHT Index: openssl-3.5.0/include/internal/quic_port.h =================================================================== --- openssl-3.5.0.orig/include/internal/quic_port.h +++ openssl-3.5.0/include/internal/quic_port.h @@ -189,4 +189,8 @@ void ossl_quic_port_raise_net_error(QUIC # endif +uint64_t ossl_quic_port_get_max_pending_channels(const QUIC_PORT *port); + +void ossl_quic_port_set_max_pending_channels(QUIC_PORT *port, uint64_t max_pending_channels); + #endif Index: openssl-3.5.0/include/openssl/ssl.h.in =================================================================== --- openssl-3.5.0.orig/include/openssl/ssl.h.in +++ openssl-3.5.0/include/openssl/ssl.h.in @@ -2439,6 +2439,7 @@ __owur int SSL_get_conn_close_info(SSL * # define SSL_VALUE_STREAM_WRITE_BUF_SIZE 7 # define SSL_VALUE_STREAM_WRITE_BUF_USED 8 # define SSL_VALUE_STREAM_WRITE_BUF_AVAIL 9 +# define SSL_VALUE_QUIC_MAX_PENDING_CONNS 16 # define SSL_VALUE_EVENT_HANDLING_MODE_INHERIT 0 # define SSL_VALUE_EVENT_HANDLING_MODE_IMPLICIT 1 Index: openssl-3.5.0/ssl/quic/quic_impl.c =================================================================== --- openssl-3.5.0.orig/ssl/quic/quic_impl.c +++ openssl-3.5.0/ssl/quic/quic_impl.c @@ -383,6 +383,11 @@ static int expect_quic_cs(const SSL *s, return expect_quic_as(s, ctx, QCTX_C | QCTX_S); } +static int expect_quic_cl(const SSL *s, QCTX *ctx) +{ + return expect_quic_as(s, ctx, QCTX_C | QCTX_L); +} + static int expect_quic_csl(const SSL *s, QCTX *ctx) { return expect_quic_as(s, ctx, QCTX_C | QCTX_S | QCTX_L); @@ -3591,6 +3596,33 @@ err: } QUIC_TAKES_LOCK +static int qc_getset_max_pending_channels(QCTX *ctx, uint32_t class_, + uint64_t *p_value_out, uint64_t *p_value_in) +{ + int ret = 0; + uint64_t value_out = 0; + + qctx_lock(ctx); + + if (class_ == SSL_VALUE_CLASS_GENERIC && ctx->is_listener) { + value_out = ossl_quic_port_get_max_pending_channels(ctx->ql->port); + if (p_value_in != NULL) + ossl_quic_port_set_max_pending_channels(ctx->ql->port, *p_value_in); + ret = 1; + } else { + QUIC_RAISE_NON_NORMAL_ERROR(ctx, SSL_R_UNSUPPORTED_CONFIG_VALUE_CLASS, NULL); + ret = 0; + } + + qctx_unlock(ctx); + + if (ret && p_value_out != NULL) + *p_value_out = value_out; + + return ret; +} + +QUIC_TAKES_LOCK static int qc_get_stream_avail(QCTX *ctx, uint32_t class_, int is_uni, int is_remote, uint64_t *value) @@ -3723,6 +3755,8 @@ static int expect_quic_for_value(SSL *s, case SSL_VALUE_STREAM_WRITE_BUF_USED: case SSL_VALUE_STREAM_WRITE_BUF_AVAIL: return expect_quic_cs(s, ctx); + case SSL_VALUE_QUIC_MAX_PENDING_CONNS: + return expect_quic_cl(s, ctx); default: return expect_quic_conn_only(s, ctx); } @@ -3744,6 +3778,8 @@ int ossl_quic_get_value_uint(SSL *s, uin switch (id) { case SSL_VALUE_QUIC_IDLE_TIMEOUT: return qc_getset_idle_timeout(&ctx, class_, value, NULL); + case SSL_VALUE_QUIC_MAX_PENDING_CONNS: + return qc_getset_max_pending_channels(&ctx, class_, value, NULL); case SSL_VALUE_QUIC_STREAM_BIDI_LOCAL_AVAIL: return qc_get_stream_avail(&ctx, class_, /*uni=*/0, /*remote=*/0, value); @@ -3790,6 +3826,8 @@ int ossl_quic_set_value_uint(SSL *s, uin case SSL_VALUE_EVENT_HANDLING_MODE: return qc_getset_event_handling(&ctx, class_, NULL, &value); + case SSL_VALUE_QUIC_MAX_PENDING_CONNS: + return qc_getset_max_pending_channels(&ctx, class_, NULL, &value); default: return QUIC_RAISE_NON_NORMAL_ERROR(&ctx, Index: openssl-3.5.0/ssl/quic/quic_port.c =================================================================== --- openssl-3.5.0.orig/ssl/quic/quic_port.c +++ openssl-3.5.0/ssl/quic/quic_port.c @@ -93,6 +93,8 @@ typedef struct validation_token { */ #define ENCRYPTED_TOKEN_MAX_LEN (MARSHALLED_TOKEN_MAX_LEN + 16 + 12) +#define DEFAULT_MAX_PENDING_CONNS 256 + DEFINE_LIST_OF_IMPL(ch, QUIC_CHANNEL); DEFINE_LIST_OF_IMPL(incoming_ch, QUIC_CHANNEL); DEFINE_LIST_OF_IMPL(port, QUIC_PORT); @@ -110,6 +112,7 @@ QUIC_PORT *ossl_quic_port_new(const QUIC port->validate_addr = args->do_addr_validation; port->get_conn_user_ssl = args->get_conn_user_ssl; port->user_ssl_arg = args->user_ssl_arg; + port->max_pending_channels = DEFAULT_MAX_PENDING_CONNS; if (!port_init(port)) { OPENSSL_free(port); @@ -1560,6 +1563,9 @@ static void port_default_packet_handler( if (hdr.type != QUIC_PKT_TYPE_INITIAL) goto undesirable; + if (port->max_pending_channels > 0 && ossl_list_incoming_ch_num(&port->incoming_channel_list) >= port->max_pending_channels) + goto undesirable; + odcid.id_len = 0; /* @@ -1738,3 +1744,13 @@ void ossl_quic_port_restore_err_state(co ERR_clear_error(); OSSL_ERR_STATE_restore(port->err_state); } + +uint64_t ossl_quic_port_get_max_pending_channels(const QUIC_PORT *port) +{ + return port->max_pending_channels; +} + +void ossl_quic_port_set_max_pending_channels(QUIC_PORT *port, uint64_t max_pending_channels) +{ + port->max_pending_channels = max_pending_channels; +} Index: openssl-3.5.0/ssl/quic/quic_port_local.h =================================================================== --- openssl-3.5.0.orig/ssl/quic/quic_port_local.h +++ openssl-3.5.0/ssl/quic/quic_port_local.h @@ -116,6 +116,7 @@ struct quic_port_st { /* AES-256 GCM context for token encryption */ EVP_CIPHER_CTX *token_ctx; + uint64_t max_pending_channels; }; # endif Index: openssl-3.5.0/util/other.syms =================================================================== --- openssl-3.5.0.orig/util/other.syms +++ openssl-3.5.0/util/other.syms @@ -781,6 +781,7 @@ SSL_VALUE_CLASS_FEATURE_REQUEST SSL_VALUE_CLASS_FEATURE_PEER_REQUEST define SSL_VALUE_CLASS_FEATURE_NEGOTIATED define SSL_VALUE_QUIC_IDLE_TIMEOUT define +SSL_VALUE_QUIC_MAX_PENDING_CONNS define SSL_VALUE_QUIC_STREAM_BIDI_LOCAL_AVAIL define SSL_VALUE_QUIC_STREAM_BIDI_REMOTE_AVAIL define SSL_VALUE_QUIC_STREAM_UNI_LOCAL_AVAIL define ++++++ openssl-CVE-2026-14457.patch ++++++ commit 796b30a3e4db85b64c61a86a8ac30aa34ac29860 Author: Viktor Dukhovni <[email protected]> Date: Sat Jun 27 01:02:53 2026 +1000 Handle signature_algorithms_cert extension in key-only context Servers or clients that configure only a private key in expectation of always negotiating use of RFC7250 raw public keys failed to handle the "signature_algorithms_cert" extension. The issue is now resolved and the RPK tests now check that key-only configurations are robust also when the extension is sent by the peer. Key-only configurations are quite uncommon. As a best practice, RPK-capable servers and clients pair their private key with a (possibly self-signed) certificate, enabling fallback to X.509 handshakes with non-RPK peers. Fixes CVE-2026-14457 Index: openssl-3.5.0/ssl/t1_lib.c =================================================================== --- openssl-3.5.0.orig/ssl/t1_lib.c +++ openssl-3.5.0/ssl/t1_lib.c @@ -4545,6 +4545,20 @@ static int check_cert_usable(SSL_CONNECT return 0; /* + * When RPK is negotiated there are no certificate signatures to + * constrain, and there may not even be a certificate configured. + */ + if (TLSEXT_cert_type_rpk == (s->server ? s->ext.server_cert_type : s->ext.client_cert_type)) + return 1; + + /* + * RPK was enabled, adding candidate private-key-only slots, but was not + * negotiated, so the key-only slot is not usable. + */ + if (x == NULL) + return 0; + + /* * The TLS 1.3 signature_algorithms_cert extension places restrictions * on the sigalg with which the certificate was signed (by its issuer). */ Index: openssl-3.5.0/test/rpktest.c =================================================================== --- openssl-3.5.0.orig/test/rpktest.c +++ openssl-3.5.0/test/rpktest.c @@ -38,6 +38,37 @@ static OSSL_PROVIDER *defctxnull = NULL; static const unsigned char cert_type_rpk[] = { TLSEXT_cert_type_rpk, TLSEXT_cert_type_x509 }; static const unsigned char SID_CTX[] = { 'r', 'p', 'k' }; +/* + * Wire form of a SignatureSchemeList that lists rsa_pkcs1_sha256 + * and ed448 -- between them they cover the issuer signature on + * every cert this file loads from test/certs + * (sha256WithRSAEncryption for the RSA/ECDSA/Ed25519 leaves and + * ED448 for the Ed448 leaf), so the extension is harmless when + * the handshake is non-RPK and the server's check_cert_usable() + * has to walk the list against a real cert. When RPK is + * negotiated check_cert_usable() returns early without inspecting + * the list, and when the slot is an RPK-listed key-only slot but + * X509 was negotiated check_cert_usable() returns 0 on the x == + * NULL path -- the inevitable outcome, now discovered earlier. + * + * Payload: length, rsa_pkcs1_sha256, ed448 + */ +static const unsigned char sigalgs_cert_payload[] = { + 0x00, 0x04, + 0x04, 0x01, + 0x08, 0x08 +}; + +static int sigalgs_cert_add_cb(SSL *s, unsigned int ext_type, + unsigned int context, + const unsigned char **out, size_t *outlen, + X509 *x, size_t chainidx, int *al, void *add_arg) +{ + *out = sigalgs_cert_payload; + *outlen = sizeof(sigalgs_cert_payload); + return 1; +} + static int rpk_verify_client_cb(int ok, X509_STORE_CTX *ctx) { int err = X509_STORE_CTX_get_error(ctx); @@ -255,18 +318,43 @@ static int test_rpk(int idx) /* NEW */ SSL_CTX_set_verify(cctx, SSL_VERIFY_PEER, rpk_verify_client_cb); - if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl, - NULL, NULL))) + /* + * Send signature_algorithms_cert in every ClientHello, and in + * every TLS 1.3 CertificateRequest. The OpenSSL stack doesn't + * construct this extension by default in either direction, so + * register a custom add hook on both ends. This exercises the + * three distinct paths through check_cert_usable() on whichever + * side receives the extension: + * - RPK was negotiated for this side's cert -- early return 1, + * list contents ignored. + * - RPK was offered but X509 was negotiated and this side's + * slot holds only a private key -- x == NULL, return 0 + * (any peer-sent signature_algorithms_cert against a key-only + * slot would otherwise trigger a crash). + * - X509 negotiated with a real cert -- walk the list, find + * a match against the issuer's signature algorithm. + * The server's registration only fires on TLS 1.3 connections + * where the server requests a client certificate (case 2, 9, + * 10 etc.); on TLS 1.2 the sigalgs travel inside the + * CertificateRequest body, not as a separate extension. + */ + if (!TEST_true(SSL_CTX_add_custom_ext(cctx, + TLSEXT_TYPE_signature_algorithms_cert, + SSL_EXT_CLIENT_HELLO, + sigalgs_cert_add_cb, NULL, NULL, + NULL, NULL)) + || !TEST_true(SSL_CTX_add_custom_ext(sctx, + TLSEXT_TYPE_signature_algorithms_cert, + SSL_EXT_TLS1_3_CERTIFICATE_REQUEST, + sigalgs_cert_add_cb, NULL, NULL, + NULL, NULL)) + || !TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl, + NULL, NULL)) + || !TEST_int_gt(SSL_dane_enable(serverssl, NULL), 0) + || !TEST_int_gt(SSL_dane_enable(clientssl, "example.com"), 0) + || !TEST_int_eq(SSL_use_PrivateKey_file(serverssl, privkey_file, SSL_FILETYPE_PEM), 1)) goto end; - if (!TEST_int_gt(SSL_dane_enable(serverssl, NULL), 0)) - goto end; - if (!TEST_int_gt(SSL_dane_enable(clientssl, "example.com"), 0)) - goto end; - - /* Set private key and certificate */ - if (!TEST_int_eq(SSL_use_PrivateKey_file(serverssl, privkey_file, SSL_FILETYPE_PEM), 1)) - goto end; /* Only a private key */ if (idx == 1) { if (idx_server_server_rpk == 0 || idx_client_server_rpk == 0) { ++++++ openssl-CVE-2026-18798.patch ++++++ commit 7446ec3f872d035cf3b18056594c6262a42cd843 Author: Alexandr Nedvedicky <[email protected]> Date: Wed Aug 5 00:56:25 2026 +0200 Avoid double free of qrx in port_default_packet_handler() port_default_packet_handler() may perform double free of qrx when channel creation fails. The port_default_packet_handler() transfers ownership of qrx to channel/connection via call to port_bind_channel(). The port_bind_channel() however may release the qrx when channel can not be bound. The error is then detected in port_default_packet_handler() which then agains releases qrx for the second time. The fix is to add a reference counter to QRX object so transfer of ownership between port_default_packet_handler() and QUIC_CHANNEL can be handled safely. Fixes CVE-2026-18798 Index: openssl-3.5.3/include/internal/quic_record_rx.h =================================================================== --- openssl-3.5.3.orig/include/internal/quic_record_rx.h +++ openssl-3.5.3/include/internal/quic_record_rx.h @@ -51,8 +51,9 @@ typedef struct ossl_qrx_args_st { OSSL_QRX *ossl_qrx_new(const OSSL_QRX_ARGS *args); /* - * Frees the QRX. All packets obtained using ossl_qrx_read_pkt must already - * have been released by calling ossl_qrx_release_pkt. + * Frees the QRX/reference to QRX. Frees the QRX object, if all references are + * gone. All packets obtained using ossl_qrx_read_pkt must already have been + * released by calling ossl_qrx_release_pkt. * * You do not need to call ossl_qrx_remove_dst_conn_id first; this function will * unregister the QRX from the demuxer for all registered destination connection @@ -60,6 +61,12 @@ OSSL_QRX *ossl_qrx_new(const OSSL_QRX_AR */ void ossl_qrx_free(OSSL_QRX *qrx); +/* + * Obtains a new reference to QRX object. Returns NULL if reference can not + * be obtained. + */ +OSSL_QRX *ossl_qrx_newref(OSSL_QRX *qrx); + /* Setters for the msg_callback and msg_callback_arg */ void ossl_qrx_set_msg_callback(OSSL_QRX *qrx, ossl_msg_cb msg_callback, SSL *msg_callback_ssl); Index: openssl-3.5.3/ssl/quic/quic_port.c =================================================================== --- openssl-3.5.3.orig/ssl/quic/quic_port.c +++ openssl-3.5.3/ssl/quic/quic_port.c @@ -530,8 +530,10 @@ static QUIC_CHANNEL *port_make_channel(Q * start by allocation and provisioning as much of the channel as we can */ ch = ossl_quic_channel_alloc(&args); - if (ch == NULL) + if (ch == NULL) { + ossl_qrx_free(qrx); return NULL; + } /* * Fixup the channel tls connection here before we init the channel @@ -1485,7 +1487,7 @@ static void port_default_packet_handler( QUIC_CHANNEL *ch = NULL, *new_ch = NULL; QUIC_CONN_ID odcid, scid; uint8_t gen_new_token = 0; - OSSL_QRX *qrx = NULL; + OSSL_QRX *qrx = NULL, *qrx_ref; OSSL_QRX *qrx_src = NULL; OSSL_QRX_ARGS qrx_args = {0}; uint64_t cause_flags = 0; @@ -1671,8 +1673,22 @@ static void port_default_packet_handler( } } + qrx_ref = NULL; + if (qrx != NULL) { + /* + * if we are here, then client is validated via retry packet + * (client sent a valid token). In this case the qrx has valid + * secrets set for QUIC initial level encryption. We can pass + * reference to qrx to newly created channel. + * + * Note: port_bind_channel()/channel becomes owner of qrx_ref. + */ + qrx_ref = ossl_qrx_newref(qrx); + if (qrx_ref == NULL) + goto undesirable; + } port_bind_channel(port, &e->peer, &scid, &hdr.dst_conn_id, - &odcid, qrx, &new_ch); + &odcid, qrx_ref, &new_ch); /* * if packet validates it gets moved to channel, we've just bound @@ -1687,19 +1703,19 @@ static void port_default_packet_handler( if (gen_new_token == 1) generate_new_token(new_ch, &e->peer); - if (qrx != NULL) { + if (qrx_src != NULL) { /* - * The qrx belongs to channel now, so don't free it. - */ - qrx = NULL; - } else { - /* - * We still need to salvage packets from almost forgotten qrx - * and pass them to channel. + * Time to reinject packets from qrx to channel before + * qrx will be destroyed here. */ while (ossl_qrx_read_pkt(qrx_src, &qrx_pkt) == 1) ossl_quic_channel_inject_pkt(new_ch, qrx_pkt); ossl_qrx_update_pn_space(qrx_src, new_ch->qrx); + /* + * transfer ownership back to qrx; + */ + qrx = qrx_src; + qrx_src = NULL; } /* @@ -1716,7 +1732,7 @@ static void port_default_packet_handler( */ undesirable: - ossl_qrx_free(qrx); + ossl_qrx_free(qrx); /* releases reference */ ossl_qrx_free(qrx_src); ossl_quic_demux_release_urxe(port->demux, e); } Index: openssl-3.5.3/ssl/quic/quic_record_rx.c =================================================================== --- openssl-3.5.3.orig/ssl/quic/quic_record_rx.c +++ openssl-3.5.3/ssl/quic/quic_record_rx.c @@ -171,6 +171,8 @@ struct ossl_qrx_st { ossl_msg_cb msg_callback; void *msg_callback_arg; SSL *msg_callback_ssl; + + uint32_t refcount; }; static RXE *qrx_ensure_free_rxe(OSSL_QRX *qrx, size_t alloc_len); @@ -212,6 +214,7 @@ OSSL_QRX *ossl_qrx_new(const OSSL_QRX_AR qrx->short_conn_id_len = args->short_conn_id_len; qrx->init_key_phase_bit = args->init_key_phase_bit; qrx->max_deferred = args->max_deferred; + qrx->refcount = 1; return qrx; } @@ -247,13 +250,10 @@ void ossl_qrx_update_pn_space(OSSL_QRX * return; } -void ossl_qrx_free(OSSL_QRX *qrx) +static void qrx_destroy(OSSL_QRX *qrx) { uint32_t i; - if (qrx == NULL) - return; - /* Free RXE queue data. */ qrx_cleanup_rxl(&qrx->rx_free); qrx_cleanup_rxl(&qrx->rx_pending); @@ -267,6 +267,30 @@ void ossl_qrx_free(OSSL_QRX *qrx) OPENSSL_free(qrx); } +void ossl_qrx_free(OSSL_QRX *qrx) +{ + if (qrx == NULL) + return; + + qrx->refcount--; + if (qrx->refcount == 0) + qrx_destroy(qrx); +} + +OSSL_QRX *ossl_qrx_newref(OSSL_QRX *qrx) +{ + OSSL_QRX *rv_qrx; + + if (qrx != NULL && qrx->refcount != (uint32_t)~0) { + qrx->refcount++; + rv_qrx = qrx; + } else { + rv_qrx = NULL; + } + + return rv_qrx; +} + void ossl_qrx_inject_urxe(OSSL_QRX *qrx, QUIC_URXE *urxe) { /* Initialize our own fields inside the URXE and add to the pending list. */ ++++++ openssl-CVE-2026-34181.patch ++++++ >From afe522fec8038db9a6c8b99b0fd2b1ebd49b5592 Mon Sep 17 00:00:00 2001 From: Alicja Kario <[email protected]> Date: Wed, 29 Apr 2026 16:29:35 +0200 Subject: [PATCH] pkcs12: verify that the pbmac1 key length is safe Short mac keys (as short as 1 byte) can be used to probe the system under attack to accept a PKCS#12 file created by an attacker even if the attacker doesn't know the password used for MAC protection. Fixes CVE-2026-34181 (also update the reference to the PBMAC1 PKCS#12 RFC) Signed-off-by: Alicja Kario <[email protected]> --- crypto/pkcs12/p12_mutl.c | 7 ++++--- test/recipes/80-test_pkcs12.t | 13 ++++++++----- .../pbmac1_256_256.bad-key-len.p12 | Bin 0 -> 2803 bytes .../pbmac1_256_256.good-shorter-key-len.p12 | Bin 0 -> 2803 bytes 4 files changed, 12 insertions(+), 8 deletions(-) create mode 100644 test/recipes/80-test_pkcs12_data/pbmac1_256_256.bad-key-len.p12 create mode 100644 test/recipes/80-test_pkcs12_data/pbmac1_256_256.good-shorter-key-len.p12 Index: openssl-3.5.0/crypto/pkcs12/p12_mutl.c =================================================================== --- openssl-3.5.0.orig/crypto/pkcs12/p12_mutl.c +++ openssl-3.5.0/crypto/pkcs12/p12_mutl.c @@ -144,11 +144,13 @@ static int PBMAC1_PBKDF2_HMAC(OSSL_LIB_C } pbkdf2_salt = pbkdf2_param->salt->value.octet_string; - /* RFC 9579 specifies missing key length as invalid */ + /* RFC 9879 specifies missing key length as invalid */ if (pbkdf2_param->keylength != NULL) keylen = ASN1_INTEGER_get(pbkdf2_param->keylength); - if (keylen <= 0 || keylen > EVP_MAX_MD_SIZE) { - ERR_raise(ERR_LIB_PKCS12, PKCS12_R_PARSE_ERROR); + /* RFC 9879 specifies too short key length as untrustworthy too */ + if (keylen < 20 || keylen > EVP_MAX_MD_SIZE) { + ERR_raise_data(ERR_LIB_PKCS12, PKCS12_R_PARSE_ERROR, + "Invalid Key length (%d is not in the range 20..64)", keylen); goto err; } ++++++ openssl-CVE-2026-54874.patch ++++++ commit 5cf71fc0fef60fabe39c5b8eabb2b668a0d9c395 Author: Matt Caswell <[email protected]> Date: Tue Jun 23 11:53:17 2026 +0100 Avoid full read buffer allocation when buffering DTLS next-epoch records dtls_rlayer_buffer_record() buffers records that arrive early for the next epoch while a handshake is in progress. It did this by taking ownership of the entire live read buffer (sized for the largest possible record, ~16.7KB) and allocating a brand new one to carry on reading, regardless of how small the buffered record actually was. With the queue capped at 100 entries, a peer could send around 100 tiny bogus next-epoch records (~14 bytes each on the wire) and force around 1.7MB of heap allocation per connection. Instead, copy only the record's own on-wire bytes (header and ciphertext) into the queue entry, and leave the live read buffer untouched. Memory use is now proportional to what the peer actually sends. Fixes CVE-2026-54874 Assisted-by: Claude:claude-sonnet-4-6 Index: openssl-3.5.0/ssl/record/methods/dtls_meth.c =================================================================== --- openssl-3.5.0.orig/ssl/record/methods/dtls_meth.c +++ openssl-3.5.0/ssl/record/methods/dtls_meth.c @@ -285,7 +285,7 @@ static int dtls_rlayer_buffer_record(OSS pitem *item; /* Limit the size of the queue to prevent DOS attacks */ - if (pqueue_size(queue) >= 100) + if (pqueue_size(queue) >= 16) return 0; rdata = OPENSSL_malloc(sizeof(*rdata)); @@ -297,29 +297,26 @@ static int dtls_rlayer_buffer_record(OSS return -1; } - rdata->packet = rl->packet; + /* + * Take a copy of just this record's on-wire bytes (header + ciphertext) + * rather than the whole (much larger) read buffer. The live rl->rbuf is + * left untouched and continues to be used for subsequent reads. + */ rdata->packet_length = rl->packet_length; - memcpy(&(rdata->rbuf), &rl->rbuf, sizeof(TLS_BUFFER)); - memcpy(&(rdata->rrec), &rl->rrec[0], sizeof(TLS_RL_RECORD)); - - item->data = rdata; - - rl->packet = NULL; - rl->packet_length = 0; - memset(&rl->rbuf, 0, sizeof(TLS_BUFFER)); - memset(&rl->rrec[0], 0, sizeof(rl->rrec[0])); - - if (!tls_setup_read_buffer(rl)) { - /* RLAYERfatal() already called */ - OPENSSL_free(rdata->rbuf.buf); + rdata->packet = OPENSSL_memdup(rl->packet, rl->packet_length); + if (rdata->packet == NULL) { OPENSSL_free(rdata); pitem_free(item); + RLAYERfatal(rl, SSL_AD_INTERNAL_ERROR, ERR_R_CRYPTO_LIB); return -1; } + memcpy(&(rdata->rrec), &rl->rrec[0], sizeof(TLS_RL_RECORD)); + + item->data = rdata; if (pqueue_insert(queue, item) == NULL) { /* Must be a duplicate so ignore it */ - OPENSSL_free(rdata->rbuf.buf); + OPENSSL_free(rdata->packet); OPENSSL_free(rdata); pitem_free(item); } @@ -327,44 +324,6 @@ static int dtls_rlayer_buffer_record(OSS return 1; } -/* copy buffered record into OSSL_RECORD_LAYER structure */ -static int dtls_copy_rlayer_record(OSSL_RECORD_LAYER *rl, pitem *item) -{ - DTLS_RLAYER_RECORD_DATA *rdata; - - rdata = (DTLS_RLAYER_RECORD_DATA *)item->data; - - ossl_tls_buffer_release(&rl->rbuf); - - rl->packet = rdata->packet; - rl->packet_length = rdata->packet_length; - memcpy(&rl->rbuf, &(rdata->rbuf), sizeof(TLS_BUFFER)); - memcpy(&rl->rrec[0], &(rdata->rrec), sizeof(TLS_RL_RECORD)); - - /* Set proper sequence number for mac calculation */ - memcpy(&(rl->sequence[2]), &(rdata->packet[5]), 6); - - return 1; -} - -static int dtls_retrieve_rlayer_buffered_record(OSSL_RECORD_LAYER *rl, - struct pqueue_st *queue) -{ - pitem *item; - - item = pqueue_pop(queue); - if (item) { - dtls_copy_rlayer_record(rl, item); - - OPENSSL_free(item->data); - pitem_free(item); - - return 1; - } - - return 0; -} - /*- * Call this to get a new input record. * It will return <= 0 if more data is needed, normally due to an error @@ -398,12 +357,6 @@ int dtls_get_more_records(OSSL_RECORD_LA } again: - /* if we're renegotiating, then there may be buffered records */ - if (dtls_retrieve_rlayer_buffered_record(rl, rl->processed_rcds)) { - rl->num_recs = 1; - return OSSL_RECORD_RETURN_SUCCESS; - } - /* get something from the wire */ /* check if we have the header */ @@ -601,23 +554,13 @@ static int dtls_free(OSSL_RECORD_LAYER * /* Push to the next record layer */ ret &= BIO_write_ex(rl->next, rdata->packet, rdata->packet_length, &written); - OPENSSL_free(rdata->rbuf.buf); + OPENSSL_free(rdata->packet); OPENSSL_free(item->data); pitem_free(item); } pqueue_free(rl->unprocessed_rcds); } - if (rl->processed_rcds!= NULL) { - while ((item = pqueue_pop(rl->processed_rcds)) != NULL) { - rdata = (DTLS_RLAYER_RECORD_DATA *)item->data; - OPENSSL_free(rdata->rbuf.buf); - OPENSSL_free(item->data); - pitem_free(item); - } - pqueue_free(rl->processed_rcds); - } - return tls_free(rl) && ret; } @@ -647,10 +590,8 @@ dtls_new_record_layer(OSSL_LIB_CTX *libc return ret; (*retrl)->unprocessed_rcds = pqueue_new(); - (*retrl)->processed_rcds = pqueue_new(); - if ((*retrl)->unprocessed_rcds == NULL - || (*retrl)->processed_rcds == NULL) { + if ((*retrl)->unprocessed_rcds == NULL) { dtls_free(*retrl); *retrl = NULL; ERR_raise(ERR_LIB_SSL, ERR_R_SSL_LIB); Index: openssl-3.5.0/ssl/record/methods/recmethod_local.h =================================================================== --- openssl-3.5.0.orig/ssl/record/methods/recmethod_local.h +++ openssl-3.5.0/ssl/record/methods/recmethod_local.h @@ -345,9 +345,8 @@ struct ossl_record_layer_st { size_t taglen; - /* DTLS received handshake records (processed and unprocessed) */ + /* DTLS received handshake records awaiting the next epoch */ struct pqueue_st *unprocessed_rcds; - struct pqueue_st *processed_rcds; /* records being received in the current epoch */ DTLS_BITMAP bitmap; @@ -375,7 +374,6 @@ struct ossl_record_layer_st { typedef struct dtls_rlayer_record_data_st { unsigned char *packet; size_t packet_length; - TLS_BUFFER rbuf; TLS_RL_RECORD rrec; } DTLS_RLAYER_RECORD_DATA; ++++++ openssl-CVE-2026-63072.patch ++++++ commit cf7f399052febacbd6a1ff7f33021dfc2db2fc07 Author: Daniel Kubec <[email protected]> Date: Thu Jul 23 11:09:55 2026 +0200 Add test for CVE-2026-63072 Assisted-by: Claude:claude-fable-5 Index: openssl-3.5.0/test/cmsapitest.c =================================================================== --- openssl-3.5.0.orig/test/cmsapitest.c +++ openssl-3.5.0/test/cmsapitest.c @@ -21,6 +21,7 @@ static X509 *cert = NULL; static EVP_PKEY *privkey = NULL; static char *derin = NULL; static char *too_long_iv_cms_in = NULL; +static char *ec_recip_in = NULL; static int test_encrypt_decrypt(const EVP_CIPHER *cipher) { @@ -418,7 +419,102 @@ end: return ret; } -OPT_TEST_DECLARE_USAGE("certfile privkeyfile derfile\n") + + +#ifndef OPENSSL_NO_EC + +/* + * Regression test for CVE-2026-63072: an 8-byte out-of-bounds heap write + * reachable through CMS_decrypt() when a KeyAgreeRecipientInfo names an + * id-aesNNN-wrap-pad key-wrap OID. CMS sizes the unwrap output buffer from + * the cipher's length query (inlen - 8), but AES-WRAP-PAD unwrap cleanses + * inlen bytes of it on every RFC 5649 integrity-failure path. + * + * We build a valid ECDH KARI message (which uses non-padded id-aes256-wrap), + * flip the single OID byte an attacker would flip on the wire to turn it into + * id-aes256-wrap-pad (key length unchanged), and decrypt with the matching + * private key. The unwrap must fail its integrity check without writing past + * the CMS-allocated buffer; CMS_decrypt() must fail cleanly. Under a + * memory-checking build (e.g. valgrind) the overflow is flagged directly. + */ +static int test_kari_wrap_pad_unwrap_overflow(void) +{ + /* DER encoding of the id-aes256-wrap OID (2.16.840.1.101.3.4.1.45). */ + static const unsigned char aes256_wrap_oid[] = { + 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x2d + }; + int ret = 0; + X509 *eccert = NULL; + EVP_PKEY *eckey = NULL; + BIO *certbio = NULL, *keybio = NULL, *msgbio = NULL, *outbio = NULL; + STACK_OF(X509) *recips = NULL; + CMS_ContentInfo *cms = NULL, *cms2 = NULL; + unsigned char *der = NULL; + const unsigned char *p; + int derlen, i, patched = 0; + const char *msg = "secret content for kari"; + + if ((certbio = BIO_new_file(ec_recip_in, "r")) == NULL + || PEM_read_bio_X509(certbio, &eccert, NULL, NULL) == NULL + || (keybio = BIO_new_file(ec_recip_in, "r")) == NULL + || PEM_read_bio_PrivateKey(keybio, &eckey, NULL, NULL) == NULL) { + goto end; + } + + if (!TEST_ptr(recips = sk_X509_new_null()) + || !TEST_int_gt(sk_X509_push(recips, eccert), 0)) + goto end; + + /* Build a normal ECDH KARI message; it uses non-padded id-aes256-wrap. */ + if (!TEST_ptr(msgbio = BIO_new_mem_buf(msg, (int)strlen(msg))) + || !TEST_ptr(cms = CMS_encrypt(recips, msgbio, EVP_aes_256_cbc(), + CMS_BINARY))) + goto end; + + if (!TEST_int_gt(derlen = i2d_CMS_ContentInfo(cms, &der), 0)) + goto end; + + /* Swap id-aes256-wrap -> id-aes256-wrap-pad (0x2d -> 0x30). */ + for (i = 0; i + (int)sizeof(aes256_wrap_oid) <= derlen; i++) { + if (memcmp(der + i, aes256_wrap_oid, sizeof(aes256_wrap_oid)) == 0) { + der[i + sizeof(aes256_wrap_oid) - 1] = 0x30; + patched = 1; + break; + } + } + if (!TEST_true(patched)) + goto end; + + p = der; + if (!TEST_ptr(cms2 = d2i_CMS_ContentInfo(NULL, &p, derlen))) + goto end; + + /* + * The wrap-pad unwrap fails the AIV check; with the fix it does so without + * writing past the CMS-allocated buffer. CMS_decrypt() must fail cleanly. + */ + if (!TEST_ptr(outbio = BIO_new(BIO_s_mem())) + || !TEST_false(CMS_decrypt(cms2, eckey, eccert, NULL, outbio, 0))) + goto end; + + ret = 1; +end: + ERR_clear_error(); + OPENSSL_free(der); + sk_X509_free(recips); + CMS_ContentInfo_free(cms); + CMS_ContentInfo_free(cms2); + BIO_free(certbio); + BIO_free(keybio); + BIO_free(msgbio); + BIO_free(outbio); + X509_free(eccert); + EVP_PKEY_free(eckey); + return ret; +} +#endif + +OPT_TEST_DECLARE_USAGE("certfile privkeyfile derfile ecrecip\n") int setup_tests(void) { @@ -433,7 +529,8 @@ int setup_tests(void) if (!TEST_ptr(certin = test_get_argument(0)) || !TEST_ptr(privkeyin = test_get_argument(1)) || !TEST_ptr(derin = test_get_argument(2)) - || !TEST_ptr(too_long_iv_cms_in = test_get_argument(3))) + || !TEST_ptr(too_long_iv_cms_in = test_get_argument(3)) + || !TEST_ptr(ec_recip_in = test_get_argument(4))) return 0; certbio = BIO_new_file(certin, "r"); @@ -467,6 +564,9 @@ int setup_tests(void) ADD_TEST(test_d2i_CMS_bio_NULL); ADD_ALL_TESTS(test_d2i_CMS_decode, 2); ADD_TEST(test_cms_aesgcm_iv_too_long); +#ifndef OPENSSL_NO_EC + ADD_TEST(test_kari_wrap_pad_unwrap_overflow); +#endif return 1; } Index: openssl-3.5.0/test/recipes/80-test_cmsapi.t =================================================================== --- openssl-3.5.0.orig/test/recipes/80-test_cmsapi.t +++ openssl-3.5.0/test/recipes/80-test_cmsapi.t @@ -19,5 +19,6 @@ plan tests => 1; ok(run(test(["cmsapitest", srctop_file("test", "certs", "servercert.pem"), srctop_file("test", "certs", "serverkey.pem"), srctop_file("test", "recipes", "80-test_cmsapi_data", "encryptedData.der"), - srctop_file("test", "recipes", "80-test_cmsapi_data", "encDataWithTooLongIV.pem")])), + srctop_file("test", "recipes", "80-test_cmsapi_data", "encDataWithTooLongIV.pem"), + srctop_file("test", "smime-certs", "smec1.pem")])), "running cmsapitest"); Index: openssl-3.5.0/crypto/cms/cms_kari.c =================================================================== --- openssl-3.5.0.orig/crypto/cms/cms_kari.c +++ openssl-3.5.0/crypto/cms/cms_kari.c @@ -217,6 +217,7 @@ static int cms_kek_cipher(unsigned char int rv = 0; unsigned char *out = NULL; int outlen; + size_t outsize; keklen = EVP_CIPHER_CTX_get_key_length(kari->ctx); if (keklen > EVP_MAX_KEY_LENGTH) @@ -230,7 +231,13 @@ static int cms_kek_cipher(unsigned char /* obtain output length of ciphered key */ if (!EVP_CipherUpdate(kari->ctx, NULL, &outlen, in, inlen)) goto err; - out = OPENSSL_malloc(outlen); + /* + * On its integrity-failure paths that primitive writes and cleanses up to + * inlen bytes of the output buffer. Size the buffer for that worst case so + * a failed unwrap cannot write past the allocation. + */ + outsize = (size_t)outlen < inlen ? inlen : (size_t)outlen; + out = OPENSSL_malloc(outsize); if (out == NULL) goto err; if (!EVP_CipherUpdate(kari->ctx, out, &outlen, in, inlen)) ++++++ openssl-CVE-2026-63073.patch ++++++ commit 69cc259407c14c689801ba677b292a1366ef90d7 Author: Norbert Pocs <[email protected]> Date: Mon Jul 20 14:10:47 2026 +0200 CMP unexpected sender DN used as format string in ERR_raise_data() ossl_cmp_msg_check_update() converts an unexpected CMP response sender DN with X509_NAME_oneline() and passes that peer-controlled string directly as the format argument to ERR_raise_data(). Printable percent characters survive the DN conversion, so a sender such as CN=%s%n reaches vsnprintf() as active format syntax without matching varargs. Fixes: CVE-2026-63073 Original patch by: Filipe Casal of Trail of Bits in collaboration with OpenAI Signed-off-by: Norbert Pocs <[email protected]> Index: openssl-3.5.0/crypto/cmp/cmp_vfy.c =================================================================== --- openssl-3.5.0.orig/crypto/cmp/cmp_vfy.c +++ openssl-3.5.0/crypto/cmp/cmp_vfy.c @@ -733,7 +733,7 @@ int ossl_cmp_msg_check_update(OSSL_CMP_C "expected sender", expected_sender)) { str = X509_NAME_oneline(actual_sender, NULL, 0); ERR_raise_data(ERR_LIB_CMP, CMP_R_UNEXPECTED_SENDER, - str != NULL ? str : "<unknown>"); + "%s", str != NULL ? str : "<unknown>"); OPENSSL_free(str); return 0; } Index: openssl-3.5.0/test/cmp_vfy_test.c =================================================================== --- openssl-3.5.0.orig/test/cmp_vfy_test.c +++ openssl-3.5.0/test/cmp_vfy_test.c @@ -574,6 +574,55 @@ static int test_msg_check_recipient_nonc } #endif +/* Regression test for CVE-2026-63073 */ +static int execute_msg_check_update_malicious_sender(CMP_VFY_TEST_FIXTURE *fixture) +{ + const char *data = NULL; + unsigned long err; + + if (!TEST_int_eq(ossl_cmp_msg_check_update(fixture->cmp_ctx, fixture->msg, NULL, 0), 0) + || !TEST_int_ne((err = ERR_peek_last_error_all(NULL, NULL, NULL, &data, NULL)), 0) + || !TEST_int_eq(ERR_GET_LIB(err), ERR_LIB_CMP) + || !TEST_int_eq(ERR_GET_REASON(err), CMP_R_UNEXPECTED_SENDER) + || !TEST_ptr(data) + || !TEST_str_eq(data, "/CN=%n")) + return 0; + return 1; +} + +static int test_msg_check_update_malicious_sender(void) +{ + OSSL_CMP_PKIHEADER *hdr; + X509_NAME *expected = X509_NAME_new(); + X509_NAME *actual = X509_NAME_new(); + + if (expected == NULL || actual == NULL) { + X509_NAME_free(expected); + return 0; + } + + SETUP_TEST_FIXTURE(CMP_VFY_TEST_FIXTURE, set_up); + if (!TEST_ptr(fixture->msg = load_pkimsg(ir_protected_f, libctx)) + || !TEST_ptr(hdr = OSSL_CMP_MSG_get0_header(fixture->msg)) + || !TEST_int_eq(X509_NAME_add_entry_by_txt(expected, "CN", MBSTRING_ASC, + (unsigned char *)"%n", -1, -1, 0), + 1) + || !TEST_int_eq(X509_NAME_add_entry_by_txt(actual, "CN", MBSTRING_ASC, + (unsigned char *)"actual", -1, -1, 0), + 1) + || !TEST_int_eq(ossl_cmp_hdr_set1_sender(hdr, expected), 1) + || !TEST_int_eq(OSSL_CMP_CTX_set1_expected_sender(fixture->cmp_ctx, actual), 1)) { + X509_NAME_free(expected); + X509_NAME_free(actual); + tear_down(fixture); + return 0; + } + EXECUTE_TEST(execute_msg_check_update_malicious_sender, tear_down); + X509_NAME_free(expected); + X509_NAME_free(actual); + return result; +} + void cleanup_tests(void) { X509_free(srvcert); @@ -714,6 +763,7 @@ int setup_tests(void) #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION ADD_TEST(test_msg_check_recipient_nonce_bad); #endif + ADD_TEST(test_msg_check_update_malicious_sender); return 1; ++++++ openssl-CVE-2026-63074.patch ++++++ commit d2730faae84443914ed04b105a34e1aaa9be5cc7 Author: Neil Horman <[email protected]> Date: Tue Jun 30 14:52:18 2026 -0400 Add a test for restricting growth in cmp cert cache Test to ensure that if certs are rejected we don't add them unboundedly to the cmp contexts cert cache. Assisted-by: Claude sonnet 4.6 diff --git a/test/build.info b/test/build.info index 439a18fb51..1fb08ca8fa 100644 --- a/test/build.info +++ b/test/build.info @@ -811,7 +811,7 @@ IF[{- !$disabled{tests} -}] IF[{- !$disabled{cmp} -}] PROGRAMS{noinst}=cmp_asn_test cmp_ctx_test cmp_status_test cmp_hdr_test \ cmp_protect_test cmp_msg_test cmp_vfy_test \ - cmp_server_test cmp_client_test + cmp_server_test cmp_client_test cmp_extracerts_dos_test ENDIF SOURCE[cmp_asn_test]=cmp_asn_test.c helpers/cmp_testlib.c @@ -838,6 +838,10 @@ IF[{- !$disabled{tests} -}] INCLUDE[cmp_msg_test]=.. ../include ../apps/include DEPEND[cmp_msg_test]=../libcrypto.a libtestutil.a + SOURCE[cmp_extracerts_dos_test]=cmp_extracerts_dos_test.c helpers/cmp_testlib.c + INCLUDE[cmp_extracerts_dos_test]=.. ../include ../apps/include + DEPEND[cmp_extracerts_dos_test]=../libcrypto.a libtestutil.a + SOURCE[cmp_vfy_test]=cmp_vfy_test.c helpers/cmp_testlib.c INCLUDE[cmp_vfy_test]=.. ../include ../apps/include DEPEND[cmp_vfy_test]=../libcrypto.a libtestutil.a diff --git a/test/cmp_extracerts_dos_test.c b/test/cmp_extracerts_dos_test.c new file mode 100644 index 0000000000..273281c943 --- /dev/null +++ b/test/cmp_extracerts_dos_test.c @@ -0,0 +1,338 @@ +/* + * Copyright 2026 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +/* + * Regression test for: CMP server unauthenticated memory/CPU DoS via + * cached extraCerts on failed protection checks. + * + * Root cause (crypto/cmp/cmp_vfy.c, ossl_cmp_msg_check_update(), current + * master as of this writing): + * + * res = ossl_x509_add_certs_new(&ctx->untrusted, msg->extraCerts, ...); + * ... + * res = OSSL_CMP_validate_msg(ctx, msg) || (cb...); // may be 0 (rejected) + * + * if (ctx->noCacheExtraCerts) // <-- rollback is + * while (num_added-- > 0) // gated on this + * X509_free(sk_X509_shift(ctx->untrusted)); // flag only, NOT + * // on the + * // validation + * // result (res) + * + * if (!res) { ...; return 0; } // certs from a REJECTED msg are kept + * + * This test exercises ossl_cmp_msg_check_update() directly -- no sockets, + * no HTTP server, no apps/cmp.c -- and asserts on the resulting size of + * ctx->untrusted. It builds a genuinely PBM-protected OSSL_CMP_MSG using + * the project's own internal message-creation function + * (ossl_cmp_genm_new(), same one exercised in test/cmp_msg_test.c) so the + * message is not hand-crafted to "look" rejectable -- it is rejected for a + * real reason (the receiving ctx has no matching secret configured), the + * same way OSSL_CMP_validate_msg() would reject any unauthenticated CMP + * request in the field. + * + * Expected results: + * - BEFORE the fix: untrusted_count_after == untrusted_count_before + N + * (every rejected message's extraCerts persist) + * - AFTER the fix: untrusted_count_after == untrusted_count_before + * (rejected messages leave no residue) + */ + +#include "helpers/cmp_testlib.h" + +#define NUM_REJECTED_REQUESTS 25 /* "attacker" sends this many distinct certs */ + +typedef struct test_fixture { + const char *test_case_name; + OSSL_CMP_CTX *server_ctx; /* long-lived ctx under test, mirrors srv_ctx->ctx */ +} CMP_DOS_TEST_FIXTURE; + +static OSSL_LIB_CTX *libctx = NULL; + +static CMP_DOS_TEST_FIXTURE *set_up(const char *const test_case_name) +{ + CMP_DOS_TEST_FIXTURE *fixture; + + if (!TEST_ptr(fixture = OPENSSL_zalloc(sizeof(*fixture)))) + return NULL; + fixture->test_case_name = test_case_name; + + if (!TEST_ptr(fixture->server_ctx = OSSL_CMP_CTX_new(libctx, NULL))) { + OPENSSL_free(fixture); + return NULL; + } + /* + * Deliberately do NOT call OSSL_CMP_CTX_set1_secretValue() on the + * server ctx. Per OSSL_CMP_validate_msg() (crypto/cmp/cmp_vfy.c): + * case NID_id_PasswordBasedMAC: + * if (ctx->secretValue == NULL) { + * ossl_cmp_info(ctx, "no secret available for verifying.."); + * ERR_raise(ERR_LIB_CMP, CMP_R_ERROR_VALIDATING_PROTECTION); + * return 0; + * } + * so every PBM-protected message this ctx receives is unconditionally + * rejected -- a deterministic, content-independent rejection path that + * models "missing or invalid protection" from the report's repro + * steps, without needing to forge a bad MAC by hand. + * ctx->noCacheExtraCerts is left at its default (0), exactly as in the + * vulnerable deployment ("not setting -no_cache_extracerts"). + */ + return fixture; +} + +static void tear_down(CMP_DOS_TEST_FIXTURE *fixture) +{ + if (fixture == NULL) + return; + OSSL_CMP_CTX_free(fixture->server_ctx); + OPENSSL_free(fixture); +} + +/* Generates a throwaway EC P-256 keypair; cheap, and key strength is + * irrelevant to this test. */ +static EVP_PKEY *generate_throwaway_keypair(void) +{ + EVP_PKEY_CTX *pctx = NULL; + EVP_PKEY *pkey = NULL; + + if (!TEST_ptr(pctx = EVP_PKEY_CTX_new_from_name(libctx, "EC", NULL))) + return NULL; + if (!TEST_int_gt(EVP_PKEY_keygen_init(pctx), 0) + || !TEST_int_gt(EVP_PKEY_CTX_set_group_name(pctx, "P-256"), 0) + || !TEST_int_gt(EVP_PKEY_generate(pctx, &pkey), 0)) + pkey = NULL; + EVP_PKEY_CTX_free(pctx); + return pkey; +} + +/* + * Builds a minimal, self-signed, syntactically valid X509 with a unique + * subject/issuer per index, so X509_ADD_FLAG_NO_DUP cannot collapse it + * with any other generated cert (matching the report's exploitation + * requirement of "unique certificates across requests"). + */ +static X509 *generate_unique_self_signed_cert(EVP_PKEY *pkey, int index) +{ + X509 *cert = NULL; + X509_NAME *name = NULL; + ASN1_INTEGER *serial = NULL; + char cn[64]; + + BIO_snprintf(cn, sizeof(cn), "attacker-cert-%d", index); + + if (!TEST_ptr(cert = X509_new()) + || !TEST_true(X509_set_version(cert, X509_VERSION_3))) + goto err; + + if (!TEST_ptr(serial = ASN1_INTEGER_new()) + || !TEST_true(ASN1_INTEGER_set(serial, 1000L + index)) + || !TEST_true(X509_set_serialNumber(cert, serial))) + goto err; + + if (!TEST_ptr(X509_gmtime_adj(X509_getm_notBefore(cert), 0)) + || !TEST_ptr(X509_gmtime_adj(X509_getm_notAfter(cert), + 60L * 60L * 24L * 365L))) + goto err; + + if (!TEST_true(X509_set_pubkey(cert, pkey))) + goto err; + + if (!TEST_ptr(name = X509_NAME_new()) + || !TEST_true(X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, + (unsigned char *)"cmp-dos-test", + -1, -1, 0)) + || !TEST_true(X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, + (unsigned char *)cn, + -1, -1, 0)) + || !TEST_true(X509_set_subject_name(cert, name)) + || !TEST_true(X509_set_issuer_name(cert, name))) + goto err; + + if (!TEST_int_gt(X509_sign(cert, pkey, EVP_sha256()), 0)) + goto err; + + X509_NAME_free(name); + ASN1_INTEGER_free(serial); + return cert; + +err: + X509_NAME_free(name); + ASN1_INTEGER_free(serial); + X509_free(cert); + return NULL; +} + +/* + * Builds a real, internally consistent, PBM-protected CMP GenMsg carrying + * exactly one never-before-seen self-signed cert as its sole extraCert. + * Uses a throwaway *client*-side OSSL_CMP_CTX purely to drive message + * creation/protection (ossl_cmp_genm_new() both builds the body and calls + * ossl_cmp_msg_protect() internally, same as in test/cmp_msg_test.c). The + * client ctx's secret is intentionally never shared with the server ctx + * under test, so the message is protected (syntactically well-formed, + * non-empty protection field) but NOT verifiable by the receiver -- this + * is what "missing or invalid protection" means for a real attacker who + * has no credentials, not an empty/garbage protection field. + */ +static OSSL_CMP_MSG *build_rejectable_msg_with_unique_cert(int index) +{ + OSSL_CMP_CTX *client_ctx = NULL; + OSSL_CMP_MSG *msg = NULL; + EVP_PKEY *pkey = NULL; + X509 *fresh_cert = NULL; + STACK_OF(X509) *extra = NULL; + unsigned char ref[16], secret[16]; + + if (!TEST_ptr(client_ctx = OSSL_CMP_CTX_new(libctx, NULL))) + goto err; + + if (!TEST_ptr(pkey = generate_throwaway_keypair()) + || !TEST_ptr(fresh_cert = generate_unique_self_signed_cert(pkey, index))) + goto err; + + if (!TEST_ptr(extra = sk_X509_new_null()) + || !TEST_true(sk_X509_push(extra, fresh_cert))) + goto err; + fresh_cert = NULL; /* ownership now with the stack */ + + if (!TEST_true(OSSL_CMP_CTX_set1_extraCertsOut(client_ctx, extra))) + goto err; + + /* PBM protection with a secret the server ctx will never be given */ + memset(ref, (unsigned char)(0xA0 + (index & 0x0F)), sizeof(ref)); + memset(secret, (unsigned char)(0x50 + (index & 0x0F)), sizeof(secret)); + if (!TEST_true(OSSL_CMP_CTX_set_option(client_ctx, + OSSL_CMP_OPT_UNPROTECTED_SEND, 0)) + || !TEST_true(OSSL_CMP_CTX_set1_referenceValue(client_ctx, ref, + sizeof(ref))) + || !TEST_true(OSSL_CMP_CTX_set1_secretValue(client_ctx, secret, + sizeof(secret)))) + goto err; + + /* GenMsg is the lightest standard body type for this purpose */ + if (!TEST_ptr(msg = ossl_cmp_genm_new(client_ctx))) + goto err; + + sk_X509_pop_free(extra, X509_free); + X509_free(fresh_cert); + EVP_PKEY_free(pkey); + OSSL_CMP_CTX_free(client_ctx); + return msg; + +err: + sk_X509_pop_free(extra, X509_free); + X509_free(fresh_cert); + EVP_PKEY_free(pkey); + OSSL_CMP_CTX_free(client_ctx); + OSSL_CMP_MSG_free(msg); + return NULL; +} + +/* + * Core assertion: N distinct rejected requests must not grow + * server_ctx->untrusted at all. + * + * Before the fix this fails with e.g.: + * ERROR: untrusted count after (25) != count before (0) + */ +static int execute_no_unbounded_growth_test(CMP_DOS_TEST_FIXTURE *fixture) +{ + OSSL_CMP_CTX *server_ctx = fixture->server_ctx; + int count_before, count_after, i; + + count_before = sk_X509_num(OSSL_CMP_CTX_get0_untrusted(server_ctx)); + if (count_before < 0) + count_before = 0; + + for (i = 0; i < NUM_REJECTED_REQUESTS; i++) { + OSSL_CMP_MSG *msg = build_rejectable_msg_with_unique_cert(i); + int check_result; + + if (!TEST_ptr(msg)) + return 0; + + check_result = ossl_cmp_msg_check_update(server_ctx, msg, NULL, 0); + OSSL_CMP_MSG_free(msg); + + if (!TEST_int_eq(check_result, 0)) { + TEST_note("expected request #%d to be rejected (server ctx has" + " no matching PBM secret) but it was accepted -- test" + " setup is wrong, not exercising the rejection path", + i); + return 0; + } + } + + count_after = sk_X509_num(OSSL_CMP_CTX_get0_untrusted(server_ctx)); + if (count_after < 0) + count_after = 0; + + if (!TEST_int_eq(count_after, count_before)) { + TEST_note("server_ctx->untrusted grew from %d to %d after %d" + " rejected requests -- failed-request extraCerts caching" + " bug is present (see ossl_cmp_msg_check_update() in" + " crypto/cmp/cmp_vfy.c)", + count_before, count_after, + NUM_REJECTED_REQUESTS); + return 0; + } + return 1; +} + +/* + * Single-request variant of the same check, useful in isolation since it + * pins down that even ONE rejected request leaves no residue -- ruling out + * X509_ADD_FLAG_NO_DUP coincidentally masking the bug in the N-request test. + */ +static int execute_single_rejected_request_test(CMP_DOS_TEST_FIXTURE *fixture) +{ + OSSL_CMP_CTX *server_ctx = fixture->server_ctx; + OSSL_CMP_MSG *msg = build_rejectable_msg_with_unique_cert(999); + int count_before, count_after; + + if (!TEST_ptr(msg)) + return 0; + + count_before = sk_X509_num(OSSL_CMP_CTX_get0_untrusted(server_ctx)); + if (count_before < 0) + count_before = 0; + + if (!TEST_int_eq(ossl_cmp_msg_check_update(server_ctx, msg, NULL, 0), 0)) { + OSSL_CMP_MSG_free(msg); + return 0; + } + OSSL_CMP_MSG_free(msg); + + count_after = sk_X509_num(OSSL_CMP_CTX_get0_untrusted(server_ctx)); + if (count_after < 0) + count_after = 0; + + return TEST_int_eq(count_after, count_before); +} + +static int test_single_rejected_request_leaves_no_residue(void) +{ + SETUP_TEST_FIXTURE(CMP_DOS_TEST_FIXTURE, set_up); + EXECUTE_TEST(execute_single_rejected_request_test, tear_down); + return result; +} + +static int test_no_unbounded_growth_on_rejected_requests(void) +{ + SETUP_TEST_FIXTURE(CMP_DOS_TEST_FIXTURE, set_up); + EXECUTE_TEST(execute_no_unbounded_growth_test, tear_down); + return result; +} + +int setup_tests(void) +{ + ADD_TEST(test_single_rejected_request_leaves_no_residue); + ADD_TEST(test_no_unbounded_growth_on_rejected_requests); + return 1; +} diff --git a/test/recipes/65-test_cmp_msg.t b/test/recipes/65-test_cmp_msg.t index d104576a9d..19c17efca4 100644 --- a/test/recipes/65-test_cmp_msg.t +++ b/test/recipes/65-test_cmp_msg.t @@ -20,17 +20,22 @@ use lib srctop_dir('Configurations'); use lib bldtop_dir('.'); my $no_fips = disabled('fips') || ($ENV{NO_FIPS} // 0); +my $no_ec = disabled('ec'); plan skip_all => "This test is not supported in a no-cmp build" if disabled("cmp"); -plan tests => 2 + ($no_fips ? 0 : 1); #fips test +plan tests => 2 + ($no_fips ? 0 : 1) + ($no_ec ? 0 : 1); #fips test and ec test my @basic_cmd = ("cmp_msg_test", data_file("new.key"), data_file("server.crt"), data_file("pkcs10.der")); +unless ($no_ec) { + ok(run(test(["cmp_extracerts_dos_test"]))); +} + ok(run(test([@basic_cmd, "none"]))); ok(run(test([@basic_cmd, "default", srctop_file("test", "default.cnf")]))); commit 7b6da0756cf3df5e2d0537d586a9187b2c2dfb28 Author: Neil Horman <[email protected]> Date: Tue Jun 30 15:09:01 2026 -0400 Fix unbounded cert cache growth in cmp If a remote user sends cmp messages to a server with a list of extraCerts and the message is rejected, the extraCerts from the message remain in the server contexts untrusted certificate stack. This exposes servers with long lived ctx objects to denial of service attacks in which an attacker sends messages intending to be rejected with a large list of additional cerificated repeatedly, forcing the server to store them indefinately. Fix it by rolling back the added extra certs if the message is rejected, using the same method we do when the context is configured to not do caching at all. Fixes openssl/srt#224 Fixes CVE-2026-63074 diff --git a/crypto/cmp/cmp_vfy.c b/crypto/cmp/cmp_vfy.c index c69b0ffb57..b49edb7231 100644 --- a/crypto/cmp/cmp_vfy.c +++ b/crypto/cmp/cmp_vfy.c @@ -801,8 +801,13 @@ int ossl_cmp_msg_check_update(OSSL_CMP_CTX *ctx, const OSSL_CMP_MSG *msg, res = 1; /* support more aggressive fuzzing by letting invalid msg pass */ #endif - /* remove extraCerts again if not caching */ - if (ctx->noCacheExtraCerts) + /* + * remove extraCerts again if not caching + * or if we failed validation above, lest a remote user + * starts sending us lots of certificates in invalid messages + * leading to a DOS from unbounded certificate stack growth + */ + if (ctx->noCacheExtraCerts || res != 1) while (num_added-- > 0) X509_free(sk_X509_shift(ctx->untrusted)); ++++++ openssl-CVE-2026-63075.patch ++++++ commit 1928eaf0b12d0feed4dade5d9ee1b4b65af78534 Author: Norbert Pocs <[email protected]> Date: Tue Aug 4 08:46:11 2026 +0200 Don't store ACK-only frames in TX history for QUIC. When QUIC sends an ACK-only frame, there is no expectation that the peer will ack that ack (i.e. it is itself not ack-eliciting). However, our implementation stores these frames in the TX history regardless. In and of itself thats ok, but if a malicious client establishes a connection, and then drives the connection such that ack-only frames are forced from the peer (i.e. by sending numerous ping frames), and then withholding any subseqent acks for ack-eliciting data, like legitimate data, said malicious client can force inappropriate memory growth on the server, leading to potential DOS attacks. Don't store any ACK-only frames in the TX history to address this. Record it in our TX history so that the send window moves forward appropriately, but for ack-only frames, immediately remove it, since we don't expect to get an ack for them anyway. Initially authored by Opal Wright <[email protected]> The initial proposal had some shortcommings in which the highest pn acked value was not accounted for which I have fixed with the assistance of Claude Assisted-by: Anthopic Sonnet 5 Fixes: https://github.com/openssl/srt/issues/229 Original patch by Neil Horman <[email protected]> Signed-off-by: Norbert Pocs <[email protected]> Index: openssl-3.5.0/include/internal/quic_ackm.h =================================================================== --- openssl-3.5.0.orig/include/internal/quic_ackm.h +++ openssl-3.5.0/include/internal/quic_ackm.h @@ -129,6 +129,11 @@ struct ossl_ackm_tx_pkt_st { }; int ossl_ackm_on_tx_packet(OSSL_ACKM *ackm, OSSL_ACKM_TX_PKT *pkt); + +/* + * Records transmission of a packet containing only ACK frames. The packet + */ +int ossl_ackm_on_tx_ack_only_packet(OSSL_ACKM *ackm, OSSL_ACKM_TX_PKT *pkt); int ossl_ackm_on_rx_datagram(OSSL_ACKM *ackm, size_t num_bytes); # define OSSL_ACKM_ECN_NONE 0 Index: openssl-3.5.0/ssl/quic/quic_ackm.c =================================================================== --- openssl-3.5.0.orig/ssl/quic/quic_ackm.c +++ openssl-3.5.0/ssl/quic/quic_ackm.c @@ -1112,6 +1112,38 @@ int ossl_ackm_on_tx_packet(OSSL_ACKM *ac return 1; } +int ossl_ackm_on_tx_ack_only_packet(OSSL_ACKM *ackm, OSSL_ACKM_TX_PKT *pkt) +{ + struct tx_pkt_history_st *h; + unsigned int pkt_space; + + if (pkt == NULL || pkt->pkt_space >= QUIC_PN_SPACE_NUM) + return 0; + + /* + * A packet containing only an ACK frame must not be treated as + * in-flight or ack-eliciting; if it were, ossl_ackm_on_tx_packet() + * below would (correctly) perform bytes-in-flight/timer/CC bookkeeping + * for a packet we are about to discard from history, which would be + * incorrect. + */ + if (pkt->is_inflight || pkt->is_ack_eliciting) + return 0; + + pkt_space = pkt->pkt_space; + + /* + * No one can expect ACK for packet which carries ACK frames only + * (ack_only packet). The ACKM does not need to keep record for ack_only + * packet. For ack_only packet the ACKM manager must be updated by the + * highest packet number which got sent. + */ + h = get_tx_history(ackm, pkt_space); + h->highest_sent = pkt->pkt_num; + + return 1; +} + int ossl_ackm_on_rx_datagram(OSSL_ACKM *ackm, size_t num_bytes) { /* No-op on the client. */ Index: openssl-3.5.0/ssl/quic/quic_txp.c =================================================================== --- openssl-3.5.0.orig/ssl/quic/quic_txp.c +++ openssl-3.5.0/ssl/quic/quic_txp.c @@ -2950,6 +2950,20 @@ fatal_err: return TXP_ERR_INTERNAL; } +static int txp_pkt_is_ack_only(const QUIC_TXPIM_PKT *tpkt) +{ + return tpkt->had_ack_frame + && !tpkt->ackm_pkt.is_inflight + && !tpkt->ackm_pkt.is_ack_eliciting + && !tpkt->had_handshake_done_frame + && !tpkt->had_max_data_frame + && !tpkt->had_max_streams_bidi_frame + && !tpkt->had_max_streams_uni_frame + && !tpkt->had_conn_close + && tpkt->retx_head == NULL + && ossl_quic_txpim_pkt_get_num_chunks(tpkt) == 0; +} + /* * Commits and queues a packet for transmission. There is no backing out after * this. @@ -2958,8 +2972,9 @@ fatal_err: * * - Sends the packet to the QTX for encryption and transmission; * - * - Records the packet as having been transmitted in FIFM. ACKM is informed, - * etc. and the TXPIM record is filed. + * - Records non-ACK-only packets as having been transmitted in FIFM. ACKM is + * informed, etc. and the TXPIM record is filed only when later callbacks + * need it. * * - Informs various subsystems of frames that were sent and clears frame * wanted flags so that we do not generate the same frames again. @@ -2986,7 +3001,7 @@ static int txp_pkt_commit(OSSL_QUIC_TX_P uint32_t archetype, int *txpim_pkt_reffed) { - int rc = 1; + int ack_only, rc = 1; uint32_t enc_level = pkt->h.enc_level; uint32_t pn_space = ossl_quic_enc_level_to_pn_space(enc_level); QUIC_TXPIM_PKT *tpkt = pkt->tpkt; @@ -3029,28 +3044,35 @@ static int txp_pkt_commit(OSSL_QUIC_TX_P return 0; /* alloc error */ } - /* Dispatch to FIFD. */ - if (!ossl_quic_fifd_pkt_commit(&txp->fifd, tpkt)) + ack_only = txp_pkt_is_ack_only(tpkt); + + /* Dispatch packets that need loss/retransmit callbacks to FIFD. */ + if (!ack_only && !ossl_quic_fifd_pkt_commit(&txp->fifd, tpkt)) return 0; /* * Transmission and Post-Packet Generation Bookkeeping * =================================================== * - * No backing out anymore - at this point the ACKM has recorded the packet - * as having been sent, so we need to increment our next PN counter, or - * the ACKM will complain when we try to record a duplicate packet with - * the same PN later. At this point actually sending the packet may still - * fail. In this unlikely event it will simply be handled as though it - * were a lost packet. + * No backing out anymore - at this point we need to increment our next PN + * counter, or the ACKM will complain when we try to record a duplicate + * packet with the same PN later. Non-ACK-only packets have also been + * recorded in ACKM, so if QTX write fails they are handled as though they + * were lost. ACK-only packets are not recorded and will be cleaned up by + * the caller. */ ++txp->next_pn[pn_space]; - *txpim_pkt_reffed = 1; + if (!ack_only) + *txpim_pkt_reffed = 1; /* Send the packet. */ if (!ossl_qtx_write_pkt(txp->args.qtx, &txpkt)) return 0; + if (ack_only + && !ossl_ackm_on_tx_ack_only_packet(txp->args.ackm, &tpkt->ackm_pkt)) + rc = 0; + /* * Record FC and stream abort frames as sent; deactivate streams which no * longer have anything to do. ++++++ openssl-CVE-2026-63076.patch ++++++ commit 616348493eceb7abd368cdbbfe0b89974d5d0c93 Author: Daniel Kubec <[email protected]> Date: Tue Jul 21 11:19:29 2026 +0200 Fix Remote NULL deref in ossl_cmp_calc_protection() via crafted protectionAlg ossl_cmp_calc_protection() only checked whether the protectionAlg parameter (ppval) was NULL before treating it as a PBMParameter ASN1_STRING. X509_ALGOR_get0() does not validate the ASN.1 type of the parameter against what the caller expects. For id-PasswordBasedMAC, a crafted message can encode the parameter as a BOOLEAN instead of the expected PBMParameter SEQUENCE. Because the ASN1_TYPE value union overlays the boolean int on the pointer field, ppval comes back as a bogus non-NULL pointer (e.g. 0xff). Fixes CVE-2026-63076 diff --git a/crypto/cmp/cmp_protect.c b/crypto/cmp/cmp_protect.c index 974cb1d270..ba8cc99a07 100644 --- a/crypto/cmp/cmp_protect.c +++ b/crypto/cmp/cmp_protect.c @@ -59,7 +59,7 @@ ASN1_BIT_STRING *ossl_cmp_calc_protection(const OSSL_CMP_CTX *ctx, ERR_raise(ERR_LIB_CMP, CMP_R_MISSING_PBM_SECRET); return NULL; } - if (ppval == NULL) { + if (pptype != V_ASN1_SEQUENCE || ppval == NULL) { ERR_raise(ERR_LIB_CMP, CMP_R_ERROR_CALCULATING_PROTECTION); return NULL; } commit 3668788aff100aaed5079b46e279592c97b46ccd Author: Daniel Kubec <[email protected]> Date: Tue Jul 21 11:18:53 2026 +0200 Add test for CVE-2026-63076 Assisted-by: Claude:claude-fable-5 diff --git a/test/cmp_protect_test.c b/test/cmp_protect_test.c index a8d673e5e1..a537e9d019 100644 --- a/test/cmp_protect_test.c +++ b/test/cmp_protect_test.c @@ -185,6 +185,38 @@ static int test_cmp_calc_protection_pbmac(void) EXECUTE_TEST(execute_calc_protection_pbmac_test, tear_down); return result; } + +/* + * Regression test for the ossl_cmp_calc_protection() protectionAlg + * type-confusion DoS: a PKIMessage whose protectionAlg has the + * id-PasswordBasedMAC OID but carries a BOOLEAN parameter instead of the + * expected PBMParameter SEQUENCE. X509_ALGOR_get0() then returns the boolean's + * union member (0xff) via ppval; the unpatched code took the non-NULL ppval as + * a valid ASN1_STRING * and dereferenced 0xff, crashing with a near-NULL + * access. The fixed code must reject the malformed parameter and return NULL. + */ +static int test_cmp_calc_protection_pbmac_bad_alg_param(void) +{ + unsigned char sec_insta[] = { 'i', 'n', 's', 't', 'a' }; + X509_ALGOR *alg = NULL; + + SETUP_TEST_FIXTURE(CMP_PROTECT_TEST_FIXTURE, set_up); + if (!TEST_true(OSSL_CMP_CTX_set1_secretValue(fixture->cmp_ctx, + sec_insta, sizeof(sec_insta))) + || !TEST_ptr(fixture->msg = load_pkimsg(ip_PBM_f, libctx)) + || !TEST_ptr(alg = X509_ALGOR_new()) + || !TEST_true(X509_ALGOR_set0(alg, OBJ_nid2obj(NID_id_PasswordBasedMAC), + V_ASN1_BOOLEAN, (void *)1))) { + X509_ALGOR_free(alg); + tear_down(fixture); + fixture = NULL; + } else { + X509_ALGOR_free(fixture->msg->header->protectionAlg); + fixture->msg->header->protectionAlg = alg; + } + EXECUTE_TEST(execute_calc_protection_fails_test, tear_down); + return result; +} static int execute_MSG_protect_test(CMP_PROTECT_TEST_FIXTURE *fixture) { return TEST_int_eq(fixture->expected, @@ -609,6 +641,7 @@ int setup_tests(void) ADD_TEST(test_cmp_calc_protection_pkey_Ed); #endif ADD_TEST(test_cmp_calc_protection_pbmac); + ADD_TEST(test_cmp_calc_protection_pbmac_bad_alg_param); ADD_TEST(test_MSG_protect_with_msg_sig_alg_protection_plus_rsa_key); ADD_TEST(test_MSG_protect_with_certificate_and_key); ++++++ openssl-CVE-2026-75803.patch ++++++ >From bdeb0cd994d915342787f117ee75044f0dc36f34 Mon Sep 17 00:00:00 2001 From: Billy Brumley <[email protected]> Date: Tue, 4 Aug 2026 07:35:48 -0400 Subject: [PATCH] Check the tag on EVP_Cipher() finalize: Poly1305 and OCB AEADs For the affected OpenSSL built-in provider AEAD implementations, EVP_Cipher(ctx, out, NULL, 0) reaches the ccipher callback as a NULL-input terminal call. OCB and ChaCha20-Poly1305 took an early exit on an empty message, with or without AAD, and returned success without comparing an explicitly supplied tag. Consequently a corrupted tag was accepted before this change. Make these built-in callbacks perform their terminal tag operation, aligning their explicit-tag handling with the streaming Final path without defining NULL input as part of the generic EVP_Cipher() contract. AES-GCM-SIV also failed to generate a tag when Final was its first empty-message operation. Generate the tag in that case and propagate failures from the matching empty-message decrypt operation. The stable ChaCha20-Poly1305 implementation aliases Update to the one-shot cipher callback, so this backport introduces a dedicated Update callback to preserve zero-length Update as a no-op. Follow-up to #31555 Fixes #32258 Fixes CVE-2026-75803 Assisted-by: Claude:claude-opus-4-8 Assisted-by: Codex:gpt-5.6-sol (cherry picked from commit 5741d29a5f356e05262cd0936a472a9961398d53) Co-authored-by: Mounir IDRASSI <[email protected]> Reviewed-by: Bob Beck <[email protected]> Reviewed-by: Tomas Mraz <[email protected]> Merge-date: Wed Aug 19 17:41:16 2026 Merged-from: https://github.com/openssl/openssl/pull/32416 --- .../ciphers/cipher_aes_gcm_siv_hw.c | 14 +++++++--- .../implementations/ciphers/cipher_aes_ocb.c | 4 +++ .../ciphers/cipher_chacha20_poly1305.c | 27 ++++++++++++++----- 3 files changed, 34 insertions(+), 11 deletions(-) Index: openssl-3.5.3/providers/implementations/ciphers/cipher_aes_gcm_siv_hw.c =================================================================== --- openssl-3.5.3.orig/providers/implementations/ciphers/cipher_aes_gcm_siv_hw.c +++ openssl-3.5.3/providers/implementations/ciphers/cipher_aes_gcm_siv_hw.c @@ -267,11 +267,17 @@ static int aes_gcm_siv_finish(PROV_AES_G { int ret = 0; - if (ctx->enc) + if (ctx->enc) { + /* Generate the tag when Final is the first empty-message operation. */ + if (ctx->generated_tag == 0 + && aes_gcm_siv_encrypt(ctx, NULL, NULL, 0) == 0) + return 0; return ctx->generated_tag; - if (!ctx->generated_tag) - aes_gcm_siv_decrypt(ctx, NULL, NULL, 0); - ret = !CRYPTO_memcmp(ctx->tag, ctx->user_tag, sizeof(ctx->tag)); + } + if (ctx->generated_tag == 0 + && aes_gcm_siv_decrypt(ctx, NULL, NULL, 0) == 0) + return 0; + ret = CRYPTO_memcmp(ctx->tag, ctx->user_tag, sizeof(ctx->tag)) == 0; ret &= ctx->have_user_tag; return ret; } Index: openssl-3.5.3/providers/implementations/ciphers/cipher_aes_ocb.c =================================================================== --- openssl-3.5.3.orig/providers/implementations/ciphers/cipher_aes_ocb.c +++ openssl-3.5.3/providers/implementations/ciphers/cipher_aes_ocb.c @@ -511,6 +511,10 @@ static int aes_ocb_cipher(void *vctx, un if (!ossl_prov_is_running()) return 0; + /* NULL input indicates Final, which must generate or check the tag. */ + if (in == NULL) + return aes_ocb_block_final(vctx, out, outl, outsize); + if (outsize < inl) { ERR_raise(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL); return 0; Index: openssl-3.5.3/providers/implementations/ciphers/cipher_chacha20_poly1305.c =================================================================== --- openssl-3.5.3.orig/providers/implementations/ciphers/cipher_chacha20_poly1305.c +++ openssl-3.5.3/providers/implementations/ciphers/cipher_chacha20_poly1305.c @@ -30,11 +30,11 @@ static OSSL_FUNC_cipher_get_params_fn ch static OSSL_FUNC_cipher_get_ctx_params_fn chacha20_poly1305_get_ctx_params; static OSSL_FUNC_cipher_set_ctx_params_fn chacha20_poly1305_set_ctx_params; static OSSL_FUNC_cipher_cipher_fn chacha20_poly1305_cipher; +static OSSL_FUNC_cipher_update_fn chacha20_poly1305_update; static OSSL_FUNC_cipher_final_fn chacha20_poly1305_final; static OSSL_FUNC_cipher_gettable_ctx_params_fn chacha20_poly1305_gettable_ctx_params; static OSSL_FUNC_cipher_settable_ctx_params_fn chacha20_poly1305_settable_ctx_params; #define chacha20_poly1305_gettable_params ossl_cipher_generic_gettable_params -#define chacha20_poly1305_update chacha20_poly1305_cipher static void *chacha20_poly1305_newctx(void *provctx) { @@ -307,11 +307,6 @@ static int chacha20_poly1305_cipher(void if (!ossl_prov_is_running()) return 0; - if (inl == 0) { - *outl = 0; - return 1; - } - if (outsize < inl) { ERR_raise(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL); return 0; @@ -323,6 +318,24 @@ static int chacha20_poly1305_cipher(void return 1; } +static int chacha20_poly1305_update(void *vctx, unsigned char *out, + size_t *outl, size_t outsize, + const unsigned char *in, size_t inl) +{ + /* + * A zero-length update is a no-op. Only EVP_Cipher() and Final produce or + * check the authentication tag. + */ + if (inl == 0) { + if (!ossl_prov_is_running()) + return 0; + *outl = 0; + return 1; + } + + return chacha20_poly1305_cipher(vctx, out, outl, outsize, in, inl); +} + static int chacha20_poly1305_final(void *vctx, unsigned char *out, size_t *outl, size_t outsize) {
