Skip to content

Commit eec5e9b

Browse files
beldmitt8m
authored andcommitted
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 Reviewed-by: Neil Horman <nhorman@openssl.org> Reviewed-by: Eugene Syromiatnikov <esyr@openssl.org> Reviewed-by: Tomas Mraz <tomas@openssl.foundation> MergeDate: Mon Jun 8 20:12:25 2026 (cherry picked from commit aeb5000b0c66772bad600e9cc241a4c902e6d8b9)
1 parent 787a6df commit eec5e9b

3 files changed

Lines changed: 159 additions & 11 deletions

File tree

‎providers/implementations/ciphers/cipher_aes_gcm_siv_hw.c‎

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ static int aes_gcm_siv_initkey(void *vctx)
5858
memset(&data, 0, sizeof(data));
5959
memcpy(&data.block[sizeof(data.counter)], ctx->nonce, NONCE_SIZE);
6060

61+
ctx->generated_tag = 0;
62+
memset(ctx->tag, 0, TAG_SIZE);
63+
6164
/* msg_auth_key is always 16 bytes in size, regardless of AES128/AES256 */
6265
/* counter is stored little-endian */
6366
for (i = 0; i < BLOCK_SIZE; i += 8) {
@@ -134,17 +137,6 @@ static int aes_gcm_siv_aad(PROV_AES_GCM_SIV_CTX *ctx,
134137
return 1;
135138
}
136139

137-
static int aes_gcm_siv_finish(PROV_AES_GCM_SIV_CTX *ctx)
138-
{
139-
int ret = 0;
140-
141-
if (ctx->enc)
142-
return ctx->generated_tag;
143-
ret = !CRYPTO_memcmp(ctx->tag, ctx->user_tag, sizeof(ctx->tag));
144-
ret &= ctx->have_user_tag;
145-
return ret;
146-
}
147-
148140
static int aes_gcm_siv_encrypt(PROV_AES_GCM_SIV_CTX *ctx, const unsigned char *in,
149141
unsigned char *out, size_t len)
150142
{
@@ -271,6 +263,19 @@ static int aes_gcm_siv_decrypt(PROV_AES_GCM_SIV_CTX *ctx, const unsigned char *i
271263
return !error;
272264
}
273265

266+
static int aes_gcm_siv_finish(PROV_AES_GCM_SIV_CTX *ctx)
267+
{
268+
int ret = 0;
269+
270+
if (ctx->enc)
271+
return ctx->generated_tag;
272+
if (!ctx->generated_tag)
273+
aes_gcm_siv_decrypt(ctx, NULL, NULL, 0);
274+
ret = !CRYPTO_memcmp(ctx->tag, ctx->user_tag, sizeof(ctx->tag));
275+
ret &= ctx->have_user_tag;
276+
return ret;
277+
}
278+
274279
static int aes_gcm_siv_cipher(void *vctx, unsigned char *out,
275280
const unsigned char *in, size_t len)
276281
{

‎providers/implementations/ciphers/cipher_aes_siv.c‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ static int aes_siv_set_ctx_params(void *vctx, const OSSL_PARAM params[])
192192
PROV_AES_SIV_CTX *ctx = (PROV_AES_SIV_CTX *)vctx;
193193
const OSSL_PARAM *p;
194194
unsigned int speed = 0;
195+
SIV128_CONTEXT *sctx = &ctx->siv;
195196

196197
if (ossl_param_is_empty(params))
197198
return 1;
@@ -226,6 +227,8 @@ static int aes_siv_set_ctx_params(void *vctx, const OSSL_PARAM params[])
226227
if (keylen != ctx->keylen)
227228
return 0;
228229
}
230+
sctx->final_ret = -1;
231+
229232
return 1;
230233
}
231234

‎test/evp_extra_test.c‎

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6676,6 +6676,142 @@ static int test_aes_rc4_keylen_change_cve_2023_5363(void)
66766676
}
66776677
#endif
66786678

