Commit a4ee6965a0 for openssl.org

commit a4ee6965a09273425af35cabfefa08862c388fc3
Author: Mounir IDRASSI <mounir.idrassi@idrix.fr>
Date:   Sun Jun 28 07:50:14 2026 +0900

    Reject AES-XTS operations without an IV

    Commit 774525b38bd4 moved AES-XTS to an implementation-specific cipher
    function but did not carry over the generic iv_set guard. This allowed
    AES-XTS operations initialized with a NULL IV to proceed.

    Restore the missing iv_set check before processing input and add an
    evp_extra_test regression covering both a valid-IV control and the
    missing-IV failure case.

    Fixes #31755

    Reviewed-by: Simo Sorce <simo@redhat.com>
    Reviewed-by: Paul Dale <paul.dale@oracle.com>
    MergeDate: Wed Jul  8 18:16:55 2026
    (Merged from https://github.com/openssl/openssl/pull/31756)

diff --git a/providers/implementations/ciphers/cipher_aes_xts.c b/providers/implementations/ciphers/cipher_aes_xts.c
index 9be87374d1..54820469a9 100644
--- a/providers/implementations/ciphers/cipher_aes_xts.c
+++ b/providers/implementations/ciphers/cipher_aes_xts.c
@@ -180,7 +180,8 @@ static int aes_xts_cipher(void *vctx, unsigned char *out, size_t *outl,
     } else
 #endif
     {
-        if (ctx->xts.key1 == NULL || ctx->xts.key2 == NULL)
+        if (ctx->xts.key1 == NULL || ctx->xts.key2 == NULL
+            || !ctx->base.iv_set)
             return 0;
     }

diff --git a/test/evp_extra_test.c b/test/evp_extra_test.c
index e1b80256bd..7c8059350a 100644
--- a/test/evp_extra_test.c
+++ b/test/evp_extra_test.c
@@ -7901,6 +7901,70 @@ end:
     return ret;
 }

+static int test_aes_xts_rejects_missing_iv(void)
+{
+    static const unsigned char 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 in[32] = {
+        0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+        0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
+        0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
+        0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00
+    };
+    static const unsigned char iv[16] = {
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02
+    };
+    EVP_CIPHER_CTX *ctx = NULL;
+    EVP_CIPHER *cipher = NULL;
+    unsigned char out[sizeof(in)];
+    int outl = 0;
+    int ret = 0;
+
+    if ((cipher = EVP_CIPHER_fetch(testctx, "AES-128-XTS", testpropq)) == NULL)
+        return TEST_skip("AES-128-XTS cipher is not available");
+
+    /* Initialize with a valid IV as a positive control */
+    if (!TEST_ptr(ctx = EVP_CIPHER_CTX_new())
+        || !TEST_true(EVP_EncryptInit_ex2(ctx, cipher, key, iv, NULL))
+        || !TEST_true(EVP_EncryptUpdate(ctx, out, &outl, in, sizeof(in)))
+        || !TEST_int_eq(outl, (int)sizeof(in)))
+        goto err;
+
+    EVP_CIPHER_CTX_free(ctx);
+    ctx = NULL;
+    outl = 0;
+
+    if (!TEST_ptr(ctx = EVP_CIPHER_CTX_new()))
+        goto err;
+
+    /* Initialize with a NULL IV, which may fail immediately */
+    ERR_set_mark();
+    if (!EVP_EncryptInit_ex2(ctx, cipher, key, NULL, NULL)) {
+        ERR_pop_to_mark();
+        ret = 1;
+        goto err;
+    }
+
+    /* Test EVP_EncryptUpdate after NULL IV initialization, which should fail */
+    if (!TEST_false(EVP_EncryptUpdate(ctx, out, &outl, in, sizeof(in)))) {
+        ERR_clear_last_mark();
+        goto err;
+    }
+    ERR_pop_to_mark();
+
+    ret = 1;
+
+err:
+    EVP_CIPHER_free(cipher);
+    EVP_CIPHER_CTX_free(ctx);
+    return ret;
+}
+
 /*
  * Cross-driver round-trip test for AEAD one-shot vs streaming paths.
  *
@@ -9098,6 +9162,7 @@ int setup_tests(void)
     ADD_TEST(test_invalid_ctx_for_digest);

     ADD_TEST(test_evp_cipher_negative_length);
+    ADD_TEST(test_aes_xts_rejects_missing_iv);

     ADD_TEST(test_evp_cipher_pipeline);