Security fix: Multiple CVEs
This commit is contained in:
+41
-9
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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) {
|
||||
Reference in New Issue
Block a user