6679+
static int test_aes_gcm_siv_empty_data(void)
6680+
{
6681+
unsigned char key[16] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
6682+
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 };
6683+
unsigned char nonce[12] = { 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11,
6684+
0x22, 0x33, 0x44, 0x55 };
6685+
unsigned char aad[33] = "this AAD was never authenticated";
6686+
unsigned char zero_tag[16] = { 0 };
6687+
unsigned char real_tag[16];
6688+
unsigned char out[16];
6689+
int outl, ret = 0;
6690+
EVP_CIPHER_CTX *ctx = NULL;
6691+
EVP_CIPHER *c = EVP_CIPHER_fetch(NULL, "AES-128-GCM-SIV", NULL);
6692+
6693+
if (c == NULL) {
6694+
return TEST_skip("AES-128-GCM-SIV cipher is not available");
6695+
}
6696+
6697+
/* Compute the CORRECT tag for (key,nonce,aad,pt="") via encrypt */
6698+
ctx = EVP_CIPHER_CTX_new();
6699+
if (!TEST_ptr(ctx)
6700+
|| !TEST_true(EVP_EncryptInit_ex2(ctx, c, key, nonce, NULL))
6701+
|| !TEST_true(EVP_EncryptUpdate(ctx, NULL, &outl, aad, sizeof(aad))) /* AAD */
6702+
|| !TEST_true(EVP_EncryptUpdate(ctx, out, &outl, aad, 0)) /* empty PT, out!=NULL */
6703+
|| !TEST_true(EVP_EncryptFinal_ex(ctx, out, &outl))
6704+
|| !TEST_true(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, real_tag)))
6705+
goto err;
6706+
EVP_CIPHER_CTX_free(ctx);
6707+
6708+
/* SANITY: decrypt with CORRECT tag and an explicit empty-PT Update */
6709+
ctx = EVP_CIPHER_CTX_new();
6710+
if (!TEST_ptr(ctx)
6711+
|| !TEST_true(EVP_DecryptInit_ex2(ctx, c, key, nonce, NULL))
6712+
|| !TEST_true(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, real_tag))
6713+
|| !TEST_true(EVP_DecryptUpdate(ctx, NULL, &outl, aad, sizeof(aad)))
6714+
|| !TEST_true(EVP_DecryptUpdate(ctx, out, &outl, aad, 0)) /* force aes_gcm_siv_decrypt(len=0) */
6715+
|| !TEST_true(EVP_DecryptFinal_ex(ctx, out, &outl)))
6716+
goto err;
6717+
EVP_CIPHER_CTX_free(ctx);
6718+
6719+
/* FORGERY A: AAD only, NO ciphertext Update, ALL-ZERO tag */
6720+
ctx = EVP_CIPHER_CTX_new();
6721+
if (!TEST_ptr(ctx)
6722+
|| !TEST_true(EVP_DecryptInit_ex2(ctx, c, key, nonce, NULL))
6723+
|| !TEST_true(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, zero_tag))
6724+
|| !TEST_true(EVP_DecryptUpdate(ctx, NULL, &outl, aad, sizeof(aad))) /* AAD only, out==NULL */
6725+
|| !TEST_false(EVP_DecryptFinal_ex(ctx, out, &outl)))
6726+
goto err;
6727+
EVP_CIPHER_CTX_free(ctx);
6728+
6729+
/* FORGERY B: no AAD, no Update at all, ALL-ZERO tag */
6730+
ctx = EVP_CIPHER_CTX_new();
6731+
if (!TEST_ptr(ctx)
6732+
|| !TEST_true(EVP_DecryptInit_ex2(ctx, c, key, nonce, NULL))
6733+
|| !TEST_true(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, zero_tag))
6734+
|| !TEST_false(EVP_DecryptFinal_ex(ctx, out, &outl)))
6735+
goto err;
6736+
EVP_CIPHER_CTX_free(ctx);
6737+
6738+
/* CONTROL: AAD only, NO ciphertext Update, CORRECT tag */
6739+
ctx = EVP_CIPHER_CTX_new();
6740+
if (!TEST_ptr(ctx)
6741+
|| !TEST_true(EVP_DecryptInit_ex2(ctx, c, key, nonce, NULL))
6742+
|| !TEST_true(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, real_tag))
6743+
|| !TEST_true(EVP_DecryptUpdate(ctx, NULL, &outl, aad, sizeof(aad)))
6744+
|| !TEST_true(EVP_DecryptFinal_ex(ctx, out, &outl)))
6745+
goto err;
6746+
EVP_CIPHER_CTX_free(ctx);
6747+
ctx = NULL;
6748+
6749+
ret = 1;
6750+
err:
6751+
EVP_CIPHER_CTX_free(ctx);
6752+
6753+
EVP_CIPHER_free(c);
6754+
return ret;
6755+
}
6756+
6757+
/*
6758+
* AES-SIV reuse-without-rekey:
6759+
* msg1: legit non-empty CT, tag verifies, final_ret=0
6760+
* msg2: no reinit (or reinit with key=NULL), set forged tag,
6761+
* AAD only, DecryptFinal -> does stale final_ret leak through?
6762+
*/
6763+
static int test_aes_siv_ctx_reuse(void)
6764+
{
6765+
unsigned char key[32] = { 7 }; /* AES-128-SIV => 2*16 */
6766+
unsigned char pt[9] = "payload!";
6767+
unsigned char ct[9], tagbuf[16], out[16], zero16[16] = { 0 };
6768+
unsigned char aad[14] = "forged header";
6769+
int outl, ret = 0;
6770+
EVP_CIPHER_CTX *e = NULL, *d = NULL;
6771+
EVP_CIPHER *c = EVP_CIPHER_fetch(NULL, "AES-128-SIV", NULL);
6772+
6773+
if (c == NULL) {
6774+
return TEST_skip("AES-128-SIV cipher is not available");
6775+
}
6776+
6777+
/* produce a valid (ct,tag) for msg1 */
6778+
e = EVP_CIPHER_CTX_new();
6779+
if (!TEST_ptr(e)
6780+
|| !TEST_true(EVP_EncryptInit_ex2(e, c, key, NULL, NULL))
6781+
|| !TEST_true(EVP_EncryptUpdate(e, NULL, &outl, (unsigned char *)"hdr1", 4))
6782+
|| !TEST_true(EVP_EncryptUpdate(e, ct, &outl, pt, sizeof(pt)))
6783+
|| !TEST_true(EVP_EncryptFinal_ex(e, out, &outl))
6784+
|| !TEST_true(EVP_CIPHER_CTX_ctrl(e, EVP_CTRL_AEAD_GET_TAG, 16, tagbuf))) {
6785+
EVP_CIPHER_CTX_free(e);
6786+
goto err;
6787+
}
6788+
EVP_CIPHER_CTX_free(e);
6789+
6790+
/* msg1 decrypt */
6791+
d = EVP_CIPHER_CTX_new();
6792+
if (!TEST_ptr(d)
6793+
|| !TEST_true(EVP_DecryptInit_ex2(d, c, key, NULL, NULL))
6794+
|| !TEST_true(EVP_CIPHER_CTX_ctrl(d, EVP_CTRL_AEAD_SET_TAG, 16, tagbuf))
6795+
|| !TEST_true(EVP_DecryptUpdate(d, NULL, &outl, (unsigned char *)"hdr1", 4))
6796+
|| !TEST_true(EVP_DecryptUpdate(d, out, &outl, ct, sizeof(ct)))
6797+
|| !TEST_true(EVP_DecryptFinal_ex(d, out, &outl)))
6798+
goto err;
6799+
6800+
/* msg2 on SAME ctx, reinit with key=NULL => initkey skipped, final_ret should be reset */
6801+
if (!TEST_true(EVP_DecryptInit_ex2(d, NULL, NULL, NULL, NULL))
6802+
|| !TEST_true(EVP_CIPHER_CTX_ctrl(d, EVP_CTRL_AEAD_SET_TAG, 16, zero16))
6803+
|| !TEST_true(EVP_DecryptUpdate(d, NULL, &outl, aad, sizeof(aad))) /* forged AAD */
6804+
|| !TEST_false(EVP_DecryptFinal_ex(d, out, &outl)))
6805+
goto err;
6806+
6807+
ret = 1;
6808+
6809+
err:
6810+
EVP_CIPHER_CTX_free(d);
6811+
EVP_CIPHER_free(c);
6812+
return ret;
6813+
}
6814+
66796815
static int test_invalid_ctx_for_digest(void)
66806816
{
66816817
int ret;
@@ -7469,6 +7605,10 @@ int setup_tests(void)
74697605

74707606
ADD_ALL_TESTS(test_aead_oneshot_roundtrip, 2 * OSSL_NELEM(aead_oneshot_cfgs));
74717607

7608+
/* Test cases for CVE-2026-45446 */
7609+
ADD_TEST(test_aes_gcm_siv_empty_data);
7610+
ADD_TEST(test_aes_siv_ctx_reuse);
7611+
74727612
ADD_TEST(test_invalid_ctx_for_digest);
74737613

74747614
ADD_TEST(test_evp_cipher_negative_length);

0 commit comments

Comments
 (0)