From d6cec087ce90358dff7d90650b6b1d696175b66e Mon Sep 17 00:00:00 2001 From: Dmitry Belyavskiy Date: Wed, 13 May 2026 14:03:57 +0200 Subject: [PATCH] Fix handling of empty-ciphertext messages in AES-SIV AES-SIV: EVP_DecryptUpdate_ex Accepts All-Zero Tag for Empty-Ciphertext Messages on context reuse. Fixes CVE-2026-45446 Reviewed-by: Eugene Syromiatnikov Reviewed-by: Tomas Mraz MergeDate: Mon Jun 8 20:19:02 2026 --- .../implementations/ciphers/cipher_aes_siv.c | 3 + test/evp_extra_test.c | 61 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/providers/implementations/ciphers/cipher_aes_siv.c b/providers/implementations/ciphers/cipher_aes_siv.c index cd4beddd55..eb0f6680bd 100644 --- a/providers/implementations/ciphers/cipher_aes_siv.c +++ b/providers/implementations/ciphers/cipher_aes_siv.c @@ -201,6 +201,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 (params == NULL) return 1; @@ -235,6 +236,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; } diff --git a/test/evp_extra_test.c b/test/evp_extra_test.c index 866edf36d0..fdd8a2ef56 100644 --- a/test/evp_extra_test.c +++ b/test/evp_extra_test.c @@ -5701,6 +5701,64 @@ static int test_aes_rc4_keylen_change_cve_2023_5363(void) } #endif +/* + * 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; @@ -6197,6 +6255,9 @@ int setup_tests(void) ADD_ALL_TESTS(test_aead_oneshot_roundtrip, 2 * OSSL_NELEM(aead_oneshot_cfgs)); + /* Test case for CVE-2026-45446 */ + ADD_TEST(test_aes_siv_ctx_reuse); + ADD_TEST(test_invalid_ctx_for_digest); ADD_TEST(test_evp_cipher_negative_length); -- 2.55.0