Commit 4bf85819b7 for openssl.org

commit 4bf85819b7cba298108ea03f3e2aa067f20ded72
Author: Billy Brumley <bbb@iki.fi>
Date:   Tue Jul 14 01:45:04 2026 -0400

    [providers/implementations/ciphers] GCM-SIV: reject out-of-order update calls

    For GCM-SIV:

    1. AAD must precede the payload
    2. the payload must be single shot

    (2) was already happening, this change moves from a silent fail to
    an explicit error message for multiple update calls on the payload.

    For (1), this change unifies the logic for (2) one level up in the wrapper.
    So the code previously allowed (1), and now errors out after this change.

    Follow-up to #31906

    Assisted-by: Claude:claude-opus-4-8

    Reviewed-by: Nikola Pajkovsky <nikolap@openssl.org>
    Reviewed-by: Daniel Kubec <kubec@openssl.foundation>
    Reviewed-by: Bob Beck <beck@openssl.org>
    MergeDate: Mon Jul 20 06:56:04 2026
    (Merged from https://github.com/openssl/openssl/pull/31940)

diff --git a/doc/man7/EVP_CIPHER-AES.pod b/doc/man7/EVP_CIPHER-AES.pod
index 6da3f96a2d..0cb6d20829 100644
--- a/doc/man7/EVP_CIPHER-AES.pod
+++ b/doc/man7/EVP_CIPHER-AES.pod
@@ -65,9 +65,10 @@ L<EVP_EncryptInit(3)/PARAMETERS>.

 =head1 NOTES

-The AES-SIV and AES-WRAP mode implementations do not support streaming. That
-means to obtain correct results there can be only one L<EVP_EncryptUpdate(3)>
-or L<EVP_DecryptUpdate(3)> call after the initialization of the context.
+The AES-SIV, AES-WRAP, and GCM-SIV mode implementations do not support
+streaming. That means to obtain correct results there can be only one
+L<EVP_EncryptUpdate(3)> or L<EVP_DecryptUpdate(3)> call on the payload after
+the initialization of the context.

 When wrapping with AES-WRAP-PAD ciphers, the output buffer must be at least
 I<inl> rounded up to the cipher block size (8 bytes) plus the block size.
diff --git a/providers/implementations/ciphers/cipher_aes_gcm_siv_hw.c b/providers/implementations/ciphers/cipher_aes_gcm_siv_hw.c
index 452c9f00fe..9622c2dcca 100644
--- a/providers/implementations/ciphers/cipher_aes_gcm_siv_hw.c
+++ b/providers/implementations/ciphers/cipher_aes_gcm_siv_hw.c
@@ -15,6 +15,7 @@
 #include "internal/deprecated.h"

 #include <openssl/evp.h>
+#include <openssl/proverr.h>
 #include <internal/endian.h>
 #include <prov/implementations.h>
 #include "cipher_aes_gcm_siv.h"
@@ -151,8 +152,6 @@ static int aes_gcm_siv_encrypt(PROV_AES_GCM_SIV_CTX *ctx, const unsigned char *i
     DECLARE_IS_ENDIAN;

     ctx->generated_tag = 0;
-    if (!ctx->speed && ctx->used_enc)
-        return 0;
     /* need to check the size of the input! */
     if (len64 > ((int64_t)1 << 36))
         return 0;
@@ -212,8 +211,6 @@ static int aes_gcm_siv_decrypt(PROV_AES_GCM_SIV_CTX *ctx, const unsigned char *i
     DECLARE_IS_ENDIAN;

     ctx->generated_tag = 0;
-    if (!ctx->speed && ctx->used_dec)
-        return 0;
     /* need to check the size of the input! */
     if (len64 > ((int64_t)1 << 36))
         return 0;
@@ -285,6 +282,18 @@ static int aes_gcm_siv_cipher(void *vctx, unsigned char *out,
     if (in == NULL)
         return aes_gcm_siv_finish(ctx);

+    /*
+     * SIV derives the CTR IV from the tag, which depends on the whole plaintext,
+     * so the payload cannot be streamed.
+     * Payload must arrive in a single update, after which the tag is fixed.
+     * Any later AAD or payload update is therefore out of order and errors out.
+     * The speed benchmark test is exempt.
+     */
+    if (!ctx->speed && (ctx->used_enc || ctx->used_dec)) {
+        ERR_raise(ERR_LIB_PROV, PROV_R_UPDATE_CALL_OUT_OF_ORDER);
+        return 0;
+    }
+
     /* Deal with associated data */
     if (out == NULL)
         return aes_gcm_siv_aad(ctx, in, len);
diff --git a/test/evp_extra_test.c b/test/evp_extra_test.c
index 5c37102088..22e4689982 100644
--- a/test/evp_extra_test.c
+++ b/test/evp_extra_test.c
@@ -6144,7 +6144,6 @@ static int test_evp_aead_late_aad(int idx)
         || info->mode == EVP_CIPH_GCM_MODE /* rejects, raises 102 PROV_R_CIPHER_OPERATION_FAILED */
         || info->mode == EVP_CIPH_CCM_MODE /* fails at first AAD */
         || info->mode == EVP_CIPH_OCB_MODE /* accepts late AAD */
-        || info->mode == EVP_CIPH_GCM_SIV_MODE /* accepts late AAD */
         /* skip TLS stitched MTE cipher */
         || EVP_CIPHER_is_a(info->ciph, "AES-128-CBC-HMAC-SHA1")
         /* skip TLS stitched MTE cipher */