[PATCH 4/7] cipher-ocb: fix split-AAD abort at L-table wrap boundary

Jussi Kivilinna jussi.kivilinna at iki.fi
Fri Jul 24 20:48:03 CEST 2026


* cipher/cipher-ocb.c (_gcry_cipher_ocb_authenticate): Pass aad_nblocks
to ocb_get_L_big in buffered-block path, not aad_nblocks + 1.
* tests/basic.c (ocb_aad_tag, check_ocb_cipher_aad_tablewrap): New.
(check_ocb_cipher): Call check_ocb_cipher_aad_tablewrap.
--

Buffered-AAD path incremented aad_nblocks and then, at each L-table
wrap (every 65536 blocks), asked ocb_get_L_big for aad_nblocks + 1.
That helper needs a wrap-aligned block number, so the off-by-one hits
"Assertion `ntz >= 16' failed" and aborts whenever split AAD finishes
the wrap block through buffered path. Full-blocks path already passes
the correct count.

New test feeds about 1 MiB of AAD split so the wrap block finishes in
buffered path, comparing tag against one-shot.

Reported-by: JEAN Jeremy <Jeremy.Jean at ssi.gouv.fr>
Signed-off-by: Jussi Kivilinna <jussi.kivilinna at iki.fi>
---
 cipher/cipher-ocb.c |  2 +-
 tests/basic.c       | 60 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 61 insertions(+), 1 deletion(-)

diff --git a/cipher/cipher-ocb.c b/cipher/cipher-ocb.c
index 05e56f44..8d4f576e 100644
--- a/cipher/cipher-ocb.c
+++ b/cipher/cipher-ocb.c
@@ -281,7 +281,7 @@ _gcry_cipher_ocb_authenticate (gcry_cipher_hd_t c, const unsigned char *abuf,
           if ((c->u_mode.ocb.aad_nblocks % table_maxblks) == 0)
             {
               /* Table overflow, L needs to be generated. */
-              ocb_get_L_big(c, c->u_mode.ocb.aad_nblocks + 1, l_tmp);
+              ocb_get_L_big(c, c->u_mode.ocb.aad_nblocks, l_tmp);
             }
           else
             {
diff --git a/tests/basic.c b/tests/basic.c
index 0dceebd7..1f4273ea 100644
--- a/tests/basic.c
+++ b/tests/basic.c
@@ -9474,6 +9474,65 @@ check_ocb_cipher_largebuf (int algo, int keylen, const char *tagexpect)
 }
 
 
+static void
+ocb_aad_tag (const unsigned char key[16], const unsigned char nonce[12],
+             const unsigned char *aad, size_t len0, size_t len1,
+             unsigned char tag[16])
+{
+  gcry_cipher_hd_t hd;
+  gcry_error_t err;
+  unsigned char out[16];
+
+  err = gcry_cipher_open (&hd, GCRY_CIPHER_AES, GCRY_CIPHER_MODE_OCB, 0);
+  if (!err)
+    err = gcry_cipher_setkey (hd, key, 16);
+  if (!err)
+    err = gcry_cipher_setiv (hd, nonce, 12);
+  if (!err)
+    err = gcry_cipher_authenticate (hd, aad, len0);
+  if (!err && len1)
+    err = gcry_cipher_authenticate (hd, aad + len0, len1);
+  if (!err)
+    err = gcry_cipher_final (hd);
+  if (!err)
+    err = gcry_cipher_encrypt (hd, out, sizeof out, aad, sizeof out);
+  if (!err)
+    err = gcry_cipher_gettag (hd, tag, 16);
+  gcry_cipher_close (hd);
+  if (err)
+    fail ("ocb aad tablewrap: %s\n", gpg_strerror (err));
+}
+
+
+static void
+check_ocb_cipher_aad_tablewrap (void)
+{
+  static const unsigned char key[16] _GCRY_GCC_ATTR_NONSTRING =
+    "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f";
+  static const unsigned char nonce[12] _GCRY_GCC_ATTR_NONSTRING =
+    "\xbb\xaa\x99\x88\x77\x66\x55\x44\x33\x22\x11\x0d";
+  const size_t aadlen = (size_t)65536 * 16;   /* fills one OCB L-table wrap */
+  unsigned char tag_ref[16], tag_split[16];
+  unsigned char *aad;
+  size_t i;
+
+  if (verbose)
+    fprintf (stderr, "  checking OCB AAD split across L-table boundary\n");
+
+  aad = xmalloc (aadlen);
+  for (i = 0; i < aadlen; i++)
+    aad[i] = (unsigned char)i;
+
+  ocb_aad_tag (key, nonce, aad, aadlen, 0, tag_ref);
+  ocb_aad_tag (key, nonce, aad, aadlen - 15, 15, tag_split);
+
+  if (memcmp (tag_ref, tag_split, 16))
+    fail ("ocb aad tablewrap: split tag mismatch\n");
+
+  xfree (aad);
+}
+
+
 static void
 check_ocb_cipher_splitaad (void)
 {
@@ -9766,6 +9825,7 @@ check_ocb_cipher (void)
 
   /* Check that the AAD data is correctly buffered.  */
   check_ocb_cipher_splitaad ();
+  check_ocb_cipher_aad_tablewrap ();
 }
 
 
-- 
2.53.0




More information about the Gcrypt-devel mailing list