openssl: Fix multiple vulnerabilities

This is a rollup commit from upstream to fix:
  Reject oversized inputs in ASN1_mbstring_ncopy()
  cms: kek_unwrap_key: Fix out-of-bounds read in check-byte validation
  cms: kek_unwrap_key: test for fix out-of-bounds read in check-byte validation
  Avoid length truncation in ASN1_STRING_set
  pkcs12: verify that the pbmac1 key length is safe
  Reject potentially forged encrypted CMS AuthEnvelopedData messages
  QUIC stack must limit the number of PATH_CHALLENGE frames processed in RX
  Fix NULL dereference in QUIC address validation
  Fix potential NULL dereference processing CMS PasswordRecipientInfo
  Fix potential NULL dereference in OSSL_CRMF_ENCRYPTEDVALUE_decrypt()
  Enforce implicit rejection for CMS/PKCS#7 decryption
  Use the correct issuer when validating rootCAKeyUpdate
  Match the local q DHX parameter against the peer's q
  Apply the buffered IV on the AES-OCB EVP_Cipher() path
  Fix handling of empty-ciphertext messages in AES-GCM-SIV and AES-SIV
  Fix possible use-after-free in OpenSSL PKCS7_verify()

Approved by:	so
Obtained from:	OpenSSL
Security:	FreeBSD-SA-26:35.openssl
Security:	CVE-2026-7383
Security:	CVE-2026-9076
Security:	CVE-2026-34180
Security:	CVE-2026-34181
Security:	CVE-2026-34182
Security:	CVE-2026-34183
Security:	CVE-2026-42764
Security:	CVE-2026-42766
Security:	CVE-2026-42767
Security:	CVE-2026-42768
Security:	CVE-2026-42769
Security:	CVE-2026-42770
Security:	CVE-2026-45445
Security:	CVE-2026-45446
Security:	CVE-2026-45447
This commit is contained in:
Gordon Tetlow
2026-04-29 18:23:24 +10:00
committed by Mark Johnston
parent ebb0ea9f4f
commit e508c3431d
33 changed files with 473 additions and 94 deletions
+28 -3
View File
@@ -174,11 +174,27 @@ int ASN1_mbstring_ncopy(ASN1_STRING **out, const unsigned char *in, int len,
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;
@@ -186,8 +202,11 @@ int ASN1_mbstring_ncopy(ASN1_STRING **out, const unsigned char *in, int len,
case MBSTRING_UTF8:
outlen = 0;
ret = traverse_string(in, len, inform, out_utf8, &outlen);
if (ret < 0) {
ERR_raise(ERR_LIB_ASN1, ASN1_R_INVALID_UTF8STRING);
if (ret < 0) { /* error already raised in out_utf8() */
if (free_out) {
ASN1_STRING_free(dest);
*out = NULL;
}
return -1;
}
cpyfunc = cpy_utf8;
@@ -270,9 +289,15 @@ static int out_utf8(unsigned long value, void *arg)
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;
}
+17 -7
View File
@@ -54,7 +54,7 @@ static int asn1_d2i_ex_primitive(ASN1_VALUE **pval,
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 */
@@ -855,19 +855,24 @@ static int asn1_d2i_ex_primitive(ASN1_VALUE **pval,
/* 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) {
@@ -885,7 +890,8 @@ static int asn1_ex_c2i(ASN1_VALUE **pval, const unsigned char *cont, int len,
}
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;
@@ -940,6 +946,10 @@ static int asn1_ex_c2i(ASN1_VALUE **pval, const unsigned char *cont, int len,
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;
@@ -970,10 +980,10 @@ static int asn1_ex_c2i(ASN1_VALUE **pval, const unsigned char *cont, int len,
}
/* 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;
+4 -2
View File
@@ -202,7 +202,7 @@ static int selfsigned_verify_cb(int ok, X509_STORE_CTX *store_ctx)
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;
}
@@ -235,6 +235,7 @@ static int verify_ss_cert(OSSL_LIB_CTX *libctx, const char *propq,
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;
@@ -253,7 +254,8 @@ verify_ss_cert_trans(OSSL_CMP_CTX *ctx, X509 *trusted /* may be NULL */,
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;
+6 -4
View File
@@ -109,13 +109,15 @@ BIO *ossl_cms_EncryptedContent_init_bio(CMS_EncryptedContentInfo *ec,
goto err;
}
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;
}
} else if (auth) {
ERR_raise(ERR_LIB_CMS, CMS_R_UNSUPPORTED_CONTENT_ENCRYPTION_ALGORITHM);
goto err;
}
}
len = EVP_CIPHER_CTX_get_key_length(ctx);
-7
View File
@@ -619,13 +619,6 @@ static int cms_RecipientInfo_ktri_decrypt(CMS_ContentInfo *cms,
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)
+9 -4
View File
@@ -200,18 +200,18 @@ static int kek_unwrap_key(unsigned char *out, size_t *outlen,
const unsigned char *in, size_t inlen,
EVP_CIPHER_CTX *ctx)
{
size_t blocklen = EVP_CIPHER_CTX_get_block_size(ctx);
int blocklen = EVP_CIPHER_CTX_get_block_size(ctx);
unsigned char *tmp;
int outl, rv = 0;
if (blocklen == 0)
if (blocklen < 4)
return 0;
if (inlen < 2 * blocklen) {
if (inlen < 2 * (size_t)blocklen) {
/* too small */
return 0;
}
if (inlen % blocklen) {
if (inlen > INT_MAX || inlen % blocklen) {
/* Invalid size */
return 0;
}
@@ -367,6 +367,11 @@ int ossl_cms_RecipientInfo_pwri_crypt(const CMS_ContentInfo *cms,
/* 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, (int)pwri->passlen,
algtmp->parameter, kekctx, en_de,
+6 -4
View File
@@ -766,6 +766,7 @@ unsigned char *OSSL_CRMF_ENCRYPTEDVALUE_decrypt(const OSSL_CRMF_ENCRYPTEDVALUE *
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 */
@@ -820,11 +821,12 @@ unsigned char *OSSL_CRMF_ENCRYPTEDVALUE_decrypt(const OSSL_CRMF_ENCRYPTEDVALUE *
} 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;
}
+5 -3
View File
@@ -144,11 +144,13 @@ static int PBMAC1_PBKDF2_HMAC(OSSL_LIB_CTX *ctx, const char *propq,
}
pbkdf2_salt = pbkdf2_param->salt->value.octet_string;
/* RFC 9579 specifies missing key length as invalid */
/* RFC 9879 specifies missing key length as invalid */
if (pbkdf2_param->keylength != NULL)
keylen = ASN1_INTEGER_get(pbkdf2_param->keylength);
if (keylen <= 0 || keylen > EVP_MAX_MD_SIZE) {
ERR_raise(ERR_LIB_PKCS12, PKCS12_R_PARSE_ERROR);
/* RFC 9879 specifies too short key length as untrustworthy too */
if (keylen < 20 || keylen > EVP_MAX_MD_SIZE) {
ERR_raise_data(ERR_LIB_PKCS12, PKCS12_R_PARSE_ERROR,
"Invalid Key length (%d is not in the range 20..64)", keylen);
goto err;
}
-7
View File
@@ -203,13 +203,6 @@ static int pkcs7_decrypt_rinfo(unsigned char **pek, int *peklen,
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)
+6 -3
View File
@@ -222,6 +222,7 @@ int PKCS7_verify(PKCS7 *p7, STACK_OF(X509) *certs, X509_STORE *store,
int i, j = 0, k, ret = 0;
BIO *p7bio = NULL;
BIO *tmpout = NULL;
BIO *next = NULL;
const PKCS7_CTX *p7_ctx;
if (p7 == NULL) {
@@ -352,9 +353,11 @@ int PKCS7_verify(PKCS7 *p7, STACK_OF(X509) *certs, X509_STORE *store,
BIO_free(tmpout);
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;
+2 -2
View File
@@ -68,7 +68,7 @@ then the above behaviour is modified and an error B<is> returned if no
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 to CMS_decrypt().
=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
+9 -1
View File
@@ -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 to PKCS7_verify().
=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
@@ -149,6 +149,7 @@ QUIC_CFQ_ITEM *ossl_quic_cfq_get_priority_head(const QUIC_CFQ *cfq,
QUIC_CFQ_ITEM *ossl_quic_cfq_item_get_priority_next(const QUIC_CFQ_ITEM *item,
uint32_t pn_space);
int ossl_quic_cfq_discard_unreliable(QUIC_CFQ *cfq, QUIC_CFQ_ITEM *item);
#endif
#endif
@@ -468,6 +468,7 @@ int ossl_quic_bind_channel(QUIC_CHANNEL *ch, const BIO_ADDR *peer,
const QUIC_CONN_ID *scid, const QUIC_CONN_ID *dcid,
const QUIC_CONN_ID *odcid);
void ossl_ch_reset_rx_state(QUIC_CHANNEL *ch);
#endif
#endif
@@ -83,6 +83,7 @@ int ossl_quic_fifd_pkt_commit(QUIC_FIFD *fifd, QUIC_TXPIM_PKT *pkt);
void ossl_quic_fifd_set_qlog_cb(QUIC_FIFD *fifd, QLOG *(*get_qlog_cb)(void *arg),
void *arg);
void ossl_quic_fifd_pkt_discard_unreliable(QUIC_FIFD *fifd, QUIC_TXPIM_PKT *tpkt);
#endif
#endif
@@ -58,6 +58,9 @@ static int aes_gcm_siv_initkey(void *vctx)
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_SIV_CTX *ctx,
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_GCM_SIV_CTX *ctx, const unsigned char *i
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)
{
@@ -514,6 +514,19 @@ static int aes_ocb_cipher(void *vctx, unsigned char *out, size_t *outl,
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;
@@ -192,6 +192,7 @@ static int aes_siv_set_ctx_params(void *vctx, const OSSL_PARAM params[])
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 *vctx, const OSSL_PARAM params[])
if (keylen != ctx->keylen)
return 0;
}
sctx->final_ret = -1;
return 1;
}
@@ -146,12 +146,15 @@ static int dh_init(void *vpdhctx, void *vdh, const OSSL_PARAM params[])
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;
+15
View File
@@ -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"
@@ -307,6 +308,20 @@ void ossl_quic_cfq_mark_lost(QUIC_CFQ *cfq, QUIC_CFQ_ITEM *item,
}
}
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.
+6
View File
@@ -2213,6 +2213,12 @@ static void ch_rx_check_forged_pkt_limit(QUIC_CHANNEL *ch)
"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)
{
@@ -12,6 +12,28 @@
#include "internal/quic_stream_map.h"
#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
* ======================
@@ -457,6 +479,18 @@ 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;
@@ -467,6 +501,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
+43
View File
@@ -310,3 +310,46 @@ void ossl_quic_fifd_set_qlog_cb(QUIC_FIFD *fifd, QLOG *(*get_qlog_cb)(void *arg)
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;
}
}
}
+4 -2
View File
@@ -1666,8 +1666,10 @@ static void port_default_packet_handler(QUIC_URXE *e, void *arg,
* 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,
+37 -25
View File
@@ -931,6 +931,12 @@ static int depack_do_frame_retire_conn_id(PACKET *pkt,
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);
}
@@ -951,34 +957,40 @@ static int depack_do_frame_path_challenge(PACKET *pkt,
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 (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 (!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 (!ossl_quic_wire_encode_frame_path_response(&wpkt, frame_data)) {
WPACKET_cleanup(&wpkt);
goto err;
}
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, ch))
goto err;
ch->seen_path_challenge = 1;
ch->path_response_limit++;
}
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;
return 1;
err:
@@ -1432,7 +1444,7 @@ int ossl_quic_handle_frames(QUIC_CHANNEL *ch, OSSL_QRX_PKT *qpacket)
if (ch == NULL)
return 0;
ch->did_crypto_frame = 0;
ossl_ch_reset_rx_state(ch);
/* Initialize |ackm_data| (and reinitialize |ok|)*/
memset(&ackm_data, 0, sizeof(ackm_data));
+2
View File
@@ -3133,6 +3133,8 @@ static int txp_pkt_commit(OSSL_QUIC_TX_PACKETISER *txp,
--probe_info->pto[pn_space];
}
ossl_quic_fifd_pkt_discard_unreliable(&txp->fifd, tpkt);
return rc;
}
+46 -2
View File
@@ -21,6 +21,7 @@ static X509 *cert = NULL;
static EVP_PKEY *privkey = NULL;
static char *derin = NULL;
static char *too_long_iv_cms_in = NULL;
static char *pwri_kek_oob_der_in = NULL;
static int test_encrypt_decrypt(const EVP_CIPHER *cipher)
{
@@ -512,7 +513,48 @@ static int test_cms_aesgcm_iv_too_long(void)
return ret;
}
OPT_TEST_DECLARE_USAGE("certfile privkeyfile derfile\n")
/*
* CMS EnvelopedData with a single PasswordRecipientInfo using
* id-alg-PWRI-KEK and an AES-128-CFB key encryption cipher
* (1-byte effective block size). The encryptedKey OCTET STRING is
* only two bytes long, so the wrapped key buffer is shorter than
* the seven octets read by the check-byte test in kek_unwrap_key().
* Prior to CVE-2026-9076 this triggered an out-of-bounds heap read;
* CMS_decrypt() must now fail cleanly.
*/
static int test_pwri_kek_unwrap_short_encrypted_key(void)
{
BIO *in = NULL;
CMS_ContentInfo *cms = NULL;
unsigned long err = 0;
int ret = 0;
if (!TEST_ptr(in = BIO_new_file(pwri_kek_oob_der_in, "rb"))
|| !TEST_ptr(cms = d2i_CMS_bio(in, NULL)))
goto end;
/*
* The unwrap is attempted eagerly inside CMS_decrypt_set1_password().
* It must fail cleanly (no OOB read) and report CMS_R_UNWRAP_FAILURE.
*/
if (!TEST_false(CMS_decrypt_set1_password(cms,
(unsigned char *)"password", -1)))
goto end;
err = ERR_peek_last_error();
if (!TEST_int_eq(ERR_GET_LIB(err), ERR_LIB_CMS)
|| !TEST_int_eq(ERR_GET_REASON(err), CMS_R_UNWRAP_FAILURE))
goto end;
ERR_clear_error();
ret = 1;
end:
CMS_ContentInfo_free(cms);
BIO_free(in);
return ret;
}
OPT_TEST_DECLARE_USAGE("certfile privkeyfile derfile tooLongIVpem pwriKekOobDer\n")
int setup_tests(void)
{
@@ -527,7 +569,8 @@ int setup_tests(void)
if (!TEST_ptr(certin = test_get_argument(0))
|| !TEST_ptr(privkeyin = test_get_argument(1))
|| !TEST_ptr(derin = test_get_argument(2))
|| !TEST_ptr(too_long_iv_cms_in = test_get_argument(3)))
|| !TEST_ptr(too_long_iv_cms_in = test_get_argument(3))
|| !TEST_ptr(pwri_kek_oob_der_in = test_get_argument(4)))
return 0;
certbio = BIO_new_file(certin, "r");
@@ -564,6 +607,7 @@ int setup_tests(void)
ADD_TEST(test_encrypted_data_aead);
ADD_ALL_TESTS(test_d2i_CMS_decode, 2);
ADD_TEST(test_cms_aesgcm_iv_too_long);
ADD_TEST(test_pwri_kek_unwrap_short_encrypted_key);
return 1;
}
+140
View File
@@ -6528,6 +6528,142 @@ static int test_aes_rc4_keylen_change_cve_2023_5363(void)
}
#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;
@@ -6987,6 +7123,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_negative_length);
+2 -1
View File
@@ -19,5 +19,6 @@ plan tests => 1;
ok(run(test(["cmsapitest", srctop_file("test", "certs", "servercert.pem"),
srctop_file("test", "certs", "serverkey.pem"),
srctop_file("test", "recipes", "80-test_cmsapi_data", "encryptedData.der"),
srctop_file("test", "recipes", "80-test_cmsapi_data", "encDataWithTooLongIV.pem")])),
srctop_file("test", "recipes", "80-test_cmsapi_data", "encDataWithTooLongIV.pem"),
srctop_file("test", "recipes", "80-test_cmsapi_data", "cms_pwri_kek_oob.der")])),
"running cmsapitest");
+8 -5
View File
@@ -56,7 +56,7 @@ $ENV{OPENSSL_WIN32_UTF8}=1;
my $no_fips = disabled('fips') || ($ENV{NO_FIPS} // 0);
plan tests => $no_fips ? 53 : 59;
plan tests => $no_fips ? 55 : 61;
# Test different PKCS#12 formats
ok(run(test(["pkcs12_format_test"])), "test pkcs12 formats");
@@ -205,8 +205,11 @@ for my $instance (sort keys %pbmac1_tests) {
}
}
# Test pbmac1 pkcs12 good files, RFC 9579
for my $file ("pbmac1_256_256.good.p12", "pbmac1_512_256.good.p12", "pbmac1_512_512.good.p12")
# Test pbmac1 pkcs12 good files, RFC 9579, and one extra with shorter key
# length
for my $file ("pbmac1_256_256.good.p12", "pbmac1_512_256.good.p12",
"pbmac1_512_512.good.p12",
"pbmac1_256_256.good-shorter-key-len.p12")
{
my $path = srctop_file("test", "recipes", "80-test_pkcs12_data", $file);
ok(run(app(["openssl", "pkcs12", "-in", $path, "-password", "pass:1234", "-noenc"])),
@@ -235,12 +238,12 @@ unless ($no_fips) {
}
}
# Test pbmac1 pkcs12 bad files, RFC 9579 and CVE-2025-11187
# Test pbmac1 pkcs12 bad files, RFC 9579, CVE-2025-11187 and CVE-2026-34181
for my $file ("pbmac1_256_256.bad-iter.p12", "pbmac1_256_256.bad-salt.p12",
"pbmac1_256_256.no-len.p12", "pbmac1_256_256.bad-len.p12",
"pbmac1_256_256.bad-salt-type.p12", "pbmac1_256_256.negative-len.p12",
"pbmac1_256_256.no-salt.p12", "pbmac1_256_256.very-big-len.p12",
"pbmac1_256_256.zero-len.p12")
"pbmac1_256_256.zero-len.p12", "pbmac1_256_256.bad-key-len.p12")
{
my $path = srctop_file("test", "recipes", "80-test_pkcs12_data", $file);
with({ exit_checker => sub { return shift == 1; } },