Security fix: Multiple CVEs

This commit is contained in:
2026-06-08 16:23:49 +03:00
parent 42e4c002c3
commit 245924482f
16 changed files with 1566 additions and 9 deletions
+41 -9
View File
@@ -1,3 +1,44 @@
-------------------------------------------------------------------
Sun Mar 22 20:33:24 UTC 2026 - Lucas Mulling <lucas.mulling@suse.com>
- Security fix:
* CVE-2026-2673: TLS 1.3 servers may choose unexpected key agreement group (bsc#1259652)
Added patch openssl-CVE-2026-2673.patch
Added patch openssl-crypto-mem.c-factor-out-memory-allocation-failure-reporting.patch
Added patch openssl-Add-array-memory-allocation-routines.patch
Mon Jun 8 13:21:41 UTC 2026 - Angel Yankov <angel.yankov@suse.com>
- Security fixes:
* CVE-2026-45447: Heap Use-After-Free in OpenSSL PKCS7_verify() (bsc#1266357)
* CVE-2026-45446: Incorrect Tag Processing for Empty Messages in AES-GCM-SIV and AES-SIV modes (bsc#1266356)
* CVE-2026-42770: FFC-DH Peer Validation Uses Attacker-Supplied q (bsc#1266353)
* CVE-2026-45445: AES-OCB IV Ignored on EVP_Cipher() Path (bsc#1266355)
* CVE-2026-42767: NULL Pointer Dereference in CRMF EncryptedValue Decryption (bsc#1266350)
* CVE-2026-42768: Multi-RecipientInfo Bleichenbacher Oracle in CMS_decrypt() and PKCS7_decrypt() (bsc#1266351)
* CVE-2026-42769: Trust-Anchor Substitution via cert/issuer Typo in CMP rootCaKeyUpdate (bsc#1266352)
* CVE-2026-42766: Possible NULL Dereference in Password-Based CMS Decryption (bsc#1266349)
* CVE-2026-34183: Unbounded Memory Growth in the QUIC PATH_CHALLENGE Handler (bsc#1266345)
* CVE-2026-42764: NULL pointer dereference in QUIC server initial packet handling (bsc#1266347)
* CVE-2026-34182: CMS AuthEnvelopedData Processing May Accept Forged Messages (bsc#1266344)
* CVE-2026-9076: Out-of-Bounds Read in CMS Password-Based Decryption (bsc#1266341)
* CVE-2026-7383: Possible Heap Buffer Overflow in ASN.1 Multibyte String Conversion (bsc#1266340)
* CVE-2026-34180: Heap Buffer Over-read in ASN.1 Content Parsing (bsc#1266342)
* Add patches:
openssl-CVE-2026-45447.patch
openssl-CVE-2026-45446.patch
openssl-CVE-2026-42770.patch
openssl-CVE-2026-45445.patch
openssl-CVE-2026-42767.patch
openssl-CVE-2026-42768.patch
openssl-CVE-2026-42769.patch
openssl-CVE-2026-42766.patch
openssl-CVE-2026-34183.patch
openssl-CVE-2026-42764.patch
openssl-CVE-2026-34182.patch
openssl-CVE-2026-9076.patch
openssl-CVE-2026-7383.patch
openssl-CVE-2026-34180.patch
-------------------------------------------------------------------
Thu Mar 26 11:28:38 UTC 2026 - Pedro Monreal <pmonreal@suse.com>
@@ -22,15 +63,6 @@ Thu Mar 26 11:28:38 UTC 2026 - Pedro Monreal <pmonreal@suse.com>
- Fix NULL pointer dereference when processing an OCSP response
* Add patch openssl-NULL-pointer-dereference-in-ocsp_find_signer_sk.patch
-------------------------------------------------------------------
Sun Mar 22 20:33:24 UTC 2026 - Lucas Mulling <lucas.mulling@suse.com>
- Security fix:
* CVE-2026-2673: TLS 1.3 servers may choose unexpected key agreement group (bsc#1259652)
Added patch openssl-CVE-2026-2673.patch
Added patch openssl-crypto-mem.c-factor-out-memory-allocation-failure-reporting.patch
Added patch openssl-Add-array-memory-allocation-routines.patch
-------------------------------------------------------------------
Tue Feb 24 13:39:25 UTC 2026 - Angel Yankov <angel.yankov@suse.com>
+29
View File
@@ -179,6 +179,35 @@ Patch72: openssl-CVE-2026-31790-tests.patch
Patch73: openssl-NULL-pointer-dereference-in-ocsp_find_signer_sk.patch
# PATCH-FIX-UPSTREAM CVE-2026-28390: NULL pointer dereference during processing of a crafted CMS EnvelopedData message with KeyTransportRecipientInfo (bsc#1261678)
Patch74: openssl-CVE-2026-28390.patch
# PATCH-FIX-UPSTREAM: CVE-2026-45447 Heap Use-After-Free in OpenSSL PKCS7_verify() (bsc#1266357)
Patch75: openssl-CVE-2026-45447.patch
# PATCH-FIX-UPSTREAM: CVE-2026-45446 Incorrect Tag Processing for Empty Messages in AES-GCM-SIV and AES-SIV modes (bsc#1266356)
Patch76: openssl-CVE-2026-45446.patch
# PATCH-FIX-UPSTREAM: CVE-2026-42770 FFC-DH Peer Validation Uses Attacker-Supplied q (bsc#1266353)
Patch77: openssl-CVE-2026-42770.patch
# PATCH-FIX-UPSTREAM: CVE-2026-45445 AES-OCB IV Ignored on EVP_Cipher() Path (bsc#1266355)
Patch78: openssl-CVE-2026-45445.patch
# PATCH-FIX-UPSTREAM: CVE-2026-42767 NULL Pointer Dereference in CRMF EncryptedValue Decryption (bsc#1266350)
Patch79: openssl-CVE-2026-42767.patch
# PATCH-FIX-UPSTREAM: CVE-2026-42768 Multi-RecipientInfo Bleichenbacher Oracle in CMS_decrypt() and PKCS7_decrypt() (bsc#1266351)
Patch80: openssl-CVE-2026-42768.patch
# PATCH-FIX-UPSTREAM: CVE-2026-42769 Trust-Anchor Substitution via cert/issuer Typo in CMP rootCaKeyUpdate (bsc#1266352)
Patch81: openssl-CVE-2026-42769.patch
# PATCH-FIX-UPSTREAM: CVE-2026-42766 Possible NULL Dereference in Password-Based CMS Decryption (bsc#1266349)
Patch82: openssl-CVE-2026-42766.patch
# PATCH-FIX-UPSTREAM: CVE-2026-34183 Unbounded Memory Growth in the QUIC PATH_CHALLENGE Handler (bsc#1266345)
Patch83: openssl-CVE-2026-34183.patch
# PATCH-FIX-UPSTREAM: CVE-2026-42764 NULL pointer dereference in QUIC server initial packet handling (bsc#1266347)
Patch84: openssl-CVE-2026-42764.patch
# PATCH-FIX-UPSTREAM: CVE-2026-34182 CMS AuthEnvelopedData Processing May Accept Forged Messages (bsc#1266344)
Patch85: openssl-CVE-2026-34182.patch
# PATCH-FIX-UPSTREAM: CVE-2026-9076 Out-of-Bounds Read in CMS Password-Based Decryption (bsc#1266341)
Patch87: openssl-CVE-2026-9076.patch
# PATCH-FIX-UPSTREAM: CVE-2026-7383 Possible Heap Buffer Overflow in ASN.1 Multibyte String Conversion (bsc#1266340)
Patch88: openssl-CVE-2026-7383.patch
# PATCH-FIX-UPSTREAM: CVE-2026-34180 Heap Buffer Over-read in ASN.1 Content Parsing (bsc#1266342)
Patch89: openssl-CVE-2026-34180.patch
# ulp-macros is available according to SUSE version.
%ifarch x86_64 || ppc64le
%if 0%{?sle_version} >= 150400 || 0%{?suse_version} >= 1540
+87
View File
@@ -0,0 +1,87 @@
commit e2087fe2faba50ec14f5722c7952262b7f0cc4d2
Author: Viktor Dukhovni <openssl-users@dukhovni.org>
Date: Tue Apr 7 22:40:55 2026 +1000
Avoid length truncation in ASN1_STRING_set
The ASN1_STRING_set() function takes an `int` length, make sure the
argument is not inadvertently truncated when it is called from
asn1_ex_c2i().
Fixes CVE-2026-34180
Index: openssl-3.5.0/crypto/asn1/tasn_dec.c
===================================================================
--- openssl-3.5.0.orig/crypto/asn1/tasn_dec.c
+++ openssl-3.5.0/crypto/asn1/tasn_dec.c
@@ -59,7 +59,7 @@ static int asn1_d2i_ex_primitive(ASN1_VA
const ASN1_ITEM *it,
int tag, int aclass, char opt,
ASN1_TLC *ctx);
-static int asn1_ex_c2i(ASN1_VALUE **pval, const unsigned char *cont, int len,
+static int asn1_ex_c2i(ASN1_VALUE **pval, const unsigned char *cont, long len,
int utype, char *free_cont, const ASN1_ITEM *it);
/* Table to convert tags to bit values, used for MSTRING type */
@@ -828,19 +828,24 @@ static int asn1_d2i_ex_primitive(ASN1_VA
/* Translate ASN1 content octets into a structure */
-static int asn1_ex_c2i(ASN1_VALUE **pval, const unsigned char *cont, int len,
+static int asn1_ex_c2i(ASN1_VALUE **pval, const unsigned char *cont, long len,
int utype, char *free_cont, const ASN1_ITEM *it)
{
ASN1_VALUE **opval = NULL;
ASN1_STRING *stmp;
ASN1_TYPE *typ = NULL;
int ret = 0;
+ int ilen = (int)len;
const ASN1_PRIMITIVE_FUNCS *pf;
ASN1_INTEGER **tint;
pf = it->funcs;
- if (pf && pf->prim_c2i)
- return pf->prim_c2i(pval, cont, len, utype, free_cont, it);
+ if (pf && pf->prim_c2i) {
+ if (len == (long)ilen)
+ return pf->prim_c2i(pval, cont, ilen, utype, free_cont, it);
+ ERR_raise(ERR_LIB_ASN1, ASN1_R_TOO_LONG);
+ return 0;
+ }
/* If ANY type clear type and set pointer to internal value */
if (it->utype == V_ASN1_ANY) {
if (*pval == NULL) {
@@ -858,7 +863,8 @@ static int asn1_ex_c2i(ASN1_VALUE **pval
}
switch (utype) {
case V_ASN1_OBJECT:
- if (!ossl_c2i_ASN1_OBJECT((ASN1_OBJECT **)pval, &cont, len))
+ if (len != (long)ilen
+ || !ossl_c2i_ASN1_OBJECT((ASN1_OBJECT **)pval, &cont, ilen))
goto err;
break;
@@ -913,6 +919,10 @@ static int asn1_ex_c2i(ASN1_VALUE **pval
case V_ASN1_SET:
case V_ASN1_SEQUENCE:
default:
+ if (len != (long)ilen) {
+ ERR_raise(ERR_LIB_ASN1, ASN1_R_TOO_LONG);
+ goto err;
+ }
if (utype == V_ASN1_BMPSTRING && (len & 1)) {
ERR_raise(ERR_LIB_ASN1, ASN1_R_BMPSTRING_IS_WRONG_LENGTH);
goto err;
@@ -943,10 +953,10 @@ static int asn1_ex_c2i(ASN1_VALUE **pval
}
/* If we've already allocated a buffer use it */
if (*free_cont) {
- ASN1_STRING_set0(stmp, (unsigned char *)cont /* UGLY CAST! */, len);
+ ASN1_STRING_set0(stmp, (unsigned char *)cont /* UGLY CAST! */, ilen);
*free_cont = 0;
} else {
- if (!ASN1_STRING_set(stmp, cont, len)) {
+ if (!ASN1_STRING_set(stmp, cont, ilen)) {
ERR_raise(ERR_LIB_ASN1, ERR_R_ASN1_LIB);
ASN1_STRING_free(stmp);
*pval = NULL;
+46
View File
@@ -0,0 +1,46 @@
commit e36e3314518f0eaa5bf31e1ee16941e6758baac5
Author: Neil Horman <nhorman@openssl.org>
Date: Fri Apr 17 13:21:50 2026 -0400
Reject potentially forged encrypted CMS AuthEnvelopedData messages
1. Adjust ossl_cms_EncryptedContent_init_bio to not accept non-AEAD
ciphers.
If a forged CMS message with AuthEnvelopedData is received with
a non-AEAD cipher specified, we silently accept that and decrypt
the message, skipping any authentication, which violates RFC 5083.
We also add checks to ensure we fail if we try to encrypt
AuthEnvelopedData without using an AEAD cipher.
2. Ensure that tag lengths on cms AEAD data is the recommended size.
RFC 5084 recommends that mac tags be at least 12 bytes for AES-GCM
and 4 bytes for AES-CCM on AuthEnvelopedData. As this code is not
algorith-specific we add a check for a minimal size and just use the
lower limit which is sufficient to prevent this attack.
Without this check, its possible to set the tag length to 1 and within
256 guesses, forge a CMS message.
Fixes CVE-2026-34182
Index: openssl-3.5.0/crypto/cms/cms_enc.c
===================================================================
--- openssl-3.5.0.orig/crypto/cms/cms_enc.c
+++ openssl-3.5.0/crypto/cms/cms_enc.c
@@ -105,9 +105,10 @@ BIO *ossl_cms_EncryptedContent_init_bio(
}
if ((EVP_CIPHER_get_flags(cipher) & EVP_CIPH_FLAG_AEAD_CIPHER)) {
piv = aparams.iv;
- if (ec->taglen > 0
- && EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG,
- ec->taglen, ec->tag) <= 0) {
+
+ if (ec->taglen < 4 || ec->taglen > 16
+ || EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, (int)ec->taglen, ec->tag) <= 0)
+ {
ERR_raise(ERR_LIB_CMS, CMS_R_CIPHER_AEAD_SET_TAG_ERROR);
goto err;
}
+328
View File
@@ -0,0 +1,328 @@
commit 932bf91b183ac0e107c27df162876afa0f3c6401
Author: Alexandr Nedvedicky <sashan@openssl.org>
Date: Thu Mar 26 14:24:32 2026 +0100
QUIC stack must limit the number of PATH_CHALLENGE frames processed in RX
Currently local QUIC stack allocates PATH_RESPONSE frame for every
PATH_CHALLENGE frame it receives in single packet from its remote peer.
The memory with PATH_RESPONSE frame is released after local QUIC stack
receives an ACK which confirms reception of PATH_RESPONSE by remote peer.
This gives remote peer too much control over memory resources local
QUIC stack may consume.
Quoting RFC 9000 section 9.2.1:
...an endpoint SHOULD NOT send multiple
PATH_CHALLENGE frames in a single packet.
Limiting the number of PATCH_CHALLENGE frames to 1 per QUIC packet received
helps to reduce heap memory overhead required to process PATH_CHALLENGE
frame.
Currently QUIC ACKM (ACK-manager) keeps all frames in retransmission
buffer until ACK is received. It can be changed such frames which
don't need to be ACKed don't need to be kept in retrans buffer,
those can be released right after transmission.
Fixes CVE-2026-34183
Index: openssl-3.5.0/include/internal/quic_cfq.h
===================================================================
--- openssl-3.5.0.orig/include/internal/quic_cfq.h
+++ openssl-3.5.0/include/internal/quic_cfq.h
@@ -151,4 +151,5 @@ QUIC_CFQ_ITEM *ossl_quic_cfq_item_get_pr
# endif
+int ossl_quic_cfq_discard_unreliable(QUIC_CFQ *cfq, QUIC_CFQ_ITEM *item);
#endif
Index: openssl-3.5.0/include/internal/quic_channel.h
===================================================================
--- openssl-3.5.0.orig/include/internal/quic_channel.h
+++ openssl-3.5.0/include/internal/quic_channel.h
@@ -472,4 +472,5 @@ int ossl_quic_bind_channel(QUIC_CHANNEL
# endif
+void ossl_ch_reset_rx_state(QUIC_CHANNEL *ch);
#endif
Index: openssl-3.5.0/include/internal/quic_fifd.h
===================================================================
--- openssl-3.5.0.orig/include/internal/quic_fifd.h
+++ openssl-3.5.0/include/internal/quic_fifd.h
@@ -85,4 +85,5 @@ void ossl_quic_fifd_set_qlog_cb(QUIC_FIF
# endif
+void ossl_quic_fifd_pkt_discard_unreliable(QUIC_FIFD *fifd, QUIC_TXPIM_PKT *tpkt);
#endif
Index: openssl-3.5.0/ssl/quic/quic_cfq.c
===================================================================
--- openssl-3.5.0.orig/ssl/quic/quic_cfq.c
+++ openssl-3.5.0/ssl/quic/quic_cfq.c
@@ -7,6 +7,7 @@
* https://www.openssl.org/source/license.html
*/
+#include "internal/quic_channel.h"
#include "internal/quic_cfq.h"
#include "internal/numbers.h"
@@ -306,6 +307,20 @@ void ossl_quic_cfq_mark_lost(QUIC_CFQ *c
}
}
+int ossl_quic_cfq_discard_unreliable(QUIC_CFQ *cfq, QUIC_CFQ_ITEM *item)
+{
+ int discarded;
+
+ if (ossl_quic_cfq_item_is_unreliable(item)) {
+ ossl_quic_cfq_release(cfq, item);
+ discarded = 1;
+ } else {
+ discarded = 0;
+ }
+
+ return discarded;
+}
+
/*
* Releases a CFQ item. The item may be in either state (NEW or TX) prior to the
* call. The QUIC_CFQ_ITEM pointer must not be used following this call.
Index: openssl-3.5.0/ssl/quic/quic_channel.c
===================================================================
--- openssl-3.5.0.orig/ssl/quic/quic_channel.c
+++ openssl-3.5.0/ssl/quic/quic_channel.c
@@ -2206,6 +2206,12 @@ static void ch_rx_check_forged_pkt_limit
"forgery limit");
}
+void ossl_ch_reset_rx_state(QUIC_CHANNEL *ch)
+{
+ ch->did_crypto_frame = 0;
+ ch->seen_path_challenge = 0;
+}
+
/* Process queued incoming packets and handle frames, if any. */
static int ch_rx(QUIC_CHANNEL *ch, int channel_only, int *notify_other_threads)
{
Index: openssl-3.5.0/ssl/quic/quic_channel_local.h
===================================================================
--- openssl-3.5.0.orig/ssl/quic/quic_channel_local.h
+++ openssl-3.5.0/ssl/quic/quic_channel_local.h
@@ -13,6 +13,28 @@
# include "internal/quic_tls.h"
/*
+ * This is a part of PATH_CHALLENGE flood [1] mitigation. This limits the
+ * number of PATH_CHALLENGE frames QUIC stack is willing to process for
+ * connection. Local QUIC stack creates PATH_RESPONSE frame for PATH_CHALLENGE
+ * frame it receives from remote peer. The response frame is put Control Frame
+ * Queue waiting to be dispatched. The PATH_RESPONSE frame is removed from CFQ
+ * after it is dispatched. The QUIC_PATH_RESPONSE_QLEN limits the number of
+ * PATH_RESPONSE frames waiting to be dispatched. No new PATH_RESPONSE frames
+ * are inserted into CFQ if queue limit is exceeded.
+ *
+ * QUIC implementations use different limits for PATH_RESPONSE queue lengths:
+ * quic-go defines maxPathResponses as 256
+ * quiche from cloadflare sets DEFAULT_MAX_PATH_CHALLENGE_RX_QUEUE_LEN to 3
+ * t-quic from tencent chooses MAX_PATH_CHALS_RECV to be 8
+ *
+ * OpenSSL here introduces QUIC_PATH_RESPONSE_QLEN as 32.
+ *
+ * [1] https://www.ietf.org/archive/id/draft-chen-quic-logical-vuln-mitigations-00.txt
+ * (section 4.2)
+ */
+#define QUIC_PATH_RESPONSE_QLEN 32
+
+/*
* QUIC Channel Structure
* ======================
*
@@ -455,6 +477,19 @@ struct quic_channel_st {
/* Has qlog been requested? */
unsigned int is_tserver_ch : 1;
+ /*
+ * RFC 9000 Section 9.2.1 says:
+ * However, an endpoint SHOULD NOT send multiple
+ * PATH_CHALLENGE frames in a single packet.
+ * The counter here allows us to detect multiple presence
+ * of PATH_CHALLENGE frame in packet. We process only the
+ * first PATH_CHALLENGE frame found in packet. Remaining PATH_CHALLENGE
+ * frames are ignored.
+ * seen_path_challenge flag is always reset before
+ * ossl_quic_handle_frames() gets called.
+ */
+ unsigned int seen_path_challenge : 1;
+
/* Saved error stack in case permanent error was encountered */
ERR_STATE *err_state;
@@ -464,6 +499,11 @@ struct quic_channel_st {
/* Title for qlog purposes. We own this copy. */
char *qlog_title;
+ /*
+ * number of path responses waiting to be dispatched
+ * from control frame queue (CFQ)
+ */
+ unsigned int path_response_limit;
};
# endif
Index: openssl-3.5.0/ssl/quic/quic_fifd.c
===================================================================
--- openssl-3.5.0.orig/ssl/quic/quic_fifd.c
+++ openssl-3.5.0/ssl/quic/quic_fifd.c
@@ -310,3 +310,46 @@ void ossl_quic_fifd_set_qlog_cb(QUIC_FIF
fifd->get_qlog_cb = get_qlog_cb;
fifd->get_qlog_cb_arg = get_qlog_cb_arg;
}
+
+static void txpim_pkt_remove_cfq_item(QUIC_TXPIM_PKT *pkt, QUIC_CFQ_ITEM *cfq_item)
+{
+ QUIC_CFQ_ITEM *prev = cfq_item->pkt_prev;
+
+ if (prev != NULL) {
+ prev->pkt_next = cfq_item->pkt_next;
+ } else {
+ pkt->retx_head = cfq_item->pkt_next;
+ }
+
+ if (cfq_item->pkt_next != NULL)
+ cfq_item->pkt_next->pkt_prev = prev;
+
+ cfq_item->pkt_prev = NULL;
+ cfq_item->pkt_next = NULL;
+}
+
+void ossl_quic_fifd_pkt_discard_unreliable(QUIC_FIFD *fifd, QUIC_TXPIM_PKT *pkt)
+{
+ QUIC_CFQ_ITEM *cfq_item, *cfq_next;
+
+ /*
+ * The packet has been written to network. We can discard frames we don't
+ * retransmit when loss is detected.
+ */
+ cfq_item = pkt->retx_head;
+ while (cfq_item != NULL) {
+ /*
+ * Discarded items are moved to free list. If item
+ * got moved to free list we must also remove it from
+ * cfq list kept in pkt, so ACKM does not find it when
+ * receives an ACK for pkt.
+ */
+ if (ossl_quic_cfq_discard_unreliable(fifd->cfq, cfq_item)) {
+ cfq_next = cfq_item->pkt_next;
+ txpim_pkt_remove_cfq_item(pkt, cfq_item);
+ cfq_item = cfq_next;
+ } else {
+ cfq_item = cfq_item->pkt_next;
+ }
+ }
+}
Index: openssl-3.5.0/ssl/quic/quic_rx_depack.c
===================================================================
--- openssl-3.5.0.orig/ssl/quic/quic_rx_depack.c
+++ openssl-3.5.0/ssl/quic/quic_rx_depack.c
@@ -932,6 +932,12 @@ static int depack_do_frame_retire_conn_i
static void free_path_response(unsigned char *buf, size_t buf_len, void *arg)
{
+ QUIC_CHANNEL *ch = (QUIC_CHANNEL *)arg;
+
+ assert(ch->path_response_limit > 0);
+
+ ch->path_response_limit--;
+
OPENSSL_free(buf);
}
@@ -952,33 +958,39 @@ static int depack_do_frame_path_challeng
return 0;
}
- /*
- * RFC 9000 s. 8.2.2: On receiving a PATH_CHALLENGE frame, an endpoint MUST
- * respond by echoing the data contained in the PATH_CHALLENGE frame in a
- * PATH_RESPONSE frame.
- *
- * TODO(QUIC FUTURE): We should try to avoid allocation here in the future.
- */
- encoded_len = sizeof(uint64_t) + 1;
- if ((encoded = OPENSSL_malloc(encoded_len)) == NULL)
- goto err;
-
- if (!WPACKET_init_static_len(&wpkt, encoded, encoded_len, 0))
- goto err;
-
- if (!ossl_quic_wire_encode_frame_path_response(&wpkt, frame_data)) {
- WPACKET_cleanup(&wpkt);
- goto err;
- }
+ if (ch->seen_path_challenge == 0
+ && ch->path_response_limit < QUIC_PATH_RESPONSE_QLEN) {
+ /*
+ * RFC 9000 s. 8.2.2: On receiving a PATH_CHALLENGE frame, an endpoint
+ * MUST respond by echoing the data contained in the PATH_CHALLENGE
+ * frame in a PATH_RESPONSE frame.
+ *
+ * TODO(QUIC FUTURE): We should try to avoid allocation here in the
+ * future.
+ */
+ encoded_len = sizeof(uint64_t) + 1;
+ if ((encoded = OPENSSL_malloc(encoded_len)) == NULL)
+ goto err;
+
+ if (!WPACKET_init_static_len(&wpkt, encoded, encoded_len, 0))
+ goto err;
+
+ if (!ossl_quic_wire_encode_frame_path_response(&wpkt, frame_data)) {
+ WPACKET_cleanup(&wpkt);
+ goto err;
+ }
- WPACKET_finish(&wpkt);
+ WPACKET_finish(&wpkt);
- if (!ossl_quic_cfq_add_frame(ch->cfq, 0, QUIC_PN_SPACE_APP,
- OSSL_QUIC_FRAME_TYPE_PATH_RESPONSE,
- QUIC_CFQ_ITEM_FLAG_UNRELIABLE,
- encoded, encoded_len,
- free_path_response, NULL))
- goto err;
+ if (!ossl_quic_cfq_add_frame(ch->cfq, 0, QUIC_PN_SPACE_APP,
+ OSSL_QUIC_FRAME_TYPE_PATH_RESPONSE,
+ QUIC_CFQ_ITEM_FLAG_UNRELIABLE,
+ encoded, encoded_len,
+ free_path_response, ch))
+ goto err;
+ ch->seen_path_challenge = 1;
+ ch->path_response_limit++;
+ }
return 1;
@@ -1440,7 +1452,7 @@ int ossl_quic_handle_frames(QUIC_CHANNEL
if (ch == NULL)
goto end;
- ch->did_crypto_frame = 0;
+ ossl_ch_reset_rx_state(ch);
/* Initialize |ackm_data| (and reinitialize |ok|)*/
memset(&ackm_data, 0, sizeof(ackm_data));
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
@@ -3147,6 +3147,8 @@ static int txp_pkt_commit(OSSL_QUIC_TX_P
--probe_info->pto[pn_space];
}
+ ossl_quic_fifd_pkt_discard_unreliable(&txp->fifd, tpkt);
+
return rc;
}
+31
View File
@@ -0,0 +1,31 @@
commit dad76b993a1a9f93c2ba39c013c138d3ff239db3
Author: Alexandr Nedvedicky <sashan@openssl.org>
Date: Tue May 12 16:12:40 2026 +0200
Fix NULL dereference in QUIC address validation
QUIC server crashes when address validation (RFC 9000, Section 8.1)
is disabled and client sends initial packet with invalid token.
Issue reported and fix submitted by Sunwoo Lee (KENTECH),
Hyuk Lim (KENTECH) and Seunghyun Yoon (KENTECH)
Fixes CVE-2026-42764
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
@@ -1651,8 +1651,10 @@ static void port_default_packet_handler(
* forget qrx so channel can create a new one
* with valid initial encryption level keys.
*/
- qrx_src = qrx;
- qrx = NULL;
+ if (qrx != NULL) {
+ qrx_src = qrx;
+ qrx = NULL;
+ }
}
port_bind_channel(port, &e->peer, &scid, &hdr.dst_conn_id,
+27
View File
@@ -0,0 +1,27 @@
commit df32fc58bd726eecf88ac67b22fef0c8ff5eeef8
Author: Igor Ustinov <igus@openssl.foundation>
Date: Thu May 21 08:36:54 2026 +0200
Fix potential NULL dereference processing CMS PasswordRecipientInfo
Avoid NULL dereferencing when keyDerivationAlgorithm is absent
in CMS PasswordRecipientInfo.
Fixes CVE-2026-42766
Index: openssl-3.5.0/crypto/cms/cms_pwri.c
===================================================================
--- openssl-3.5.0.orig/crypto/cms/cms_pwri.c
+++ openssl-3.5.0/crypto/cms/cms_pwri.c
@@ -368,6 +368,11 @@ int ossl_cms_RecipientInfo_pwri_crypt(co
algtmp = pwri->keyDerivationAlgorithm;
/* Finish password based key derivation to setup key in "ctx" */
+ if (algtmp == NULL) {
+ ERR_raise_data(ERR_LIB_CMS, CMS_R_INVALID_KEY_ENCRYPTION_PARAMETER,
+ "Missing KeyDerivationAlgorithm");
+ goto err;
+ }
if (EVP_PBE_CipherInit_ex(algtmp->algorithm,
(char *)pwri->pass, pwri->passlen,
+41
View File
@@ -0,0 +1,41 @@
commit 43806050d3239fb99396858977e74de0ce8b69e4
Author: Igor Ustinov <igus@openssl.foundation>
Date: Mon May 11 16:29:47 2026 +0200
Fix potential NULL dereference in OSSL_CRMF_ENCRYPTEDVALUE_decrypt()
Check that 'parameter' != NULL before dereferencing in
OSSL_CRMF_ENCRYPTEDVALUE_decrypt().
Fixes CVE-2026-42767
Index: openssl-3.5.0/crypto/crmf/crmf_lib.c
===================================================================
--- openssl-3.5.0.orig/crypto/crmf/crmf_lib.c
+++ openssl-3.5.0/crypto/crmf/crmf_lib.c
@@ -773,6 +773,7 @@ unsigned char
EVP_CIPHER *cipher = NULL; /* used cipher */
int cikeysize = 0; /* key size from cipher */
unsigned char *iv = NULL; /* initial vector for symmetric encryption */
+ int iv_len; /* iv length */
unsigned char *out = NULL; /* decryption output buffer */
int n, ret = 0;
EVP_PKEY_CTX *pkctx = NULL; /* private key context */
@@ -826,11 +827,13 @@ unsigned char
} else {
goto end;
}
- if ((iv = OPENSSL_malloc(EVP_CIPHER_get_iv_length(cipher))) == NULL)
+ iv_len = EVP_CIPHER_get_iv_length(cipher);
+ if ((iv = OPENSSL_malloc(iv_len)) == NULL)
goto end;
- if (ASN1_TYPE_get_octetstring(enc->symmAlg->parameter, iv,
- EVP_CIPHER_get_iv_length(cipher))
- != EVP_CIPHER_get_iv_length(cipher)) {
+
+ if (enc->symmAlg->parameter == NULL
+ || ASN1_TYPE_get_octetstring(enc->symmAlg->parameter, iv, iv_len)
+ != iv_len) {
ERR_raise(ERR_LIB_CRMF, CRMF_R_MALFORMED_IV);
goto end;
}
+97
View File
@@ -0,0 +1,97 @@
commit ffb3f5572387e360f6c0861161eff44b15c11bea
Author: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Fri May 15 14:09:17 2026 +0200
Enforce implicit rejection for CMS/PKCS#7 decryption
Drop the disablement of the implicit rejection for RSA PKCS#1 v1.5
decryption.
Fixes CVE-2026-42768
Index: openssl-3.5.0/crypto/cms/cms_env.c
===================================================================
--- openssl-3.5.0.orig/crypto/cms/cms_env.c
+++ openssl-3.5.0/crypto/cms/cms_env.c
@@ -624,13 +624,6 @@ static int cms_RecipientInfo_ktri_decryp
if (!ossl_cms_env_asn1_ctrl(ri, 1))
goto err;
- if (EVP_PKEY_is_a(pkey, "RSA"))
- /* upper layer CMS code incorrectly assumes that a successful RSA
- * decryption means that the key matches ciphertext (which never
- * was the case, implicit rejection or not), so to make it work
- * disable implicit rejection for RSA keys */
- EVP_PKEY_CTX_ctrl_str(ktri->pctx, "rsa_pkcs1_implicit_rejection", "0");
-
if (evp_pkey_decrypt_alloc(ktri->pctx, &ek, &eklen, fixlen,
ktri->encryptedKey->data,
ktri->encryptedKey->length) <= 0)
Index: openssl-3.5.0/crypto/pkcs7/pk7_doit.c
===================================================================
--- openssl-3.5.0.orig/crypto/pkcs7/pk7_doit.c
+++ openssl-3.5.0/crypto/pkcs7/pk7_doit.c
@@ -205,13 +205,6 @@ static int pkcs7_decrypt_rinfo(unsigned
if (EVP_PKEY_decrypt_init(pctx) <= 0)
goto err;
- if (EVP_PKEY_is_a(pkey, "RSA"))
- /* upper layer pkcs7 code incorrectly assumes that a successful RSA
- * decryption means that the key matches ciphertext (which never
- * was the case, implicit rejection or not), so to make it work
- * disable implicit rejection for RSA keys */
- EVP_PKEY_CTX_ctrl_str(pctx, "rsa_pkcs1_implicit_rejection", "0");
-
ret = evp_pkey_decrypt_alloc(pctx, &ek, &eklen, fixlen,
ri->enc_key->data, ri->enc_key->length);
if (ret <= 0)
Index: openssl-3.5.0/doc/man3/CMS_decrypt.pod
===================================================================
--- openssl-3.5.0.orig/doc/man3/CMS_decrypt.pod
+++ openssl-3.5.0/doc/man3/CMS_decrypt.pod
@@ -68,7 +68,7 @@ then the above behaviour is modified and
recipient encrypted key can be decrypted B<without> generating a random
content encryption key. Applications should use this flag with
B<extreme caution> especially in automated gateways as it can leave them
-open to attack.
+open to attack. See L<EVP_PKEY_decrypt(3)> for more details.
It is possible to determine the correct recipient key by other means (for
example looking them up in a database) and setting them in the CMS structure
@@ -103,7 +103,7 @@ mentioned in CMS_verify() also applies t
=head1 SEE ALSO
-L<ERR_get_error(3)>, L<CMS_encrypt(3)>
+L<ERR_get_error(3)>, L<CMS_encrypt(3)>, L<EVP_PKEY_decrypt(3)>
=head1 HISTORY
Index: openssl-3.5.0/doc/man3/PKCS7_decrypt.pod
===================================================================
--- openssl-3.5.0.orig/doc/man3/PKCS7_decrypt.pod
+++ openssl-3.5.0/doc/man3/PKCS7_decrypt.pod
@@ -22,6 +22,14 @@ B<flags> is an optional set of flags.
Although the recipients certificate is not needed to decrypt the data it is needed
to locate the appropriate (of possible several) recipients in the PKCS#7 structure.
+When RSA PKCS#1 v1.5 Key Transport is in use, the invoked EVP_PKEY_decrypt()
+will use implicit rejection mechanism. It always returns the result of RSA
+decryption of the symmetric key to avoid Marvin attack. This result is
+deterministic and can happen to match the symmetric cipher used for the content
+encryption. In case when the certificate is not provided, the last
+RecipientInfo producing the key looking valid will be used. It may cause
+getting garbage content on decryption.
+
The following flags can be passed in the B<flags> parameter.
If the B<PKCS7_TEXT> flag is set MIME headers for type B<text/plain> are deleted
@@ -43,7 +51,7 @@ mentioned in PKCS7_sign() also applies t
=head1 SEE ALSO
-L<ERR_get_error(3)>, L<PKCS7_encrypt(3)>
+L<ERR_get_error(3)>, L<PKCS7_encrypt(3)>, L<EVP_PKEY_decrypt(3)>
=head1 COPYRIGHT
+46
View File
@@ -0,0 +1,46 @@
commit 092f35598ea935c0808a97637f8f8f30256359af
Author: Bob Beck <beck@openssl.org>
Date: Fri Apr 17 14:09:52 2026 -0600
Use the correct issuer when validating rootCAKeyUpdate
This correctly uses the existing root, and not the same certificate
as the root of the chain to validate.
While we are here, we also turn on self signed certificate signature
checking as this case is actually bringing in trust anchors as
self signed certs, and fix a possible NULL deref.
Fixes CVE-2026-42769
Index: openssl-3.5.0/crypto/cmp/cmp_genm.c
===================================================================
--- openssl-3.5.0.orig/crypto/cmp/cmp_genm.c
+++ openssl-3.5.0/crypto/cmp/cmp_genm.c
@@ -204,7 +204,7 @@ static int selfsigned_verify_cb(int ok,
for (i = 0; i < sk_X509_num(trust); i++) {
issuer = sk_X509_value(trust, i);
if ((*check_issued)(store_ctx, cert, issuer)) {
- if (X509_add_cert(chain, cert, X509_ADD_FLAG_UP_REF))
+ if (X509_add_cert(chain, issuer, X509_ADD_FLAG_UP_REF))
ok = 1;
break;
}
@@ -237,6 +237,7 @@ static int verify_ss_cert(OSSL_LIB_CTX *
if ((csc = X509_STORE_CTX_new_ex(libctx, propq)) == NULL
|| !X509_STORE_CTX_init(csc, ts, target, untrusted))
goto err;
+ X509_STORE_CTX_set_flags(csc, X509_V_FLAG_CHECK_SS_SIGNATURE);
X509_STORE_CTX_set_verify_cb(csc, selfsigned_verify_cb);
ok = X509_verify_cert(csc) > 0;
@@ -255,7 +256,8 @@ verify_ss_cert_trans(OSSL_CMP_CTX *ctx,
int res = 0;
if (trusted != NULL) {
- X509_VERIFY_PARAM *vpm = X509_STORE_get0_param(ts);
+ X509_VERIFY_PARAM *vpm = (ts == NULL) ? NULL
+ : X509_STORE_get0_param(ts);
if ((ts = X509_STORE_new()) == NULL)
return 0;
+35
View File
@@ -0,0 +1,35 @@
commit 00c4cdbe4f9578741be6979fc2fa9190980af857
Author: Norbert Pocs <norbertp@openssl.org>
Date: Tue May 12 15:16:04 2026 +0200
Match the local q DHX parameter against the peer's q
As FFC/DH peer public key validation uses the peer's q value instead
of checking against the local q, we must also check that these
q values match when setting the peer's public key.
Fixes CVE-2026-42770
Signed-off-by: Norbert Pocs <norbertp@openssl.org>
Index: openssl-3.5.0/providers/implementations/exchange/dh_exch.c
===================================================================
--- openssl-3.5.0.orig/providers/implementations/exchange/dh_exch.c
+++ openssl-3.5.0/providers/implementations/exchange/dh_exch.c
@@ -146,12 +146,15 @@ static int dh_init(void *vpdhctx, void *
static int dh_match_params(DH *priv, DH *peer)
{
int ret;
+ int ignore_q = 1;
FFC_PARAMS *dhparams_priv = ossl_dh_get0_params(priv);
FFC_PARAMS *dhparams_peer = ossl_dh_get0_params(peer);
+ if (dhparams_priv != NULL && dhparams_priv->q != NULL)
+ ignore_q = 0;
ret = dhparams_priv != NULL
&& dhparams_peer != NULL
- && ossl_ffc_params_cmp(dhparams_priv, dhparams_peer, 1);
+ && ossl_ffc_params_cmp(dhparams_priv, dhparams_peer, ignore_q);
if (!ret)
ERR_raise(ERR_LIB_PROV, PROV_R_MISMATCHING_DOMAIN_PARAMETERS);
return ret;
+333
View File
@@ -0,0 +1,333 @@
commit 5ca975d368639e0f2dd99ce319dbd413d5fc8b8e
Author: Viktor Dukhovni <viktor@openssl.org>
Date: Mon May 18 18:09:44 2026 +1000
Apply the buffered IV on the AES-OCB EVP_Cipher() path
aes_ocb_cipher(), the OCB provider's OSSL_FUNC_CIPHER_CIPHER slot,
processed input without flushing the buffered IV into the OCB
context. Effective nonce was 0 regardless of the caller's IV;
EVP_*Final_ex() then emitted a tag depending only on (key, iv).
This gave (key, nonce) reuse and single-query universal forgery on
the EVP_Cipher() path.
Apply update_iv() at the head of aes_ocb_cipher() to mirror the
streaming handler. The matching GCM one-shot does this already.
Add a cross-driver round-trip test for AES-{GCM,CCM,OCB} and
ChaCha20-Poly1305 in test/evp_extra_test.c. Each cipher is
exercised with and without AAD; the no-AAD case is needed because
any prior EVP_CipherUpdate(NULL, aad, ...) routes through the
streaming handler and applies the IV itself, masking the bug.
Fixes CVE-2026-45445
Index: openssl-3.5.0/providers/implementations/ciphers/cipher_aes_ocb.c
===================================================================
--- openssl-3.5.0.orig/providers/implementations/ciphers/cipher_aes_ocb.c
+++ openssl-3.5.0/providers/implementations/ciphers/cipher_aes_ocb.c
@@ -516,6 +516,19 @@ static int aes_ocb_cipher(void *vctx, un
return 0;
}
+ /*
+ * Mirror the streaming handler: refuse if the key has not been set,
+ * and push the buffered IV into the OCB context before any data is
+ * processed. Without this, CRYPTO_ocb128_encrypt/decrypt runs with
+ * Offset_0 = 0 regardless of the caller's IV -- catastrophic
+ * (key, nonce) reuse, and a subsequent EVP_*Final_ex() emits a tag
+ * that is a function of (key, iv) only.
+ */
+ if (!ctx->key_set || !update_iv(ctx)) {
+ ERR_raise(ERR_LIB_PROV, PROV_R_CIPHER_OPERATION_FAILED);
+ return 0;
+ }
+
if (!aes_generic_ocb_cipher(ctx, in, out, inl)) {
ERR_raise(ERR_LIB_PROV, PROV_R_CIPHER_OPERATION_FAILED);
return 0;
Index: openssl-3.5.0/test/evp_extra_test.c
===================================================================
--- openssl-3.5.0.orig/test/evp_extra_test.c
+++ openssl-3.5.0/test/evp_extra_test.c
@@ -4452,6 +4452,271 @@ err:
return ret;
}
+
+/*
+ * Cross-driver round-trip test for AEAD one-shot vs streaming paths.
+ *
+ * The streaming path (EVP_CipherUpdate/Final, dispatched to
+ * OSSL_FUNC_CIPHER_UPDATE/_FINAL) is treated as the oracle. For each
+ * AEAD configuration we encrypt and decrypt the same (key, iv, aad, pt),
+ * driving the body in two combinations:
+ *
+ * 1. body encrypt via EVP_Cipher() (one-shot, OSSL_FUNC_CIPHER_CIPHER),
+ * body decrypt via EVP_CipherUpdate (streaming).
+ * 2. body encrypt via EVP_CipherUpdate, body decrypt via EVP_Cipher().
+ *
+ * Both combinations must recover the plaintext and verify the tag. AAD
+ * is always fed via EVP_CipherUpdate(NULL, ...): OCB's one-shot is body
+ * only and the asymmetric "AAD streaming, body one-shot" call shape is
+ * the natural pattern a caller reaching for EVP_Cipher() for throughput
+ * would write anyway.
+ *
+ * CVE-2026-45445 (AES-OCB EVP_Cipher() ignored IV) was a silent failure
+ * in this matrix: the one-shot encrypt path produced ciphertext under
+ * Offset_0 = 0 regardless of IV, which the streaming decrypt path then
+ * could not verify. Adding this cross-check catches the same class of
+ * bug for any future AEAD whose one-shot dispatch diverges from its
+ * streaming dispatch.
+ */
+typedef struct {
+ const char *name; /* EVP_CIPHER fetch name */
+ size_t keylen;
+ size_t ivlen;
+ size_t taglen;
+ int is_ccm; /* needs length-up-front + tag-before-body dance */
+} AEAD_ONESHOT_CFG;
+
+static const AEAD_ONESHOT_CFG aead_oneshot_cfgs[] = {
+ { "AES-128-GCM", 16, 12, 16, 0 },
+ { "AES-256-GCM", 32, 12, 16, 0 },
+ { "AES-128-CCM", 16, 12, 16, 1 },
+ { "AES-256-CCM", 32, 12, 16, 1 },
+ { "AES-128-OCB", 16, 12, 16, 0 },
+ { "AES-256-OCB", 32, 12, 16, 0 },
+ { "ChaCha20-Poly1305", 32, 12, 16, 0 }
+};
+
+/*
+ * Drive an encrypt or decrypt operation. AAD always via EVP_CipherUpdate.
+ * Body via EVP_Cipher() when oneshot_body is non-zero, EVP_CipherUpdate
+ * otherwise. On encrypt, fills *out and the caller-provided tag buffer.
+ * On decrypt, reads from in and verifies tag; returns 0 if verification
+ * fails (the test asserts the expected outcome).
+ */
+static int aead_oneshot_op(const AEAD_ONESHOT_CFG *cfg, int enc,
+ int oneshot_body, const unsigned char *key,
+ const unsigned char *iv, const unsigned char *aad,
+ size_t aad_len, const unsigned char *in, size_t in_len,
+ unsigned char *out, unsigned char *tag, const char **why)
+{
+ EVP_CIPHER_CTX *ctx = NULL;
+ EVP_CIPHER *cipher = NULL;
+ int outl = 0, tmpl = 0;
+ int ok = 0;
+ int body_rv;
+
+ *why = NULL;
+
+ if (!TEST_ptr(cipher = EVP_CIPHER_fetch(testctx, cfg->name, testpropq))) {
+ *why = "CIPHER_FETCH";
+ goto end;
+ }
+ if (!TEST_ptr(ctx = EVP_CIPHER_CTX_new())) {
+ *why = "CTX_NEW";
+ goto end;
+ }
+ if (!TEST_true(EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, enc))) {
+ *why = "INIT_CIPHER";
+ goto end;
+ }
+ if (!TEST_int_gt(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN,
+ (int)cfg->ivlen, NULL),
+ 0)) {
+ *why = "SET_IVLEN";
+ goto end;
+ }
+ if (cfg->is_ccm) {
+ /* Placeholder taglen on encrypt, real tag on decrypt; both before key+iv. */
+ if (!TEST_int_gt(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG,
+ (int)cfg->taglen, enc ? NULL : tag),
+ 0)) {
+ *why = "CCM_SET_TAG";
+ goto end;
+ }
+ }
+ if (!TEST_true(EVP_CipherInit_ex(ctx, NULL, NULL, key, iv, enc))) {
+ *why = "INIT_KEY_IV";
+ goto end;
+ }
+ if (cfg->is_ccm) {
+ if (!TEST_true(EVP_CipherUpdate(ctx, NULL, &outl, NULL, (int)in_len))) {
+ *why = "CCM_LEN_DECL";
+ goto end;
+ }
+ }
+ if (aad_len > 0
+ && !TEST_true(EVP_CipherUpdate(ctx, NULL, &outl, aad, (int)aad_len))) {
+ *why = "AAD";
+ goto end;
+ }
+ if (!enc && !cfg->is_ccm
+ && !TEST_int_gt(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG,
+ (int)cfg->taglen, tag),
+ 0)) {
+ *why = "SET_TAG";
+ goto end;
+ }
+
+ if (oneshot_body) {
+ body_rv = EVP_Cipher(ctx, out, in, (unsigned int)in_len);
+ if (cfg->is_ccm && !enc) {
+ /* CCM decrypt: 0 means tag verify failed, < 0 means error. */
+ if (!TEST_int_gt(body_rv, 0)) {
+ *why = "ONESHOT_DECRYPT";
+ goto end;
+ }
+ } else {
+ if (!TEST_int_ge(body_rv, 0)) {
+ *why = "ONESHOT_BODY";
+ goto end;
+ }
+ }
+ outl = (int)in_len;
+ } else {
+ if (!TEST_true(EVP_CipherUpdate(ctx, out, &outl, in, (int)in_len))) {
+ *why = enc ? "STREAM_BODY_ENC" : "STREAM_BODY_DEC";
+ goto end;
+ }
+ }
+
+ if (!cfg->is_ccm) {
+ if (!TEST_true(EVP_CipherFinal_ex(ctx, out + outl, &tmpl))) {
+ *why = enc ? "FINAL_ENC" : "FINAL_DEC";
+ goto end;
+ }
+ }
+
+ if (enc) {
+ if (!TEST_int_gt(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG,
+ (int)cfg->taglen, tag),
+ 0)) {
+ *why = "GET_TAG";
+ goto end;
+ }
+ }
+ ok = 1;
+end:
+ EVP_CIPHER_CTX_free(ctx);
+ EVP_CIPHER_free(cipher);
+ return ok;
+}
+
+/*
+ * For each AEAD row we run two AAD modes, and within each AAD mode two
+ * cross-driver round trips:
+ *
+ * aad_mode 0: no AAD. Critical for catching the OCB-style bug: any
+ * EVP_CipherUpdate(NULL, aad, ...) call before the body
+ * would itself pass through the (correct) streaming
+ * handler and apply the buffered IV, masking the one-shot
+ * handler's failure to do so. With aad_len == 0 we make
+ * EVP_Cipher() the very first cipher operation on the
+ * context, which is the shape the bug requires.
+ *
+ * aad_mode 1: with AAD via streaming. Catches divergence between the
+ * drivers when AAD is in play.
+ *
+ * leg 0: encrypt-oneshot + decrypt-streaming
+ * leg 1: encrypt-streaming + decrypt-oneshot
+ *
+ * The test index encodes (cipher, aad_mode) so a failure points at both.
+ */
+static int test_aead_oneshot_roundtrip(int idx)
+{
+ static const unsigned char fixed_key[32] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f
+ };
+ static const unsigned char fixed_iv[12] = {
+ 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab
+ };
+ static const unsigned char fixed_aad[] = "extra:context";
+ static const unsigned char fixed_pt[] = "THE QUICK BROWN FOX JUMPS OVER LAZY!!";
+ const AEAD_ONESHOT_CFG *cfg = &aead_oneshot_cfgs[idx / 2];
+ int with_aad = idx % 2;
+ size_t aad_len = with_aad ? sizeof(fixed_aad) - 1 : 0;
+ size_t pt_len = sizeof(fixed_pt) - 1;
+ EVP_CIPHER *probe = NULL;
+ unsigned char ct[64], pt[64];
+ unsigned char tag_oneshot[16], tag_stream[16];
+ const char *why = NULL;
+ int leg, ok = 0;
+
+ /*
+ * Probe for the cipher: a build with no-ocb / no-chacha / etc. will
+ * not have it, and we treat that as a pass (nothing to test here).
+ */
+ ERR_set_mark();
+ probe = EVP_CIPHER_fetch(testctx, cfg->name, testpropq);
+ ERR_pop_to_mark();
+ if (probe == NULL) {
+ TEST_info("skipping, '%s' is not available", cfg->name);
+ return 1;
+ }
+ EVP_CIPHER_free(probe);
+
+ for (leg = 0; leg <= 1; leg++) {
+ int enc_oneshot = (leg == 0);
+ unsigned char *tag = enc_oneshot ? tag_oneshot : tag_stream;
+
+ memset(ct, 0, sizeof(ct));
+ memset(pt, 0, sizeof(pt));
+ memset(tag, 0, cfg->taglen);
+
+ if (!aead_oneshot_op(cfg, /*enc=*/1, /*oneshot_body=*/enc_oneshot,
+ fixed_key, fixed_iv, fixed_aad, aad_len,
+ fixed_pt, pt_len, ct, tag, &why)) {
+ TEST_error("%s (%s): encrypt leg %d (%s body) failed at %s",
+ cfg->name, with_aad ? "with AAD" : "no AAD",
+ leg, enc_oneshot ? "oneshot" : "stream",
+ why ? why : "?");
+ goto end;
+ }
+ if (!aead_oneshot_op(cfg, /*enc=*/0, /*oneshot_body=*/!enc_oneshot,
+ fixed_key, fixed_iv, fixed_aad, aad_len,
+ ct, pt_len, pt, tag, &why)) {
+ TEST_error("%s (%s): decrypt leg %d (%s body) failed at %s",
+ cfg->name, with_aad ? "with AAD" : "no AAD",
+ leg, enc_oneshot ? "stream" : "oneshot",
+ why ? why : "?");
+ goto end;
+ }
+ if (!TEST_mem_eq(pt, pt_len, fixed_pt, pt_len)) {
+ TEST_error("%s (%s): leg %d: recovered plaintext differs",
+ cfg->name, with_aad ? "with AAD" : "no AAD", leg);
+ goto end;
+ }
+ }
+
+ /*
+ * Both legs share the same (key, iv, aad, pt) and must therefore
+ * agree on the tag bit-for-bit, regardless of which driver computed
+ * it. This catches the OCB-style failure where the one-shot path
+ * silently emits a different ciphertext/tag from the streaming path.
+ */
+ if (!TEST_mem_eq(tag_oneshot, cfg->taglen, tag_stream, cfg->taglen)) {
+ TEST_error("%s (%s): oneshot-encrypt tag != streaming-encrypt tag",
+ cfg->name, with_aad ? "with AAD" : "no AAD");
+ goto end;
+ }
+ ok = 1;
+end:
+ return ok;
+}
+
+
#ifndef OPENSSL_NO_DES
static int test_evp_iv_des(int idx)
{
@@ -7028,6 +7293,8 @@ int setup_tests(void)
ADD_TEST(test_aes_gcm_siv_empty_data);
ADD_TEST(test_aes_siv_ctx_reuse);
+ ADD_ALL_TESTS(test_aead_oneshot_roundtrip, 2 * OSSL_NELEM(aead_oneshot_cfgs));
+
ADD_TEST(test_invalid_ctx_for_digest);
ADD_TEST(test_evp_cipher_pipeline);
+245
View File
@@ -0,0 +1,245 @@
commit e3171808327b6656e3d3a40e8880329efcd7c28c
Author: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Wed May 13 11:45:51 2026 +0200
Fix handling of empty-ciphertext messages in AES-GCM-SIV and AES-SIV
AES-GCM-SIV: EVP_DecryptFinal_ex Accepts All-Zero Tag for Empty-Ciphertext
Messages.
AES-SIV: EVP_DecryptUpdate_ex Accepts All-Zero Tag for Empty-Ciphertext
Messages on context reuse.
Fixes CVE-2026-45446
Index: openssl-3.5.0/providers/implementations/ciphers/cipher_aes_gcm_siv_hw.c
===================================================================
--- openssl-3.5.0.orig/providers/implementations/ciphers/cipher_aes_gcm_siv_hw.c
+++ openssl-3.5.0/providers/implementations/ciphers/cipher_aes_gcm_siv_hw.c
@@ -58,6 +58,9 @@ static int aes_gcm_siv_initkey(void *vct
memset(&data, 0, sizeof(data));
memcpy(&data.block[sizeof(data.counter)], ctx->nonce, NONCE_SIZE);
+ ctx->generated_tag = 0;
+ memset(ctx->tag, 0, TAG_SIZE);
+
/* msg_auth_key is always 16 bytes in size, regardless of AES128/AES256 */
/* counter is stored little-endian */
for (i = 0; i < BLOCK_SIZE; i += 8) {
@@ -134,17 +137,6 @@ static int aes_gcm_siv_aad(PROV_AES_GCM_
return 1;
}
-static int aes_gcm_siv_finish(PROV_AES_GCM_SIV_CTX *ctx)
-{
- int ret = 0;
-
- if (ctx->enc)
- return ctx->generated_tag;
- ret = !CRYPTO_memcmp(ctx->tag, ctx->user_tag, sizeof(ctx->tag));
- ret &= ctx->have_user_tag;
- return ret;
-}
-
static int aes_gcm_siv_encrypt(PROV_AES_GCM_SIV_CTX *ctx, const unsigned char *in,
unsigned char *out, size_t len)
{
@@ -271,6 +263,19 @@ static int aes_gcm_siv_decrypt(PROV_AES_
return !error;
}
+static int aes_gcm_siv_finish(PROV_AES_GCM_SIV_CTX *ctx)
+{
+ int ret = 0;
+
+ if (ctx->enc)
+ 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));
+ ret &= ctx->have_user_tag;
+ return ret;
+}
+
static int aes_gcm_siv_cipher(void *vctx, unsigned char *out,
const unsigned char *in, size_t len)
{
Index: openssl-3.5.0/providers/implementations/ciphers/cipher_aes_siv.c
===================================================================
--- openssl-3.5.0.orig/providers/implementations/ciphers/cipher_aes_siv.c
+++ openssl-3.5.0/providers/implementations/ciphers/cipher_aes_siv.c
@@ -192,6 +192,7 @@ static int aes_siv_set_ctx_params(void *
PROV_AES_SIV_CTX *ctx = (PROV_AES_SIV_CTX *)vctx;
const OSSL_PARAM *p;
unsigned int speed = 0;
+ SIV128_CONTEXT *sctx = &ctx->siv;
if (ossl_param_is_empty(params))
return 1;
@@ -226,6 +227,8 @@ static int aes_siv_set_ctx_params(void *
if (keylen != ctx->keylen)
return 0;
}
+ sctx->final_ret = -1;
+
return 1;
}
Index: openssl-3.5.0/test/evp_extra_test.c
===================================================================
--- openssl-3.5.0.orig/test/evp_extra_test.c
+++ openssl-3.5.0/test/evp_extra_test.c
@@ -6468,6 +6468,142 @@ static int test_aes_rc4_keylen_change_cv
}
#endif
+static int test_aes_gcm_siv_empty_data(void)
+{
+ unsigned char key[16] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 };
+ unsigned char nonce[12] = { 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11,
+ 0x22, 0x33, 0x44, 0x55 };
+ unsigned char aad[33] = "this AAD was never authenticated";
+ unsigned char zero_tag[16] = { 0 };
+ unsigned char real_tag[16];
+ unsigned char out[16];
+ int outl, ret = 0;
+ EVP_CIPHER_CTX *ctx = NULL;
+ EVP_CIPHER *c = EVP_CIPHER_fetch(NULL, "AES-128-GCM-SIV", NULL);
+
+ if (c == NULL) {
+ return TEST_skip("AES-128-GCM-SIV cipher is not available");
+ }
+
+ /* Compute the CORRECT tag for (key,nonce,aad,pt="") via encrypt */
+ ctx = EVP_CIPHER_CTX_new();
+ if (!TEST_ptr(ctx)
+ || !TEST_true(EVP_EncryptInit_ex2(ctx, c, key, nonce, NULL))
+ || !TEST_true(EVP_EncryptUpdate(ctx, NULL, &outl, aad, sizeof(aad))) /* AAD */
+ || !TEST_true(EVP_EncryptUpdate(ctx, out, &outl, aad, 0)) /* empty PT, out!=NULL */
+ || !TEST_true(EVP_EncryptFinal_ex(ctx, out, &outl))
+ || !TEST_true(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, real_tag)))
+ goto err;
+ EVP_CIPHER_CTX_free(ctx);
+
+ /* SANITY: decrypt with CORRECT tag and an explicit empty-PT Update */
+ ctx = EVP_CIPHER_CTX_new();
+ if (!TEST_ptr(ctx)
+ || !TEST_true(EVP_DecryptInit_ex2(ctx, c, key, nonce, NULL))
+ || !TEST_true(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, real_tag))
+ || !TEST_true(EVP_DecryptUpdate(ctx, NULL, &outl, aad, sizeof(aad)))
+ || !TEST_true(EVP_DecryptUpdate(ctx, out, &outl, aad, 0)) /* force aes_gcm_siv_decrypt(len=0) */
+ || !TEST_true(EVP_DecryptFinal_ex(ctx, out, &outl)))
+ goto err;
+ EVP_CIPHER_CTX_free(ctx);
+
+ /* FORGERY A: AAD only, NO ciphertext Update, ALL-ZERO tag */
+ ctx = EVP_CIPHER_CTX_new();
+ if (!TEST_ptr(ctx)
+ || !TEST_true(EVP_DecryptInit_ex2(ctx, c, key, nonce, NULL))
+ || !TEST_true(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, zero_tag))
+ || !TEST_true(EVP_DecryptUpdate(ctx, NULL, &outl, aad, sizeof(aad))) /* AAD only, out==NULL */
+ || !TEST_false(EVP_DecryptFinal_ex(ctx, out, &outl)))
+ goto err;
+ EVP_CIPHER_CTX_free(ctx);
+
+ /* FORGERY B: no AAD, no Update at all, ALL-ZERO tag */
+ ctx = EVP_CIPHER_CTX_new();
+ if (!TEST_ptr(ctx)
+ || !TEST_true(EVP_DecryptInit_ex2(ctx, c, key, nonce, NULL))
+ || !TEST_true(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, zero_tag))
+ || !TEST_false(EVP_DecryptFinal_ex(ctx, out, &outl)))
+ goto err;
+ EVP_CIPHER_CTX_free(ctx);
+
+ /* CONTROL: AAD only, NO ciphertext Update, CORRECT tag */
+ ctx = EVP_CIPHER_CTX_new();
+ if (!TEST_ptr(ctx)
+ || !TEST_true(EVP_DecryptInit_ex2(ctx, c, key, nonce, NULL))
+ || !TEST_true(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, real_tag))
+ || !TEST_true(EVP_DecryptUpdate(ctx, NULL, &outl, aad, sizeof(aad)))
+ || !TEST_true(EVP_DecryptFinal_ex(ctx, out, &outl)))
+ goto err;
+ EVP_CIPHER_CTX_free(ctx);
+ ctx = NULL;
+
+ ret = 1;
+err:
+ EVP_CIPHER_CTX_free(ctx);
+
+ EVP_CIPHER_free(c);
+ return ret;
+}
+
+/*
+ * AES-SIV reuse-without-rekey:
+ * msg1: legit non-empty CT, tag verifies, final_ret=0
+ * msg2: no reinit (or reinit with key=NULL), set forged tag,
+ * AAD only, DecryptFinal -> does stale final_ret leak through?
+ */
+static int test_aes_siv_ctx_reuse(void)
+{
+ unsigned char key[32] = { 7 }; /* AES-128-SIV => 2*16 */
+ unsigned char pt[9] = "payload!";
+ unsigned char ct[9], tagbuf[16], out[16], zero16[16] = { 0 };
+ unsigned char aad[14] = "forged header";
+ int outl, ret = 0;
+ EVP_CIPHER_CTX *e = NULL, *d = NULL;
+ EVP_CIPHER *c = EVP_CIPHER_fetch(NULL, "AES-128-SIV", NULL);
+
+ if (c == NULL) {
+ return TEST_skip("AES-128-SIV cipher is not available");
+ }
+
+ /* produce a valid (ct,tag) for msg1 */
+ e = EVP_CIPHER_CTX_new();
+ if (!TEST_ptr(e)
+ || !TEST_true(EVP_EncryptInit_ex2(e, c, key, NULL, NULL))
+ || !TEST_true(EVP_EncryptUpdate(e, NULL, &outl, (unsigned char *)"hdr1", 4))
+ || !TEST_true(EVP_EncryptUpdate(e, ct, &outl, pt, sizeof(pt)))
+ || !TEST_true(EVP_EncryptFinal_ex(e, out, &outl))
+ || !TEST_true(EVP_CIPHER_CTX_ctrl(e, EVP_CTRL_AEAD_GET_TAG, 16, tagbuf))) {
+ EVP_CIPHER_CTX_free(e);
+ goto err;
+ }
+ EVP_CIPHER_CTX_free(e);
+
+ /* msg1 decrypt */
+ d = EVP_CIPHER_CTX_new();
+ if (!TEST_ptr(d)
+ || !TEST_true(EVP_DecryptInit_ex2(d, c, key, NULL, NULL))
+ || !TEST_true(EVP_CIPHER_CTX_ctrl(d, EVP_CTRL_AEAD_SET_TAG, 16, tagbuf))
+ || !TEST_true(EVP_DecryptUpdate(d, NULL, &outl, (unsigned char *)"hdr1", 4))
+ || !TEST_true(EVP_DecryptUpdate(d, out, &outl, ct, sizeof(ct)))
+ || !TEST_true(EVP_DecryptFinal_ex(d, out, &outl)))
+ goto err;
+
+ /* msg2 on SAME ctx, reinit with key=NULL => initkey skipped, final_ret should be reset */
+ if (!TEST_true(EVP_DecryptInit_ex2(d, NULL, NULL, NULL, NULL))
+ || !TEST_true(EVP_CIPHER_CTX_ctrl(d, EVP_CTRL_AEAD_SET_TAG, 16, zero16))
+ || !TEST_true(EVP_DecryptUpdate(d, NULL, &outl, aad, sizeof(aad))) /* forged AAD */
+ || !TEST_false(EVP_DecryptFinal_ex(d, out, &outl)))
+ goto err;
+
+ ret = 1;
+
+err:
+ EVP_CIPHER_CTX_free(d);
+ EVP_CIPHER_free(c);
+ return ret;
+}
+
static int test_invalid_ctx_for_digest(void)
{
int ret;
@@ -6888,6 +7024,10 @@ int setup_tests(void)
ADD_TEST(test_aes_rc4_keylen_change_cve_2023_5363);
#endif
+ /* Test cases for CVE-2026-45446 */
+ ADD_TEST(test_aes_gcm_siv_empty_data);
+ ADD_TEST(test_aes_siv_ctx_reuse);
+
ADD_TEST(test_invalid_ctx_for_digest);
ADD_TEST(test_evp_cipher_pipeline);
+35
View File
@@ -0,0 +1,35 @@
commit 16aaeaab46d407f739fc74e3d89ae4fc43ef77c2
Author: Igor Ustinov <igus@openssl.foundation>
Date: Sat May 16 08:16:23 2026 +0200
Fix possible use-after-free in OpenSSL PKCS7_verify()
Fixes CVE-2026-45447
Index: openssl-3.5.0/crypto/pkcs7/pk7_smime.c
===================================================================
--- openssl-3.5.0.orig/crypto/pkcs7/pk7_smime.c
+++ openssl-3.5.0/crypto/pkcs7/pk7_smime.c
@@ -226,6 +226,7 @@ int PKCS7_verify(PKCS7 *p7, STACK_OF(X50
int i, j = 0, k, ret = 0;
BIO *p7bio = NULL;
BIO *tmpout = NULL;
+ BIO *next = NULL;
const PKCS7_CTX *p7_ctx;
if (p7 == NULL) {
@@ -356,9 +357,11 @@ int PKCS7_verify(PKCS7 *p7, STACK_OF(X50
err:
X509_STORE_CTX_free(cert_ctx);
OPENSSL_free(buf);
- if (indata != NULL)
- BIO_pop(p7bio);
- BIO_free_all(p7bio);
+ while (p7bio != NULL && p7bio != indata) {
+ next = BIO_pop(p7bio);
+ BIO_free(p7bio);
+ p7bio = next;
+ }
sk_X509_free(signers);
sk_X509_free(untrusted);
return ret;
+107
View File
@@ -0,0 +1,107 @@
From f68e868d27cfd9ad8ef29db97cf8271bcaf4c541 Mon Sep 17 00:00:00 2001
From: Viktor Dukhovni <viktor@openssl.org>
Date: Wed, 29 Apr 2026 18:23:24 +1000
Subject: [PATCH] Reject oversized inputs in ASN1_mbstring_ncopy()
In ASN1_mbstring_ncopy() the destination size for BMPSTRING and
UNIVERSALSTRING output was computed by a signed left shift on an
int:
outlen = nchar << 1; /* MBSTRING_BMP */
outlen = nchar << 2; /* MBSTRING_UNIV */
For nchar large enough the result is not representable in int. In
the worst case (nchar == 0x40000000) nchar << 2 wraps to zero,
OPENSSL_malloc(1) is called, and traverse_string() then writes
4*nchar bytes into the one-byte allocation: a heap buffer
overflow. The MBSTRING_UTF8 path computes outlen by summing
per-character byte counts in out_utf8(), and that sum can overflow
the same int under similarly large inputs.
Neither path is reachable from code that processes X.509
certificates through the DIRSTRING_TYPE mask used by
ASN1_STRING_set_by_NID(): UNIVERSALSTRING is absent from that
mask, and the UTF-8 sum requires inputs on the order of half a
gigabyte. Reaching them needs an application that calls
ASN1_mbstring_copy()/ASN1_mbstring_ncopy() directly, or registers
a custom NID via ASN1_STRING_TABLE_add(), with an oversized
attacker-controlled input.
Add range checks before each shift and in out_utf8(), raising
ASN1_R_STRING_TOO_LONG at the point of detection. Move the
existing ASN1_R_INVALID_UTF8STRING raise into out_utf8() too so
the two failure modes report distinct codes; the MBSTRING_UTF8
caller is left with cleanup only and now frees dest on error,
matching the BMP/UNIV branches.
Fixes CVE-2026-7383
---
crypto/asn1/a_mbstr.c | 31 ++++++++++++++++++++++++++++---
1 file changed, 28 insertions(+), 3 deletions(-)
Index: openssl-3.5.0/crypto/asn1/a_mbstr.c
===================================================================
--- openssl-3.5.0.orig/crypto/asn1/a_mbstr.c
+++ openssl-3.5.0/crypto/asn1/a_mbstr.c
@@ -81,10 +81,12 @@ int ASN1_mbstring_ncopy(ASN1_STRING **ou
nchar = 0;
/* This counts the characters and does utf8 syntax checking */
ret = traverse_string(in, len, MBSTRING_UTF8, in_utf8, &nchar);
- if (ret < 0) {
- ERR_raise(ERR_LIB_ASN1, ASN1_R_INVALID_UTF8STRING);
- return -1;
- }
+ if (ret < 0) { /* error already raised in out_utf8() */
+ if (free_out) {
+ ASN1_STRING_free(dest);
+ *out = NULL;
+ }
+ }
break;
case MBSTRING_ASC:
@@ -171,11 +173,27 @@ int ASN1_mbstring_ncopy(ASN1_STRING **ou
break;
case MBSTRING_BMP:
+ if (nchar > INT_MAX / 2) {
+ ERR_raise(ERR_LIB_ASN1, ASN1_R_STRING_TOO_LONG);
+ if (free_out) {
+ ASN1_STRING_free(dest);
+ *out = NULL;
+ }
+ return -1;
+ }
outlen = nchar << 1;
cpyfunc = cpy_bmp;
break;
case MBSTRING_UNIV:
+ if (nchar > INT_MAX / 4) {
+ ERR_raise(ERR_LIB_ASN1, ASN1_R_STRING_TOO_LONG);
+ if (free_out) {
+ ASN1_STRING_free(dest);
+ *out = NULL;
+ }
+ return -1;
+ }
outlen = nchar << 2;
cpyfunc = cpy_univ;
break;
@@ -263,9 +281,15 @@ static int out_utf8(unsigned long value,
int *outlen, len;
len = UTF8_putc(NULL, -1, value);
- if (len <= 0)
+ if (len <= 0) {
+ ERR_raise(ERR_LIB_ASN1, ASN1_R_INVALID_UTF8STRING);
return len;
+ }
outlen = arg;
+ if (*outlen > INT_MAX - len) {
+ ERR_raise(ERR_LIB_ASN1, ASN1_R_STRING_TOO_LONG);
+ return -1;
+ }
*outlen += len;
return 1;
}
+38
View File
@@ -0,0 +1,38 @@
commit 21c9274e92faa25753c87ea874d4bd33e33a221f
Author: Nikola Pajkovsky <nikolap@openssl.org>
Date: Thu May 21 11:53:09 2026 +0200
cms: kek_unwrap_key: Fix out-of-bounds read in check-byte validation
the check-byte test in kek_unwrap_key() reads tmp[1] through tmp[6]
unconditionally, so the decrypted buffer must hold at least seven
octets. The pre-decryption size check enforces inlen >= 2 * blocklen,
which yields the required seven octets only when blocklen >= 4. For
a KEK cipher with a smaller block size, inlen can be as small as
2 * blocklen and the check-byte read overruns the inlen-sized tmp
allocation.
Reject blocklen < 4 in the early sanity check. All block ciphers
appropriate for CMS PasswordRecipientInfo key wrapping have a block
size of at least 8 octets (DES/3DES = 8, AES = 16), so this only
forbids ciphers that would not be valid KEK choices anyway, and the
existing inlen >= 2 * blocklen check then guarantees the seven-octet
lower bound the check-byte test relies on.
Fixes CVE-2026-9076
Signed-off-by: Nikola Pajkovsky <nikolap@openssl.org>
Index: openssl-3.5.0/crypto/cms/cms_pwri.c
===================================================================
--- openssl-3.5.0.orig/crypto/cms/cms_pwri.c
+++ openssl-3.5.0/crypto/cms/cms_pwri.c
@@ -206,7 +206,7 @@ static int kek_unwrap_key(unsigned char
unsigned char *tmp;
int outl, rv = 0;
- if (blocklen == 0)
+ if (blocklen < 4)
return 0;
if (inlen < 2 * blocklen) {