]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
BUG/MEDIUM: ssl: require a full-length AEAD tag when decrypting with AES-GCM master
authorRemi Tricot-Le Breton <rlebreton@haproxy.com>
Thu, 6 Aug 2026 07:34:24 +0000 (09:34 +0200)
committerWilly Tarreau <w@1wt.eu>
Fri, 7 Aug 2026 08:50:53 +0000 (10:50 +0200)
aes_process() passes the caller-provided tag length straight to
EVP_CTRL_AEAD_SET_TAG, and OpenSSL accepts and verifies GCM tags as short
as one byte. In the "aes_gcm_dec" converter the tag argument is documented
as coming from a variable, which in practice is populated from request
data, and in the JWE AES-GCM key-wrap path it comes directly from the
token. In both cases the party submitting the ciphertext also chooses how
many bytes of the authentication tag get checked.

An attacker submitting a one-byte tag therefore only needs about 256
attempts, instead of 2^128, to have arbitrary ciphertext accepted as
authentic. For a configuration relying on these converters to validate a
signed or encrypted token, this is a full authentication bypass.

The encrypt side always emits a 16-byte tag, so let's simply require
exactly that on the decrypt side.

This has been there since these converters were introduced, the shared
helper coming from commit f0e64de75 ("MINOR: ssl: Factorize AES GCM data
processing") in 3.4. It must be backported to all stable versions
providing "aes_gcm_dec".

Reported-by: Claude (ANT-2026-15HD08AS)
src/ssl_sample.c

index 378ffabb9a0c33d3d1e0e9bac9cdbc1ff41cc524..62088db3a040fdc51f53b75c9665d8759ae84f30 100644 (file)
@@ -407,6 +407,12 @@ int aes_process(struct buffer *data, struct buffer *nonce, struct buffer *key, i
        size = out->data;
 
        if (decrypt && gcm) {
+               /* the tag length is provided by the caller, hence often by the
+                * input itself; OpenSSL happily verifies tags as short as one
+                * byte, so require the full length that the encrypt path emits.
+                */
+               if (b_data(aead_tag) != 16)
+                       goto err;
                if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, b_data(aead_tag), b_orig(aead_tag)))
                        goto err;
        }